先说结论:
- 文件读写真正靠的是系统调用,
fopen/fread/fwrite只是 libc 的封装 文件描述符就是files_struct里指针数组的下标,分配规则是最小未使用下标- 而
重定向的本质只是把下标fd指向的文件对象换掉,一行dup2就能完成
一、理解"文件":一切皆文件
文件在磁盘里,磁盘是永久性存储的外设,所以对文件的操作本质就是对外设的输入输出,简称 IO。广义看,Linux 下一切皆文件------键盘、显示器、网卡、管道、socket 都被抽象成文件,好处是只用一套 API:几乎所有"读"用 read,几乎所有"改"用 write。
另一个结论:文件 = 属性+ 内容,文件操作只有改内容、改属性两类;对文件的操作本质是进程对文件的操作,真正的读写靠系统调用而不是 C 库函数。文件名不带路径时进程怎么知道写去哪?看 /proc/[pid]:
bash
ls /proc/533463 -l
# cwd -> /home/hyb/io 当前工作目录
# exe -> /home/hyb/io/myProc 可执行文件全路径
本小节复盘:

二、系统 I/O 接口:open / read / write / close
2.1 标志位怎么传
系统接口常用一个 int 同时传多个开关,每一位代表一个含义,函数里用 flags & ONE 判断:
c
#define ONE 0001 //二进制0001
#define TWO 0002 //二进制0010
// func(ONE | TWO) -> (flags & ONE) && (flags & TWO) both true
特点:每个宏只有一位有效且所在位置不同
2.2 open 的参数
c
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
// flags:O_RDONLY / O_WRONLY / O_RDWR 三选一
// O_CREAT(不存在则创建,需配合 mode)、O_APPEND(追加)、O_TRUNC
// 成功返回新的文件描述符,失败返回 -1
只有需要创建文件时才传 mode,它表示新文件的默认权限,实际权限还要被 umask 修正,所以实验代码常先写 umask(0)。
2.3 读写与返回值
c
umask(0);
int fd = open("myfile", O_WRONLY | O_CREAT, 0644);
write(fd, msg, len); // 返回实际写入的字节数
read(fd, buf, len); // 返回实际读到的字节数,0 表示读完
close(fd);
read/write/lseek/close 与 C 的 fread/fwrite/fseek/fclose 一一对应,前者是系统调用,后者是库函数。
本小节复盘:

三、文件描述符:本质是数组下标
3.1 默认的 0、1、2
进程默认打开三个描述符:0 标准输入、1 标准输出、2 标准错误,对应键盘、显示器、显示器。于是可以直接对着数字读写:
c
read(0, buf, sizeof(buf)); // 从键盘读
write(1, buf, strlen(buf)); // 写到显示器
write(2, buf, strlen(buf)); // 写到标准错误
3.2 分配规则:最小未使用下标
打开文件时系统要在内存里建一个 file 结构体来描述它,进程则通过 files_struct 里的指针数组关联,数组下标就是文件描述符。实验最能说明问题:
c
int fd = open("myfile", O_RDONLY);
printf("fd: %d\n", fd); // 输出 3
close(0); // 先关掉 0
fd = open("myfile", O_RDONLY);
printf("fd: %d\n", fd); // 输出 0
结论一句话:找数组里当前没被使用的最小下标,作为新 fd。这也解释了 0、1、2 一旦空出来,为何会被后来打开的文件抢占。
3.3 内核链路
c
struct task_struct { ... struct files_struct *files; ... };
struct file {
struct inode *f_inode;
const struct file_operations *f_op; // 函数指针表
atomic_long_t f_count; // 引用计数
loff_t f_pos; // 当前读写位置
};
链路是 task_struct → files_struct → 指针数组 → struct file → file_operations,其中 file_operations 的成员几乎全是函数指针(read、write、open......),把系统调用与设备驱动关联起来。这就是"一切皆文件"的内核依据:设备各自实现 read、write,但通过 f_op 回调,开发者拿着一个 fd 就能操作几乎所有资源。
本小节复盘:

四、重定向:换个指向
4.1 现象:关了 1 会怎样
c
close(1);
int fd = open("myfile", O_WRONLY | O_CREAT, 00644);
printf("fd: %d\n", fd);
fflush(stdout);
本该出现在屏幕上的内容跑到了 myfile 里,且 fd 正好是 1。这就是输出重定向,日常对应 >、>>、<。
4.2 dup2 完成替换
c
int dup2(int oldfd, int newfd); // 让 newfd 指向 oldfd 的文件对象
先 open 拿到新文件,再 dup2(fd, 1)。此时 printf 往 stdout 写,stdout 底层找的还是 fd 1,只是下标 1 已从显示器换成 myfile,输出便落到文件里。
本小节复盘:

五、总结
- 文件:一切皆文件,操作只有改内容和改属性两类,底层靠系统调用;
- 系统接口:
open读写标志三选一,加O_CREAT/O_APPEND,read/write返回实际字节数; - 文件描述符:
files_struct数组下标,0/1/2 是标准流,分配规则为"最小未使用下标"; - 重定向:
dup2(fd, 1)换指向,必须在子进程里做,exec后依然有效;
感谢阅读,我们下篇见。