Linux启动流程-init守护进程
一.Preface
最进在项目上碰到了很多与“守护(init)进程”相关的问题,例如有:
- 制作根文件系统的时候需要去选择某个具体的“Init system”。
- 在开机加速的问题上,需要对Init程序的脚本进行“裁剪”。
- kernel的启动流程,Init进程作为系统的第一个进程也非常重要。
所以这里做一个笔记,方便日后复习。
二.What is "Init"
- 内核启动的第一个用户空间进程,Linux 中所有的进程都由 init 进程直接或间接进行创建并运行。
- 创建Init进程,内核初始化(__init start_kernel函数)的最后一步调用reset_init函数,在内核态创建Init守护进程。
- 内核态的Init进程的callback
- 设备驱动初始化。
- 通过uboot的bootargs(环境变量)设置console作为标准的input、output、error。
- 挂载filesystem。
- 在filesystem特定目录下查找具体的init程序并运行,init进程也会切换至用户态。
- Init守护进程的实现方式分类
- SysV init(System V init):最早的Linux发行版采用这种方式(包括imx6ull), 它采用脚本的形式来管理系统服务。在启动过程中,SysV init会按照特定的顺序启动系统服务,并运行对应的启动脚本。缺点是启动速度比较慢,因为它需要按照顺序逐个启动服务。
- Upstart:Upstart是一种事件驱动的init程序,可以并行启动多个服务,提高系统启动速度。
- Systemd:,目前最流行的init程序;让所有无论有无依赖关系的程序全部并行启动,并且仅按照系统启动的需要启动相应的服务,最大化提高开机启动速度。
三.What is the "Init" used for
这里根文件系统里的Init程序进行分析,因为笔者用的开发板采用"SysV init"守护进程,所以这里只分析"SysV init"守护进程。
1. 在“/sbin”目录下找到init程序并执行
2. init程序会读取/etc/initab文件,进行初始化
点击查看代码
# /etc/inittab: init(8) configuration.
# The default runlevel.
id:5:initdefault:
# Boot-time system configuration/initialization script.
# This is run first except when booting in emergency (-b) mode.
si::sysinit:/etc/init.d/rcS
# What to do in single-user mode.
~~:S:wait:/sbin/sulogin
# Runlevel 0 is halt.
# Runlevel 1 is single-user.
# Runlevels 2-5 are multi-user.
# Runlevel 6 is reboot.
l0:0:wait:/etc/init.d/rc 0 # 当系统进入运行级别 0 时,init 进程会等待 /etc/init.d/rc 脚本执行完成,并将系统切换到运行级别 0,通常用于系统关闭。
l1:1:wait:/etc/init.d/rc 1
l2:2:wait:/etc/init.d/rc 2
l3:3:wait:/etc/init.d/rc 3
l4:4:wait:/etc/init.d/rc 4
l5:5:wait:/etc/init.d/rc 5 # 当系统进入运行级别 5 时,init 进程会等待 /etc/init.d/rc 脚本执行完成,并将系统切换到运行级别 5,通常用于启动图形界面环境。因为开机默认运行级别为5,所以开机时会默认执行rc5.d这个目录下的脚本。
l6:6:wait:/etc/init.d/rc 6
# Normally not reached, but fallthrough in case of emergency.
z6:6:respawn:/sbin/sulogin
#mxc0:12345:respawn:/bin/start_getty 115200 ttymxc0
mxc0:12345:respawn:/sbin/getty -l /bin/autologin -n -L 115200 ttymxc0
3.首先执行rcS脚本(si::sysinit:/etc/init.d/rcS)
点击查看代码
#!/bin/sh
PATH=/sbin:/bin:/usr/sbin:/usr/bin
runlevel=S
prevlevel=N
umask 022
export PATH runlevel prevlevel
# Make sure proc is mounted
#
[ -d "/proc/1" ] || mount /proc
#
# Source defaults.
#
. /etc/default/rcS
trap ":" INT QUIT TSTP
exec /etc/init.d/rc S #在当前的 shell 进程中执行 /etc/init.d/rc 脚本。并向它传递一个参数 S。这里的 S 表示特殊启动(special boot),通常用于系统维护或单用户模式。
4.执行rc脚本(si::sysinit:/etc/init.d/rc)
点击查看代码
root@ATK-IMX6U:/etc/init.d# cat rc
#!/bin/sh
. /etc/default/rcS
export VERBOSE
# 启动进度条的更新
startup_progress() {
step=$(($step + $step_change))
if [ "$num_steps" != "0" ]; then
progress=$((($step * $progress_size / $num_steps) + $first_step))
else
progress=$progress_size
fi
#echo "PROGRESS is $progress $runlevel $first_step + ($step of $num_steps) $step_change $progress_size"
if type psplash-write >/dev/null 2>&1; then
TMPDIR=/mnt/.psplash psplash-write "PROGRESS $progress" || true
fi
#if [ -e /mnt/.psplash/psplash_fifo ]; then
# echo "PROGRESS $progress" > /mnt/.psplash/psplash_fifo
#fi
}
# Start script or program.
startup() {
# Handle verbosity
[ "$VERBOSE" = very ] && echo "INIT: Running $@..."
case "$1" in
*.sh)
# Source shell script for speed.
(
trap - INT QUIT TSTP
scriptname=$1
shift
. $scriptname
)
;;
*)
"$@"
;;
esac
startup_progress
}
# Ignore CTRL-C only in this shell, so we can interrupt subprocesses.
trap ":" INT QUIT TSTP
stty onlcr 0>&1
[ "$STACK_SIZE" == "" ] || ulimit -S -s $STACK_SIZE # 限制启动脚本的栈大小
runlevel=$RUNLEVEL
# Get first argument. Set new runlevel to this argument.
[ "$1" != "" ] && runlevel=$1
if [ "$runlevel" = "" ]
then
echo "Usage: $0 <runlevel>" >&2 #检查运行此运行级别的rc目录是否存在,如"/etc/rc2.d"
exit 1
fi
previous=$PREVLEVEL
[ "$previous" = "" ] && previous=N
export runlevel previous
# Is there an rc directory for this new runlevel?
if [ -d /etc/rc$runlevel.d ]
then
# Split the remaining portion of the progress bar into thirds
progress_size=$(((100 - $PROGRESS_STATE) / 3)) # 根据运行级别,分割进度条并设置进度条的起始点和步长。
case "$runlevel" in
0|6)
# Count down from -100 to 0 and use the entire bar
first_step=-100
progress_size=100
step_change=1
;;
S)
# Begin where the initramfs left off and use 2/3
# of the remaining space
first_step=$PROGRESS_STATE
progress_size=$(($progress_size * 2))
step_change=1
;;
*)
# Begin where rcS left off and use the final 1/3 of
# the space (by leaving progress_size unchanged)
first_step=$(($progress_size * 2 + $PROGRESS_STATE))
step_change=1
;;
esac
num_steps=0
for s in /etc/rc$runlevel.d/[SK]*; do #根据运行级别,分割进度条并设置进度条的起始点和步长。
case "${s##/etc/rc$runlevel.d/S??}" in
gdm|xdm|kdm|reboot|halt)
break
;;
esac
num_steps=$(($num_steps + 1))
done
step=0
# First, run the KILL scripts. 先运行 K 脚本停止服务。
if [ $previous != N ]
then
for i in /etc/rc$runlevel.d/K[0-9][0-9]*
do
# Check if the script is there.
[ ! -f $i ] && continue
# Stop the service.
startup $i stop
done
fi
# Now run the START scripts for this runlevel.
for i in /etc/rc$runlevel.d/S* #运行 S 脚本启动服务,但会跳过那些在上一个运行级别已经运行且没有对应的 K 脚本的服务。
do
[ ! -f $i ] && continue
if [ $previous != N ] && [ $previous != S ]
then
suffix=${i#/etc/rc$runlevel.d/S[0-9][0-9]}
stop=/etc/rc$runlevel.d/K[0-9][0-9]$suffix
previous_start=/etc/rc$previous.d/S[0-9][0-9]$suffix
[ -f $previous_start ] && [ ! -f $stop ] && continue
fi
case "$runlevel" in
0|6)
startup $i stop
;;
*)
startup $i start
;;
esac
done
fi
#Uncomment to cause psplash to exit manually, otherwise it exits when it sees a VC switch
if [ "x$runlevel" != "xS" ] && [ ! -x /etc/rc${runlevel}.d/S??xserver-nodm ]; then # 如果不是 S 级别,并且没有 xserver-nodm 服务(即没有显示管理器),则发送信号让 Plymouth 退出。
if type psplash-write >/dev/null 2>&1; then
TMPDIR=/mnt/.psplash psplash-write "QUIT" || true
umount -l /mnt/.psplash
fi
fi
5.最后执行"rc5.d"目录下的脚本
可以在此处对一些不需要的应用进行裁剪。(注释:S表示start,K表示kill)
四.what's the problem with "init"?
若内核没有在根文件系统下没有找到"init"程序,则会启动失败报错。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通