前面顺着文件 I/O、阻塞 / 非阻塞、零拷贝一路往下看时,很快就会碰到另一组在服务端开发里绕不开的概念:
- I/O 多路复用
selectpollepoll- 就绪事件
- 非阻塞 socket
这一块如果只是背结论,其实很容易卡在几个似懂非懂的地方,比如:
- 为什么一个线程也能管很多连接
epoll_wait()到底在等什么- 为什么
epoll还属于同步 I/O - 为什么工程里几乎总是
epoll + 非阻塞 - LT 和 ET 到底差在哪

前面顺着文件 I/O、阻塞 / 非阻塞、零拷贝一路往下看时,很快就会碰到另一组在服务端开发里绕不开的概念:
selectpollepoll这一块如果只是背结论,其实很容易卡在几个似懂非懂的地方,比如:
epoll_wait() 到底在等什么epoll 还属于同步 I/Oepoll + 非阻塞这里主要整理操作系统视角下和网络 I/O、事件驱动、数据搬运路径相关的内容。
目前已经补上的内容主要包括:
后面会继续补阻塞 / 非阻塞 socket、Reactor、网络缓冲区、用户态与内核态协作等内容。
前面把 I/O 多路复用、非阻塞、epoll 这些概念理顺之后,再往下看服务端代码怎么组织时,很快就会碰到一个绕不开的词:
这个词如果只看定义,其实很容易觉得有点虚。因为它既不是具体的系统调用,也不是某个固定函数名,而更像是一种写服务器的方式。
我一开始最容易混的点,是把:
epoll全部揉成一团。
后来慢慢顺下来之后,我现在更愿意先把最核心的话立住:
前面把目录项、 inode、dentry、fd、硬链接这些概念理顺之后,再往下看文件系统时,很快就会碰到另一组特别容易混在一起的词:
fsync这些概念之所以容易乱,不是因为它们都难,而是因为它们其实不在同一个层面上。
有的在描述:
前面把文件 I/O、page cache、直接 I/O 这些内容顺了一遍之后,再往下看网络传输和文件发送时,我发现还有一块特别容易听懂关键词、但一串起来就开始乱的内容:
mmapsendfile一开始我对“零拷贝”这个词的直觉是:是不是数据完全不拷贝了,直接从磁盘飞到网卡?后来越看越发现,不是这么回事。
更准确地说:
前面把目录项、inode、dentry、fd 这些对象关系先理顺之后,再回头看文件系统,接下来最自然的问题其实是两件事:
前者更偏“文件的存储方式”,后者更偏“空闲空间管理”。这两块问题经常会放在一起讲,但本质上其实是在回答两个不同的问题:
文件存储解决的是“这个文件的数据块怎么组织、怎么找到”,空闲空间管理解决的是“还有哪些块可以继续分配”。
所以这一篇我想把这两条线先拆开,再放回到同一张图里看。
前面在整理进程、线程、同步这些内容时,我更多是在关注执行流和资源竞争。真正开始看文件系统之后,我发现这部分最容易让人混乱的地方,不是概念本身有多难,而是很多概念都长得很像:
如果不把这些层次拆开,就很容易越学越乱。所以这篇先不急着展开 ext4、日志恢复这些更底层的话题,而是先把文件系统里最基础的一条主线理顺:
一个路径到底是怎么被解析的,一个文件到底由哪些结构共同表示,磁盘上的结构和内存里的结构又分别是什么。
前面把目录项、inode、dentry、fd、page cache 这条主线先理了一遍之后,我发现文件系统里还有一组概念特别容易混在一起:
它们看起来都是在“改文件”,但实际上很多时候改的并不是文件内容,而是名字和目录项这一层的关系。把这部分看清之后,很多现象就会顺起来,比如:
这一组系统调用是 Linux / Unix 进程管理里最经典的一组面试题。很多同学一开始会把它们混在一起,觉得都和“创建进程、执行程序”有关,但其实它们三者分别负责完全不同的事情:
fork:创建子进程exec:让当前进程执行新的程序wait:父进程等待并回收子进程最典型的组合关系是:
父进程先
fork出子进程,子进程再exec执行新程序,父进程最后通过wait回收子进程。
这一组概念非常容易混,因为它们看起来都和“锁”有关,但其实不在同一层面。
理解这组概念最重要的一句话是:
悲观锁和乐观锁,是处理并发冲突的思路;自旋锁和互斥锁,是具体的加锁实现方式。
如果不先把这两层分开,就很容易越学越乱。
这是:
并发控制策略
也就是说,它们描述的是: