systemd 作为目前主流 Linux 发行版的系统和服务管理器,取代了传统的 SysV init,以并行启动、集中管理、配置简洁的特性成为 Linux 启动初始化的标准方案。它不仅负责系统引导阶段的用户空间初始化,还统一管理进程、守护进程、网络、挂载点等核心功能,基于纯文本的单元文件实现服务配置,大幅简化了自定义启动脚本、自动服务的开发与维护。本文以 NXP i.MX93EVK(Linux 6.1.36+)为例,详解 systemd 核心原理、常用操作及两个典型实战场景(自动登录、自定义启动脚本),实现系统启动的自动化定制。
资料获取:基于systemd的启动初始化
1. systemd 核心基础:组件与核心特性
1.1 systemd 的核心定位
systemd 是 Linux 的系统和服务管理器,作为系统启动后的第一个进程(PID=1),替代传统 /sbin/init,核心职责包括:
- 引导用户空间初始化,并行启动各类系统服务,大幅缩短启动时间;
- 集中管理进程、守护进程、设备、挂载点、网络连接、日志等;
- 为登录用户启动独立的服务实例,实现用户级服务管理;
- 提供统一的配置方式和命令行工具,简化服务的启动、停止、自启配置。
在系统中,/sbin/init 为 systemd 的符号链接,可通过以下命令验证:
ls /sbin/init -al
# 输出:lrwxrwxrwx 1 root root 20 Sep 20 01:57 /sbin/init -> /lib/systemd/systemd
1.2 三大核心操作工具
systemd 提供了一套简洁的命令行工具,覆盖服务管理、启动性能分析、单元查询等所有场景,核心工具如下:
(1)systemctl:核心服务管理工具
用于控制 systemd 的状态,管理所有服务单元(.service),常用命令:
# 重新加载systemd守护进程(修改配置后必执行)
sudo systemctl daemon-reload
# 查看服务状态(关键:查看是否运行、是否自启、错误日志)
sudo systemctl status [服务名]
# 启动/停止/重启服务
sudo systemctl start/stop/restart [服务名]
# 设置服务开机自启/关闭自启
sudo systemctl enable/disable [服务名]
# 查看所有已加载的单元
systemctl list-units
# 查看指定类型的单元(如target、service)
systemctl list-units --type=[类型]
(2)systemd-analyze:启动性能分析工具
用于分析系统启动耗时,定位启动缓慢的服务,优化启动性能,常用命令:
# 查看启动总耗时(内核+用户空间)
systemd-analyze
# 示例输出:Startup finished in 2.649s (kernel) + 7.091s (userspace) = 9.740s
# 查看各服务启动耗时,按耗时从长到短排序
systemd-analyze blame
# 查看启动关键链,定位核心耗时节点
systemd-analyze critical-chain
(3)单元文件:systemd 的配置核心
systemd 摒弃了传统的 shell 启动脚本,通过纯文本单元文件定义服务的初始化规则,单元文件存放在
/etc/systemd/system/(自定义)和/usr/lib/systemd/system/(系统默认),支持多种类型,核心类型包括:| 单元类型 | 后缀 | 用途 |
|---|---|---|
| service | .service | 系统服务 / 守护进程管理 |
| target | .target | 启动目标(如多用户、图形) |
| socket | .socket | 套接字管理 |
| timer | .timer | 定时任务 |
| mount | .mount | 文件系统挂载 |
所有单元文件均由
[Unit]、[Service]、[Install]三大段构成,各段职责明确,配置语法简洁。1.3 systemd 的核心优势
相比传统 SysV init,systemd 的核心优势体现在:
- 并行启动:同时启动多个无依赖的服务,大幅缩短系统启动时间;
- 集中管理:统一管理进程、网络、设备、日志等,替代多个独立工具;
- 按需启动:通过 socket/timer 实现服务的按需启动,减少系统资源占用;
- 配置简洁:基于声明式单元文件,无需编写复杂的 shell 脚本;
- 状态监控:实时监控服务状态,服务异常时可自动重启(配置后);
- 兼容性:向下兼容 SysV init 的启动脚本,平滑迁移。
2. systemd 核心单元文件解析
所有自定义启动服务均需通过编写单元文件实现,单元文件的三大段为固定格式,各段核心配置项如下,是实现所有自定义功能的基础:
2.1 [Unit] 段:定义单元的元信息与依赖关系
用于描述服务名称、说明,配置服务的启动依赖(如在某个服务 / 目标之后启动),核心配置项:
Description:服务的简短描述,便于识别;After:指定本服务在哪些服务 / 目标之后启动(如 network.target,网络启动后);Before:指定本服务在哪些服务 / 目标之前启动;Requires:强依赖,依赖的服务启动失败,本服务也会启动失败;Wants:弱依赖,依赖的服务启动失败,本服务仍可正常启动(推荐使用)。
2.2 [Service] 段:定义服务的运行规则
核心段,指定服务的执行命令、运行模式、重启策略等,核心配置项:
ExecStart:服务启动时执行的命令 / 脚本路径(必配);ExecStop:服务停止时执行的命令;ExecReload:服务重新加载时执行的命令;Type:服务运行模式,常用simple(默认,立即启动)、idle(等待系统空闲后启动);Restart:重启策略,常用always(服务异常退出时自动重启)、on-failure(仅失败时重启);Environment:设置服务运行的环境变量;TTYPath:指定服务使用的终端设备(如串口登录服务)。
2.3 [Install] 段:定义服务的安装规则
用于配置服务的自启目标,即服务开机自启时归属的启动目标,核心配置项:
WantedBy:指定服务被哪个目标所需要,配置后可通过systemctl enable设置自启;- 常用目标:
multi-user.target(多用户命令行目标,最常用)、default.target(系统默认目标)、graphical.target(图形界面目标);
- 常用目标:
RequiredBy:强依赖目标,目标启动时必须启动本服务。
2.4 通用单元文件模板
基于以上配置项,自定义启动脚本的通用单元文件模板如下(保存为
/etc/systemd/system/[服务名].service):[Unit]
Description=My Custom Startup Service # 自定义服务描述
After=network.target # 网络启动后再执行本服务
[Service]
Type=simple # 运行模式
ExecStart=/path/to/your/script.sh # 自定义脚本的绝对路径
Restart=on-failure # 脚本执行失败时自动重启
[Install]
WantedBy=multi-user.target # 多用户目标下自启
3. 实战场景 1:实现系统串口自动登录
NXP i.MX93EVK 等嵌入式 Linux 设备,默认串口登录需要手动输入用户名和密码,在工业现场、无人值守场景下,自动登录是刚需。该功能通过修改 systemd 的串口登录服务单元文件实现,核心是修改
agetty的启动参数,添加自动登录选项。3.1 找到串口登录的服务单元文件
嵌入式 Linux 中,串口登录服务通常为
serial-getty@ttyLP0.service(ttyLP0 为串口设备名,可根据实际修改),文件位于:/etc/systemd/system/getty.target.wants/serial-getty@ttyLP0.service
3.2 修改单元文件的 [Service] 段
原文件的
ExecStart配置为:[Service]
Environment="TERM=linux"
ExecStart=-/sbin/agetty -8 -L %I 115200 $TERM
Type=idle
Restart=always
# 其他配置...
在
agetty命令中添加--autologin root(自动登录 root 用户),修改后:ExecStart=-/sbin/agetty -8 -L %I --autologin root 115200 $TERM
%I:自动替换为串口设备名(如 ttyLP0);115200:串口波特率,根据实际配置修改。
3.3 生效配置并验证
无需重新加载 systemd,直接重启系统即可生效:
reboot
重启后,串口将直接自动登录 root 用户,无需要求输入用户名和密码,示例输出:
OK] Reached target Graphical Interface.
NXP i.MX Release Distro6.1-mickledore imx93evk ttyLP0
imx93evk login:root(automatic login)
root@imx93evk:~#
4. 实战场景 2:通过 profile.d 实现自定义脚本自动执行
除了编写 systemd service 单元文件,嵌入式 Linux 中还可通过 **
/etc/profile.d/目录实现自定义脚本的自动执行,该方式更轻量,适合环境变量配置、简单驱动加载、基础服务启动 ** 等场景,无需编写复杂的单元文件。4.1 profile.d 的核心原理
/etc/profile是 Linux 系统为 Bash shell 配置的全局初始化脚本,系统启动或用户登录时会自动执行;/etc/profile.d/是/etc/profile的扩展目录,该目录下所有以.sh 结尾的脚本会被自动执行,执行顺序按脚本名称字典序;- 适用于所有登录方式(串口、SSH、图形界面),脚本执行完成后才会进入用户终端;
- 推荐将脚本按数字命名(如 01-wifi.sh、02-bt.sh),实现按顺序执行。
4.2 实战:实现 WiFi-BT 驱动自动加载与 IP 自动获取
以 NXP i.MX93EVK 为例,编写脚本实现系统启动时自动加载 WiFi-BT 驱动、固件,并自动连接 WiFi 获取 IP 地址,步骤如下:
(1)在 /etc/profile.d/ 创建自定义脚本
创建脚本
/etc/profile.d/01-wifi-bt.sh,赋予可执行权限:touch /etc/profile.d/01-wifi-bt.sh
chmod +x /etc/profile.d/01-wifi-bt.sh
(2)编写脚本内容
脚本实现驱动加载、WiFi 使能、自动连接 WiFi、DHCP 获取 IP,核心代码:
#!/bin/sh
# 加载WiFi驱动
modprobe moal mod_para=nxp/wifi_mod_para.conf
echo "wifi driver load successfully" > /dev/kmesg
# 加载BT驱动
modprobe btnxpuart
echo "bt driver load successfully" > /dev/kmesg
# 检查WiFi网卡mlan0是否存在
ifconfig -a | awk '{print $1}' | grep mlan0
if [ $? -eq 0 ]
then
# 使能WiFi网卡
ifconfig mlan0 up
# 添加WiFi密码到配置文件(替换SSID和PASSWORD为实际WiFi信息)
wpa_passphrase SSID PASSWORD >> /etc/wpa_supplicant.conf
# 关闭以太网(可选)
ifconfig eth0 down
# 后台启动wpa_supplicant,连接WiFi
wpa_supplicant -d -B -i mlan0 -c /etc/wpa_supplicant.conf -Dnl80211
# DHCP自动获取IP
udhcpc -i mlan0
else
echo "wifi module loaded failure!" > /dev/kmesg
fi
(3)重启系统验证效果
重启系统后,脚本会自动执行,通过dmesg或串口日志可查看执行过程,最终 WiFi 会自动连接并获取 IP,通过ifconfig mlan0验证:
ifconfig mlan0
# 示例输出:
mlan0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 192.168.0.221 netmask 255.255.255.0 broadcast 192.168.0.255
ether d0:17:69:ee:6b:08 txqueuelen 1000 (Ethernet)
RX packets 33 bytes 7000 (6.8 KiB)
TX packets 48 bytes 6906 (6.7 KiB)
4.3 profile.d 的优缺点
优点
- 配置简单:无需编写 systemd 单元文件,仅需创建.sh 脚本并赋予执行权限;
- 按序执行:通过数字命名实现脚本的顺序执行,满足依赖需求;
- 全局生效:适用于所有登录方式,一次配置全局有效;
- 易于维护:单独的脚本文件,修改、删除方便,不影响其他系统配置。
缺点
- 无状态监控:systemd 不会监控脚本执行状态,执行失败无自动重启机制;
- 阻塞登录:脚本执行完成后才会进入用户终端,脚本耗时过长会导致登录延迟;
- 仅适用于简单场景:不适合复杂的守护进程、需要后台运行的服务(此类场景推荐使用 systemd service)。
5. systemd 服务的完整配置流程(自定义 service 方式)
对于需要后台运行、状态监控、自动重启的复杂服务(如自定义应用、守护进程),推荐使用 systemd service 单元文件的方式,完整配置流程如下,以自定义
myapp.service为例:5.1 编写自定义执行脚本
创建脚本
/usr/local/bin/myapp.sh,实现业务逻辑,赋予可执行权限:#!/bin/sh
# 自定义业务逻辑,如启动后台应用
nohup /usr/local/bin/myapp > /var/log/myapp.log 2>&1 &
chmod +x /usr/local/bin/myapp.sh
5.2 编写 systemd 单元文件
创建单元文件
/etc/systemd/system/myapp.service,内容如下:[Unit]
Description=My Custom Application Service
After=network.target syslog.target # 网络和日志服务启动后执行
[Service]
Type=forking # 后台运行模式(对应nohup)
ExecStart=/usr/local/bin/myapp.sh # 启动脚本
ExecStop=/usr/bin/pkill -9 myapp # 停止命令
Restart=always # 任何情况都自动重启
RestartSec=3 # 重启间隔3秒
[Install]
WantedBy=multi-user.target
5.3 加载配置并设置自启
# 重新加载systemd守护进程,使新配置生效
sudo systemctl daemon-reload
# 设置服务开机自启
sudo systemctl enable myapp.service
# 启动服务
sudo systemctl start myapp.service
# 查看服务状态
sudo systemctl status myapp.service
5.4 服务的日常管理
# 停止服务
sudo systemctl stop myapp.service
# 重启服务
sudo systemctl restart myapp.service
# 查看服务日志(定位错误)
journalctl -u myapp.service -f
6. 常见问题排查与避坑指南
6.1 自定义 service 启动失败,提示 "ExecStart path is not absolute"
- 原因:
ExecStart配置的脚本 / 命令路径为相对路径,systemd 要求必须使用绝对路径; - 解决:修改为绝对路径,如
/usr/local/bin/script.sh,而非./script.sh。
6.2 profile.d 中的脚本未执行
- 原因 1:脚本未赋予可执行权限(缺少 x 权限);
- 原因 2:脚本后缀不是.sh,systemd 仅执行.sh 结尾的脚本;
- 原因 3:脚本存在语法错误,导致执行中断;
- 解决:执行
chmod +x 脚本.sh,检查脚本后缀和语法,通过sh 脚本.sh手动执行测试。
6.3 服务设置自启后,重启系统仍未自动启动
- 原因 1:未执行
sudo systemctl daemon-reload,配置未生效; - 原因 2:
[Install]段的WantedBy配置的目标不存在,或目标未启动; - 原因 3:服务存在依赖错误,通过
systemctl status 服务名查看具体错误; - 解决:重新执行
daemon-reload,核对WantedBy配置为multi-user.target(通用),排查依赖错误。
6.4 自动登录配置后,串口无输出
- 原因:串口设备名配置错误(如 ttyLP0 改为 ttyS0),或波特率不匹配;
- 解决:通过
dmesg | grep tty查看实际串口设备名,修改单元文件中的波特率和设备名。
6.5 服务重启策略不生效(Restart=always)
- 原因:
Type配置的运行模式与服务实际运行方式不匹配(如后台运行的服务配置为simple); - 解决:后台运行的服务(如 nohup、&)配置
Type=forking,前台运行的服务配置Type=simple。
7. 两种自动启动方式的选型建议
systemd service 和 profile.d 是嵌入式 Linux 中最常用的两种自动启动方式,需根据实际场景选择,核心选型原则如下:
推荐使用 systemd service 的场景
- 自定义应用、守护进程,需要后台运行、状态监控、自动重启;
- 服务有明确的依赖关系(如需要在网络 / 数据库启动后执行);
- 需要对服务进行启动、停止、重启的日常管理;
- 无人值守场景,要求服务异常时自动恢复。
推荐使用 profile.d 的场景
- 简单的初始化操作,如环境变量配置、驱动加载、临时文件清理;
- 无需状态监控,执行一次即可,无需后台运行;
- 希望快速配置,无需编写复杂的单元文件;
- 多个简单脚本需要按顺序执行。
systemd 作为 Linux 启动初始化的标准方案,通过单元文件实现了服务配置的标准化和简洁化,相比传统 SysV init 大幅提升了启动效率和管理便捷性。本文讲解的串口自动登录和profile.d 自定义脚本是嵌入式 Linux 中最典型的实战场景,而自定义 systemd service 则适用于更复杂的后台服务管理。
核心使用要点:
- 所有 systemd 配置修改后,需执行
systemctl daemon-reload生效; - 单元文件的
ExecStart必须使用绝对路径,依赖关系优先使用After和Wants; - 简单场景用 profile.d,复杂场景用 systemd service,兼顾效率和可维护性;
- 利用
systemctl status和journalctl排查服务问题,利用systemd-analyze优化启动性能。
基于 systemd 的启动初始化配置,可灵活实现嵌入式 Linux 设备的自动化定制,满足工业现场、无人值守、消费电子等各类场景的需求,是嵌入式 Linux 开发工程师的必备技能。
阅读全文
158