安卓之啓動-初析(三)

代碼解析

首先引入依賴文件init.rc,這個是init執行依賴的文件。還有uevetd.rc,其他rc文件我就不在這裏列舉了。關於以rc爲後綴的文件的說明請看,它裏面介紹了The Android Init Language consists of four broad classes ofstatements,

which are Actions, Commands, Services, and Optionsinit.h頭文件也有定義結構。

然後看一個重要的文件Makefile--Android.mk。我摘幾行重要的代碼:

要進行編譯的源文件

LOCAL_SRC_FILES:= \

         builtins.c \

         init.c \

         devices.c \

         property_service.c \

         util.c \

         parser.c \

         logo.c \

         keychords.c \

         signal_handler.c \

         init_parser.c \

         ueventd.c \

         ueventd_parser.c \

         watchdogd.c

ifeq ($(strip$(INIT_BOOTCHART)),true)  可能會編譯的文件條件是ifeq成立

LOCAL_SRC_FILES +=bootchart.c

endif

LOCAL_MODULE:= init 編譯出來的模塊,看見了嘛,這個就是那個可執行的init

# Make a symlink from /sbin/ueventdand /sbin/watchdogd to /init 創建一個符號鏈接從/sbin/ueventd /sbin/watchdogd /init

SYMLINKS :=\

    $(TARGET_ROOT_OUT)/sbin/ueventd\

$(TARGET_ROOT_OUT)/sbin/watchdogd

然後讓我們看一下init.c和其他文件

init.c 三個並列程序流程 uevent、watchdog、init

int main(intargc, char**argv)

{//init的入口,一切從這裏開始

   …………

   if(!strcmp(basename(argv[0]),"ueventd"))basename函數作用是去掉輸入string中的/。如果傳入參數去掉/不等於ueventd則執行ueventd_main

        return 1ueventd_main(argc,argv);

網上說這實際是init啓動的自己的進程,它的主要作用根據uevent是創建或刪除/dev/xxx(xxx設備名),我們知道在linux下面創建設備節點的接口mknod       

ueventd_main這個函數就是init啓動的自己的進程的入口。這個函數比較複雜,並且代碼涉及大部分底層API,我個人看起來還是比較吃力的,不過我儘量去給源碼加上自己合理的註釋,並且粘貼一些我在網上搜索出來的認爲比較好的說明。

1###################################################################################################

int ueventd_main(int argc, char **argv)

{//ueventd入口,在ueventd.c

 …………

   import_kernel_cmdline(0, import_kernel_nv);//從設備文件/dev/cmdline(內核讀取命令行參數方式)讀取hardware硬件名稱

   get_hardware_name(hardware, &revision);//變成軟件可識別硬件名字,因爲需要帶有設備號

   ueventd_parse_config_file("/ueventd.rc");//解析配置文件uevevtd.rc

   snprintf(tmp,sizeof(tmp),"/ueventd.%s.rc",hardware);   ueventd_parse_config_file(tmp); //解析ueventd.xxx.rc,xxx是剛剛獲取的hardware

device_init();//這個函數比較重要這裏做一個流程描述,由於代碼過多我就不粘貼出來了。

這裏是方法跳轉的順序流,且不是在同一個文件中,大家可以去查看相關源碼/system/core/init/*

devicd_init流程(|表示並列、||表示一種流程結束、|表示有先後關係,粗體字表示重要的方法)__start uevent_open_socket(註冊socketHandle,爲和內核打交道)(system/core/libcutils/uevetd.c)------->

   | coldboot("/sys/class");       |

    | coldboot("/sys/block");       |

    | coldboot("/sys/devices");     |--------> coldboot ----------->do_coldboot--->

    | handle_device_fd(重要的設備處理入口)|---> parse_event -------->

    |handle_firmware_event  |-------->process_firmware_event------>

    | open底層設備驅動open,打開驅動。firmware流程結束||

    |handle_device_event    |--------->

    | fixup_sys_perms   uevent->action={add,change} 安裝系統權限/sys下的------------>

    | handle_block_device_event  uevent->subsystem={block} 處理/dev/block下驅------>

    | parse_device_name------->parse_platform_block_device-------->handle_device--->

    | make_device ----->get_device_perm(設置設備權限)----->makedev(等同於linux創建設備                      MKDEV,創建設備dev_tdev)----->setegid(gid)(設置有效組id)------>mknod(path, mode, dev)(真正創建特殊設備文件)------>chown(path, uid, -1)(修改屬組)--->setegid(AID_ROOT)|

remove_link刪除文件(此方法在make_device後執行)||

    | handle_platform_device_event   uevent->subsystem={platform},處理/devices/platform-------> add_platform_device(添加/devices/platform下設備)------> remove_platform_device(移除相應設備)||

    | handle_generic_device_eventuevent->subsystem ={!platform,!block},處理常規的驅動和相應的驅動映射文件/dev,其中包含usbmnt,相機,input,圖形等不一一列舉,詳細看源代碼。--->

handle_device_fd(同上)||

回顧

至此這個device_init就講完了,流程比較複雜,現在我們在稍微回顧一下:

    首先註冊了一個套接字socket和內核互發消息(這個我沒太自信看,不是非常確認),之後觸發/sys/class,/sys/block,/sys/devices這三個目錄及其子目錄下的uevent,然後接受並創建設備節點,至此設備節點纔算創建。最終handle_device_fd這纔是真正處理驅動的入口的開始,會最終調用mknod創建設備節點或者open打開真正的驅動(firmware),流程如下:

handle_device_fd->handle_device_event-> make_device-> mknodfirmware不同,見上面)。

 

繼續下面代碼:

   ufd.events= POLLIN;

   ufd.fd =get_device_fd();

   while(1){

        ufd.revents = 0;

        nr = poll(&ufd, 1, -1);//Unix 經典poll模式和select模式相似

        if (nr <= 0)

            continue;

        if (ufd.revents == POLLIN)

               handle_device_fd();//接受kernel傳過來的uevent,動態創建或刪除節點,同上

   }

}

1###################################################################################################

   if(!strcmp(basename(argv[0]),"watchdogd"))同上

        return 2watchdogd_main(argc,argv);啓動watchdog,相關看門狗介紹可以google

   umask(0);//把文件掩碼設置爲空

   //以下是生成安卓系統中的一些基本的系統目錄並掛載對應的文件系統3它完成工作和ueventd相似,但是包含一些系統服務進程的啓動。

   mkdir("/dev",0755);

   mkdir("/proc",0755);

   mkdir("/sys",0755);

   mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755");

   mkdir("/dev/pts", 0755);

   mkdir("/dev/socket",0755);

   mount("devpts", "/dev/pts", "devpts", 0, NULL);

   mount("proc", "/proc", "proc", 0, NULL);

   mount("sysfs", "/sys", "sysfs", 0, NULL);

   close(open("/dev/.booting",O_WRONLY | O_CREAT, 0000));

   open_devnull_stdio();//生成類似linux/dev/null的設備

   klog_init();//日誌系統

   property_init();//打開/dev/__properties__,使之成爲共享區域,用來保存一些屬性

   get_hardware_name(hardware, &revision);//同上面講到的

   process_kernel_cmdline();//同上

#ifdef HAVE_SELINUX //是否啓用了安全linux,如果啓動了設置一些SELinux處理器

    union selinux_callback cb;

    cb.func_log = klog_write;

    selinux_set_callback(SELINUX_CB_LOG, cb);

    cb.func_audit = audit_callback;

    selinux_set_callback(SELINUX_CB_AUDIT, cb);

    if (selinux_android_load_policy() <0) {

            selinux_enabled = 0;        } else {

            selinux_init_all_handles();

    }

    restorecon("/dev");

    restorecon("/dev/socket");

#endif

   is_charger = !strcmp(bootmode, "charger");

   property_load_boot_defaults();//設置加載boot默認properties,放在共享區域

    // 解析 /init.rc 和 /init.$hardware.rc 腳本,其中 $hardware 參數從 /proc/cpuinfo 中讀取

   init_parse_config_file("/init.rc");//我們終於看到這個了init.rc

   //尋找 early-init 觸發器,加到 action_queue 

   action_for_each_trigger("early-init", action_add_queue_tail);

  //等待完成coldboot動作同上ueventd

   queue_builtin_action(wait_for_coldboot_done_action, "wait_for_coldboot_done");

   queue_builtin_action(keychord_init_action, "keychord_init");//keychord初始化

   queue_builtin_action(console_init_action, "console_init");//控制檯初始化

    //爲開始執行所有引導的actions,執行action(不管is_charger是什麼模式) initproperty_service_initsignal_initcheck_startupchargerqueue_property_triggers,非is_charger模式下下執行early-fsfspost-fspost-fs-dataearly-bootboot

   action_for_each_trigger("init", action_add_queue_tail);

    if(!is_charger) {

      action_for_each_trigger("early-fs",action_add_queue_tail);

      action_for_each_trigger("fs",action_add_queue_tail);

      action_for_each_trigger("post-fs",action_add_queue_tail);

      action_for_each_trigger("post-fs-data",action_add_queue_tail);

   }

   queue_builtin_action(property_service_init_action, "property_service_init");

   queue_builtin_action(signal_init_action, "signal_init");

   queue_builtin_action(check_startup_action, "check_startup");

   if(is_charger) {

        action_for_each_trigger("charger",action_add_queue_tail);

   } else{

        action_for_each_trigger("early-boot",action_add_queue_tail);

        action_for_each_trigger("boot", action_add_queue_tail);

   }

   queue_builtin_action(queue_property_triggers_action, "queue_property_triggers");

#if BOOTCHART //是否啓動bootchart_init,這裏就不介紹了

    queue_builtin_action(bootchart_init_action,"bootchart_init");

#endif

//init進程開始執行邏輯

   for(;;){

        int nr, i, timeout = -1;

        execute_one_command();//command是存放在隊列中的,具體actioncommandservice等信息定義在init.h頭中,我不一一詳細介紹了,可以查看相關文檔和代碼。但是這一個函數就是執行一條command,這個時候就啓動了如下的服務,摘自init.rc

service adbd /sbin/adbd  //adb

service servicemanager/system/bin/servicemanager //servicemanager

service surfaceflinger/system/bin/surfaceflinger//觸摸

service zygote/system/bin/app_process -Xzygote /system/bin --zygote --start-system-server  //zygote就在這個時候啓動的                         

        restart_processes();//重啓進程

        if (!property_set_fd_init &&get_property_set_fd() > 0) {//啓動相關計數

            ufds[fd_count].fd =get_property_set_fd();

            ufds[fd_count].events = POLLIN;

            ufds[fd_count].revents = 0;

            fd_count++;

            property_set_fd_init = 1;

        }

        if (!signal_fd_init && get_signal_fd()> 0) {//信號處理相關計數

            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) {//keychord相關計數

            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);//開始進入poll模式,註冊各類handle,都很複雜

        if (nr <= 0)

            continue;

        for (i = 0; i < fd_count; i++) {

            if (ufds[i].revents == POLLIN) {

                if (ufds[i].fd == get_property_set_fd())

                    handle_property_set_fd();

                else if (ufds[i].fd ==get_keychord_fd())

                    handle_keychord();

                else if (ufds[i].fd ==get_signal_fd())

                    handle_signal();

            }

        }

   }

   return0;

}

 

回顧

         至此init相關的代碼我們就差不多講完了,大家都看了init在安卓啓動過程中都做了些什麼,而且都是怎麼做的。

         我稍作說明:

         引自ueventd.rc

    /dev/binder               0666   root      root

    看到binder驅動映射的時候了吧,呵呵。

         引自init.rc

    serviceadbd /sbin/adbd 

         service servicemanager/system/bin/servicemanager

         service zygote /system/bin/app_process-Xzygote /system/bin --zygote --start-system-server

看到adb啓動了吧,看到servicemanager啓動了吧,看到zygote啓動了吧。所以我們的疑惑是不是打開了呢,我們不管底層linux做了什麼有一些什麼,但是我們經常會聽到別人提前什麼binder驅動binder通訊,什麼zygoteservicemanager等,今天我們終於知道它是在init的時候啓動的了。並且按照我上面代碼流程,你會在源文件中看見詳細的啓動過程。
發佈了41 篇原創文章 · 獲贊 6 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章