Android系统的开机画面显示过程分析(5)

       2. 第二个开机画面的显示过程sql

      因为第二个开机画面是在init进程启动的过程当中显示的,所以,咱们就从init进程的入口函数main开始分析第二个开机画面的显示过程。
      init进程的入口函数main实如今文件system/core/init/init.c中,以下所示:
  
  
  
  
  1. int main(int argc, char **argv)   
  2. {   
  3.     int fd_count = 0;   
  4.     struct pollfd ufds[4];   
  5.     ......   
  6.     int property_set_fd_init = 0;   
  7.     int signal_fd_init = 0;   
  8.     int keychord_fd_init = 0;   
  9.    
  10.     if (!strcmp(basename(argv[0]), "ueventd"))   
  11.         return ueventd_main(argc, argv);   
  12.    
  13.     ......   
  14.    
  15.     queue_builtin_action(console_init_action, "console_init");   
  16.    
  17.     ......   
  18.    
  19.     for(;;) {   
  20.         int nr, i, timeout = -1;   
  21.    
  22.         execute_one_command();   
  23.         restart_processes();   
  24.    
  25.         if (!property_set_fd_init && get_property_set_fd() > 0) {   
  26.             ufds[fd_count].fd = get_property_set_fd();   
  27.             ufds[fd_count].events = POLLIN;   
  28.             ufds[fd_count].revents = 0;   
  29.             fd_count++;   
  30.             property_set_fd_init = 1;   
  31.         }   
  32.         if (!signal_fd_init && get_signal_fd() > 0) {   
  33.             ufds[fd_count].fd = get_signal_fd();   
  34.             ufds[fd_count].events = POLLIN;   
  35.             ufds[fd_count].revents = 0;   
  36.             fd_count++;   
  37.             signal_fd_init = 1;   
  38.         }   
  39.         if (!keychord_fd_init && get_keychord_fd() > 0) {   
  40.             ufds[fd_count].fd = get_keychord_fd();   
  41.             ufds[fd_count].events = POLLIN;   
  42.             ufds[fd_count].revents = 0;   
  43.             fd_count++;   
  44.             keychord_fd_init = 1;   
  45.         }   
  46.    
  47.         if (process_needs_restart) {   
  48.             timeout = (process_needs_restart - gettime()) * 1000;   
  49.             if (timeout < 0)   
  50.                 timeout = 0;   
  51.         }   
  52.    
  53.         if (!action_queue_empty() || cur_action)   
  54.             timeout = 0;   
  55.    
  56.         ......   
  57.    
  58.         nr = poll(ufds, fd_count, timeout);   
  59.         if (nr <= 0)   
  60.             continue;   
  61.    
  62.         for (i = 0; i < fd_count; i++) {   
  63.             if (ufds[i].revents == POLLIN) {   
  64.                 if (ufds[i].fd == get_property_set_fd())   
  65.                     handle_property_set_fd();   
  66.                 else if (ufds[i].fd == get_keychord_fd())   
  67.                     handle_keychord();   
  68.                 else if (ufds[i].fd == get_signal_fd())   
  69.                     handle_signal();   
  70.             }   
  71.         }   
  72.     }   
  73.    
  74.     return 0;   
  75. }   
         函数一开始就首先判断参数argv[0]的值是否等于“ueventd”,即当前正在启动的进程名称是否等于“ueventd”。若是是的话,那么就以ueventd_main函数来做入口函数。这是怎么回事呢?当前正在启动的进程不是init吗?它的名称怎么可能会等于“ueventd”?原来,在目标设备上,可执行文件/sbin/ueventd是可执行文件/init的一个符号连接文件,即应用程序ueventd和init运行的是同一个可执行文件。内核启动完成以后,可执行文件/init首先会被执行,即init进程会首先被启动。init进程在启动的过程当中,会对启动脚本/init.rc进行解析。在启动脚本/init.rc中,配置了一个ueventd进程,它对应的可执行文件为/sbin/ueventd,即ueventd进程加载的可执行文件也为/init。所以,经过判断参数argv[0]的值,就能够知道当前正在启动的是init进程仍是ueventd进程。        
          ueventd进程是做什么用的呢?它是用来处理uevent事件的,即用来管理系统设备的。从前面的描述能够知道,它真正的入口函数为ueventd_main,实如今system/core/init/ueventd.c中。ueventd进程会经过一个socket接口来和内核通讯,以即可以监控系统设备事件。例如,在前面 在Ubuntu上为Android系统编写Linux内核驱动程序 一文中, 咱们调用device_create函数来建立了一个名称为“hello”的字符设备,这时候内核就会向前面提到的socket发送一个设备增长事件。ueventd进程经过这个socket得到了这个设备增长事件以后,就会/dev目录下建立一个名称为“hello”的设备文件。这样用户空间的应用程序就能够经过设备文件/dev/hello来和驱动程序hello进行通讯了。

        接下来调用另一个函数queue_builtin_action来向init进程中的一个待执行action队列增长了一个名称等于“console_init”的action。这个action对应的执行函数为console_init_action,它就是用来显示第二个开机画面的。socket

        函数queue_builtin_action实如今文件system/core/init/init_parser.c文件中,以下所示:ide

  
  
  
  
  1. static list_declare(action_list);   
  2. static list_declare(action_queue);   
  3.    
  4. void queue_builtin_action(int (*func)(int nargs, char **args), char *name)   
  5. {   
  6.     struct action *act;   
  7.     struct command *cmd;   
  8.    
  9.     act = calloc(1, sizeof(*act));   
  10.     act->name = name;   
  11.     list_init(&act->commands);   
  12.    
  13.     cmd = calloc(1, sizeof(*cmd));   
  14.     cmd->func = func;   
  15.     cmd->args[0] = name;   
  16.     list_add_tail(&act->commands, &cmd->clist);   
  17.    
  18.     list_add_tail(&action_list, &act->alist);   
  19.     action_add_queue_tail(act);   
  20. }   
  21.    
  22. void action_add_queue_tail(struct action *act)   
  23. {   
  24.     list_add_tail(&action_queue, &act->qlist);   
  25. }   

       action_list列表用来保存从启动脚本/init.rc解析获得的一系列action,以及一系列内建的action。当这些action须要执行的时候,它们就会被添加到action_queue列表中去,以便init进程能够执行它们。函数

 

       回到init进程的入口函数main中,最后init进程会进入到一个无限循环中去。在这个无限循环中,init进程会作如下五个事情:
ui

       A. 调用函数execute_one_command来检查action_queue列表是否为空。若是不为空的话,那么init进程就会将保存在列表头中的action移除,而且执行这个被移除的action。因为前面咱们将一个名称为“console_init”的action添加到了action_queue列表中,所以,在这个无限循环中,这个action就会被执行,即函数console_init_action会被调用。spa

       B. 调用函数restart_processes来检查系统中是否有进程须要重启。在启动脚本/init.rc中,咱们能够指定一个进程在退出以后会自动从新启动。在这种状况下,函数restart_processes就会检查是否存在须要从新启动的进程,若是存在的话,那么就会将它从新启动起来。rest

       C. 处理系统属性变化事件。当咱们调用函数property_set来改变一个系统属性值时,系统就会经过一个socket(经过调用函数get_property_set_fd能够得到它的文件描述符)来向init进程发送一个属性值改变事件通知。init进程接收到这个属性值改变事件以后,就会调用函数handle_property_set_fd来进行相应的处理。后面在分析第三个开机画面的显示过程时,咱们就会看到,SurfaceFlinger服务就是经过修改“ctl.start”和“ctl.stop”属性值来启动和中止第三个开机画面的。blog

       D. 处理一种称为“chorded keyboard”的键盘输入事件。这种类型为chorded keyboard”的键盘设备经过不一样的铵键组合来描述不一样的命令或者操做,它对应的设备文件为/dev/keychord。咱们能够经过调用函数get_keychord_fd来得到这个设备的文件描述符,以即可以监控它的输入事件,而且调用函数handle_keychord来对这些输入事件进行处理。接口

       E. 回收僵尸进程。咱们知道,在Linux内核中,若是父进程不等待子进程结束就退出,那么当子进程结束的时候,就会变成一个僵尸进程,从而占用系统的资源。为了回收这些僵尸进程,init进程会安装一个SIGCHLD信号接收器。当那些父进程已经退出了的子进程退出的时候,内核就会发出一个SIGCHLD信号给init进程。init进程能够经过一个socket(经过调用函数get_signal_fd能够得到它的文件描述符)来将接收到的SIGCHLD信号读取回来,而且调用函数handle_signal来对接收到的SIGCHLD信号进行处理,即回收那些已经变成了僵尸的子进程。队列

      注意,因为后面三个事件都是能够经过文件描述符来描述的,所以,init进程的入口函数main使用poll机制来同时轮询它们,以即可以提升效率。

相关文章
相关标签/搜索