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機制來同時輪詢它們,以即可以提升效率。

相關文章
相關標籤/搜索