4.4.1. 自启动设置
4.4.1.1. 概述与介绍
在Busybox init。通过/etc/init.d 目录
4.4.1.2. 功能详细介绍
系统启动过程
从 Kernel 到 init

在 Linux 内核
init 进程: 所有
用户 空间 进程 的 祖先,负责管理 和 创建 用户 空间 进程。 kthreadd 进程: 所有
内核 线程 的 祖先,负责 内核 线程 的 管理。
通过
static noinline void __ref rest_init(void)
{
...
pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
...
cpu_startup_entry(CPUHP_ONLINE);
}
static int __ref kernel_init(void *unused)
{
...
/* 通过设置 bootargs 的 "rdinit=/sbin/init" 来指定,如果指定则启动ramdisk。*/
if (ramdisk_execute_command) {
ret = run_init_process(ramdisk_execute_command);
...
}
/* 通过设置 bootargs 的 "init=/sbin/init" 来指定,包括启动参数 argv_init[]。*/
if (execute_command) {
ret = run_init_process(execute_command);
...
}
/* 如果没有指定rdinit和init,那么依次尝试下面几个固定路径init程序。*/
if (!try_to_run_init_process("/sbin/init") ||
!try_to_run_init_process("/etc/init") ||
!try_to_run_init_process("/bin/init") ||
!try_to_run_init_process("/bin/sh"))
return 0;
...
}
int kthreadd(void *unused)
{
...
/* Setup a clean context for our children to inherit. */
/* 修改内核线程名为kthreadd。*/
set_task_comm(tsk, "kthreadd");
...
cgroup_init_kthreadd();
for (;;) {
...
spin_lock(&kthread_create_lock);
/* 内核线程的创建是由kthreadd遍历kthread_create_list列表 */
/* 然后取出成员,通过create_kthread()创建内核线程。*/
while (!list_empty(&kthread_create_list)) {
struct kthread_create_info *create;
create = list_entry(kthread_create_list.next,
struct kthread_create_info, list);
list_del_init(&create->list);
spin_unlock(&kthread_create_lock);
create_kthread(create);
spin_lock(&kthread_create_lock);
}
spin_unlock(&kthread_create_lock);
}
return 0;
}
init 进程分析
init_main() 是 Busybox 中
int init_main(int argc UNUSED_PARAM, char **argv)
{
... ...
/* Make sure environs is set to something sane */
/* 设置环境变量,SHELL指向/bin/sh */
putenv((char *) "HOME=/");
putenv((char *) bb_PATH_root_path);
putenv((char *) "SHELL=/bin/sh");
putenv((char *) "USER=root"); /* needed? why? */
... ...
/* Check if we are supposed to be in single user mode */
if (argv[1]
&& (strcmp(argv[1], "single") == 0 || strcmp(argv[1], "-s") == 0 || LONE_CHAR(argv[1], '1'))
) {
... ...
} else {
/* Not in single user mode - see what inittab says */
/* NOTE that if CONFIG_FEATURE_USE_INITTAB is NOT defined,
* then parse_inittab() simply adds in some default
* actions (i.e., INIT_SCRIPT and a pair
* of "askfirst" shells) */
/* 解析/etc/inittab文件,下面按照SYSINIT->WAIT->ONCE->RESPAWN|ASKFIRST顺序执行inittab内容。 */
parse_inittab();
}
... ...
/* Now run the looping stuff for the rest of forever */
while (1) {
... ...
/* Wait for any child process(es) to exit */
while (1) {
/* -1表示等待任一子进程。若成功则返回状态改变的子进程ID,若出错则返回-1,
* 若指定了WNOHANG选项且pid指定的子进程状态没有发生改变则返回0。
*/
wpid = waitpid(-1, NULL, WNOHANG);
if (wpid <= 0)
break;
/* 将进程的init_action->pid改成0. */
a = mark_terminated(wpid);
if (a) {
message(L_LOG, "process '%s' (pid %u) exited. "
"Scheduling for restart.",
a->command, (unsigned)wpid);
}
}
... ...
} /* while (1) */
}
在 init_main() 函数
/* NOTE that if CONFIG_FEATURE_USE_INITTAB is NOT defined,
* then parse_inittab() simply adds in some default
* actions (i.e., runs INIT_SCRIPT and then starts a pair
* of "askfirst" shells). If CONFIG_FEATURE_USE_INITTAB
* _is_ defined, but /etc/inittab is missing, this
* results in the same set of default behaviors.
*/
static void parse_inittab(void)
{
#if ENABLE_FEATURE_USE_INITTAB
char *token[4];
parser_t *parser = config_open2("/etc/inittab", fopen_for_read);
if (parser == NULL)
... ...
/* No inittab file - set up some default behavior */
/* Sysinit */
new_init_action(SYSINIT, INIT_SCRIPT, "");
... ...
}
... ...
}
在 parse_inittab 里面/etc/init.d/rcS。
/* Default sysinit script. */
#ifndef INIT_SCRIPT
# define INIT_SCRIPT "/etc/init.d/rcS"
#endif
查看
rcS:12345:wait:/etc/init.d/rcS
/etc/init.d/rcS
/etc/init.d/rcS 脚本.sh结尾
.sh后缀的 程序:对于 以 .sh结尾的 文件,会 使用 source的方式( . filename)执行,脚本中 的 命令 将 在 当前 shell 进程 中 执行,而 不是 在 一个 子 shell 中 执行。这样 可以 更 快 地 执行,并且 允许 脚本 中 的 变量 和 环境 在 当前 shell 中 保持。 非
.sh后缀的 程序:对于 没有 .sh结尾的 文件,直接 执行 $i start,将在 一个 单独 的 进程 中 执行。
#!/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
4.4.1.3. 移植与开发
在
示例:创建初始化脚本
步骤 1:创建脚本文件
vi /etc/init.d/S99my_custom_service
步骤 2:编辑脚本
将
#!/bin/sh
# Path to your program
PROG="/path/to/your/program"
# Optional arguments for your program
ARGS=""
start() {
echo "Starting my_custom_service"
$PROG $ARGS &
}
stop() {
echo "Stopping my_custom_service"
# Add commands to stop your service gracefully
}
restart() {
stop
sleep 1
start
}
case "$1" in
start)
start
;;
stop)
stop
;;
restart)
restart
;;
*)
echo "Usage: $0 {start|stop|restart}"
exit 1
;;
esac
exit 0
步骤 3:设置权限
确保
chmod +x /etc/init.d/S99my_custom_service
用法
启动
服务: sudo /etc/init.d/S99my_custom_service start停止
服务: sudo /etc/init.d/S99my_custom_service stop重启
服务: sudo /etc/init.d/S99my_custom_service restart
注意事项
替换
/path/to/your/program为实际 程序 的 路径 根据
需要 编辑 ARGS变量,以传递 任何 必要 的 参数 给 你 的 程序 根据
需要 添加 停止 和 重启 服务 的 命令,将 其 放在 stop和restart部分自启动
程序 根据 名称 前缀(S??)的 数字 可以 设定 执行 优先级,数字 越小 优先级 越高,取值 范围0-99。
在/etc/init.d/S99auto_startup 自启动/app 和 /userdata/ 目录startup.sh ,如果 startup.sh 程序
#!/bin/bash
... ...
start()
{
APP_STARTUP="/app/startup.sh"
USERDATA_STARTUP="/userdata/startup.sh"
if [ -x "$APP_STARTUP" ]; then
echo -n "<$LOG_INFO>Starting custom script: $APP_STARTUP" > /dev/kmsg
$APP_STARTUP &
fi
if [ -x "$USERDATA_STARTUP" ]; then
echo -n "<$LOG_INFO>Starting custom script: $USERDATA_STARTUP" > /dev/kmsg
$USERDATA_STARTUP &
fi
}
... ...
用户
4.4.1.4. 常见问题
自启动
脚本 执行 失败 的 原因 可能 是 什么?
常见原因 包括 脚本 缺少 可 执行 权限、路径 不 正确、依赖 的 服务 未 启动 等。 如何
判断 自启动 脚本 是否 执行?
您可以 通过 查看 系统日志 或 在 脚本 中 添加 日志 输出,确认 是否 成功 执行。