《跟韩工学Ubuntu》第1课讲到系统架构、启动流程与内核管理,前三篇把主线梳理完了,这一篇是补“边角料”的。但说实在的,我在实际折腾中越来越发现,边角料才是最容易救命的部分。韩工在课上反复强调一句话:不懂架构也能用Ubuntu,但想诊断问题、想定制系统、想避免升级翻车,就必须把结构和流程吃透。这篇会把架构、启动、内核管理三条线里那些“听说过但没实战用过”的知识点展开,每一条都配一个能直接上手的命令或验证方法。适合刚学完基础、想从“会用”切换到“能查、能修、能自定义”的朋友。内容按三个方向展开:先谈怎么查证系统结构,再谈启动每个阶段怎么验证,最后谈内核升级和模块管理中的实践细节,末尾附常见问题实录。
1. 架构扩展:从记住结构图到真正会查证
系统架构在多数教程里都长一个样:应用程序在最上层,下面是标准库、系统调用、内核、硬件,中间再画几个箭头。这张图本身没毛病,但解决不了任何实际问题。真正有用的能力是,系统出问题时你能说出当前状态是“用户态进程的行为”还是“内核态的行为”,并且能用工具把一层层的调用关系拉出来看。架构知识落到实处,无非三件事:会查、会读、会判断。
1.1 系统调用:用户态和内核态之间的“门票”
平时写个脚本、敲条命令,所有涉及资源的操作几乎都需要内核把关。打开文件要检查权限,读写磁盘要调度驱动,网络收发要走协议栈。用户态的程序想用内核的功能,必须走系统调用(syscall)这道门。打个比方:去银行柜台办事,你不能自己绕进金库取钱,得取号、填单、排队,由柜员审核后帮你办,柜员就是内核。
关键是怎么看这道门。Ubuntu上装一个strace,就能把一个命令发起的系统调用全部拉出来:
sudo apt install strace strace -c cat /etc/hostname-c参数会把系统调用按类型汇总。我第一次跑这个命令还挺意外,一个简单的cat,背后居然有几十次系统调用,大部分是加载动态链接库时的openat、read、mmap,真正读目标文件的反而只有最后那么一两次。如果想只看打开文件的调用,可以这样:
strace -e openat cat /etc/hostname这个工具在程序卡死时价值最大。比如某个服务启动后一直没反应,strace -p 进程号附上去,看它卡在哪个调用上:卡在read多半在等磁盘或管道输入,卡在connect多半是网络不通,卡在futex则可能陷入了锁等待。这就是从“知道有系统调用”到“用系统调用查问题”的进阶。
顺带说一句,用户态和内核态之间的切换是有开销的。每次系统调用都要做上下文切换、参数校验、权限检查,所以追求性能的代码会尽量用更少的系统调用完成更多操作,比如批量读写、大块缓冲,而不是一次一字节。这也是为什么同样处理一份文件,有的工具快、有的工具慢。
1.2 模块与驱动:lsmod一分钟看出系统里住了哪些“房客”
Linux内核不是一整块铁板,而是支持按需加载模块的。模块就是驱动、文件系统、网络协议等功能的载体。类比一下:内核是公寓管理员,模块是不同功能的房客,有的负责打扫(存储驱动),有的负责水电(网络协议),有的负责安保(安全模块)。你不需要让所有房客都住在楼里,用到哪个功能再搬进来就行。
Ubuntu里查看当前加载的模块:
lsmod输出三列:模块名、占用的内存大小、被谁使用。最后一列“Used by”特别有用,它体现模块之间的依赖关系。比如某个模块显示被另一个模块引用,那卸载前就必须先处理掉上层引用者,否则系统会拒绝卸载。
看某个模块的详细信息:
modinfo usb_storage能看到模块路径、许可证、描述和可传参数。模块文件实际存放在/lib/modules/$(uname -r)/下,其中uname -r是当前内核版本号。为什么强调这个路径?因为不同版本内核的模块是分开存放的,换内核后旧模块不会自动迁移到新目录,驱动“丢失”就是从这里开始的。
日常排查时,硬件识别不了,第一件事就是lsmod | grep 模块名关键字,看对应驱动到底有没有加载。装好系统后USB设备没反应,先看看usb_storage和xhci_hcd在不在列表里;显卡驱动装不上,先确认nvidia或amdgpu模块是否存在,如果压根没加载,再往下查是没安装还是被禁用了。
1.3 /proc与/sys:两个不占磁盘空间的“信息窗口”
/proc和/sys这两个目录,ls 一下能看到很多文件,但它们并不真实存在于磁盘上,属于虚拟文件系统。读这些文件,本质上是向内核要实时数据。
/proc主要提供进程和系统运行状态信息。很多人天天用top、free、ps,却不知道这些命令的数据来源就是/proc:
cat /proc/cpuinfo # CPU型号、核心数、标志位 cat /proc/meminfo # 详细内存统计 cat /proc/loadavg # 最近1/5/15分钟平均负载/sys则偏向设备与驱动模型,比如/sys/class/net/下列出了所有网络接口,/sys/block/下列出了所有块设备。需要写内核参数调优时,/proc/sys/下那些文件就是sysctl的底层接口,可以直接用echo写入让参数临时生效。
但这里必须提醒一句:能写不代表可以乱写。/proc和/sys下的大部分文件是只读的,能写的那部分全是内核实时状态,改动无提示、无撤销,写错了可能直接影响运行。我的建议是,遇到不认识的节点,先cat看看内容和权限,别上来就echo。想了解一个服务为什么起不来时,先cat /proc/进程PID/status看看状态,往往比翻配置文件更接近真相。
2. 启动流程扩展:给每一步都装上检查仪表
启动流程的大框架,前三篇已经说了:固件→GRUB→内核→systemd。但实际遇到启动慢或者卡死时,大框架帮不了你。真正的排查经验是,把启动全过程拆成可验证的阶段,每个阶段都有对应的日志和命令可查。这一节把启动流水线拆得更细,告诉你每一步去哪看、怎么看。
2.1 完整启动路线的9个检查点
- 按下电源键,硬件加电,固件初始化
- UEFI固件扫描启动设备,加载引导程序
- GRUB从
/boot/grub/grub.cfg读取菜单 - 选定条目后,加载
vmlinuz内核和initrd初始内存盘 - 内核解压,初始化核心子系统
- initramfs中的init脚本执行,加载块设备驱动等必要模块
- 根文件系统被挂载,切换到真正的根目录(switch_root)
- systemd接管,按默认target启动服务
- getty打开虚拟终端,桌面或登录界面就绪
第1到3阶段怎么验证?开机时留意屏幕文字,连续按Esc或Shift呼出GRUB菜单,看是不是列出了多个内核条目。看不到菜单但不代表没经过GRUB,只是被隐藏了。
第4到7阶段是排查启动问题的重点区域。在GRUB菜单按e编辑选中条目,找到以linux开头的那一行,去掉quiet splash两个参数,就可以看到内核和initramfs阶段的详细输出。这里能捕捉到大量第一手错误信息,比如磁盘控制器认不到、根分区挂载失败等。
第8阶段以后,系统的控制权已经交给了systemd,可以用:
systemd-analyze blame | head -20查看每个服务占用了多少启动时间。哪个服务卡了十几秒,一眼就能看出来。日志则靠:
journalctl -b # 本次启动的全部日志 journalctl -b -1 # 上一次启动的日志如果你发现启动从“内核跑完”到“出现图形界面”之间有一段漫长的等待,问题多半出在systemd服务依赖上,而不是GRUB或内核。
2.2 initramfs:解决“先有鸡还是先有蛋”的临时系统
内核启动时,负责读取磁盘的驱动模块还不在内存里,而这些驱动模块又存放在磁盘上。谁先谁后?这就是经典的“鸡生蛋”问题。initramfs 就是来解这个套的。
它本质上是一个打包好的微型根文件系统,文件在/boot/initrd.img-*。内核加载后,会先把initramfs解开到内存,执行里面的init脚本,加载磁盘控制器驱动、文件系统驱动、加密解密模块等。等真正的根分区别准备好后,再通过switch_root把控制权交给主系统的/sbin/init。
这个机制不是黑盒,你可以解开看:
lsinitramfs /boot/initrd.img-$(uname -r) | head不需要sudo也能列内容。里面无非是一些目录结构、必要工具、驱动模块和init脚本。看多了你会发现,它内核态和用户态交接的那个“浅滩”,正是启动故障的高发区。
实际使用中,如果修改了/etc/fstab导致根分区挂载出错,启动失败,处理思路往往要往initramfs上靠。日常运维中,手动重建initramfs也是一项必备操作:
sudo update-initramfs -u这个命令会在内核模块有更新或你手动添加了新的根目录支持后,重新打包初始内存盘,让新模块能进启动流程。
2.3 GRUB里最值得动手的3项配置
GRUB主配置文件是/etc/default/grub,注意不是/boot/grub/grub.cfg。改完前者后必须执行:
sudo update-grub才会把变更写到后者。三个最常用也最值得改的项:
GRUB_DEFAULT:默认启动哪个条目。0表示第一项。设成saved后可以配合grub-set-default记住上一次的选择,适合多系统用户。GRUB_TIMEOUT:菜单等待秒数。设0是跳过菜单直接启动,设成-1会一直等着你手选。服务器环境建议0或一个很小的值,少占用重启时间。GRUB_CMDLINE_LINUX_DEFAULT:默认内核启动参数。默认是quiet splash,排查图形界面问题加nomodeset,想看到完整启动输出就去掉这两个词。
再多说一个参数。有些生产脚本和网卡名绑定得比较死,但新系统里网卡名变成了enp3s0这种带总线号的风格。可以在GRUB_CMDLINE_LINUX里加net.ifnames=0 biosdevname=0,把网卡命名改回传统的eth0风格。这么做能省很多改脚本的事。
改GRUB配置前务必备份:
sudo cp /etc/default/grub /etc/default/grub.bak另外,永远别把系统里所有内核全删光。GRUB菜单里保留一个旧内核条目,等于给启动买了一份保险。
3. 内核管理延伸:升级、参数与模块三板斧
很多新手以为管理内核就是“apt upgrade 的时候顺便升一下”。其实内核更新在Ubuntu里有独立的包管理逻辑,升级后可能带来驱动不兼容、启动失败这些副作用。这一节把内核升级、参数调优、模块管理三件高频操作分别讲透。
3.1 内核升级的正确姿势和回退兜底方案
Ubuntu的内核是deb包管理的。查看当前安装的内核:
apt list --installed | grep linux-image你会发现系统里往往同时装了好几个内核版本。其中linux-image-generic是元包,它不包含内核本体,只负责追踪并依赖最新的通用内核版本。装它就等于告诉系统:有新内核就跟上。
推荐的升级流程是:
sudo apt update sudo apt install linux-image-generic装完新内核后先重启,确认一切正常,再考虑清理旧内核:
sudo apt autoremove为什么不建议立刻删旧内核?因为新内核如果和现有驱动或闭源驱动不兼容,你能在GRUB菜单的 “Advanced options for Ubuntu” 子菜单里直接选择旧内核启动。这个回退路径是免费的保险,等新内核稳定运行一两周再清理不迟。
如果确实要手动删除某个不再需要的内核版本:
sudo apt purge linux-image-旧版本号但前提是先用uname -r确认当前运行的内核不是它。还有一个细节:如果你装过第三方驱动,比如NVIDIA闭源驱动,升级内核前要检查DKMS状态:
dpkg -l | grep dkmsDKMS的机制是内核升级后自动为新内核重新编译第三方模块。没装DKMS或者DKMS编译失败,升级后就会“丢驱动”。很多升级后显卡花屏、网卡消失的问题,根源就是这里。
3.2 sysctl调优:先看懂再动手
sysctl是运行时查看和修改内核参数的接口。所有参数在/proc/sys下都能找到,也可以用一条命令列出全部:
sysctl -a临时修改:
sudo sysctl -w net.ipv4.ip_forward=1立刻生效,重启失效。想持久化,就写入/etc/sysctl.conf,然后:
sudo sysctl -p重载配置,不用重启。
说三个日常用得最多、相对安全的参数:
fs.file-max:系统级最大文件句柄数。高并发服务、大量连接数导致文件句柄耗尽时报“too many open files”,就是它小了。fs.inotify.max_user_watches:inotify文件监控数量。编辑器、文件同步工具报“watch limit reached”时,调大它。vm.swappiness:内存交换倾向性,范围0到100。值越低,系统越不愿意用swap,桌面机设一个10到20的数值体验不错。
我的建议是,改之前一定先看默认值:
sysctl vm.swappiness不要直接抄网上的“优化模板”。不同机器、不同负载,最优参数差异很大。改完再查一遍确认生效:
sysctl fs.file-max特别提醒一句:像vm.dirty_ratio、vm.dirty_background_ratio这类控制内存回写策略的参数,影响面很大,没有明确需求不要乱动。
3.3 模块管理的日常:加载、卸载、禁用与传参
日常模块操作,核心命令就这几个:
sudo modprobe 模块名 # 加载模块,自动解析依赖 sudo modprobe -r 模块名 # 卸载模块 lsmod # 查看已加载模块 modinfo 模块名 # 查看模块信息insmod和rmmod也可以加载和卸载,但它们按指定路径操作,不解析依赖,日常优先用modprobe。
模块的控制配置文件在/etc/modprobe.d/下,后缀是.conf。两个最常用的配置场景:
禁用某个不需要的模块:
blacklist 模块名给模块传启动参数:
options 模块名 参数名=值比如某些无线网卡模块需要指定地区码(country code),就可以写在options行里,每次加载自动带入。
模块的依赖关系是由depmod生成的。每当你手动安装了新驱动模块,却发现加载时报“模块不存在”或找不到依赖,先跑一下:
sudo depmod -a这是很多新手最容易漏掉的一步,忘了它,新驱动就像买了家具没装电梯,根本进不了门。模块加载后,立刻用:
dmesg | tail看内核日志有没有报错,再lsmod确认状态。这条验证路径能帮你把很多问题扼杀在加载阶段。
4. 常见问题与排查技巧实录
这一章的扩展内容里,最值钱的部分我认为是故障排查。下面几个问题是我长期实践中遇到频率最高的,也最能检验你对架构与启动流程的理解是否到位。每个问题都写清表现、判断方法和处理步骤。
4.1 开机卡在GRUB菜单
现象:开机后停在GRUB文字菜单,倒计时不动,或者键盘按了没反应。
先判断是不是等待输入导致的。GRUB菜单里按到了方向键或者GRUB_TIMEOUT被设成了负数,倒计时就会暂停,看起来就是“卡住”。如果倒计时在走但进度条不动,按e编辑当前内核条目,去掉quiet splash,看具体卡在哪一步——是卡在加载initramfs,还是卡在内核初始化。
如果系统还能进,直接执行:
sudo update-grub重新生成引导配置。如果配置损坏严重到无法进系统,就得用安装U盘的live环境,挂载根分区后通过chroot重装GRUB。具体流程是:启动live环境,挂载根分区到/mnt,绑定/dev、/proc、/sys后chroot进去,执行grub-install /dev/sda和update-grub。这个过程有点繁琐,但比反复重启碰运气强得多。
经验之谈:平时备份/etc/default/grub,真的能省掉一大半GRUB排障的功夫。
4.2 升级内核后无线网卡或显卡失灵
现象:系统刚升级完,Wi-Fi列表消失,或者图形界面花屏、黑屏。
绝大多数情况是模块兼容性出了问题:要么新内核里对应驱动的模块没有启用,要么DKMS没帮你自动重编第三方驱动。
排查第一步,确认自己是不是进了新内核:
uname -r第二步,用lspci -k看硬件对应的驱动状态:
lspci -k | grep -A 3 -i network lspci -k | grep -A 3 -i vga“Kernel driver in use” 显示的是当前驱动模块名,如果显示N/A或加载失败,问题就定位了。
第三步,看内核日志:
dmesg | grep -i firmware dmesg | tail -50如果日志里全是firmware failed或module verification failed,大概率是模块与新内核不匹配。
处理路径也很清晰:先在GRUB的 Advanced options 里退回旧内核,确认是否恢复正常。正常就说明是兼容问题,安装或更新DKMS后重装驱动包。如果是显卡无法进图形界面,可以在GRUB条目里临时加nomodeset参数,绕开内核模式设置,优先保证能进系统。
4.3 模块加载报错与Secure Boot的坑
现象:执行modprobe加载某个驱动模块,报错信息里出现 “Required key not available” 或者 “Operation not permitted”。
第一个怀疑对象是Secure Boot。开启Secure Boot后,内核只允许加载有合法签名的内核模块,而很多第三方驱动模块没有签名,自然被拒之门外。
先查状态:
mokutil --sb-state输出是SecureBoot enabled还是SecureBoot disabled,一目了然。
处理方式有两种:一是进固件设置关闭Secure Boot,这是最直接的办法;二是走MOK(Machine Owner Key)流程,注册自签名密钥后给模块签名。后者适合必须保持Secure Boot开启的生产环境,但操作链路较长,个人使用很少选它。
模块加载报错还有一种是 “Exec format error”,这个多半是模块的架构和当前内核不匹配。比如给64位内核装了32位模块,或者模块编译时的内核版本和现在运行的内核版本不一致。此时重新用当前内核的头文件编译模块即可。
这类问题最能体现“架构知识落实到日常排查”的价值。你不理解模块签名验证机制,遇到报错就只能反复重启碰运气,理解之后,一次定位,五分钟解决。
最后分享一个我自己的体会。韩工在第1课收尾时说,系统架构、启动流程和内核管理,这三样东西花一周能学完,但要真正内化可能得几个月。别急着去折腾桌面美化或者装各种花哨的终端工具,先把这篇里涉及的命令一个个跑一遍。跑的过程里遇到问题,停下来查一查、想一想,第1章才算真正学完了。我个人最耐用的一个习惯是:每次改配置前先备份,每次重启前确认有回退路径。这个习惯救了我很多次,现在也送给你。