android中init過程解析

在Android系統啓動時,內核引導參數上一般都會設置“init=/init”,這樣的話,如果內核成功掛載了這個文件系統之後,首先運行的就是這個根目錄下的init程序。這個程序所了什麼呢? 我們只有RFSC(Read the Fucking Source code)!! init程序源碼在Android官方源碼的system/core/init中,main在init.c裏。我們的分析就從main開始

init:(1)安裝SIGCHLD信號。(如果父進程不等待子進程結束,子進程將成爲殭屍進程(zombie)從而佔用系統資源。因此需要對SIGCHLD信號做出處理,回收殭屍進程的資源,避免造成不必要的資源浪費

static void sigchld_handler(int s)
        {
                write(signal_fd, &s, 1);
        }

        int main(int argc, char **argv)
        {
                act.sa_handler = sigchld_handler;
                act.sa_flags = SA_NOCLDSTOP;
                sigaction(SIGCHLD, &act, 0);
                struct sigaction act;
                act.sa_handler = sigchld_handler;
                act.sa_flags = SA_NOCLDSTOP;
                ………………………………………..
        }

Linux進程通過互相發送接收消息來實現進程間的通信,這些消息被稱爲“信號”。每個進程在處理其他進程發送的信號時都需要註冊程序,此程序被稱爲信號處理。當進程的運行狀態改變或者終止時,就會產生某種信號,init進程是所有進程的父進程,當其子進程終止產生SIGCHLD信號時,init進程需要調用信號安裝函數sigaction(),並通過參數傳遞至sigcation結構體中,已完成信號處理器的安裝。

Init進程通過上述代碼註冊與子進程相關的SIGCHLD信號處理器,並把sigcation結構體的sa_flags設置爲SA_NOCLDSTOP,該值表示僅當進程終止時才接收SIGCHLD信號。 sigchld_handler函數用於通知全局變量signal_fd,SIGCHLD信號已發生。對於產生的信號的實際處理,在init進程的事件處理循環中進行。

(2)對umask進行清零。

何爲umask,請看http://www.szstudy.cn/showArticle/53978.shtml

umask是什麼?
        當我們登錄系統之後創建一個文件總是有一個默認權限的,那麼這個權限是怎麼來的呢?這就是umask乾的事情。umask設置了用戶創建文件的默認權限,它與chmod的效果剛好相反,umask設置的是權限“補碼”,而chmod設置的是文件權限碼。一般在/etc/profile、$ [HOME]/.bash_profile或$[HOME]/.profile中設置umask值。

如何計算umask值?
        umask命令允許你設定文件創建時的缺省模式,對應每一類用戶(文件屬主、同組用戶、其他用戶)存在一個相應的umask值中的數字。對於文件來說,這一數字的最大值分別是6。系統不允許你在創建一個文本文件時就賦予它執行權限,必須在創建後用chmod命令增加這一權限。目錄則允許設置執行權限,這樣針對目錄來說,umask中各個數字最大可以到7。

該命令的一般形式爲:umask nnn
        其中nnn爲umask置000 - 777。
        如:umask值爲022,則默認目錄權限爲755,默認文件權限爲644。

(3)爲rootfs建立必要的文件夾,並掛載適當的分區。

        /dev                 (        tmpfs        )
                /dev/pts                          ( devpts )
                                                /dev/socket
                /proc                                  ( proc )
        /sys (sysfs)

編譯Android系統源碼時,在生成的根文件系統中,不存在/dev,/proc/,/sys這類目錄,他們是系統運行時的目錄,有init進程在運行中生成,當系統終止時,他們就會消失。

Init進程執行後,生成/dev目錄,包含系統使用的設備,而後調用open_devnull_stdio();函數,創建運行日誌輸出設備。open_devnull_stdio()函數會在/dev目錄下生成__null__設備節點文件,並將標準輸入,標準輸出,標準錯誤,標準錯誤輸出全部重定向__null__設備中。

void open_devnull_stdio(void)
        {
                int fd;
                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 >= 0) {
                                dup2(fd, 0);
                                dup2(fd, 1);
                                dup2(fd, 2);
                                if (fd > 2) {
                                        close(fd);
                                }
                                return;
                        }
                }

                exit(1);
        }

(4)創建/dev/null和/dev/kmsg節點。

init進程通過log_init函數,生成"/dev/__kmsg__"設備節點文件。__kmsg__設備調用內核信息輸出函數printk(),init進程即是通過該函數輸出log信息。

void log_init(void)
        {
                static const char *name = "/dev/__kmsg__";
                if (mknod(name, S_IFCHR | 0600, (1 << 8) | 11) == 0) {
                        log_fd = open(name, O_WRONLY);
                        fcntl(log_fd, F_SETFD, FD_CLOEXEC);
                        unlink(name);
                }
        }

Init進程通過__kmsg__設備定義用於輸出信息的宏。關於宏輸出信息,可以使用dmesg實用程序進行確認,dmesg用於顯示內核信息。
        #define ERROR(x...) log_write(3, "<3>init: " x)
        #define NOTICE(x...) log_write(5, "<5>init: " x)
        #define INFO(x...) log_write(6, "<6>init: " x)

(5)解析/init.rc,將所有服務和操作信息加入鏈表

parse_config_file("/init.rc");
        parse_config_file()函數用來分析*.rc配置文件,用來指定init.rc文件的路徑。執行parse_config_file函數,讀取並分析init.rc文件後,生成服務列表與動作列表。動作列表與服務列表全部會以鏈表的形式註冊到service_list和action_list中,service_list和action_list是init進程中聲明的全局結構體。

(6) 初始化qemu設備,設置模擬器環境;從/proc/cmdline中提取信息內核啓動參數,並保存到全局變量。

qemu_init();
        QEMU模擬器允許Android應用開發者在缺少Android實際設備的情況下運行處於開發中的應用程序。QEMU是面向PC的開源模擬器,能夠模擬具有特定處理器設備,此外還提供虛擬網絡,視頻設備等。Android模擬器是虛擬的硬件平臺,運行在模擬器的軟件可以執行ARM命令集,LCD,相機,SD卡控制器等硬件設備都可以運行在Goldfish這種虛擬平臺上。

import_kernel_cmdline(0);
        static void import_kernel_cmdline(int in_qemu)
        {
                char cmdline[1024];
                char *ptr;
                int fd;

                fd = open("/proc/cmdline", O_RDONLY);
                if (fd >= 0) {
                        int n = read(fd, cmdline, 1023);
                        if (n < 0) n = 0;

                        /* get rid of trailing newline, it happens */
                        if (n > 0 && cmdline[n-1] == '\n') n--;

                        cmdline[n] = 0;
                        close(fd);
                } else {
                        cmdline[0] = 0;
                }

                ptr = cmdline;
                while (ptr && *ptr) {
                        char *x = strchr(ptr, ' ');
                        if (x != 0) *x++ = 0;
                        import_kernel_nv(ptr, in_qemu);
                        ptr = x;
                }

(7)先從上一步獲得的全局變量中獲取信息硬件信息和版本號,如果沒有則從/proc/cpuinfo中提取,並保存到全局變量。

(8)根據硬件信息選擇一個/init.(硬件).rc,並解析,將服務和操作信息加入鏈表。
        在G1的ramdisk根目錄下有兩個/init.(硬件).rc:init.goldfish.rc和init.trout.rc,init程序會根據上一步獲得的硬件信息選擇一個解析。

(9)執行鏈表中帶有“early-init”觸發的的命令。

action_for_each_trigger("early-init", action_add_queue_tail);觸發在init腳本文件中名字爲early-init的action,並且執行其commands,其實是: on early-init,在我們的init.rc中是沒有的。action_for_each_trigger函數會將第一個參數中的命令保存到action_add_queue_tail,而後通過drain_action_queue()函數將運行隊列中的命令逐一取出執行。

(10)遍歷/sys文件夾, 將這些目錄下的uevent 文件找出,並使kernel 重新生成那些在init 的設備管理器開始前的設備添加事件。 初始化動態設備管理,使內核產生設備添加事件(爲了自動產生設備節點), 設備文件有變化時反應給內核。

device_fd = device_init();

        int device_init(void)
        {
                suseconds_t t0, t1;
                int fd;

                fd = open_uevent_socket();

                if(fd < 0)
                return -1;

                fcntl(fd, F_SETFD, FD_CLOEXEC);
                fcntl(fd, F_SETFL, O_NONBLOCK);

                t0 = get_usecs();
                coldboot(fd, "/sys/class");
                coldboot(fd, "/sys/block");
                coldboot(fd, "/sys/devices");
                t1 = get_usecs();

                log_event_print("coldboot %ld uS\n", ((long) (t1 - t0)));

                make_device("/dev/pvrsrvkm", 0, 240, 0);
        #if 0
                make_device("/dev/bc_example", 0, 242, 0);
        #endif

        #if 1
                make_device("/dev/fb0", 0, 29, 0);
                make_device("/dev/fb1", 0, 29, 1);
                make_device("/dev/fb2", 0, 29, 2);
                make_device("/dev/fb3", 0, 29, 3);
                make_device("/dev/fb4", 0, 29, 4);
        #endif
                return fd;
        }

(11)初始化屬性系統,並導入初始化屬性文件。

初始化屬性服務器,Actually the property system is working as share memory.Logically it looks like a registry under windows system。
        首先創建一個名字爲system_properties的匿名共享內存區域,對並本init進程做mmap讀寫映射,其餘共享它 的進程只有讀的權限。然後將這個prop_area結構體通過全局變量__system_property_area__傳遞給property services。
        接着調用函數load_properties_from_file(PROP_PATH_RAMDISK_DEFAULT)從/default.prop文件中加載編譯時生成的屬性。這個有點像Windows 下的註冊表的作用。
         在Android系統中,所有的進程共享系統設置值,爲此提供了一個名稱爲屬性的保存空間。Init進程調用property_init()函數,在共享內存區域中,創建並初始化屬性域。而後通過執行中的進程鎖提供的API,訪問屬性中的設置值。但更改屬性值只能在init進程中進行。當修改屬性值時,要預先向init進程提交值變更申請,然後init進程處理該申請,並修改屬性值。

(12)從屬性系統中得到ro.debuggable,如果ro.debuggable 爲1 ,則初始化組合鍵(keychord )監聽

這段代碼是從屬性裏獲取調試標誌,如果是可以調試,就打開組合按鍵輸入驅動程序,初始化keychord 監聽。

// only listen for keychords if ro.debuggable is true
        debuggable = property_get("ro.debuggable");
        if (debuggable && !strcmp(debuggable, "1")) {
        keychord_fd = open_keychord();
        }

(13)打開console,如果cmdline中沒有指定console則打開默認的/dev/console。

if (console[0]) {
                snprintf(tmp, sizeof(tmp), "/dev/%s", console);
                console_name = strdup(tmp);
        }
        //打開console,如果cmdline 中沒有指定console 則打開默認的/dev/console
        fd = open(console_name, O_RDWR);
        if (fd >= 0)
        have_console = 1;
        close(fd);

(14)讀取/initlogo.rle, 是一張565 rle 壓縮的位圖,如果成功則在/dev/fb0 顯示Logo, 如果失敗則將/dev/tty0 設爲TEXT 模式並打開/dev/tty0, 輸出文本的ANDROID 字樣。

load_565rle_image(INIT_IMAGE_FILE)函數將加載由參數傳遞過來的圖像文件,而後將該文件顯示在LCD屏幕上。如果想更改logo,只需修改INIT_IMAGE_FILE即可。由於函數只支持rle565格式圖像的顯示,再更改圖像時,注意所選圖像文件的格式。

(15) 這段代碼是用來判斷是否使用模擬器運行,如果是,就加載內核命令行參數。

if (qemu[0])
        import_kernel_cmdline(1);

(16)這段代碼是根據內核命令行參數來設置工廠模式測試,比如在工廠生產手機過程裏需要自動化演示功能,就可以根據這個標誌來進行特別處理。

if (!strcmp(bootmode,"factory"))
        property_set("ro.factorytest", "1");
        else if (!strcmp(bootmode,"factory2"))
        property_set("ro.factorytest", "2");
        else
        property_set("ro.factorytest", "0");
        //這段代碼是設置手機序列號到屬性裏保存,以便上層應用程序可以識別這臺手機。
        property_set("ro.serialno", serialno[0] ? serialno : "");
        //這段代碼是保存啓動模式到屬性裏。
        property_set("ro.bootmode", bootmode[0] ? bootmode : "unknown");
        //這段代碼是保存手機基帶頻率到屬性裏。
        property_set("ro.baseband", baseband[0] ? baseband : "unknown");
        //這段代碼是保存手機硬件載波的方式到屬性裏。
        property_set("ro.carrier", carrier[0] ? carrier : "unknown");
        //保存引導程序的版本號到屬性裏,以便系統知道引導程序有什麼特性。
        property_set("ro.bootloader", bootloader[0] ? bootloader : "unknown");
        //這裏是保存硬件信息到屬性裏,其實就是獲取CPU 的信息。
        property_set("ro.hardware", hardware);
        //這裏是保存硬件修訂的版本號到屬性裏,這樣可以方便應用程序區分不同的硬件版本。
        snprintf(tmp, PROP_VALUE_MAX, "%d", revision);
        property_set("ro.revision", tmp);
        /*

(17)執行所有觸發標識爲init的action。

/* execute all the boot actions to get us started */
        //執行所有觸發標誌爲init 的action
        action_for_each_trigger("init", action_add_queue_tail);
        drain_action_queue();

(18)開始property服務

/* read any property files on system or data and
        * fire up the property service. This must happen
        * after the ro.foo properties are set above so
        * that /data/local.prop cannot interfere with them.
        */
        /*

開始property 服務,讀取一些property 文件,這一動作必須在前面那些ro.foo 設置後做,
        以便/data/local.prop 不能干預到他們
        - /system/build.prop
        - /system/default.prop
        - /data/local.prop
        - 在讀取認識的property 後讀取presistent propertie,在/data/property 中
        這段代碼是加載system 和data 目錄下的屬性,並啓動屬性監聽服務。

(19)為sigchld handler創建信號機制。

property_set_fd = start_property_service();

(20)創建一個全雙工的通訊機制的兩個SOCKET

/*
        這段代碼是創建一個全雙工的通訊機制的兩個SOCKET,信號可以在signal_fd
        和signal_recv_fd 雙向通訊,從而建立起溝通的管道。其實這個信號管理,就
        是用來讓init 進程與它的子進程進行溝通的,子進程從signal_fd 寫入信息,init
        進程從signal_recv_fd 收到信息,然後再做處理。
        */
        /* create a signalling mechanism for the sigchld handler */
        if (socketpair(AF_UNIX, SOCK_STREAM, 0, s) == 0) {
        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);
        }
        /* make sure we actually have all the pieces we need */
        /*

(21)這段代碼是判斷關鍵的幾個組件是否成功初始化,主要就是設備文件系統是否成功初始化,屬性服務是否成功初始化,信號通訊機制是否成功初始化。

if ((device_fd < 0) ||
        (property_set_fd < 0) ||
        (signal_recv_fd < 0)) {
        ERROR("init startup failure\n");
        return 1;
        }

(22) 執行所有觸發標識爲early-boot的

action action_for_each_trigger("early-boot", action_add_queue_tail);
        //執行所有觸發標誌爲boot 的action
        action_for_each_trigger("boot", action_add_queue_tail);
        drain_action_queue();

(23)基於當前property狀態,執行所有觸發標識爲property的action

//這段代碼是根據當前屬性,運行屬性命令。
        queue_all_property_triggers();
        drain_action_queue();
        /* enable property triggers */
        // 標明屬性觸發器已經初始化完成。
        property_triggers_enabled = 1;

        /*

這段代碼是保存三個重要的服務socket,以便後面輪詢使用。

*/
        ufds[0].fd = device_fd;
        ufds[0].events = POLLIN;
        ufds[1].fd = property_set_fd;
        ufds[1].events = POLLIN;
        ufds[2].fd = signal_recv_fd;
        ufds[2].events = POLLIN;
        fd_count = 3;

//這段代碼是判斷是否處理組合鍵輪詢。

ufds[3].events = POLLIN;
        fd_count++;
        } else {
        ufds[3].events = 0;
        ufds[3].revents = 0;
        }
        //如果支持BOOTCHART,則初始化BOOTCHART
        #if BOOTCHART
        /*

這段代碼是初始化linux 程序啓動速度的性能分析工具,這個工具有一個好處,就是圖形化顯示每個進程啓動順序和佔用時間,如果想優化系統的啓動速度,記得啓用這個工具。

*/
        bootchart_count = bootchart_init();
        if (bootchart_count < 0) {
        ERROR("bootcharting init failure\n");
        } else if (bootchart_count > 0) {
        NOTICE("bootcharting started (period=%d ms)\n",
        bootchart_count*BOOTCHART_POLLING_MS);
        } else {
        NOTICE("bootcharting ignored\n");
        }
        #endif
        /*

進入主進程循環:

- 查詢action 隊列,並執行。
        - 重啓需要重啓的服務
        - 輪詢註冊的事件
        - 如果signal_recv_fd 的revents 爲POLLIN,則得到一個信號,獲取並處理
        - 如果device_fd 的revents 爲POLLIN,調用handle_device_fd
        - 如果property_fd 的revents 爲POLLIN,調用handle_property_set_fd
        - 如果keychord_fd 的revents 爲POLLIN,調用handle_keychord
        這段代碼是進入死循環處理,以便這個init 進程變成一個服務。
        */
        for(;;) {
        int nr, i, timeout = -1;
        // 清空每個socket 的事件計數。
        for (i = 0; i < fd_count; i++)
        ufds[i].revents = 0;
        // 這段代碼是執行隊列裏的命令。
        drain_action_queue();
        // 這句代碼是用來判斷那些服務需要重新啓動。
        restart_processes();
        // 這段代碼是用來判斷哪些進程啓動超時。
        if (process_needs_restart) {
        timeout = (process_needs_restart - gettime()) * 1000;
        if (timeout < 0)
        timeout = 0;
        }
        #if BOOTCHART

//這段代碼是用來計算運行性能。

if (bootchart_count > 0) {
        if (timeout < 0 || timeout > BOOTCHART_POLLING_MS)
        timeout = BOOTCHART_POLLING_MS;
        if (bootchart_step() < 0 || --bootchart_count == 0) {
        bootchart_finish();
        bootchart_count = 0;
        }
        }
        #endif

// 這段代碼用來輪詢幾個socket 是否有事件處理。

nr = poll(ufds, fd_count, timeout);
        if (nr <= 0)
        continue;
        /*

這段代碼是用來處理子進程的通訊,並且能刪除任何已經退出或者殺死進程,這樣做可以保持系統更加健壯性,增強容錯能力。

*/
        if (ufds[2].revents == POLLIN) {
        /* we got a SIGCHLD - reap and restart as needed */
        read(signal_recv_fd, tmp, sizeof(tmp));
        while (!wait_for_one_process(0))
        ;
        continue;
        }
        // 這段代碼是處理設備事件。
        if (ufds[0].revents == POLLIN)
        handle_device_fd(device_fd);
        // 這段代碼是處理屬性服務事件。
        if (ufds[1].revents == POLLIN)
        handle_property_set_fd(property_set_fd);
        // 這段代碼是處理調試模式下的組合按鍵。
        if (ufds[3].revents == POLLIN)
        handle_keychord(keychord_fd);
        }
        return 0;
        }


http://www.embedu.org/Column/Column703.htm

發佈了27 篇原創文章 · 獲贊 8 · 訪問量 9萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章