Linux 基础 IO 学习笔记

。Linux 基础 IO 学习笔记

最近学习了 Linux 的基础 IO,从底层原理到实际应用,整理一下核心概念。

一、从磁盘说起

要理解文件 IO,先得知道数据存在哪。

磁盘的物理结构是这样的:多个盘片叠在一起,每个盘片有上下两个盘面,每个盘面对应一个磁头。盘面上有很多同心圆叫磁道,磁道被切成小块叫扇区,一个扇区通常 512 字节。

所有盘面上同一位置的磁道组成一个柱面。定位数据需要三个参数:柱面(磁头移到哪个位置)、磁头(选哪个盘面)、扇区(那一圈上的哪一块),简称 CHS。

但是对程序员来说,CHS 太复杂了。所以操作系统把磁盘抽象成线性结构,给所有扇区编号,变成一维数组。定位一个扇区只需要一个数字 LBA(逻辑块地址)。磁盘控制器会自动把 LBA 转换成 CHS,程序员不用关心物理结构。

二、一切皆文件

Linux 有个核心设计思想:一切皆文件。

不管是磁盘文件、键盘、显示器还是网络,都被抽象成文件,都可以用 read/write 来操作。

这样做的好处是,开发者只需要学一套 API 就能操作各种设备。但问题是,每个硬件的读写方式明明不一样,怎么做到统一的?

答案是函数指针。内核用 struct file 来描述打开的文件,里面有个 f_op 字段,是个函数指针,指向具体的操作函数。磁盘文件的 f_op->read 指向 disk_read,键盘的指向 keyboard_read,网络的指向 socket_read。

用户调用 read(fd, buf, n) 的时候,内核通过 fd 找到对应的 struct file,然后调用 file->f_op->read()。接口统一,实现各异,这其实就是多态的思想,只不过是用 C 语言的函数指针实现的。

三、文件描述符 fd

那 fd 是什么?

fd 就是进程文件描述符表的下标。每个进程都有一个文件描述符表,本质是个数组,里面的指针指向对应的 struct file。

每个进程默认打开三个文件:fd=0 是 stdin,fd=1 是 stdout,fd=2 是 stderr。

当你 open 一个文件,内核会创建一个 struct file,然后在 fd 表里找一个最小的空位,把指针填进去,返回这个下标给你,这就是 fd。

所以 fd 是连接用户程序和内核文件结构的桥梁。通过 fd 可以找到 struct file,struct file 里有缓冲区地址、读写位置、操作函数等所有信息。

四、struct file 里有什么

每次 open 都会创建一个新的 struct file,里面包含:

f_pos:当前读写位置

f_mode:打开模式(只读/读写)

f_flags:打开标志(O_APPEND 等)

f_op:操作函数指针

f_inode:指向文件的元信息

还有内核缓冲区的指针

不同进程打开同一个文件,各自有独立的 struct file,所以 f_pos、f_mode 这些是独立的。但是 f_inode 指向同一个 inode,因为是同一个物理文件。

这就解释了为什么两个进程可以用不同的模式打开同一个文件,各自的读写位置也互不影响。

五、两层缓冲区

这是重点中的重点。

缓冲区分了两层:语言层缓冲区和内核缓冲区。

语言层缓冲区在用户态,比如 C 库的 FILE 结构体里就有一个。printf、fprintf、fwrite 这些函数都是先把数据写到这个缓冲区里,不会立刻进入内核。

内核缓冲区在内核态,在 struct file 里。数据从语言缓冲区刷新到内核缓冲区后,什么时候写入磁盘就是操作系统决定的事了。

数据流向:printf 写数据 -> 语言缓冲区 -> 刷新 -> 内核缓冲区 -> 操作系统决定时机 -> 磁盘

我们认为把数据交给操作系统就相当于交给了硬件,剩下的内核负责。

六、C 库的 FILE 和内核的 struct file

这两个都叫 file,容易混淆。

C 库的 FILE 在用户态,定义在 stdio.h 里。里面包含了 fd(_fileno 字段)和用户态缓冲区的指针。

内核的 struct file 在内核态,定义在 linux/fs.h 里。里面包含了内核缓冲区指针、inode 指针、操作函数指针等。

两者通过 fd 联系。FILE 里存了 fd,刷新的时候通过 write(fd, ...) 把用户态缓冲区的数据送到内核。

各自提供各自的接口:

C 库管用户态缓冲区:fopen/fread/fwrite/fflush/fclose

内核管内核缓冲区:open/read/write/fsync/close

七、缓冲区的刷新

语言缓冲区什么时候刷新到内核?

  1. 遇到 \n(行缓冲模式下,比如 stdout 指向终端时)
  2. 缓冲区满了
  3. 手动调用 fflush
  4. 进程正常退出(exit 会刷新,_exit 不会)
  5. 关闭文件 fclose

内核缓冲区什么时候刷新到磁盘?

  1. 内核定时刷新(后台线程)
  2. 缓冲区满了
  3. 手动调用 fsync
  4. 文件关闭
  5. 系统关机

语言层缓冲区存在的意义是减少系统调用次数。系统调用很贵,每次都要从用户态切换到内核态。有了缓冲区,可以攒一批数据再一起 write,比频繁小写入快得多。

八、read 和 write 的本质

理解了缓冲区,就能理解 read/write 的本质:拷贝。

write 是把用户缓冲区的内容拷贝到内核缓冲区。

read 是把内核缓冲区的内容拷贝到用户缓冲区。

数据在各层之间流动,本质都是拷贝。这也是为什么 IO 操作相对慢,后来才有了 mmap、零拷贝等技术来减少拷贝次数。

还有一点要注意:read 只是把数据读到内存里,不会自动显示在屏幕上。要显示还得再 write 到 stdout。比如 cat 命令,内部就是 read 从文件读到 buf,然后 write 把 buf 写到 fd=1。

九、重定向原理

理解了 fd,重定向就很好理解了。

c 复制代码
close(1);  // 关闭 stdout
int fd = open("log.txt", O_WRONLY | O_CREAT, 0666);
// fd 会分配为 1,因为 1 是最小可用的
printf("hello");  // 写到 log.txt 而不是屏幕

因为 fd=1 现在指向 log.txt,printf 默认写到 fd=1,所以就写到文件里了。shell 里的 ./a.out > log.txt 就是这么实现的。

重定向还会改变缓冲模式:stdout 指向终端时是行缓冲,\n 会触发刷新;重定向到文件后变成全缓冲,\n 不再触发刷新。

十、fork 和缓冲区的经典问题

这个问题很经典,面试常考:

c 复制代码
printf("hello");  // 没有 \n
fork();

直接运行,输出一次 hello。重定向到文件,输出两次。

原因:重定向后 stdout 变成全缓冲,\n 不触发刷新。fork 的时候,用户态缓冲区里还有 hello 没刷新。fork 会复制整个用户空间内存,包括 C 库的缓冲区。所以父子进程各有一份 hello,各刷新一次,就输出两遍了。

write 是系统调用,直接写到内核,没有用户态缓冲区,所以不受影响,只输出一次。

解决方法:加 \n、fork 前 fflush、用 write 代替 printf。

十一、其他细节

open 的标志位:O_RDONLY、O_WRONLY 这些是宏定义,底层是数字,用位运算组合。用 | 组合多个选项,内核用 & 判断设置了哪些。

文件的三个时间:atime(访问时间)、mtime(修改时间)、ctime(属性改变时间)。

引用计数:struct file 有引用计数,用来决定什么时候释放资源。这也是为什么删除一个正在被使用的文件,它不会立刻消失,等最后一个进程关闭才真正删除。

总结

学完这些,对文件系统的理解清晰多了:

从磁盘的物理结构开始,理解数据是怎么存储的。然后是一切皆文件的设计思想,通过函数指针实现统一接口。fd 是连接用户态和内核态的桥梁。两层缓冲区各有各的作用,语言层减少系统调用,内核层减少磁盘 IO。数据在各层之间流动,本质都是拷贝。fork 会复制用户态缓冲区,这是很多坑的来源。

理解了这些底层原理,很多以前觉得奇怪的现象就都能解释了。

相关推荐
林鸿风采2 小时前
Alpine Linux 安装指南:轻量、安全、高效的系统部署实践
linux·运维·安全·alpine
wdfk_prog2 小时前
[Linux]学习笔记系列 -- compiler
linux·笔记·学习
heda32 小时前
zip在linux上解压出错Unicode编码-解决
linux·运维·python
济6172 小时前
linux 系统移植(第四期)--Uboot移植(4)--在U-Boot 中添加自己的开发板(3) -网络驱动修改-- Ubuntu20.04
linux·运维·服务器
2301_765715142 小时前
Linux虚拟机NAT模式网络故障解析与修复指南
linux·运维·服务器
焦糖布丁的午夜2 小时前
数据库大王mysql---linux
linux·数据库·mysql
冰冰菜的扣jio2 小时前
RocketMQ入门——快速搭建
linux·rocketmq
一路向前的月光2 小时前
在loongArch64--linux 安装全局nvm和nodejs(npm)
linux·运维·npm
Lam㊣2 小时前
CentOS上搭建时间同步服务器
linux·服务器·centos