1.init进程运行过程
init进程是有内核启动的第一个用户级进程。
2.init进程源码分析
1.主要功能
- 子进程的终止处理
- 生成设备节点
- 提供属性服务,保存运行所需的环境变量
- 分析init.rc启动脚本文件
文章图片
2.基本流程
(1)注册与子进程相关的SIGCHLD信号处理器,这里只是用作通知,具体的时间处理在init的事件处理循环中。
(2)创建并挂载启动所需的文件目录
open_devnull_stdio();
//创建日志输出设备
log_init();
//生成/dev/__kmsg__设备节点文件,输出log信息。
(3)解析init.rc文件
init.rc和 init.{hardware}.rc两个文件
生成服务列表和动作列表。 分别以链表的形式注册到service_list和action_list中。
(4)初始化QEMU 设备,
(5)解析init.{hardware}.rc文件
(6)执行early-init,片段中的命令
init, early-boot, boot
action_for_each_trigger("early-init", action_add_queue_tail);
//将参数中的命令保存到运行队列中
drain_action_queue();
//将运行队列中的命令逐一取出执行。
(7)创建init进程中已经定义好的设备节点文件。
device_fd = device_init();
(8)初始化属性服务
property_init();
(9)将Android启动Logo显示再LCD屏幕上。
#define INIT_IMAGE_FILE "/initlogo.rle"
load_565rle_image(INIT_IMAGE_FILE);
(10)设置属性: property_set()函数。
(11)执行动作列表中 init 片段中的命令。
action_for_each_trigger("init", action_add_queue_tail);
drain_action_queue();
(12)启动属性服务
(13)创建套接字 ==接收==子进程终止的SIGCHLD信号,调用相关handler进程处理。
(14)执行动作列表中 early-boot, boot, property相关的命令。
文章图片
(15)设置事件处理的监听事件
文章图片
3.init.rc脚本文件分析与执行 两个功能
- 设置系统环境,记录待执行的进程
- action_list 与 service_list相关的内容。
(1) on init 主要设置环境变量,生成系统运行所需的文件或目录
文章图片
(2)on boot 主要设置应用终止条件
文章图片
(3)on property :
文章图片
3.2 服务列表
sevice 段落用来记录init进程启动的进程。
由init进程启动的子进程或是一次性程序,或是运行在后台的与应用程序、系统相关的守护进程。
service段落中的服务全部注册在服务列表中,init进程从该列表中依次取出相应服务,并启动它。
3.3init.rc文件分析函数
parse_config_file() 用来分析init.rc脚本文件。
int parse_config_file(const char *fn)
{
char *data;
data = https://www.it610.com/article/read_file(fn, 0);
//读取文件到内存中,保存为字符串格式,返回字符串在内存中的初始地址
parse_config(fn, data);
//分析read_file函数返回的字符串,并生成动作列表和参数列表。
}
(1)parse_config()函数
static void parse_config(const char *fn, char *s)
{
for(;
;
){
switch (next_token(&state)){//以行为单位分割参数传递过来的字符串。
case T_NEWLINE:
if(nargs) {
int kw = lookup_keyword(args[0]);
//返回每行首个单词在keyword_list结构体数组中的数组编号。
if (kw_is(kw, SECTION)){ //SECTION分组区分动作列表和服务列表
parse_new_section(&state, kw, nargs, args);
//将筛选出的命令注册动作列表或者服务列表中。
}
}
}
}
}
3.4 动作列表与服务列表的运行
(1)动作列表的运行
文章图片
1)获取动作列表的 head
2)从action列表取出动作列表 转换成command结构体
3)执行 动作列表中动作对应的函数。
(2)服务列表的运行
文章图片
通过on boot段落中的最后一行命令 class_start 运行service 段落中所有的程序。
service_start 通过执行execve()系统调用来运行服务列表中的程序。
4.创建设备节点文件。 设备节点文件是设备驱动的逻辑文件
- 冷插拔:以预先定义的设备信息为基础,当init进程被启动运行时,统一创建设备节点文件
- 热插拔:系统运行时,为插入的设备动态创建设备节点文件。
文章图片
4.1创建静态设备节点
冷插拔机制
在init进程启动后,在/sys下读取实现注册好的设备信息,而后引发与各设备相对应的uevent,创建设备节点文件。
举例Binder驱动程序
(1)binder驱动程序在初始化函数中调用misc_rigister()函数,将创建设备节点的所需的信息保存在/sys目录下。
(2)相关的节点文件是已经保存在/system/core/init/device.c中,devperms结构体。
这里的这个文件充当一个列表,
(3)init进程调用device_init()函数
ini device_init(void)
{
fd = open_uevent_socket();
//创建一个套接字,接收uevent
to = get_usecs();
coldboot(fd, "/sys/class");
//调用 do_coldboot()函数。
coldboot(fd, "/sys/block");
coldboot(fd, "/sys/devices");
t1 = get_usecs();
log_event_print("coldboot %ld uS\n", ((long) (t1 - t0)));
}static void do_coldboot(int event_fd, DIR *d)
{
fd = openat(dfd, "uevent", O_WRONLY);
if(fd >= 0){
write(fd, "add\n", 4);
//写入"add"信息,强制引起uevent
close(fd);
handle_device_fd(event_fd);
//handler_device_fd()函数接收相关的uevent.
}
}void handle_device_fd(int fd)
{
while((n = recv(fd, msg, UEVENT_MSG_LEN)) > 0){
struct uevent uevent;
parse_event(msg, &uevent);
//将uevent信息写入uevent结构体中
handle_devive_event(&uevent);
//创建节点文件
}
}static void handle_device_event(strut uevent *uevent)
{if(!strncmp(uevent->subsystem, "block", 5)){
block = 1;
base = "/dev/block/";
mkdir(base, 0755);
}
.
.
else
base = "dev";
if(!strcmp (uevent->action, "add)){
make_device(devpath, block, uevent->major, uevent->minor);
//调用mknod()函数,创建设备节点文件。
}
}
总结:驱动程序将创建设备节点所需的信息保存到/sys目录下,
相关的节点文件 文件已经保存在 /system/core/init/device.c中
init进程启动后调用 docoolboot(),写入add 强制引起uevent
从列表中获取相关的节点文件,创建设备节点文件。
uevent.
4.2动态设备感知
热插拔由init进程的事件处理循环来完成。
调用handle_device_fd()函数,创建设备节点文件。
文章图片
5.进程的终止与再启动。
文章图片
进程再启动代码分析 子进程终止时,init进程接收传递过来的SIGCHLD信号,调用与之对应的处理函数sigchld_handler(),
static void sigchld_handler(int s)
{
write(signal_fd, &s, 1);
}
文章图片
signal_fd记录信号编号,调用wait_for_one_process()函数被调用。
- 发生SIGCHLD信号时,程序从监听状态中跳出,执行poll()函数。
- wait_for_one_process()函数在产生SIGCHLD信号的进程的服务列表中,检查进程的设置选项
static int wait_for_one_process(int block)
{
...
//回收进程所占用的资源。
while( (pid = waitpid(-1, &status, block ? 0 : WNOHANG)) == -1
&& errno == EINTR );
//用来取出与服务列表中终止进程相关的服务项目。
svc = service_find_by_pid(pid);
//在取出的服务项目选项中,检查SVC_ONESHOT是否被设置。如果已经设置了,直接终止。
if(!(svc->flags & SVC_ONESHOT)){
kill(-pid, SIGKILL);
}
/* remove any socket we may have created*/
for(si = svc->sockets;
si;
si = si->next){
//删除所有进程持有的socketDescriptor
unlink(tmp);
}
svc->pid = 0;
svc->flags &= (~SVC_RUNNING);
//取消正在运行标记。if(svc->flags & SVC_ONESHOT) {//设置进程标记为SVC_DISABLED,从wait_for_one_process中跳出。
svc->flags |= SVC_DISABLED;
}
if(svc->flags & SVC_DISABLED)
return 0;
list_for_each(node, &svc->onrestart.commands) {//重新启动相关进程????
cmd = node_to_item(node, struct command, clist);
cmd->func(cmd->nargs, cmd->args);
}
svc->flags |= SVC_RESTARING;
}
wait_for_one_process()函数执行完毕后,事件处理循环中的restart_processes()函数就会被调用执行。
static void rstart_service_if_needed(struct service *svc)
{
svc->flags &= (~SVC_RESTSRING);
service_start(svc);
return;
}
static void restart_process()
{
process_needs_restart = 0;
service_for_each_flags(SVC_RESARTING, restart_service_if_needed);
}
//运行服务列表中带有SVC_RESTART标记的进程。当一个带有此标记的进程被终止,产生SIGCHLD信号时,restart_process()函数将重新启动它。
6.属性服务 只有init进程才能修改属性指,其他进程修改属性值时,必须向init进程提出请求,init检查权限后,再修改属性指。
文章图片
6.1属性初始化
void property_init(void)
{
init_property_area();
//属性域初始化。
load_properties_from_file(PROP_PATH_RAMDISK_DEFAULT);
}
int main (int argc, char **argv)
{
...
property_init();
//在共享内存中生成属性域
...
}
int start_property_service(void)
{
int fd;
//读取存储在各文件中的基本设置,将他们设置为属性指。
load_properties_from_file(PROP_PATH_SYSTEM_BUILD);
load_properties_from_file(PROP_PATH_SYSTEM_DEFAULT);
load_properties_from_file(PROP_PATH_LOCAL_OVERRIDE);
//read persistent properties after all default values have been
//读取系统运行时其他进程新生成的属性值或者更改的属性值。
load_persistent_properties();
//创建/dev/socket/property_service的Unix域套接字
fd_createsocket(PROP_SERVICE NAME, SOCK STREAM, 0666, 0, 0);
if(fd < 0) return -1;
fcntl(fd, F_SETFD, FD CLOEXEC);
fcntl(fd, F_SETFL, 0 NONBLOCK)listen(fd, 8);
return fd
}
int main (int argc, char **argv)
{
property_set_fd = start_property_service();
//start_property_service()函数,创建启动属性服务所需要的Unix域套接字。
}
6.2属性变更请求处理
【Android init 学习】接收到属性变更请求后,init进程就会调用handle_property_set_fd()函数。
void handle_property_set_fd(int fd)
{
...
/* check socket options here */
if(getsockopt(s, SOL_SOCKER, SO_PEERCRED, &cr, &cr_size) < 0){
//获取SO_PEERCRED值, 以便检查传递信息进程的访问权限
close(s);
ERROR("Unable to recieve socket options\n");
return;
}
...
switch(msg.cmd) {
case PROP_MSG_SETPROP:
...
if(memcmp(msg.name, "ctl." , 4) ==0 ){//ctl消息是请求进程启动与终止的消息。
if(check_control_perms(msg.value, cr.uid)){//检查访问权限
handle_control_message((char *)msg.name + 4, (char*) msg.value);
}
...
}else{
if(check_perms(msg.name, cr.uid)){//检查访问权限
property_set((char*)msg.name, (char*)msg.value);
//更改属性值,
}
...
}
...
}
}