ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Linux引导过程与systemd服务控制实战指南

Linux引导过程与systemd服务控制实战指南 1. 从按下电源键到登录界面Linux引导过程到底做了什么很多刚接触Linux运维的朋友第一次被问到Linux系统启动过程是怎样的时往往只能说出开机→进系统这个笼统的流程。但真正处理过系统无法启动、开机服务异常、内核崩溃这类问题之后你会发现引导过程里每一步都藏着排障的关键线索。这篇文章就围绕Linux引导过程与服务控制这条主线把从按下电源键到出现登录提示符之间发生的事情完整拆开来讲同时把systemd服务管理的常用操作、实战场景和故障排查一并梳理清楚。适合刚入门Linux的初学者也适合已经工作一段时间、想系统补全这块知识体系的运维朋友。内容不需要你提前掌握太多底层知识只要跟着节奏走每个环节我都会解释清楚为什么是这样。我先把核心结论放在前面Linux的引导过程可以分成固件初始化→引导加载程序→内核初始化→init进程→系统服务启动这五个阶段而现代Linux发行版里服务控制这件事几乎全部由systemd接管。整篇文章就是沿着这条主线展开的。2. 引导过程的五个阶段BIOS/UEFI、GRUB2、内核、initramfs与systemd2.1 第一阶段固件初始化BIOS vs UEFI按下电源键之后第一个执行的并不是Linux内核而是主板上的固件程序。传统的主板用的是BIOS新一些的主板则用UEFI取代了BIOS。BIOS的工作逻辑很直接做加电自检POST检测CPU、内存、硬盘这些基础硬件是否正常然后按照你设定的启动顺序去第一个可启动设备里找引导记录。它找的是硬盘主引导记录MBRMBR位于磁盘的第一个扇区大小只有512字节里面存放着一段很小的引导程序——GRUB2的第一阶段就装在这里。UEFI和BIOS最大的区别在于它不再依赖MBR那512字节的空间而是直接读取硬盘上独立划分的EFI系统分区ESP这个分区通常格式化为FAT32里面存放着.efi格式的引导文件。UEFI固件会根据NVRAM中的启动项记录直接加载GRUB2的efi文件跳过了传统BIOS逐级寻找MBR的过程。注意在老旧的BIOSMBR组合下如果GRUB2第一阶段损坏你会看到屏幕上出现GRUB Loading之后卡住或者直接报Missing GRUB之类的错误。而UEFIGPT组合下常见的故障是启动项丢失开机直接进入固件设置界面。这两种故障的处理思路完全不同后面我会专门展开。2.2 第二阶段GRUB2引导加载程序GRUB2是绝大多数Linux发行版默认的引导加载程序它的核心作用是把内核加载到内存里然后把控制权交给内核。但GRUB2实际做的事情比这多得多它还能让你选择启动哪个内核版本、给内核传递启动参数、进入救援模式或内存测试模式。GRUB2的配置文件在/boot/grub2/grub.cfgCentOS/RHEL系或/boot/grub/grub.cfgDebian/Ubuntu系但这里有个重点这个文件不建议手动编辑。因为它是通过grub2-mkconfig命令根据/etc/default/grub和/etc/grub.d/目录下的脚本自动生成的。手动改grub.cfg下次重新生成配置时你的修改就会被覆盖。GRUB2的工作流程可以简单概括为加载grub.cfg配置→显示菜单或直接按默认项执行→加载vmlinuz内核文件和initramfs镜像→把控制权交给内核。这里有个容易忽略的点GRUB2加载内核时/boot分区不一定已经被挂载所以GRUB2自身需要能够识别文件系统这也是为什么GRUB2比老式GRUB复杂得多——它内置了ext4、xfs、btrfs等文件系统的读取驱动。2.3 第三阶段内核初始化与initramfs内核被加载到内存之后第一件事不是去挂载你的根文件系统而是先做CPU、内存、中断控制器这些最基础硬件的初始化。但这里存在一个先有鸡还是先有蛋的问题要挂载根文件系统需要对应的文件系统驱动和磁盘控制器驱动而这些驱动本身就在根文件系统里。解决这个问题的方案就是initramfsinitial RAM filesystem也就是你在/boot目录下看到的initramfs-xxx.img文件。initramfs是一个小型的临时根文件系统里面打包了最基本的驱动和初始化工具比如磁盘控制器驱动、文件系统驱动、LVM工具、dm-crypt工具等。内核启动时会先把initramfs加载到内存里并解压然后执行里面的init脚本这个脚本负责的事情包括加载必要的内核模块、组装根文件系统如果根分区在LVM卷或加密分区上这里就要做激活操作、把真正的根文件系统挂载到/sysroot目录最后用switch_root切换过去释放initramfs占用的内存。我遇到过不少运维朋友对initramfs的理解比较模糊觉得它只是一堆驱动打包在一起。实际上initramfs里包含的是一整套完整的用户空间初始化环境你甚至可以把它解压出来用chroot进去执行命令很多系统修复操作就是在这一步完成的。2.4 第四阶段根文件系统挂载与init进程启动当initramfs里的init脚本完成了根文件系统的挂载并且switch_root切换成功之后内核会正式启动根文件系统上的第一个用户空间进程。在传统的System V init体系里这个进程是/sbin/init在现代systemd体系里这个进程同样是/sbin/init但它是systemd的符号链接——也就是说pid为1的进程实际上就是systemd。这里有个很多新手容易混淆的概念内核和init进程的分界线。内核初始化完毕的标志是能够启动用户空间程序而init进程启动之后才算进入了真正的用户空间初始化阶段。内核态和用户态的分界点就在switch_root之后、pid 1进程启动的那一刻。systemd作为pid 1进程会读取/etc/systemd/system/和/usr/lib/systemd/system/目录下的unit文件建立起整个系统的服务依赖树然后按照依赖关系依次启动各个服务。从这一刻开始系统的引导过程算是走完了硬件和内核部分进入了服务控制的领域。2.5 第五阶段systemd并行启动服务与会话初始化传统SysV init是按脚本顺序一个一个启动服务的一个服务卡住后面全部排队等待。systemd最大的改进就是引入并行启动机制它根据unit文件中的依赖关系构建一张依赖图没有依赖关系的服务可以同时启动有依赖关系的服务则按照依赖顺序等待。同样的启动任务SysV可能需要一两分钟systemd往往十几秒就能完成。但这并不意味着systemd是无脑并行。unit文件里通过After和Requires这些指令定义了启动顺序。有个容易踩坑的点After只控制顺序不控制依赖Requires才表示我必须要这个服务活着而Wants是弱依赖目标服务启动失败不会影响当前服务继续启动。很多服务启动异常的根源就是unit文件里这几种依赖关系被搞混了。会话初始化这一层getty服务会为每个虚拟终端启动登录进程显示登录提示符。如果是图形界面环境display manager服务如gdm、sddm会在此时启动最终呈现登录界面。3. systemd服务控制unit类型、target与systemctl实战3.1 systemd的核心概念unit、service与targetsystemd把系统中的一切可管理对象都抽象成了unit可以分为service服务、socket套接字、device设备、mount挂载点、timer定时器、target聚合目标等类型。其中service是最常见的我们平时说的启动Nginx停止MySQL操作的就是service类型的unit。target则是一个逻辑分组用来表示系统当前处于什么状态。你可以把它理解为运行级别的现代替代品SysV时代的运行级别3对应多用户文本模式运行级别5对应图形模式而systemd里对应的是multi-user.target和graphical.target。不过target不只是简单的运行级别映射它还能聚合一组服务比如network.target表示所有网络服务都准备好了。用systemctl list-units --typetarget可以看到当前系统里有哪些target可用。systemctl get-default显示默认启动targetsystemctl set-default multi-user.target可以把系统设置为默认无图形界面启动。很多服务器为了省内存都会把默认target设置为multi-user.target有需要的时候再手动启动图形界面。3.2 systemctl命令的完整操作清单服务控制最常用的工具就是systemctl我按使用场景整理了一份命令清单建议你直接收藏启动与停止类systemctl start nginx立即启动服务systemctl stop nginx立即停止服务systemctl restart nginx重启服务先stop再startsystemctl reload nginx重新加载配置不中断服务。这个命令非常实用比如修改了Nginx配置想让它生效用reload而不需要restart避免连接中断开机自启类systemctl enable nginx设置开机自启systemctl disable nginx取消开机自启systemctl enable --now nginx设置开机自启的同时立即启动服务这条命令是我日常用得最多的一步到位systemctl is-enabled nginx查看服务是否设置了开机自启查看状态类systemctl status nginx查看服务详细状态包括主进程PID、占用内存、最近日志systemctl is-active nginx只看服务是否在运行脚本里做判断时用这个命令很合适返回值0表示运行中systemctl list-units --typeservice --staterunning列出所有运行中的服务systemctl list-unit-files --typeservice列出所有服务unit文件及其是否启用状态屏蔽与恢复类systemctl mask nginx彻底屏蔽服务即使手动start也会提示Failed to start。mask会在/etc/systemd/system/下创建一个指向/dev/null的符号链接相当于把这个unit彻底禁用systemctl unmask nginx解除屏蔽这里插一个实际场景有时候你只是想临时停掉某个服务不想改它的开机自启配置用systemctl stop就好。但如果你想让某个服务怎么都无法启动比如一个安全合规要求必须禁掉的服务就应该用systemctl mask而不是仅仅disable。disable只是不开机自启手动启动还是能起来的。3.3 使用journalctl查看服务日志和systemd配套的日志系统是journald它把服务和内核的日志统一收集起来用journalctl命令查询。这个命令对排查问题极其重要journalctl -u nginx查看nginx服务的日志journalctl -u nginx -f实时跟踪日志输出调试问题时非常有用journalctl -u nginx --since 10 minutes ago只看最近10分钟的日志journalctl -u nginx --since today只看今天的日志journalctl -p err -b查看本次启动以来所有错误级别的日志系统出问题时第一步就该这样查有个关键点journald的日志默认是保存在内存和/var/log/journal/目录里的。如果/var/log/journal/不存在系统重启后日志就容易丢。建议你确保这个目录存在并且可以用journalctl --vacuum-size200M来限制日志占用的磁盘空间防止日志文件无限增长把磁盘占满。4. 手写一个systemd服务文件从0到1的完整实操4.1 场景需求与unit文件结构理论说再多不如自己动手做一个服务。我下面用一个实际需求来演示我们有一个Python写的Web服务脚本/opt/myapp/app.py需要让它以守护进程方式运行开机自启崩溃后能被自动拉起来。首先在/etc/systemd/system/myapp.service创建一个unit文件内容如下[Unit] DescriptionMy Python Web Application Afternetwork.target [Service] Typesimple Userappuser Groupappgroup WorkingDirectory/opt/myapp ExecStart/usr/bin/python3 /opt/myapp/app.py Restarton-failure RestartSec3 EnvironmentAPP_ENVproduction [Install] WantedBymulti-user.target拆开来看各个配置项的含义[Unit]部分Description是服务的描述信息Afternetwork.target表示在网络服务就绪之后再启动本服务。如果不加这行服务可能在网络接口尚未配置完成时启动Python程序里如果依赖网络连接就会失败。[Service]部分Typesimple表示ExecStart启动的进程就是服务主进程。这里要注意的是如果你想让systemd帮你管理子进程就需要用Typeforking并且在unit文件里指定PIDFile但对绝大多数应用场景和脚本用Typesimple最简单可靠。User和Group指定服务运行的用户和组这是安全基线里很重要的一环。用root运行一切服务是运维大忌应该为每个服务创建专用系统用户。WorkingDirectory指定工作目录很多Python应用依赖相对路径找配置文件这个不设置就容易报文件找不到。Restarton-failure的含义是仅当服务以非零退出码退出时才自动重启。配合RestartSec3表示失败后等3秒再拉起。如果你的服务是主动停止比如systemctl stop它不会触发重启。如果你希望无论什么原因退出都重启可以把Restartalways。Environment用于设置环境变量多个环境变量就写多行。[Install]部分WantedBymulti-user.target是核心配置它声明了这个服务被安装到哪个target下。WantedBy的意思就是当进入multi-user.target时启动这个服务。执行systemctl enable的时候实际做的事情就是在/etc/systemd/system/multi-user.target.wants/目录下创建一个指向unit文件的符号链接。4.2 配置生效、启动与自检写完unit文件后先执行systemctl daemon-reload让systemd重新读取unit文件。这一步经常被新手跳过结果修改了配置之后执行start发现systemd还在用旧配置怎么排查都不对劲。之后的完整验证流程# 1. 重新加载unit配置 systemctl daemon-reload # 2. 设置开机自启并立即启动 systemctl enable --now myapp # 3. 查看服务运行状态 systemctl status myapp # 4. 测试服务是否真正工作 curl http://127.0.0.1:8000 # 5. 查看实时日志 journalctl -u myapp -f如果启动失败最有效的排查命令组合是systemctl status myapp journalctl -u myapp --since 5 minutes ago前者能看到服务有没有启动成功、退出码是多少后者能看到具体的报错信息。大部分情况下日志里已经把问题根源写得清清楚楚了。4.3 修改配置后的正确姿势服务运行一段时间后需要调整参数比如改环境变量或者改启动命令。修改完unit文件之后记得一定要重新执行systemctl daemon-reload然后再systemctl restart myapp。有个细节只改unit文件里的Environment变量执行restart之后新配置一定会生效吗答案是必须先daemon-reload再restart顺序反了的话restart依然用的是旧配置。这个坑我踩过一次改完配置直接restart结果服务起来还是老环境变量当时还在想是不是systemd缓存了后来才反应过来是忘了先reload。5. 服务控制的实战场景开机自启、超时控制与资源限制5.1 开机自启的完整链路解析前面提到enable实际是创建软链接这里展开讲一下完整的链路/usr/lib/systemd/system/myapp.service是软件包安装时自带的unit文件属于系统整体配置。/etc/systemd/system/myapp.service是管理员自定义的unit文件优先级更高。当你在/etc/systemd/system/下创建unit文件时它会覆盖/usr/lib/systemd/system/下的同名文件。执行systemctl enable myapp时systemd会在/etc/systemd/system/multi-user.target.wants/目录下创建符号链接指向myapp.service文件。这样在启动过程中systemd在加载multi-user.target时就会顺带加载这个目录下的所有符号链接指向的unit。如果你想禁用一个开机自启服务执行systemctl disable myappsystemd会删除这个符号链接。配置文件本身还在只是系统启动时不会去加载它了。5.2 服务启动超时的处理技巧有些服务启动很慢比如依赖数据库初始化的应用在启动阶段可能耗时超过systemd默认的90秒超时限制。此时systemd会杀掉启动中的服务并报告错误。调整方法是在unit文件的[Service]部分增加超时配置TimeoutStartSec180 TimeoutStopSec30TimeoutStartSec控制启动超时TimeoutStopSec控制停止超时。尤其是停止超时我遇到过一些应用在收到stop信号后需要较长时间做优雅退出如果限制太短systemd会强制kill进程可能导致数据未落盘或配置未保存。还有一个更细致的参数是TimeoutStopSec可以写成TimeoutStopSecinfinity表示永不超时但一般不建议这么干服务卡死的时候你连停都停不掉很难收场。5.3 通过systemd限制服务CPU和内存systemd本身就能做简单的资源限制不需要依赖cgroup工具手动操作。比如要给服务限制最大内存[Service] MemoryMax500M MemoryHigh400M CPUQuota50%MemoryMax是硬限制超过这个值进程会被OOM killer杀掉MemoryHigh是软限制超过后内核会积极回收内存但不一定杀进程。CPUQuota50%表示最多使用一个CPU核心的50%。这个功能在做多租户隔离或者防止某个服务占光系统资源时很实用。比如线上服务器同时跑了好几个Java应用其中一个出现了内存泄漏如果没有限制它会逐步吃光系统内存导致全部服务不可用加了MemoryMax之后即使有泄漏到了上限就会被限制或杀掉其他服务不受影响。6. 从引导到服务控制的故障排查实录6.1 系统启动卡住的定位方法先看两个最常见的启动故障表现现象一开机后卡在黑屏光标闪烁。这种情况通常是GRUB2已经加载了内核但内核找不到根文件系统导致panic。可以按CtrlAltF2切换到其他虚拟终端看日志或者在GRUB2菜单里按e编辑启动项在内核启动参数最后加上rd.break进入dracut shell手动检查根分区设备是否可用。现象二开机直接进入紧急模式Emergency Mode提示Failed to mount某些挂载点。这通常是因为/etc/fstab里的挂载配置有问题比如磁盘的UUID写错了或者挂载的磁盘没有插好。此时系统会进入紧急模式让你输入root密码进行修复。修复方法是用journalctl -xb查看具体报错然后检查fstab里的UUID用blkid重新获取正确的UUID并修改fstab。6.2 服务启动失败的五步排查流程当systemctl start一个服务报错时我的排查顺序是固定的第一步systemctl status 服务名看服务当前状态、主进程PID、退出码以及最后几行日志。这一步多半能判断是不是根本性问题比如二进制文件不存在、端口被占用。第二步journalctl -u 服务名 --since 10 minutes ago把完整日志拉出来看。服务启动失败的具体报错一般都藏在这里。第三步手动执行启动命令。比如服务启动脚本是/usr/bin/python3 /opt/myapp/app.py那就手动在终端跑一下看前台能不能起来、报什么错。这一步能区分是程序本身的问题还是systemd环境导致的问题。第四步检查权限和上下文。很多服务以专用用户运行但日志目录、socket文件、数据目录的所有权没给到位导致启动时报Permission denied。另外还要检查SELinux在RHEL/CentOS系列上SELinux拦截服务是非常常见的问题看日志如果出现AVC denied就需要用ausearch -m avc查看具体被拒的操作或者临时用setenforce 0验证是不是SELinux导致的。第五步如果以上都没有头绪看/var/log/messages或/var/log/syslog以及应用自身的日志文件。有些应用把日志写到自己的目录不经过journald所以系统日志里看不到完整信息。6.3 实战案例Nginx启动失败排查全过程我拿一个真实的案例来完整走一遍排查流程。某台服务器上执行systemctl start nginx报错Job for nginx.service failed because the control process exited with error code. See systemctl status nginx.service and journalctl -xe for details.执行systemctl status nginx看到的关键信息是nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use)这个错误很直白80端口被占用了。继续查谁占用了80端口ss -lntp | grep :80发现是一个旧的nginx进程还在跑是之前测试时手动启动的没走systemd。这种情况比较尴尬systemd要启动nginx但端口已经被非systemd管理的进程占用了。处理方法是先停掉旧的nginx进程再启动systemd服务pkill -f nginx: master systemctl start nginx这个案例说明一个问题排查的核心是先读懂报错信息再按图索骥找根源。Address already in use是最常见的端口冲突问题也是最容易解决的。更麻烦的是那种日志只说exit code 1但没给出细节的情况这时就需要把排查重心放到程序日志和手动执行上。7. 常用排查命令速查与经验心得7.1 命令速查表我整理了一份平时运维中最高频使用的命令组合贴在服务器终端旁边或者放在笔记里都会很方便使用场景命令查看系统本次启动时间uptime -s查看上次启动时间who -b查看本次启动的日志journalctl -b查看上次启动的日志journalctl -b -1查看启动耗时最长的服务systemd-analyze blame查看启动关键路径systemd-analyze critical-chain查看所有失败的服务systemctl list-units --statefailed查看服务依赖关系systemctl list-dependencies sshd查看开机自启列表systemctl list-unit-files --stateenabled查看内核启动参数cat /proc/cmdline查看内存启动早期日志dmesg | grep -i error其中systemd-analyze blame是我非常推荐的一个命令系统启动慢的时候它能直接告诉你每个服务各花了多少时间一眼就能定位到启动瓶颈。很多开机要两分钟的问题查完发现是一个网络挂载服务在傻等超时优化思路立刻就有了。7.2 我再分享几个实操中的体会第一点尽量用systemd管理一切服务包括你自己编译安装的软件。很多朋友习惯了用nohup或者写个start.sh脚本启动应用这当然也能跑但带来的问题是你失去了systemd提供的守护能力进程死了没人拉起来、开机不会自动启动、日志分散在各处。花十分钟写个unit文件长期收益非常大。第二点修改系统和服务的任何配置前先备份。我在生产环境操作时习惯先把要改的配置文件复制一份带时间戳的备份比如cp /etc/fstab /etc/fstab.bak.20250601。一旦操作失误能快速回滚。这在排查fstab问题时尤其重要——fstab写错了系统重启直接进紧急模式没有备份的话还得现场算UUID压力很大。第三点排查问题要有耐心先读日志再动手。这是我在多次踩坑之后总结的最大教训。刚入行时遇到服务起不来我第一反应是反复重启、各种乱试结果有时候碰巧好了但根本不知道原因是什么。后来养成了先看状态、再看日志、再动手的习惯排障效率高了很多。很多你以为的疑难杂症其实就是日志里第一行错误已经写明的事情。8. 结尾一个小技巧让排障效率再提升一档最后再分享一个我个人的小习惯。每次拿到一台新服务器我会先把journald的持久化存储打开命令很简单mkdir -p /var/log/journal systemd-tmpfiles --create --prefix /var/log/journal这个操作的目的是让journal日志在重启后依然保留。默认情况下如果/var/log/journal目录不存在journal日志存放在内存文件系统里重启即消失。一旦系统出了问题需要重启排查之前的内存日志全部丢失等于最关键的证据没了。还有一个小习惯是配置终端里的/etc/hosts文件。我总会在/etc/hosts里给服务器自身加一条解析记录很多服务的启动过程会反查主机名解析不通会导致启动失败或超时。这个坑看起来特别低级但实际发生的频率比我预想的高得多。Linux引导过程和服务控制这块知识看起来是基础但真的是整个系统管理的基石。引导过程搞清楚了系统起不来不慌服务控制搞清楚了服务异常也不会手忙脚乱。把这套逻辑理顺很多问题都不是问题了。
返回列表