首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Linux中有关文件操作的系统接口,文件描述符,重定向的介绍

Linux中有关文件操作的系统接口,文件描述符,重定向的介绍

作者头像
再睡一下就好
发布2025-06-11 09:56:03
发布2025-06-11 09:56:03
18300
代码可运行
举报
文章被收录于专栏:深究算法深究算法
运行总次数:0
代码可运行

前言

我们为什么要了解系统调用接口?

各种程序语言操作文件的语句不尽相同,但底层调用的都是相同的系统接口。所以,不管上层语言如何更新变换,只要我们掌握了不变的底层,在面对日益复杂的操作系统时总能找到入手的方向。


一、Linux中有关文件操作的系统接口

Linux中有关文件操作的系统接口主要包括:

①打开/关闭文件的open和close;

②负责读写的read和write。

1.open:文件的打开(创建)

上面两个open在参数上有些不同,下面详细阐述他们的区别:

1)功能用法介绍

功能:open的主要功能是打开一个文件,但如果该文件不存在则会创建按此路径文件。

参数:

①pathname:目标文件的路径,可以是绝对路径或者相对路径;

②flags:标记位。必须指定以下其中之一:

O_RDONLY(只读)、O_WRONLY(只写)、O_RDWR(读写)。

可选标记位组合:

O_CREAT:文件不存在时创建文件;

O_TRUNC:打开时清空文件内容;

O_APPEND:追加写入。

③mode:文件不存在时,指定新文件权限,默认0666,实际收umask影响。

返回值:成功时返回文件描述符fd;失败时返回-1。

有关什么是文件描述符在本文后半段会讲解,这里不影响理解open。

以下补充标记位和umask以便理解open的使用,读者可按需阅读。


什么是标记位?

在bool类型诞生之前,通过使用32位bite位来传递选项,每一个比特位都代表着一个选项。通常标记位与位运算组合使用。例如下面的三个标记位,他们的本质其实是宏。

O_RDONLY(只读)、O_WRONLY(只写)、O_RDWR(读写)。

组合使用:

多个标记位可通过按位或“ | ”合并一起使用,如

int flags = O_RDWR | O_CREAT | O_TRUNC; / / 作用是读写 + 创建 + 清空文件。

掩码检查:

可以通过按位与“ & ”判断是否包含某个标记位,如:

if(flags & O_CREAT){/ / 检查该传入的标记位是否包含创建选项} ;

什么是umask?

umask是文件创建掩码,它的主要功能是控制新创建的文件或目录的默认权限。

新文件的最终权限 = mode & ~umask。

在程序中可更改umask的值,如下:

在文件中修改的umask不会影响bash中的umask的值,相关知识参考“进程地址空间和写时拷贝”:进程优先级介绍,详解环境变量,详解进程地址空间-CSDN博客

代码语言:javascript
代码运行次数:0
运行
复制
  1 #include<stdio.h>
  2 #include<sys/types.h>
  3 #include<sys/stat.h>
  4 int main()
  5 {
  6     umask(0664);                                                                                                                                         
  7     return 0;                                                                                                            
  8 } 

若读者有意了解在linux环境下,文件权限是如何设置以及怎么修改,可参看:初识Linux:常见指令解读,权限、粘滞位的理解,以及其相关衍生知识-CSDN博客


回到正题,下面讨论open函数的基本使用。

2)open的基本使用方法

若没有目标文件,则open函数中需要传入三个参数,这里传入的flags包括O_WRONLY(只读),O_CREAT(创建),O_TRUNC(每次打开文件清空)。

相应的若传入路径中有目标文件,则只用传入两个参数即可。值得注意的是若是选择创建新文件,则flags中必须与上O_CREAT。

代码语言:javascript
代码运行次数:0
运行
复制
  1 #include<stdio.h>
  2 #include<sys/types.h>
  3 #include<sys/stat.h>
  4 #include<fcntl.h>
  5 #include<unistd.h>
  6 #include<string.h>
  7 #define FILE_NAME "log.txt"
  8 int main()
  9 {
 10     int fd =open("log.txt",O_WRONLY | O_CREAT | O_TRUNC,0664);
 11     if(fd<0)
 12     {
 13         perror("open");
 14         return 1;
 15     }
        //…………
 17     close(fd);
 18     return 0;
 29 }

2.close

由上述示例代码,我们引出close的介绍

close的用法十分简单。

作用:释放文件描述符(后面会介绍)及其相关的内核资源。

参数:文件描述符;

返回值:成功返回0,失败返回-1.

文件打开后如不用,应及时close关闭,防止资源耗尽。尽管程序退出时OS会自动关闭所有文件描述符。

3.write

打开关闭文件介绍后,就到了往文件中读写了。

作用:write,往打开的文件中写入内容。

参数:

①fd:被打开的文件描述符——由open返回。

②buf:要写入数据指针(本质上是个缓冲区)

③count:预期写入的字节数。

返回值:ssize_t是一个有符号整数类型(signed size_t),包含在“sys/types.h”等头文件中。

成功,返回实际写入的字节数;失败,返回 -1。

在上述open、close示例代码基础上,加入write相关操作语句。

代码语言:javascript
代码运行次数:0
运行
复制
  1 #include<stdio.h>
  2 #include<sys/types.h>
  3 #include<sys/stat.h>
  4 #include<fcntl.h>
  5 #include<unistd.h>
  6 #include<string.h>
  7 #define FILE_NAME "log.txt"
  8 int main()
  9 {
 10     int fd =open("log.txt",O_WRONLY | O_CREAT | O_TRUNC,0664);
 11     if(fd<0)
 12     {
 13         perror("open");
 14         return 1;
 15     }
 16     const char *str="hello Linux\n";                                                                                                                     
 17     write(fd,str,strlen(str));
 18     close(fd);
 19     return 0;
 20 }

结果:

4.read

作用:从打开的文件中读取数据。

参数:fd,文件描述符;buf,存储读取数据的数组指针(缓冲区);count,期望读取的最大字节数。

返回值:成功,返回实际读取的字节数;失败,返回-1;若中途读取到文件末则立即返回0.jiji

read的使用方法示例

目标文件内容

这里笔者创建了一个12字节的char str数组,但这肯定不够将log.txt中的内容完全存放。

于是笔者采用了循环读取的方法,每次读取12个字节,然后打印,再读取,直到将文件内容读完。

代码语言:javascript
代码运行次数:0
运行
复制
  1 #include<stdio.h>
  2 #include<sys/types.h>
  3 #include<sys/stat.h>
  4 #include<fcntl.h>
  5 #include<unistd.h>
  6 #include<string.h>
  7 #define FILE_NAME "log.txt"
  8 int main()
  9 {
 10     int fd =open("log.txt",O_RDONLY);
 11     if(fd<0)
 12     {
 13         perror("open");
 14         return 1;
 15     }
 16     char str[12];
 17     memset(str,0,sizeof(str));
 18 
 19     int total=0;
 20     while(read(fd,str,sizeof(str)))
 21     {
 22         //str[sizeof(str)-1]='\0';
 23         printf("%s\n",str);
 24         total+=(sizeof(str));                                                                                                                            
 25     }
 26     close(fd);
 27     return 0;
 28 }

结果:

二、文件操作的本质

首先需要明确的共识是:系统中存在非常多的文件,大致可分为打开的文件和没被打开的文件。

而进程文件操作的对象,就是那些打开的文件。

换句话说,文件操作的本质就是:进程与被打开文件之间的关系。

1.文件描述符

在上文介绍文件的相关操作时,多次提及文件描述符。下面我们一起探讨什么是文件描述符。

我们知道当程序执行时,操作系统会为其创建进程,以及PCB(Linux环境下为task_struct)用以管理进程,而task_struct中有个成员变量为struct_file * files,files指针指向内存中另一个数据结构——struct_file。

在struct_file中存在着一个成员struct_file * fd_arr【】文件描述符表,fd_arr【】中每个元素都是都是一个指针,指向内存中属于该进程的文件(即该指针存储着这些文件在内存中的地址)。其中文件描述符就是fd_arr【】的数组下标!

即文件描述符的本质就是数组下标。

2.文件描述符的分配规则

3.文件描述符与C语言中的FILE的关系

在C语言中,就像fwrite和fread是系统调用接口write和read的封装一样,C语言将有关文件类型封装成了一个结构体FILE,而该结构体的成员就包括了文件描述符表

1)三个标准输入输出流

如何证明FILE中封装了文件描述符表?我们可以通过三个标准输入输出来验证。

编写一个简单的程序查看三个标准输入输出的文件描述符fd:

结果:

由上我们也可以得知:

三、重定向

什么是重定向?

还记得我们说过fd就是struct_file * fd_arr【】文件描述符表的数组下标吗。简单来说重定向就是将某fd存储的文件地址更换为另一个文件的文件地址。

重定向函数dup2

作用:宏观上上层fd不变,更改内核中fd所对应的struct_file*的地址。

参数:oldfd是来源,newfd是目标fd。即将oldfd存储的文件地址拷贝给newfd。

返回值:成功,返回oldfd的fd,失败返回-1。

注意:拷贝的是oldfd中存储的文件地址,返回的是oldfd(数组下标)。

重定向前: 重定向后:

输出重定向示例

这里读者将stdout标准输出流与log.txt文件重定向,本应该打印到屏幕的数据会被打印到log.txt文件中。

代码语言:javascript
代码运行次数:0
运行
复制
  1 #include<stdio.h>
  2 #include<unistd.h>
  3 #include<sys/types.h>
  4 #include<sys/stat.h>
  5 #include<fcntl.h>
  6 #include<string.h>
  7 int main()
  8 {
  9     int fd=open("log.txt",O_WRONLY | O_TRUNC);
 10     if(fd<0)return 1;
 11 
 12     dup2(fd,1);//stdout标准输入流
 13     printf("hello dup2\n");
 14     fprintf(stdout,"open fd:%d\n",fd);
 15                                                                                                                                                          
 16     close(fd);
 17     return 0; 
 18 }  

四、一些问题

当两个进程打开同一份文件时,如果其中一个进程将文件关了,另一个进程还能对这个文件进程操作吗?


总结

这里对文章进行总结:

我们首先是介绍了Linux中有关文件操作的几个系统接口:open、close、write、read,其中穿插了标记位和umask的介绍和使用。然后我们讨论了文件操作的本质——进程与被打开文件之间的关系,再紧接着阐述了什么是文件描述符及其分配规则,再然后我们通过C语言中的三个标准输入输出证明了FILE中存在着文件描述符表,并得知三个标准输入输出默认占用了0 1 2文件描述符。最后我们介绍连重定向的概念,并用普通文件log.txt重定向了stdout。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-05-16,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 一、Linux中有关文件操作的系统接口
    • 1.open:文件的打开(创建)
      • 1)功能用法介绍
      • 什么是标记位?
      • 什么是umask?
      • 2)open的基本使用方法
    • 2.close
    • 3.write
    • 4.read
      • read的使用方法示例
  • 二、文件操作的本质
    • 1.文件描述符
    • 2.文件描述符的分配规则
    • 3.文件描述符与C语言中的FILE的关系
      • 1)三个标准输入输出流
  • 三、重定向
    • 重定向函数dup2
    • 输出重定向示例
  • 四、一些问题
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档