init进程【1】——init启动过程
欢迎转载,转载请注明:http://blog.csdn.net/zhgxhuaa
init启动过程
众所周知,Linux中的所有进程都是有init进程创建并运行的。首先Linux内核启动,然后在用户空间中启动init进程,再启动其他系统进程。在系统启动完成完成后,init将变为守护进程监视系统其他进程。Android是基于Linux的操作系统,所以init也是Android系统中用户空间的第一个进程,它的进程号是1。下面先简单的看一下init进程的启动过程。
@/kernel/goodfish/init/main.c
static int __init kernel_init(void * unused){/* * Wait until kthreadd is all set-up. */wait_for_completion(&kthreadd_done);/* * init can allocate pages on any node */set_mems_allowed(node_states[N_HIGH_MEMORY]);/* * init can run on any cpu. */set_cpus_allowed_ptr(current, cpu_all_mask);cad_pid = task_pid(current);smp_prepare_cpus(setup_max_cpus);do_pre_smp_initcalls();lockup_detector_init();smp_init();sched_init_smp();do_basic_setup();/* Open the /dev/console on the rootfs, this should never fail */if (sys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0)printk(KERN_WARNING "Warning: unable to open an initial console.\n");(void) sys_dup(0);(void) sys_dup(0);/* * check if there is an early userspace init. If yes, let it do all * the work */if (!ramdisk_execute_command)ramdisk_execute_command = "/init";if (sys_access((const char __user *) ramdisk_execute_command, 0) != 0) {ramdisk_execute_command = NULL;prepare_namespace();}/* * Ok, we have completed the initial bootup, and * we're essentially up and running. Get rid of the * initmem segments and start the user-mode stuff.. */init_post();return 0;}
/* This is a non __init function. Force it to be noinline otherwise gcc * makes it inline to init() and it becomes part of init.text section */static noinline int init_post(void){/* 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();current->signal->flags |= SIGNAL_UNKILLABLE;if (ramdisk_execute_command) {run_init_process(ramdisk_execute_command);printk(KERN_WARNING "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) {run_init_process(execute_command);printk(KERN_WARNING "Failed to execute %s. Attempting ""defaults...\n", execute_command);}run_init_process("/sbin/init");run_init_process("/etc/init");run_init_process("/bin/init");run_init_process("/bin/sh");panic("No init found. Try passing init= option to kernel. " "See Linux Documentation/init.txt for guidance.");}
static void run_init_process(const char *init_filename){argv_init[0] = init_filename;kernel_execve(init_filename, argv_init, envp_init);}
在init_post()中会判断execute_command是否为空,如果不为空则执行run_init_process调用。execute_command的赋值在init_setup()中,所以这里应该注意在设置内核启动选项时,应设置为“ init=/init”,以便正常启动init进程,因为编译完Android后生成的文件系统中,init位于最顶层目录。
<span style="font-size:14px;">static const char * argv_init[MAX_INIT_ARGS+2] = { "init", NULL, };</span>
static int __init init_setup(char *str){unsigned int i;execute_command = str;/* * In case LILO is going to boot us with default command line, * it prepends "auto" before the whole cmdline which makes * the shell think it should execute a script with such name. * So we ignore all arguments entered _before_ init=... [MJ] */for (i = 1; i < MAX_INIT_ARGS; i++)argv_init[i] = NULL;return 1;}__setup("init=", init_setup);当根目录中不存在init时,或者未指定启动项“init=”时,内核会到/sbin、/etc、/bin目录下查找init。
了解了init进程的启动过程后,接下来看一下init进程都干了些什么?Android中的init进程与Linux不同,其职责可以归结如下:
- 作为守护进程
- 解析和执行init.rc文件
- 生成设备驱动节点
- 属性服务
init源码分析
init进程的入口函数是main,它的代码如下:
@/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; //启动ueventd if (!strcmp(basename(argv[0]), "ueventd")) return ueventd_main(argc, argv); //启动watchdogd if (!strcmp(basename(argv[0]), "watchdogd")) return watchdogd_main(argc, argv); /* clear the umask */ umask(0); /* Get the basic filesystem setup we need put * together in the initramdisk on / and then we'll * let the rc file figure out the rest. */ //创建并挂在启动所需的文件目录 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); /* indicate that booting is in progress to background fw loaders, etc */ close(open("/dev/.booting", O_WRONLY | O_CREAT, 0000));//检测/dev/.booting文件是否可读写和创建 /* We must have some place other than / to create the * device nodes for kmsg and null, otherwise we won't * be able to remount / read-only later on. * Now that tmpfs is mounted on /dev, we can actually * talk to the outside world. */ open_devnull_stdio();//重定向标准输入/输出/错误输出到/dev/_null_ klog_init();//log初始化 property_init();//属性服务初始化 //从/proc/cpuinfo中读取Hardware名,在后面的mix_hwrng_into_linux_rng_action函数中会将hardware的值设置给属性ro.hardware get_hardware_name(hardware, &revision); //导入并设置内核变量 process_kernel_cmdline(); //selinux相关,暂不分析 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(); /* These directories were necessarily created before initial policy load * and therefore need their security context restored to the proper value. * This must happen before /dev is populated by ueventd. */ 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");//解析init.rc配置文件 /* * 解析完init.rc后会得到一系列的action等,下面的代码将执行处于early-init阶段的action。 * init将action按照执行时间段的不同分为early-init、init、early-boot、boot。 * 进行这样的划分是由于有些动作之间具有依赖关系,某些动作只有在其他动作完成后才能执行,所以就有了先后的区别。 * 具体哪些动作属于哪个阶段是在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(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng"); queue_builtin_action(keychord_init_action, "keychord_init"); queue_builtin_action(console_init_action, "console_init"); /* execute all the boot actions to get us started */ action_for_each_trigger("init", action_add_queue_tail); /* skip mounting filesystems in charger mode */ 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); } /* Repeat mix_hwrng_into_linux_rng in case /dev/hw_random or /dev/random * wasn't ready immediately after wait_for_coldboot_done */ queue_builtin_action(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng"); 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"); 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); } /* run all property triggers based on current state of the properties */ queue_builtin_action(queue_property_triggers_action, "queue_property_triggers");#if BOOTCHART queue_builtin_action(bootchart_init_action, "bootchart_init");#endif for(;;) {//init进入无限循环 int nr, i, timeout = -1; //检查action_queue列表是否为空。如果不为空则移除并执行列表头中的action 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())//处理keychord事件 handle_keychord(); else if (ufds[i].fd == get_signal_fd())//处理 handle_signal();//处理SIGCHLD信号 } } } return 0;}
main函数分析:
if (!strcmp(basename(argv[0]), "ueventd")) return ueventd_main(argc, argv);
main函数一开始就会判断参数argv[0]的值是否等于“ueventd”,如果是就调用ueventd进程的入口函数ueventd_main()启动ueventd进程。这是怎么回事呢?当前正在启动的进程不是init吗?它的名称怎么可能会等于“ueventd”?所以这里有必要看一下ueventd的启动过程,ueventd是在init.rc中被启动的。
on bootservice ueventd /sbin/ueventd class core critical seclabel u:r:ueventd:s0可以看出ueventd可执行文件位于/sbin/ueventd,在观察了/sbin/ueventd后我们发现,它只不过是 是可执行文件/init的一个符号链接文件,即应用程序ueventd和init运行的是同一个可执行文件。 所以,整个过程是这样的:内核启动完成之后,可执行文件/init首先会被执行,即init进程会首先被启动。init进程在启动的过程中,会对启动脚本/init.rc进行解析。在启动脚本/init.rc中,配置了一个ueventd进程,它对应的可执行文件为/sbin/ueventd,即ueventd进程加载的可执行文件也为/init(此时init中main函数的参数argv[0] = “/sbin/ueventd”)。因此,通过判断参数argv[0]的值,就可以知道当前正在启动的是init进程还是ueventd进程。
PS:ueventd是一个守护进程,主要作用是接收uevent来创建或删除/dev/xxx(设备节点),其实现位于@system/core/init/ueventd.c中。ueventd进程会通过一个socket接口来和内核通信,以便可以监控系统设备事件。
在开始所有的工作之前,main进程首先做的是创建并挂载启动所需的(其他的会在解析init.rc时创建)文件目录,如下所示:
/* Get the basic filesystem setup we need put * together in the initramdisk on / and then we'll * let the rc file figure out the rest. */ //创建并挂在启动所需的文件目录 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);说明:
tmpfs是一种虚拟内存的文件系统,典型的tmpfs文件系统完全驻留在RAM中,读写速度远快于内存或硬盘文件系统。
/dev目录保存着硬件设备访问所需要的设备驱动程序。在Android中,将相关目录作用于tmpfs,可以大幅度提高设备访问的速度。
devpts是一种虚拟终端文件系统。
proc是一种虚拟文件系统,只存在于内存中,不占用外存空间。借助此文件系统,应用程序可以与内核内部数据结构进行交互。
sysfs是一种特殊的文件系统,在Linux 2.6中引入,用于将系统中的设备组织成层次结构,并向用户模式程序提供详细的内核数据结构信息,将proc、devpts、devfs三种文件系统统一起来。
编译Android系统源码时,在生成的根文件系统中,并不存在/dev、/proc、/sys这类目录,它们是系统运行时的目录,有init进程在运行中生成,当系统终止时,它们就会消失。上面的代码所形成的的文件层次结构为:
<span style="font-size:14px;"> /* We must have some place other than / to create the * device nodes for kmsg and null, otherwise we won't be able to remount / read-only later on. * Now that tmpfs is mounted on /dev, we can actually talk to the outside world. */ open_devnull_stdio();//重定向标准输入/输出/错误输出到/dev/_null_</span>open_devnull_stdio()函数的作用是重定向标准输入/输出/错误输出到/dev/_null_,至于为什么要重定向的原因在注释中已经写明。open_devnull_stdio()的实现如下:
@system/core/init/util.c
void open_devnull_stdio(void){ int fd; static const char *name = "/dev/__null__"; if (mknod(name, S_IFCHR | 0600, (1 << 8) | 3) == 0) { fd = open(name, O_RDWR); unlink(name); if (fd >= 0) { dup2(fd, 0); dup2(fd, 1); dup2(fd, 2); if (fd > 2) { close(fd); } return; } } exit(1);}
<span style="font-size:14px;">klog_init();//log初始化</span>klog_init()用于初始化log,通过其实现可以看出log被打印到/dev/__kmsg__文件中。主要在代码中最后通过fcntl和unlink使得/dev/__kmsg__不可被访问,这就保证了只有log程序才可以访问。
void klog_init(void){ static const char *name = "/dev/__kmsg__"; if (klog_fd >= 0) return; /* Already initialized */ if (mknod(name, S_IFCHR | 0600, (1 << 8) | 11) == 0) { klog_fd = open(name, O_WRONLY); if (klog_fd < 0) return; fcntl(klog_fd, F_SETFD, FD_CLOEXEC); unlink(name); }}
property_init属性服务初始化,这里先不深究,接下来会单独分析。
<span style="font-size:14px;"> </span>//从/proc/cpuinfo中读取Hardware名,在后面的mix_hwrng_into_linux_rng_action函数中会将hardware的值设置给属性ro.hardware get_hardware_name(hardware, &revision);get_hardware_name()函数的作用是从/proc/cpuinfo中获取Hardware和Revision的值,并保持到全局变量hardware和revision中。
下面的截图是在我的手机上的CPU info信息:
这里获取hardware信息有什么用呢?在main()函数后面的代码中,我们可以看见这样一句:
//导入并设置内核变量 process_kernel_cmdline();下面看一下process_kernel_cmdline的实现:
@system/core/init/init.c
static void process_kernel_cmdline(void){ /* don't expose the raw commandline to nonpriv processes */ chmod("/proc/cmdline", 0440); /* first pass does the common stuff, and finds if we are in qemu. * second pass is only necessary for qemu to export all kernel params * as props. */ import_kernel_cmdline(0, import_kernel_nv); if (qemu[0]) import_kernel_cmdline(1, import_kernel_nv); /* now propogate the info given on command line to internal variables * used by init as well as the current required properties */ export_kernel_boot_props();}
static void export_kernel_boot_props(void){ char tmp[PROP_VALUE_MAX]; ...... /* if this was given on kernel command line, override what we read * before (e.g. from /proc/cpuinfo), if anything */ ret = property_get("ro.boot.hardware", tmp); if (ret) strlcpy(hardware, tmp, sizeof(hardware)); property_set("ro.hardware", hardware); snprintf(tmp, PROP_VALUE_MAX, "%d", revision); property_set("ro.revision", tmp); ......}process_kernel_cmdline()函数用于导入和设置一些内核变量,在export_kernel_boot_props()中我们看见将hardware的值赋值给了属性"ro.hardware"。那这个赋值又是干什么的呢?我们再看一下main()函数,在解析init.rc配置文件的时候,有没有发现少了点什么?
INFO("reading config file\n"); init_parse_config_file("/init.rc");//解析init.rc配置文件是的,在以前比较老的代码中(例如2.3和4.0)这里除了init.rc以外还会有一个与硬件相关的rc脚本,如下:
snprintf(tmp, sizeof(tmp), "/init.%s.rc", hardware); init_parse_config_file(tmp);那现在这段代码跑去哪里了呢?我们在init.rc中找到了它: 所以,之前设置的ro.hardware的值是在这里用的,在init.rc中用来导入init.${ro.hardware}.rc脚本,然后一起进行解析。与之前相比,这里只是方式变了,本质上还是一样的。
INFO("reading config file\n"); init_parse_config_file("/init.rc");//解析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(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng"); queue_builtin_action(keychord_init_action, "keychord_init"); queue_builtin_action(console_init_action, "console_init"); /* execute all the boot actions to get us started */ action_for_each_trigger("init", action_add_queue_tail); /* skip mounting filesystems in charger mode */ 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); } /* Repeat mix_hwrng_into_linux_rng in case /dev/hw_random or /dev/random * wasn't ready immediately after wait_for_coldboot_done */ queue_builtin_action(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng"); 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"); 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); } /* run all property triggers based on current state of the properties */ queue_builtin_action(queue_property_triggers_action, "queue_property_triggers");#if BOOTCHART queue_builtin_action(bootchart_init_action, "bootchart_init");#endif这部分代码用于解析init.rc脚本,并触发执行解析生成的action。这部分后面单独进行分析。
在main()函数的最后,init进入了一个无限循环,并等待一些事情的发生。即:在执行完前面的初始化工作以后,init变为一个守护进程。init所关心的事件有三类:属性服务事件、keychord事件和SIGNAL,当有这三类事件发生时,init进程会调用相应的handle函数进行处理。
更多相关文章
- 箭头函数的基础使用
- Python技巧匿名函数、回调函数和高阶函数
- 浅析android通过jni控制service服务程序的简易流程
- Android(安卓)Wifi模块分析(三)
- Android(安卓)bluetooth介绍(四): a2dp connect流程分析
- Android(安卓)Activity的启动
- Android架构分析之使用自定义硬件抽象层(HAL)模块
- Android(安卓)任务和回退堆栈---启动任务
- 2011年Android(安卓)Camera学习笔记之一