Sealessland logo Sealessland
Interview Prep

OS 面试复习:进程、地址空间与文件系统主线

把进程、虚拟内存、页表、文件描述符和系统调用放到一条能讲顺的链上。

OS 面试复习:进程、地址空间与文件系统主线

OS 这块我之前最容易卡在“每个词都认识,但是连不起来”。进程、线程、虚拟内存、页表、文件描述符,这些单拿出来都能说两句,真要从程序启动一路讲到系统调用,又总觉得中间断了。

后来复习时我就强行按一条链去记:程序跑起来先变成进程,进程活在自己的地址空间里,CPU 访问内存时要经过页表,访问文件、管道、socket 时则是通过文件描述符去碰内核对象。这样一串,很多散点就能挂上去。

先说进程和线程。

一句话记法其实够用了:进程更偏资源隔离,线程更偏执行和调度。

一个进程有自己的虚拟地址空间、打开文件表、信号状态这些东西。同一个进程里的线程共享这些资源,但每个线程自己也有寄存器上下文和栈。所以线程切换通常比进程切换轻,核心原因也就在这里,不是因为“线程更小”这种空话。

然后是虚拟内存。

我现在不太想把它记成“让程序看到一大片连续内存”这种说法。这个描述不算错,但有点轻。更重要的其实还是隔离和映射。每个进程看到的是自己的虚拟地址空间,虚拟页和物理页怎么对应由页表决定,所以虚拟地址连续和物理地址连续不是一回事。

这个点一顺下来,很多现象就不奇怪了。比如 malloc 成功不代表物理页已经马上分好,很多系统会按需分配;再比如缺页异常也不等于程序出错,它也可能只是第一次访问某段映射,内核现在才去补齐这页。

页表这块我现在也不想背得太碎。先记 CPU 做 load/store 时会先查地址翻译,TLB 只是常用翻译的缓存。如果翻译失败或者权限不对,就会进内核,让内核判断这是不是合法但还没建好的映射,还是说它本来就是一次非法访问。把这层逻辑带上以后,TLB、缺页、换页这些东西就都不飘了。

forkexec 也一样,单独背很容易散,放回程序启动链里就好很多。

fork 出来的是一个新的进程视图,但现代系统一般不会傻到一上来就把所有内存真拷一份,更多还是 copy-on-write。exec 则是把当前进程的程序映像换掉,PID 还是那个 PID,只是地址空间和代码数据段换成了新程序。shell 起程序那条线其实就是 fork 一个子进程,再在子进程里 exec

文件描述符这块也得强行记成一条链,不然特别容易说成“fd 就是文件”。其实它只是进程里一个整数索引,真正的打开状态在内核里。这样去想,dup 为什么共享偏移、fork 之后父子为什么能共用同一个打开文件状态,这些问题都好答很多。

mmap 我觉得也很值得单独记一下,因为它正好把“内存”和“文件”接起来了。映射之后你拿到的是一段虚拟地址,真正访问时内核再把这段地址和文件页或者匿名页联系起来。这样文件 IO、页缓存、缺页处理其实就不再是三件分开的事。

所以如果面试里真被问到“你怎么理解 OS 里最重要的几块”,我现在大概会这样答:程序运行后成了进程,进程通过地址空间和页表使用内存,通过文件描述符和系统调用进入内核对象;fork/exec 管程序的创建和替换,mmap 把文件和内存管理接到一起。这样讲至少是一条线,不会只剩一堆术语。

顺手记几个我觉得容易答空的点:

  • 虚拟内存别只说“大”,要带上隔离和按需映射。
  • fork 别答成“复制整个进程”,copy-on-write 这层最好带一下。
  • 文件描述符别答成“文件”,它只是进程访问内核对象的入口。