【嵌入式Linux基础】启动初始化程序--init程序
标签: 【嵌入式Linux基础】启动初始化程序--init程序
2023-06-04 18:23:47 43浏览
内核启动后
内核引导代码在initramfs中通过内核命令行中的root=指定的文件系统中去寻找根系统文件,并执行一个相应的程序。在默认情况下,对于initramfs这个程序是/init,对于常规文件系统,是/sbin/init程序。init程序具有root权限,且因为它是第一个运行的进程,所以其进程ID(PID)是1。如果init程序未能启动,则内核将会崩溃。
init程序是所有其他进程的祖先。在这里,通过pstree命令可以看出,在大部分版本中,init通常是psmisc包中的一部分:
# pstree -gn
init(1)-+-syslogd(63)
|-klogd(66)
|-dropbear(99)
`-sh(100)---pstree(109)
init程序的任务是接管系统并使之运行。它和运行shell脚本的shell命令一样简单。
- 在启动阶段,它启动守护进程,配置系统参数,以及负责让系统进入工作状态所需要做的配置操作。
- 作为可选项,它可以启动其他守护进程,如在终端上的getty守护进程,该进程允许登录shell。
- 接收那些因为直接父进程被终止,以及线程组中没有其他进程而形成的孤儿进程。
- 它通过捕捉信号SIGCHLD并收集其返回值以防止它们成为僵尸进程,对init的直接子进程的终止进行响应。
- 作为一个可选项,它重新启动那些已经终止的守护进程。
- 它处理系统关机。
换句话说,init管理着系统从开机到关机的整个生命周期。
初始化程序简介
在嵌入式设备中,你最有可能遇到三个初始化程序是BusyBox init、System V init 和 systemd。Buildroot默认只构建BusyBox init,你也可以选择构建所有三个初始化程序。Yocto项目默认构建System V init,当然其他两种也可以选择。
类别 | Busy Box init | System V init | systemd |
---|---|---|---|
复杂性 | 低 | 中 | 高 |
启动速度 | 快 | 慢 | 中 |
所需shell | ash | ash 或 bash | 无 |
可执行程序数量 | 0 | 4 | 50(构建系统时的配置 |
支持的C库 | 任意 | 任意 | glibc |
大小(MB) | 0 | 0.1 | 34(构建系统时的配置 |
systemd的50个可执行程序、34MB的大小都是基于Buildroot的配置。
一般来说,从BusyBox init到 systemd,灵活性和复杂性都会逐渐增加。
BusyBox init
BusyBox 的 init 程序最小,它使用配置文件/etc/inittab
来定义规则,在系统启动阶段控制程序启动,在关机阶段控制程序终止。通常情况下,实际工作是由shell脚本来完成的,而按照惯例,脚本会放置在/etc/init.d
目录下。
init 首先会读取配置文件/etc/inittab
。配置文件包含了一个需要运行的程序列表,一行一个,格式如下:
<id>::<action>:<program>
- id:指令所针对的控制终端
- action:运行该指令的条件,将在下面的段落展示
- program:待运行的程序
运行该指令的条件(action
字段)的可填写的内容:
- sysinit:在其他所有类型的操作之前,当初始化开始时,运行程序。
- respawn:运行指定程序,如果程序终止则重新启动。
- askfirst:与respawn相同,但是这个会向控制台输出“Please press Enter to activate this console”,并在按下Enter键时运行该程序。它用于在终端上启动一个交互式shell,且不提示输入用户名或密码。
- once:运行指定程序,如果该程序终止,不会尝试重新启动它。
- wait:运行指定程序,并等待其完成。
- restart:当init接收到信号SIGHUP时,这表明其应该重新载入inittab文件,此时运行指定程序。
- ctrlaltdel:当init接收到信号SIGINT时运行指定程序,这通常是由于用户在控制台按下了 Ctrl+Alt+Del 组合按键。
- shutdown:当init关闭时运行指定程序。
这是一个完整的实际使用的inittab
文件
# /etc/inittab
#
# Copyright (C) 2001 Erik Andersen <andersen@codepoet.org>
#
# Note: BusyBox init doesn't support runlevels. The runlevels field is
# completely ignored by BusyBox init. If you want runlevels, use
# sysvinit.
#
# Format for each entry: <id>:<runlevels>:<action>:<process>
#
# id == tty to run on, or empty for /dev/console
# runlevels == ignored
# action == one of sysinit, respawn, askfirst, wait, and once
# process == program to run
# Startup the system
::sysinit:/bin/mount -t proc proc /proc
::sysinit:/bin/mount -o remount,rw /
::sysinit:/bin/mkdir -p /dev/pts
::sysinit:/bin/mkdir -p /dev/shm
::sysinit:/bin/mount -a 2>/dev/null
::sysinit:/bin/hostname -F /etc/hostname
# now run any rc scripts
::respawn:-/bin/sh
::sysinit:/etc/init.d/rcS
# Put a getty on the serial port
#ttyFIQ0::respawn:/sbin/getty -L ttyFIQ0 0 vt100 # GENERIC_SERIAL
# Stuff to do for the 3-finger salute
#::ctrlaltdel:/sbin/reboot
# Stuff to do before rebooting
::shutdown:/etc/init.d/rcK
::shutdown:/sbin/swapoff -a
::shutdown:/bin/umount -a -r
/etc/inittab
文件中<action>
字段的意义
action名称 | 执行条件 | 说明 |
---|---|---|
sysinit | 系统启动后最先执行 | 只执行一次,init进程等待它结束后才继续执行其他动作 |
wait | 系统执行完sysinit进程后 | 只执行一次,init进程等待它结束才继续执行其他动作 |
once | 系统执行完wait进程后 | 只执行一次,init进程不等待它结束 |
respawn | 启动完once进程后 | init进程检测发现子进程退出时,重新启动它 |
askfirst | 启动完respawn进程后 | 与respawn进程类型,不过init进程先输出“Please press Enter to activate this console”,等用户输入回车键之后才启动子进程。 |
shutdown | 当系统关机时执行 | 即重启、关闭系统命令时 |
restart | BusyBox中配置了CONFIG_FEATURE_USE_INITTAB,并且init进程接收到SIGHUP信号时 | 先重新读取、解析/etc/inittab文件,再执行restart程序 |
ctrlaltdel | 按下 Ctrl+Alt+Del 组合键时 |
以下是一个小例子,包括挂载的 proc 和 sysfs ,以及在串行接口运行 shell:
null::sysinit:/bin/mount -t proc proc /proc
null::sysinit:/bin/mount -t sysfs sysfs /sys
console::askfirst:-/bin/sh
对于简单的项目,比如你只想启动少量的守护进程,或者是在串口终端启动一个登录shell,手写一个脚本也很容易。这种情况下,如果你创建一个简单的定制的嵌入式Linux是很合适的。然而,你会发现随着需要配置的事情不断增加,手写一个init脚本很快就会变得非常难以维护。
Buildroot init 脚本
多年以来,Buildroot有效使用了BusyBox 的init程序。Buildroot在/etc/init.d
目录中有两个脚本,名为 rcS
和 rcK
。第一个脚本 rcS 在开机时运行,并从一个大写S加两位数字开始遍历所有的脚步,并按数字顺序运行,这就是开始脚本。rcK 脚本在关机时运行,从一个大写K 加两位数字开始遍历所有的脚本,并按数字顺序运行,这就是结束脚本。
以上的做法也是存在的,实际上更多做法是在目录/etc/init.d
里面只有众多的S开头的启动脚本,在S开头的脚本里面通过传入的参数start
或者stop
来执行开机时的操作逻辑或者关机时的操作逻辑。
一个典型实际使用的rcS脚本:
#!/bin/sh
# Start all init scripts in /etc/init.d
# executing them in numerical order.
#
for i in /etc/init.d/S??* ;do
# Ignore dangling symlinks (if any).
[ ! -f "$i" ] && continue
case "$i" in
*.sh)
# Source shell script for speed.
(
trap - INT QUIT TSTP
set start
. $i
)
;;
*)
# No sh extension, so fork subprocess.
$i start
;;
esac
done
一个典型实际使用的rcK脚本:
#!/bin/sh
# Stop all init scripts in /etc/init.d
# executing them in reversed numerical order.
#
for i in $(ls -r /etc/init.d/S??*) ;do
# Ignore dangling symlinks (if any).
[ ! -f "$i" ] && continue
case "$i" in
*.sh)
# Source shell script for speed.
(
trap - INT QUIT TSTP
set stop
. $i
)
;;
*)
# No sh extension, so fork subprocess.
$i stop
;;
esac
done
一个典型实际使用的S开头的用于alsa系统启动的脚本:
S04alsa.sh
#!/bin/sh
#
case "$1" in
start)
if [ -f "/etc/asound.state" ];then
alsactl restore -f /etc/asound.state
fi
#enable spk
echo 1 > /proc/rp_power/spk_on
echo 0 > /proc/rp_power/spk_mute
;;
stop)
echo "not stop function"
;;
*)
echo "Usage: $0 {start|stop}"
exit 1
;;
esac
在操作系统中,一般系统的服务都是以后台进程的方式存在,而且都会常驻系统中,直到关机才结束。这类服务也称Daemon,在Linux系统中就包含许多的Daemon。判断Daemon最简单的方法就是从名称上看。多数的Daemon都是由服务名称加上d。例如,在Linux操作系统中HTTP服务的Deamon就是httpd。
通过这一套机制,Buildroot包能够很容易地提供自己的开始脚本和结束脚本,并利用两个数字编号影响这些脚本的运行顺序,使其按照应有的顺序执行,从而使系统成为可扩展的。
后记
其他的init程序比如:System V init 和 systemd,笔者也没有使用过,有兴趣可以去看《嵌入式Linux编程》【Chris Simmonds】这本书的【第9章 启动初始化程序】的9.4和9.5小节。
好博客就要一起分享哦!分享海报
此处可发布评论
评论(0)展开评论
展开评论