目录
[1.1 内核中的文件管理模型](#1.1 内核中的文件管理模型)
[1.2 内核源码验证:关键结构体的关联](#1.2 内核源码验证:关键结构体的关联)
[1.2.1 task_struct 与 files_struct 的关联](#1.2.1 task_struct 与 files_struct 的关联)
[1.2.2 files_struct 与 fd_array 的关联](#1.2.2 files_struct 与 fd_array 的关联)
[1.2.3 file 结构体的核心成员](#1.2.3 file 结构体的核心成员)
[1.3 默认打开的三个文件描述符](#1.3 默认打开的三个文件描述符)
[2.1 验证 1:新打开文件的默认 fd](#2.1 验证 1:新打开文件的默认 fd)
[实战代码:新打开文件的 fd 分配](#实战代码:新打开文件的 fd 分配)
[2.2 验证 2:关闭默认 fd 后的分配规则](#2.2 验证 2:关闭默认 fd 后的分配规则)
[实战代码:关闭标准输入(fd=0)后分配 fd](#实战代码:关闭标准输入(fd=0)后分配 fd)
[2.3 扩展:文件描述符的上限](#2.3 扩展:文件描述符的上限)
[三、重定向的本质:修改 fd_array 的指向](#三、重定向的本质:修改 fd_array 的指向)
[3.1 输出重定向的实现原理](#3.1 输出重定向的实现原理)
[3.2 追加重定向(>>)与输入重定向(<)](#3.2 追加重定向(>>)与输入重定向(<))
[3.3 使用 dup2 系统调用:更优雅的重定向](#3.3 使用 dup2 系统调用:更优雅的重定向)
[dup2 函数原型](#dup2 函数原型)
[实战代码:使用 dup2 实现输出重定向](#实战代码:使用 dup2 实现输出重定向)
[实战代码:使用 dup2 实现输入重定向](#实战代码:使用 dup2 实现输入重定向)
[四、文件描述符与 FILE 结构体:库函数与系统调用的关系](#四、文件描述符与 FILE 结构体:库函数与系统调用的关系)
[5.1 FILE 结构体的本质:封装文件描述符和缓冲区](#5.1 FILE 结构体的本质:封装文件描述符和缓冲区)
[5.2 库函数与系统调用的区别:缓冲区的影响](#5.2 库函数与系统调用的区别:缓冲区的影响)
[情况 1:直接运行(输出到显示器,行缓冲)](#情况 1:直接运行(输出到显示器,行缓冲))
[情况 2:重定向到文件(全缓冲)](#情况 2:重定向到文件(全缓冲))
前言
在 Linux 系统编程中,文件描述符(File Descriptor,简称 fd)是一个贯穿始终的核心概念。无论是文件读写、设备操作,还是网络通信、进程间通信,都离不开文件描述符的身影。很多初学者在接触文件描述符时,往往会被 "小整数" 的表象迷惑,不清楚它的本质、分配规则以及与系统调用的关联。
本文将从文件描述符的定义出发,深入内核源码结构,拆解分配规则、重定向原理,结合大量C 语言实战代码,带你彻底搞懂文件描述符的核心逻辑,让你从 "会用" 升级到 "懂原理",轻松应对 Linux 系统编程中的各类 IO 场景。下面就让我们正式开始吧!
一、文件描述符是什么?不止是一个小整数
提到文件描述符,最直观的感受就是open系统调用返回的一个非负整数(比如 3、4、5)。但这个小整数背后,隐藏着 Linux 内核对文件的管理逻辑。要理解文件描述符,我们需要从 "进程如何与文件关联" 说起。
1.1 内核中的文件管理模型
当我们调用open系统调用打开或创建一个文件时,内核会完成三件关键事情:
- 创建 file 结构体 :内核会在内存中创建一个
file结构体,用于存储该文件的元数据,包括文件路径、权限、读写位置、引用计数,以及最重要的 ------ 文件操作函数指针集合(file_operations)。- 维护进程的文件表 :每个进程都有一个进程控制块(
task_struct),其中包含一个指向**files_struct结构体的指针。files_struct是进程的 "文件表",核心成员是一个名为fd_array的指针数组,数组中的每个元素都指向一个file**结构体。- 分配文件描述符 :内核会在**
fd_array**中找到一个未被使用的最小下标,将第一步创建的file结构体指针存入该下标对应的位置,这个下标就是文件描述符。
简单来说:文件描述符本质上是进程files_struct结构体中fd_array数组的下标 。通过这个下标,进程可以快速找到对应的**file**结构体,进而调用内核提供的文件操作函数,完成对文件的读写等操作。
我们可以用一个通俗的比喻理解:**fd_array**就像一个 "文件抽屉柜",每个抽屉都有一个编号(下标),抽屉里放着对应文件的 "说明书"(file结构体)。文件描述符就是抽屉编号,进程通过编号找到抽屉,再按照说明书操作文件。
1.2 内核源码验证:关键结构体的关联
要真正理解文件描述符的本质,最好的方式是查看内核源码中相关结构体的定义。以下是基于 Linux 内核 3.10 版本的关键结构体位置(可通过uname -a查看内核版本):
task_struct(进程控制块):/usr/src/kernels/3.10.0-1160.71.1.el7.x86_64/include/linux/sched.hfiles_struct(进程文件表):/usr/src/kernels/3.10.0-1160.71.1.el7.x86_64/include/linux/fdtable.hfile(文件元数据):/usr/src/kernels/3.10.0-1160.71.1.el7.x86_64/include/linux/fs.h
1.2.1 task_struct 与 files_struct 的关联
task_struct是描述进程的核心结构体,其中包含一个指向**files_struct**的指针:
cpp
struct task_struct {
// 其他成员省略...
struct files_struct *files; // 指向进程的文件表
// 其他成员省略...
};
这个指针将进程与它打开的所有文件关联起来,进程通过files指针访问自己的文件表。
1.2.2 files_struct 与 fd_array 的关联
files_struct结构体的核心是**fd_array数组,用于存储file**结构体指针:
cpp
struct files_struct {
// 其他成员省略...
struct file __rcu *fd_array[NR_OPEN_DEFAULT]; // 文件描述符数组
// 其他成员省略...
};
其中NR_OPEN_DEFAULT是默认的文件描述符上限(通常为 1024),意味着一个进程默认最多可以打开 1024 个文件(文件描述符范围 0~1023)。
1.2.3 file 结构体的核心成员
file结构体存储了文件的关键信息,其中最核心的是f_op(文件操作函数指针集合)和f_pos(当前读写位置):
cpp
struct file {
// 其他成员省略...
const struct file_operations *f_op; // 文件操作函数指针集合
loff_t f_pos; // 当前读写位置
atomic_long_t f_count; // 引用计数
unsigned int f_flags; // 文件打开标志(如O_RDONLY、O_APPEND)
fmode_t f_mode; // 文件访问模式(只读、只写、读写)
// 其他成员省略...
};
f_op指向的**file_operations**结构体,包含了read、write、close等系统调用对应的函数指针,是内核实现 "一切皆文件" 的关键(后续会为大家详细介绍)。
1.3 默认打开的三个文件描述符
当一个进程启动时,内核会默认打开三个文件描述符,分别对应三个标准流,这也是为什么我们在编程时可以直接使用stdin、stdout、stderr的原因:
- 0:标准输入(stdin) :对应键盘设备,**fd_array[0]**指向键盘的
file结构体;- 1:标准输出(stdout) :对应显示器设备,**fd_array[1]**指向显示器的
file结构体;- 2:标准错误(stderr) :对应显示器设备,**fd_array[2]**指向显示器的
file结构体。

这三个文件描述符是进程的 "默认装备",我们可以直接通过它们进行输入输出操作。例如,直接使用read(0, ...)从键盘读取输入,使用write(1, ...)向显示器输出内容:
实战代码:使用默认文件描述符进行输入输出
cpp
#include <stdio.h>
#include <string.h>
#include <unistd.h>
int main() {
char buf[1024];
printf("请输入内容(输入后按回车):");
fflush(stdout); // 刷新标准输出缓冲区,确保提示语立即显示
// 从标准输入(fd=0)读取数据
ssize_t read_len = read(0, buf, sizeof(buf) - 1);
if (read_len < 0) {
perror("read error");
return 1;
}
// 给读取到的数据添加字符串结束符
buf[read_len] = '\0';
// 向标准输出(fd=1)写入数据
write(1, "你输入的内容(标准输出):", strlen("你输入的内容(标准输出):"));
write(1, buf, strlen(buf));
// 向标准错误(fd=2)写入数据
write(2, "你输入的内容(标准错误):", strlen("你输入的内容(标准错误):"));
write(2, buf, strlen(buf));
return 0;
}
编译运行:
bash
gcc -o default_fd default_fd.c
./default_fd
运行结果:
请输入内容(输入后按回车):hello fd!
你输入的内容(标准输出):hello fd!
你输入的内容(标准错误):hello fd!
可以看到,read(0)成功读取了键盘输入,write(1)和write(2)都向显示器输出了内容(因为标准输出和标准错误默认都指向显示器)。
二、文件描述符的分配规则:最小未使用下标
理解了文件描述符的本质后,我们自然会问:当我们新打开一个文件时,内核会分配哪个整数作为文件描述符?答案是:内核会在fd_array数组中,找到当前未被使用的最小下标,作为新的文件描述符。
这个规则看似简单,但却是很多高级特性(如重定向)的基础。我们通过几个实战代码来验证这个规则。
2.1 验证 1:新打开文件的默认 fd
进程启动时,0、1、2 三个文件描述符已被占用,因此新打开的第一个文件,其文件描述符应该是 3。
实战代码:新打开文件的 fd 分配
cpp
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main() {
// 以只读模式打开文件(文件不存在则创建)
int fd1 = open("test1.txt", O_RDWR | O_CREAT, 0644);
if (fd1 < 0) {
perror("open test1.txt error");
return 1;
}
printf("新打开文件test1.txt的fd:%d\n", fd1); // 预期输出3
// 再打开一个文件
int fd2 = open("test2.txt", O_RDWR | O_CREAT, 0644);
if (fd2 < 0) {
perror("open test2.txt error");
close(fd1);
return 1;
}
printf("新打开文件test2.txt的fd:%d\n", fd2); // 预期输出4
// 关闭fd1
close(fd1);
printf("已关闭fd1(%d)\n", fd1);
// 再打开一个文件,验证是否复用fd1
int fd3 = open("test3.txt", O_RDWR | O_CREAT, 0644);
if (fd3 < 0) {
perror("open test3.txt error");
close(fd2);
return 1;
}
printf("新打开文件test3.txt的fd:%d\n", fd3); // 预期输出3(复用已关闭的fd1)
// 关闭剩余文件描述符
close(fd2);
close(fd3);
return 0;
}
编译运行:
bash
gcc -o fd_allocate fd_allocate.c
./fd_allocate
运行结果:
新打开文件test1.txt的fd:3
新打开文件test2.txt的fd:4
已关闭fd1(3)
新打开文件test3.txt的fd:3
结果完全符合预期:新打开的文件优先使用最小的未使用下标,关闭 fd 后,后续打开的文件会复用该下标。
2.2 验证 2:关闭默认 fd 后的分配规则
如果我们关闭默认打开的 fd(如 0、1、2),再新打开文件,新文件的 fd 会复用这些被关闭的下标吗?答案是肯定的。
实战代码:关闭标准输入(fd=0)后分配 fd
cpp
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main() {
// 关闭标准输入(fd=0)
close(0);
printf("已关闭标准输入(fd=0)\n");
// 新打开一个文件
int fd = open("test4.txt", O_RDWR | O_CREAT, 0644);
if (fd < 0) {
perror("open test4.txt error");
return 1;
}
printf("新打开文件test4.txt的fd:%d\n", fd); // 预期输出0(复用关闭的fd=0)
close(fd);
return 0;
}
编译运行:
bash
gcc -o fd_reuse fd_reuse.c
./fd_reuse
运行结果:
已关闭标准输入(fd=0)
新打开文件test4.txt的fd:0
同样,如果关闭 fd=1(标准输出),新打开文件的 fd 会是 1;关闭 fd=2(标准错误),新打开文件的 fd 会是 2。这个特性正是重定向的核心原理。
2.3 扩展:文件描述符的上限
默认情况下,一个进程最多可以打开 1024 个文件(fd 范围 0~1023)。如果需要打开更多文件,可以通过以下两种方式修改:
-
临时修改 :通过ulimit -n 2048命令,将当前 shell 会话中进程的最大文件描述符数改为 2048(重启 shell 后失效);
-
永久修改 :修改
/etc/security/limits.conf文件,添加以下配置(需要重启系统生效):- soft nofile 2048 # 软限制
- hard nofile 4096 # 硬限制
其中,软限制是进程当前能使用的最大文件描述符数,硬限制是软限制的上限,普通用户可以在软限制和硬限制之间调整,root 用户可以修改硬限制。
三、重定向的本质:修改 fd_array 的指向
我们在 Linux 命令行中经常使用重定向符号(>、>>、<),例如ls -l > file.txt将命令输出写入文件,cat < file.txt从文件读取输入。这些重定向的本质,就是修改文件描述符对应的fd_array元素指向,将其从默认设备(键盘、显示器)指向目标文件。
3.1 输出重定向的实现原理
以**>(覆盖重定向)**为例,其实现步骤如下:
- 关闭标准输出(fd=1),此时
fd_array[1]变为空(未使用);- 打开目标文件(如
file.txt),根据文件描述符分配规则,新文件的 fd 会是 1(因为 fd=1 是当前最小的未使用下标);- 此时,
fd_array[1]指向file.txt的file结构体,而非原来的显示器;- 后续所有向 fd=1 写入的数据,都会写入
file.txt,而非显示器,从而实现输出重定向。

实战代码:手动实现输出重定向
cpp
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
int main() {
// 步骤1:关闭标准输出(fd=1)
close(1);
printf("已关闭标准输出(fd=1)\n"); // 这行不会输出到显示器,因为fd=1已关闭
// 步骤2:打开目标文件,fd会被分配为1(最小未使用下标)
int fd = open("redirect.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd < 0) {
perror("open redirect.txt error");
return 1;
}
printf("新打开文件的fd:%d\n", fd); // 输出到redirect.txt,而非显示器
// 步骤3:向stdout(底层使用fd=1)写入数据,实际写入redirect.txt
printf("这是重定向输出的内容\n");
fprintf(stdout, "这也是重定向输出的内容\n");
// 强制刷新缓冲区(避免缓冲区未刷新导致数据丢失)
fflush(stdout);
close(fd);
return 0;
}
编译运行:
bash
gcc -o redirect_demo redirect_demo.c
./redirect_demo
cat redirect.txt # 查看重定向文件内容
运行结果:
新打开文件的fd:1
这是重定向输出的内容
这也是重定向输出的内容
可以看到,原本应该输出到显示器的内容,全部写入了redirect.txt文件,这正是输出重定向的本质:修改 fd=1 对应的file结构体指针。
3.2 追加重定向(>>)与输入重定向(<)
追加重定向(>>)与覆盖重定向(>)的区别在于,打开文件时使用**O_APPEND标志,而非O_TRUNC**标志:
- O_TRUNC:文件存在时,清空文件内容;
- O_APPEND:文件存在时,保留文件内容,写指针指向文件末尾。
实战代码:手动实现追加重定向
cpp
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
int main() {
close(1);
// 打开文件时使用O_APPEND标志,实现追加重定向
int fd = open("append.txt", O_WRONLY | O_CREAT | O_APPEND, 0644);
if (fd < 0) {
perror("open append.txt error");
return 1;
}
printf("这是追加的第一行内容\n");
fprintf(stdout, "这是追加的第二行内容\n");
fflush(stdout);
close(fd);
return 0;
}
运行两次程序后,查看append.txt:
bash
./append_demo
./append_demo
cat append.txt
运行结果:
这是追加的第一行内容
这是追加的第二行内容
这是追加的第一行内容
这是追加的第二行内容
输入重定向(<)的原理类似,只是关闭的是标准输入(fd=0),再打开目标文件,让 fd=0 指向该文件,后续从 fd=0 读取的数据都会来自目标文件,而非键盘。
实战代码:手动实现输入重定向
cpp
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
int main() {
// 准备输入文件:向input.txt写入测试数据
int input_fd = open("input.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (input_fd < 0) {
perror("open input.txt error");
return 1;
}
const char *input_data = "hello input redirect!";
write(input_fd, input_data, strlen(input_data));
close(input_fd);
// 步骤1:关闭标准输入(fd=0)
close(0);
// 步骤2:打开input.txt,fd=0指向该文件
int fd = open("input.txt", O_RDONLY);
if (fd < 0) {
perror("open input.txt error");
return 1;
}
printf("新打开文件的fd:%d\n", fd); // 输出0
// 步骤3:从stdin(fd=0)读取数据,实际从input.txt读取
char buf[1024];
ssize_t read_len = read(0, buf, sizeof(buf) - 1);
if (read_len < 0) {
perror("read error");
close(fd);
return 1;
}
buf[read_len] = '\0';
printf("从标准输入读取到的内容:%s\n", buf); // 输出input.txt中的数据
close(fd);
return 0;
}
编译运行:
bash
gcc -o input_redirect input_redirect.c
./input_redirect
运行结果:
新打开文件的fd:0
从标准输入读取到的内容:hello input redirect!
3.3 使用 dup2 系统调用:更优雅的重定向
手动关闭 fd 再打开文件的方式虽然能实现重定向,但不够灵活。Linux 提供了**dup2**系统调用,可以直接将一个文件描述符的指向复制到另一个文件描述符,从而更优雅地实现重定向。
dup2 函数原型
cpp
#include <unistd.h>
int dup2(int oldfd, int newfd);
功能 :将**oldfd对应的file结构体指针,复制到newfd对应的fd_array位置。如果newfd已经打开,则先关闭newfd**。
- 参数 :
oldfd:已打开的文件描述符(源 fd);newfd:目标文件描述符(要修改的 fd);
- 返回值 :成功返回**
newfd**,失败返回-1。
实战代码:使用 dup2 实现输出重定向
cpp
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
int main() {
// 步骤1:打开目标文件(获取oldfd)
int oldfd = open("dup2_redirect.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (oldfd < 0) {
perror("open dup2_redirect.txt error");
return 1;
}
// 步骤2:使用dup2将oldfd的指向复制到newfd=1(标准输出)
int ret = dup2(oldfd, 1);
if (ret < 0) {
perror("dup2 error");
close(oldfd);
return 1;
}
// 此时,fd=1指向dup2_redirect.txt,输出重定向生效
printf("使用dup2实现重定向:这行内容会写入文件\n");
fprintf(stdout, "dup2重定向的第二行内容\n");
fflush(stdout);
// 关闭oldfd(newfd=1依然指向文件,不会影响重定向)
close(oldfd);
return 0;
}
编译运行:
bash
gcc -o dup2_demo dup2_demo.c
./dup2_demo
cat dup2_redirect.txt
运行结果:
使用dup2实现重定向:这行内容会写入文件
dup2重定向的第二行内容
dup2的优势在于:无需手动关闭**newfd,函数会自动处理;可以将任意oldfd的指向复制到newfd,灵活性更高。例如,要实现输入重定向,只需将文件的oldfd复制到newfd=0**即可。
实战代码:使用 dup2 实现输入重定向
cpp
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
int main() {
// 准备输入文件
int input_fd = open("dup2_input.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);
const char *data = "dup2 input redirect test";
write(input_fd, data, strlen(data));
close(input_fd);
// 打开输入文件,获取oldfd
int oldfd = open("dup2_input.txt", O_RDONLY);
if (oldfd < 0) {
perror("open dup2_input.txt error");
return 1;
}
// 将oldfd复制到newfd=0(标准输入),实现输入重定向
dup2(oldfd, 0);
// 从标准输入读取数据,实际从dup2_input.txt读取
char buf[1024];
ssize_t read_len = read(0, buf, sizeof(buf) - 1);
if (read_len < 0) {
perror("read error");
close(oldfd);
return 1;
}
buf[read_len] = '\0';
printf("读取到的内容:%s\n", buf); // 输出文件中的数据
close(oldfd);
return 0;
}
编译运行:
bash
gcc -o dup2_input dup2_input.c
./dup2_input
运行结果:
读取到的内容:dup2 input redirect test
四、文件描述符与 FILE 结构体:库函数与系统调用的关系
我们在 C 语言中使用的**FILE**结构体(如stdin、stdout、fopen返回的文件指针),与文件描述符密切相关。理解它们的关系,能帮助我们更清晰地区分库函数和系统调用。
5.1 FILE 结构体的本质:封装文件描述符和缓冲区
FILE结构体是 C 标准库(Glibc)提供的,其核心作用是封装文件描述符和用户级缓冲区,为用户提供更便捷、高效的 IO 操作。
FILE结构体的定义位于/usr/include/libio.h(简化版):
cpp
struct _IO_FILE {
int _fileno; // 封装的文件描述符
// 缓冲区相关指针
char *_IO_read_base; // 读缓冲区起始地址
char *_IO_read_ptr; // 读缓冲区当前指针
char *_IO_read_end; // 读缓冲区结束地址
char *_IO_write_base; // 写缓冲区起始地址
char *_IO_write_ptr; // 写缓冲区当前指针
char *_IO_write_end; // 写缓冲区结束地址
char *_IO_buf_base; // 缓冲区起始地址
char *_IO_buf_end; // 缓冲区结束地址
// 其他成员省略...
};
typedef struct _IO_FILE FILE;
可以看到,FILE结构体内部封装了_fileno(文件描述符),以及一系列与缓冲区相关的指针。C 库函数(如fread、fwrite)的底层,正是通过**_fileno**调用系统调用(read、write),并利用缓冲区减少系统调用次数,提高 IO 效率。
5.2 库函数与系统调用的区别:缓冲区的影响
库函数(如fwrite)和系统调用(如write)的核心区别在于是否有用户级缓冲区:
- 库函数(
fopen、fread、fwrite):带有用户级缓冲区,减少系统调用次数,效率更高;- 系统调用(
open、read、write):无用户级缓冲区,直接与内核交互,每次调用都会触发用户态到内核态的切换。
我们通过一个实验来验证这一点:
实战代码:库函数与系统调用的缓冲区差异
cpp
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main() {
const char *msg0 = "hello printf\n"; // fprintf使用的字符串
const char *msg1 = "hello fwrite\n"; // fwrite使用的字符串
const char *msg2 = "hello write\n"; // write使用的字符串
// 1. 直接输出到显示器(行缓冲)
printf("%s", msg0);
fwrite(msg1, strlen(msg1), 1, stdout);
write(1, msg2, strlen(msg2));
// 2. fork创建子进程
fork();
return 0;
}
情况 1:直接运行(输出到显示器,行缓冲)
编译运行:
bash
gcc -o buf_demo buf_demo.c
./buf_demo
运行结果:
hello printf
hello fwrite
hello write
原因 :输出到显示器时,库函数使用行缓冲,遇到换行符\n会立即刷新缓冲区,因此printf和fwrite的内容在fork前已输出,父子进程不会重复输出。
情况 2:重定向到文件(全缓冲)
bash
./buf_demo > buf_result.txt
cat buf_result.txt
运行结果:
hello write
hello printf
hello fwrite
hello printf
hello fwrite
原因:
- 重定向到文件后,库函数使用全缓冲,
printf和fwrite的内容被存入缓冲区,未立即刷新;fork创建子进程时,父子进程共享缓冲区(写时拷贝);- 进程退出时,缓冲区被刷新,父子进程各自输出缓冲区中的内容,因此
printf和fwrite的内容输出两次;write是系统调用,无用户级缓冲区,内容在fork前已写入文件,因此只输出一次。
这个实验充分说明:库函数的缓冲区是由 C 标准库提供的,系统调用没有用户级缓冲区。
总结
文件描述符是 Linux 系统编程的基础,掌握它的核心原理,能让你在后续的网络编程、进程间通信、驱动开发等领域事半功倍。如果本文对你有帮助,欢迎点赞、收藏、转发,也欢迎在评论区交流讨论~
