
凌晨三点服务器又自动重启了一次。业务群的消息像催命符一样往外弹我登录上去先翻dmesg再查上次开机的journalctl最后在一堆硬件错误日志里找到了疑似根因。处理完问题我靠在椅子上突然想起本科时啃《计算机操作系统》的下午老师讲进程调度、虚拟内存、中断向量表我抄了满满几页笔记期末也拿了个不错的分数但实际上很多内容根本没进脑子只是背熟了考试而已。工作后遇到各种跟操作系统相关的疑难杂症才真正意识到这门课欠下的债迟早要还。于是有了这篇再学习。不是去重新背一遍《操作系统》教材而是带着真实问题、真实日志、真实服务器把当年那些停留在考卷上的概念重新过一遍。目标很直接让操作系统期末复习里的知识点变成排查问题时的武器库。这篇文章也写给所有正在刷操作系统八股的人换个角度说不定你会发现那些看起来像死记硬背的概念其实每一条都在生产环境里活着。1. 为什么多年以后我又把《操作系统》翻开工作越久越发现一个规律很多线上问题表面上是应用代码问题、配置问题、网络问题追到最后一层全部落在操作系统上。进程为什么被杀、内存为什么涨、磁盘I/O为什么突然飙高、为什么重启后服务起不来——这些问题的答案教材里其实早就写过。1.1 考试复习与实际排障之间的巨大落差当年学《计算机操作系统》教材是汤小丹那本配套的PPT里全是状态转换图、信号量、页面置换算法、死锁必要条件。考试前背得滚瓜烂熟考完两星期就忘得差不多。最典型的例子是进程状态转换图就绪、运行、阻塞三个状态背起来很简单但直到我亲手处理一个Java服务频繁卡顿的工单才真正理解什么叫进程被挂起、什么是等待I/O、什么是CPU时间片耗尽。类似的落差还有很多。比如背过LRU页面置换算法但到线上服务器swap占用居高不下的时候根本意识不到这是页交换在拖垮性能背过死锁的四个必要条件但当数据库死锁和分布式锁纠缠在一起时完全不知道从哪个维度去分析。不是教材写得不好而是我把一门极端讲究运行机制的课学成了名词解释大全。翻看那些热搜词操作系统期末复习操作系统八股常年霸榜说明很多人都在走同样的弯路考前突击背诵考后扔掉。但八股本身不是问题问题在于我们只背了文字没有把文字背后的执行过程、数据结构、硬件交互装进大脑。真正有用的复习是拿起一本教材对照一台运行中的Linux服务器把每一个知识点都看成可观察、可验证的对象。1.2 热搜里那些操作系统问题几乎全是原理题如果你打开搜索引擎看操作系统相关的高频问题会发现一个很有意思的现象提问方式五花八门但本质全是教材某一章的标题。比如linux操作系统不定时直接重启会是什么原因导致的呢对应的是中断、异常处理、内核崩溃转储机制客户机操作系统已禁用 cpu。请关闭或重置虚拟机对应的是CPU虚拟化、中断描述符表、硬件辅助虚拟化在操作系统的引导中中断向量表的建立和bios的通电自检有先后顺序吗更是直接问到了引导流程的核心。这些问题有一个共同点如果只是背过概念没有真正在机器上验证过遇到的时候会非常慌。因为你不知道该从哪里开始查不知道用哪条命令更不知道日志里的某一行到底意味着什么。反过来如果你把操作系统原理真正理解了一遍再去看这些问题会发现它们都有清晰的排查路径而不是玄学故障。这也是我写这篇博客的原因。我不会把教材目录重新抄一遍而是挑几个在工作里最常踩的场景讲清楚原理、排查方法、以及我在实际操作中总结的经验。2. 重读教材才看懂这些概念当年根本没学透第二遍读操作系统最明显的感觉是很多当年觉得抽象、难背、不实用的章节现在每一句话都像在描述我处理过的故障。这一轮重读信息量比当年大了不止一倍。2.1 进程线程和调度从多道程序到Linux CFS教材一上来就讲进程模型说进程是资源分配的基本单位线程是CPU调度的基本单位。当年我背得很溜但并不知道这句话在工程上意味着什么。直到我处理过线程池爆炸导致的CPU飙升才真正理解进程之间的切换需要切换地址空间、页表、文件描述符等一堆上下文代价很高而同一进程内的线程切换因为共享大部分资源代价要小得多。这也是为什么高并发服务普遍使用多线程而不是多进程的底层原因。Linux的CFS调度器完全公平调度器也是重读时才真正看懂的。教材里说现代操作系统一般都采用基于优先级、抢占式调度但没细说实现。CFS用一棵红黑树维护调度实体按照虚拟运行时间vruntime来保证公平睡眠较久的进程会获得补偿从而能更快被调度到。这个机制直接解释了为什么一个用户交互进程即使CPU占用很低响应也总是很快也解释了为什么容器平台要对CPU做限额其实是在CFS里设置带宽限制参数。理解了这一层再看top命令里的%wa、%st这些指标就能分辨哪些是CPU忙哪些是CPU被虚拟化层偷走了。调度问题还有一个经典场景高并发服务并不是线程越多越好。当线程数量超过CPU核心数太多时大量的时间花在上下文切换上业务响应反而变慢。我曾经优化过一个服务把线程池从256降到64吞吐量反而提升了一倍。当时我只当是经验主义重读调度章节后才明白这就是操作系统在告诉我们让CPU大部分时间执行有用代码而不是切换线程。2.2 内存管理分页、虚拟内存与不够用的本质内存管理这一章当年最头疼的是页表、页框、页帧、逻辑地址和物理地址的换算。现在回头看这些全是大白话每个进程都有独立的虚拟地址空间操作系统负责把虚拟页映射到物理页CPU里通过TLB缓存加速映射。没有这套机制进程之间不可能这么安全地隔离。缺页异常page fault是我重读时才深刻理解的。教材说是进程访问的页面不在内存中时产生的异常我当时只当是规则背下来。直到有次排查一个内存监控告警发现Java进程堆内存设置得很小物理内存却居高不下才意识到进程地址空间的很多部分并不直接占物理内存只有真正访问到某个地址时才会触发缺页异常并分配物理页面。Linux的malloc分配内存往往只是建立虚拟映射真正写入时才产生物理内存消耗。这也是为什么top显示的RES和Java的Xmx经常对不上。内存管理的另一大重点是swap和OOM Killer。教材里讲了页面置换算法但没告诉你Linux在内存真的不够时会如何暴力收尾。每触发一次OOM Killer内核会根据每个进程的oom_score挑一个牺牲者。不看日志的人会觉得系统随机杀进程看得懂的人会去检查/proc/[pid]/oom_score、/var/log/kern.log里的Out of Memory信息然后调整进程的内存限制。理解了这一点就不会再对着突然消失的Java进程一脸迷茫而是知道去哪看判决书。2.3 文件系统与I/O性能瓶颈的另一面文件系统章节在教材里往往是偏记忆的内容inode、目录项、文件描述符、页缓存。重读之后它变成了一张性能排查地图。例如文件系统为什么频繁有写放大问题为什么数据库经常建议用XFS而不是ext4为什么写日志要用O_APPEND这些背后都跟文件系统的日志模式、块分配策略、页缓存回收策略有关。Linux的页缓存page cache是我工作中特别有体感的部分。free -h里那列buff/cache看着很大其实不是内存泄漏而是内核把磁盘数据缓存到内存里加速后续读取。当业务进程申请内存时这些缓存会被回收。但如果回收不及时就可能出现内存明明很空却迟迟释放不出来的错觉。理解页缓存后再看vm.dirty_ratio、vm.dirty_background_ratio这些内核参数就知道它们是控制脏页回写行为的旋钮而不是网上随便搜来的优化项。文件描述符也是一样。教科书说文件是字节流通过文件描述符访问我当年根本没感觉。直到有服务报too many open files我才知道进程能打开的文件数是有限的可以看ulimit -n还能从/proc/[pid]/fd目录里逐个排查。这些知识都是操作系统这一章在现实世界里的投影。2.4 中断与异常系统响应一切的根源中断章节是第三部分排查问题的基础也是最近热搜里中断向量表和BIOS自检顺序这种问题的出发点。教材把中断分为外部中断、内部异常、系统调用并介绍中断描述符表。现代Linux里从键盘按一下到网络收到一个包处理过程都依赖中断机制硬件设备通过中断通知CPUCPU暂停当前任务跳转到对应的中断处理程序处理完再返回。复杂的部分在于下半部机制。老教材只讲到中断处理程序要尽量短但实际系统中硬件中断处理完往往只是标记一下大量耗时操作交给软中断softirq、tasklet或工作队列在更安全的时机执行。理解了这套机制就能看懂top里的si时间也就是软中断消耗的CPU。网络高并发时如果网卡收到的包太多si占比会居高不下这时可以通过网卡RSS多队列、CPU亲和性绑定来缓解。这些都是中断这一章没直接写但完全能从原理推导出来的操作。3. 从开机到崩溃用原理解决三个真实问题重读不是目的能解决实际问题才是。这一节挑三个网上高发的问题用原理配合实际操作讲清楚排查路径。3.1 中断向量表与BIOS自检到底谁先谁后这个热搜问题特别典型因为它混淆了两种中断向量表。在x86体系里CPU上电后一开始工作于实模式BIOS固件为实模式提供了一套中断向量表IVT位于内存最低地址处地址范围一般是0x0000到0x03FF。BIOS的自检和初始化本质上就是通过这套IVT来调用各类硬件服务比如读键盘、读磁盘、显示字符。所以从BIOS正在使用的中断表这个角度看它确实是在通电自检之前或之中就存在的。但人们通常说的操作系统建立中断向量表指的是操作系统进入保护模式后建立的IDT中断描述符表。这个表由操作系统内核在启动阶段初始化地址记录在IDTR寄存器中包含中断门、陷阱门等描述符指向内核自己的处理函数。流程上是这样BIOS/UEFI自检完成后引导程序加载内核内核在初始化阶段完成内存管理、调度器、中断控制器的初始化最终建立并加载IDT。在这之后操作系统的中断处理才正式接管。所以回答很简单BIOS通电自检在前操作系统建立自己的中断向量表在后。但中间有一个重叠区就是引导程序如GRUB可能临时使用BIOS或UEFI的中断服务去读取内核文件。把这套流程理顺了再看到类似的引导顺序问题就不会再被绕晕。3.2 客户机操作系统已禁用 CPU / 虚拟机的启动异常另一个高频问题是VMware报错客户机操作系统已禁用 CPU。请关闭或重置虚拟机。很多人第一反应是虚拟机配置坏了其实大部分情况下是CPU虚拟化功能或者宿主机上的虚拟化组件冲突了。这个错误的本质是虚拟机需要硬件辅助虚拟化Intel VT-x/AMD-V来让客户机操作系统安全地执行特权指令。当宿主机BIOS里关闭了VT-x/AMD-V或者宿主机上已经运行了一个Hypervisor例如Windows的Hyper-V、基于虚拟化的安全性VBS/Core IsolationVMware Workstation无法获得硬件虚拟化资源客户机就会报CPU被禁用。排查顺序我一般是这样重启进宿主机BIOS/UEFI确认Intel Virtualization Technology或SVM Mode处于开启状态。在Windows中检查启用或关闭Windows功能看Hyper-V、虚拟机监控程序平台、Windows虚拟机监控程序平台是否开启同时检查设置-隐私和安全性-Windows安全中心-设备安全性-内核隔离里面有没有打开内存完整性VBS。这些功能会和VMware争抢CPU虚拟化能力。如果开启了Hyper-V要么彻底关闭并从引导项里移除hypervisorlaunchtype要么反过来不用VMware用Hyper-V创建虚拟机。两个同时跑很容易出现资源冲突。在VMware虚拟机设置里如果需要在虚拟机里再跑虚拟机嵌套虚拟化还要勾选虚拟化引擎虚拟化 Intel VT-x/EPT 或 AMD-V/RVI。理解这个问题的原理其实就是理解CPU如何在root模式和非root模式之间切换、虚拟机监视器如何通过VM Entry/VM Exit接管特权指令。教科书上不会直接写VMware的报错但中断和虚拟化的原理能让你在看到报错时知道从哪个方向下手。3.3 Linux不定时重启从内核日志到硬件故障的判断链路linux操作系统不定时直接重启也是个高搜索量问题。因为重启发生得非常干脆经常来不及留下一堆应用日志所以很多人不知道从何查起。我的经验是这种问题一定要从内核日志和硬件事件入手而不是先怀疑应用层。先看系统到底是怎么关机的。执行journalctl --list-boots能看到历次启动记录再查看上一次启动过程的结尾日志journalctl -b -1 -e如果末尾是System is going down for power-offShutdown scheduled之类的字样说明是正常关机流程发出的重启命令那大概率是有人执行了reboot、系统更新自动重启、看门狗触发或者某个脚本调用。如果没有任何关机记录、直接跳电大概率是硬件层面的断电或内核panic后强制重启。内核panic崩了一般会留下现场。如果配置了kdump/var/crash目录下会有vmcore文件没配置的话至少能在dmesg里看到panic栈。另外检查/var/log/kern.log或journalctl -k里有没有MCEMachine Check Exception硬件错误、EDAC内存错误、NVMe controller重置等。硬件层面同样重要。电源供电不稳定、CPU过热、内存坏块、主板电容老化都会让机器瞬间重启。这类问题在日志里经常表现为突然断电而不是系统正常关机。所以如果内核日志找不到panic就要去BMC/IPMI界面看传感器日志、记录重启前后的温度和电源状态。还有一个容易被忽略的点如果这台机器是虚拟机重启原因可能在宿主机上。虚拟机内部的uptime永远不可信需要看宿主机是否发生过漂移、迁移、抢占或者内存超卖。所以我每次处理不定时重启问题时第一件事先确认是物理机还是虚拟机然后决定看哪一套日志。这其实也是操作系统再学习教给我的思维先搞清楚运行环境再开始排障。4. 动手做点东西才叫真正的再学习光看不练还是容易忘。我第三次学操作系统时给自己定了一个规矩每看懂一个机制就想办法在代码里跑一遍。哪怕只是改一个系统调用也比单纯看20页书管用。4.1 从读源码到写mini内核最高效的学习路径很多人在学习群里问读Linux源码从哪下手。如果你直接打开最新内核的源码目录大概率坚持不了三天因为树太大。我比较推荐从教学内核入手MIT的xv6是专门为操作系统课设计的类Unix教学系统代码量只有一万行左右麻雀虽小五脏俱全进程调度、虚拟内存、文件系统都有。哈工大李治军老师的操作系统课程基于Linux 0.11源码要求学生在实验里添加系统调用、实现信号量、修改调度算法非常适合在虚拟机上做实验。赵炯老师的《Linux内核完全注释》配合Linux 0.11源码适合想深入理解x86启动流程和早期内核设计的人。川合秀实的《30天自制操作系统》适合想从零写一个能开机、能显示、能处理鼠标键盘的极简系统的读者。虽然不一定真的30天完成但过程极其锻炼人。我的建议是不要试图去看整棵树而是定义一条最小路径先看内核启动过程从入口汇编到start_kernel然后跑通一个系统调用最后试着把调度器改一下看效果。这三步走完你至少能解答一个进程从fork()到execve()到底经历了什么这种问题。4.2 课程设计、哈工大实验、30天自制操作系统怎么选如果你是在校生正在为操作系统课程设计发愁我的经验是宁可做一个小而完整的实验也不要做一个只有界面没有原理的大项目。常见的选择有几个方向给Linux 0.11或xv6添加一个新的系统调用。这几乎是经典中的经典。你需要修改系统调用表、添加内核处理函数、在用户态写测试程序。做完之后你对用户态/内核态切换系统调用号参数传递的理解会远超背概念。在QEMU里用Rust或C写一个简单的硬件驱动比如键盘中断处理。这一步能让你对中断描述符表、中断控制器有直观感受。自己写一个简单的内存分配器模拟buddy system或slab分配器然后用一个用户态测试程序验证。虽然不完全是内核里的实现但能把数据结构讲清楚。至于孙志岗老师的课程或者《30天自制操作系统》风格不同但都值得借鉴。孙老师的课更偏工程和实践会把操作系统的思想和现代开发方式结合《30天自制操作系统》更偏底层的童趣适合不抵触汇编的人。我自己是在工作后才把这两类内容补上的说实话看别人做系统和自己从头写一点体感差很多。4.3 用一个小实验把中断、调度、内存管理串起来我给自己设计过一个小实验不复杂但能把操作系统最重要的三条线串起来。具体是这样的先写一个极简的32位内核用GRUB或直接在QEMU里加载初始化GDT和IDT然后注册一个时钟中断每触发100次时钟中断就执行一次任务切换准备两个任务每个任务有一段独立的内存页切换时保存和恢复寄存器并在屏幕上打印任务ID和切换次数。这个实验的每一步都有对应的教材章节写GDT/IDT对应保护模式和中断机制注册时钟中断对应外部中断和时钟管理任务切换对应进程上下文切换和调度器给任务分配独立内存页对应内存管理和地址空间隔离。做完这个实验你再去回答进程线程区别中断上下文上下文切换为什么贵这些问题根本不需要背因为你在调试器里亲眼见过。工具方面我习惯用QEMU加GDB远程调试在汇编级别单步看寄存器变化。说实话比在书本上画一百遍流程图都好使。5. 再学习一定要落到工程Linux与信创系统的实用加固操作系统学到第三遍我开始把注意力放到工程落地。尤其是信创服务器越来越常见Galaxy Kylin、OpenEuler这些系统在项目里经常碰到。很多人一看到不熟悉的发行版就发怵其实只要你懂Linux内核的基本原理换发行版只是换一套包管理器和配置路径而已。5.1 麒麟/OpenEuler环境从虚拟机安装到基础配置安装OpenEuler或者其他国产系统虚拟机时热搜词里提到的虚拟机要怎么设置是最初级的拦路虎。关键设置其实就几条CPU务必开启硬件虚拟化宿主机BIOS里打开VT-x/AMD-V虚拟机配置里如果做嵌套虚拟化还要勾选对应的虚拟化引擎。固件类型现在很多新系统默认支持UEFI启动如果安装时选了传统BIOS而ISO只支持UEFI就会卡在引导阶段。反过来也一样建议安装前先确认镜像支持哪种启动模式。磁盘控制器绝大多数Linux发行版都自带virtio驱动用virtio磁盘和virtio网卡性能会好很多。如果安装时识别不到磁盘再切换到SATA或IDE兼容模式装完系统装好驱动后再改回virtio。网络若虚拟机需要通过DHCP获取地址在虚拟网络编辑器里配置NAT或桥接网络若需要固定场景建议装完系统后立刻用nmcli配置静态IP。OpenEuler和麒麟系统安装第三方软件很多人问装不了软件怎么办。本质还是软件包源的问题。如果系统基于yum/dnf例如openEuler、银河麒麟V10的Server版就优先用dnfdnf install nginx如果没有现成源可以下载对应的rpm包然后用dnf localinstall或rpm -ivh安装。对于aarch64ARM64机器一定要下载aarch64的rpm包x86_64的包装不上。如果基于apt麒麟的桌面版本有类似Ubuntu的则用apt安装。还有一类软件只有源码包那就先装gcc、make然后./configure make make install。至于设置DNS服务器地址很多国产系统用的是NetworkManager推荐用nmcli改而不是直接改/etc/resolv.conf因为后者可能会被NetworkManager或systemd-resolved覆盖nmcli con mod eth0 ipv4.dns 223.5.5.5 114.114.114.114 nmcli con up eth0改完以后用cat /etc/resolv.conf确认一下用dig或nslookup验证。5.2 系统安全加固SSH、危险服务、口令策略的正确处理方式热搜词里有银河麒麟V10危险服务检测怎么关闭ssh服务这个问题我有不同看法。正常情况下不要因为检测脚本报了危险服务就直接把SSH关掉否则你远程连不上机器后续运维都成问题。安全加固的目标是减少暴露面而不是拆掉必要的管理通道。如果评测系统提示SSH存在风险正确做法是先问自己这台机器需不需要对外提供SSH如果不需要那就systemctl disable --now sshd但前提是你有带外管理口IPMI/iLO或者物理控制台。如果需要SSH应该加固它修改SSH监听端口降低被扫描的概率但这只是隐藏不能作为唯一手段在/etc/ssh/sshd_config里设置PermitRootLogin prohibit-password禁止root直接用密码登录限定可登录用户AllowUsers opsadmin使用防火墙只放行管理网段firewall-cmd --permanent --add-rich-rulerule familyipv4 source address192.168.1.0/24 port port2222 protocoltcp accept配置密钥登录并在确认密钥有效后关闭密码认证。口令有效期策略也很重要热搜里那句应用服务器未设置口令有效期策略正是等保测评里常见的问题。在Linux上可以用chage命令chage -M 90 -m 7 -W 7 opsadmin表示密码最长使用90天、最短7天、到期前7天提醒。相比手工挨个修改系统加固脚本里统一跑一遍更靠谱。Ubuntu服务器做安全加固也是同一套思路更新补丁、创建非root管理用户、配置UFW或iptables、禁用不需要的服务、检查开机自启动项。把操作系统原理学扎实了你就知道这些加固操作其实都是在资源管理和权限控制两个维度上减少系统暴露。5.3 从操作系统到服务可用我常用的检查命令集最后分享一个我的习惯再学习操作系统时每学一个章节就找一个对应的Linux命令去验证和记忆。下面这个表是我自己整理的虽然简单但在实际排查时非常管用系统状态对应的操作系统知识点常用命令内核与系统架构内核版本、发行版信息uname -a、cat /etc/os-releaseCPU调度与负载调度队列、上下文切换top、htop、mpstat -P ALL、vmstat 1物理内存与虚拟内存页表、缺页异常、swapfree -h、cat /proc/meminfo、vmstat 1进程与线程状态PCB、进程状态、僵尸进程ps -ef、ps -eLf、top -Hp PID文件描述符与打开文件文件表、inode、内核对象lsof -p PID、ls -l /proc/PID/fd文件系统与磁盘I/O页缓存、块设备调度df -hT、iostat -x 1、cat /proc/mounts中断与软中断中断请求、下半部机制cat /proc/interrupts、top的si列网络与协议栈套接字、TCP状态机ss -tunlp、netstat -s、ip -s link系统启动与日志内核初始化、systemd单元systemctl、journalctl -b、dmesg -T我用这个表相当于给操作系统原理建了一条通往命令行的桥梁。状态列自己看知识点列是教材目录命令列是实操入口。如果你也在再学习不妨自己也建一张类似的表分类越细排障时越有底。整个过程走下来我的最大体会是操作系统不是一门用来考试的学科而是一套理解计算机系统的通用语言。中断、调度、内存、文件系统这些词在你真正接触真实系统时会一遍遍遇见只是你未必认识它们。把《操作系统》翻出来再学一遍不是为了精通内核而是为了在系统出问题时你手里有地图而不是靠猜。最后再分享一个我坚持很久的小习惯每学完一个章节就整理一篇排障笔记或者做一个十分钟的mini实验。不用高大上哪怕只是在内核里改一行打印输出也算真正碰过它。积少成多当你能把教材里的知识点和实际日志一一对应起来操作系统就不再是八股而是你手里最趁手的工具。