业无高卑志当坚,男儿有求安得闲?这篇文章主要讲述Android Framework学习之init进程解析相关的知识,希望能为你提供帮助。
init进程是android系统中用户空间的第一个进程,
它被赋予了很多极其重要的工作职责,
init进程相关源码位于system/core/init,
本篇博客我们就一起来学习init进程(
基于Android 7.0)
。
init入口函数分析 init的入口函数为main,
位于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);
}// Clear the 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.
//1.创建一些文件夹,
并挂载设备,
这些都是与Linux相关
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.
//2.重定向标准输入,
输出,
错误输出到/dev/_null_
open_devnull_stdio();
3.初始化内核log系统
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.
close(open("
/dev/.booting"
, O_WRONLY | O_CREAT | O_CLOEXEC, 0000));
//4.初始化和属性相关资源
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.
5.完成SELinux相关工作
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) {
6.重新设置属性
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"
);
7.创建epoll句柄
epoll_fd =
epoll_create1(EPOLL_CLOEXEC);
if (epoll_fd =
=
-1) {
ERROR("
epoll_create1 failed: %s\\n"
, strerror(errno));
exit(1);
}
8.装载子进程信号处理器
signal_handler_init();
property_load_boot_defaults();
export_oem_lock_status();
//9.启动属性服务
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>
());
//10.解析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(set_mmap_rnd_bits_action, "
set_mmap_rnd_bits"
);
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.
std::string bootmode =
property_get("
ro.bootmode"
);
if (bootmode =
=
"
charger"
) {
am.QueueEventTrigger("
charger"
);
} 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;
}
从上面代码中可以精简归纳init的main方法做的事情:
1.创建文件系统目录并挂载相关的文件系统
2.屏蔽标准的输入输出
3.初始化内核log系统
4.调用property_init初始化属性相关的资源
5.完成SELinux相关工作
6.重新设置属性
7.创建epoll句柄
8.装载子进程信号处理器
9.通过property_start_service启动属性服务
10.通过parser.ParseConfig(“/init.rc”)来解析init.rc
接下来对上述部分步骤, 进行详细解析。
1.创建文件系统目录并挂载相关的文件系统
//清除屏蔽字(file mode creation mask),
保证新建的目录的访问权限不受屏蔽字影响。
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
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);
}
该部分主要用于创建和挂载启动所需的文件目录。
需要注意的是, 在编译Android系统源码时, 在生成的根文件系统中, 并不存在这些目录, 它们是系统运行时的目录, 即当系统终止时, 就会消失。
在init初始化过程中, Android分别挂载了tmpfs, devpts, proc, sysfs这4类文件系统。
2.屏蔽标准的输入输出
open_devnull_stdio();
前文生成/dev目录后, init进程将调用open_devnull_stdio函数, 屏蔽标准的输入输出。
open_devnull_stdio函数会在/dev目录下生成null设备节点文件, 并将标准输入、标准输出、标准错误输出全部重定向到null设备中。
void open_devnull_stdio(void)
{
// Try to avoid the mknod() call if we can. Since SELinux makes
// a /dev/null replacement available for free, let'
s use it.
int fd =
open("
/sys/fs/selinux/null"
, O_RDWR);
if (fd =
=
-1) {
// OOPS, /sys/fs/selinux/null isn'
t available, likely because
// /sys/fs/selinux isn'
t mounted. Fall back to mknod.
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 =
=
-1) {
exit(1);
}
}dup2(fd, 0);
dup2(fd, 1);
dup2(fd, 2);
if (fd >
2) {
close(fd);
}
}
open_devnull_stdio函数定义于system/core/init/util.cpp中。
这里需要说明的是, dup2函数的作用是用来复制一个文件的描述符, 通常用来重定向进程的stdin、stdout和stderr。它的函数原形是:
int dup2(int oldfd, int targetfd)
该函数执行后, targetfd将变成oldfd的复制品。
因此上述过程其实就是: 创建出null设备后, 将0、1、2绑定到null设备上。因此init进程调用open_devnull_stdio函数后, 通过标准的输入输出无法输出信息。
4.初始化属性域
if (!is_first_stage) {
.......
property_init();
.......
}
调用property_init初始化属性域。在Android平台中, 为了让运行中的所有进程共享系统运行时所需要的各种设置值, 系统开辟了属性存储区域, 并提供了访问该区域的API。
需要强调的是, 在init进程中有部分代码块以is_first_stage标志进行区分, 决定是否需要进行初始化, 而is_first_stage的值, 由init进程main函数的入口参数决定。 其原因在于, 在引入selinux机制后, 有些操作必须要在内核态才能完成;
但init进程作为android的第一个进程, 又是运行在用户态的。
于是, 最终设计为用is_first_stage进行区分init进程的运行状态。init进程在运行的过程中, 会完成从内核态到用户态的切换。
void property_init() {
if (__system_property_area_init()) {
ERROR("
Failed to initialize property area\\n"
);
exit(1);
}
}
property_init函数定义于system/core/init/property_service.cpp中, 如上面代码所示, 最终调用_system_property_area_init函数初始化属性域。
5.完成SELinux相关工作
// Set up SELinux, including loading the SELinux policy if we'
re in the kernel domain.
selinux_initialize(is_first_stage);
init进程进程调用selinux_initialize启动SELinux。从注释来看, init进程的运行确实是区分用户态和内核态的。
static void selinux_initialize(bool in_kernel_domain) {
Timer t;
selinux_callback cb;
//用于打印log的回调函数
cb.func_log =
selinux_klog_callback;
selinux_set_callback(SELINUX_CB_LOG, cb);
//用于检查权限的回调函数
cb.func_audit =
audit_callback;
selinux_set_callback(SELINUX_CB_AUDIT, cb);
if (in_kernel_domain) {
//内核态处理流程
INFO("
Loading SELinux policy...\\n"
);
//用于加载sepolicy文件。该函数最终将sepolicy文件传递给kernel,
这样kernel就有了安全策略配置文件,
后续的MAC才能开展起来。
if (selinux_android_load_policy() <
0) {
ERROR("
failed to load policy: %s\\n"
, strerror(errno));
security_failure();
}
//内核中读取的信息
bool kernel_enforcing =
(security_getenforce() =
=
1);
//命令行中得到的数据
bool is_enforcing =
selinux_is_enforcing();
if (kernel_enforcing !=
is_enforcing) {
//用于设置selinux的工作模式。selinux有两种工作模式:
//1、”permissive”,
所有的操作都被允许(
即没有MAC)
,
但是如果违反权限的话,
会记录日志
//2、”enforcing”,
所有操作都会进行权限检查。在一般的终端中,
应该工作于enforing模式
if(security_setenforce(is_enforcing)) {
........
//将重启进入recovery mode
security_failure();
}
}
if (write_file("
/sys/fs/selinux/checkreqprot"
, "
0"
) =
=
-1) {
security_failure();
}NOTICE("
(Initializing SELinux %s took %.2fs.)\\n"
,
is_enforcing ? "
enforcing"
: "
non-enforcing"
, t.duration());
} else {
selinux_init_all_handles();
}
}
6.重新设置属性
// 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) {
//按selinux policy要求,
重新设置init文件属性
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 };
//这里就是前面所说的,
启动用户态的init进程,
即second-stage
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.
INFO("
Running restorecon...\\n"
);
restorecon("
/dev"
);
restorecon("
/dev/socket"
);
restorecon("
/dev/__properties__"
);
restorecon_recursive("
/sys"
);
上述文件节点在加载Sepolicy之前已经被创建了, 因此在加载完Sepolicy后, 需要重新设置相关的属性。
9.启动配置属性的服务端
start_property_service();
init进程在共享内存区域中, 创建并初始化属性域。其它进程可以访问属性域中的值, 但更改属性值仅能在init进程中进行。这就是init进程调用start_property_service的原因。其它进程修改属性值时, 要预先向init进程提交值变更申请, 然后init进程处理该申请, 并修改属性值。在访问和修改属性时, init进程都可以进行权限控制。
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);
}
//调用listen函数监听property_set_fd,
于是该socket变成一个server
listen(property_set_fd, 8);
//监听server socket上是否有数据到来
register_epoll_handler(property_set_fd,handle_property_set_fd);
}
我们知道, 在create_socket函数返回套接字property_set_fd时, property_set_fd是一个主动连接的套接字。此时, 系统假设用户会对这个套接字调用connect函数, 期待它主动与其它进程连接。
由于在服务器编程中, 用户希望这个套接字可以接受外来的连接请求, 也就是被动等待用户来连接, 于是需要调用listen函数使用主动连接套接字变为被连接套接字, 使得一个进程可以接受其它进程的请求, 从而成为一个服务器进程。
因此, 调用listen后, init进程成为一个服务进程, 其它进程可以通过property_set_fd连接init进程, 提交设置系统属性的申请。
listen函数的第二个参数, 涉及到一些网络的细节。
在进程处理一个连接请求的时候, 可能还存在其它的连接请求。因为TCP连接是一个过程, 所以可能存在一种半连接的状态。有时由于同时尝试连接的用户过多, 使得服务器进程无法快速地完成连接请求。
因此, 内核会在自己的进程空间里维护一个队列, 以跟踪那些已完成连接但服务器进程还没有接手处理的用户, 或正在进行的连接的用户。这样的一个队列不可能任意大, 所以必须有一个上限。listen的第二个参数就是告诉内核使用这个数值作为上限。因此, init进程作为系统属性设置的服务器, 最多可以同时为8个试图设置属性的用户提供服务。
在启动配置属性服务的最后, 调用函数register_epoll_handler。该函数将利用之前创建出的epoll句柄监听property_set_fd。当property_set_fd中有数据到来时, init进程将利用handle_property_set_fd函数进行处理。
static void handle_property_set_fd() {
..........
if ((s =
accept(property_set_fd, (struct sockaddr *) &
addr, &
addr_size)) <
0) {
return;
}
........
r =
TEMP_FAILURE_RETRY(recv(s, &
msg, sizeof(msg), MSG_DONTWAIT));
.........
switch(msg.cmd) {
.........
}
.........
}
handle_propery_set_fd函数实际上是调用accept函数监听连接请求, 接收property_set_fd中到来的数据, 然后利用recv函数接受到来的数据, 最后根据到来数据的类型, 进行设置系统属性等相关操作, 在此不做深入分析。
介绍一下系统属性改变的一些用途。
在init.rc中定义了一些与属性相关的触发器。当某个条件相关的属性被改变时, 与该条件相关的触发器就会被触发。举例来说, 如下面代码所示, debuggable属性变为1时, 将执行启动console进程等操作。
on property:ro.debuggable=
1
# Give writes to anyone for the trace folder on debug builds.
# The folder is used to store method traces.
chmod 0773 /data/misc/trace
start console
总结一下, 其它进程修改系统属性时, 大致的流程如下图所示: 其它的进程像init进程发送请求后, 由init进程检查权限后, 修改共享内存区。
文章图片
10.解析配置文件init.rc init.rc是系统配置文件, 位于system/core/rootdir/init.rc, Android 7.0中对init.rc文件进行了拆分, 每个服务一个rc文件。
init.rc文件是在init进程启动后执行的启动脚本, 文件中记录着init进程需执行的操作。在Android系统中, 使用init.rc和init.{ hardware }.rc两个文件。
其中init.rc文件在Android系统运行过程中用于通用的环境设置与进程相关的定义, init.{hardware}.rc( 例如, 高通有init.qcom.rc, MTK有init.mediatek.rc) 用于定义Android在不同平台下的特定进程和环境设置等。
init.rc文件大致分为两大部分, 一部分是以“on”关键字开头的动作列表( action list) :
on early-init
# Set init and its forked children'
s oom_adj.
write /proc/1/oom_score_adj -1000
.........
start ueventd
另一部分是以“service”关键字开头的服务列表( service list) :
service ueventd /sbin/ueventd
class core
critical
seclabel u:r:ueventd:s0
动作列表用于创建所需目录, 以及为某些特定文件指定权限, 而服务列表用来记录init进程需要启动的一些子进程。如上面代码所示, service关键字后的第一个字符串表示服务( 子进程) 的名称, 第二个字符串表示服务的执行路径。
接下来, 我们从ParseConfig函数入手, 逐步分析整个解析过程(函数定义于system/core/init/ Init_parser.cpp中):
bool Parser::ParseConfig(const std::string&
path) {
if (is_dir(path.c_str())) {
//传入参数为目录地址
return ParseConfigDir(path);
}
//传入参数为文件地址
return ParseConfigFile(path);
}
bool Parser::ParseConfigDir(const std::string&
path) {
...........
std::unique_ptr<
DIR, int(*)(DIR*)>
config_dir(opendir(path.c_str()), closedir);
..........
//看起来很复杂,
其实就是递归目录
while ((current_file =
readdir(config_dir.get()))) {
std::string current_path =
android::base::StringPrintf("
%s/%s"
, path.c_str(), current_file->
d_name);
if (current_file->
d_type =
=
DT_REG) {
//最终还是靠ParseConfigFile来解析实际的文件
if (!ParseConfigFile(current_path)) {
.............
}
}
}
}
从上面的代码可以看出, 解析init.rc文件的函数是ParseConfigFile:
bool Parser::ParseConfigFile(const std::string&
path) {
INFO("
Parsing file %s...\\n"
, path.c_str());
Timer t;
std::string data;
//读取路径指定文件中的内容,
保存为字符串形式
if (!read_file(path.c_str(), &
data)) {
return false;
}data.push_back('
\\n'
);
// TODO: fix parse_config.
//解析获取的字符串
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();
NOTICE("
(Parsing %s took %.2fs.)\\n"
, path.c_str(), t.duration());
return true;
}
ParseData函数定义于system/core/init/init_parser.cpp中, 根据关键字解析出服务和动作。动作与服务会以链表节点的形式注册到service_list与action_list中, service_list与action_list是init进程中声明的全局结构体
void Parser::ParseData(const std::string&
filename, const std::string&
data) {
.......
parse_state state;
.......
std::vector<
std::string>
args;
for (;
;
) {
//next_token以行为单位分割参数传递过来的字符串
//最先走到T_TEXT分支
switch (next_token(&
state)) {
case T_EOF:
if (section_parser) {
//EOF,解析结束
section_parser->
EndSection();
}
return;
case T_NEWLINE:
state.line+
+
;
if (args.empty()) {
break;
}
//创建parser时,
会为init.rc中以service,
on,
import开头的都定义了对应的解析parser
//这里就是根据第一个参数,
判断是否有对应的parser
if (section_parsers_.count(args[0])) {
if (section_parser) {
//结束上一个parser的工作,
将构造出的对象加入到对应的service_list与action_list中
section_parser->
EndSection();
}
//获取参数对应的parser
section_parser =
section_parsers_[args[0]].get();
std::string ret_err;
//调用实际parser的ParseSection函数
if (!section_parser->
ParseSection(args, &
ret_err)) {
parse_error(&
state, "
%s\\n"
, ret_err.c_str());
section_parser =
nullptr;
}
} else if (section_parser) {
std::string ret_err;
//如果第一个参数不是service,
on,
import
//则调用前一个parser的ParseLineSection函数
//这里相当于解析一个参数块的子项
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中
args.emplace_back(state.text);
break;
}
}
}
这里的解析看起来比较复杂, 在6.0以前的版本中, 整个解析是面向过程的。init进程统一调用一个函数来进行解析, 然后在该函数中利用switch-case的形式, 根据解析的内容进行相应的处理。
在Android 7.0中, 为了更好地封装及面向对象, 对于不同的关键字定义了不同的parser对象, 每个对象通过多态实现自己的解析操作。
在init进程main函数中, 创建各种parser的代码如下:
...........
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>
());
...........
看看三个Parser的定义:
class ServiceParser : public SectionParser {......}
class ActionParser : public SectionParser {......}
class ImportParser : public SectionParser {.......}
可以看到三个Parser均是继承SectionParser, 具体的实现各有不同, 我们以比较常用的ServiceParser和ActionParser为例
ServiceParser
ServiceParser定义于system/core/init/service.cpp中。从前面的代码, 我们知道, 解析一个service块, 首先需要调用ParseSection函数, 接着利用ParseLineSection处理子块, 解析完所有数据后, 调用EndSection。
因此, 我们着重看看ServiceParser的这三个函数:
bool ServiceParser::ParseSection(.....) {
.......
const std::string&
name =
args[1];
.......
std::vector<
std::string>
str_args(args.begin() +
2, args.end());
//主要根据参数,
构造出一个service对象
service_ =
std::make_unique<
Service>
(name, "
default"
, str_args);
return true;
}
//注意这里已经在解析子项了
bool ServiceParser::ParseLineSection(......) const {
//调用service对象的HandleLine
return service_ ? service_->
HandleLine(args, err) : false;
}
bool Service::HandleLine(.....) {
........
//OptionHandlerMap继承自keywordMap<
OptionHandler>
static const OptionHandlerMap handler_map;
//根据子项的内容,
找到对应的handler函数
//FindFunction定义于keyword模块中,FindFunction方法利用子类生成对应的map中,
然后通过通用的查找方法,
即比较键值找到对应的处理函数
auto handler =
handler_map.FindFunction(args[0], args.size() - 1, err);
if (!handler) {
return false;
}
//调用handler函数
return (this->
*handler)(args, err);
}
class Service::OptionHandlerMap : public KeywordMap<
OptionHandler>
{
...........
Service::OptionHandlerMap::Map&
Service::OptionHandlerMap::map() const {
constexpr std::size_t kMax =
std::numeric_limits<
std::size_t>
::max();
static const Map option_handlers =
{
{"
class"
,{1,1,&
Service::HandleClass}},
{"
console"
,{0,0,&
Service::HandleConsole}},
{"
critical"
,{0,0,&
Service::HandleCritical}},
{"
disabled"
,{0,0,&
Service::HandleDisabled}},
{"
group"
,{1,NR_SVC_SUPP_GIDS +
1, &
Service::HandleGroup}},
{"
ioprio"
,{2,2,&
Service::HandleIoprio}},
{"
keycodes"
,{1,kMax, &
Service::HandleKeycodes}},
{"
oneshot"
,{0,0,&
Service::HandleOneshot}},
{"
onrestart"
,{1,kMax, &
Service::HandleOnrestart}},
{"
seclabel"
,{1,1,&
Service::HandleSeclabel}},
{"
setenv"
,{2,2,&
Service::HandleSetenv}},
{"
socket"
,{3,6,&
Service::HandleSocket}},
{"
user"
,{1,1,&
Service::HandleUser}},
{"
writepid"
,{1,kMax, &
Service::HandleWritepid}},
};
return option_handlers;
}
//以class对应的处理函数为例,
可以看出其实就是填充service对象对应的域
bool Service::HandleClass(const std::vector<
std::string>
&
args, std::string* err) {
classname_ =
args[1];
return true;
}//注意此时service对象已经处理完毕
void ServiceParser::EndSection() {
if (service_) {
ServiceManager::GetInstance().AddService(std::move(service_));
}
}void ServiceManager::AddService(std::unique_ptr<
Service>
service) {
Service* old_service =
FindServiceByName(service->
name());
if (old_service) {
ERROR("
ignored duplicate definition of service '
%s'
"
,
service->
name().c_str());
return;
}
//将service对象加入到services_里
//7.0里,
services_已经是个vector了
services_.emplace_back(std::move(service));
}
总结一下: ServiceParser中, 首先根据第一行的名字和参数创建出service对象, 然后根据选项域的内容填充service对象, 最后将创建出的service对象加入到vector类型的service链表中。
【Android Framework学习之init进程解析】ActionParser
ActionParser定义于system/core/init/action.cpp中。Action的解析过程, 其实与Service一样, 也是先后调用ParseSection, ParseLineSection和EndSection。
bool ActionParser::ParseSection(....) {
........
//创建出新的action对象
auto action =
std::make_unique<
Action>
(false);
//根据参数,
填充action的trigger域,
不详细分析了
if (!action->
InitTriggers(triggers, err)) {
return false;
}
.........
}bool ActionParser::ParseLineSection(.......) const {
//构造Action对象的command域
return action_ ? action_->
AddCommand(args, filename, line, err) : false;
}bool Action::AddCommand(.....) {
........
//找出action对应的执行函数
auto function =
function_map_->
FindFunction(args[0], args.size() - 1, err);
........
//利用所有信息构造出command,
加入到action对象中
AddCommand(function, args, filename, line);
return true;
}void Action::AddCommand(......) {
commands_.emplace_back(f, args, filename, line);
}void ActionParser::EndSection() {
if (action_ &
&
action_->
NumCommands() >
0) {
ActionManager::GetInstance().AddAction(std::move(action_));
}
}void ActionManager::AddAction(.....) {
........
auto old_action_it =
std::find_if(actions_.begin(),
actions_.end(),
[&
action] (std::unique_ptr<
Action>
&
a) {
return action->
TriggersEqual(*a);
});
if (old_action_it !=
actions_.end()) {
(*old_action_it)->
CombineAction(*action);
} else {
//加入到action链表中,
类型也是vector,
其中装的是指针
actions_.emplace_back(std::move(action));
}
}
可以看出, 加载action块的逻辑和service一样, 不同的是需要填充trigger和command域。当然, 最后解析出的action也需要加入到action链表中。
这里最后还剩下一个问题, 那就是哪里定义了Action中command对应处理函数?
答案就是在init.cpp的main函数中:
.......
const BuiltinFunctionMap function_map;
Action::set_function_map(&
function_map);
.......
Action中调用function_map_-> FindFunction时, 实际上调用的是BuiltinFunctionMap的FindFunction函数。FindFunction是keyword定义的通用函数, 重点是重构的map函数。所以需要看BuiltinFunctionMap, 其定义在system/core/init/builtins.cpp:
BuiltinFunctionMap::Map&
BuiltinFunctionMap::map() const {
constexpr std::size_t kMax =
std::numeric_limits<
std::size_t>
::max();
static const Map builtin_functions =
{
{"
bootchart_init"
,{0,0,do_bootchart_init}},
{"
chmod"
,{2,2,do_chmod}},
{"
chown"
,{2,3,do_chown}},
{"
class_reset"
,{1,1,do_class_reset}},
{"
class_start"
,{1,1,do_class_start}},
{"
class_stop"
,{1,1,do_class_stop}},
{"
copy"
,{2,2,do_copy}},
{"
domainname"
,{1,1,do_domainname}},
{"
enable"
,{1,1,do_enable}},
{"
exec"
,{1,kMax, do_exec}},
{"
export"
,{2,2,do_export}},
{"
hostname"
,{1,1,do_hostname}},
{"
ifup"
,{1,1,do_ifup}},
{"
init_user0"
,{0,0,do_init_user0}},
{"
insmod"
,{1,kMax, do_insmod}},
{"
installkey"
,{1,1,do_installkey}},
{"
load_pe
推荐阅读
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Eclipse Android 代码自己主动提示功能
- Android Sensor Development
- 计算机体系结构与计算机组织
- 如何使用Linux SS命令(用法示例指南)
- 如何使用Linux tee命令(用法示例指南)
- Linux如何使用Netstat命令(28个命令和用法示例)
- Snap与APT有什么区别(哪一个更好?为什么?)
- 如何安装VirtualBox扩展包(操作步骤分步指南)
- 如何在Linux中使用md5sum命令(用法示例指南)