资讯详情

【Linux】系统I/O

📅 2026/10/19 7:14:21 | 华诺云谱 👁 阅读
【Linux】系统I/O
目录两个细节关于fopen的操作总结系统I\Oopenwritecloseread标志位的传递文本写入和二进制写入的理解文件描述符初步理解文件描述符在内核怎么样发挥作用重定向一个现象dup2文件重定向的补充两个细节关于fopen的操作如果以w方式打开文件的时候内部有内容的就会先清空文件看一下操作。那么对应到命令行操作的时候利用echo 输入内容文件名的方式输入内容会先清空文件,再写入新的内容。以a方式打开不会清空会在后面追加。类似于命令行echo 内容文件名信息写到文件里面要加上\0吗——不用\0是C语言的内容和文件没有关系。总结因为shell是C语言写的因此我们可以推测出这种命令行和fopen有着调用和被调用的关系。系统I\Ofopen,ifstream等流式是语言层的方案其实系统的接口open才是打开文件的底层方案。open#include sys/types.h #include sys/stat.h #include fcntl.h int open(const char *pathname, int flags); int open(const char *pathname, int flags, mode_t mode);参数含义pathname要打开或创建的文件路径如myfile或/etc/passwdflags打开方式如只读、只写、创建等mode权限仅在使用了O_CREAT时有效返回值成功返回新的文件描述符整数失败返回-1并设置errnowrite#include unistd.h ssize_t write(int fd, const void *buf, size_t count);参数含义fd文件描述符就是open的返回值buf指向要写入的数据缓冲区的指针内存地址count期望写入的字节数不是字符数中文字符通常占多个字节返回值含义 0成功返回实际写入的字节数。注意这个数字可能小于你请求的count比如磁盘满了。-1失败并设置errno比如没有写权限、磁盘满等。#include stdio.h #include fcntl.h #include unistd.h #include string.h int main() { int fd open(test.txt, O_WRONLY | O_CREAT | O_TRUNC, 0644); if (fd 0) { perror(open); return 1; } const char *msg Hello write!\n; // strlen(msg) 不包含 \0所以不会把 \0 写进文件 ssize_t n write(fd, msg, strlen(msg)); if (n 0) { perror(write); } else { printf(实际写入了 %zd 个字节\n, n); } close(fd); return 0; }close#include unistd.h int close(int fd);参数含义fd要关闭的文件描述符返回值含义0成功-1失败比如传入了一个已经关闭的fd或无效的fdclose的本质是断开进程文件描述符表与内核file结构体的连接。释放资源让内核的引用计数减 1。当引用计数降为 0 时内核才会真正释放该文件占用的所有内存和资源。刷新数据关闭文件时内核会把该文件在内核缓冲区中还没写入磁盘的数据脏页刷新到磁盘或者交给系统异步刷盘。释放 fd 编号关闭后这个数字比如 3就空出来了下次open新文件时会优先分配这个最小的空闲编号。注意一定要检查返回值虽然close很少失败但在网络编程或写入大量数据后如果close失败可能导致数据没有真正写入磁盘。所以严谨的代码应当检查close的返回值。进程退出自动关闭当一个进程退出时操作系统会自动关闭该进程打开的所有文件描述符。readssize_t read(int fd, void *buf, size_t count);参数fd是文件描述符buf是把数据读到哪里的内存地址count是期望读取的字节数。返回值 0实际读到的字节数。0代表读到了文件末尾EOF。-1读取失败比如文件被删了或者fd不对。标志位的传递先看一下手写的代码#include stdio.h #define ONE_FLAG (10) // 0000 0000 0000...0000 0001 #define TWO_FLAG (11) // 0000 0000 0000...0000 0010 #define THREE_FLAG (12) // 0000 0000 0000...0000 0100 #define FOUR_FLAG (13) // 0000 0000 0000...0000 1000 void Print(int flags) { if(flags ONE_FLAG) { printf(One!\n); } if(flags TWO_FLAG) { printf(Two\n); } if(flags THREE_FLAG) { printf(Three\n); } if(flags FOUR_FLAG) { printf(Four\n); } } int main() { Print(ONE_FLAG); printf(\n); Print(ONE_FLAG | TWO_FLAG); printf(\n); Print(ONE_FLAG | TWO_FLAG | THREE_FLAG); printf(\n); Print(ONE_FLAG | TWO_FLAG | THREE_FLAG | FOUR_FLAG); printf(\n); Print(ONE_FLAG | FOUR_FLAG); printf(\n); return 0; }这个代码可以预测得到open的falgs,类似运用了对应的宏代表读写创造文件等。1. 访问模式必须且只能选一个标志含义O_RDONLY只读打开O_WRONLY只写打开O_RDWR读写打开2. 附加标志可选标志含义O_CREAT若文件不存在则创建它。使用此标志必须提供第三个参数mode来指定权限。O_TRUNC若文件已存在且以写方式打开则将其长度截断为 0清空内容。O_APPEND追加模式。每次写操作前文件偏移量都会自动移动到文件末尾。O_EXCL配合O_CREAT使用。若文件已存在则报错防止覆盖。#include stdio.h #include string.h #include unistd.h #include sys/types.h #include sys/stat.h #include fcntl.h int main() { umask(0);//小知识点设置掩码默认就是666不需要002 int fd open(log.txt, O_CREAT | O_WRONLY | O_TRUNC, 0666); //打开文件的时候如果没有就创建可以写入同时要清空原来的内容再写入 int fd open(log.txt, O_CREAT | O_WRONLY | O_APPEND, 0666); //APPEND就是不清空就要追加了 if(fd 0) { perror(open); return 1; } printf(fd: %d\n, fd); const char *msg hello bit\n; int cnt 5; while(cnt) { // 当做字符来写 write(fd, msg, strlen(msg)); cnt--; } close(fd); return 0; }文本写入和二进制写入的理解#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_CREAT | O_WRONLY | O_TRUNC, 0666); //系统不关心你的文件“模式”只认操作标识 //int fd open(log.txt, O_CREAT | O_WRONLY | O_APPEND, 0666); if(fd 0) { perror(open); return 1; } printf(fd: %d\n, fd); //const char *msg abcd; int cnt 1; int a 1234567; while(cnt) { // 当做字符来写 ---系统不关心你分几次写它只看到连续的字节流 char buffer[16]; sprintf(buffer, sizeof(buffer), %d, a); write(fd, buffer, strlen(buffer)); cnt--; }//无论你怎么转成字符串进去的只是字节 close(fd); return 0; }当你调用write时系统内核的态度是“我不管你写的是printf格式化出来的字符串、是一个图片、还是一段二进制代码。我只知道你有fd你给我一个内存地址buffer你告诉我写多少个字节strlen我就负责把这些字节原封不动地顺着文件描述符塞进磁盘里。其他的一概不管。”文件描述符上述代码的fd就是文件描述符初步理解#include stdio.h #include stdlib.h #include string.h #include unistd.h #include sys/types.h #include sys/stat.h #include fcntl.h int main() { umask(0); int fd1 open(log1.txt, O_CREAT | O_WRONLY | O_TRUNC, 0666); int fd2 open(log2.txt, O_CREAT | O_WRONLY | O_TRUNC, 0666); int fd3 open(log3.txt, O_CREAT | O_WRONLY | O_TRUNC, 0666); int fd4 open(log4.txt, O_CREAT | O_WRONLY | O_TRUNC, 0666); if(fd1 0)exit(1); if(fd2 0)exit(1); if(fd3 0)exit(1); if(fd4 0)exit(1); printf(fd1: %d\n, fd1); printf(fd2: %d\n, fd2); printf(fd3: %d\n, fd3); printf(fd4: %d\n, fd4); close(fd1); close(fd2); close(fd3); close(fd4); }最后文件描述符打印出来分别是3,4,5,6那012呢描述符宏设备0stdin标准输入键盘1stdout标准输出显示器2stderr标准错误显示器他们怎么封装的——和熟悉的FILE*有关FILE是一个结构体封装了文件描述符总结这个fd是系统级别的OS只认他他的值决定了文件的流。文件描述符在内核怎么样发挥作用1. 建立连接图上的红色标号 1 和 6用户层调用open(log.txt)。OS 系统层收到指令在磁盘上找到了log.txt。如图所示OS 会在内存中为这个打开的文件创建一个庞大的struct file结构体图中右侧的方框。这个结构体里包含了文件的所有属性、读写位置、甚至缓存区Buffer。同时OS 会去磁盘把文件的一部分内容预加载图注 5到内核的“缓冲区”中为了后面读文件提速。2. 分配门牌号图上的红色标号 2 和 3既然有了struct fileOS 要把这个结构体交给谁呢交给当前进程。看图中间进程的task_struct里有一个files_struct里面装着fd_array。OS 从下标 0 开始找发现 0, 1, 2 被占用了于是把新创建的struct file的地址填入下标为3的位置。此时open函数返回了整数3给用户。这个3就是 fd。3. 读写数据读文件流程你执行read(fd, ...)。OS 看到fd 3立刻去当前进程的fd_array中找第 3 号位置。通过第 3 号位置里的指针图注 3 的箭头精准找到了刚才创建的struct file。然后 OS 把内核缓冲区里的数据拷贝到你用户层的buffer里。4. 关闭文件你执行close(fd)。OS 直接把fd_array第 3 号位置的指针清空置为 NULL。当这个struct file没有其他进程引用时OS 销毁它释放资源。这个 3 号下标就空出来了下次open又会优先分配它。重定向在 Linux 中输入输出重定向完全依赖于底层文件描述符fd的分配规则。回想一下我们之前说的规则open打开文件时系统总是分配当前未被使用的、最小的 fd。close(1); int fd open(log.txt, O_CREAT | O_WRONLY | O_TRUNC, 0666); printf(fd: %d\n, fd);一个现象#include stdio.h #include stdlib.h #include string.h #include unistd.h #include sys/types.h #include sys/stat.h #include fcntl.h int main() { close(1); int fd open(log.txt, O_CREAT | O_WRONLY | O_TRUNC, 0666); printf(fd: %d\n, fd); close(fd); return 0; }屏幕上什么都不会打印log.txt文件会被创建但里面大概率也是空的或者只有部分内容。这个和缓冲区有关。dup2#include unistd.h int dup2(int oldfd, int newfd);参数含义oldfd已经打开的、有效的文件描述符源newfd你想要覆盖的目标文件描述符目标成功返回newfd目标描述符失败返回-1#include stdio.h #include stdlib.h #include string.h #include unistd.h #include sys/types.h #include sys/stat.h #include fcntl.h int main() { int fd open(log.txt, O_CREAT | O_WRONLY | O_TRUNC, 0666); if(fd 0) exit(1); dup2(fd, 1); close(fd); printf(fd: %d\n, fd); printf(hello world\n); printf(hello world\n); printf(hello world\n); fprintf(stdout, hello stdout\n); fprintf(stdout, hello stdout\n); fprintf(stdout, hello stdout\n); fprintf(stdout, hello stdout\n); const char *msg hello world\n; write(fd, msg, strlen(msg)); return 0; }进程替换会影响重定向的结果吗当我们调用fork()创建子进程时子进程会复制父进程的文件描述符表fd_array。当子进程调用exec替换成新程序比如ls时OS 会做两件事把新程序ls的代码和数据加载到内存替换掉旧的代码和数据。重点来了exec绝对不会动进程的task_struct也绝对不会动里面的files_struct文件描述符表。所以无论你把当前程序替换成什么0号、1号、2号 fd 依然稳稳地指向你之前用dup2设定的目标文件。文件重定向的补充#include stdio.h #include stdlib.h #include string.h #include unistd.h #include sys/types.h #include sys/stat.h #include fcntl.h int main(int argc, char *argv[]) { if(argc ! 2) exit(1); int fd open(argv[1], O_RDONLY); if(fd 0) exit(1); dup2(fd, 0); close(fd); while(1) { char buffer[64]; if(!fgets(buffer, sizeof(buffer), stdin))break; printf(%s, buffer); } return 0; }正常情况下stdin0 号 fd指向的是键盘。当你执行./a.out log.txt时fd拿到了log.txt。dup2(fd, 0)把 0 号的下标强行指向了log.txt。这样一来程序里所有的fgets(..., stdin)都不会再去等键盘输入了而是去读取 log.txt 文件里的内容。读完后遇到 EOF文件末尾fgets返回 NULL循环结束。./a.out 1log.txt 21 # 把屏幕输出1和错误2写进文件
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑