【Linux】六、Linux 基础IO(一)|重谈文件|C语言文件操作|操作系统文件操作(系统文件I/O)|文件描述符

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

目录

一、重谈文件

二、C语言文件操作

2.1 重谈C语言文件操作

2.2 补充细节

三、操作系统文件操作系统文件I/O

3.1 文件相关系统调用close 

3.2 文件相关系统调用open

3.2.1 open 的第二个参数 flags

3.2.2 open 的第三个参数 mode

3.2.3 open 的返回值

3.3 文件相关系统调用write

3.4 文件相关系统调用read

四、文件描述符

4.1 如何理解文件

4.2 什么是文件描述符

4.3 文件描述符的分配规则


一、重谈文件

文件的基本知识

  1. 文件由文件内容和文件属性两部分构成
  2. 空文件只是文件内容为空但文件依旧存在属性所以空文件也要占用磁盘空间
  3. 因为文件由内容和属性构成所以对文件的操作 = 对内容操作 + 对属性操作 or 对内容和属性操作
  4. 标识一个文件文件路径 + 文件名唯一性
  5. 如果没有指明路径默认在当前路径下进行文件访问当前路径是指当前进程的工作路径
  6. 在C语言中对包含 fopen、fclose、fwrite、fread 等函数接口的程序进行编译链接形成可执行程序之后如果不运行该可执行程序相应的文件接口就没有被调用则对应的文件操作也不会被执行。所以对文件的操作只有进程运行了才会对文件进行操作运行起来的程序就是一个进程所以对文件的操作本质上就是进程对文件的操作
  7. 一个文件没有打开不能直接对文件进行访问一个文件要被访问就必须先被打开。怎么打开用户进程 + OS操作系统进程负责调用文件接口OS 负责打开相应的文件
  8. 一个文件被打开并不是磁盘上所有的这些文件都被打开了只是打开了相应的文件。所以磁盘上的文件分为 被打开的文件 + 未打开的文件。所以文件操作的本质进程 和 被打开的文件 的关系
  9. 而 没有被打开的文件 与文件系统相关文件系统也是 基础IO 重点学的对象

二、C语言文件操作

2.1 重谈C语言文件操作

在学习文件操作之前我们先来回顾一下C语言的文件操作

C语言文件操作部分接口

C语言文件操作函数功能
fopen打开文件
fclose关闭文件
fputc写入一个字符
fgetc读取一个字符
fputs写入一个字符串
fgets读取一个字符串
fprintf格式化写入数据
fscanf格式化读取数据
fwrite向二进制文件写入数据
fread从二进制文件读取数据
fseek设置文件指针的位置
ftell计算当前文件指针相对于起始位置的偏移量
rewind设置文件指针到文件的起始位置
ferror判断文件操作过程中是否发生错误
feof判断文件指针是否读取到文件末尾

C语言文件打开的几种方式部分

文件打开方式含义如果指定文件不存在
“r”只读为了输入数据打开一个已经存在的文本文件出错
“w”只写为了输出数据打开一个文本文件建立一个新的文件
“a”追加向文本文件尾添加数据建立一个新的文件
“r+”读写为了读和写打开一个文本文件出错
“w+”读写为了读和写建议一个新的文件建立一个新的文件
“a+”读写打开一个文件在文件尾进行读写建立一个新的文件
“rb”只读为了输入数据打开一个二进制文件出错
“wb”只写为了输出数据打开一个二进制文建立一个新的文件
“ab”追加向一个二进制文件尾添加数据出错

C语言文件操作的例子在 C语言专栏的文件操作里面这里不再演示

        默认打开的三个流任何进程在运行的时候都会默认打开三个输入输出流即标准输入流、标准输出流以及标准错误流对应到C语言当中就是 stdin、stdout 以及 stderr

        随着我们的不断学习接触的语言逐渐增多比如面向过程语言C、面向对象语言 C++/java、静态编译语言 go、解释型语言 python 等等语言它们的都有文件操作并且接口都不一样学习成本高。

        但是它们底层调用的接口都一样。因为文件是在磁盘上磁盘归 OS 管理而所有人想访问磁盘都绕不开操作系统想访问磁盘就必须使用 OS 提供的接口所以想要访问文件OS 就必须提供相关文件级别的系统接口所以想访问文件就必须直接或间接使用操作系统提供的接口

        所以无论上层的语言C/C++/Java..怎么变化库函数的底层必须调用系统接口。库函数可以千变万化但是底层是不变的要降低学习成本我们只要学习不变的东西即可

        所以我们学习文件操作只需要学习操作系统系统调用有关文件操作方面的接口即可以后我们再学习语言的文件操作时只需要学习一些新的特性即可总体层面上是不变的因为底层接口都一致大大降低了学习成本

2.2 补充细节

测试代码

#include<stdio.h>      
      
#define FILE_NAME "log.txt"      
      
int main()      
{      
   FILE* fp = fopen(FILE_NAME, "w");                                                                                                                                       
   if(fp == NULL)      
   {      
       perror("fopen");      
       return 1;      
   }      
          
   int cnt = 5;      
   while(cnt)      
   {      
       fprintf(fp, "%s, %d\n", "hello", cnt--);      
   }      
      
   fclose(fp);      
      
    return 0;      
}      

运行结果

        log.txt 文件创建出来的权限为 664普通文件的默认权限为0666Linux 下普通用户默认的 umask 为 0002而文件最终权限等于默认权限 & ~umask所以 log.txt 的权限为 0664

        C语言以 “w” 方式打开文件时无论是否写入新数据都会清空之前文件中的数据 

三、操作系统文件操作系统文件I/O

3.1 文件相关系统调用close 

close关闭一个文件

man 2 close 进行查看

3.2 文件相关系统调用open

open打开或者创建一个文件 

man 2 open 进行查看

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

int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);

pathname: 要打开或创建的目标文件

flags: 打开文件时可以传入多个参数选项用下面的一个或者多个常量进行“或”运算构成flags
参数:
    O_RDONLY: 只读打开
    O_WRONLY: 只写打开
    O_RDWR : 读写打开
        这三个常量必须指定一个且只能指定一个
    O_CREAT : 若文件不存在则创建它。需要使用mode选项来指明新文件的访问权限
    O_APPEND: 追加写

mode: 指定创建新文件时文件的默认权限(文件最终权限还要受umask的影响)

返回值
    成功新打开的文件描述符
    失败-1

3.2.1 open 的第二个参数 flags

        open函数的第二个参数是 flags表示打开文件的方式

        系统接口 open的第二个参数flags是整型有 32比特位若将一个比特位作为一个标志位则理论上 flags 可以传递32种不同的标志位。实际上传入 flags 的每一个选项在系统当中都是以宏的方式进行定义的

参数选项

 上述这些宏表示不同的文件打开方式其原理是 通过比特位传递选项

什么是比特位传递选项

#include<stdio.h>    
    
//每一个宏只占用一个比特位该比特位为1说明该选项成立且各个宏的位置不重叠    
#define ONE (1<<0)    
#define TWO (1<<1)    
#define THREE (1<<2)    
#define FOUR (1<<3)    
    
void show(int flags)    
{    
    //flags与上面哪个选项匹配就执行对应的操作    
    if(flags & ONE) printf("one\n");    
    if(flags & TWO) printf("two\n");    
    if(flags & THREE) printf("three\n");    
    if(flags & FOUR) printf("four\n");    
}    
    
int main()                                                                                                                                                                 
{    
    //主函数中通过传递不同选项来达到不同效果    
    show(ONE);    
    printf("--------------\n");    
    show(ONE | TWO);    
    printf("--------------\n");    
    show(ONE | TWO | THREE);    
    printf("--------------\n");    
    show(ONE | TWO | THREE | FOUR);    
    
    return 0;    
}    

 运行结果

下面进行测试使用

  #include<stdio.h>    
  #include<sys/types.h>    
  #include<sys/stat.h>    
  #include<fcntl.h>    
  #include<unistd.h>                                                                                                                                                       
                                            
  #define FILE_NAME "log.txt"               
                                            
  int main()                                
  {                                                          
      int fp = open(FILE_NAME, O_WRONLY);//以写的形式打开    
      if(fp < 0)                            
      {                                     
          perror("open");                   
          return 1;                         
      }                                     
                                            
      close(fp);                                                                                                                                    
                                                                                                                                                    
      return 0;                                                                                                                                     
  }                                                

运行结果

        文件没有创建成功是因为 没有传创建参数 O_CREAT系统接口默认是不会自动创建的必须传创建参数

修改一下代码

运行结果文件创建成功了

但是这个文件有点不对劲它的权限是乱码这就跟第三个参数有关了

3.2.2 open 的第三个参数 mode

        open函数的第三个参数是mode表示创建文件的默认权限

        但实际上创建出来文件的权限值还会受到 umask文件默认掩码的影响实际创建出来文件的权限为mode&(~umask)。umask的默认值普通用户为 0002当我们设置 mode 值为 0666 时实际创建出来文件的权限为 0664 

再次修改代码

运行结果文件就可以正常创建了

3.2.3 open 的返回值

        open函数的返回值是新打开文件的 文件描述符

文件描述符下面讲解 

3.3 文件相关系统调用write

        write 系统接口的作用是向文件写入信息

man 2 write 查看

ssize_t write(int fd, const void *buf, size_t count);

        使用 write 系统调用将 buf 位置开始向后 count 字节的数据写入文件描述符为 fd 的文件当中

  • 如果数据写入成功实际写入数据的字节个数被返回。
  • 如果数据写入失败-1被返回

测试代码

 #include<stdio.h>                                                                                                                                     
  #include<sys/types.h>                                                                                                                                 
  #include<sys/stat.h>                                                                                                                                  
  #include<fcntl.h>                                                                                                                                     
  #include<unistd.h>                                                                                                                                    
  #include<string.h>                                                                                                                                    
                                                                                                                                                        
  #define FILE_NAME "log.txt"                                                                                                                           
                                                                                                                                                        
  int main()                                                                                                                                            
  {                                                                                                                                                     
      int fp = open(FILE_NAME, O_WRONLY|O_CREAT, 0666);//以写的形式打开不存在创建权限设置为 0666                                                    
      if(fp < 0)                                                                                                                                        
      {                                                                                                                                                 
          perror("open");                                                                                                                               
          return 1;                                                                                                                                     
      }                                                                                                                                                 
                                                                                                                                                        
      int cnt = 5;                                                                                                                                      
      char outBuffer[64];                                                                                                                               
      while(cnt)                                                                                                                                        
      {                                                                                                                                                 
          sprintf(outBuffer, "%s:%d\n", "hello", cnt--);                                                                                                
          write(fp, outBuffer, strlen(outBuffer));//strlen 之后不用+1系统接口是以有效数据结尾不是C语言C语言 strlen+1代表添加'\0',因为C语言的字符串是以'\0'结尾       
      }                                                                                                                                
                                                                                                                                       
      close(fp);                                                                                                                       
                                                                                                                                       
      return 0;                                                                                                                        
  }                                    

运行结果

修改一下写入文件的内容

   

再次运行cat 查看文件内容

文件里面怎么有上一次的内容怎么没有清理干净

这是因为我们没有传清空文件内容的参数 O_TRUNC修改代码

再次运行cat 查看上一次的内容就被清空了

        C语言的 ‘w’  则是自动清空上一次文件的数据当你C语言在以 ‘w’ 的方式写入时实际上操作系统在底层给你传了 O_WRONLY|O_CREAT|O_TRUNC, 0666这就是系统接口与库函数的区别

3.4 文件相关系统调用read

        read 系统接口的作用是从文件读取信息

man 2 read 进行查看

ssize_t read(int fd, void *buf, size_t count);

 使用read函数从文件描述符为fd的文件读取count字节的数据到buf位置当中

  • 如果数据读取成功实际读取数据的字节个数被返回
  • 如果数据读取失败-1被返回

测试代码

 #include<stdio.h>    
  #include<sys/types.h>    
  #include<sys/stat.h>    
  #include<fcntl.h>    
  #include<unistd.h>    
  #include<string.h>    
      
  #define FILE_NAME "log.txt"    
      
  int main()    
  {    
      int fp = open(FILE_NAME, O_RDWR);//O_RDWR读    
      if(fp < 0)    
      {    
          perror("open");    
          return 1;    
      }    
      
      char buffer[512];    
      ssize_t num = read(fp, buffer, sizeof(buffer)-1);    
      if(num>0)    
      {    
          buffer[num] = 0;//C语言的字符串以'\0'结尾    
      }    
      printf("%s", buffer);                                                                                                                                                
      
      close(fp);    
      
      return 0;    
  }    

运行结果

        由于C语言字符串以 ‘\0’ 结尾而文件中的字符串数据并不包含 ‘\0’所以这里我们需要预留一个位置便于在数据量大于等于512字节情况下 buf 中仍有空间来放置 ‘\0’ 

总结

        C语言的库函数接口 fwrite fread fclose fopen ..类比系统调用接口 write read close open ...实际上就是 C语言对系统调用接口进行封装形成库函数

四、文件描述符

4.1 如何理解文件

上面已经说过

        文件操作本质上是 进程 与 被打开文件 之间的关系

        一个进程可以打开多个文件且操作系统同时运行着许多个进程那么操作系统中就一定存在着大量被打开的文件 

那这些被打开的文件要不要被操作系统管理起来呢肯定要的

怎么管理

        先描述在组织

        操作系统为了管理被打开的文件必定要为文件创建对应的 内核数据结构用于标识文件在 Linux 中这个数据结构是 struct file {}这个结构体包含了文件的大部分属性这个结构体与C语言的 FILE 没有任何关系

4.2 什么是文件描述符

        open 系统调用的返回值是新打开文件的 文件描述符 

测试代码

C语言 # 在宏当中的作用 – 将参数插入到字符串中 

#include<stdio.h>    
  #include<sys/types.h>    
  #include<sys/stat.h>    
  #include<fcntl.h>    
  #include<unistd.h>    
  #include<string.h>    
      
  #define FILE_NAME(number) "log.txt"#number    
      
  int main()    
  {    
      
      int fd0 = open(FILE_NAME(1), O_WRONLY|O_CREAT|O_TRUNC, 0666);    
      int fd1 = open(FILE_NAME(2), O_WRONLY|O_CREAT|O_TRUNC, 0666);    
      int fd2 = open(FILE_NAME(3), O_WRONLY|O_CREAT|O_TRUNC, 0666);    
      int fd3 = open(FILE_NAME(4), O_WRONLY|O_CREAT|O_TRUNC, 0666);    
      int fd4 = open(FILE_NAME(5), O_WRONLY|O_CREAT|O_TRUNC, 0666);    
      
      printf("fd0:%d\n", fd0);    
      printf("fd1:%d\n", fd1);    
      printf("fd2:%d\n", fd2);    
      printf("fd3:%d\n", fd3);    
      printf("fd4:%d\n", fd4);    
      
      close(fd0);    
      close(fd1);                                                                                                                                                          
      close(fd2);    
      close(fd3);    
      close(fd4);    
      
      return 0;    
}

运行结果

        从运行结果可以看到文件描述符是连续分配且依次增大的这也很合理因为文件描述符本质上是数组下标而连续增长正好是数组下标的特性但是这里有一个很奇怪的地方 – 文件描述符是从3开始的但是0、1、2 呢 

        这是由三个默认打开的标准流引起的标准输入stdin、标准输出流stdout 与 标准错误流stderr

man stdin 查看这三个流都是 FILE* 的指针FILE 结构体里面就一定会封装一个变量来表示文件描述这个这个变量是 _fileno

         在C语言中打开一个文件FILE* fp = open()这个 FILE 实际上也是一个结构体它底层封装采用的是系统调用所以必定有一个字段叫做文件描述符

所以我们进行打印一下这三个值

  #include<stdio.h>    
  #include<sys/types.h>    
  #include<sys/stat.h>    
  #include<fcntl.h>                                                                                                                                                        
  #include<unistd.h>    
  #include<string.h>    
      
  #define FILE_NAME(number) "log.txt"#number    
      
  int main()    
  {    
      printf("stdin->fd:%d\n", stdin->_fileno);    
      printf("stdout->fd:%d\n", stdout->_fileno);    
      printf("stderr->fd:%d\n", stderr->_fileno);    
      
      int fd0 = open(FILE_NAME(1), O_WRONLY|O_CREAT|O_TRUNC, 0666);    
      int fd1 = open(FILE_NAME(2), O_WRONLY|O_CREAT|O_TRUNC, 0666);    
      int fd2 = open(FILE_NAME(3), O_WRONLY|O_CREAT|O_TRUNC, 0666);    
      int fd3 = open(FILE_NAME(4), O_WRONLY|O_CREAT|O_TRUNC, 0666);    
      int fd4 = open(FILE_NAME(5), O_WRONLY|O_CREAT|O_TRUNC, 0666);    
      
      printf("fd0:%d\n", fd0);    
      printf("fd1:%d\n", fd1);    
      printf("fd2:%d\n", fd2);    
      printf("fd3:%d\n", fd3);    
      printf("fd4:%d\n", fd4);    
      

      close(fd0);
      close(fd1);
      close(fd2);
      close(fd3);
      close(fd4);
  
      return 0;
  }

 运行结果

        从运行结果就可以知道 因为三个标准流是默认打开的所以 0、1、2 是默认被占用的所以文件描述符默认是从 3 开始的

        而现在知道文件描述符就是从0开始的小整数。当我们打开文件时操作系统在内存中要创建相应的数据结构来描述目标文件。于是就有了file结构体表示一个已经打开的文件对象

        而进程执行 open系统调用所以必须让进程和文件关联起来。每个进程都有一个指针*files, 指向一张表files_struct该表最重要的部分就是包涵一个指针数组每个元素都是一个指向打开文件的指针

        所以本质上文件描述符就是该数组的下标。所以只要拿着文件描述符就可以找到对应的文件

4.3 文件描述符的分配规则

测试代码

  #include<stdio.h>    
  #include<sys/types.h>    
  #include<sys/stat.h>    
  #include<fcntl.h>
  #include<unistd.h>    
      
  int main()    
  {    
      int fd = open("log.txt", O_WRONLY|O_CREAT|O_TRUNC, 0666);    
      if(fd < 0)    
      {    
          perror("open");    
          return 1;    
      }    
      
      printf("open fd: %d\n", fd);    
      
      close(fd);                                                                                                                                                           
                                                                                                                                                       
      return 0;                                                                                                                                        
  }                                              

运行结果是3没毛病0、1、2 默认被占用

 

若我们先关闭文件描述符为0的文件此后文件描述符的分配又会是怎样的呢 

修改一下代码

再次运行可以看到打开的文件获取到的文件描述符变成了0 

  

再次修改代码

  #include<stdio.h>                                                                                                                  
  #include<sys/types.h>                                                                                                              
  #include<sys/stat.h>                                                                                                               
  #include<fcntl.h>                                                                                                                  
  #include<unistd.h>                                                                                                                                                                                                                                 
                                                                                                                                     
  int main()                                                                                                                         
  {                                                                                                                                  
      close(0);//关闭文件描述符0                                                                                                     
      close(2);                                                                                                                      
                                                                                                                                     
      int fd1 = open("log.txt1", O_WRONLY|O_CREAT|O_TRUNC, 0666);                                                                    
      int fd2 = open("log.txt2", O_WRONLY|O_CREAT|O_TRUNC, 0666);                                                                    
      int fd3 = open("log.txt3", O_WRONLY|O_CREAT|O_TRUNC, 0666);                                                                                                          
                             
      printf("open fd1: %d\n", fd1);    
      printf("open fd2: %d\n", fd2);    
      printf("open fd3: %d\n", fd3);    
                                                                                                                                                        
      close(fd1);                                                                                                                                       
      close(fd2);                                                                                                                                       
      close(fd3);                                                                                                                                       
                                                                                                                                                        
      return 0;                                                                                                                                         
  }                               

 运行结果

  

可以看到当 0 和 2 文件描述符被关闭以后系统将其分配给了新打开的文件

        close 关闭文件并不是将 fd 指向的文件对象释放掉而仅仅是让当前进程文件描述符表中的对应下标不再指向该文件对象因为同一个文件可能会被多个进程访问

        其底层采用 count “引用计数” 的方式来实现即当有指向该文件的进程关闭时文件计数减1有指向该文件的进程打开时文件计数加1当 ount 为 0 时操作系统才释放该文件的内核数据结构即真正意义上的关闭文件

        所以文件描述符的分配规则是在 files_struct 数组当中从小到大依次搜寻找到当前没有被使用的最小的一个下标作为新的文件描述符

----------------我是分割线---------------

文章到这里就结束了下一篇即将更新

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: linux

“【Linux】六、Linux 基础IO(一)|重谈文件|C语言文件操作|操作系统文件操作(系统文件I/O)|文件描述符” 的相关文章