ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Linux引导过程与systemd服务管理:从内核参数到单元文件的全链路实战排查

Linux引导过程与systemd服务管理:从内核参数到单元文件的全链路实战排查 我最早系统性地研究 Linux 引导过程是拜一台生产机所赐。那天凌晨重启完屏幕上只剩一个grub提示符系统菜单丢了。我虽然背过不少命令但那一刻才发现引导链路里的每个环节我都是一知半解。后来把 BIOS/UEFI、GRUB2、内核、initramfs、systemd 这条链完整理了一遍才开始真正看懂那台机器。这篇文章想把你从“会用 systemctl 重启服务”带到“能从内核参数一路排查到服务单元文件”的位置。内容不绕高深理论全部是这几年实际维护服务器、处理开机故障、写 service 文件时验证过的东西。适合系统运维、SRE、后端同学也适合正在准备 Linux 面试题和运维故障案例复盘的人。1. 整体思路引导过程到底在干什么1.1 从按下电源到看到登录提示符中间发生了什么很多人第一次听说“Linux 引导过程”脑子里只有 BIOS、GRUB、内核这几个词但真要他自己描述一遍往往卡壳。我建议把引导过程理解成四段接力固件阶段CPU 上电后主板固件BIOS 或 UEFI做自检然后按照启动顺序去找可引导设备。引导加载器阶段GRUB2 从磁盘上被加载读取配置文件把内核镜像和 initramfs 加载到内存。内核初始化阶段内核解压自身、初始化硬件驱动然后借助 initramfs 里的临时文件系统和驱动找到真正的根文件系统。用户态接管阶段内核挂载根文件系统后启动第一个用户态进程。在绝大多数现代发行版上这个进程就是 systemdPID 1随后由它拉起所有开机服务。这四个阶段不是抽象概念。每个阶段都有对应的观察工具efibootmgr -v看 UEFI 启动项grub2-mkconfig重建 GRUB 配置dmesg看内核日志systemd-analyze看服务启动耗时。故障发生在哪个阶段决定你该用哪套手段去救。1.2 为什么引导过程理解得越深故障恢复越快我常跟团队里的人说引导过程是 Linux 系统管理的“底层原理”面试题考它不是因为要背名词而是因为它能把内核、文件系统、设备驱动、进程管理串起来。理解深了很多“灵异事件”一眼就能定位。举个例子。虚拟机开机卡在黑屏光标一直闪但没到 GRUB 菜单。这种问题大概率在固件阶段你可能要查启动顺序或者镜像是不是没写正确。可如果屏幕已经出现了 GRUB 菜单但选完内核后报Kernel panic - not syncing那就是内核或根文件系统的问题。还有一种情况是启动到一半卡在某条Started ...日志上这时候问题又跑到 systemd 的服务依赖上了。这三个问题的排查入口完全不一样。如果不懂整条链路大概率会在网上乱搜然后靠重启碰运气。2. 核心细节引导过程的四个阶段拆解2.1 固件与引导加载器BIOS/UEFI 与 GRUB2 的关键点先讲固件。老式 BIOS 启动时读磁盘第一个扇区的 MBR代码量非常小只能再跳转到 GRUB 的剩余代码。新机器基本都是 UEFI它不再依赖 MBR而是读取 FAT 格式的 EFI 系统分区ESP里的.efi文件。很多人照着网上老教程安装 Linux明明镜像没问题装完却无法引导多半就是没搞明白自己是 UEFI 还是 BIOS 启动。UEFI 机器上可以用efibootmgr -v查看当前的启动项顺序类似这样Boot0000* ubuntu HD(1,GPT,xxx,0x800,0x100000)/File(\EFI\ubuntu\shimx64.efi) Boot0001* UEFI Shell ...可以看到 Linux 发行版会在 ESP 里放一个shimx64.efi或grubx64.efi固件直接加载它然后才轮到 GRUB2。GRUB2 的配置文件在 RHEL/CentOS 系是/boot/grub2/grub.cfgDebian/Ubuntu 系是/boot/grub/grub.cfg。注意你平时不应该手改这个文件而是改/etc/default/grub和/etc/grub.d/下的脚本再执行grub2-mkconfig -o /boot/grub2/grub.cfgUbuntu 上直接update-grub。这样做的原因是 GRUB 配置是动态生成的手改会被下次生成覆盖而且容易把root或内核参数改坏。2.2 内核初始化与 initramfs临时根文件系统到底有什么用内核镜像本身不包含所有磁盘驱动。比如根分区在 NVMe 固态或 LSI RAID 卡后面内核一开始根本认不出那块盘。所以引导加载器会额外加载一个 initramfs——一个打包了必要内核模块和工具的小型根文件系统解压到内存里。内核先启动这个临时根加载驱动找到真正的根分区再通过pivot_root切过去最后执行真正的/sbin/init。个别发行版里你还会看到initrd这个叫法。不用纠结名字现代系统里的 initrd 和 initramfs 基本都指同一个东西。想查看 initramfs 里到底有什么RHEL/CentOS 可以用lsinitrdDebian/Ubuntu 用lsinitramfs。排查“内核认识分区但开机报找不到根设备”这类问题时第一个动作就是确认 initramfs 有没有包含对应的文件系统驱动。如果/boot分区满了内核升级后 initramfs 没生成成功重启很容易掉进dracut或initramfs的 shell 提示符里。我处理过好几次这种案例最后都是删掉旧内核、腾出/boot空间再重新生成 initramfs 解决的。2.3 systemd 接管从内核态到用户态的交接内核完成挂载根文件系统后就会启动 PID 1。在 systemd 发行版上这个 PID 1 是/usr/lib/systemd/systemd它会去读取默认 target。你可以用systemctl get-default看到当前默认启动级别对应的 target常见的multi-user.target相当于过去的 runlevel 3graphical.target相当于 runlevel 5。systemd 设计的核心是“声明依赖并行启动”。一个服务单元文件里写清楚After、Requires、Wantssystemd 就能画出整个启动依赖图然后在没有依赖冲突的前提下并行拉起一批服务。所以千万别以为服务是按字母顺序或按文件顺序启动的。想分析启动慢的问题可以用systemd-analyze systemd-analyze blame systemd-analyze critical-chainblame会按耗时排序列出每个服务critical-chain会告诉你一条关键依赖链上谁的耗时最长。我去客户现场排查“服务器启动要五分钟”的时候第一件事永远是跑这三个命令十次有八次都能直接定位到某个等待网络超时的服务。3. 服务控制实战systemd 的正确打开方式3.1 常用命令速查与背后的原理systemctl 是系统管理的核心但很多人只会start、stop、restart对enable和mask的理解比较模糊。我整理了一张实用速查表命令作用备注systemctl start nginx立即启动服务只对这次开机有效systemctl enable nginx设置开机自启创建符号链接到 targetsystemctl enable --now nginx自启并立即启动日常最常用systemctl status nginx查看服务状态和最近日志加-l显示完整输出systemctl mask nginx彻底禁用服务连手动启动都不允许systemctl daemon-reload重载单元文件改完配置必须执行journalctl -u nginx看服务日志-b -1看上一次开机enable和mask的区别值得多说一句。disable只是取消开机自启你还能手动start但mask是把这个单元“屏蔽”掉系统会拒绝任何方式启动它。比如某些容器环境里自带的apt-daily定时任务老是半夜跑直接mask掉比反复stop干净利落。3.2 服务自启与开机顺序的坑服务自启的坑集中在依赖没写全。最典型的是自定义服务需要网络就绪你只写了Afternetwork.target结果服务还是起不来。原因是network.target只代表网络服务被启动过不代表网卡真的配好、能通外网了。正确做法是同时声明Afternetwork-online.target Wantsnetwork-online.target并且在 NetworkManager 环境下确保NetworkManager-wait-online.service没有被禁用。同理如果服务依赖数据库、Redis也要在单元文件里用After和Requires把依赖关系写明白而不是靠脚本里sleep 10硬等。我见过太多把sleep写进 ExecStart 的脚本机器配置一换照样服务起不来。另一个容易踩的坑是“默认 target 变了”。有些团队为了省内存把服务器默认 target 从graphical.target改成了multi-user.target但 GUI 程序的服务又依赖graphical.target于是开机后怎么都起不来。遇到这种问题先systemctl get-default看一眼别凭感觉猜。3.3 手写一个 service 单元文件的完整示例自定义服务是 Linux 系统管理的基本功。我以一个 Python 写的 Web 服务为例写一个最稳的单元文件模板[Unit] DescriptionDemo web service Afternetwork-online.target Wantsnetwork-online.target [Service] Typesimple Userdemo Groupdemo WorkingDirectory/opt/demo EnvironmentFile/etc/demo.env ExecStart/usr/bin/python3 /opt/demo/app.py ExecReload/bin/kill -HUP $MAINPID Restarton-failure RestartSec3 LimitNOFILE65535 [Install] WantedBymulti-user.target逐个讲关键参数Typesimple表示 ExecStart 启动的进程本身就是主进程systemd 会一直跟踪它。如果进程会 fork 到后台需要改成Typeforking但现代服务尽量别自己 fork交给 systemd 管理最省心。Userdemo、Groupdemo用来降权运行避免服务用 root 启动。很多安全扫描工具会根据进程用户打分普通服务用 root 跑纯属给自己找麻烦。EnvironmentFile/etc/demo.env用来加载环境变量注意环境变量文件里不要有export前缀一行KEYvalue即可。ExecReload/bin/kill -HUP $MAINPID处理systemctl reload动作前提是程序自己能处理 SIGHUP 信号并重载配置。如果程序不支持就别写这一行否则 reload 会报错。Restarton-failure告诉 systemd 只在异常退出时自动拉起正常 stop 不会拉。RestartSec3是重启间隔防止无限快速重启把机器拖垮。LimitNOFILE65535是调文件描述符上限。Python、Java 这类服务请求量一上来默认 1024 很容易抛too many open files。写完文件放在/etc/systemd/system/demo.service然后按顺序执行systemctl daemon-reload systemctl enable --now demo systemctl status demo记住这个顺序。先 re 载再 enable 和 start。如果没有daemon-reloadsystemd 可能还在用旧配置start 之后你以为改了没用实际上系统根本没重新读文件。4. 实操过程与故障排查引导失败怎么办4.1 引导菜单按 e 改内核参数进单用户/紧急模式Linux 系统故障案例里最常见的诉求是“忘记 root 密码”和“系统起不来”。这两件事都可以从 GRUB 菜单入手。开机看到 GRUB 菜单时光标停在要启动的内核上按e进入编辑模式找到以linux开头的那一行在末尾追加参数。想进紧急模式追加systemd.unitemergency.target想进救援模式追加systemd.unitrescue.target两者的区别在于rescue.target更接近传统单用户模式会挂载本地文件系统只是不启动网络和普通服务emergency.target则基本只在最小环境里连文件系统都尽量少挂。我平时修系统优先用rescue.target因为可以挂载读写然后 chroot 进去修如果连挂载都失败才退到emergency.target。按CtrlX启动后如果是 root 密码丢失典型操作是先重新挂载根文件系统为可写然后修改/etc/passwd或执行passwd。有时系统会提示文件系统只读先执行mount -o remount,rw /如果用的是rd.break方式系统会停在 initramfs shell真正根文件系统挂在/sysroot下需要先mount -o remount,rw /sysroot chroot /sysroot这个流程我在虚拟机里演示过很多次关键点是“先看清楚提示符在哪一层”别在 initramfs 的 shell 里直接改/etc/shadow改完发现改的是内存里的临时文件系统白白折腾。4.2 从 GRUB 命令行手动引导比编辑内核参数更“硬核”的是 GRUB 菜单完全坏了直接掉进grub提示符。这种情况下系统不会自己改好你要手动喂给 GRUB 三样东西根分区、内核文件、initramfs 文件。先输入ls看 GRUB 能识别哪些磁盘和分区比如看到(hd0,gpt2)可以用ls (hd0,gpt2)/确认里面内容。接着手动指定set root(hd0,gpt2) linux /boot/vmlinuz-5.14.0-362.8.1.el9_1.x86_64 root/dev/mapper/cl-root initrd /boot/initramfs-5.14.0-362.8.1.el9_1.x86_64.img boot这里的root参数是关键它告诉内核真正的根文件系统在哪。如果你不确定具体版本号可以先按Tab键让 GRUB 自动补全能省很多敲键盘的时间。手动引导成功后进系统第一件事就是重装 GRUBmount --bind /dev /mnt/dev mount --bind /proc /mnt/proc mount --bind /sys /mnt/sys chroot /mnt grub2-install /dev/sda grub2-mkconfig -o /boot/grub2/grub.cfg为什么用--bind因为 chroot 之后里面的进程也需要访问/dev、/proc、/sys这些内核虚拟文件系统不 bind 进去很多命令和修复工具会报错。4.3 常见故障定位思路与排查步骤引导类故障不要凭感觉盲目执行命令我推荐一个固定流程先确认卡在哪个阶段再决定操作。故障现象可能原因首选排查动作黑屏无任何输出固件启动项丢失 / 镜像问题检查固件启动顺序U 盘重写镜像卡在 GRUB 菜单GRUB 配置损坏手动引导后重建 grub.cfg内核 panic内核参数错误 / root 无法挂载按 e 查看root参数检查 initramfs卡在Started ...某条日志systemd 服务依赖超时记录卡住的单位名journalctl 查日志启动后无网络network.target 与实际需求不符使用network-online.target检查 wait-online举个例子如果开机后屏幕最后一行停在Started Update UTMP about System Boot/Shutdown.后面就再没动静别瞎猜硬件先按CtrlAltF2看看能不能切到另一个终端。如果能进去说明 systemd 主流程其实活着只是显示终端卡住如果不能再考虑用journalctl -b看启动日志。日志是排查故障的最终依据别绕过它。5. 常见问题与避坑技巧实录5.1 服务起不来的三种常见原因自定义服务起不来原因通常集中在三个地方第一路径不对。系统里可能存在/bin和/usr/bin的符号链接关系但不代表所有环境都一样。ExecStart 里我始终坚持写绝对路径比如/usr/bin/python3不要写python。因为 systemd 执行命令时用的 PATH 很精简未必包含/usr/local/bin。一个用户装完 Python 后直接写ExecStartpython3 app.py服务就是起不来。用which python3查清楚再写进去。第二权限问题。User指定了运行用户这个用户没有WorkingDirectory的读权限或者环境变量文件权限是 600 但属主不对服务就会在启动瞬间退出。排查时别只看status里的红字执行journalctl -u demo.service -b看完整日志里面会直接告诉你Permission denied。第三忘了daemon-reload。这是新手最容易忽略的改完单元文件直接systemctl restart demo如果 systemd 没重新加载文件老配置还会继续生效。更诡异的是有时候改配置文件不生效改完还要检查是不是路径写错导致 systemd 加载的是另一个同名文件。用systemctl cat demo.service可以确认 systemd 到底读的是哪个单元文件。5.2 关于镜像、面试题与常用命令我的个人经验再回到开头说的“linux 镜像安装”和“linux 常用命令大全”这个话题。很多人在虚拟机上装 Linux 失败第一反应是镜像问题但十有八九是 ISO 写入 U 盘的方式不对。用dd直接写 ISO 到 U 盘工作得很好但如果只是把 ISO 文件复制进 U 盘固件根本不会把它当成可引导设备。另外下载完镜像一定要校验 sha256官方页面放出来就是给你对的省这一分钟后面装完系统出诡异问题能折腾你一天。至于面试题我见过太多人背“硬链接和软链接区别”“进程通信方式”背得滚瓜烂熟但让他现场演示 systemd 单元文件怎么写立马卡壳。我的建议很直接找一台虚拟机从grub2-mkconfig从头走一遍引导流程再自己写一个 service 把服务拉起来然后故意把服务目录权限改错观察 journalctl 里的报错。把这条路完整走一遍比刷题有用得多。我个人在实际操作中最大的体会是把引导过程和服务控制放在一起理解你看系统的方式就变了。遇到问题不再是拆东墙补西墙而是知道当前卡在链路哪一环该去哪里看日志动了哪个配置会影响哪些服务。这套思路才是 Linux 系统管理的底层原理也是环境越复杂、它越值钱的地方。
返回列表