☰
【Linux】系统文件IO接口与重定向原理
2026/10/15 1:25:15 网站建设 项目流程

前言:这篇介绍 Linux 中常用的系统文件 IO 接口,理解文件描述符的作用,再看看重定向是怎么改变输入输出位置的,本篇和以前我写的【C语言】文件操作详细解析-CSDN博客练习紧密所以忘了的话可以看看。



1.从文件到系统接口

平时提到文件,我们首先想到的往往是里面的内容,比如一段文字、一份代码。但对于操作系统来说,管理一个文件还需要知道它的大小、权限、所属用户等信息。

所以可以先把一个文件理解成两部分:文件内容和文件属性。

即使文件大小是 0,也只是没有保存内容,文件的属性仍然需要记录。我们查看文件时看到的权限、大小、修改时间等,就是这些属性的一部分。

普通文件通常保存在磁盘上,程序退出以后,文件还可以继续保留。而磁盘属于外部设备,程序从文件中读取内容、向文件中写入内容,就是在进行 IO 操作。

文件由操作系统统一管理。程序想要访问文件,需要通过操作系统提供的接口,把“打开哪个文件”“读多少内容”“往哪里写”等要求交给它。

前面使用的 C 文件接口,也是在底层系统接口的基础上提供更方便的操作方式。这里直接来看 Linux 中的open、read、write、close等接口。


2.常用的系统文件 IO 接口

2.1open:打开文件

操作文件通常从打开文件开始。理解open,主要看三个参数:

参数作用
pathname要打开的文件路径,可以是相对路径,也可以是绝对路径
flags以什么方式打开,是否需要创建、清空或者追加
mode创建新文件时使用的权限,使用O_CREAT时必须提供

打开成功后,open返回一个非负整数,叫作文件描述符;失败则返回-1。后面读写和关闭文件,都要用到这个描述符。

先看flags中最基本的三种访问方式:

访问方式含义
O_RDONLY只读
O_WRONLY只写
O_RDWR可读可写

这三种方式选择一种即可。在此基础上,还可以按需要搭配其他选项:

选项作用
O_CREAT文件不存在时创建文件
O_TRUNC以可写方式打开已有普通文件时,把原来的内容清空
O_APPEND每次写入时,都从文件末尾追加

例如,想要“只写,文件不存在就创建,存在就清空”,就把O_WRONLY、O_CREAT、O_TRUNC通过按位或运算符|组合起来。

这些名字实际上都是宏,可以看看它们的定义:

不过,访问方式需要单独记住:**O_RDONLY的值为 0,不能把它与O_WRONLY组合成读写方式,读写要直接选O_RDWR注意一下就行了。


再看创建文件时的权限。

只要使用了O_CREAT,就要传入mode,即使这次打开的文件已经存在,也不能省略。如果文件已经存在,这个参数不会重新修改它的权限。

创建新文件时,还要考虑umask。它会屏蔽掉一部分权限,常见情况下可以按mode & ~umask理解。例如给定0666,当前umask为0022,最终得到的权限就是0644,也就是rw-r--r--。

来看下面的图:

使用了O_CREAT,却没有给出创建权限,因此出现了不符合预期的权限结果。

除了上面那些还要区分两个概念:flags中的访问方式决定这次打开后准备怎么读写,mode决定新建文件申请哪些权限。

2.2读取、写入、关闭与定位

拿到文件描述符以后,就可以围绕它操作文件了。

接口主要参数作用与返回结果
readfd、buf、count从fd对应的文件读取最多count个字节,放进buf,返回实际读取的字节数
writefd、buf、count把buf中的内容写入fd对应的文件,最多写入count个字节,返回实际写入的字节数
closefd关闭这个文件描述符,成功返回 0,失败返回-1
lseekfd、offset、whence调整文件偏移量,成功返回调整后相对文件开头的位置,失败返回-1

这里的buf就是程序中用来存放数据的一块内存。读取时把文件内容放进来,写入时把里面的数据交出去。

对于普通文件,在请求读取的字节数大于 0 时,read返回 0,表示已经读到文件末尾;返回-1,表示读取失败。write失败也返回-1。

之前在学C语言的文件读写操作时实际上也是对这些底层的系统调用做了封装,经过封装后的C标准库就可以支持各个平台系统,底层上就是在不同的平台都相应的实现了一份对应的代码,再通过条件编译的方式。这样我们使用C标准库提供的文件读写接口就具有可移植性。


3.文件描述符与重定向

3.1文件描述符为什么是整数

前面知道,open返回的文件描述符是一个非负整数。程序拿着这个整数,就能继续读写文件,操作系统是怎么根据它找到文件的?

可以先看下面的简化关系图:

每个进程都有自己的文件描述符表。图中可以把它看作一个数组,数组的每个位置保存着一个指向内核文件对象的指针,文件描述符就是这个位置的下标。

沿着关系看,就是:

进程 → 文件描述符表 → 对应表项 → 内核中的打开文件对象。

内核中的打开文件对象会记录这次打开的读写方式、当前偏移量等信息,并关联到文件及其属性。

因此,当程序把fd交给read时,操作系统就能通过当前进程的描述符表找到对应对象,再进行读取。文件描述符只在当前进程的这张表里表示一个位置,不是某个磁盘文件永久不变的编号。


正常在终端中启动的程序,通常已经有三个标准描述符:

描述符名称通常的用途
0标准输入接收输入
1标准输出输出正常结果
2标准错误输出错误信息

它们在终端中运行时通常连接到终端,所以我们能通过键盘输入,在屏幕上看到输出。但0、1、2 并没有固定绑定键盘和屏幕,它们也可以指向普通文件等其他对象。

当 0、1、2 已经被占用,3 又是空闲的时,再打开一个文件,就会拿到描述符 3。这正好对应图中myfile的位置。

这里的分配规则是:使用当前最小的可用描述符编号。

3.2改变描述符指向的文件

如果把 0 号描述符关闭,再打开一个文件,会发生什么?

此时 0 是最小的空闲编号,新文件就会使用这个位置:

原来 0 号位置的连接被断开,新的myfile接到了这个位置上。程序之后仍然使用描述符 0,但操作对象已经变了。

如果这里以只读方式打开myfile,那么之后从 0 号描述符读取,就会读到myfile中的内容,这就是理解输入重定向的一个直观例子。


把同样的思路用在 1 号描述符上,就能理解输出重定向。

比如让 1 号描述符关联到一个普通文件,程序继续向标准输出写内容,内容就会进入这个文件。程序使用的描述符没有变,描述符关联的对象变了。

3.3使用 dup2 完成重定向

关闭一个描述符,再利用最小编号分配规则重新打开文件,可以帮助我们理解原理。实际安排指定的描述符时,使用dup2更直接。

它的两个主要参数是:

参数含义
oldfd已经打开的描述符,我们想使用它所关联的文件对象
newfd要调整的目标描述符

dup2会让newfd指向oldfd所指向的打开文件对象。如果newfd原来已经打开,它会在完成这个操作时关闭原有的关联。

例如,先以可写方式打开输出文件,把得到的描述符作为oldfd,把 1 作为newfd,就能让标准输出指向这个文件。如果要安排标准输入,则先以允许读取的方式打开文件,再把目标描述符设为 0。

这里要看清参数的方向:先写已有的描述符,再写想要改变的描述符。

操作完成后,两个描述符引用的是同一个打开文件对象,共用当前文件偏移量等状态。dup2复制的是描述符的关联关系,文件内容并不会因此复制一份。

如果打开文件时得到的是 3,随后把它复制到 1,那么在不再需要额外的 3 号描述符时,可以关闭 3。1 号描述符仍然保持着与这个文件的连接,可以继续用于输出。


4.小总结

前面已经看到,同样的 0、1、2,可以关联终端,也可以关联普通文件。程序通过描述符和统一的读写接口,就能访问不同的对象。

这就是理解 Linux“一切皆文件”的一个角度:把不同对象组织成可以通过文件接口访问的形式,让程序使用相近的操作方式。

这样,上层程序使用统一的接口,下层再根据对象的不同执行各自的操作。我们学习read、write时掌握的访问方式,也就能够用于更多类型的对象。


完

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询