目录
[一、chdir("/home/ranjiaju/test"); 函数解析](#一、chdir("/home/ranjiaju/test"); 函数解析)
[二、进程 cwd 的查看与验证:/proc/[pid]/cwd](#二、进程 cwd 的查看与验证:/proc/[pid]/cwd)
[三、无 chdir 时进程 cwd 的默认值](#三、无 chdir 时进程 cwd 的默认值)
[四、fopen("log.txt", "w") 与进程 cwd 的关联](#四、fopen("log.txt", "w") 与进程 cwd 的关联)
五、核心总结
[fopen 中以 "w" 的方式写入](#fopen 中以 "w" 的方式写入)[一、fwrite(message, strlen(message), 1, fp); 函数解析](#一、fwrite(message, strlen(message), 1, fp); 函数解析)
[二、"w" 模式写入的核心特性:文件清空](#二、"w" 模式写入的核心特性:文件清空)
[三、Shell 命令与 "w" 模式的底层关联](#三、Shell 命令与 "w" 模式的底层关联)
四、核心总结
[fopen 中以 "a" 的方式写入](#fopen 中以 "a" 的方式写入)[一、fopen 中以 "a" 方式写入的核心特性](#一、fopen 中以 "a" 方式写入的核心特性)
[二、Shell 重定向 >> 与 "a" 模式的关联](#二、Shell 重定向 >> 与 "a" 模式的关联)
三、核心总结
[系统调用 open](#系统调用 open)[一、umask(0); 解释](#一、umask(0); 解释)
[二、系统调用 open 解释](#二、系统调用 open 解释)
[三、open 函数参数详解](#三、open 函数参数详解)
[四、O_WRONLY | O_CREAT | O_TRUNC 含义](#四、O_WRONLY | O_CREAT | O_TRUNC 含义)
[五、O_WRONLY | O_CREAT | O_APPEND 含义](#五、O_WRONLY | O_CREAT | O_APPEND 含义)
[六、open 函数 flags 参数宏定义及常用组合表](#六、open 函数 flags 参数宏定义及常用组合表)
[七、系统调用 write 解释](#七、系统调用 write 解释)
[八、close(fd); 解释](#八、close(fd); 解释)
[深度刨析 open 函数的返回值](#深度刨析 open 函数的返回值)[一、open 函数的返回值](#一、open 函数的返回值)
[四、open 返回值与 write 函数参数的关联](#四、open 返回值与 write 函数参数的关联)
[一、重定向 > log.txt 的底层原理](#一、重定向 > log.txt 的底层原理)
创建文件的当前路径
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ myfile]$ pwd
/home/ranjiaju/test/learning-linux/myfile
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ myfile]$ ll
total 8
-rw-rw-r-- 1 ranjiaju ranjiaju 84 Dec 29 15:34 makefile
-rw-rw-r-- 1 ranjiaju ranjiaju 309 Dec 29 15:45 myproc.c
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ myfile]$ cat myproc.c
#include<stdio.h>
#include<string.h>
#include<unistd.h>
int main()
{
chdir("/home/ranjiaju/test");
printf("pid: %d\n", getpid());
FILE* fp = fopen("log.txt", "w");
if(fp == NULL)
{
perror("fopen");
return -1;
}
fclose(fp);
sleep(1000);
return 0;
}
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ myfile]$ make
gcc myproc.c -o myproc -std=c99
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ myfile]$ ./myproc
pid: 2250
// 同时查看 2250 进程中的 cwd,确实已经被 chdir 更改
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ myfile]$ ll /proc/2250/cwd
lrwxrwxrwx 1 ranjiaju ranjiaju 0 Dec 29 15:44 /proc/2250/cwd -> /home/ranjiaju/test
// 同时查看 test 目录下的文件
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ myfile]$ ll /home/ranjiaju/test
total 4
drwxrwxr-x 12 ranjiaju ranjiaju 4096 Dec 29 15:33 learning-linux
-rw-rw-r-- 1 ranjiaju ranjiaju 0 Dec 29 15:50 log.txt
一、chdir("/home/ranjiaju/test"); 函数解析
chdir(Change Directory)是 Linux 中用于修改当前进程工作目录 的系统调用,其核心作用是改变进程对 "当前路径" 的认知,影响后续所有相对路径的文件操作。下面从函数原型、执行逻辑、与 cd 命令的区别三个维度详细解析:
1. 函数原型与参数
#include <unistd.h>
int chdir(const char *path);
- 参数
path:目标目录的路径(绝对路径或相对路径均可),示例中使用绝对路径/home/ranjiaju/test,确保无论进程当前在哪个目录,都能准确切换到目标目录; - 返回值 :成功返回
0,失败返回-1并设置errno(如路径不存在、权限不足等)。
2. chdir 的执行逻辑
示例中进程执行 chdir("/home/ranjiaju/test") 后,内核会完成以下操作:
- 更新进程的
cwd属性 :内核在进程的 PCB(进程控制块)中维护一个名为cwd(Current Working Directory,当前工作目录)的属性,chdir会将该属性的值从进程启动时的目录(/home/ranjiaju/test/learning-linux/myfile)修改为/home/ranjiaju/test; - 不影响父进程 :
chdir仅修改当前进程的cwd,不会影响启动它的父进程(如 bash 终端),这与cd命令的行为一致(cd是 shell 内置命令,若为外部命令则无法改变 shell 的目录)。
3. 与 cd 命令的本质区别
| 特性 | chdir 函数 |
cd 命令 |
|---|---|---|
| 作用对象 | 当前进程(如示例中的 myproc) |
shell 进程本身(如 bash) |
| 作用范围 | 仅当前进程及其子进程(若有) | 仅当前 shell 会话 |
| 实现方式 | 系统调用,直接修改内核中进程的 cwd 属性 |
shell 内置命令,本质是 shell 进程调用 chdir |
| 对父进程影响 | 无(子进程修改 cwd 不影响父进程) |
无(shell 是独立进程,修改自身 cwd 不影响其父进程) |
二、进程 cwd 的查看与验证:/proc/[pid]/cwd
Linux 提供了 **/proc 文件系统 **(虚拟文件系统,存储内核和进程的实时信息),通过该文件系统可直接查看任意进程的 cwd。示例中通过以下命令验证了 chdir 的效果:
ll /proc/2250/cwd
/proc/2250:对应 PID 为 2250 的进程的信息目录,每个进程在/proc下都有一个以其 PID 命名的子目录;cwd:是一个符号链接 ,指向该进程当前的工作目录。示例中输出lrwxrwxrwx 1 ranjiaju ranjiaju 0 Dec 29 15:44 /proc/2250/cwd -> /home/ranjiaju/test,明确显示进程 2250 的cwd已被chdir修改为/home/ranjiaju/test。
三、无 chdir 时进程 cwd 的默认值
若示例中删除 chdir("/home/ranjiaju/test"); 这行代码,进程 2250 的 cwd 会默认继承自其父进程(bash 终端)。具体规则如下:
- 进程启动时的
cwd继承 :当通过./myproc启动进程时,新进程的cwd会完全继承父进程(bash)的cwd。示例中执行./myproc时,bash 终端的当前目录是/home/ranjiaju/test/learning-linux/myfile,因此进程 2250 的默认cwd就是该目录; - 验证方式 :若删除
chdir,执行ll /proc/2250/cwd会输出-> /home/ranjiaju/test/learning-linux/myfile,与 bash 终端的当前目录一致。
四、fopen("log.txt", "w") 与进程 cwd 的关联
fopen 打开文件时,若使用相对路径 (如 log.txt),其查找文件的基准路径是当前进程的 cwd,而非程序所在的目录或其他路径。示例中的执行逻辑清晰地证明了这一点:
chdir修改cwd后 :进程 2250 的cwd为/home/ranjiaju/test,fopen("log.txt", "w")会在该目录下查找log.txt;若文件不存在,则创建新文件(示例中ll /home/ranjiaju/test显示log.txt已被创建);若文件存在,则清空文件内容并以写模式打开;- 无
chdir时 :进程 2250 的cwd为/home/ranjiaju/test/learning-linux/myfile,fopen("log.txt", "w")会在该目录下创建或打开log.txt。
关键补充:相对路径 vs 绝对路径
- 相对路径 :以当前进程的
cwd为基准进行路径解析,如log.txt、../test.log等; - 绝对路径 :以根目录
/为基准进行路径解析,不受进程cwd的影响,如/home/ranjiaju/test/log.txt。
五、核心总结
chdir的核心作用 :修改当前进程的cwd(当前工作目录),影响后续所有相对路径的文件操作,且仅对当前进程有效;- 进程
cwd的默认值 :进程启动时默认继承父进程的cwd,可通过chdir主动修改; fopen与cwd的关系 :使用相对路径时,fopen以进程的cwd为基准查找文件;使用绝对路径时,不受cwd影响;cwd的查看方式 :通过/proc/[pid]/cwd符号链接可实时查看任意进程的当前工作目录。
这一机制体现了 Linux 进程设计的灵活性:进程可通过 chdir 自主管理其文件操作的基准路径,使得相对路径的使用更加灵活和可预测。
fopen 中以 "w" 的方式写入
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ myfile]$ ll
total 8
-rw-rw-r-- 1 ranjiaju ranjiaju 84 Dec 29 15:34 makefile
-rw-rw-r-- 1 ranjiaju ranjiaju 344 Dec 29 16:17 myproc.c
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ myfile]$ cat myproc.c
#include<stdio.h>
#include<string.h>
#include<unistd.h>
int main()
{
printf("pid: %d\n", getpid());
FILE* fp = fopen("log.txt", "w");
if(fp == NULL)
{
perror("fopen");
return -1;
}
const char* message = "hello Linux";
fwrite(message, strlen(message), 1, fp);
fclose(fp);
return 0;
}
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ myfile]$ make
gcc myproc.c -o myproc -std=c99
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ myfile]$ ./myproc
pid: 2781
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ myfile]$ ll
total 24
-rw-rw-r-- 1 ranjiaju ranjiaju 11 Dec 29 16:19 log.txt
-rw-rw-r-- 1 ranjiaju ranjiaju 84 Dec 29 15:34 makefile
-rwxrwxr-x 1 ranjiaju ranjiaju 8752 Dec 29 16:19 myproc
-rw-rw-r-- 1 ranjiaju ranjiaju 344 Dec 29 16:17 myproc.c
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ myfile]$ cat log.txt
hello Linux[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ myfile]$
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ myfile]$ echo "abcd"
abcd
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ myfile]$ echo "abcd" > log.txt
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ myfile]$ cat log.txt
abcd
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ myfile]$ >log.txt
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ myfile]$ cat log.txt
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ myfile]$
一、fwrite(message, strlen(message), 1, fp); 函数解析
fwrite 是 C 语言标准库中用于二进制数据写入文件的函数,其核心作用是将指定内存区域的数据按块写入文件流。下面结合函数原型和示例代码,详细拆解每个参数的意义及执行逻辑:
1. 函数原型
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
2. 参数详解(对应示例中的调用)
| 参数 | 示例中的值 | 核心意义 |
|---|---|---|
ptr |
message |
待写入数据的内存起始地址 。示例中 message 是字符串 "hello Linux" 的首地址,fwrite 从该地址开始读取数据。 |
size |
strlen(message) |
每个数据块的字节大小 。示例中 strlen(message) 计算得字符串长度为 11(不含末尾的 \0),因此每个数据块大小为 11 字节。 |
nmemb |
1 |
要写入的数据块数量。示例中为 1,表示只写入 1 个大小为 11 字节的数据块。 |
stream |
fp |
目标文件的文件指针 ,指向已通过 fopen 打开的文件流(示例中为 log.txt)。 |
3. 执行逻辑与结果
示例中 fwrite 的实际行为是:
- 从
message指向的内存地址读取 1 个数据块(每个块 11 字节),即完整读取字符串"hello Linux"(不含\0,因为strlen不计算结束符); - 将这 11 字节数据写入
fp对应的log.txt文件; - 函数返回值为成功写入的数据块数量(示例中为 1),若返回值小于
nmemb,则表示写入异常(如磁盘空间不足)。
二、"w" 模式写入的核心特性:文件清空
C 语言中 fopen 函数的 "w"(write,写入模式)是文件操作的基础模式之一,其核心行为是:
- 文件存在时 :打开文件并清空所有原有内容(文件大小变为 0),后续写入从文件开头开始;
- 文件不存在时 :创建新文件,权限默认与系统相关(示例中为
rw-rw-r--)。
这种 "先清空再写入" 的特性,是导致示例中文件内容被覆盖的根本原因。
三、Shell 命令与 "w" 模式的底层关联
用户观察到的 Shell 命令行为(如 echo "abcd" > log.txt 和 >log.txt),其底层原理与 C 语言的 "w" 模式完全一致:
1. echo "abcd" > log.txt:重定向写入的本质
>的作用 :Shell 中的>是输出重定向运算符 ,其底层行为等价于:- 以
"w"模式打开目标文件log.txt(存在则清空,不存在则创建); - 将命令的标准输出(示例中为
"abcd\n")写入该文件; - 关闭文件。
- 以
- 结果 :
log.txt原有内容"hello Linux"被清空,替换为新内容"abcd"。
2. >log.txt:仅清空文件的特殊重定向
>单独使用 :当>后无命令输出时(如>log.txt),其行为简化为:- 以
"w"模式打开log.txt(存在则清空,不存在则创建); - 无任何数据写入(因为没有命令输出);
- 立即关闭文件。
- 以
- 结果 :文件仅被清空(大小变为 0),无新内容写入,与 C 语言中
fopen("log.txt", "w")后直接fclose(fp)的效果完全一致。
四、核心总结
fwrite的核心逻辑 :按 "数据块大小 × 块数量" 写入文件,示例中通过strlen精准控制了写入的字符串长度(不含\0)。- "w" 模式的本质:打开文件时强制清空原有内容,是 "覆盖写入" 的底层实现。
- Shell 重定向与 "w" 模式的一致性 :
>和>log.txt均通过 "以w模式打开文件" 实现内容覆盖或清空,与 C 语言的文件操作原理相通。
这种设计保证了文件写入的可预测性:无论文件是否存在,"w" 模式和 Shell 重定向都能确保从 "干净的文件开头" 开始写入,避免旧数据干扰。
fopen 中以 "a" 的方式写入
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ myfile]$ ll
total 8
-rw-rw-r-- 1 ranjiaju ranjiaju 92 Dec 29 16:32 makefile
-rw-rw-r-- 1 ranjiaju ranjiaju 346 Dec 29 16:32 myproc.c
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ myfile]$ cat myproc.c
#include<stdio.h>
#include<string.h>
#include<unistd.h>
int main()
{
printf("pid: %d\n", getpid());
FILE* fp = fopen("log.txt", "a");
if(fp == NULL)
{
perror("fopen");
return -1;
}
const char* message = "hello Linux\n";
fwrite(message, strlen(message), 1, fp);
fclose(fp);
return 0;
}
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ myfile]$ make
gcc myproc.c -o myproc -std=c99
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ myfile]$ ./myproc
pid: 3052
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ myfile]$ cat log.txt
hello Linux
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ myfile]$ ./myproc
pid: 3054
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ myfile]$ cat log.txt
hello Linux
hello Linux
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ myfile]$ echo "hello linux" >> log.txt
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ myfile]$ cat log.txt
hello Linux
hello Linux
hello linux
一、fopen 中以 "a" 方式写入的核心特性
在 C 语言中,fopen 函数的 "a"(append,追加模式)是文件写入的另一种基础模式,其核心行为与 "w"(写入模式)形成鲜明对比:
- 文件存在时 :打开文件但不清空原有内容 ,而是将文件读写指针移动到文件末尾,后续所有写入操作都从文件末尾开始,实现 "追加" 效果;
- 文件不存在时 :与
"w"模式相同,会创建新文件。
这种 "不清空、只追加" 的特性,是示例中多次执行 ./myproc 后文件内容不断累加的根本原因。
二、Shell 重定向 >> 与 "a" 模式的关联
我们观察到的 echo "hello linux" >> log.txt 行为,其底层原理与 C 语言的 "a" 模式完全一致:
>>的作用 :Shell 中的>>是追加输出重定向运算符 ,其底层行为等价于:- 以
"a"模式打开目标文件log.txt(存在则保留内容并将指针移至末尾,不存在则创建); - 将命令的标准输出(示例中为
"hello linux\n")写入文件末尾; - 关闭文件。
- 以
- 结果 :新内容
"hello linux"被追加到log.txt已有内容之后,而非覆盖原有内容。
三、核心总结
"a"模式的本质:打开文件时不清空内容,仅将读写指针移至末尾,实现追加写入;>>与"a"的关系 :Shell 的>>重定向底层通过调用"a"模式实现,两者行为完全一致,均为 "追加写入"。
这一机制体现了文件操作的分层设计:Shell 命令的重定向功能是对底层 C 语言文件操作模式(如 "a" 模式)的封装和简化。
系统调用 open
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ myfile]$ ll
total 8
-rw-rw-r-- 1 ranjiaju ranjiaju 92 Dec 29 16:32 makefile
-rw-rw-r-- 1 ranjiaju ranjiaju 797 Dec 29 20:46 myproc.c
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ myfile]$ cat myproc.c
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
int main()
{
umask(0);
// int fd = open("log.txt", O_WRONLY|O_CREAT|O_TRUNC, 0666);
int fd = open("log.txt", O_WRONLY|O_CREAT|O_APPEND, 0666);
if(fd < 0)
{
printf("open file error\n");
return -1;
}
const char* message = "hell open";
write(fd, message, strlen(message));
close(fd);
return 0;
}
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ myfile]$ make
gcc myproc.c -o myproc -std=c99
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ myfile]$ ./myproc
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ myfile]$ ll
total 24
-rw-rw-rw- 1 ranjiaju ranjiaju 9 Dec 29 20:47 log.txt
-rw-rw-r-- 1 ranjiaju ranjiaju 92 Dec 29 16:32 makefile
-rwxrwxr-x 1 ranjiaju ranjiaju 8696 Dec 29 20:47 myproc
-rw-rw-r-- 1 ranjiaju ranjiaju 797 Dec 29 20:46 myproc.c
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ myfile]$ cat log.txt
hell open[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ myfile]$
一、umask(0); 解释
umask (User File-creation Mask) 是一个进程级别的权限掩码 。其核心作用是:在创建新文件或目录时,从请求的权限中 "屏蔽" 掉某些权限位。
工作原理:
- 默认行为 :每个进程都有一个默认的
umask值(通常是0002或0022)。这个值决定了新创建文件的默认权限。 - 计算方式 :新文件的最终权限 =
open函数中请求的权限 -umask的值。- 注意 :这里的 "减" 不是数学减法,而是按位取反后再按位与的操作。
- 公式为:
最终权限 = (请求的权限) & (~umask)。
代码中的 umask(0):
umask(0)的作用是将当前进程的权限掩码设置为 0。- 这意味着不屏蔽任何权限 。因此,新文件的最终权限将完全等于
open函数中mode参数所请求的权限。 - 在代码中,因为
umask被设置为 0,所以log.txt的最终权限就是0666。
二、系统调用 open 解释
open 是一个非常基础且强大的 Linux 系统调用 ,用于打开或创建一个文件,并返回一个文件描述符(File Descriptor, fd),后续的文件读写等操作都通过这个 fd 来进行。
其函数原型如下:
int open(const char *pathname, int flags, mode_t mode);
三、open 函数参数详解
1. pathname (路径名)
- 示例值 :
"log.txt" - 含义 : 要打开或创建的文件的路径名。可以是相对路径(如
log.txt)或绝对路径(如/home/user/log.txt)。
2. flags (标志位)
这是 open 函数最灵活的部分,它通过位掩码 (bitmask)的方式,使用 | (按位或) 操作符来组合多个选项。flags 大致可以分为以下几类:
访问模式 (Access Mode) - 必须指定一个
O_RDONLY: 以只读方式打开文件。O_WRONLY: 以只写方式打开文件。O_RDWR: 以可读可写方式打开文件。
创建和截断模式 (Creation and Truncation Flags)
O_CREAT: 如果文件不存在 ,则创建 它。如果这个标志被使用,open函数必须提供第三个参数mode来指定新文件的权限。O_TRUNC: 如果文件已存在 并且是以写方式打开的,则清空 文件内容(将文件大小截断为 0)。这类似于fopen中的"w"模式。O_APPEND: 以追加 方式打开文件。每次写入数据时,都会将数据添加到文件末尾。这类似于fopen中的"a"模式。
其他常用模式
O_NONBLOCK: 以非阻塞模式打开文件。对于普通文件,这个标志通常没有效果,但对于管道、套接字等特殊文件非常重要。
3. mode (权限)
- 示例值 :
0666 - 含义 : 当
flags中包含O_CREAT时,此参数用于设置新创建文件的权限。它是一个八进制数。
四、O_WRONLY | O_CREAT | O_TRUNC 含义
这是 flags 参数的一个组合,其含义是:
O_WRONLY: 程序想要以只写的方式操作这个文件。O_CREAT: 在打开之前,系统会检查文件是否存在。如果不存在,就创建一个新文件。O_TRUNC: 如果文件已存在 ,则在打开的同时清空其所有内容。
所以,O_WRONLY | O_CREAT | O_TRUNC 的完整意思是:"请打开这个文件用于写入。如果文件不存在,请先创建它。如果文件已存在,请先清空它的内容。" 这完全等同于 C 标准库中的 fopen(path, "w")。
五、O_WRONLY | O_CREAT | O_APPEND 含义
这是 flags 参数的另一个组合,其含义是:
O_WRONLY: 程序想要以只写的方式操作这个文件。O_CREAT: 在打开之前,系统会检查文件是否存在。如果不存在,就创建一个新文件。O_APPEND: 如果文件已存在 ,则在打开的同时,将文件的读写指针移动到文件末尾。
所以,O_WRONLY | O_CREAT | O_APPEND 的完整意思是:"请打开这个文件用于写入。如果文件不存在,请先创建它。如果文件已存在,请将写入位置设置在文件末尾。" 这完全等同于 C 标准库中的 fopen(path, "a")。
六、open 函数 flags 参数宏定义及常用组合表
| 宏定义 (Macro) | 含义 (Meaning) | 类别 (Category) |
|---|---|---|
O_RDONLY |
只读方式打开 | 访问模式 |
O_WRONLY |
只写方式打开 | 访问模式 |
O_RDWR |
读写方式打开 | 访问模式 |
O_CREAT |
如果文件不存在则创建 | 创建 / 状态标志 |
O_TRUNC |
如果文件存在且为写模式,则清空文件 | 创建 / 状态标志 |
O_APPEND |
每次写入前,将文件指针移至末尾 | 创建 / 状态标志 |
O_NONBLOCK |
非阻塞模式打开 | 其他 |
| 常用组合 (Common Combinations) | 含义 (Meaning) | 等效 fopen 模式 |
| `O_WRONLY | O_CREAT | O_TRUNC` |
| `O_WRONLY | O_CREAT | O_APPEND` |
| `O_RDWR | O_CREAT | O_TRUNC` |
| `O_RDWR | O_CREAT | O_APPEND` |
O_RDWR |
读写,文件必须已存在 | "r+" |
七、系统调用 write 解释
write 是用于将数据从内存写入已打开文件的系统调用。
其函数原型如下:
ssize_t write(int fd, const void *buf, size_t count);
fd:open函数返回的文件描述符,用于标识要写入的文件。buf: 指向要写入数据的内存缓冲区的指针。count: 要写入的字节数。
返回值:
- 成功: 返回实际写入的字节数。
- 失败 : 返回
-1,并设置errno。
在代码中,write(fd, message, strlen(message)); 的作用是将 message 指针指向的字符串(长度为 strlen(message))写入到文件描述符 fd 所代表的文件中。
八、close(fd); 解释
close 是用于关闭一个已打开文件的系统调用。
其函数原型如下:
int close(int fd);
fd: 要关闭的文件的文件描述符。
核心作用:
- 释放资源: 通知操作系统,程序不再使用该文件,可以回收与之相关的内核资源(如文件表项、内存等)。
- 保证数据完整性: 关闭文件时,操作系统会确保所有在缓冲区中的数据都被刷新(flush)到物理磁盘上。
- 避免资源泄漏 : 每个进程能打开的文件数量是有限的。如果不关闭文件,文件描述符会一直被占用,最终导致程序无法打开新文件,这被称为文件描述符泄漏。
在代码中,close(fd); 是一个必不可少的步骤,它确保了文件被正确关闭,所有写入的数据都已安全保存,并且文件描述符被释放以供他用。
深度刨析 open 函数的返回值
一、open 函数的返回值
open 函数的返回值是一个 int 类型的整数,称为文件描述符(File Descriptor, fd)。它是内核为进程分配的、用于唯一标识一个已打开文件的索引。
- 成功时:返回一个非负整数(通常是从 3 开始的小整数,0、1、2 分别被标准输入、标准输出、标准错误占用)。
- 失败时 :返回
-1,并通过errno变量存储具体的错误原因(如文件不存在、权限不足等)。
文件描述符是进程与内核之间操作文件的 "凭证",后续的 read、write、close 等系统调用都需要通过它来指定操作的目标文件。
二、"先描述,再组织":操作系统管理文件的核心思想
操作系统管理任何资源(包括文件)的逻辑遵循 "先描述,再组织" 的原则:
-
描述 :当一个文件被打开时,内核会创建一个 **
struct file结构体 **,用于存储该文件的所有相关信息,相当于文件在内存中的 "身份证"。- 结构体中包含的信息示例:
- 文件的路径、状态(如是否可读 / 可写);
- 文件的当前读写偏移量(
f_pos); - 指向文件索引节点(
struct inode)的指针(inode存储文件的元数据,如大小、权限、磁盘位置等); - 用于链接其他
struct file的指针(prev和next)。
- 结构体中包含的信息示例:
-
组织 :当一个进程打开多个文件时,内核会将这些文件对应的
struct file结构体通过双链表的形式组织起来。- 每个
struct file结构体中包含struct file* prev和struct file* next指针,分别指向链表中的前一个和后一个文件结构体; - 这种组织方式使得内核可以高效地遍历、查找和管理进程打开的所有文件。
- 每个
三、进程与文件描述符表的关联
进程的内核数据结构 task_struct(进程控制块)中包含一个 struct files_struct* files 指针,该指针指向一个名为 files_struct 的结构体,其核心作用是管理进程打开的所有文件。
files_struct 结构体中包含一个关键的数组 ------文件描述符表,定义类似:
struct files_struct {
// ... 其他成员 ...
struct file* fd_array[]; // 文件描述符表,存储指向 struct file 的指针
};
- 数组下标 :即文件描述符(
fd),是open函数的返回值; - 数组元素 :是指向对应
struct file结构体的指针。
例如,当进程调用 open("log.txt", ...) 成功后,内核会:
- 创建一个
struct file结构体描述log.txt; - 将该结构体的地址存入
fd_array数组的某个空闲下标(如 3); - 返回下标
3作为文件描述符。
四、open 返回值与 write 函数参数的关联
write 函数的第一个参数要求传入文件描述符,其本质是让进程通过该下标在 fd_array 数组中找到对应的 struct file 指针,进而定位到要写入的文件。
流程示例 :当执行 write(fd, message, strlen(message)) 时:
- 进程通过
task_struct找到files_struct指针; - 以
fd为下标,在fd_array数组中获取指向log.txt的struct file指针; - 内核根据该结构体中的信息(如文件偏移量、
inode指针),将数据写入文件的正确位置。
核心总结
open的返回值 :是文件描述符(fd),即文件描述符表fd_array的下标;- 内核管理逻辑 :通过
struct file结构体描述文件,用双链表组织多个文件,再通过files_struct将文件与进程关联; write的参数意义 :通过fd作为下标,在文件描述符表中找到目标文件的struct file指针,从而完成写入操作。
这种 "描述 - 组织 - 索引" 的设计,使得操作系统能够高效、安全地管理进程与文件之间的交互。
重定向
Linux 重定向 的本质是修改进程文件描述符的指向,通过改变数据的输入输出目标,实现命令或程序的输入输出流重定向。以下从底层原理、命令语法、系统调用三个维度展开解释。
一、底层原理
Linux 系统遵循一切皆文件的设计哲学,进程启动时会默认打开三个标准文件描述符(File Descriptor,FD),每个文件描述符对应内核中的文件描述符表项,表项指向系统级的文件表(包含文件状态、偏移量等),最终关联到物理文件或设备。
- 标准输入 (stdin) :文件描述符为0,默认指向终端输入设备(/dev/tty),负责接收进程的输入数据。
- 标准输出 (stdout) :文件描述符为1,默认指向终端输出设备(/dev/tty),负责输出进程的正常执行结果。
- 标准错误 (stderr) :文件描述符为2,默认指向终端输出设备(/dev/tty),负责输出进程的错误信息。
重定向的底层逻辑是修改进程文件描述符表中特定 FD 的指向 ,将原本指向终端设备的 FD,改为指向普通文件、管道、设备文件等其他文件对象。例如执行ls > file.txt时,shell 会先打开 file.txt 文件,然后将当前进程的 1 号 FD(stdout)的指向从终端设备替换为 file.txt,后续ls命令的输出数据会通过 1 号 FD 写入 file.txt,而非终端。
二、命令层面的重定向符号
shell 提供了多种重定向符号,用于控制不同类型的数据流方向,核心符号及功能如下:
| 符号 | 功能说明 | 对应文件描述符 | 示例 |
|---|---|---|---|
| > | 标准输出覆盖重定向,目标文件存在则清空 | 1>(可省略 1) | ls > file.txt |
| >> | 标准输出追加重定向,目标文件存在则追加内容 | 1>>(可省略 1) | echo "test" >> file.txt |
| < | 标准输入重定向,从文件读取输入数据 | 0<(可省略 0) | cat < file.txt |
| 2> | 标准错误覆盖重定向,单独重定向错误信息 | 2 | gcc test.c 2> error.log |
| 2>> | 标准错误追加重定向 | 2 | ./a.out 2>> error.log |
| &> | 标准输出 + 标准错误同时覆盖重定向 | 1+2 | ./a.out &> all.log |
| 2>&1 | 将标准错误重定向到标准输出的当前目标 | 2→1 | ./a.out > all.log 2>&1 |
关键注意点:2>&1 的顺序不可颠倒 ,必须先执行> all.log将 1 号 FD 指向 all.log,再执行2>&1将 2 号 FD 的指向同步到 1 号 FD 的目标,否则会导致错误重定向失效。
三、系统调用层面的实现
shell 的重定向功能,底层依赖 Linux 提供的dup() 和dup2() 两个核心系统调用,用于复制文件描述符,从而修改 FD 的指向。
-
核心函数定义
#include <unistd.h> // 复制oldfd,返回当前最小可用的新文件描述符 int dup(int oldfd); // 将oldfd复制到newfd,若newfd已打开则先关闭,复制成功后oldfd和newfd指向同一文件 int dup2(int oldfd, int newfd); -
实现逻辑(以 stdout 重定向为例) 执行
command > file.txt时,shell 内部执行步骤为:- 调用open("file.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644) 打开文件,得到一个新的文件描述符(假设为 3)。
- 调用dup2(3, 1),将 1 号 FD(stdout)的指向替换为 3 号 FD 对应的 file.txt,此时 1 号和 3 号 FD 指向同一文件。
- 调用close(3) 关闭多余的 3 号 FD,避免文件描述符泄露。
- 调用execve() 执行
command,command的所有 stdout 输出(write (1, ...))都会写入 file.txt。
-
代码示例(C 语言实现 stdout 重定向)
#include <stdio.h> #include <unistd.h> #include <fcntl.h> int main() { // 打开文件,模式为写入、当文件不存在时创建此文件、每次进入文件清空文件内容 int fd = open("output.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644); if (fd == -1) { perror("open failed"); return 1; } // 将stdout(1号FD)重定向到fd对应的文件 dup2(fd, 1); close(fd); // 关闭多余的文件描述符 // printf默认输出到stdout,此时会写入output.txt printf("This is redirected output\n"); return 0; }编译运行结果:
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ myfile]$ ll total 8 -rw-rw-r-- 1 ranjiaju ranjiaju 92 Dec 29 16:32 makefile -rw-rw-r-- 1 ranjiaju ranjiaju 1376 Dec 30 14:10 myproc.c [ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ myfile]$ cat myproc.c #include<stdio.h> #include<string.h> #include<unistd.h> #include<sys/types.h> #include<sys/stat.h> #include<fcntl.h> int main() { // 打开文件,模式为写入、当文件不存在时创建此文件、每次进入文件清空文件内容 int fd = open("output.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644); if (fd == -1) { perror("open failed"); return 1; } // 将stdout(1号FD)重定向到fd对应的文件 dup2(fd, 1); close(fd); // 关闭多余的文件描述符 // printf默认输出到stdout,此时会写入output.txt printf("This is redirected output\n"); return 0; } [ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ myfile]$ make gcc myproc.c -o myproc -std=c99 [ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ myfile]$ ./myproc [ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ myfile]$ ll total 24 -rw-rw-r-- 1 ranjiaju ranjiaju 92 Dec 29 16:32 makefile -rwxrwxr-x 1 ranjiaju ranjiaju 8648 Dec 30 14:10 myproc -rw-rw-r-- 1 ranjiaju ranjiaju 1376 Dec 30 14:10 myproc.c -rw-r--r-- 1 ranjiaju ranjiaju 26 Dec 30 14:11 output.txt [ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ myfile]$ cat output.txt This is redirected output
总结
- 底层原理 进程默认打开 3 个文件描述符:0(标准输入 stdin)、1(标准输出 stdout)、2(标准错误 stderr),默认均指向终端。重定向通过改变这三类 FD 的指向,让数据读写目标切换为普通文件、设备等。
- 命令符号 核心符号包括覆盖输出
>、追加输出>>、输入重定向<、错误重定向2>,以及合并输出2>&1/&>。需注意 **2>&1必须放在标准输出重定向之后 **,否则失效。 - 系统调用实现 依赖dup()/dup2() 系统调用,流程为:open 目标文件获取新 FD → dup2 将新 FD 复制到目标编号(如 1)→ close 多余 FD → execve 执行命令,完成 FD 指向替换。
用户级缓冲区
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ Buffer]$ ll
total 8
-rw-rw-r-- 1 ranjiaju ranjiaju 92 Jan 2 13:33 makefile
-rw-rw-r-- 1 ranjiaju ranjiaju 409 Jan 2 13:33 myproc.c
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ Buffer]$ cat myproc.c
#include<stdio.h>
#include<string.h>
#include<unistd.h>
int main()
{
const char* fstr = "hello fwrite";
const char* str = "hello write";
// c语言接口
printf("hello printf"); //stdout->1
fprintf(stdout, "hello fprintf"); //stdout->1
fwrite(fstr, strlen(fstr), 1, stdout); //stdout->1
// 操作系统接口
write(1, str, strlen(str));
close(1);
return 0;
}
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ Buffer]$ make
gcc myproc.c -o myproc -std=c99
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ Buffer]$ ./myproc
hello write[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ Buffer]$
一、用户级缓冲区的概念
用户级缓冲区是 C 语言标准库在用户空间 为每个文件流(如stdout)维护的一块内存区域。其核心作用是:将多次小规模的 I/O 操作合并为一次大规模的系统调用,减少 CPU 在内核态与用户态之间的切换开销,从而提升程序执行效率。
用户级缓冲区与内核缓冲区的关系:
- 用户级缓冲区 :位于用户空间,由 C 库管理,程序可通过
fflush等函数主动控制刷新; - 内核缓冲区:位于内核空间,由操作系统管理,用于暂存数据并优化磁盘 I/O(如延迟写入以合并多次磁盘操作)。
C 语言 I/O 接口(如printf)会先将数据写入用户级缓冲区,满足刷新条件后再通过系统调用(如write)将数据从用户级缓冲区拷贝到内核缓冲区,最终由操作系统写入物理设备(如显示器)。
二、缓冲区的三种刷新方式
C 语言标准库根据文件流的类型,将用户级缓冲区分为三种刷新策略:
| 刷新方式 | 触发条件 | 适用场景 | 示例 |
|---|---|---|---|
| 无缓冲 | 数据写入后立即刷新,直接调用系统调用 | 标准错误流stderr(需立即显示错误信息) |
fprintf(stderr, "error");(立即输出) |
| 行缓冲 | 缓冲区满或遇到换行符\n时刷新 |
标准输出流stdout(终端输出场景) |
printf("hello\n");(遇\n刷新) |
| 全缓冲 | 缓冲区满或调用fflush时刷新 |
文件流(如fopen打开的普通文件) |
fprintf(fp, "data");(缓冲区满后刷新) |
三、代码解析
代码中同时使用了 C 语言 I/O 接口和操作系统接口,通过对比可清晰体现用户级缓冲区的作用:
1. C 语言 I/O 接口(用户级缓冲区)
printf("hello printf"); // 行缓冲,无`\n`,不刷新
fprintf(stdout, "hello fprintf"); // 行缓冲,无`\n`,不刷新
fwrite(fstr, strlen(fstr), 1, stdout); // 行缓冲,无`\n`,不刷新
- 行缓冲特性 :
stdout默认是行缓冲,上述代码均未以\n结尾,因此数据仅写入用户级缓冲区,未触发刷新; - 进程结束时的刷新 :若程序正常结束(无
close(1)),C 库会在main函数返回前自动刷新所有用户级缓冲区,此时所有数据会通过系统调用写入内核并显示到终端。
2. 操作系统接口(无用户级缓冲区)
write(1, str, strlen(str)); // 直接调用系统调用,无用户级缓冲区
- 无缓冲特性 :
write是操作系统提供的系统调用,直接将数据从用户空间拷贝到内核缓冲区,不经过 C 库的用户级缓冲区; - 即时输出:数据写入内核缓冲区后,由操作系统根据自身策略(如终端设备通常会立即刷新)显示到终端,因此 "hello write" 会立即输出。
3. close(1)的影响
close(1); // 关闭标准输出文件描述符(1号文件)
- 文件描述符:0、1、2 分别对应标准输入、标准输出、标准错误,是内核为进程默认分配的文件描述符;
- 内核视角 :
close是系统调用,仅操作内核中的文件描述符表,无法感知用户级缓冲区的存在; - 数据丢失 :当
close(1)执行时,内核会关闭标准输出对应的文件。此时,C 语言接口写入用户级缓冲区的数据尚未刷新到内核,由于文件已关闭,后续即使进程结束,C 库也无法通过系统调用将数据写入内核,导致数据丢失。
四、用户级缓冲区的意义
-
提高效率:
- 若每次
printf都直接调用write,会导致频繁的系统调用(CPU 从用户态切换到内核态),开销较大; - 用户级缓冲区将多次小数据写入合并为一次系统调用,显著减少切换次数,提升程序执行效率。
- 若每次
-
支持格式化输出:
- C 语言 I/O 接口(如
printf)提供丰富的格式化功能(如%d、%s),这些功能需要在用户空间完成数据拼接和格式转换; - 用户级缓冲区为格式化操作提供了暂存空间,待数据格式处理完成后再统一写入内核。
- C 语言 I/O 接口(如
五、核心总结
- 用户级缓冲区:C 库在用户空间维护的内存区域,用于合并 I/O 操作以提升效率;
- 刷新方式 :行缓冲(
stdout)需\n或缓冲区满时刷新,全缓冲(文件)需缓冲区满或fflush刷新,无缓冲(stderr)立即刷新; close(1)的影响:关闭标准输出后,用户级缓冲区的数据因无法通过系统调用写入内核而丢失;- 存在意义:减少系统调用开销,支持格式化输出,平衡效率与功能需求。
这种 "用户级缓冲 + 内核级缓冲" 的分层设计,是操作系统与应用程序协同优化 I/O 性能的典型体现。
全缓冲与fork
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ Buffer]$ ll
total 8
-rw-rw-r-- 1 ranjiaju ranjiaju 92 Jan 2 13:33 makefile
-rw-rw-r-- 1 ranjiaju ranjiaju 414 Jan 2 14:25 myproc.c
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ Buffer]$ cat myproc.c
#include<stdio.h>
#include<string.h>
#include<unistd.h>
int main()
{
const char* fstr = "hello fwrite\n";
const char* str = "hello write\n";
// c语言接口
printf("hello printf\n"); //stdout->1
fprintf(stdout, "hello fprintf\n"); //stdout->1
fwrite(fstr, strlen(fstr), 1, stdout); //stdout->1
// 操作系统接口
write(1, str, strlen(str));
fork();
return 0;
}
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ Buffer]$ make
gcc myproc.c -o myproc -std=c99
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ Buffer]$ ./myproc > log.txt
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ Buffer]$ cat log.txt
hello write
hello printf
hello fprintf
hello fwrite
hello printf
hello fprintf
hello fwrite
一、重定向 > log.txt 的底层原理
当执行 ./myproc > log.txt 时,Shell(命令行解释器)在启动 myproc 进程之前,做了以下关键操作:
-
打开文件 :Shell 调用
open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666)。O_WRONLY:以只写方式打开。O_CREAT:如果log.txt不存在,则创建它。O_TRUNC:如果log.txt已存在,则清空其所有内容。
-
重定向文件描述符 :Shell 将
open返回的新文件描述符(假设是 3)复制 到文件描述符1的位置。这个操作通过dup2(new_fd, 1)系统调用完成。- 这意味着,在
myproc进程看来,文件描述符1(标准输出)不再指向终端,而是指向了log.txt文件。
- 这意味着,在
-
启动子进程 :Shell 调用
execve启动myproc程序。新的myproc进程继承了这个已经被修改过的文件描述符表。
核心影响 :由于标准输出(stdout)现在指向一个普通文件而不是终端,其缓冲模式发生了改变:
- 默认行为 :当标准输出连接到终端时,是行缓冲 模式(遇到
\n刷新)。 - 重定向后 :当标准输出重定向到文件时,变为全缓冲模式(缓冲区满或进程结束时才刷新)。
二、程序执行流程与缓冲区行为
现在我们来跟踪 myproc 进程的执行:
1. C 语言 I/O 接口(printf, fprintf, fwrite)
printf("hello printf\n");
fprintf(stdout, "hello fprintf\n");
fwrite(fstr, strlen(fstr), 1, stdout);
- 目标 :这些函数都试图向
stdout(文件描述符 1)写入数据。 - 缓冲行为 :因为
stdout现在是全缓冲 模式,所以这些函数并不会立即调用系统调用。它们会将数据("hello printf\n", "hello fprintf\n", "hello fwrite\n")暂存到用户级缓冲区 中。此时,这些数据只存在于进程的用户空间内存中,并未写入log.txt文件。
2. 系统调用接口(write)
write(1, str, strlen(str));
- 目标 :直接向文件描述符
1写入数据。 - 无缓冲行为 :
write是一个系统调用,它绕过了 C 语言的用户级缓冲区 。它会直接将数据("hello write\n")从用户空间拷贝到内核缓冲区,并最终由操作系统写入log.txt文件。 - 结果 :
log.txt文件中此时已经有了第一行内容:hello write。
3. fork() 的关键作用
fork();
- 创建子进程 :
fork()创建了一个与父进程几乎完全相同的子进程。 - 继承与共享 :子进程会继承 父进程的文件描述符表和用户级缓冲区。这意味着,在
fork的那一刻,子进程的用户级缓冲区里也包含了尚未刷新的 C 语言 I/O 数据("hello printf\n", "hello fprintf\n", "hello fwrite\n")。 - 写时拷贝(Copy-on-Write, COW) :在
fork之后,父进程和子进程的用户级缓冲区是共享 的,内核只为它们维护一份物理内存。只有当其中一个进程试图修改 这块内存时(例如,向缓冲区写入新数据,或者在刷新时标记缓冲区为已清空),内核才会为该进程复制一份新的内存副本,然后再执行修改。
三、进程结束与缓冲区刷新
fork() 之后,父进程和子进程会各自独立地继续执行,直到 main 函数返回。
-
父进程结束 :当父进程执行到
return 0;时,C 语言运行时库会自动调用fflush来刷新所有打开的流。- 它会尝试刷新用户级缓冲区中的数据。这个 "刷新" 操作本身就是一种修改。
- 触发写时拷贝 :由于父进程要修改共享的用户级缓冲区,内核会为它创建一个私有副本。
- 执行刷新 :父进程将自己副本中的数据("hello printf\n", "hello fprintf\n", "hello fwrite\n")通过系统调用写入
log.txt。
-
子进程结束 :几乎在同一时间,子进程也执行到
return 0;。- 它同样会尝试刷新自己的用户级缓冲区。
- 使用自己的副本:因为父进程已经触发了写时拷贝,子进程现在也拥有了自己的一份用户级缓冲区副本(内容与父进程刷新前完全相同)。
- 执行刷新 :子进程将自己副本中的数据同样写入
log.txt。
四、最终结果分析
- 系统调用
write:只在父进程中执行了一次,并且是在fork之前,所以hello write在log.txt中只出现一次。 - C 语言 I/O 接口 :它们的数据被暂存在用户级缓冲区中。
fork导致父进程和子进程各有一份相同的缓冲区数据。当两个进程分别结束并刷新时,它们各自将缓冲区内容写入文件,导致 C 语言 I/O 的内容在log.txt中出现两次。
log.txt 内容解析:
hello write <-- 来自父进程在 fork() 前的 write() 调用
hello printf \
hello fprintf |-- 来自父(子)进程结束时的缓冲区刷新
hello fwrite /
hello printf \
hello fprintf |-- 来自子(父)进程结束时的缓冲区刷新
hello fwrite /
核心总结
- 重定向改变缓冲模式 :
> log.txt将stdout从终端(行缓冲)改为文件(全缓冲),导致 C 语言 I/O 数据暂存。 - 系统调用无缓冲 :
write直接与内核交互,不受用户级缓冲区影响。 fork继承缓冲区:子进程完整继承了父进程的用户级缓冲区数据。- 写时拷贝与双重刷新:父、子进程在结束时分别刷新自己的缓冲区副本,导致 C 语言 I/O 内容被写入两次。
这个案例完美地展示了用户级缓冲区、I/O 重定向和fork写时拷贝机制如何协同工作并产生非直观的结果。