文章目录
1. 前言
这篇文章主要讲的是system目录下的init进程启动过程相关的内容,仅作参考。码字不易,转载请注明出处!
2. init.c
当linux内核启动之后,运行的第一个用户进程是init,进程号为1。这个进程是一个守护进程,它的生命周期贯穿整个linux 内核运行的始终,linux中所有其它的进程的共同始祖均为init进程。这边可以简单说一下init进程的启动过程,然后详细说明init进程做了哪些事情。
2.1 init进程的诞生
当我们设备上电的时候,系统到init进程启动之前做了以下的操作:
- 初始化 RAM(一般指内存)
- 初始化硬件设备
- 加载内核和内存空间影像图
- 跳转到内核
1-3阶段,不同芯片厂家会有属于自家的方案,感兴趣的话可以去查一下资料,这里也不再赘述,这里只说明第4个阶段,即跳转到内核阶段:
========= kernel/linux-3.10.y/init/main.c ===========
smlinkage void __init start_kernel(void)
{
.......
/* Do the rest non-__init'ed, we're now alive */
rest_init();
}
static noinline void __init_refok rest_init(void)
{
int pid;
rcu_scheduler_starting();
/*
* We need to spawn init first so that it obtains pid 1, however
* the init task will end up wanting to create kthreads, which, if
* we schedule it before we create kthreadd, will OOPS.
*/
kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);
numa_default_policy();
pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
rcu_read_lock();
kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);
rcu_read_unlock();
complete(&kthreadd_done);
/*
* The boot idle thread must execute schedule()
* at least once to get things moving:
*/
init_idle_bootup_task(current);
schedule_preempt_disabled();
/* Call into cpu_idle with preempt disabled */
cpu_startup_entry(CPUHP_ONLINE);
}
kernel_thread会调用do_fork函数用于创建进程,进程创建成功后会通过函数指针回调执行kernel_init函数
static int __ref kernel_init(void *unused)
{
kernel_init_freeable();
/* need to finish all async __init code before freeing the memory */
async_synchronize_full();
free_initmem();
mark_rodata_ro();
system_state = SYSTEM_RUNNING;
numa_default_policy();
flush_delayed_fput();
if (ramdisk_execute_command) {
if (!run_init_process(ramdisk_execute_command))
return 0;
pr_err("Failed to execute %s\n", ramdisk_execute_command);
}
/*
* We try each of these until one succeeds.
*
* The Bourne shell can be used instead of init if we are
* trying to recover a really broken machine.
*/
if (execute_command) {
if (!run_init_process(execute_command))
return 0;
pr_err("Failed to execute %s. Attempting defaults...\n",
execute_command);
}
if (!run_init_process("/sbin/init") ||
!run_init_process("/etc/init") ||
!run_init_process("/bin/init") ||
!run_init_process("/bin/sh"))
return 0;
panic("No init found. Try passing init= option to kernel. "
"See Linux Documentation/init.txt for guidance.");
}
kernel_init主要工作是完成一些init的初始化操作,然后去系统根目录下依次找ramdisk_execute_command和execute_command设置的应用程序,如果这两个目录都找不到,就依次去根目录下找 /sbin/init,/etc/init,/bin/init,/bin/sh 这四个应用程序进行启动,只要这些应用程序有一个启动了,其他就不启动了。
最终是通过调用run_init_process函数启动init进程:
static int run_init_process(const char *init_filename)
{
argv_init[0] = init_filename;
return do_execve(init_filename,
(const char __user *const __user *)argv_init,
(const char __user *const __user *)envp_init);
}
2.1 init进程的工作
============== system/core/init/init.c ================
int main(int argc, char **argv)
{
int fd_count = 0;
struct pollfd ufds[4];
char *tmpdev;
char* debuggable;
char tmp[32];
int property_set_fd_init = 0;
int signal_fd_init = 0;
int keychord_fd_init = 0;
bool is_charger = false;
init_prop_list();
if (!strcmp(basename(argv[0]), "ueventd"))
return ueventd_main(argc, argv);
if (!strcmp(basename(argv[0]), "watchdogd"))
return watchdogd_main(argc, argv);
/* clear the umask */
umask(0);
mkdir("/dev", 0755);
mkdir("/proc", 0755);
mkdir("/sys", 0755);
mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755");
mkdir("/dev/pts", 0755);
mkdir("/dev/socket", 0755);
mount("devpts", "/dev/pts", "devpts", 0, NULL);
mount("proc", "/proc", "proc", 0, NULL);
mount("sysfs", "/sys", "sysfs", 0, NULL);
close(open("/dev/.booting", O_WRONLY | O_CREAT, 0000));
open_devnull_stdio();
klog_init();
property_init();
get_hardware_name(hardware, &revision);
process_kernel_cmdline();
union selinux_callback cb;
cb.func_log = klog_write;
selinux_set_callback(SELINUX_CB_LOG, cb);
cb.func_audit = audit_callback;
selinux_set_callback(SELINUX_CB_AUDIT, cb);
selinux_initialize();
restorecon("/dev");
restorecon("/dev/socket");
restorecon("/dev/__properties__");
restorecon_recursive("/sys");
is_charger = !strcmp(bootmode, "charger");
INFO("property init\n");
if (!is_charger)
property_load_boot_defaults();
INFO("reading config file\n");
init_parse_config_file("/init.rc");
action_for_each_trigger("early-init", action_add_queue_tail);
queue_builtin_action(wait_for_coldboot_done_action, "wait_for_coldboot_done");
queue_builtin_action(keychord_init_action, "keychord_init");
queue_builtin_action(console_init_action, "console_init");
action_for_each_trigger("init", action_add_queue_tail);
if (!is_charger) {
action_for_each_trigger("early-fs", action_add_queue_tail);
action_for_each_trigger("fs", action_add_queue_tail);
action_for_each_trigger("post-fs", action_add_queue_tail);
action_for_each_trigger("post-fs-data", action_add_queue_tail);
}
queue_builtin_action(property_service_init_action, "property_service_init");
queue_builtin_action(signal_init_action, "signal_init");
queue_builtin_action(check_startup_action, "check_startup");
queue_builtin_action(set_init_properties_action, "set_init_properties");
if (is_charger) {
action_for_each_trigger("charger", action_add_queue_tail);
} else {
action_for_each_trigger("early-boot", action_add_queue_tail);
action_for_each_trigger("boot", action_add_queue_tail);
}
queue_builtin_action(queue_property_triggers_action, "queue_property_triggers");
queue_builtin_action(configenv_init_action,"configenv_init");
#if BOOTCHART
queue_builtin_action(bootchart_init_action, "bootchart_init");
#endif
queue_builtin_action(setWifimacRandom,"wifimac_random");
for(;;) {
int nr, i, timeout = -1;
execute_one_command();
restart_processes();
if (!property_set_fd_init && get_property_set_fd() > 0) {
ufds[fd_count].fd = get_property_set_fd();
ufds[fd_count].events = POLLIN;
ufds[fd_count].revents = 0;
fd_count++;
property_set_fd_init = 1;
}
if (!signal_fd_init && get_signal_fd() > 0) {
ufds[fd_count].fd = get_signal_fd();
ufds[fd_count].events = POLLIN;
ufds[fd_count].revents = 0;
fd_count++;
signal_fd_init = 1;
}
if (!keychord_fd_init && get_keychord_fd() > 0) {
ufds[fd_count].fd = get_keychord_fd();
ufds[fd_count].events = POLLIN;
ufds[fd_count].revents = 0;
fd_count++;
keychord_fd_init = 1;
}
if (process_needs_restart) {
timeout = (process_needs_restart - gettime()) * 1000;
if (timeout < 0)
timeout = 0;
}
if (!action_queue_empty() || cur_action)
timeout = 0;
#if BOOTCHART
if (bootchart_count > 0) {
if (timeout < 0 || timeout > BOOTCHART_POLLING_MS)
timeout = BOOTCHART_POLLING_MS;
if (bootchart_step() < 0 || --bootchart_count == 0) {
bootchart_finish();
bootchart_count = 0;
}
}
#endif
nr = poll(ufds, fd_count, timeout);
if (nr <= 0)
continue;
for (i = 0; i < fd_count; i++) {
if (ufds[i].revents == POLLIN) {
if (ufds[i].fd == get_property_set_fd())
handle_property_set_fd();
else if (ufds[i].fd == get_keychord_fd())
handle_keychord();
else if (ufds[i].fd == get_signal_fd())
handle_signal();
}
}
}
return 0;
}
上面代码不多,主要是做了下面几点操作:
- 判断是否需要启动的ueventd和watchdogd,及增加环境变量
- 创建文件系统目录并挂载相应设备
- 屏蔽标准的输入输出并设置kernel logger
- 安全相关的初始化工作
- 创建epoll句柄
- 加载default.prop
- 解析init.rc
3. init.rc
3.1 脚本语法
init.rc文件是以“块”为单位服务的,,一个“块”可以包含多行。“块”分成两大类:一类称为"action",另一类称为“service”。
action以关键字"on" 开头。
模板如下:
on <trigger>
<command>
<command1>
变量说明:
trigger:触发条件
command:执行命令,可以有多个
例子:
on property:ro.debuggable=1
start console
意思是当属性满足条件ro.debuggable=1的时候就启动console服务
service:以关键字“service”开头。
模板如下:
service <name> <pathname> <argument> *
<option>
<option>
变量说明:
name:表示此service的名称
pathname:此service所在路径。因为是可执行文件,所以一定有存储路径
argument:启动service所带的参数
option:对此service的约束选项
例子:
service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server
class main
socket zygote stream 660 root system
onrestart write /sys/android_power/request_state wake
onrestart write /sys/power/state on
onrestart restart media
onrestart restart netd
ps:
service的option这边提出来说明一下:
3.2 解析脚本
由上面可知init进程通过调用init_parse_config_file函数来解析init.rc,下面将详细说明该函数解析init.rc的过程。
============================ system/core/init/init.c ==================================
int init_parse_config_file(const char *fn)
{
char *data;
data = read_file(fn, 0); //1
if (!data) return -1;
parse_config(fn, data); //2
DUMP();
return 0;
}
注释1处,通过调用read_file函数将init.rc读到内存中,然后在注释2处进行解析init.rc的配置。接下来我们分别看read_file和parse_config函数实现:
3.2.1 read_file
============================== system\core\init\util.c ============================
/* reads a file, making sure it is terminated with \n \0 */
void *read_file(const char *fn, unsigned *_sz)
{
char *data;
int sz;
int fd;
struct stat sb;
data = 0;
fd = open(fn, O_RDONLY);
if(fd < 0) return 0;
// for security reasons, disallow world-writable
// or group-writable files
if (fstat(fd, &sb) < 0) {
ERROR("fstat failed for '%s'\n", fn);
goto oops;
}
if ((sb.st_mode & (S_IWGRP | S_IWOTH)) != 0) { //3
ERROR("skipping insecure file '%s'\n", fn);
goto oops;
}
sz = lseek(fd, 0, SEEK_END);
if(sz < 0) goto oops;
if(lseek(fd, 0, SEEK_SET) != 0) goto oops;
data = (char*) malloc(sz + 2);
if(data == 0) goto oops;
if(read(fd, data, sz) != sz) goto oops;
close(fd);
data[sz] = '\n';
data[sz+1] = 0;
if(_sz) *_sz = sz;
return data;
oops:
close(fd);
if(data != 0) free(data);
return 0;
}
上面的代码需要注意的是要注释3处代码,注意init.rc的权限是否正确,它是没有group和other权限的!为啥我要特别提这个问题,因为我遇到过,哈哈哈。
3.2.2 parse_config
============================ system\core\init\init_parser.c ===========================
static void parse_config(const char *fn, char *s)
{
struct parse_state state;
struct listnode import_list;
struct listnode *node;
char *args[INIT_PARSER_MAXARGS];
int nargs;
nargs = 0;
state.filename = fn;
state.line = 0;
state.ptr = s;
state.nexttoken = 0;
state.parse_line = parse_line_no_op;
list_init(&import_list);
state.priv = &import_list;
for (;;) { //4
switch (next_token(&state)) {
case T_EOF:
state.parse_line(&state, 0, 0);
goto parser_done;
case T_NEWLINE:
state.line++;
if (nargs) {
int kw = lookup_keyword(args[0]);
if (kw_is(kw, SECTION)) {
state.parse_line(&state, 0, 0);
parse_new_section(&state, kw, nargs, args); //5
} else {
state.parse_line(&state, nargs, args);
}
nargs = 0;
}
break;
case T_TEXT:
if (nargs < INIT_PARSER_MAXARGS) {
args[nargs++] = state.text;
}
break;
}
}
parser_done:
list_for_each(node, &import_list) {
struct import *import = node_to_item(node, struct import, list);
int ret;
INFO("importing '%s'", import->filename);
ret = init_parse_config_file(import->filename);
if (ret)
ERROR("could not import file '%s' from '%s'\n",
import->filename, fn);
}
上面的代码在注释4处开始解析init.rc文件。next _token的函数原理是,针对state->ptr的指针进行解析,依次向后读取注释1中传下来的data数组中的内容,如果读取到"0","\n"的话,分别返回T_EOF和T_NEWLINE,如果读取出来的是一个词的话,则将内容保存在args的数组中,内容依次向后。
如果找到的是行结束符,则根据行中的第一个单词来判断是否是一个"section",是"section"则调用函数parse_new_section来开启一个新"section"的处理,否则把这一行继续作为当前"section"所属的行来处理。
接下来就来看parse_new_section的实现:
void parse_new_section(struct parse_state *state, int kw,
int nargs, char **args)
{
printf("[ %s %s ]\n", args[0],
nargs > 1 ? args[1] : "");
switch(kw) {
case K_service:
state->context = parse_service(state, nargs, args);
if (state->context) {
state->parse_line = parse_line_service;
return;
}
break;
case K_on:
state->context = parse_action(state, nargs, args);
if (state->context) {
state->parse_line = parse_line_action;
return;
}
break;
case K_import:
parse_import(state, nargs, args);
break;
}
state->parse_line = parse_line_no_op;
}
parse_new_section()函数区分关键字然后分别调用parse_line_service,parse_line_action,parse_import解析"service",“on”,“import”。
ps:
- 这个只是一个语法文件,就像一个xml文件一样,没有执行顺序的,解析器通过读这个文件获取想要的数据,包括service,action等;
- Actions和Services声明一个新的分组Section。所有的命令或选项都属于最近声明的分组。位于第一个分组之前的命令或选项将会被忽略;
- Actions和Services有唯一的名字。如果有重名的情况,第二个申明的将会被作为错误忽略;
- 所有这些都是以行为单位的,各种记号由空格来隔开;
- 行末的反斜杠用于折行,注释行以井号(#)开头(允许以空格开头)。