android 开机启动流程分析(08)无限循环的处理

该系列文章总纲链接:专题分纲目录 android 开机启动流程分析


本章关键点总结 & 说明:

说明:思维导图是基于之前文章不断迭代的,本章内容我们关注➕"loop {属性变化 & 子进程signal & 组合按键} "部分即可

1 循环中关键语句execute_one_command(),实现如下:

void execute_one_command(void)
{
    int ret, i;
    char cmd_str[256] = "";
    /**
      list_tail(&act->commands) == &cmd->clist
      判定是否到达cmd list的结尾,即是否是当前action的最后一个命令
      */
    if (!cur_action || !cur_command || is_last_command(cur_action, cur_command)) {
        /**
	      action_remove_queue_head
	      从action_queue{即qlist}中获取action
	      从原来链表中移除后重新初始化,返回该action
	      */
        cur_action = action_remove_queue_head();//获取当前的action
        cur_command = NULL;
        if (!cur_action)
            return;
        cur_command = get_first_command(cur_action);//获取当前action的第一个command
    } else {
        cur_command = get_next_command(cur_action, cur_command);//获取当前action的next command
    }
    if (!cur_command)
        return;
    //当前的action和command均有效,则执行command的func方法
    ret = cur_command->func(cur_command->nargs, cur_command->args);
    ...//日志处理相关
}

每次循环都会进入到execute_one_command方法,这里的逻辑是,顺序执行所有action的command列表。

2 循环中关键语句restart_processes();

//restart_processes->service_for_each_flags(SVC_RESTARTING,restart_service_if_needed)
void service_for_each_flags(unsigned matchflags,void (*func)(struct service *svc))
{
    struct listnode *node;
    struct service *svc;
    list_for_each(node, &service_list) {
        svc = node_to_item(node, struct service, slist);
        if (svc->flags & matchflags) {
			/**
			  这里传递的func是restart_service_if_needed,实现的核心是:
              restart_service_if_needed(svc)->service_start(svc, NULL);//在这里启动service
			  */
            func(svc);
			
        }
    }
}

关键点service_start的处理如下所示:

void service_start(struct service *svc, const char *dynamic_args)
{    
    ...//变量声明初始化
	...//标志位的处理
    ...//SELinux相关
    pid = fork();//fork进程
    if (pid == 0) {//子进程的处理
        ...//变量声明初始化
		...//属性context初始化
        ...//环境变量初始化
		...//socket创建并将其写入到环境变量
        if (svc->ioprio_class != IoSchedClass_NONE) {
            if (android_set_ioprio(getpid(), svc->ioprio_class, svc->ioprio_pri)) {
                ERROR("Failed to set pid %d ioprio = %d,%d: %s\n",
                      getpid(), svc->ioprio_class, svc->ioprio_pri, strerror(errno));
            }
        }
		...//是否需要控制台,输入输出是否重定向
        setpgid(0, getpid());
		...//设置gid,group,uid,seclabel等
		子进程中调用execve执行svc->args[0]{即对应的service},{如果传递了参数用传递的;如果没有用本身的svc->args}
    }
    if (pid < 0){}...//fork错误处理
    svc->time_started = gettime();
    svc->pid = pid;
    svc->flags |= SVC_RUNNING;
    if (properties_inited())
        //如果属性初始化完毕,则设置"init.svc.{$service_name}" "running"
        notify_service_state(svc->name, "running");
}

3 循环中的其他逻辑{bootchart除外,property部分除外}

抽离后,仅关注如下关键部分代码即可,如下所示:

queue_builtin_action(keychord_init_action, "keychord_init");//这里将action{keychord_init}添加到action list中
queue_builtin_action(signal_init_action, "signal_init");//这里将action{signal_init}添加到action list中
...
for(;;) {
    /**
	  execute_one_command()
	  这里执行循环执行到相应action的command时,会分别执行keychord_init_action和signal_init_action
	  signal_init_action->signal_init()
	  keychord_init_action->keychord_init()
	  */
    execute_one_command(); //关键语句1
    restart_processes();   //关键语句2
	{
		...
		if (!signal_fd_init && get_signal_fd() > 0) {//只有第一次会进入,ufds初始化工作
            ufds[fd_count].fd = get_signal_fd();
            ufds[fd_count].events = POLLIN;
            ufds[fd_count].revents = 0;
            fd_count++;
            signal_fd_init = 1;
        }
        if (!keychord_fd_init && get_keychord_fd() > 0) {//只有第一次会进入,ufds初始化工作
            ufds[fd_count].fd = get_keychord_fd();
            ufds[fd_count].events = POLLIN;
            ufds[fd_count].revents = 0;
            fd_count++;
            keychord_fd_init = 1;
        }
		...
		nr = poll(ufds, fd_count, timeout);
        if (nr <= 0)
            continue;
        for (i = 0; i < fd_count; i++) {
            if (ufds[i].revents & POLLIN) {//poll到事件
                ...
				if (ufds[i].fd == get_keychord_fd())//组合按键事件
                    handle_keychord();//关键点1,处理keychord
                else if (ufds[i].fd == get_signal_fd())//子进程挂掉信号
                    handle_signal();//关键点2,处理signal
            }
        }
		...
	}
}

3.1 处理组合按键handle_keychord的流程

{关于keychords处理,关键分析两个方法,keychords_init和handle_keychords}
keychords本身是组合按键,多点触控的意思,实现的功能主要是,满足组合按键的要求,就会触发特定service的启动
3.1.1 keychords_init的实现如下:

void keychord_init()
{
    int fd, ret;
	/**
	 service_for_each
	 遍历整个service_list列表,对每个svc均执行add_service_keycodes操作
	 */
    service_for_each(add_service_keycodes);
    if (!keychords)/* nothing to do if no services require keychords */
        return;
    fd = open("/dev/keychord", O_RDWR);
	...//open错误处理
    fcntl(fd, F_SETFD, FD_CLOEXEC);//确保子进程无法使用该fd
    ret = write(fd, keychords, keychords_length);//向驱动写入长度为keychords_length的keychords
    ...//write错误处理
    free(keychords);//使用后释放资源
    keychords = 0;
    keychord_fd = fd;//当init中执行get_keychord_fd时获取该fd
}

这里继续专注于add_service_keycodes的实现,如下:

void add_service_keycodes(struct service *svc)
{
	...
    if (svc->keycodes) {//只要svc的keycodes不为空就add一个全新的keychord到list中
	    /**
		 对于一个svc,有nkeycodes个keycodes,nkeycodes个keycodes需要申请的空间即为svc->nkeycodes * sizeof(keychord->keycodes[0])
		 对于结构体struct input_keychord {
          __u16 version;
          __u16 id;
          __u16 count;
          __u16 keycodes[];//注意,该写法是动态数组,keycodes是可以根据申请的空间来扩充的
         };
		 整个keychord结构体的大小为sizeof(*keychord) + nkeycodes个keycodes需要申请的空间
		 */
        size = sizeof(*keychord) + svc->nkeycodes * sizeof(keychord->keycodes[0]);
        keychords = realloc(keychords, keychords_length + size);//realloc表示重新申请空间,基于原来申请空间的扩充
        ...
		//keychord的初始化,实际上就是对keychords上一段地址空间初始化
        keychord = (struct input_keychord *)((char *)keychords + keychords_length);
        keychord->version = KEYCHORD_VERSION;
        keychord->id = keychords_count + 1;
        keychord->count = svc->nkeycodes;
        svc->keychord_id = keychord->id;
        for (i = 0; i < svc->nkeycodes; i++) {
            keychord->keycodes[i] = svc->keycodes[i];
        }
        keychords_count++;
        keychords_length += size;
    }
}

3.1.2 handle_keychord实现如下:

void handle_keychord()
{
    struct service *svc;
    char adb_enabled[PROP_VALUE_MAX];
    int ret;
    __u16 id;
    // Only handle keychords if adb is enabled.
    property_get("init.svc.adbd", adb_enabled);//读取属性init.svc.adbd到adb_enabled中
    ret = read(keychord_fd, &id, sizeof(id));//从驱动中读取有效keychords
	...//容错处理
    if (!strcmp(adb_enabled, "running")) {
        svc = service_find_by_keychord(id);//通过keycordid获取对应的service
        if (svc) {
            INFO("starting service %s from keychord\n", svc->name);
            service_start(svc, NULL);//启动service
        } else {
            ERROR("service for keychord %d not found\n", id);
        }
    }
}

这里总结下:处理组合按键的核心:

  1. 遍历所有的service,查找包含keychords触发机制的service,并将这些service对应的keychords初始化并注册到驱动中
  2. 驱动不断返回组合按键,当满足了某个service的keychords条件,则启动该service

3.2 处理子进程挂掉handle_signal的流程

{关于signal处理,关键分析两个方法,signal_init和handle_signal}
3.2.1 signal_init实现如下:

void signal_init(void)
{
    int s[2];
    struct sigaction act;
    memset(&act, 0, sizeof(act));
    act.sa_handler = sigchld_handler;//sigchld_handler(s)->write(signal_fd, &s, 1);
    act.sa_flags = SA_NOCLDSTOP;
	
	/**
	 sigaction->handle_signal
	 这里注册了信号处理函数,即一旦收到子进程挂掉发出的SIGCHLD信号,就会执行sigchld_handler来向signal_fd中写入数据
	 相对于init的循环处理,就会监听到数据,通过ufds[i].fd == get_signal_fd()来触发handle_signal
	 */
    sigaction(SIGCHLD, &act, 0);
    /* create a signalling mechanism for the sigchld handler*/
    if (socketpair(AF_UNIX, SOCK_STREAM, 0, s) == 0) {//创建socketpair,s[0]和s[1]两者是一边读一边写,完成操作
        signal_fd = s[0];
        signal_recv_fd = s[1];
        fcntl(s[0], F_SETFD, FD_CLOEXEC);
        fcntl(s[0], F_SETFL, O_NONBLOCK);
        fcntl(s[1], F_SETFD, FD_CLOEXEC);
        fcntl(s[1], F_SETFL, O_NONBLOCK);
    }
	/**
	 handle_signal
     read(signal_recv_fd, tmp, sizeof(tmp));//阻塞读操作,
     while (!wait_for_one_process(0));
	 */
    handle_signal();
}

3.2.2 handle_signal的核心由wait_for_one_process来处理,它的实现如下:

static int wait_for_one_process(int block)
{
    ...//变量初始化
	/**
	 waitpid
	 方法说明:指定要等待的子进程{等待子进程结束,并获取到它的pid进程号,WNOHANG表明若没有进程结束,则立即返回}
	     pid>0表示子进程ID;pid=0表示当前进程组中的子进程;
		 pid=-1表示等待所有子进程;pid<-1表示进程组ID为pid绝对值的子进程。
	 */
    while ( (pid = waitpid(-1, &status, block ? 0 : WNOHANG)) == -1 && errno == EINTR );
    if (pid <= 0) return -1;
    INFO("waitpid returned pid %d, status = %08x\n", pid, status);
    svc = service_find_by_pid(pid);//通过pid查找对应服务
    ...//svc==NULL的错误处理
	/**
	  如果该服务进程没有设定SVC_ONESHOT标志,或者设置了SVC_RESTART标志,则先杀掉当前的进程,在重新创建新的进程;  
      以避免后面重启进程时,因当前服务进程已经存在而发生错误. 
	 */
    if (!(svc->flags & SVC_ONESHOT) || (svc->flags & SVC_RESTART)) {
        kill(-pid, SIGKILL);
        NOTICE("process '%s' killing any children in process group\n", svc->name);
    }
	...//遍历svc->sockets,释放该进程创建的socket资源
    svc->pid = 0;
	...//标志位处理{去掉SVC_RUNNING,对于SVC_ONESHOT,直接设置为SVC_DISABLED}
	...//且对于服务不需要自动重启则设置属性"init.svc.{$service_name}" "stopped"
	//总结性说明:一个服务进程在init.rc中只要没有声明SVC_ONESHOT和SVC_RESET标志,当该进程死亡时,就会被重启;
    .../**
         如果一个服务进程带有SVC_CRITICAL标志,且没有SVC_RESTART标志,当它crash、
         一定时间内重启的次数超过4此时,系统会自动重启并进入recovery模式
         */
    svc->flags &= (~SVC_RESTART);
    svc->flags |= SVC_RESTARTING;
    ...//如果svc服务中有onrestart选项,则遍历进程重启时需要执行的command列表,并执行
    notify_service_state(svc->name, "restarting");//设置属性"init.svc.{$service_name}" "restarting",供别处获取
    return 0;
}

处理子进程挂掉的核心:

  1. 通过挂掉的pid找到对应的svc,将僵尸进程kill掉,释放资源
  2. 根据标志位信息确认是否重启,如果重启则设置属性"init.svc.{$service_name}" "restarting",供别处获取
  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

图王大胜

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值