注:此文章主要基于展锐Android R代码加上学习总结自IngresGe大佬的分析
文章目录
一、kthreadd
/bsp/kernel/kernel4.14/kernel/kthread.c
int kthreadd(void *unused)
{
struct task_struct *tsk = current;
/* Setup a clean context for our children to inherit. */
set_task_comm(tsk, "kthreadd");
ignore_signals(tsk);
//允许kthreadd在任意CPU上运行
set_cpus_allowed_ptr(tsk, cpu_all_mask);
set_mems_allowed(node_states[N_MEMORY]);
current->flags |= PF_NOFREEZE;
cgroup_init_kthreadd();
for (;;) {
set_current_state(TASK_INTERRUPTIBLE);
if (list_empty(&kthread_create_list))
schedule();
__set_current_state(TASK_RUNNING);
spin_lock(&kthread_create_lock);
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
kernel_init启动后,完成一些init的初始化操作,然后去系统根目录下依次找ramdisk_execute_command和execute_command设置的应用程序,如果这两个目录都找不到,就依次去根目录下找 /sbin/init,/etc/init,/bin/init,/bin/sh 这四个应用程序进行启动,只要这些应用程序有一个启动了,其他就不启动了。
Android系统一般会在根目录下放一个init的可执行文件,也就是说Linux系统的init进程在内核初始化完成后,就直接执行init这个文件。
static int __ref kernel_init(void *unused)
{
int ret;
//进行init进程的一些初始化操作
kernel_init_freeable();
/* need to finish all async __init code before freeing the memory */
// 等待所有异步调用执行完成,,在释放内存前,必须完成所有的异步 __init 代码
async_synchronize_full();
ftrace_free_init_mem();
// 释放所有init.* 段中的内存
free_initmem();
mark_readonly();
// 设置系统状态为运行状态
system_state = SYSTEM_RUNNING;
// 设定NUMA系统的默认内存访问策略
numa_default_policy();
// 释放所有延时的struct file结构体
rcu_end_inkernel_boot();
pr_emerg("run init\n");
//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/admin-guide/init.rst for guidance.");
}
进行init进程的一些初始化操作
static noinline void __init kernel_init_freeable(void)
{
/*
* Wait until kthreadd is all set-up.
*/
wait_for_completion(&kthreadd_done);
/* Now the scheduler is fully set up and can do blocking allocations */
gfp_allowed_mask = __GFP_BITS_MASK;
/*
* init can allocate pages on any node
*/
set_mems_allowed(node_states[N_MEMORY]);
cad_pid = task_pid(current);
smp_prepare_cpus(setup_max_cpus);
workqueue_init();
init_mm_internals();
do_pre_smp_initcalls();
lockup_detector_init();
smp_init();
sched_init_smp();
page_alloc_init_late();
/* Initialize page ext after all struct pages are initialized. */
page_ext_init();
do_basic_setup();
test_executor_init();
/* Open the /dev/console on the rootfs, this should never fail */
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
*/
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..
*
* rootfs is available now, try loading the public keys
* and default modules
*/
integrity_load_keys();
load_default_modules();
}
/*
* Ok, the machine is now initialized. None of the devices
* have been touched yet, but the CPU subsystem is up and
* running, and memory and process management works.
*
* Now we can finally start doing some real work..
*/
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();
}
以上就是init启动的相关操作,接下来看看它启动之后会做哪些操作,从它的主函数入手
三、Init 进程入口
system/core/init/main.cpp
/*
1. 1.第一个参数argc表示参数个数,第二个参数是参数列表,也就是具体的参数
2. 2.main函数有四个参数入口,
*一是参数中有ueventd,进入ueventd_main
*二是参数中有subcontext,进入InitLogging 和SubcontextMain
*三是参数中有selinux_setup,进入SetupSelinux
*四是参数中有second_stage,进入SecondStageMain
*3.main的执行顺序如下:
3. (1)ueventd_main init进程创建子进程ueventd,
4. 并将创建设备节点文件的工作托付给ueventd,ueventd通过两种方式创建设备节点文件
5. (2)FirstStageMain 启动第一阶段
6. (3)SetupSelinux 加载selinux规则,并设置selinux日志,完成SELinux相关工作
7. (4)SecondStageMain 启动第二阶段
*/
int main(int argc, char** argv) {
//当argv[0]的内容为ueventd时,strcmp的值为0,!strcmp为1
//1表示true,也就执行ueventd_main,ueventd主要是负责设备节点的创建、权限设定等一些列工作
if (!strcmp(basename(argv[0]), "ueventd")) {
return ueventd_main(argc, argv);
}
//当传入的参数个数大于1时,执行下面的几个操作
if (argc > 1) {
//参数为subcontext,初始化日志系统,
if (!strcmp(argv[1], "subcontext")) {
android::base::InitLogging(argv, &android::base::KernelLogger);
const BuiltinFunctionMap function_map;
return SubcontextMain(argc, argv, &function_map);
}
//参数为“selinux_setup”,启动Selinux安全策略
if (!strcmp(argv[1], "selinux_setup")) {
return SetupSelinux(argv);
}
//参数为“second_stage”,启动init进程第二阶段
if (!strcmp(argv[1], "second_stage")) {
return SecondStageMain(argc, argv);
}
}
// 默认启动init进程第一阶段
return FirstStageMain(argc, argv);
}
3.1 ueventd_main
system/core/init/ueventtd.cpp
int ueventd_main(int argc, char** argv) {
//设置新建文件的默认值,这个与chmod相反,这里相当于新建文件后的权限为666
umask(000);
//初始化内核日志,位于节点/dev/kmsg, 此时logd、logcat进程还没有起来,
//采用kernel的log系统,打开的设备节点/dev/kmsg, 那么可通过cat /dev/kmsg来获取内核log。
android::base::InitLogging(argv, &android::base::KernelLogger);
//注册selinux相关的用于打印log的回调函数
SelinuxSetupKernelLogging();
SelabelInitialize();
//解析xml,根据不同SOC厂商获取不同的hardware rc文件
auto ueventd_configuration = ParseConfig({"/ueventd.rc", "/vendor/ueventd.rc",
"/odm/ueventd.rc", "/ueventd." + hardware + ".rc"});
//冷启动
if (access(COLDBOOT_DONE, F_OK) != 0) {
ColdBoot cold_boot(uevent_listener, uevent_handlers);
cold_boot.Run();
}
for (auto& uevent_handler : uevent_handlers) {
uevent_handler->ColdbootDone();
}
//忽略子进程终止信号
signal(SIGCHLD, SIG_IGN);
// Reap and pending children that exited between the last call to waitpid() and setting SIG_IGN
// for SIGCHLD above.
//在最后一次调用waitpid()和为上面的sigchld设置SIG_IGN之间退出的获取和挂起的子级
while (waitpid(-1, nullptr, WNOHANG) > 0) {
}
//监听来自驱动的uevent,进行“热插拔”处理
uevent_listener.Poll([&uevent_handlers](const Uevent& uevent) {
for (auto& uevent_handler : uevent_handlers) {
uevent_handler->HandleUevent(uevent); //热启动,创建设备
}
return ListenerAction::kContinue;
});
return 0;
}
由init开启的一个子进程用来创建设备节点文件,有两种方式
- “冷插”(Cold Plug):即以预先定义的设备信息为基础,当ueventd启动后,统一创建设备节点文件。这一类设备节点文件也被称为静态节点文件。
- “热插拔”(Hot Plug):即在系统运行中,当有设备插入USB端口时,ueventd就会接收到这一事件,为插入的设备动态创建设备节点文件。这一类设备节点文件也被称为动态节点文件。
其中完成的主要操作如下:
- 设置文件权限
- 初始化内核日志(/dev/kmsg),可通过cat /dev/kmsg来获取内核log
- 注册selinux相关的用于打印log的回调函数
- 解析xml,根据不同SOC厂商获取不同的hardware rc文件
- 冷启动
- 监听热启动进行处理
3.2 FirstStageMain
第一阶段主要完成了:
- 挂载分区
- 创建设备节点和关键目录
- 初始化日志系统
- 启动selinux安全策略
system\core\init\first_stage_init.cpp
int FirstStageMain(int argc, char** argv) {
//init crash时重启引导加载程序
//这个函数主要作用将各种信号量,如SIGABRT,SIGBUS等的行为设置为SA_RESTART,一旦监听到这些信号即执行重启系统
if (REBOOT_BOOTLOADER_ON_PANIC) {
InstallRebootSignalHandlers();
}
//清空文件权限
umask(0);
CHECKCALL(clearenv());
CHECKCALL(setenv("PATH", _PATH_DEFPATH, 1));
//在RAM内存上获取基本的文件系统,剩余的被rc文件所用
CHECKCALL(mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755"));
CHECKCALL(mkdir("/dev/pts", 0755));
CHECKCALL(mkdir("/dev/socket", 0755));
CHECKCALL(mount("devpts", "/dev/pts", "devpts", 0, NULL));
#define MAKE_STR(x) __STRING(x)
CHECKCALL(mount("proc", "/proc", "proc", 0, "hidepid=2,gid=" MAKE_STR(AID_READPROC)));
#undef MAKE_STR
// 非特权应用不能使用Andrlid cmdline
CHECKCALL(chmod("/proc/cmdline", 0440));
gid_t groups[] = {AID_READPROC};
CHECKCALL(setgroups(arraysize(groups), groups));
CHECKCALL(mount("sysfs", "/sys", "sysfs", 0, NULL));
CHECKCALL(mount("selinuxfs", "/sys/fs/selinux", "selinuxfs", 0, NULL));
CHECKCALL(mknod("/dev/kmsg", S_IFCHR | 0600, makedev(1, 11)));
if constexpr (WORLD_WRITABLE_KMSG) {
CHECKCALL(mknod("/dev/kmsg_debug", S_IFCHR | 0622, makedev(1, 11)));
}
CHECKCALL(mknod("/dev/random", S_IFCHR | 0666, makedev(1, 8)));
CHECKCALL(mknod("/dev/urandom", S_IFCHR | 0666, makedev(1, 9)));
//这对于日志包装器是必需的,它在ueventd运行之前被调用
CHECKCALL(mknod("/dev/ptmx", S_IFCHR | 0666, makedev(5, 2)));
CHECKCALL(mknod("/dev/null", S_IFCHR | 0666, makedev(1, 3)));
//在第一阶段挂在tmpfs、mnt/vendor、mount/product分区。其他的分区不需要在第一阶段加载,
//只需要在第二阶段通过rc文件解析来加载。
CHECKCALL(mount("tmpfs", "/mnt", "tmpfs", MS_NOEXEC | MS_NOSUID | MS_NODEV,
"mode=0755,uid=0,gid=1000"));
//创建可供读写的vendor目录
CHECKCALL(mkdir("/mnt/vendor", 0755));
// /mnt/product is used to mount product-specific partitions that can not be
// part of the product partition, e.g. because they are mounted read-write.
CHECKCALL(mkdir("/mnt/product", 0755));
// 挂载APEX,这在Android 10.0中特殊引入,用来解决碎片化问题,类似一种组件方式,对Treble的增强,
// 不写谷歌特殊更新不需要完整升级整个系统版本,只需要像升级APK一样,进行APEX组件升级
CHECKCALL(mount("tmpfs", "/apex", "tmpfs", MS_NOEXEC | MS_NOSUID | MS_NODEV,
"mode=0755,uid=0,gid=0"));
// /debug_ramdisk is used to preserve additional files from the debug ramdisk
CHECKCALL(mount("tmpfs", "/debug_ramdisk", "tmpfs", MS_NOEXEC | MS_NOSUID | MS_NODEV,
"mode=0755,uid=0,gid=0"));
#undef CHECKCALL
//把标准输入、标准输出和标准错误重定向到空设备文件"/dev/null"
SetStdioToDevNull(argv);
//在/dev目录下挂载好 tmpfs 以及 kmsg
//这样就可以初始化 /kernel Log 系统,供用户打印log
InitKernelLogging(argv);
...
/* 初始化一些必须的分区
*主要作用是去解析/proc/device-tree/firmware/android/fstab,
* 然后得到"/system", "/vendor", "/odm"三个目录的挂载信息
*/
if (!DoFirstStageMount()) {
LOG(FATAL) << "Failed to mount required partitions early ...";
}
struct stat new_root_info;
if (stat("/", &new_root_info) != 0) {
PLOG(ERROR) << "Could not stat(\"/\"), not freeing ramdisk";
old_root_dir.reset();
}
if (old_root_dir && old_root_info.st_dev != new_root_info.st_dev) {
FreeRamdisk(old_root_dir.get(), old_root_info.st_dev);
}
SetInitAvbVersionInRecovery();
static constexpr uint32_t kNanosecondsPerMillisecond = 1e6;
uint64_t start_ms = start_time.time_since_epoch().count() / kNanosecondsPerMillisecond;
setenv("INIT_STARTED_AT", std::to_string(start_ms).c_str(), 1);
//启动init进程,传入参数selinux_steup
// 执行命令: /system/bin/init selinux_setup
const char* path = "/system/bin/init";
const char* args[] = {path, "selinux_setup", nullptr};
execv(path, const_cast<char**>(args));
PLOG(FATAL) << "execv(\"" << path << "\") failed";
return 1;
}
3.3 SetupSelinux
此阶段主要完成了:初始化selinux,加载SELinux规则,配置SELinux相关log输出,并启动第二阶段
system\core\init\selinux.cpp
/*此函数初始化selinux,然后执行init以在init selinux中运行*/
int SetupSelinux(char** argv) {
//初始化Kernel日志
InitKernelLogging(argv);
// Debug版本init crash时重启引导加载程序
if (REBOOT_BOOTLOADER_ON_PANIC) {
InstallRebootSignalHandlers();
}
//注册回调,用来设置需要写入kmsg的selinux日志
SelinuxSetupKernelLogging();
//加载SELinux规则
SelinuxInitialize();
/*
*我们在内核域中,希望转换到init域。在其xattrs中存储selabel的文件系统(如ext4)不需要显式restorecon,
*但其他文件系统需要。尤其是对于ramdisk,如对于a/b设备的恢复映像,这是必需要做的一步。
*其实就是当前在内核域中,在加载Seliux后,需要重新执行init切换到C空间的用户态
*/
if (selinux_android_restorecon("/system/bin/init", 0) == -1) {
PLOG(FATAL) << "restorecon failed of /system/bin/init failed";
}
//准备启动innit进程,传入参数second_stage
const char* path = "/system/bin/init";
const char* args[] = {path, "second_stage", nullptr};
execv(path, const_cast<char**>(args));
/*
*执行 /system/bin/init second_stage, 进入第二阶段
*/
PLOG(FATAL) << "execv(\"" << path << "\") failed";
return 1;
}
3.4 SecondStageMain
- 创建进程会话密钥并初始化属性系统
- 进行SELinux第二阶段并恢复一些文件安全上下文
- 新建epoll并初始化子进程终止信号处理函数
- 启动匹配属性的服务端
- 解析init.rc等文件,建立rc文件的action 、service,启动其他进程
此阶段内容过于繁杂,主要了解了一下rc文件的解析。
之前启动进程都是通过exec传参的方式启动,如果每个都是这样的方式启动就会无比繁琐,所以推出了init.rc这个机制。
init.rc文件解析
init.rc主要包含五种类型语句:Action Command Service Option Import
action由一组command命令组成,包含一个触发器,以on开头
command常用命令:
class_start <service_class_name>: 启动属于同一个class的所有服务;
class_stop <service_class_name> : 停止指定类的服务
start <service_name>: 启动指定的服务,若已启动则跳过;
stop <service_name>: 停止正在运行的服务
setprop <name> <value>:设置属性值
mkdir <path>:创建指定目录
symlink <target> <sym_link>: 创建连接到<target>的<sym_link>符号链接;
write <path> <string>: 向文件path中写入字符串;
exec: fork并执行,会阻塞init进程直到程序完毕;
exprot <name> <name>:设定环境变量;
loglevel <level>:设置log级别
hostname <name> : 设置主机名
import <filename> :导入一个额外的init配置文件
options:
Options是Service的可选项,与service配合使用
disabled: 不随class自动启动,只有根据service名才启动;
oneshot: service退出后不再重启;
user/group: 设置执行服务的用户/用户组,默认都是root;
class:设置所属的类名,当所属类启动/退出时,服务也启动/停止,默认为default;
onrestart:当服务重启时执行相应命令;
socket: 创建名为/dev/socket/<name>的socket
critical: 在规定时间内该service不断重启,则系统会重启并进入恢复模式
default: 意味着disabled=false,oneshot=false,critical=false。
解析init.rc
system/core/init/init.cpp
static void LoadBootScripts(ActionManager& action_manager, ServiceList& service_list) {
Parser parser = CreateParser(action_manager, service_list);
std::string bootscript = GetProperty("ro.boot.init_rc", "");
if (bootscript.empty()) {
std::string bootmode = GetProperty("ro.bootmode", "");
if (bootmode == "charger") {
parser.ParseConfig("/vendor/etc/init/charge.rc");
} else {
parser.ParseConfig("/init.rc");
if (!parser.ParseConfig("/system/etc/init")) {
late_import_paths.emplace_back("/system/etc/init");
}
if (!parser.ParseConfig("/product/etc/init")) {
late_import_paths.emplace_back("/product/etc/init");
}
if (!parser.ParseConfig("/product_services/etc/init")) {
late_import_paths.emplace_back("/product_services/etc/init");
}
if (!parser.ParseConfig("/odm/etc/init")) {
late_import_paths.emplace_back("/odm/etc/init");
}
if (!parser.ParseConfig("/vendor/etc/init")) {
late_import_paths.emplace_back("/vendor/etc/init");
}
}
} else {
parser.ParseConfig(bootscript);
}
}
创建解析对象,service on import
Parser CreateParser(ActionManager& action_manager, ServiceList& service_list) {
Parser parser;
parser.AddSectionParser("service", std::make_unique<ServiceParser>(&service_list, subcontexts));
parser.AddSectionParser("on", std::make_unique<ActionParser>(&action_manager, subcontexts));
parser.AddSectionParser("import", std::make_unique<ImportParser>(&parser));
return parser;
}
init.rc中===>import /init.${ro.zygote}.rc 通过此值来判断加载哪一个rc文件
在/system/core/rootdir下,存在init.zygoteXXX.rc,此例为init.zygote32.rc
service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server
class main
priority -20
//设置用户 root
user root
//访问组支持 root readproc reserved_disk
group root readproc reserved_disk
//创建一个socket,名字叫zygote,以tcp形式 ,可以在/dev/socket 中看到一个 zygote的socket
socket zygote stream 660 root system
socket usap_pool_primary stream 660 root system
// onrestart 指当进程重启时执行后面的命令
onrestart write /sys/android_power/request_state wake
onrestart write /sys/power/state on
onrestart restart audioserver
onrestart restart cameraserver
onrestart restart media
onrestart restart netd
onrestart restart wificond
// 创建子进程时,向 /dev/cpuset/foreground/tasks 写入pid
writepid /dev/cpuset/foreground/tasks
service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server
定义一个名为zygote的service,执行/system/bin/app_process二进制文件,传入四个参数
-Xzygote ---->将作为虚拟机启动时所需的参数
/system/bin ---->代表虚拟机程序所在目录
--zygote ---->指明以ZygoteInit.java类中的main函数作为虚拟机执行入口
--start-system-server ---->启动systemServer进程
*以上就是对init进程启动及主要流程的一个学习记录,大佬的分析思路很清晰,赞一个!还有很多不懂的地方还需跟进学习。