ARTICLE DETAIL

资讯详情

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

Linux引导过程与systemd服务控制深度解析

Linux引导过程与systemd服务控制深度解析 开机这个动作大家天天都在做但 Linux 系统从按下电源键到出现登录界面背后究竟发生了多少事服务又是怎么被一个个拉起来的这俩问题运维面试几乎必问实际排查故障时也绕不开。很多人会用 systemctl 敲几个命令就觉得服务控制不过如此结果真遇到开机卡死、某个服务起不来、grub 引导丢了就抓瞎了。这篇东西我想把 Linux 引导过程和服务控制这条线从头到尾捋一遍不只是背命令而是把每个环节为什么这么设计、排查时该往哪个方向想都讲清楚。如果你是刚接触 Linux 的新手这篇文章能帮你建立起一个完整的启动链路认知如果你已经在用 Linux 做开发或运维里面的排障思路和 systemd 的细节也许能补上一些平时容易忽略的盲区。我会尽量用做过的真实操作来说话少讲空洞的理论。1. 引导全过程拆解从按下电源键到登录界面1.1 固件自检与启动介质加载很多人习惯把引导理解为从 grub 开始其实最早的环节在 CPU 通电那一刻就启动了。主板上的固件传统 BIOS 或 UEFI先做加电自检检测内存、CPU、硬盘这些基础硬件然后根据设定的启动顺序去寻找可引导的介质。传统 BIOS 时代引导逻辑是读取硬盘第一个扇区MBR主引导记录的 512 字节这 512 字节里有引导代码和分区表。MBR 本身空间极小装不下多少逻辑所以它的任务只是找到活动分区里的引导加载器比如 grub 的第一阶段并跳转过去。UEFI 时代就不一样了主板直接读取 ESPEFI System PartitionEFI 系统分区里的 .efi 引导文件比如 \EFI\centos\grubx64.efi逻辑更直接也不受 MBR 512 字节限制。我遇到过不少新手在装双系统时明明分区没问题但开机就是直接进了 Windows。这种情况多半是因为 UEFI 固件里的启动项顺序不对或者安装 Linux 时没有把引导文件装进已有的 ESP 分区。排查思路很简单进固件设置界面看 Boot Order 里有没有 Linux 对应的启动项没有就用 efibootmgr 手动添加。注意如果是老机器且用了 MBR 分区表路径通常是 /dev/sda 开头的传统方式如果是 UEFI 模式一般会看到 /dev/sda1 之类挂载在 /boot/efi 的分区。两者在处理引导修复时的工具和手法完全不同别搞混。1.2 GRUB2 的职责与配置文件固件找到引导加载器之后就轮到 GRUB2 登场了。GRUB2 是大多数发行版默认配备的引导加载器它负责两件事一是给用户提供菜单选择比如选内核版本、进入救援模式二是把选中的内核镜像和 initramfs 加载进内存再把控制权交给内核。GRUB2 的配置文件是 /boot/grub2/grub.cfgCentOS/RHEL 系或 /boot/grub/grub.cfgDebian/Ubuntu 系。但你需要记住一个关键点这个文件不建议手工直接改它通常是由 grub2-mkconfig 或 update-grub 命令根据 /etc/default/grub 和 /etc/grub.d/ 下的脚本自动生成的。手工改了 grub.cfg下次更新内核或者重新生成配置时改动就会被覆盖白折腾。我自己调试内核启动参数时更推荐在开机出现 GRUB 菜单时按 e 进入编辑模式临时修改启动行测试没问题后再把参数写进 /etc/default/grub 并重新生成配置文件。这样不会因为配置写错导致系统起不来属于比较安全的调试手法。1.3 内核初始化与 initramfs 的作用GRUB2 加载内核镜像 vmlinuz 和 initramfs 之后内核开始初始化。这里有个常见的误解很多人以为内核一开始就能直接挂载根文件系统。实际上根文件系统所在的分区可能是 LVM 逻辑卷、LUKS 加密设备、软件 RAID 或者需要特定文件系统驱动比如 xfs、btrfs才能识别。这些驱动如果编译成内核模块而不是直接编进内核那么内核在启动早期根本没有能力访问根分区。解决这个问题靠的就是 initramfs初始内存文件系统。它是一个打包好的小型根文件系统里面包含了必要的驱动模块和启动脚本。内核先把这个 initramfs 加载到内存里并挂载为临时的根然后运行里面的 init 程序通常是 systemd 或 busybox 里的工具由它负责加载存储驱动、组装 LVM、解锁加密设备最后挂载真正的根文件系统。你可以用 lsinitrd 或 zcat /boot/initramfs-xxx.img | cpio -t 查看 initramfs 里到底打了哪些驱动排查明明驱动没问题为什么开机找不到磁盘这类故障时会很有用。这类问题常见的表象是开机卡在 dracut 或 initramfs 的 shell 提示符说明内核没能找到并挂载根设备。2. 服务控制的底层逻辑从 SysVinit 到 systemd2.1 为什么 systemd 成了事实标准早期 Linux 发行版用 SysVinit 管理服务它把系统启动划分成一组运行级别runlevel每个级别对应一组服务脚本基本是串行启动脚本放在 /etc/rc.d/rc?.d/ 目录下通过 S/K 开头的符号链接控制启停顺序。用起来其实还可以但问题在于串行启动实在太慢而且脚本各自为政依赖关系只能靠启动顺序硬编码很容易出现服务 A 必须在服务 B 之后启动这类脆弱的设计。systemd 解决的是三个痛点并行启动、按需启动、统一管理。它用 socket 激活和 D-Bus 激活机制允许服务之间通过通信接口按需拉起而不是全部堆在开机阶段硬排队。同时所有服务都用单元文件Unit File描述格式统一依赖关系声明清晰systemctl 一套命令就能完成查看、启动、停止、启用全部管理操作。2.2 systemd 的单元文件与依赖体系单元文件有几种常见的后缀.service 代表服务.target 代表一组服务的集合类似运行级别的角色.socket 代表套接字监听单元.timer 替代了传统的 cron 定时任务。最核心的 .service 文件通常放在三个位置/usr/lib/systemd/system/软件包安装时自带的单元文件/etc/systemd/system/系统管理员自定义或覆盖的单元文件/run/systemd/system/运行时生成的临时单元文件优先级从高到低是 /etc /run /usr/lib。我想强调一个之前踩过的坑当你需要修改某个服务比如 nginx 或 docker的启动参数时请别直接改 /usr/lib/systemd/system/ 下的原始文件而应该在 /etc/systemd/system/ 下创建同名的覆盖文件用 systemctl edit 命令会自动帮你处理成 override.conf 片段。这样软件包更新时不会把你的自定义修改冲掉。单元文件里的依赖字段也有讲究。最常见的是After只影响启动顺序不强制依赖Requires强依赖前方服务启动失败本服务也会失败Wants弱依赖前方服务启动失败不影响本服务我见过不少运维把 Requires 和 After 一起写以为双保险更稳实际上它们描述的是两码事。After 决定了谁先谁后Requires 决定了是否强绑定。你要表达A 必须在 B 之后启动但 B 挂了 A 也无所谓就写 AfterB 而不写 RequiresB。2.3 target 与 runlevel 的对应关系systemd 用 target 替代了 SysVinit 的 runlevel。常见的对应关系如下SysVinit runlevelsystemd target说明0poweroff.target关机1rescue.target单用户维护模式2 / 3multi-user.target多用户命令行模式5graphical.target图形界面模式6reboot.target重启切换到图形界面systemctl isolate graphical.target这个命令等价于老办法里的init 5。开机默认进入什么模式可以通过设置默认 targetsystemctl set-default multi-user.target。服务器一般默认进 multi-user.target不带图形界面节省资源也减少攻击面。这个默认 target是由 /etc/systemd/system/default.target 符号链接指向的你完全可以手动维护这个链接。提示如果桌面 Linux 开机卡在命令行模式不进入图形界面最直接的排查方向就是确认默认 target 是不是 graphical.target以及显示管理器服务gdm、sddm、lightdm 之一有没有处于 enabled 状态。别一上来就重装显卡驱动。3. 服务管理实操systemctl 使用心得与踩坑记录3.1 常用命令的进阶用法systemctl 命令大家都会几个基础的比如systemctl start nginx、systemctl enable nginx。但实际排查问题时有几个命令的价值远比 start/stop 高却经常被忽略查看服务是否开机自启systemctl is-enabled nginx查看服务当前运行状态比看屏幕输出更细systemctl status nginx这条命令会显示该服务的主进程 PID、内存占用、最近日志片段还有监听的端口基本是一个迷你版巡检。查看某个服务依赖哪些其他服务systemctl list-dependencies nginx这会递归列出 Requires 和 Wants 的所有单元排查为什么我明明启动了服务甲服务乙也起来了这种很常见的困惑特别好用。查看系统启动耗时和各服务耗时systemd-analyze blame这条命令我会在系统开机变慢时第一时间跑它能按时间从长到短列出每个服务的启动耗时找出拖慢开机的大头。另外enable 操作背后的本质是在 /etc/systemd/system/ 下创建符号链接指向 /usr/lib/systemd/system/ 里的原始单元文件。如果你想精确控制某服务在启动到哪个阶段时被拉起可以手动处理这些链接关系但对大多数人来说用好enable --now这种组合命令就够了它会帮你同时做完设置开机自启和立即启动两件事比分开敲两条命令少了一次服务状态切换。3.2 编写一个自定义服务的完整示例用 systemd 管理自定义脚本是家常便饭比如启动一个 Java 应用、运行一个爬虫、拉起一个内网穿透客户端。这里给一个完整的示例假设我们要管理一个 Python 写的 Web 服务脚本放在 /opt/myapp/run.py。在 /etc/systemd/system/myapp.service 写入如下内容[Unit] DescriptionMy Python Web Application Afternetwork-online.target Wantsnetwork-online.target [Service] Typesimple Usermyappuser WorkingDirectory/opt/myapp ExecStart/usr/bin/python3 /opt/myapp/run.py Restarton-failure RestartSec5 EnvironmentPYTHONUNBUFFERED1 [Install] WantedBymulti-user.target然后依次执行systemctl daemon-reload systemctl enable --now myapp systemctl status myapp这里我想解释几个关键字段的意图Typesimple 表示 ExecStart 启动的进程就是主进程systemd 不会额外 fork 或者等待通知。如果你的程序启动过程比较长或者需要确认启动完成后才算 ready可以考虑 Typenotify 并在代码里调用 sd_notify或者 Typeforking 配合 PIDFile。新手最常见的坑是把原本在前台运行的脚本设成 forking结果 systemd 找不到主进程 PID服务状态一直显示 activating。Restarton-failure 表示进程异常退出时才自动拉起正常退出不拉。RestartSec5 表示拉起之前等 5 秒避免程序崩溃后疯狂重启把系统资源耗尽。Usermyappuser 这一步很多人忽略。直接用 root 跑 Web 服务风险很大单独建一个低权限用户来跑业务进程是基本的安全习惯。如果没建这个用户服务会启动失败错误信息在 journalctl -u myapp 里能看到。一个关键习惯是每次修改了单元文件之后记得执行systemctl daemon-reload。我见过有人改完文件直接systemctl restart myapp结果发现改动的参数没生效一头雾水。原因就是 systemd 没有重新加载单元文件restart 只是按旧配置重启了进程。3.3 日志排查的几种姿势服务起不来或者异常重启第一现场就在日志里。systemd 统一接管了服务的标准输出和错误输出查日志的核心命令是journalctl -u myapp -n 50 journalctl -u myapp -f journalctl -u myapp --since 10 minutes ago-u 指定单元名-n 控制行数-f 是实时跟踪--since 按时间范围过滤。三个参数可以混用。如果某次系统启动过程中某个服务失败了你可以加上 -b 参数来指定查看哪一次启动的日志比如journalctl -u nginx -b -1查看上一次开机时 nginx 的日志。有时候服务其实是好的只是开机时序紧张导致依赖的服务没来得及就绪这种偶发问题回看早期日志才会发现端倪。日志默认保存在 /var/log/journal/ 目录下持久化存储重启不丢失。如果看到 /var/log/journal 不存在或者只有空的目录说明日志存储没有开启journald 只在内存里保留重启就没了。解决办法是创建目录并重启 systemd-journald 服务mkdir -p /var/log/journal systemctl restart systemd-journald。建议新环境第一时间把这个目录建好否则以后出了问题想回看昨天崩溃时的日志什么都没有只能干瞪眼。4. 引导故障与服务故障排查实录4.1 GRUB 引导失败与修复案例grub 损坏的场景我做运维这些年真没少见。表象通常是开机黑屏显示 grub rescue 提示符或者直接进入 bootloader 选择界面但找不到任何系统。造成这个现象的原因主要有三种重装 Windows 覆盖了 MBR 或 ESP 分区里的引导文件手工误删了 /boot 分区内容或者把分区表弄乱了磁盘分区调整导致 grub.cfg 里的 root 设备 UUID 对不上实际分区修复方法取决于你用的是 UEFI 还是传统 BIOS。传统 BIOS MBR 的情况用安装光盘或 Live USB 启动进入救援模式chroot 到系统根目录然后重新安装并生成 grub 配置mount /dev/sda2 /mnt mount /dev/sda1 /mnt/boot mount --bind /dev /mnt/dev mount --bind /proc /mnt/proc mount --bind /sys /mnt/sys chroot /mnt /bin/bash grub2-install /dev/sda grub2-mkconfig -o /boot/grub2/grub.cfg exitUEFI GPT 的情况grub2-install 的目标是 EFI 分区一般用grub2-install --targetx86_64-efi --efi-directory/boot/efi再重新生成 grub.cfg。要注意的是很多发行版引导修复还需要挂载 EFI 系统分区也就是 /dev/sda1 挂到 /boot/efi 而不是 /boot这两个路径别搞混。用 live 环境修复时有个隐藏细节容易被忽略chroot 之后/etc/fstab 里如果没有正确配置根分区、/boot 分区grub2-mkconfig 可能会生成一个找不到分区配置的残缺 grub.cfg。所以 chroot 进去后先确认 mount 结果和 /etc/fstab 内容再动手生成配置能省不少麻烦。4.2 服务启动失败的常见原因与定位手段服务起不来表象五花八门但归纳下来主要是这几类模块缺失或配置语法错误。拿 nginx 举例配置文件里多打了一个分号进程启动直接失败。这类错误用nginx -t或对应程序自带的语法检查命令就能提前发现systemd 层面只看到 status 显示 failed不会告诉你具体哪里出错。依赖的服务没起来。比如启动某个 Java 服务它要连数据库但数据库服务还没就绪。此时服务日志里往往是一堆连接超时而不是直接报缺某个文件。遇到这种情况先查依赖链systemctl list-dependencies myapp把数据库、缓存等底层服务逐个确认状态。权限不对。前面说过 User 指定的运行用户不存在或者业务目录权限是 700、其他用户进不去服务一样会失败。日志里通常会看到 Permission denied但有时候程序做了错误吞掉打印一条无关痛痒的警告就直接退出不细看 journalctl 根本想不到是权限问题。资源耗尽。系统内存不够、进程数达到 ulimit 上限、磁盘写满都可能让服务反复崩溃。排查这类问题建议用systemctl status看进程状态配合dmesg -T查看内核日志看有没有 OOM killer 把进程杀掉的记录。我自己的排查习惯是先systemctl status 服务名看失败状态码和最近日志再journalctl -u 服务名 -n 30 --no-pager看完整输出最后检查进程、端口、文件权限。这个顺序能覆盖绝大多数问题如果日志信息不足再用 strace 跟踪系统调用看卡在哪个环节。4.3 开机卡死与超时的定位思路服务控制不只是管理单个服务还涉及整个开机流程的编排。开机卡死是运维日常里最让人头疼的问题之一因为它没有运行中的系统让你敲命令排查。先说最常见的一种systemd 等待某个挂载点超时。比如 /etc/fstab 里配置了一个网络文件系统NFS挂载项但网络还没就绪systemd 默认会等 90 秒超时这就是开机过程中卡在某个地方很久才进入系统的经典原因。解决思路是在挂载参数里加上 _netdev或者在 systemd 单元文件里明确依赖 network-online.target。第二种是某个服务启动时卡死连累了后面的启动顺序。系统会卡在等待该服务的地方直到 systemd 的超时时间默认 90 秒触发才强制跳过。系统进入后马上跑systemd-analyze blame看哪个服务的启动耗时异常高再针对性处理。第三种是内核参数问题。比如显卡驱动加载失败在图形界面启动时卡在黑屏或花屏状态。此时可以在 GRUB 启动菜单按 e 编辑启动参数在内核行末尾加上nomodeset或者systemd.unitmulti-user.target先进命令行模式排查这种临时改启动参数的手法在引导故障排查里非常实用。注意修改启动参数临时测试时参数之间用空格隔开不要用逗号逗号是给某些参数内部用的。改完按 CtrlX 或 F10 启动只对这一次启动生效重启后恢复原状适合测试。另外开机时如果系统起不来除了看屏幕上的报错还可以在 GRUB 菜单里选择带 rescue 或 single 字样的内核条目进入单用户模式。在单用户模式下文件系统可读写网络不一定有但修 fstab、调整服务 enable 状态、重置密码这些操作都够用了。如果连单用户模式都进不去只能上 live 环境挂载磁盘修复手法跟前面 grub 修复的 chroot 类似。5. 开机加速与服务自启的隐藏技巧5.1 systemd 并行启动与 socket 激活systemd 之所以快除了并行化还有按需启动。举个例子systemd 自带的 journald 服务会在系统很早期就启动但它不一定需要立即开始读写日志文件可以等有进程真正产生日志时才激活。类似地很多服务可以通过 socket 单元先监听端口等有连接进来时再拉起对应的服务进程这就把真正的 CPU/内存开销延迟到了实际使用时。这个机制对服务器的意义很大一堆服务不必挤在开机阶段抢资源系统整体启动速度更快内存占用也更低。不过日常运维里大多数人用到的只是 systemd 的皮毛真正体现设计优势的场景往往是在容器编排和大规模部署里。如果你想优化开机速度先跑systemd-analyze看总耗时再跑systemd-analyze blame看具体哪些服务最慢。对于非关键服务可以考虑把它们的 enable 状态改为 disable或者干脆按需用systemctl start手动拉起。但这里有个原则不要为了追求开机快而盲目禁用安全相关服务比如 firewalld、sshd 这些禁用容易出事后悔就晚了。5.2 通过 mask 和 preset 控制服务自启除了 enable/disablesystemd 还提供了 mask 功能用于彻底屏蔽某个服务。被 mask 的服务无论你手动 start 还是依赖它被拉起都无法启动因为它的单元文件被符号链接指向了 /dev/null。这在应对某个服务总是被其他服务连带拉起来但我就是不想要它的场景很有效比如你不希望系统里自动运行某个监控代理直接systemctl mask 服务名。查看一个服务的 enable/disable/mask 状态用systemctl list-unit-files --typeservice | grep 关键词。如果批量管理大量服务可以用 preset 机制它根据发行版预先制定的默认策略批量设置服务的 enable 状态运行systemctl preset 服务名就能把服务恢复成发行版的默认自启策略比手动逐个 enable/disable 快得多。5.3 定时任务的新选择systemd timer 替代 cron既然谈到服务控制顺便把 systemd timer 也说了。它在功能上可以完全替代 cron 的绝大多数使用场景而且和 systemd 深度集成执行记录写进 journal、可以设置随机延迟、可以精确到秒级调度。一个最小示例创建 /etc/systemd/system/backup.timer 和 backup.service[Unit] DescriptionNightly backup timer [Timer] OnCalendar*-*-* 02:00:00 Persistenttrue [Install] WantedBytimers.target[Unit] DescriptionNightly backup job [Service] Typeoneshot ExecStart/usr/local/bin/backup.sh然后执行systemctl daemon-reload systemctl enable --now backup.timer systemctl list-timers设计上.timer 负责调度.service 负责做事。OnCalendar 指定执行时间Persistenttrue 表示如果到了执行时间但系统当时处于关机状态下次开机后补执行一次这对备份任务来说比 cron 的错过就错过要稳妥得多。建议用systemd-analyze calendar 指定时间表达式验证一下时间写的是不是自己想要的这个命令会告诉你表达式的下一次执行时间点。6. 面试高频考点与自测清单这东西在面试里几乎是送分题和高频题的混合体。为了帮你快速自测我整理了一份引导过程与服务控制的常见问题集合你可以试着不看答案在脑子里过一遍数码设备从按下电源到显示登录界面经历了哪些主要阶段GRUB2 配置文件是哪个为什么不能直接改initramfs 是干什么用的没有它会怎样SysVinit 的 runlevel 3 和 5分别对应 systemd 的哪个 targetsystemd 中 After、Requires、Wants 三者的区别是什么修改了 .service 文件之后为什么要 systemctl daemon-reload服务状态显示 failed你会用哪些命令排查systemd-analyze blame 和 systemd-analyze critical-chain 有什么区别如何临时修改内核启动参数来排查开机黑屏问题systemd timer 相比 cron 有哪些优势最后一个问题我展开说一下因为 critical-chain 比 blame 更容易定位根因。blame 列出的是每个服务各自的耗时但服务之间是有依赖关系的一个服务启动慢可能是因为它在等另一个服务。critical-chain 会把整个启动关键链路上的服务以及每层的等待时间都展示出来你能直观看到瓶颈在哪一环。排查开机慢的痛点时我推荐先跑systemd-analyze critical-chain再对症下药。我自己的经验是引导过程和服务控制这两个知识点面试官很少只问你命令更多是给一个故障场景让你说排查思路比如开机卡在 grub rescue或者nfs 挂载导致开机慢。这种问题没有统一的标准答案但要能分清故障发生在引导的哪一阶段是硬件/固件层、引导加载器层、内核层还是 systemd 服务编排层。定位到层再逐层往下排查比背一百条命令都管用。上面提到的所有命令我都建议你在虚拟机里实际跑一遍尤其是损坏 grub 后的修复流程和 systemd 单元文件的各种排障。这些操作需要动手才记得住光看文章很快就会忘。真要遇到生产环境出问题手里有操作经验心里就不慌。
返回列表