这一组系统调用是 Linux / Unix 进程管理里最经典的一组面试题。很多同学一开始会把它们混在一起,觉得都和“创建进程、执行程序”有关,但其实它们三者分别负责完全不同的事情:
fork:创建子进程exec:让当前进程执行新的程序wait:父进程等待并回收子进程
最典型的组合关系是:
父进程先
fork出子进程,子进程再exec执行新程序,父进程最后通过wait回收子进程。
这一组系统调用是 Linux / Unix 进程管理里最经典的一组面试题。很多同学一开始会把它们混在一起,觉得都和“创建进程、执行程序”有关,但其实它们三者分别负责完全不同的事情:
fork:创建子进程exec:让当前进程执行新的程序wait:父进程等待并回收子进程最典型的组合关系是:
父进程先
fork出子进程,子进程再exec执行新程序,父进程最后通过wait回收子进程。
讲完进程之后,下一步最自然就是线程。
线程是面试里最常见也最容易讲乱的概念之一。很多人只会背:
但如果不理解“为什么要有线程”,这些话就容易变成死记硬背。
线程出现的核心目的,是在同一个进程内部实现更轻量的并发。
如果所有并发都用进程来做,会有几个问题:
线程这块最容易让人越学越绕,因为它同时牵扯:
如果这些层次混在一起,很容易越看越糊。所以这一篇我们只先抓住最重要的主线:
用户线程、内核线程、LWP 分别是什么,它们是怎么对应起来的。
用户线程可以理解成:
在线程库或运行时里实现和管理的线程。
调度这部分,本质上在回答:
CPU 先给谁用、给多久、什么时候换人。
单核 CPU 在同一时刻只能真正执行一个任务,因此操作系统必须从就绪任务中做选择,这个选择过程就是调度。
调度程序(scheduler)的职责,就是在多个可运行进程 / 线程之间做选择:
如果没有调度,系统就无法在多个任务之间高效分配 CPU 资源。
进程管理这部分,最先要搞清楚的不是各种系统调用,而是:
什么是进程,操作系统为什么要管理进程,以及它到底是怎么管理的。
很多同学一开始会把进程理解成“一个正在运行的程序”,这句话本身没错,但还不够。面试里如果只答这一句,后面的状态、PCB、上下文切换就很难接上。
程序本身是磁盘上的静态文件,只有被装入内存并开始执行后,才会变成进程。
所以可以直接记成:
理解了堆、虚拟地址空间和缺页之后,一个很自然的问题就是:
程序里写的
malloc,到底是怎么和操作系统内存管理接上的?
这背后最常见的一组概念就是 malloc、brk 和 mmap。
最重要的一句话是:
malloc是用户态库函数;brk和mmap是向操作系统申请虚拟地址空间的底层机制。
内存管理不只是“怎么分配页、怎么换页”,还包括一个更关键的问题:
为什么一个进程不能随便访问另一个进程的内存? 既然进程隔离这么强,为什么多个进程又能共享同一块内存?
这背后依赖的就是内存保护与共享机制。
内存保护可以简单理解成:
操作系统要保证一个进程只能按规定方式访问属于自己的那部分内存。
它要防止这些情况:
内存泄漏和内存溢出经常一起被问,但它们不是一回事。
最核心的一句话是:
内存泄漏是“该回收的没回收”,内存溢出是“想申请的申请不到”。
内存泄漏是指:
程序已经不再需要某块内存了,但这块内存没有被正确释放或回收,导致它一直被占用。
它的特点通常是:
这篇不讲新的知识点,主要整理内存管理里最容易混淆的边界问题。
很多时候不是完全不会,而是:
所以这篇的重点不是“扩知识”,而是“压边界”。
很多人会把它们直接画等号,觉得:
TLB miss = 缺页
这是不对的。
堆和栈是面试里非常高频的一题。虽然很多时候会放在语言题里问,但它本质上和操作系统内存管理密切相关。
真正需要抓住的,不是“栈快堆慢”这句结论,而是:
栈和堆分别服务什么场景,它们为什么会采用完全不同的管理方式。
栈主要服务函数调用过程。
函数调用时,系统通常会在栈上创建一个新的栈帧(stack frame),里面保存: