[Linux系统] 一切皆文件 | 缓冲区机制 | FILE 结构
先说结论一切皆文件不是口号而是内核把每个设备都装进struct file再用file_operations里的函数指针做回调于是开发者拿着一个 fd 就能读文件、读系统状态、读管道缓冲区是一块内存让低速外设和高速 CPU 协调工作——它由 C 标准库提供所以库函数有缓冲、write没有后面两个怪现象都源于此。一、一切皆文件Linux 的终极抽象在 Windows 里是文件的在 Linux 里也是文件Windows 里不是文件的进程、磁盘、显示器、键盘在 Linux 里同样被抽象成文件可以用访问文件的方式取到信息。管道是文件将来网络编程里的 socket接口也和文件一致。好处很直接一套 API 通吃。几乎所有读都能用read读文件、读系统状态、读管道几乎所有改都能用write改文件、改系统参数、写管道。二、内核凭什么做到struct file file_operations打开文件时操作系统会为它创建一个file结构体定义在include/linux/fs.h它同时属于所有设备structfile{structinode*f_inode;conststructfile_operations*f_op;// 函数指针表atomic_long_tf_count;// 引用计数unsignedintf_flags;// 打开权限fmode_tf_mode;// 访问模式loff_tf_pos;// 当前读写位置};关键在于f_op它指向的file_operations里除了owner几乎全是函数指针structfile_operations{loff_t(*llseek)(structfile*,loff_t,int);// 改读写位置ssize_t(*read)(structfile*,char__user*,size_t,loff_t*);ssize_t(*write)(structfile*,constchar__user*,size_t,loff_t*);int(*open)(structinode*,structfile*);int(*release)(structinode*,structfile*);};每个成员都对应一个系统调用。用户调read内核读出相应的函数指针并把控制权交出去设备驱动的工作就完成了。每个设备都可以有自己的 read、write 实现但通过这层回调开发者只用 file 就能调取系统绝大部分资源——这就是Linux 下一切皆文件的核心理解。本小节复盘和图示三、缓冲区给高速 CPU 配一条缓冲带缓冲区是内存空间的一部分在内存中预留一块存储空间用来缓冲输入或输出的数据。按对应设备方向分为输入缓冲区和输出缓冲区。为什么要引入它不设缓冲区时每次读写都要对磁盘发起系统调用而一次系统调用意味着 CPU 从用户空间切到内核空间、发生上下文切换CPU 时间是白花的频繁的磁盘访问会严重拖慢程序。有了缓冲机制从磁盘取信息时一次读入大量数据进缓冲区后续访问不再走系统调用取完再读下一批磁盘读写次数大幅减少打印机这类慢设备则先把文档送进缓冲区自己慢慢打CPU 转身去干别的事。所以缓冲区的本质就是一块用在设备和 CPU 之间、缓存数据的内存区让低速外设与高速 CPU 协调工作。3.1 三种缓冲类型全缓冲填满整个缓冲区才做 I/O 系统调用磁盘文件默认如此行缓冲遇换行符就刷新终端设备默认如此默认大小 1024无缓冲不缓存直接调系统调用stderr就是如此保证出错信息尽快显示。除默认规则外缓冲区满、执行flush、进程结束也会触发刷新。复盘和图示四、两个实验看穿缓冲行为4.1 重定向后 printf 的内容去哪了close(1);intfdopen(log.txt,O_WRONLY|O_CREAT|O_TRUNC,0666);printf(hello world: %d\n,fd);close(fd);// cat log.txt —— 空的明明把 1 号描述符重定向到了文件为什么文件里什么都没有因为 1 号指向磁盘文件后刷新方式从行缓冲变成了全缓冲内容没填满缓冲区自然不会落盘加一句fflush(stdout)强制刷新即可。另一个验证把 2 号描述符重定向到文件用perror输出close(2);intfdopen(log.txt,O_WRONLY|O_CREAT|O_TRUNC,0666);perror(hello world);// 不用 fflush直接写进文件因为stderr没有缓冲区内容立刻落盘反向验证了无缓冲这一类型。4.2 fork 之后为什么输出两遍printf(%s,hello printf\n);fwrite(hello fwrite\n,13,1,stdout);write(1,hello write\n,12);fork();直接运行输出三行但执行./hello file重定向到文件后出现五行——printf和fwrite各输出两次write只有一次。原因链条重定向到普通文件后缓冲方式变为全缓冲数据先留在库函数的用户级缓冲区fork时父子写时拷贝各自持有一份未刷新的数据进程退出时统一刷新两份都写进了文件write是系统调用没有缓冲区不受影响顺带回答一个疑问write没有缓冲而printf有说明该缓冲区是二次加上的由 C 标准库提供这里讨论的都是用户级缓冲区OS 另有内核级缓冲区。五、FILE 结构fd 与缓冲区的载体访问文件最终都要走 fd所以FILE内部必然封装了 fdtypedefstruct_IO_FILEFILE;// stdio.hstruct_IO_FILE{int_flags;char*_IO_read_ptr,*_IO_read_end,*_IO_read_base;// 读缓冲区指针char*_IO_write_base,*_IO_write_ptr,*_IO_write_end;// 写缓冲区指针char*_IO_buf_base,*_IO_buf_end;// 缓冲区首尾int_fileno;// 封装的文件描述符};一组指针描述读写缓冲区的起点与当前位置_fileno则是通往内核的钥匙。结论闭环用户调printf数据先进FILE的缓冲区满足刷新条件后再由write交给 fd最后落到设备上。六、总结一切皆文件进程、磁盘、键盘、管道、socket 都被抽象成文件read/write一套 API 通吃内核依据f_op指向file_operations函数指针表把系统调用与设备驱动挂钩缓冲区内存中预留的一块空间用空间换系统调用次数让慢外设与快 CPU 协调三种类型全缓冲磁盘文件、行缓冲终端1024、无缓冲stderr缓冲区满、flush、进程退出也会刷新两张验方重定向后 printf 不输出全缓冲需fflush、fork后库函数输出两遍写时拷贝带走未刷新数据write始终只一次。
上一篇/下一篇内容由系统自动关联
返回资讯列表 →