声明
- 前阶段在项目中涉及到了Android系统定制任务,Android系统定制前提要知道Android系统是如何启动的。
- 本文参考了一些书籍的若干章节,比如《Android进阶解密-第2章-Android系统启动》、《深入理解Android虚拟机-第8/9/10章-init进程详解/Dalvik VM的进程系统/Dalvik VM运作流程详解》、《深入理解Android系统-第6/7/8章-init启动进程详解/Zygote进程详解/System进程详解》等
- 本文使用的代码是LineageOS的cm-14.1,对应Android 7.1.2,可以参考我的另一篇博客:如何下载Nexus5的LineageOS14.1(cm-14.1)系统源码并编译、刷机
- 很多代码注释待详细写
1 init进程
init进程在Linux系统中是内核启动后启动的1号进程,init进程在Android系统中依然是内核初始化完成后首先启动的1号进程。init进程主要作用是:
- 创建、挂载启动所需的文件目录,包括:tmpfs、devpts、proc、sys、selinuxfs;
- 解析、处理init.rc等脚本文件中的命令;
- 创建Zygote和属性服务;
- 使用while(true)循环创建子进程;
其源码位置在:
vim ~/LineageOS/system/core/init/init.cpp
int main(int argc, char** argv) {
if (!strcmp(basename(argv[0]), "ueventd")) {
return ueventd_main(argc, argv);
}
if (!strcmp(basename(argv[0]), "watchdogd")) {
return watchdogd_main(argc, argv);
}
// 清理umask
umask(0);
add_environment("PATH", _PATH_DEFPATH);
bool is_first_stage = (argc == 1) || (strcmp(argv[1], "--second-stage") != 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.
//创建和挂载启动所需的用户空间文件目录
if (is_first_stage) {
mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755");
mkdir("/dev/pts", 0755);
mkdir("/dev/socket", 0755);
mount("devpts", "/dev/pts", "devpts", 0, NULL);
#define MAKE_STR(x) __STRING(x)
mount("proc", "/proc", "proc", 0, "hidepid=2,gid=" MAKE_STR(AID_READPROC));
mount("sysfs", "/sys", "sysfs", 0, NULL);
}
// 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();
klog_init();
klog_set_level(KLOG_NOTICE_LEVEL);
NOTICE("init %s started!\n", is_first_stage ? "first stage" : "second stage");
if (!is_first_stage) {
// Indicate that booting is in progress to background fw loaders, etc.
// 检测/dev/.booting文件是否可读写和创建
close(open("/dev/.booting", O_WRONLY | O_CREAT | O_CLOEXEC, 0000));
//初始化属性服务
property_init();
// If arguments are passed both on the command line and in DT,
// properties set in DT always have priority over the command-line ones.
process_kernel_dt();
//处理内核命令行
process_kernel_cmdline();
// Propagate the kernel variables to internal variables
// used by init as well as the current required properties.
export_kernel_boot_props();
}
// Set up SELinux, including loading the SELinux policy if we're in the kernel domain.
selinux_initialize(is_first_stage);
// If we're in the kernel domain, re-exec init to transition to the init domain now
// that the SELinux policy has been loaded.
if (is_first_stage) {
if (restorecon("/init") == -1) {
ERROR("restorecon failed: %s\n", strerror(errno));
security_failure();
}
char* path = argv[0];
char* args[] = { path, const_cast<char*>("--second-stage"), nullptr };
if (execv(path, args) == -1) {
ERROR("execv(\"%s\") failed: %s\n", path, strerror(errno));
security_failure();
}
}
// 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.
NOTICE("Running restorecon...\n");
restorecon("/dev");
restorecon("/dev/socket");
restorecon("/dev/__properties__");
restorecon("/property_contexts");
restorecon_recursive("/sys");
//创建epoll句柄
epoll_fd = epoll_create1(EPOLL_CLOEXEC);
if (epoll_fd == -1) {
ERROR("epoll_create1 failed: %s\n", strerror(errno));
exit(1);
}
//设置子进程信号处理函数,如果子进程(Zygote进程)异常退出,init进程会调用该函数中设定的信号处理函数进行处理,防止init的子进程成为僵尸进程。
//系统会在子进程暂停/终止时发出SIGCHLD信号通知父进程init,signal_handler_init()函数用来设置init进程接收SIGCHLD信号后的处理动作。
signal_handler_init();
//导入默认的环境变量
property_load_boot_defaults();
export_oem_lock_status();
//启动属性服务
start_property_service();
const BuiltinFunctionMap function_map;
Action::set_function_map(&function_map);
Parser& parser = Parser::GetInstance();
parser.AddSectionParser("service",std::make_unique<ServiceParser>());
parser.AddSectionParser("on", std::make_unique<ActionParser>());
parser.AddSectionParser("import", std::make_unique<ImportParser>());
//解析init.rc配置文件
parser.ParseConfig("/init.rc");
ActionManager& am = ActionManager::GetInstance();
am.QueueEventTrigger("early-init");
// Queue an action that waits for coldboot done so we know ueventd has set up all of /dev...
am.QueueBuiltinAction(wait_for_coldboot_done_action, "wait_for_coldboot_done");
// ... so that we can start queuing up actions that require stuff from /dev.
am.QueueBuiltinAction(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng");
am.QueueBuiltinAction(keychord_init_action, "keychord_init");
am.QueueBuiltinAction(console_init_action, "console_init");
// Trigger all the boot actions to get us started.
am.QueueEventTrigger("init");
// Repeat mix_hwrng_into_linux_rng in case /dev/hw_random or /dev/random
// wasn't ready immediately after wait_for_coldboot_done
am.QueueBuiltinAction(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng");
// Don't mount filesystems or start core system services in charger mode.
//在charger模式下略过mount文件系统的工作
std::string bootmode = property_get("ro.bootmode");
if (bootmode == "charger" || charging_mode_booting() ||
strcmp(battchg_pause, BOARD_CHARGING_CMDLINE_VALUE) == 0) {
am.QueueEventTrigger("charger");
} else if (strncmp(bootmode.c_str(), "ffbm", 4) == 0) {
NOTICE("Booting into ffbm mode\n");
am.QueueEventTrigger("ffbm");
} else {
am.QueueEventTrigger("late-init");
}
// Run all property triggers based on current state of the properties.
am.QueueBuiltinAction(queue_property_triggers_action, "queue_property_triggers");
while (true) {
if (!waiting_for_exec) {
am.ExecuteOneCommand();
//重启死去的进程
restart_processes();
}
int timeout = -1;
if (process_needs_restart) {
timeout = (process_needs_restart - gettime()) * 1000;
if (timeout < 0)
timeout = 0;
}
if (am.HasMoreCommands()) {
timeout = 0;
}
bootchart_sample(&timeout);
epoll_event ev;
int nr = TEMP_FAILURE_RETRY(epoll_wait(epoll_fd, &ev, 1, timeout));
if (nr == -1) {
ERROR("epoll_wait failed: %s\n", strerror(errno));
} else if (nr == 1) {
((void (*)()) ev.data.ptr)();
}
}
return 0;
}
2 Android Init Language脚本文件语法
Android源码中会有很多的rc脚本文件用来对系统进行初始化配置,一般存放在:
~/LineageOS/system/core/rootdir
及
~/LineageOS/device/lge/hammerhead
完整的init.rc脚本包含四个状态类别:
- Actions-动作
- Commands-命令
- Services-服务
- Options-选项
2.1 通用语法规则
- 注释以"#"开头;
- 关键字和参数以空格隔开,每个语句以行为单位;
- C语言风格的反斜杠转义字符("")可以用来为参数添加空格;
- 为了防止字符串中的空格把其分隔成多个部分,我们需要对其使用双引号;
- Actions和Services暗示着一个新语句(section)的开始,这两个关键字后面跟着的commands或者options都属于这个新语句(section);
- Actions和Services有唯一的名字,如果出现和已有动作或服务重名的,将会被当做错误忽略掉;
init.rc文件是以块(section)为单位组织的,一个section可以包含多行。section分成两大类:一类称为“Action(行为)”,另一类称为“Service(服务)”。
“行为”块以关键字“on”开始,表示一堆命令的集合,“服务”块以“service”开始,表示启动某个进程的方式和参数。
“块”以关键字“on”或“service”开始,直到下一个“on”或“service”结束,中间所有行都属于这个“块”(空行或者注释都不具备分割作用)。下面是一部分init.rc文件示例:
on early-init
# Set init and its forked children's oom_adj.
write /proc/1/oom_score_adj -1000
# Disable sysrq from keyboard
write /proc/sys/kernel/sysrq 0
# Set the security context of /adb_keys if present.
restorecon /adb_keys
# Shouldn't be necessary, but sdcard won't start without it. http://b/22568628.
mkdir /mnt 0775 root system
# Set the security context of /postinstall if present.
restorecon /postinstall
start ueventd
on property:sys.boot_from_charger_mode=1
class_stop charger
trigger late-init
on nonencrypted
class_start main
class_start late_start
service installd /system/bin/installd
class main
socket installd stream 600 system system
on load_persist_props_action
load_persist_props
start logd
start logd-reinit
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 audioserver
onrestart restart cameraserver
onrestart restart media
onrestart restart netd
writepid /dev/cpuset/foreground/tasks
2.2 Actions
Action是在某种条件下触发一系列的命令,通常有一个trigger。如:
on <trigger>
<command>
<command>
Actionde 的关键字“on”后面的字符串为“trigger”(触发器),如上面代码中的“early-init”,trigger后面的内容是命令列表command,每一行都是一条命令,常见的command参见2.4节。
Action其实就是一系列的Command集合。每个Action都有一个trigger,它决定本Action的执行时机。当一个符合Action触发条件的事件发生了,此Action会加入到执行队列的末尾(除非它已经在队列里了)。每一个Action都将依次从队列中取出,此Action的每个Command都将依次执行。
2.2.1 trigger
Triggers是一个用于匹配某种事件类型的字符串,它将使对应的action执行
- boot是Init执行后第一个触发的Trigger(在/init.conf装载之后);
- =:这种形式的Trigger会在属性"name"设置为指定的“value”时被触发;
- device-added-
:这种形式的Trigger会在一个设备文件增加时触发; - device-removed-
:这种形式的Trigger会在一个设备文件移除时触发; - service-exited-:这种形式的Trigger会在一个指定的Service退出时触发;
2.2.2 command
Commands是Action的命令列表中的命令,或者是Service的选项onrestart的参数命令。
- exec [ [ [ ]* ] ] – [ ]*:fork和启动一个程序,在程序完成启动之前,Init进程将会阻塞。command跟在“–”之后,在这之前我们可以指定像安全上下文、用户名和组名这样的参数信息;seclabel默认指定为“-”。
- export :设置全局环境变量为(所有在这命令执行之后运行的进程都将继承该环境变量)。
- ifup :启动网络接口。
- import :引入一个配置文件,扩展当前配置。
- hostname :设置主机名。
- chdir :设置进程当前的工作目录。
- chmod
:设置文件或目录的访问权限。 - chown
:设置文件或目录的所有者和组。 - chroot :设置进程的根目录。
- class_start :启动所有指定服务名称下的未运行服务。
- class_stop :停止所有指定服务名称下的已运行的服务。
- class_reset :停止服务,后续可以通过class_start启动。
- domainname :设置域名。
- insmod
:安装一个驱动模块。 - mkdir
[mode] [owner] [group]:新创建一个目录 ,可以指定访问权限、拥有者和组。如果没有指定,默认的访问权限是755,属于root用户和root组。 - mount
[ ]*:在指定目录下挂载一个设备。可以是以mtd@name的格式指定的一个mtd块设备。包括“ro”、“rw”、“remount”、“noatime”等。 - restorecon
:重新存储指定的文件到一个有file_contexts配置的安全上下文。不用指定目录,它们会被Init进程自动创建。 - setcon :设置当前进程的安全上下文为指定的串。主要用在early-init中去设置Init的安全上下文。
- setenforce 0|1:设置SELinux系统级的enforcing状态。0代表permissive,1代表enforcing。
- setkey:目前未使用。
- setprop :设置系统属性为值。
- setrlimit :设置的rlimit值。
- setsebool :设置SELinux的boolean型属性的值为。的值可以是“1|true|on"或者”0|false|off“。
- start :启动指定服务(如果此服务还未运行)。
- stop :停止指定服务(如果此服务还在运行中)。
- restart :重启指定名称的服务,先stop,再start。
- symlink
:创建一个符号连接。 - sysclktz <mins_west_of_gmt>:设置系统的时钟基准(0代表格林尼治平均时(GMT)为准)。
- trigger 触发一个事件。用于将一个Action和另一个Action连在一起执行。
- wait
[]:等待指定路径的文件被创建出来,创建完成就停止等待,或者等到超时时间到达。如果未指定超时时间,缺省时间是5秒。 - write
[<string]*:打开指定的文件,并写入一个或多个字符串。
2.3 Service
Service是指的服务,如:
service <name> <pathname> [ <argument> ]*
<option>
<option>
service后的的字符串为服务名称,service下面的行称为option(选项),常见的option参见2.5节
无论是“Action”块还是“Service”块,它们的执行顺序都与.rc文件中的声明顺序无关,是否执行及执行顺序要由init进程在运行时决定。
2.3.1 opinion
Options是Service的修饰词,它们决定一个Service何时以及如何运行,主要包括下面选项:
- critical:表示这是一个关键的服务。如果该Service 4分钟内重新启动超过4次,系统将自动重启并进入recovery模式。
- disabled:表示服务不能通过start className(main/core等)启动。它必须以命令“start service_name”的形式显示指定名称启动。
- setenv :在Service启动时将环境变量“name”设置为“value”。
- socket [ []]:创建一个名为/dev/socket/的套接字,并把文件描述符传递给要启动的进程。的值是“dgram”或者是“stream”,User和group的值默认为0。
- user :在启动这个Service前设置Service的用户名,默认是root。如果进程没有相应的权限,将不能使用该命令。如果进程有root权限,可以在程序中设置你想要的uid。
- group []*:在启动这个Service前设置Service的服务。除了第一个组名,剩下的组名同城用于设置进程的附加组(通过setgroups())。默认是root。
- oneshot:Service退出后不再重启。
- class :给Service指定一个名字。所有同名字的服务可以同时启动和停止。如果不通过class选项指定一个名字,则默认是“default”。
- onrestart:当Service重启时,执行一条命令。
- writepid <file…>:将当前进程的进程号写到<file…>文件中。
2.4 语法总结
通过这些语法规则,可以去源码目录的~/LineageOS/system/core/rootdir
.rc文件遵循的这些语法功能其实主要还是给人看的,方便程序员通过.rc文件来控制Android系统启动初始化;对于机器来说,它会对*.rc文件进行解析,换另外的一个视角去读取*.rc文件。
3 init.rc中Service/Action解析
在源码 ~/LineageOS/system/core/init/init.cpp中对init.rc进行解析的代码如下:
Parser& parser = Parser::GetInstance();
parser.AddSectionParser("service",std::make_unique<ServiceParser>());
parser.AddSectionParser("on", std::make_unique<ActionParser>());
parser.AddSectionParser("import", std::make_unique<ImportParser>());
//解析init.rc配置文件
parser.ParseConfig("/init.rc");
在源码 ~/LineageOS/system/core/init/init_parser.cpp中,所调用的ParseConfig函数定义为:
bool Parser::ParseConfig(const std::string& path) {
//判断传入path为目录的话执行:ParseConfigDir(path)
if (is_dir(path.c_str())) {
return ParseConfigDir(path);
}
//传入的为/init.rc,所以执行:ParseConfigFile(path)
return ParseConfigFile(path);
}
在源码 ~/LineageOS/system/core/init/init_parser.cpp中,所调用的ParseConfigFile函数定义为:
bool Parser::ParseConfigFile(const std::string& path) {
INFO("Parsing file %s...\n", path.c_str());
//计时用
Timer t;
//将./init.rc文件内容逐个字符读入一个string类型的对象data中;
std::string data;
if (!read_file(path.c_str(), &data)) {
return false;
}
//添加字符'\n'到字符串一定用push_back函数,切不可直接用"+"
data.push_back('\n'); // TODO: fix parse_config.
//调用ParseData函数对
ParseData(path, data);
//
for (const auto& sp : section_parsers_) {
sp.second->EndFile(path);
}
// Turning this on and letting the INFO logging be discarded adds 0.2s to
// Nexus 9 boot time, so it's disabled by default.
if (false) DumpState();
//记录下解析./init.rc所用时间
NOTICE("(Parsing %s took %.2fs.)\n", path.c_str(), t.duration());
return true;
}
在源码 ~/LineageOS/system/core/init/init_parser.cpp中,所调用的ParseData函数定义为:
void Parser::ParseData(const std::string& filename, const std::string& data) {
//TODO: Use a parser with const input and remove this copy
//将data内容副本存入data_copy中,并在结尾添加'\0';
std::vector<char> data_copy(data.begin(), data.end());
data_copy.push_back('\0');
//初始化parse_state结构体,用于对已经字符串化的init.rc进行封装,其声明在parser.h中
parse_state state;
state.filename = filename.c_str();
state.line = 0;
state.ptr = &data_copy[0];
state.nexttoken = 0;
//SectionParser类声明在init_parser.h中
SectionParser* section_parser = nullptr;
std::vector<std::string> args;
for (;;) {
//next_token函数定义在parser.cpp中,用于解析state
switch (next_token(&state)) {
//next_token函数逐字符解析到state的最结尾'\0'时会返回T_EOF,结束解析;
case T_EOF:
if (section_parser) {
section_parser->EndSection();
}
return;
//next_token函数逐字符解析到state的'\n'字符时会返回T_NEWLINE;
case T_NEWLINE:
state.line++;
if (args.empty()) {
break;
}
if (section_parsers_.count(args[0])) {
if (section_parser) {
section_parser->EndSection();
}
//根据此行首词args[0]是关键字service/on,此行的解析要创建Ation或Service新对象
section_parser = section_parsers_[args[0]].get();
std::string ret_err;
//ParseSection函数主要用来搭建Service/Action的架子
if (!section_parser->ParseSection(args, &ret_err)) {
parse_error(&state, "%s\n", ret_err.c_str());
section_parser = nullptr;
}
//根据此行首词args[0]不是关键字service/on,此行的解析用来填充上面创建的Ation或Service对象的子项
} else if (section_parser) {
std::string ret_err;
//ParseLineSection函数主要用来解析Service/Action子项
if (!section_parser->ParseLineSection(args, state.filename,
state.line, &ret_err)) {
parse_error(&state, "%s\n", ret_err.c_str());
}
}
args.clear();
break;
//
case T_TEXT:
args.emplace_back(state.text);
break;
}
}
}
解析过程总体来讲就是根据参数创建出Service/Action对象,然后根据选项域的内容填充Service/Action对象,最后将Service/Action对象加入到vector类型的Services/Action链表中。
解析service时,会用到两个函数,一个是ServiceParser::ParseSection函数主要用来搭建service的架子。另一个是ServiceParser::ParseLineSection用于解析子项。
解析Action时,会用到两个函数,一个是ActionParser::ParseSection函数主要用来搭建service的架子。另一个是ActionParser::ParseLineSection用于解析子项。
提示:关于init进程解析init.rc的这部分的代码,从Android4.4-Android7.1是一直在变,以前使用纯C实现,现在完全是C++代码,但是原理是不变的!
4 init进程控制Action
待补充…
5 init进程控制Service
以init启动zygote为例,init.rc中利用import命令加载了init.zygote32.rc(因为我的是Nexus5手机),截取init.rc中一段代码:
import /init.environ.rc
import /init.usb.rc
import /init.${ro.hardware}.rc
import /init.usb.configfs.rc
import /init.${ro.zygote}.rc
...省略n行...
查看源码中:~/LineageOS/system/core/rootdir/init.zygote32.rc文件内容为:
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 audioserver
onrestart restart cameraserver
onrestart restart media
onrestart restart netd
writepid /dev/cpuset/foreground/tasks
根据2.3.1节中对于opinion的介绍可知,此服务所在的class为main。在init.rc中可查看main是何时启动的呢?
...省略n行...
on nonencrypted
# A/B update verifier that marks a successful boot.
exec - root cache -- /system/bin/update_verifier nonencrypted
class_start main
class_start late_start
...省略n行...
其中class_start main的含义就是启动所有classname 为 main 的Service,此时zygote服务也就被启动了。class_start命令对应的实际执行函数为do_class_start,查看其源代码在~/LineageOS/system/core/init/builtins.cpp中:
static int do_class_start(const std::vector<std::string>& args) {
//ForEachServiceInClass将遍历Service链表,查找到classname为main的服务(zygote),执行StartIfNotDisabled函数
ServiceManager::GetInstance().
ForEachServiceInClass(args[1], [] (Service* s) { s->StartIfNotDisabled(); });
std::string prop_name = StringPrintf("class_start:%s", args[1].c_str());
if (prop_name.length() < PROP_NAME_MAX) {
ActionManager::GetInstance().QueueEventTrigger(prop_name);
}
return 0;
}
StartIfNotDisabled函数代码在源码 ~/LineageOS/system/core/init/service.cpp中:
bool Service::StartIfNotDisabled() {
if (!(flags_ & SVC_DISABLED)) {//检测相关service的opinion中是否设置了disabled选项,若没有设置则直接运行Start函数;
return Start();
} else {
flags_ |= SVC_DISABLED_START;
}
return true;
}
在此文件中查看Start函数:
bool Service::Start() {
// Starting a service removes it from the disabled or reset state and
// immediately takes it out of the restarting state if it was in there.
flags_ &= (~(SVC_DISABLED|SVC_RESTARTING|SVC_RESET|SVC_RESTART|SVC_DISABLED_START));
time_started_ = 0;
// Running processes require no additional work --- if they're in the
// process of exiting, we've ensured that they will immediately restart
// on exit, unless they are ONESHOT.
//如果Service已经运行了,则不启动;
if (flags_ & SVC_RUNNING) {
return false;
}
bool needs_console = (flags_ & SVC_CONSOLE);
if (needs_console && !have_console) {
ERROR("service '%s' requires console\n", name_.c_str());
flags_ |= SVC_DISABLED;
return false;
}
//判断所要启动的Service对应的可执行文件是否存在,否则不启动;
struct stat sb;
if (stat(args_[0].c_str(), &sb) == -1) {
ERROR("cannot find '%s' (%s), disabling '%s'\n",
args_[0].c_str(), strerror(errno), name_.c_str());
flags_ |= SVC_DISABLED;
return false;
}
std::string scon;
if (!seclabel_.empty()) {
scon = seclabel_;
} else {
char* mycon = nullptr;
char* fcon = nullptr;
INFO("computing context for service '%s'\n", args_[0].c_str());
int rc = getcon(&mycon);
if (rc < 0) {
ERROR("could not get context while starting '%s'\n", name_.c_str());
return false;
}
rc = getfilecon(args_[0].c_str(), &fcon);
if (rc < 0) {
ERROR("could not get context while starting '%s'\n", name_.c_str());
free(mycon);
return false;
}
char* ret_scon = nullptr;
rc = security_compute_create(mycon, fcon, string_to_security_class("process"),
&ret_scon);
if (rc == 0) {
scon = ret_scon;
free(ret_scon);
}
if (rc == 0 && scon == mycon) {
ERROR("Service %s does not have a SELinux domain defined.\n", name_.c_str());
free(mycon);
free(fcon);
return false;
}
free(mycon);
free(fcon);
if (rc < 0) {
ERROR("could not get context while starting '%s'\n", name_.c_str());
return false;
}
}
NOTICE("Starting service '%s'...\n", name_.c_str());
//创建子进程
pid_t pid = fork();
if (pid == 0) {
umask(077);
for (const auto& ei : envvars_) {
add_environment(ei.name.c_str(), ei.value.c_str());
}
for (const auto& si : sockets_) {
int socket_type = ((si.type == "stream" ? SOCK_STREAM :
(si.type == "dgram" ? SOCK_DGRAM :
SOCK_SEQPACKET)));
const char* socketcon =
!si.socketcon.empty() ? si.socketcon.c_str() : scon.c_str();
//后面介绍create_socket函数的左右
int s = create_socket(si.name.c_str(), socket_type, si.perm,
si.uid, si.gid, socketcon);
if (s >= 0) {
//后面介绍PublishSocket函数的作用
PublishSocket(si.name, s);
}
}
std::string pid_str = StringPrintf("%d", getpid());
for (const auto& file : writepid_files_) {
if (!WriteStringToFile(pid_str, file)) {
ERROR("couldn't write %s to %s: %s\n",
pid_str.c_str(), file.c_str(), strerror(errno));
}
}
if (ioprio_class_ != IoSchedClass_NONE) {
if (android_set_ioprio(getpid(), ioprio_class_, ioprio_pri_)) {
ERROR("Failed to set pid %d ioprio = %d,%d: %s\n",
getpid(), ioprio_class_, ioprio_pri_, strerror(errno));
}
}
if (needs_console) {
setsid();
OpenConsole();
} else {
ZapStdio();
}
setpgid(0, getpid());
// As requested, set our gid, supplemental gids, and uid.
if (gid_) {
if (setgid(gid_) != 0) {
ERROR("setgid failed: %s\n", strerror(errno));
_exit(127);
}
}
if (!supp_gids_.empty()) {
if (setgroups(supp_gids_.size(), &supp_gids_[0]) != 0) {
ERROR("setgroups failed: %s\n", strerror(errno));
_exit(127);
}
}
if (uid_) {
if (setuid(uid_) != 0) {
ERROR("setuid failed: %s\n", strerror(errno));
_exit(127);
}
}
if (!seclabel_.empty()) {
if (setexeccon(seclabel_.c_str()) < 0) {
ERROR("cannot setexeccon('%s'): %s\n",
seclabel_.c_str(), strerror(errno));
_exit(127);
}
}
std::vector<std::string> expanded_args;
std::vector<char*> strs;
expanded_args.resize(args_.size());
strs.push_back(const_cast<char*>(args_[0].c_str()));
for (std::size_t i = 1; i < args_.size(); ++i) {
if (!expand_props(args_[i], &expanded_args[i])) {
ERROR("%s: cannot expand '%s'\n", args_[0].c_str(), args_[i].c_str());
_exit(127);
}
strs.push_back(const_cast<char*>(expanded_args[i].c_str()));
}
strs.push_back(nullptr);
//调用execve函数,Service子进程启动,即启动/system/bin/app_process;
if (execve(strs[0], (char**) &strs[0], (char**) ENV) < 0) {
ERROR("cannot execve('%s'): %s\n", strs[0], strerror(errno));
}
_exit(127);
}
if (pid < 0) {
ERROR("failed to start '%s'\n", name_.c_str());
pid_ = 0;
return false;
}
time_started_ = gettime();
pid_ = pid;
flags_ |= SVC_RUNNING;
if ((flags_ & SVC_EXEC) != 0) {
INFO("SVC_EXEC pid %d (uid %d gid %d+%zu context %s) started; waiting...\n",
pid_, uid_, gid_, supp_gids_.size(),
!seclabel_.empty() ? seclabel_.c_str() : "default");
}
NotifyStateChange("running");
return true;
}
create_socket函数所在源码位置~/LineageOS/system/core/init/util.cpp:
/*
* create_socket - creates a Unix domain socket in ANDROID_SOCKET_DIR
* ("/dev/socket") as dictated in init.rc. This socket is inherited by the
* daemon. We communicate the file descriptor's value via the environment
* variable ANDROID_SOCKET_ENV_PREFIX<name> ("ANDROID_SOCKET_foo").
*/
int create_socket(const char *name, int type, mode_t perm, uid_t uid,
gid_t gid, const char *socketcon)
{
struct sockaddr_un addr;
int fd, ret, savederrno;
char *filecon;
if (socketcon) {
if (setsockcreatecon(socketcon) == -1) {
ERROR("setsockcreatecon(\"%s\") failed: %s\n", socketcon, strerror(errno));
return -1;
}
}
//调用socket函数创建一个socket,使用文件描述符fd来描述此socket;
fd = socket(PF_UNIX, type, 0);
if (fd < 0) {
ERROR("Failed to open socket '%s': %s\n", name, strerror(errno));
return -1;
}
if (socketcon)
setsockcreatecon(NULL);
//为socket创建一个类型为AF_UNIX的socket地址addr;
memset(&addr, 0 , sizeof(addr));
addr.sun_family = AF_UNIX;
snprintf(addr.sun_path, sizeof(addr.sun_path), ANDROID_SOCKET_DIR"/%s",
name);
ret = unlink(addr.sun_path);
if (ret != 0 && errno != ENOENT) {
ERROR("Failed to unlink old socket '%s': %s\n", name, strerror(errno));
goto out_close;
}
filecon = NULL;
if (sehandle) {
ret = selabel_lookup(sehandle, &filecon, addr.sun_path, S_IFSOCK);
if (ret == 0)
setfscreatecon(filecon);
}
ret = bind(fd, (struct sockaddr *) &addr, sizeof (addr));
savederrno = errno;
setfscreatecon(NULL);
freecon(filecon);
if (ret) {
ERROR("Failed to bind socket '%s': %s\n", name, strerror(savederrno));
goto out_unlink;
}
//设置设备文件/dev/socket/zygote的用户id、用户组id、用户权限;
ret = lchown(addr.sun_path, uid, gid);
if (ret) {
ERROR("Failed to lchown socket '%s': %s\n", addr.sun_path, strerror(errno));
goto out_unlink;
}
ret = fchmodat(AT_FDCWD, addr.sun_path, perm, AT_SYMLINK_NOFOLLOW);
if (ret) {
ERROR("Failed to fchmodat socket '%s': %s\n", addr.sun_path, strerror(errno));
goto out_unlink;
}
INFO("Created socket '%s' with mode '%o', user '%d', group '%d'\n",
addr.sun_path, perm, uid, gid);
return fd;
out_unlink:
unlink(addr.sun_path);
out_close:
close(fd);
return -1;
}
PublishSocket函数所在源码位置~/LineageOS/system/core/init/service.cpp:
//参数fd是指向create_socket函数创建的socket的文件描述符;
void Service::PublishSocket(const std::string& name, int fd) const {
//将宏ANDROID_SOCKET_ENV_PREFIX和参数name描述的字符串连接起来,保存在字符串key中;
std::string key = StringPrintf(ANDROID_SOCKET_ENV_PREFIX "%s", name.c_str());
std::string val = StringPrintf("%d", fd);
add_environment(key.c_str(), val.c_str());
/* make sure we don't close-on-exec */
fcntl(fd, F_SETFD, 0);
}
6 属性服务
init进程启动时会启动Android系统的属性服务,并在内存中开辟一块空间来存储这些属性。相关代码在系统源码:
...省略n行...
if (!is_first_stage) {
// Indicate that booting is in progress to background fw loaders, etc.
close(open("/dev/.booting", O_WRONLY | O_CREAT | O_CLOEXEC, 0000));
//初始化属性服务的配置
property_init();
// If arguments are passed both on the command line and in DT,
// properties set in DT always have priority over the command-line ones.
process_kernel_dt();
//设置内核变量
process_kernel_cmdline();
// Propagate the kernel variables to internal variables
// used by init as well as the current required properties.
//用属性值设置内核变量
export_kernel_boot_props();
}
...省略n行...
//启动属性服务
start_property_service();
6.1 init.rc中就用到的属性
在分析init.rc时就发现在该脚本开头就有用到属性ro.hardware、ro.zygote:
import /init.environ.rc
import /init.usb.rc
import /init.${ro.hardware}.rc
import /init.usb.configfs.rc
import /init.${ro.zygote}.rc
其实在init.cpp中可发现,init进程启动了属性服务后,随即就进行了init.rc脚本文件的解析。
6.2 利用内核属性设置应用层配置文件
内核变量可以由bootloader传入,其中init.cpp中process_kernel_cmdline函数就是用于获取传入内核变量的值,其源码为:
static void process_kernel_cmdline() {
// Don't expose the raw commandline to unprivileged processes.
chmod("/proc/cmdline", 0440);
// The first pass does the common stuff, and finds if we are in qemu.
// The second pass is only necessary for qemu to export all kernel params
// as properties.
import_kernel_cmdline(false, import_kernel_nv);
if (qemu[0]) import_kernel_cmdline(true, import_kernel_nv);
}
init.cpp随后调用了export_kernel_boot_props函数,其就是利用内核属性来设置应用层配置文件的属性,其源码为:
static void export_kernel_boot_props() {
//初始化该结构体类型的数组prop_map[],并赋初始值为unknown;
struct {
const char *src_prop;
const char *dst_prop;
const char *default_value;
} prop_map[] = {
#ifndef IGNORE_RO_BOOT_SERIALNO
{ "ro.boot.serialno", "ro.serialno", "", },
#endif
{ "ro.boot.mode", "ro.bootmode", "unknown", },
{ "ro.boot.baseband", "ro.baseband", "unknown", },
{ "ro.boot.bootloader", "ro.bootloader", "unknown", },
{ "ro.boot.hardware", "ro.hardware", "unknown", },
#ifndef IGNORE_RO_BOOT_REVISION
{ "ro.boot.revision", "ro.revision", "0", },
#endif
};
//通过property_set来获取proc_map中src_prop的属性值,并赋给相应的dst_prop;
for (size_t i = 0; i < ARRAY_SIZE(prop_map); i++) {
std::string value = property_get(prop_map[i].src_prop);
property_set(prop_map[i].dst_prop, (!value.empty()) ? value.c_str() : prop_map[i].default_value);
}
}
其中,property_get、property_set函数定义在源码~/LineageOS/system/core/init/property_service.cpp中:
std::string property_get(const char* name) {
char value[PROP_VALUE_MAX] = {0};
__system_property_get(name, value);
return value;
}
property_get函数最终调用的是bionic库函数中的__system_property_get函数,在源码目录 ~/LineageOS/bionic/libc/bionic/system_properties.cpp中:
int __system_property_get(const char *name, char *value)
{
const prop_info *pi = __system_property_find(name);
if (pi != 0) {
return __system_property_read(pi, 0, value);
} else {
value[0] = 0;
return 0;
}
}
在源码文件~/LineageOS/bionic/libc/include/sys/_system_properties.hz中定义了几个宏:
#define PROP_PATH_RAMDISK_DEFAULT "/default.prop"
#define PROP_PATH_SYSTEM_BUILD "/system/build.prop"
#define PROP_PATH_VENDOR_BUILD "/vendor/build.prop"
#define PROP_PATH_LOCAL_OVERRIDE "/data/local.prop"
#define PROP_PATH_FACTORY "/factory/factory.prop"
这些宏在~/LineageOS/system/core/init/property_service.cpp的load_properties_from_file函数中用得到,意思是从这些文件中获取属性值。
6.3 初始化属性服务
初始化属性服务的配置函数property_init,定义在 ~/LineageOS/system/core/init/property_service.cpp中:
void property_init() {
//初始化属性内存区域
if (__system_property_area_init()) {
ERROR("Failed to initialize property area\n");
exit(1);
}
}
启动属性服务的函数start_property_service,定义在~/LineageOS/system/core/init/property_service.cpp中:
void start_property_service() {
//创建非阻塞的socket
property_set_fd = create_socket(PROP_SERVICE_NAME, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK,
0666, 0, 0, NULL);
if (property_set_fd == -1) {
ERROR("start_property_service socket creation failed: %s\n", strerror(errno));
exit(1);
}
//对property_set_fd进行监听,此socket就成为server,即属性服务;参数8代表的意思是该属性服务最多可以同时为8个用户设置属性的需求提供服务;
listen(property_set_fd, 8);
//将property_set_fd放入epoll中,利用epoll来监听property_set_fd,当property_set_fd接收到数据时会调用handle_property_set_fd函数对其处理;
register_epoll_handler(property_set_fd, handle_property_set_fd);
}
在linux新的内核中,epoll用来替换select,epoll最大的好处在于它不会随着监听fd数目的增长而降低效率。因为内核中的select实现是数组数据结构轮询来处理的,轮询的fd数目越多,自然耗时越多。而epoll用于保存事件的数据类型为红黑树,查找速度快。
6.4 处理客户端请求
属性服务接收到客户端的请求后会调用handle_property_set_fd函数进行处理,定义在~/LineageOS/system/core/init/property_service.cpp中:
static void handle_property_set_fd()
{
prop_msg msg;
int s;
int r;
struct ucred cr;
struct sockaddr_un addr;
socklen_t addr_size = sizeof(addr);
socklen_t cr_size = sizeof(cr);
char * source_ctx = NULL;
struct pollfd ufds[1];
const int timeout_ms = 2 * 1000; /* Default 2 sec timeout for caller to send property. */
int nr;
if ((s = accept(property_set_fd, (struct sockaddr *) &addr, &addr_size)) < 0) {
return;
}
/* Check socket options here */
if (getsockopt(s, SOL_SOCKET, SO_PEERCRED, &cr, &cr_size) < 0) {
close(s);
ERROR("Unable to receive socket options\n");
return;
}
ufds[0].fd = s;
ufds[0].events = POLLIN;
ufds[0].revents = 0;
nr = TEMP_FAILURE_RETRY(poll(ufds, 1, timeout_ms));
if (nr == 0) {
ERROR("sys_prop: timeout waiting for uid=%d to send property message.\n", cr.uid);
close(s);
return;
} else if (nr < 0) {
ERROR("sys_prop: error waiting for uid=%d to send property message: %s\n", cr.uid, strerror(errno));
close(s);
return;
}
r = TEMP_FAILURE_RETRY(recv(s, &msg, sizeof(msg), MSG_DONTWAIT));
if(r != sizeof(prop_msg)) {
ERROR("sys_prop: mis-match msg size received: %d expected: %zu: %s\n",
r, sizeof(prop_msg), strerror(errno));
close(s);
return;
}
switch(msg.cmd) {
case PROP_MSG_SETPROP:
msg.name[PROP_NAME_MAX-1] = 0;
msg.value[PROP_VALUE_MAX-1] = 0;
if (!is_legal_property_name(msg.name, strlen(msg.name))) {
ERROR("sys_prop: illegal property name. Got: \"%s\"\n", msg.name);
close(s);
return;
}
getpeercon(s, &source_ctx);
if(memcmp(msg.name,"ctl.",4) == 0) {
// Keep the old close-socket-early behavior when handling
// ctl.* properties.
close(s);
if (check_control_mac_perms(msg.value, source_ctx, &cr)) {
handle_control_message((char*) msg.name + 4, (char*) msg.value);
} else {
ERROR("sys_prop: Unable to %s service ctl [%s] uid:%d gid:%d pid:%d\n",
msg.name + 4, msg.value, cr.uid, cr.gid, cr.pid);
}
} else {
if (check_mac_perms(msg.name, source_ctx, &cr)) {
property_set((char*) msg.name, (char*) msg.value);
} else {
ERROR("sys_prop: permission denied uid:%d name:%s\n",
cr.uid, msg.name);
}
// Note: bionic's property client code assumes that the
// property server will not close the socket until *AFTER*
// the property is written to memory.
close(s);
}
freecon(source_ctx);
break;
default:
close(s);
break;
}
}
7 总结
init进程的工作内容很多,主要的就三部分:
- 创建并挂载启动所需的文件系统及目录;
- 初始化、启动属性服务;
- 解析init.rc脚本文件并按照脚本文件中的指令启动各系统服务,如Zygote、Linux层守护进程、ServiceManager等;
Enjoy it!!
下一篇分析:[日更-2019.4.20、21] cm-14.1 Android系统启动过程分析(二)-Zygote进程启动过程
来源:CSDN
作者:小馬佩德罗
链接:https://blog.csdn.net/Xiaoma_Pedro/article/details/103892754