一、先从整体看操作系统到底在解决什么问题如果把计算机看成一套完整系统那么最底层是硬件比如CPU内存磁盘网卡键盘鼠标等输入输出设备但硬件本身不会主动“优雅地服务程序”所以如果没有一层统一管理程序想跑起来会非常麻烦。如果没有操作系统应用程序想做任何事都要自己直接面对硬件自己抢 CPU自己分配内存自己决定文件怎么存到磁盘自己操作网卡收发数据自己和别的程序协调资源所以操作系统存在的根本意义是操作系统是程序和硬件之间的管理层。它一方面管理底层硬件资源另一方面为上层程序提供统一、方便、安全的使用方式。操作系统主要在解决三类问题1. 资源管理问题硬件资源是有限的CPU、内存、磁盘、网络都要统一调度不能让程序随便乱抢。2. 隔离与安全问题程序之间不能互相乱改数据普通程序也不能直接无限制操作硬件不然系统会非常危险。3. 抽象与简化问题操作系统把复杂的底层细节封装起来让应用程序不需要直接面对硬件而是通过更统一、更稳定的方式使用机器能力。二、操作系统主要在管什么如果把操作系统的工作拆开来看它最核心就是在管理四类东西。1. 管 CPUCPU 是真正干计算活的地方。操作系统要决定当前哪个任务先运行哪个任务后运行每个任务能运行多久多个任务之间如何切换这背后就会引出进程线程调度上下文切换也就是说CPU 管理本质上是在回答谁现在能上场干活。2. 管内存程序运行起来之后必须占用内存。操作系统要管哪个程序能用哪些内存程序之间怎么互相隔离内存不够怎么办为什么程序感觉自己有一整块连续空间这背后会引出地址空间内存隔离虚拟内存本质上是在回答程序运行时用的空间怎么分怎么保护。3. 管磁盘和文件系统程序不可能所有数据都只放内存很多信息必须落到磁盘上长期保存。操作系统要负责文件怎么组织文件怎么读写数据如何长期保存目录结构怎么维护4. 管输入输出设备包括磁盘 IO网络 IO键盘鼠标输入网卡数据收发尤其对后端开发来说网络 IO 特别关键因为大量程序其实都不是纯计算型而是处在“等待 IO”的状态。这部分最后就会自然连接到阻塞 / 非阻塞同步 / 异步IO 多路复用select / poll / epoll三、程序、进程、线程操作系统是怎么组织运行任务的1. 程序是什么程序是静态的。比如一个 Python 文件一个 Java jar 包一个可执行文件一个安装好的应用程序它们本质上都是放在磁盘上的代码和数据还没有真正运行起来。所以程序可以理解成一份静态的代码蓝图。2. 进程是什么当程序真正被执行时事情就变了。操作系统会为它分配内存为它建立运行环境跟踪它的状态给它安排 CPU 运行这时候程序就不再只是静态代码而变成了进程。所以进程实际上是程序的一次运行实例。同一个程序可以运行多次每运行一次都可以对应一个独立进程。比如你开两个浏览器窗口如果底层实现是两个独立运行实例那它们就可能是两个不同进程。进程之所以重要不只是因为“程序跑起来了”而是因为操作系统需要有一个单位来承载当前执行状态占用的资源已打开的文件使用的内存空间正在运行的执行流所以进程本质上更像操作系统管理运行任务和分配资源的重要单位。进程的核心价值是独立隔离可管理也就是说操作系统用进程把“一个程序的运行现场”包起来方便调度和保护。3. 为什么要有进程因为没有进程的话多个程序一旦同时运行很容易混在一起内存互相覆盖资源互相抢占一个程序崩溃影响整个系统无法清晰区分“谁在干什么”所以进程的意义是给每个运行中的程序一个相对独立的资源空间和身份。也就是常说的进程更强调资源隔离。4. 线程是什么但进程还不是最细的“执行单位”真正执行代码的不是进程这个壳子而是进程里的线程。线程可以理解成进程内部的执行流。也就是说一个进程不一定只有一条执行路径它可以有多个线程同时在内部工作。可以把关系理解成进程像一个工作空间或容器线程像这个空间里真正干活的人线程会共享所属进程的大部分资源比如地址空间已打开文件很多进程级数据但线程自己也有独立的一部分状态比如执行位置、栈等。所以线程的关键词是轻量执行单元。5. 为什么有了进程还要有线程如果没有线程那么程序内部如果想并发做很多事就只能不断新建进程。但进程比较重因为它自带一整套独立资源环境。很多时候我们只是希望在同一个程序内部同时处理多个任务共享同一批数据不想每次都重新创建一整套进程资源这时候线程就很合适。所以有了进程还要线程是因为进程解决资源隔离问题线程解决进程内部更轻量的并发执行问题。6. 进程和线程最核心的区别进程更偏资源容器线程更偏执行流。再展开一点进程隔离更强资源更独立线程共享更多资源创建和切换通常更轻不同进程之间通信更复杂同一进程内线程共享内存更方便但也更容易出现竞争问题所以线程不是“更高级的进程”而是在同一进程内部用更低成本实现并发的一种机制。四、上下文切换为什么线程多了不一定更快看完线程后可能会觉得线程更轻那我开很多线程不就更快了吗现实里不一定因为线程切换不是免费的。1. 什么是上下文上下文可以先理解成一个任务想继续运行下去所必须保存的现场信息。比如一个线程执行到一半时它当前的执行位置寄存器状态栈信息某些运行环境信息都要被保存起来。 不然等它下次再回来时就不知道从哪里继续了。2. 什么是上下文切换CPU 不可能永远只运行一个任务操作系统要不断在多个任务间切换。当 CPU 从执行任务 A 转到执行任务 B 时需要保存 A 的现场恢复 B 之前的现场让 B 从它上次停下的位置继续这个过程就叫上下文切换。所以切换是一个保存与恢复现场的完整动作。3. 为什么切换有开销线程切换有开销核心原因就在于CPU 会花一部分时间在“切任务”而不是在“真正干活”。开销主要来自保存当前线程的运行现场恢复目标线程的运行现场调度器本身的调度判断可能带来的缓存命中率下降线程之间同步与竞争的额外成本所以线程虽然轻但不是零成本。4. 为什么线程多了不一定更快线程多意味着潜在并发能力变强但同时也意味着更多调度更多切换更多竞争更多同步更多资源占用所以如果线程数远超合理范围系统可能会把很多时间浪费在上下文切换上。最终就会出现一种很典型的情况看起来线程很多、大家都很忙但 CPU 实际大量时间花在“换人”而不是“做事”。这就是为什么后端程序不是线程越多越好而需要根据CPU 核数、任务类型、IO 比例、锁竞争情况来控制线程数量。五、用户态、内核态、系统调用程序为什么不能直接操作硬件这部分是理解“程序如何使用操作系统能力”的关键。1. 为什么程序不能直接操作硬件如果普通程序都能直接无限制操作内存磁盘网卡CPU 控制指令那系统会非常危险。比如一个程序写错了就可能破坏整个系统一个恶意程序可以直接控制关键资源程序之间的隔离和安全根本无法保证所以操作系统必须做权限分层。2. 什么是用户态和内核态用户态和内核态本质上是CPU 执行代码时的两种不同权限级别。用户态普通应用程序大多数时候运行在用户态。在这个状态下程序权限较低不能直接做很多敏感操作。内核态操作系统核心代码运行在内核态。 在这个状态下系统拥有更高权限可以真正管理硬件和底层资源。可以理解成用户态普通程序活动区内核态操作系统核心管理区这种划分的目的就是安全稳定可控3. 什么是系统调用既然用户态程序权限有限那它想做底层操作时怎么办通过系统调用向操作系统申请服务。系统调用可以理解成用户态程序请求内核帮忙完成某项操作的标准方式。比如读文件写文件发网络请求创建线程创建进程申请内存这些很多都不是程序自己直接做而是通过系统调用让操作系统代为完成。所以系统调用本质上是用户态 - 内核态 - 用户态 的一个过程。也就是说程序发起请求切到内核态由操作系统处理处理完再切回用户态。4. 为什么系统调用有成本因为这不是普通函数调用而是一次权限级别切换。它通常涉及状态切换现场保存与恢复内核执行逻辑再返回用户态这也是为什么高性能程序很关注系统调用频率减少不必要的内核切换高效的 IO 模型5. 这和后端有什么关系后端程序平时做的很多事其实都离不开系统调用比如文件读写网络收发线程创建进程管理内存使用六、阻塞 / 非阻塞、同步 / 异步程序等待结果时到底在怎么等最核心的区分方式一定要先记住阻塞 / 非阻塞关注的是线程会不会被卡住同步 / 异步关注的是结果回来和任务完成的方式。这两组概念不是一个维度。1. 阻塞 / 非阻塞阻塞阻塞的意思是线程发起一个操作后如果结果还没准备好它就只能停在那里等。比如线程去读网络数据如果数据没到而调用方式又是阻塞的那这个线程就得卡在这儿不能继续做别的事。非阻塞非阻塞的意思是线程发起操作后如果结果还没准备好它不会一直卡住而是先返回线程还能继续做别的事。所以阻塞和非阻塞的核心问题是当前线程会不会被等待动作卡住。2. 同步 / 异步同步同步的核心是结果需要调用方自己主动等待、主动确认、主动获取。也就是说事情是我发起的结果也得我自己盯着拿。异步异步的核心是任务完成后不需要调用方一直自己盯着系统会通过通知、回调、事件等方式告诉你或者替你推进后续逻辑。所以同步和异步关注的是结果是怎么回来的。3. 为什么大家总把它们混在一起因为很多常见场景里它们经常同时出现。比如最传统的读文件、发请求代码经常既是阻塞的线程被卡住等结果同步的结果还得自己等回来所以很多人就误以为阻塞 同步。但其实是两个维度的问题阻塞 / 非阻塞线程状态维度同步 / 异步结果返回方式维度4. 这对后端有什么意义因为后端程序很多时候都在等网络数据磁盘数据数据库结果远程服务响应锁释放怎么等会直接影响线程利用率服务吞吐并发能力性能表现所以这四个概念是理解高并发和 IO 模型的基础。七、IO 多路复用为什么高并发服务不能一个连接一个线程傻等1. 问题背景服务器里有很多网络连接但不是每个连接都时时刻刻有数据。如果给每个连接都分一个线程让每个线程一直阻塞等数据就会有几个问题线程太多资源占用高大量线程其实都在空等上下文切换成本很高所以问题来了能不能少用一些线程同时等很多个连接谁准备好了就处理谁这就是 IO 多路复用要解决的问题。2. 什么是 IO 多路复用IO 多路复用就是用一个或少量线程同时监视很多个 IO谁就绪了就处理谁。也就是说它不是为每一路连接都配一个线程而是把“等待很多连接”的动作集中处理。它优化的重点不是“让某一个连接更快”而是让大量连接的等待方式更高效。3. select、poll、epoll它们本质上都在做同一件事帮助程序同时关注很多个 IO看哪些已经准备好了。select比较早期的方案。核心问题是监听数量有限制每次都要重新提交关注集合返回后还要自己遍历全集poll和 select 思路类似只是管理方式更灵活一些。但本质问题没变仍然要遍历关注集合连接多时仍然会有明显扫描成本epollLinux 下非常经典的高并发方案。核心思路是先注册好感兴趣的连接等谁就绪了系统更高效地返回就绪结果不用像 select / poll 那样每次都低效扫描全集所以 epoll 为什么经典因为在“大量连接、少量活跃”的高并发场景下它更高效。4. IO 多路复用和后端的关系这部分对后端特别重要因为很多高并发网络服务本质上都在做同一件事用尽量少的线程高效管理大量连接。所以后面NginxRedisNetty高并发网关事件驱动模型都会不断看到 IO 多路复用的影子。八、死锁为什么并发系统有时会彻底卡死1. 什么是死锁死锁指的是多个线程或进程因为争夺资源而互相等待导致谁都无法继续执行。注意它不是普通“慢一点”而是如果没有外力干预就可能一直僵在那里。其实就是你等我我等你最后谁也走不动。2. 死锁为什么会发生最典型的情况是每个人都先拿到一部分资源但不释放已经拿到的资源同时又继续等待别人手里的资源一旦这种等待关系形成闭环就可能死锁。比如最经典的例子线程 1 先拿锁 A再等锁 B线程 2 先拿锁 B再等锁 A于是两边互相等待谁都走不下去。3. 死锁的四个必要条件这部分是面试重点。死锁成立通常要同时满足四个条件1.互斥资源同一时刻只能给一个执行单元使用。2.请求并保持已经拿到一部分资源还继续申请新资源而且不释放旧资源。3.不可剥夺已占有的资源不能被别人强行夺走只能等持有者自己释放。4.循环等待多个线程 / 进程之间形成首尾相接的等待环。这四个条件缺一不可。4. 怎么避免死锁思路就是破坏四个必要条件中的至少一个。工程里最常见、最实用的方法是固定加锁顺序比如所有线程都规定先拿锁 A再拿锁 B这样就不容易形成循环等待。另外还有一些常见思路一次性申请所需资源获取失败就释放重试减少锁持有时间不要在持锁时做耗时操作死锁这部分要建立一种并发资源竞争意识只要有多线程、多资源、锁嵌套就要警惕有没有形成等待环。九、总结以上操作系统内容是在回答同一个大问题程序到了机器上以后操作系统是怎么让它安全、有序、高效地跑起来的第一步操作系统先作为管理层存在它夹在程序和硬件之间负责管 CPU管内存管文件和磁盘管各种 IO 设备第二步程序运行后变成进程进程是程序的一次运行实例是资源管理的重要单位。第三步进程里有线程真正执行代码线程是进程里的执行流负责具体干活。第四步操作系统不断调度线程 / 进程不同任务之间来回切换这就会涉及上下文切换。第五步程序权限受限不能直接碰硬件所以需要区分用户态和内核态。第六步程序想用底层资源时要通过系统调用申请读文件、发网络、创建线程等都离不开这一层。第七步程序很多时间都在等 IO所以会涉及阻塞 / 非阻塞、同步 / 异步这些等待模型。第八步高并发场景下要高效地等待很多连接这就引出了 IO 多路复用以及 select / poll / epoll。第九步并发资源竞争时还可能出现死锁所以必须考虑锁顺序、资源申请方式和等待关系。后端开发角度这部分操作系统知识最重要的价值是开始有一种真实的机器视角。慢慢意识到后端服务不是飘在空中它最终是进程服务处理请求最终靠线程执行线程太多会有切换开销程序很多能力要通过系统调用获得等 IO 是后端程序最常见的状态之一高并发不是多开线程就行还要考虑等待模型并发系统一旦资源竞争处理不好就可能死锁开始真正理解程序在机器上是怎么被操作系统托起来运行的。