【Linux】文件描述符 - fd

2024-04-30 1204阅读

文章目录

  • 1. open 接口介绍
    • 1.1 代码演示
    • 1.2 open 函数返回值
    • 2. 文件描述符 fd
      • 2.1 0 / 1 / 2
      • 2.2 文件描述符的分配规则
      • 3. 重定向
        • 3.1 dup2 系统调用函数
        • 4. FILE 与 缓冲区

          【Linux】文件描述符 - fd

          1. open 接口介绍

          使用 man open 指令查看手册:

          #include 
          #include 
          #include 
          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: 追加写	
          返回值:
          		成功:新打开的文件描述符
          		失败:-1
          

          open 函数具体使用哪个,和具体应用场景有关。如:目标文件不存在,需要 open 创建,则第三个参数表示创建文件的默认权限;否则使用两个参数的 open。

          write read close lseek ,类比 C 文件相关接口。

          1.1 代码演示

          操作文件,除了使用 C 语言的接口【Linux】回顾 C 文件接口,还可以采用系统接口来进行文件访问;

          写文件:

          #include 
          #include 
          #include 
          #include 
          #include 
          #include 
          int main()
          {
              umask(0);
              int fd = open("myfile", O_WRONLY | O_CREAT, 0644);
              if (fd  
          

          读文件:

          #include 
          #include 
          #include 
          #include 
          #include 
          #include 
          int main()
          {
              int fd = open("myfile", O_RDONLY);
              if (fd  0)
                  {
                      printf("%s", buf);
                  }
                  else
                  {
                      break;
                  }
              }
              close(fd);
              return 0;
          }
          

          1.2 open 函数返回值

          在认识返回值之前,先来认识两个概念:系统调用 和 库函数 :

          • fopen fclose fread fwrite 都是 C 标准库当中的函数,我们称之为库函数(libc);
          • 而 open close read write lseek 都属于系统提供的接口,称之为系统调用接口;

            【Linux】文件描述符 - fd

            • 系统调用接口与库函数的关系如上图;
            • 所以,可以认为,f# 系列的函数,都是对系统调用的封装,方便二次开发。

              2. 文件描述符 fd

              • 文件描述符的本质,就是数组下标!!!

                2.1 0 / 1 / 2

                • Linux 进程默认情况下会有 3 个缺省打开的文件描述符,分别是标准输入 0,标准输出 1,标准错误 2;
                • 0,1,2 对应的物理设备一般是:键盘,显示器,显示器;
                • 所以输入输出也可以采用如下方式:
                  #include 
                  #include 
                  #include 
                  #include 
                  #include 
                  int main()
                  {
                      char buf[1024];
                      ssize_t s = read(0, buf, sizeof(buf));
                      if (s > 0)
                      {
                          buf[s] = 0;
                          write(1, buf, strlen(buf));
                          write(2, buf, strlen(buf));
                      }
                      return 0;
                  }
                  

                  【Linux】文件描述符 - fd

                  • 现在我们知道,文件描述符就是从 0 开始的小整数;
                  • 当我们打开文件时,操作系统在内存中要创建相应的数据结构来描述目标文件,于是就有了 file 结构体,表示一个已经打开的文件对象;
                  • 而进程执行 open 系统调用,就必须让进程和文件关联起来;
                  • 每个进程都有一个指针 *files ,指向一张表 files_struct ,该表最重要的部分就是包含一个指针数组,每个元素都是一个指向打开文件的指针;
                  • 所以,本质上,文件描述符就是该数组的下标,只要拿着文件描述符,就可以找到对应的文件。

                    2.2 文件描述符的分配规则

                    直接看代码:

                    #include 
                    #include 
                    #include 
                    #include 
                    int main()
                    {
                        int fd = open("myfile", O_RDONLY);
                        if (fd  
                    

                    输出发现是 fd: 3 ,

                    关闭 0 或者 2,再看:

                    #include 
                    #include 
                    #include 
                    #include 
                    int main()
                    {
                        close(0);
                        //close(2);
                        int fd = open("myfile", O_RDONLY);
                        if (fd  
                    

                    发现结果是:fd: 0 或者 fd: 2 ,

                    可见,文件描述符的分配规则:在 files_struct 数组当中,找到当前没有被使用的最小的一个下标,作为新的文件描述符,会分配给最新打开的文件。

                    3. 重定向

                    那如果关闭 1 呢?看代码:

                    #include 
                    #include 
                    #include 
                    #include 
                    #include 
                    int main()
                    {
                        close(1);
                        int fd = open("myfile", O_WRONLY | O_CREAT, 00644);
                        if (fd  
                    

                    此时,我们发现,本来应该输出到显示器上的内容,输出到了文件 myfile 当中,其中 fd = 1。这种现象叫做输出重定向。

                    常见的重定向有:> ,>> ,

                    那重定向的本质是什么呢?

                    【Linux】文件描述符 - fd

                    3.1 dup2 系统调用函数

                    函数原型如下:

                    #include 
                    int dup2(int oldfd, int newfd);
                    

                    函数简介:

                    makes newfd be the copy of oldfd, closing newfd first if necessary, but note the following:
                    将newfd设置为oldfd的副本,并在必要时先关闭newfd,但请注意以下事项:
                    *	If oldfd is not a valid file descriptor, then the call fails, and newfd is not closed.
                    	如果oldfd不是有效的文件描述符,则调用失败,newfd不会关闭。
                    *	If oldfd is a valid file descriptor, and newfd has the same value as oldfd, then dup2() does nothing, and returns newfd.
                    	如果oldfd是一个有效的文件描述符,并且newfd与oldfd具有相同的值,那么dup2()什么都不做,并返回newfd。
                    

                    示例代码:

                    #include 
                    #include 
                    #include 
                    int main()
                    {
                        int fd = open("./log", O_CREAT | O_RDWR, 0644);
                        if (fd  
                    
                    • printf 是 C 库当中的 IO 函数,一般往 stdout 中输出,但是 stdout 底层访问文件的时候,找的还是 fd:1 ;
                    • 但此时 fd:1 下标所表示的内容已经变成了 log 的地址,不再是显示器文件的地址;
                    • 所以,输出的任何消息都会往文件中写入,进而完成输出重定向。

                      4. FILE 与 缓冲区

                      • 因为 IO 相关函数与系统调用接口对应,并且库函数封装系统调用,所以本质上,访问文件都是通过 fd 访问的。
                      • 所以 C 库当中的 FILE 结构体内部,必定封装了 fd。
                      • 缓冲区就是一块内存区域,其存在目的是为了提升使用者的效率(用空间换时间)。
                      • 我们这里说的缓冲区是语言层面的缓冲区,也就是 C 自带的缓冲区,跟内核中的缓冲区没有关系。
                      • 缓冲区刷新方式:
                        • 无缓冲 - 无刷新;
                        • 行缓冲 - 行刷新 :写满一行才刷新,我们平时写代码经常会遇到缓冲区的问题;
                        • 全缓冲 - 全部刷新:在普通文件中写入时,缓冲区被写满,才刷新!
                        • 强制刷新:使用各种方法让缓冲区强制刷新,如:fflush() 函数;
                        • 自动刷新:程序退出的时候会自动刷新。

                          来段代码研究一下:

                          #include 
                          #include 
                          int main()
                          {
                              const char* msg0 = "hello printf\n";
                              const char* msg1 = "hello fwrite\n";
                              const char* msg2 = "hello write\n";
                              printf("%s", msg0);
                              fwrite(msg1, strlen(msg0), 1, stdout);
                              write(1, msg2, strlen(msg2));
                              fork();
                              return 0;
                          }
                          

                          运行出结果:

                          hello printf
                          hello fwrite
                          hello write
                          

                          但如果对进程实现输出重定向呢?./a.out > file ,我们发现结果变成了:

                          hello write
                          hello printf
                          hello fwrite
                          hello peintf
                          hello fwrite
                          

                          我们发现 printf 和 fwrite(库函数)都输出了 2 次,而 write 只输出了一次(系统调用)。

                          为什么呢?肯定和 fork 有关:

                          • 一般 C 库函数写入文件是全缓冲的,而写入显示器是行缓冲。
                          • printf fwrite 库函数会自带缓冲区(进度条例子可以说明【Linux】编写第一个小程序:进度条),当发生重定向到普通文件时,数据的缓冲方式由行缓冲变成了全缓冲。
                          • 而我们放在缓冲区中的数据,就不会被立即刷新,即使是 fork 之后;
                          • 但是进程退出之后,会统一刷新,写入文件当中。
                          • 但是 fork 的时候,父子数据会发生写时拷贝,所以当你父进程准备刷新的时候,子进程也就有了同样的一份数据,随即产生两份数据。
                          • write 没有变化,说明没有所谓的缓冲。

                            综上:printf fwrite 库函数会自带缓冲区,而 write 系统调用没有带缓冲区。另外,我们这里所说的缓冲区,都是用户级缓冲区。其实为了提升整机性能,OS 也会提供相关内核级缓冲区,不过不在我们讨论范围之内。那这个缓冲区谁提供呢?printf fwrite 是库函数,writre 是系统调用,库函数在系统调用的“上层”,是对系统调用的“封装”,但是 write 没有缓冲区,而 printf fwrite 有,足以说明,该缓冲区是二次加上的,又因为是 C,所以由 C 标准库提供。


                            END
VPS购买请点击我

免责声明:我们致力于保护作者版权,注重分享,被刊用文章因无法核实真实出处,未能及时与作者取得联系,或有版权异议的,请联系管理员,我们会立即处理! 部分文章是来自自研大数据AI进行生成,内容摘自(百度百科,百度知道,头条百科,中国民法典,刑法,牛津词典,新华词典,汉语词典,国家院校,科普平台)等数据,内容仅供学习参考,不准确地方联系删除处理! 图片声明:本站部分配图来自人工智能系统AI生成,觅知网授权图片,PxHere摄影无版权图库和百度,360,搜狗等多加搜索引擎自动关键词搜索配图,如有侵权的图片,请第一时间联系我们,邮箱:ciyunidc@ciyunshuju.com。本站只作为美观性配图使用,无任何非法侵犯第三方意图,一切解释权归图片著作权方,本站不承担任何责任。如有恶意碰瓷者,必当奉陪到底严惩不贷!

目录[+]