> 文章列表 > 【Linux】基础IO(系统文件I/O Open write 文件描述符fd 什么是当前路径? 重新谈论文件 文件操作)

【Linux】基础IO(系统文件I/O Open write 文件描述符fd 什么是当前路径? 重新谈论文件 文件操作)

【Linux】基础IO(系统文件I/O Open write 文件描述符fd 什么是当前路径? 重新谈论文件 文件操作)

文章目录

    • 什么是当前路径?
    • 重新谈论文件
      • 文件操作
    • 系统文件I/O
      • Open
      • write
    • 文件描述符fd

什么是当前路径?

编写代码并运行:

  1 #include <stdio.h>2 #include <unistd.h>3 4 int main()5 {6   while(1)7   {8     sleep(1);                                                         9     printf("我是一个进程: %d\\n",getpid());10   }11   return 0;12 }

【Linux】基础IO(系统文件I/O Open write 文件描述符fd 什么是当前路径? 重新谈论文件 文件操作)

【Linux】基础IO(系统文件I/O Open write 文件描述符fd 什么是当前路径? 重新谈论文件 文件操作)
exe代表的是当前进程执行的是磁盘的可执行程序。

cwd叫做当前进程的current working directory,表示当前进程所在的工作目录。

系统中有一个可以更改目录的方法,chdir,谁调用chdir,就更改谁的当前工作目录。
【Linux】基础IO(系统文件I/O Open write 文件描述符fd 什么是当前路径? 重新谈论文件 文件操作)
原目录:

[likaixuan1@VM-4-13-centos 20230404-基础IO]$ pwd
/home/likaixuan1/106/20230404-基础IO
[likaixuan1@VM-4-13-centos 20230404-基础IO]$ 

改改代码:

  1 #include <stdio.h>2 #include <unistd.h>3 4 int main()5 {6  chdir("/home/likaixuan1");                                            7   while(1)                     8   {          9     sleep(1);10     printf("我是一个进程: %d\\n",getpid());11   }          12   return 0;  13 }    

编译运行:
【Linux】基础IO(系统文件I/O Open write 文件描述符fd 什么是当前路径? 重新谈论文件 文件操作)

输入指令:[likaixuan1@VM-4-13-centos 20230404-基础IO]$ ls /proc/32714 -al

【Linux】基础IO(系统文件I/O Open write 文件描述符fd 什么是当前路径? 重新谈论文件 文件操作)
当前进程的cwd变成了/home/likaixuan1,当前工作目录被修改了。

补充问题:
为什么我们自己写的shell,cd的时候,路径没有变化?

fork创建子进程,子进程有自己的工作目录,执行cd更改的是子进程的目录,子进程执行完毕继续用的是父进程即shell,子进程改了和父进程没有关系,我们看到的是父进程。

重新谈论文件

  1. 空文件也要在磁盘中占空间。
  2. 文件 = 内容 + 属性。
  3. 文件操作 = 对内容 + 对属性 or 对内容和属性。
  4. 标定一个文件,必须使用:文件路径 + 文件名。(唯一性)
  5. 如果没有指明对应的文件路径,默认是在当前路径进行文件访问。
  6. 当我们把fopen、fclose、fread、fwrite等接口写完之后,代码编译之后,形成二进制可执行程序之后,但是没运行,文件对应的操作没有执行。对文件的操作本质是:进程对文件的操作
  7. 一个文件没有被打开,不可以直接执行文件访问。(一个文件要被访问,就必须先被打开)被用户进程(调用接口)和操作系统OS(实际的打开文件)打开。还有就是不是所有的磁盘文件都被打开。文件在应用层面可以分两类:1.被打开的文件。2.没有被打开的文件。所以进一步得出结论,文件操作的本质:进程和被打开文件的关系

文件操作

文件在磁盘,磁盘是硬件,只有操作系统可以管理,那么所有的人想要访问磁盘就不能跨过操作系统,必须得使用操作系统提供的文件级别的系统调用的接口。操作系统只有一个,所以上层语言无论如何变化,a.库函数底层必须调用系统调用接口。b.库函数可以千变万化,但底层不变。

r+:读写(不存在就出错)。
w+:读写(不存在就创建)。
a:追加(append),写在文件末尾。

  1 #include <stdio.h>2 #include <unistd.h>3 4 5 #define FILE_NAME "log.txt"6 int main()7 {8   FILE *fp = fopen(FILE_NAME,"w");9   if(NULL == fp)10   {11     perror("fopen");12     return 1;13   }14 15   fclose(fp);                                                                16 17 }

编译运行,我们发现创建了log.txt.文件。
运行前:
【Linux】基础IO(系统文件I/O Open write 文件描述符fd 什么是当前路径? 重新谈论文件 文件操作)
运行后:

【Linux】基础IO(系统文件I/O Open write 文件描述符fd 什么是当前路径? 重新谈论文件 文件操作)

此时文件大小为0,也就证明了w就是写入,但是文件不存在自动会创建。
现在我们想往里面写入5句话,用fprintf()接口。

  1 #include <stdio.h>2 #include <unistd.h>3 4 5 #define FILE_NAME "log.txt"6 int main()7 {8   FILE *fp = fopen(FILE_NAME,"w");9   if(NULL == fp)10   {11     perror("fopen");12     return 1;13   }14 15   int cnt = 5;16   while(cnt)17   {18     fprintf(fp,"%s:%d\\n","hello                             19 }      

运行然后cat,我们就看到文件内容写到文件里了。
【Linux】基础IO(系统文件I/O Open write 文件描述符fd 什么是当前路径? 重新谈论文件 文件操作)

我们也可以以r方式打开文件,这个叫做读取,

修改代码:

    1 #include <stdio.h>2 #include <unistd.h>                                                     3 4 5 #define FILE_NAME "log.txt"6 int main()7 {                                                                       8   FILE *fp = fopen(FILE_NAME,"w");9   if(NULL == fp)10   {11     perror("fopen");12     return 1;13   }                      14 15   char buffer[64];                            16   while(fgets(buffer,sizeof(buffer)-1,fp) != NULL)17   {18     puts(buffer);//把读的字符串,显示到显示器上。                           19   }                                20                                                         21   fclose(fp);                                   22 }   

我们使用fgets,表示以行为单位,从特定的文件当中读取数据,将读到的数据放到s所指向的缓冲区的当中。
【Linux】基础IO(系统文件I/O Open write 文件描述符fd 什么是当前路径? 重新谈论文件 文件操作)
运行程序,就可以按照文本行的方式把文件读上去了:
【Linux】基础IO(系统文件I/O Open write 文件描述符fd 什么是当前路径? 重新谈论文件 文件操作)

我们以a方式打开,继续修改代码:

  1 #include <stdio.h>  2 #include <unistd.h>  5 #define FILE_NAME "log.txt"  6 int main()  7 {  10   FILE *fp = fopen(FILE_NAME,"a");11   if(NULL == fp)12   {13     perror("fopen");14     return 1;15   }23   int cnt = 5;24   while(cnt)25   {26     fprintf(fp,"%s:%d\\n","hello file",cnt--);27   }                                                                          28   fclose(fp);                     36 }                                

运行程序:

【Linux】基础IO(系统文件I/O Open write 文件描述符fd 什么是当前路径? 重新谈论文件 文件操作)

这就叫做追加。

注意细节:以w方式单纯打开文件,c语言会自动清空内部的数据

系统文件I/O

Open

操作文件,除了上述C接口(当然,C++也有接口,其他语言也有),我们还可以采用系统接口来进行文件访问。
我们在系统当中实际上用fopen打开文件底层调用的系统调用接口是open
【Linux】基础IO(系统文件I/O Open write 文件描述符fd 什么是当前路径? 重新谈论文件 文件操作)
头文件是:

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

我们先介绍:
int open(const char *pathname, int flags, mode_t mode);

我们创建文件时权限是什么由第三个参数mode_t mode告诉。第一个参数就是当前路径。

返回成功会返回给我们一个文件描述符file descriptor,失败了-1被设置,errno也会被设置。
【Linux】基础IO(系统文件I/O Open write 文件描述符fd 什么是当前路径? 重新谈论文件 文件操作)

第二个参数int flags,它有很多选项:
【Linux】基础IO(系统文件I/O Open write 文件描述符fd 什么是当前路径? 重新谈论文件 文件操作)

O_RDONLY…这些东西叫做宏。
我们一般用C传标记为,一个整数传一个标记位。我们知道一个整数有32个比特位,也就意味着我们可以通过比特位传递选项。

操作系统如何使用比特位传递选项?

一个比特位,一个选项,比特位位置不能重复。

#define ONE 0x1    
#define TWO 0x2    
#define THREE 0x4  
#define FOUR 0x8   

每一个宏对应的数值,只有一个比特位是1,彼此位置不重叠。
有的开源项目是这样写的:

#define ONE (1<<0)    
#define TWO (1<<1)    
#define THREE (1<<2)  
#define FOUR (1<<3)  

代码:

  1 #include <stdio.h>2 #include <unistd.h>3 #define FILE_NAME "log.txt"4 5 //每一个比特位对应的数值,只有一个比特位时1,彼此位置不重叠。6 #define ONE (1<<0)7 #define TWO (1<<1)8 #define THREE (1<<2)9 #define FOUR (1<<3)10 11 void show(int flags)12 {13   if(flags & ONE) printf("one\\n");14   if(flags & TWO) printf("two\\n");15   if(flags & THREE) printf("three\\n");16   if(flags & FOUR) printf("four\\n");17 }18 19 20 int main()21 { 22   show(ONE);23   printf("-------------------------\\n");24   show(TWO);25   printf("-------------------------\\n");26   show(ONE | TWO);27   printf("-------------------------\\n");28   show(ONE | TWO | THREE);29   printf("-------------------------\\n");30   show(ONE | TWO | THREE | FOUR); 31   printf("-------------------------\\n");     32 } 

编译运行:

【Linux】基础IO(系统文件I/O Open write 文件描述符fd 什么是当前路径? 重新谈论文件 文件操作)
上面就是标记位传参。

再次看:int open(const char *pathname, int flags, mode_t mode);
第一个参数是对应的路径起始就是我们要打开的文件。第二个是按标记位传参,O_RDONLY表示只读,O_WRONLY表示只写,O_RDWR表示读写。此时这就表示不同的标记位(宏),这些宏是通过不同的比特位来表示不同的含义的。
补充:O_TRUNC:表示对文件内容做清空。O_APPEND:表示追加。
【Linux】基础IO(系统文件I/O Open write 文件描述符fd 什么是当前路径? 重新谈论文件 文件操作)

下面我们看看int open(const char *pathname, int flags, mode_t mode);怎么用?

close是关闭文件描述符的。

代码:

 #include <stdio.h>#include <unistd.h>#include <sys/stat.h>#include <sys/types.h>#include <fcntl.h>#include <assert.h> #define FILE_NAME "log.txt"int main(){ int fd = open(FILE_NAME,O_WRONLY | O_CREAT,0666);  if(fd < 0){perror("open");return 1;}close(fd);}

编译运行:
运行前:
【Linux】基础IO(系统文件I/O Open write 文件描述符fd 什么是当前路径? 重新谈论文件 文件操作)
运行后:
【Linux】基础IO(系统文件I/O Open write 文件描述符fd 什么是当前路径? 重新谈论文件 文件操作)
此时log.txt的权限是664(-rw-rw-r–)和我们用C语言创建的一样。
因为umask默认是0002所以我们的权限最终是664
【Linux】基础IO(系统文件I/O Open write 文件描述符fd 什么是当前路径? 重新谈论文件 文件操作)
我们可以设置umask为0,默认权限就是666了。
【Linux】基础IO(系统文件I/O Open write 文件描述符fd 什么是当前路径? 重新谈论文件 文件操作)

此时权限就是(-rw-rw-rw-),需要注意的是我们改的是我们自己的文件权限并不影响shell。

write

向文件写入:

【Linux】基础IO(系统文件I/O Open write 文件描述符fd 什么是当前路径? 重新谈论文件 文件操作)

#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);

第一个参数fd就是我们想往哪个文件去写,第二个是我们想写的时候对应的缓冲区数据所在地,第三个是缓冲区当中字节个数。返回值是我们所写的字节数。

以前学C的时候我们了解到读写文件两种方案:文本类和二进制类,这里的文件读取分类是语言给我们提供的。传const void *操作系统看来都是二进制位。

【Linux】基础IO(系统文件I/O Open write 文件描述符fd 什么是当前路径? 重新谈论文件 文件操作)
sprintf是将特定的内容格式化,形成到字符串里面。

代码:

 #include <stdio.h>#include <unistd.h>#include <sys/stat.h>#include <sys/types.h>#include <fcntl.h>#include <assert.h>#include <string.h>#define FILE_NAME "log.txt"int main(){ umask(0);int fd = open(FILE_NAME,O_WRONLY | O_CREAT | O_TRUNC,0666);if(fd < 0){perror("open");return 1;}int cnt = 5;                                char outBuffer[64];                                                                      while(cnt)                                                                 {                                           sprintf(outBuffer, "%s:%d\\n","aaaa",cnt--);                                      write(fd,outBuffer,strlen(outBuffer));//向文件中写入string的时候 不用+1                                        close(fd);}

编译运行就完成了写入:
【Linux】基础IO(系统文件I/O Open write 文件描述符fd 什么是当前路径? 重新谈论文件 文件操作)
追加的话:需要使用O_APPEND
修改下面代码:

int fd = open(FILE_NAME,O_WRONLY | O_CREAT | O_APPEND,0666);

编译运行看下面实验结果:

【Linux】基础IO(系统文件I/O Open write 文件描述符fd 什么是当前路径? 重新谈论文件 文件操作)


想要读取文件:得使用O_RSONLY

【Linux】基础IO(系统文件I/O Open write 文件描述符fd 什么是当前路径? 重新谈论文件 文件操作)
read这个接口表示从一个文件描述符中读取文件,返回ssize_t(有符号整数,可以大于小于等于0)
ssize_t read(int fd, void *buf, size_t count); 从特定的文件(fd)中读到缓冲区里(buf),期望读count个。

代码:

 #include <stdio.h>#include <unistd.h>  #include <sys/stat.h> #include <sys/types.h>#include <fcntl.h> #include <assert.h>#include <string.h>                                           #define FILE_NAME "log.txt"                          int main(){         umask(0);int fd = open(FILE_NAME,O_RDONLY); if(fd < 0)                         {         perror("open");return 1;      }          char buffer[1024];ssize_t num = read(fd,buffer,sizeof(buffer)-1);if(num > 0)                                    {          buffer[num] = 0;printf("%s",buffer);}                     close(fd);}

此时就可以读出文件内容了。

如上,系统调用接口:open / close / write / read对应C语言是库函数接口:fopen / fclose / fwrite / fread

访问文件时必须使用系统调用接口,C库函数是封装了系统调用接口的。

文件描述符fd

进程可以打开多个文件,系统中一定会存在大量被打开的文件,被打开的文件要被操作系统管理。
如何管理?(先描述,再组织)
操作系统为了管理对应的文件,必定要为文件创建对应的内核数据结构标识文件,这个内核数据结构是struct file{},它包含了文件的大部分属性。

三个标准输入输出流:
stdin :键盘
stdout :显示器
stderr :显示器

FILE *fp = fopen();
FILE是一个结构体,我们底层访问文件时必须用系统调用,而系统调用接口访问文件时必须用文件描述符。所以这个结构体里必定有一个字段叫做文件描述符。

输出文件描述符:
【Linux】基础IO(系统文件I/O Open write 文件描述符fd 什么是当前路径? 重新谈论文件 文件操作)

【Linux】基础IO(系统文件I/O Open write 文件描述符fd 什么是当前路径? 重新谈论文件 文件操作)

我们自己打开的文件或文件描述符是从3开始的,原因就是0 1 2 默认被占用,我们C语言FILE类型指针也封装了操作系统内的文件描述符。

为什么数字是0 1 2开始的?

进程执行了一个接口,接口叫做open,系统在启动的时候默认启动了三个文件:键盘、显示器、显示器。操作系统要把log.txt加载到对应的内存里,那么首先并不是把内容直接加载到内存里,而是先描述这个文件,那么log.txt对应的结构叫做struct filestruct file{}保存的是文件的属性。PCB里有一个struct files_struct *file这样的一个指针,这个指针指向了一个属于进程的数据结构对象,叫做struct files_struct这个结构体是专门构建进程和文件对应关系的结构体,这个结构体里面包含了一个数组,这个数组的名字叫struct file* fd_array[]这是一个指针数组,数组里面所有的成员都是struct file*的指针,数组就有下标,分别都可以指向文件,0指向键盘,1指向显示器,2指向显示器,当再打开文件时从上往下找没有被使用的文件标识符了,所以从磁盘中把文件加载进来,把对象构建好,然后把这个对象的地址填到3号文件描述符里,此时3号文件描述符就指向新打开的文件了,然后我们再把3号文件描述符通过系统调用给用户返回,就得到了一个数字就叫做3,所以当一个进程在访问文件时它需要传入3,通过系统调用,操纵系统会找进程的文件描述符表,根据它找到对应的文件,文件找到了就可以对文件进行操作了。

文件描述符的本质:数组的下标
【Linux】基础IO(系统文件I/O Open write 文件描述符fd 什么是当前路径? 重新谈论文件 文件操作)