Android 8.0 开机流程 (三) Linux 内核 init 进程的启动
前面分析了kthredd 内核守护进程的分析,接下里会分析init 进程。init是用户空间的第一个进程,也是所有用户空间的所有父进程。这也是从内核态切换到用户态。
1.kernel_init
kernel_init 定义在 Kernel\init\main.c
[code]static int __ref kernel_init(void *unused) { #if (MP_CHECKPT_BOOT == 1) unsigned int PiuTick; unsigned int PiuTime; #endif int ret; kernel_init_freeable();//前期初始化动作 /* need to finish all async __init code before freeing the memory */ //等待所有异步调用完成,释放内存前,必须完成所有的异步_init代码 async_synchronize_full();// #if !defined(CONFIG_MP_MSTAR_STR_BASE) free_initmem(); //释放所有init.*段中的代码 #endif mark_readonly(); system_state = SYSTEM_RUNNING; //设置系统状态为运行态 numa_default_policy(); //设定NUMA系统的默认内存访问策略 flush_delayed_fput();//释放有延时的文件结构体struct file rcu_end_inkernel_boot(); #ifdef CONFIG_MP_DEBUG_TOOL_MEMORY_USAGE_MONITOR show_mm_time(0); #endif //ramdisk_execute_command 值为/init if (ramdisk_execute_command) { ret = run_init_process(ramdisk_execute_command);//执行根目录下的init程序 if (!ret) return 0; pr_err("Failed to execute %s (error %d)\n", ramdisk_execute_command, ret); } /* * 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. */ //execute_command 如果有定义,就去相应的根目录下执行的相应的程序 if (execute_command) { ret = run_init_process(execute_command); if (!ret) return 0; panic("Requested init %s failed (error %d).", execute_command, ret); } // ramdisk_execute_command execute_command定义的应用程序没有找到 //在根目录下 /sbin/init /etc/init /bin/init /bin/sh 找相应的应用程序 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; panic("No working init found. Try passing init= option to kernel. " "See Linux Documentation/init.txt for guidance."); }
在kthread_init 中 有一些相关的初始化,然后根据 ramdisk_execute_command execute_command和去执行相应程序,如果没有找到相应的程序,再去/sbin/init /etc/init /bin/init /bin/sh 找相应的应用程序。只需要找一个程序,执行即可。
2.kernel_init_freeable
kernel_init_freeable 定义在 Kernel\init\main.c
[code]static noinline void __init kernel_init_freeable(void) { /* * Wait until kthreadd is all set-up. */ /*等待&kthreadd_done这个值complete,这个在rest_init方法中有写,在ktreadd进程启动完成后设置 为complete*/ wait_for_completion(&kthreadd_done); /* Now the scheduler is fully set up and can do blocking allocations */ //设置bitmask, 使得init进程可以使用PM并且允许I/O阻塞操作 gfp_allowed_mask = __GFP_BITS_MASK; /* * init can allocate pages on any node */ //init进程可以分配物理页面 set_mems_allowed(node_states[N_MEMORY]); /* * init can run on any cpu. */ //init进程可以在任意cpu上执行 set_cpus_allowed_ptr(current, cpu_all_mask); /*设置到init进程的pid号给cad_pid,cad就是ctrl-alt-del,设置init进程来处理ctrl-alt-del信 号*/ cad_pid = task_pid(current); //设置smp初始化时的最大CPU数量,然后将对应数量的CPU状态设置为present smp_prepare_cpus(setup_max_cpus); //调用__initcall_start到__initcall0_start之间的initcall_t函数指针 do_pre_smp_initcalls(); //开启watchdog_threads,watchdog主要用来监控、管理CPU的运行状态 lockup_detector_init(); //启动cpu0外的其他cpu核 smp_init(); //进程调度域初始化 sched_init_smp(); page_alloc_init_late(); //初始化设备,驱动等,这个方法比较重要,将在下面单独讲 do_basic_setup(); /* Open the /dev/console on the rootfs, this should never fail */ 首先打开了/dev/console文件,在Linux中一切皆是文件,所以/dev/console文件代表的是控制台设备 (其实就是串口) 在Linux中,进程打开文件后会获得该文件的文件描述符。这里kernel_init进程就得到了控制台的文件描 述符,然后又使用sys_dup复制了2次….一共得到了3个文件描述符。这三个文件描述符分别是0、1、2.这 三个文件描述符就是所谓的:标准输入、标准输出、标准错误 之后kernel_init所有的子进程都将继承这3个文件描述符,也就是说后面所有的进程一生出来,就默认拥 有标准输入、标准输出、标准错误的文件描述符 其实这一步的根本目的就是传承这3个文件描述符、 if (sys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0) pr_err("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 */ //如果 ramdisk_execute_command 没有赋值,则赋值为"/init",之前有讲到 if (!ramdisk_execute_command) ramdisk_execute_command = "/init"; // 尝试进入ramdisk_execute_command指向的文件,如果失败则重新挂载根文件系统 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.. * * rootfs is available now, try loading the public keys * and default modules */ integrity_load_keys(); // 加载I/O调度的电梯算法 load_default_modules(); }
在 kernel_init_freeable 中 启用了smp SMP(Symmetric Multi-Processor) 所谓对称多处理器结构,是指服务器中多个CPU对称工作,各CPU共享相同的物理内存及总线结构。初始化了设备驱动,加载了文件系统和获取了控制台。打开标准输入,标准输出。
2.1.do_basic_setup
do_basic_setup 定义在 Kernel\init\main.c
[code]static void __init do_basic_setup(void) { ////针对SMP系统,初始化内核control group的cpuset子系统。 cpuset_init_smp(); // 初始化共享内存 shmem_init(); // 初始化驱动程序模型 driver_init(); //创建/proc/irq目录, 并初始化系统中所有中断对应的子目录 init_irq_proc(); // 执行内核的构造函数 do_ctors(); // 启用usermodehelper usermodehelper_enable(); //遍历initcall_levels数组,调用里面的initcall函数,这里主要是对设备、驱动、文件系统进行初始 化,之所有将函数封装到数组进行遍历,主要是为了好扩展 do_initcalls(); }
do_basic_setup 是在系统起来以后做一些基础初始化,相应子系统的初始化。cpuset子系统 设备驱动子系统,irq子系统的初始化等。
2.2.driver_init
driver_init 定义在 Kernel\drivers\base\init.c
[code]void __init driver_init(void) { /* These are the core pieces */ // 注册devtmpfs文件系统,启动kdevtmpfs进程 devtmpfs_init(); // 初始化驱动模型中的部分子系统,kset:devices 和 kobject:dev、 dev/block、 dev/char devices_init(); // 初始化驱动模型中的bus子系统,kset:bus、devices/system buses_init(); // 初始化驱动模型中的class子系统,kset:class classes_init(); // 初始化驱动模型中的firmware子系统 ,kobject:firmware firmware_init(); // 初始化驱动模型中的hypervisor子系统,kobject:hypervisor hypervisor_init(); /* These are also core pieces, but must come after the * core core pieces. */ // 初始化驱动模型中的bus/platform子系统,这个节点是所有platform设备和驱动的总线类型,即所有 platform设备和驱动都会挂载到这个总线上 platform_bus_init(); // 初始化驱动模型中的devices/system/cpu子系统,该节点包含CPU相关的属性 cpu_dev_init(); //初始化驱动模型中的/devices/system/memory子系统,该节点包含了内存相关的属性,如块大小等 memory_dev_init(); //初始化系统总线类型为容器 container_dev_init(); /初始化创建,访问和解释设备树的过程。 of_core_init(); }
driver_init 中linux 内核驱动模型的子系统基本上已经初始化完成,建立了linux 驱动框架。但是它只是创建了目录,具体的驱动加载在do_install 函数中。
3.free_initmem
free_initmem 定义在 Kernel\arch\arm64\mm\init.c
[code]void free_initmem(void) { free_reserved_area(__va(__pa(__init_begin)), __va(__pa(__init_end)), 0, "unused kernel"); /* * Unmap the __init region but leave the VM area in place. This * prevents the region from being reused for kernel modules, which * is not supported by kallsyms. */ unmap_kernel_range((u64)__init_begin, (u64)(__init_end - __init_begin)); }
所有使用__init标记过的函数和使用__initdata标记过的数据,在free_initmem函数执行后,都不能使用,它们曾经获得的内存现在可以重新用于其他目的。
4.flush_delayed_fput
flush_delayed_fput 定义在 Kernel\fs\file_table.c中
[code]void flush_delayed_fput(void) { delayed_fput(NULL); } static void delayed_fput(struct work_struct *unused) { struct llist_node *node = llist_del_all(&delayed_fput_list); struct llist_node *next; for (; node; node = next) { next = llist_next(node); ////释放struct file __fput(llist_entry(node, struct file, f_u.fu_llist)); } }
释放 delayed_fput_list 链表中struct 文件结构体,用户空间每打开一个文件,内核就会关联一个struct 文件结构体。
5.run_init_process
run_init_process 定义在 Kernel\init\main.c
[code]static int run_init_process(const char *init_filename) { argv_init[0] = init_filename; return do_execve(getname_kernel(init_filename),//执行程序 (const char __user *const __user *)argv_init, (const char __user *const __user *)envp_init); }
run_init_process 执行一个可执行程序。kthread_init 中执行/.init文件
6.小结
在init进程中,相关初始化后。就会去执行用户空间的init程序。从内核态转化到用户态。Linux内核的启动流程已完毕。主要就是kthread_init 进程和kthreadd守护进程的创建。后面就会进入到用户空间 Android 的启动流程分析。
- Android系统启动流程——init进程
- Android开机流程分析 -- init进程之配置文件解析
- ARM-Linux移植之(三)——init进程启动流程分析
- android从init到开机动画启动关闭流程一简易图(surfaceflinger启动的位置)
- 内核启动流程3--Busybox的init进程
- Android O: init进程启动流程分析(阶段一)
- Android系统启动流程(一)解析init进程启动过程
- android init进程启动的大致流程
- ARM-Linux移植之(三)——init进程启动流程分析
- android从init到开机动画启动关闭流程一简易图(surfaceflinger启动的位置)
- MTD系列 - android平台上linux启动时init进程解析init.rc文件分析
- Android如何配置init.rc中的开机启动进程(service)【转】
- 跟踪分析Linux内核的启动过程(start_kernel到init进程启动)
- Android——从init进程启动流程
- Android O: init进程启动流程分析(阶段二)
- 基于android2.3.5系统:Linux如何启动Andriod的守护进程init
- Android启动流程分析(三) init进程初窥
- MTD系列 - android平台上linux启动时init进程解析init.rc文件分析
- Linux-Android启动之Init进程前传
- Linux-Android启动之Init进程前传