深入Android系統(六)第一個使用者程序-Init程序

2020-10-26 12:01:32

十一假期有點墮落,無限火力有點上癮,謹戒、謹戒

Init程序Linux 核心啟動後建立的第一個使用者程序,地位非常重要。

Init程序在初始化過程中會啟動很多重要的守護行程,因此,瞭解Init程序的啟動過程有助於我們更好的理解Android系統。

在介紹Init程序前,我們先簡單介紹下Android的啟動過程。從系統角度看,Android的啟動過程可分為3個大的階段:

  • bootloader引導
  • 裝載和啟動Linux核心
  • 啟動Android系統,可分為
    • 啟動Init程序
    • 啟動Zygote
    • 啟動SystemService
    • 啟動SystemServer
    • 啟動Home
    • 等等…

我們看下啟動過程圖:
image

下面簡單介紹下啟動過程:

  1. Bootloader引導

當按下電源鍵開機時,最先執行的就是Bootloader

  • Bootloader的主要作用是初始化基本的硬體裝置(如 CPU、記憶體、Flash等)並且建立記憶體空間對映,為裝載Linux核心準備好合適的執行環境。
  • 一旦Linux核心裝載完畢,Bootloader將會從記憶體中清除掉
  • 如果在Bootloader執行期間,按下預定義的的組合鍵,可以進入系統的更新模組。Android的下載更新可以選擇進入Fastboot模式或者Recovery模式:
    • FastbootAndroid設計的一套通過USB來更新Android分割區映像的協定,方便開發人員快速更新指定分割區。
    • RecoveryAndroid特有的升級系統。利用Recovery模式可以進行恢復出廠設定,或者執行OTA、修補程式和韌體升級。進入Recovery模式實際上是啟動了一個文字模式的Linux
  1. 裝載和啟動Linux核心

Android 的 boot.img 存放的就是Linux核心和一個根檔案系統

  • Bootloader會把boot.img映像裝載進記憶體
  • 然後Linux核心會執行整個系統的初始化
  • 然後裝載根檔案系統
  • 最後啟動Init程序
  1. 啟動Init程序

Linux核心載入完畢後,會首先啟動Init程序,Init程序是系統的第一個程序

  • Init程序啟動過程中,會解析Linux的設定指令碼init.rc檔案。根據init.rc檔案的內容,Init程序會:
    • 裝載Android的檔案系統
    • 建立系統目錄
    • 初始化屬性系統
    • 啟動Android系統重要的守護行程,像USB守護行程adb守護行程vold守護行程rild守護行程
  • 最後,Init程序也會作為守護行程來執行修改屬性請求,重新啟動崩潰的程序等操作
  1. 啟動ServiceManager

ServiceManagerInit程序啟動。在Binder 章節已經講過,它的主要作用是管理Binder服務,負責Binder服務的註冊與查詢

  1. 啟動Zygote程序

Init程序初始化結束時,會啟動Zygote程序。Zygote程序負責fork出應用程序,是所有應用程序的父程序

  • Zygote程序初始化時會建立Android 虛擬機器器、預裝載系統的資原始檔和Java
  • 所有從Zygote程序fork出的使用者程序都將繼承和共用這些預載入的資源,不用浪費時間重新載入,加快的應用程式的啟動過程
  • 啟動結束後,Zygote程序也將變為守護行程,負責響應啟動APK的請求
  1. 啟動SystemServer

SystemServerZygote程序fork出的第一個程序,也是整個Android系統的核心程序

  • SystemServer中執行著Android系統大部分的Binder服務
  • SystemServer首先啟動本地服務SensorManager
  • 接著啟動包括ActivityManagerServiceWindowsManagerServicePackageManagerService在內的所有Java服務
  1. 啟動MediaServer

MediaServerInit程序啟動。它包含了一些多媒體相關的本地Binder服務,包括:CameraServiceAudioFlingerServiceMediaPlayerServiceAudioPolicyService

  1. 啟動Launcher
  • SystemServer載入完所有的Java服務後,最後會呼叫ActivityManagerServiceSystemReady()方法
  • SystemReady()方法中,會發出Intent<android.intent,category.HOME>
  • 凡是響應這個Intentapk都會執行起來,一般Launcher應用才回去響應這個Intent

Init程序的初始化過程

Init程序的原始碼目錄在system/core/init下。程式的入口函數main()位於檔案init.c

main()函數的流程

書中使用的是Android 5.0原始碼,相比Android 9.0這部分已經有很多改動,不過大的方向是一致的,只能對比著學習了。。。

main()函數比較長,整個Init程序的啟動流程都在這個函數中。由於涉及的點比較多,這裡我們先了解整體流程,細節後面補充,一點一點來哈

Init程序的main()函數的結構是這樣的:

int main(int argc, char** argv) {
    //啟動引數判斷部分
    
    if (is_first_stage) {
        //初始化第一階段部分
    }
    
    //初始化第二階段部分

    while (true) {
        //一個無限迴圈部分
    }

啟動程式引數判斷

進入main()函數後,首先檢查啟動程式的檔名

函數原始碼:

    if (!strcmp(basename(argv[0]), "ueventd")) {
        return ueventd_main(argc, argv);
    }
    if (!strcmp(basename(argv[0]), "watchdogd")) {
        return watchdogd_main(argc, argv);
    }
  • 如果檔名是ueventd,執行ueventd守護行程的主函數ueventd_main
  • 如果檔名是watchdogd,執行watchdogd守護行程的主函數watchdogd_main
  • 都不是,則繼續執行

才開始是不是就已經有些奇怪了,Init程序中還包含了另外兩個守護行程的啟動,這主要是因為這幾個守護行程的程式碼重合度高,開發人員乾脆都放在一起了。

我們看一下Android.mk中的片段:

# Create symlinks.
LOCAL_POST_INSTALL_CMD := $(hide) mkdir -p $(TARGET_ROOT_OUT)/sbin; \
    ln -sf ../init $(TARGET_ROOT_OUT)/sbin/ueventd; \
    ln -sf ../init $(TARGET_ROOT_OUT)/sbin/watchdogd
  • 在編譯時,Android生成了兩個指向init檔案的符號連結ueventdwatchdogd
  • 這樣,啟動時如果執行的是這兩個符號連結,main()函數就可以根據名稱判斷到底啟動哪一個

初始化的第一階段

設定檔案屬性掩碼

函數原始碼:

    // Clear the umask.
    umask(0);

預設情況下一個程序建立出的檔案合資料夾的屬性是022,使用umask(0)意味著程序建立的屬性是0777

mount相應的檔案系統

函數原始碼:

        // Get the basic filesystem setup we need put together in the initramdisk
        // on / and then we'll let the rc file figure out the rest.
        mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755");
        mkdir("/dev/pts", 0755);
        mkdir("/dev/socket", 0755);
        mount("devpts", "/dev/pts", "devpts", 0, NULL);
        #define MAKE_STR(x) __STRING(x)
        mount("proc", "/proc", "proc", 0, "hidepid=2,gid=" MAKE_STR(AID_READPROC));
        //......
        mount("sysfs", "/sys", "sysfs", 0, NULL);
        mount("selinuxfs", "/sys/fs/selinux", "selinuxfs", 0, NULL);
        //......
        // Mount staging areas for devices managed by vold
        // See storage config details at http://source.android.com/devices/storage/
        mount("tmpfs", "/mnt", "tmpfs", MS_NOEXEC | MS_NOSUID | MS_NODEV,
              "mode=0755,uid=0,gid=1000");
        // /mnt/vendor is used to mount vendor-specific partitions that can not be
        // part of the vendor partition, e.g. because they are mounted read-write.
        mkdir("/mnt/vendor", 0755);
        InitKernelLogging(argv);

建立一些基本的目錄,包括/dev/proc/sys等,同時把一些檔案系統,如tmpfsdevptprocsysfsmount到相應的目錄

  • tmpfs是一種基於記憶體的檔案系統,mount後就可以使用。
    • tmpfs檔案系統下的檔案都放在記憶體中,存取速度快,但是掉電丟失。因此適合存放一些臨時性的檔案
    • tmpfs檔案系統的大小是動態變化的,剛開始佔用空間很小,隨著檔案的增多會隨之變大
    • Androidtmpfs檔案系統mount/dev目錄。/dev目錄用來存放系統創造的裝置節點
  • devpts是虛擬終端檔案系統,通常mount/dev/pts目錄下
  • proc也是一種基於記憶體的虛擬檔案系統,它可以看作是核心內部資料結構的介面
    • 通過它可以獲得系統的資訊
    • 同時能夠在執行時修改特定的核心引數
  • sysfs檔案系統和proc檔案系統類似,它是在Linux 2.6核心引入的,作用是把系統裝置和匯流排按層次組織起來,使得他們可以在使用者空間存取

初始化kernelLog系統

通過InitKernelLogging()函數進行初始化,由於此時Androidlog系統還沒有啟動,所以Init只能使用kernellog系統

        // Now that tmpfs is mounted on /dev and we have /dev/kmsg, we can actually
        // talk to the outside world...
        InitKernelLogging(argv);

初始化SELinux

        // Set up SELinux, loading the SELinux policy.
        SelinuxSetupKernelLogging();
        SelinuxInitialize();

SELinux是在Android 4.3加入的安全核心,後面詳細介紹

初始化的第二階段

建立.booting空檔案

/dev目錄下建立一個空檔案.booting表示初始化正在進行

    // At this point we're in the second stage of init.
    InitKernelLogging(argv);
    LOG(INFO) << "init second stage started!";
    //......
    // Indicate that booting is in progress to background fw loaders, etc.
    close(open("/dev/.booting", O_WRONLY | O_CREAT | O_CLOEXEC, 0000));

大家留心註釋,我們已經處於初始化的第二階段了

  • is_booting()函數會依靠空檔案.booting來判斷是否程序處於初始化中
  • 初始化結束後這個檔案將被刪除

初始化Android的屬性系統

    property_init();

property_init()函數主要作用是建立一個共用區域來儲存屬性值,後面會詳細介紹

解析kernel引數並進行相關設定

    // If arguments are passed both on the command line and in DT,
    // properties set in DT always have priority over the command-line ones.
    process_kernel_dt();
    process_kernel_cmdline();

    // Propagate the kernel variables to internal variables
    // used by init as well as the current required properties.
    export_kernel_boot_props();

    // Make the time that init started available for bootstat to log.
    property_set("ro.boottime.init", getenv("INIT_STARTED_AT"));
    property_set("ro.boottime.init.selinux", getenv("INIT_SELINUX_TOOK"));

    // Set libavb version for Framework-only OTA match in Treble build.
    const char* avb_version = getenv("INIT_AVB_VERSION");
    if (avb_version) property_set("ro.boot.avb_version", avb_version);

這部分進行的是屬性的設定,我們看下幾個重點方法:

  • process_kernel_dt()函數:讀取裝置樹(DT)上的屬性設定資訊,查詢系統屬性,然後通過property_set設定系統屬性
  • process_kernel_cmdline()函數:解析kernelcmdline檔案提取以 androidboot.字串打頭的字串,通過property_set設定該系統屬性
  • export_kernel_boot_props()函數:額外設定一些屬性,這個函數中定義了一個集合,集合中定義的屬性都會從kernel中讀取並記錄下來

進行第二階段的SELinux設定

進行第二階段的SELinux設定並恢復一些檔案安全上下文

    // Now set up SELinux for second stage.
    SelinuxSetupKernelLogging();
    SelabelInitialize();
    SelinuxRestoreContext();

初始化子程序終止訊號處理常式

    epoll_fd = epoll_create1(EPOLL_CLOEXEC);
    if (epoll_fd == -1) {
        PLOG(FATAL) << "epoll_create1 failed";
    }

    sigchld_handler_init();

linux當中,父程序是通過捕捉SIGCHLD訊號來得知子程序執行結束的情況,SIGCHLD訊號會在子程序終止的時候發出。

  • 為了防止init的子程序成為殭屍程序(zombie process),init 在子程序結束時獲取子程序的結束碼
  • 通過結束碼將程式表中的子程序移除,防止成為殭屍程序的子程序佔用程式表的空間
  • 程式表的空間達到上限時,系統就不能再啟動新的程序了,這樣會引起嚴重的系統問題

設定系統屬性並開啟屬性服務

    property_load_boot_defaults();
    export_oem_lock_status();
    start_property_service();
    set_usb_controller();
  • property_load_boot_defaults()export_oem_lock_status()set_usb_controller()這三個函數都是呼叫、設定一些系統屬性
  • start_property_service():開啟系統屬性服務

載入init.rc檔案

init.rc是一個可設定的初始化檔案,在Android中被用作程式的啟動指令碼,它是run commands執行命令的縮寫

通常第三方客製化廠商可以設定額外的初始化設定:init.%PRODUCT%.rc。在init的初始化過程中會解析該組態檔,完成客製化化的設定過程。

init.rc檔案的規則和具體解析邏輯後面詳解,先看下它在main函數中的相關流程。

函數程式碼:

    const BuiltinFunctionMap function_map;
    Action::set_function_map(&function_map);

    subcontexts = InitializeSubcontexts();
    // 建立 action 相關物件
    ActionManager& am = ActionManager::GetInstance();
    // 建立 service 相關物件
    ServiceList& sm = ServiceList::GetInstance();
    // 載入並解析init.rc檔案到對應的物件中
    LoadBootScripts(am, sm);

解析完成後,會執行

    am.QueueEventTrigger("early-init");

    // Queue an action that waits for coldboot done so we know ueventd has set up all of /dev...
    // 等待冷插拔裝置初始化完成
    am.QueueBuiltinAction(wait_for_coldboot_done_action, "wait_for_coldboot_done");
    // ... so that we can start queuing up actions that require stuff from /dev.
    am.QueueBuiltinAction(MixHwrngIntoLinuxRngAction, "MixHwrngIntoLinuxRng");
    am.QueueBuiltinAction(SetMmapRndBitsAction, "SetMmapRndBits");
    am.QueueBuiltinAction(SetKptrRestrictAction, "SetKptrRestrict");
    // 初始化組合鍵監聽模組
    am.QueueBuiltinAction(keychord_init_action, "keychord_init");
    // 在螢幕上顯示 Android 字樣的Logo
    am.QueueBuiltinAction(console_init_action, "console_init");

    // Trigger all the boot actions to get us started.
    am.QueueEventTrigger("init");

    // Starting the BoringSSL self test, for NIAP certification compliance.
    am.QueueBuiltinAction(StartBoringSslSelfTest, "StartBoringSslSelfTest");

    // Repeat mix_hwrng_into_linux_rng in case /dev/hw_random or /dev/random
    // wasn't ready immediately after wait_for_coldboot_done
    am.QueueBuiltinAction(MixHwrngIntoLinuxRngAction, "MixHwrngIntoLinuxRng");

    // Don't mount filesystems or start core system services in charger mode.
    std::string bootmode = GetProperty("ro.bootmode", "");
    if (bootmode == "charger") {
        am.QueueEventTrigger("charger");
    } else {
        am.QueueEventTrigger("late-init");
    }

    // Run all property triggers based on current state of the properties.
    am.QueueBuiltinAction(queue_property_triggers_action, "queue_property_triggers");
  • am.QueueEventTrigger函數即表明到達了某個所需的某個時間條件
    • am.QueueEventTrigger("early-init")表明early-init條件觸發,對應的動作可以開始執行
    • 需要注意的是這個函數只是將時間點(如:early-init)填充進event_queue_執行佇列
    • 後面的while(true)迴圈才會真正的去按順序取出,並觸發相應的操作

到這裡,

  • init.rc相關的actionservice已經解析完成
  • 對應的列表也已經準備就緒
  • 對應的Trigger也已經新增完成

接下來就是執行階段了:

    while (true) {
        // 執行命令列表中的 Action
        if (!(waiting_for_prop || Service::is_exec_service_running())) {
            am.ExecuteOneCommand();
        }
        if (!(waiting_for_prop || Service::is_exec_service_running())) {
            if (!shutting_down) {
                // 啟動服務列表中的服務
                auto next_process_restart_time = RestartProcesses();
                //......
            }
            //......
        }
        
        // 監聽子程序的死亡通知訊號
        epoll_event ev;
        int nr = TEMP_FAILURE_RETRY(epoll_wait(epoll_fd, &ev, 1, epoll_timeout_ms));
        if (nr == -1) {
            PLOG(ERROR) << "epoll_wait failed";
        } else if (nr == 1) {
            ((void (*)()) ev.data.ptr)();
        }
    }

到這裡,main函數的整體流程就分析完了。分析過程中我們捨棄了很多細節,接下來就是填補細節的時候了。

啟動Service程序

main函數的while()迴圈中會呼叫RestartProcesses()來啟動服務列表中的服務程序,我們看下函數原始碼:

static std::optional<boot_clock::time_point> RestartProcesses() {
    //......
    //迴圈檢查每個服務
    for (const auto& s : ServiceList::GetInstance()) {
        // 判斷標誌位是否為 SVC_RESTARTING
        if (!(s->flags() & SVC_RESTARTING)) continue;
        // ......省略時間相關的判斷
        // 啟動服務程序
        auto result = s->Start();
    }
    //......
}

RestartProcesses()會檢查每個服務:凡是帶有SVC_RESTARTING標誌的,才會執行服務的啟動s->Start();

其實重點在s->Start();方法,我們具體來看下(刪減版):

Result<Success> Service::Start() {

    //......省略部分
    // 清空service相關標記
    // 判斷service狀態,如果已執行直接返回
    // 判斷service二進位制檔案是否存在
    // 初始化console、scon(安全上下文)等
    //......省略部分

    pid_t pid = -1;
    if (namespace_flags_) {// 當service定義了namespace時會賦值為CLONE_NEWPID|CLONE_NEWNS
        pid = clone(nullptr, nullptr, namespace_flags_ | SIGCHLD, nullptr);
    } else {
        pid = fork();
    }

    if (pid == 0) {// 子程序建立成功
        //......省略部分
        // setenv、writepid、重定向標準IO
        //......省略部分

        // As requested, set our gid, supplemental gids, uid, context, and
        // priority. Aborts on failure.
        SetProcessAttributes();

        if (!ExpandArgsAndExecv(args_)) {// 解析引數並啟動service
            PLOG(ERROR) << "cannot execve('" << args_[0] << "')";
        }
        // 不太懂這裡退出的目的是幹啥
        _exit(127);
    }

    if (pid < 0) {
        // 子程序建立失敗
        pid_ = 0;
        return ErrnoError() << "Failed to fork";
    }

    //......省略部分
    // 執行service其他引數的設定,如oom_score_adj、建立並設定ProcessGroup相關的引數
    //......省略部分
    NotifyStateChange("running");
    return Success();
}

Service程序的啟動流程還有很多細節,這部分只是簡單介紹下流程,涉及的sconPIDSIDPGID等東西還很多。

偷懶啦!能力、時間有限,先往下學習,待用到時再來啃吧

解析啟動指令碼init.rc

Init程序啟動時最重要的工作就是解析並執行啟動檔案init.rc官方說明檔案下載連結

init.rc檔案格式

init.rc檔案是以section為單位組織的

  • section分為兩大類:

    • action:以關鍵字on開始,表示一堆命令的集合
    • service:以關鍵字service開始,表示啟動某個程序的方式和引數
  • section以關鍵字onservice開始,直到下一個onservice結束

  • section中的註釋以#開始

打個樣兒:

import /init.usb.rc
import /init.${ro.hardware}.rc

on early-init
    mkdir /dev/memcg/system 0550 system system
    start ueventd

on init
    symlink /system/bin /bin
    symlink /system/etc /etc

on nonencrypted
    class_start main
    class_start late_start
    
on property:sys.boot_from_charger_mode=1
    class_stop charger
    trigger late-init

service ueventd /sbin/ueventd
    class core
    critical
    seclabel u:r:ueventd:s0
    shutdown critical

service flash_recovery /system/bin/install-recovery.sh
    class main
    oneshot

service ueventd /sbin/ueventd
    class core
    critical
    seclabel u:r:ueventd:s0
    shutdown critical

無論是action還是service,並不是按照檔案中的書寫順序執行的,執行與否以及何時執行要由Init程序在執行時決定。

對於init.rcaction

  • 關鍵字on後面跟的字串稱為trigger,如上面的early-initinit等。
  • trigger後面是命令列表,命令列表中的每一行就是一條命令。

對於init.rcservice

  • 關鍵字service後面是服務名稱,可以使用start服務名稱來啟動一個服務。如start ueventd
  • 服務名稱後面是程序的可執行檔案的路徑和啟動引數
  • service下面的行稱為option,每個option佔一行
    • 例如:class main中的class表示服務所屬的類別,可以通過class_start來啟動一組服務,像class_start main

想要了解更多,可以參考原始碼中的README檔案,路徑是system/core/init/README.md

init.rc的關鍵字

這部分是對system/core/init/README.md檔案的整理,挑重點記錄哈

Androidrc指令碼包含了4中型別的宣告:ActionCommandsServicesOptions

  • 所有的指令都以行為單位,各種符號則由空格隔開。
  • c語言風格的反斜槓\可用於在符號間插入空格
  • 雙引號""可用於防止字串被空格分割成多個記號
  • 行末的反斜槓\可用於折行
  • 註釋以#開頭
  • ActionServices用來申明一個分組
    • 所有的CommandsOptions都屬於最近宣告的分組
    • 位於第一個分組之前的CommandsOptions將被忽略

Actions

Actions是一組Commands的集合。每個Action都有一個trigger用來決定何時執行。當觸發條件與Actiontrigger匹配一致時,此Action會被加入到執行佇列的尾部

每個Action都會依次從佇列中取出,此Action的每個Command都將依次執行。

Actions格式如下:

on  < trigger >
    < command >
    < command >
    < command >

Services

通過Services定義的程式,會在Init中啟動,如果退出了會被重新啟動。

Services的格式如下:

service <name> <pathname> [ <argument> ]*
   <option>
   <option>
   ...

Options

OptionsServices的修訂項。它們決定了一個Service何時以及如何執行。

  • critical:表示這是一個關鍵的Service,如果Service4分鐘內重新啟動超過4次,系統將自動重新啟動並進入recovery模式
  • console [<console>]:表示該服務需要一個控制檯
    • 第二個引數用來指定特定控制檯名稱,預設為/dev/console
    • 指定時需省略掉/dev/部分,如/dev/tty0需寫成console tty0
  • disabled:表示服務不會通過class_start啟動。它必須以命令start service_name的方式指定來啟動
  • setenv <name> <value>:在Service啟動時將環境變數name設定為value
  • socket <name> <type> <perm> [ <user> [ <group> [ <seclabel> ] ] ]:建立一個名為/dev/socket/<name>的通訊端,並把檔案描述符傳遞給要啟動的程序
    • type的值必須是dgramstreamseqpacket
    • usergroup預設為0
    • seclabel是這個socketSElinux安全上下文,預設為當前service的上下文
  • user <username>:在執行此服務之前切換使用者名稱,預設的是root。如果程序沒有相應的許可權,則不能使用該命令
  • oneshotService退出後不再重新啟動
  • class <name>:給Service指定一個名字。所有同名字的服務可以同時啟動和停止。如果不通過class顯示指定,預設為default
  • onrestart:當Service重新啟動時,執行一條命令

還有很多哈,就不一一介紹了,像shutdown <shutdown_behavior>這種,參照官方說明就好啦

Triggers

trigger本質上是一個字串,能夠匹配某種包含該字串的事件。trigger又被細分為事件觸發器(event trigger)屬性觸發器(property trigger)

  • 事件觸發器可由trigger命令或初始化過程中通過QueueEventTrigger()觸發

    • 通常是一些事先定義的簡單字串,例如:boot,late-init
  • 屬性觸發器是當指定屬性的變數值變成指定值時觸發

    • 其格式為property:<name>=*

請注意,一個Action可以有多個屬性觸發器,但是最多有一個事件觸發器。看下官方的例子:

on boot && property:a=b

上面的Action只有在boot事件發生時,並且屬性a數值b相等的情況下才會被觸發。

而對於下面的Action

on property:a=b && property:c=d 

存在三種觸發情況:

  • 在啟動時,如果屬性a的值等於b並且屬性c的值等於d
  • 屬性c的值已經是d的情況下,屬性a的值被更新為b
  • 屬性a的值已經是b的情況下,屬性c的值被更新為d

對於事件觸發器,大體包括:

型別說明
bootinit.rc被裝載後觸發
device-added-<path>指定裝置被新增時觸發
device-removed-<path>指定裝置被移除時觸發
service-exited-<name>在特定服務退出時觸發
early-init初始化之前觸發
late-init初始化之後觸發
init初始化時觸發

Commands

Command是用於Action的命令列表或者ServiceOption<onrestart>中,在原始碼中是這樣的:

static const Map builtin_functions = {
        {"chmod",                   {2,     2,    {true,   do_chmod}}},
        {"chown",                   {2,     3,    {true,   do_chown}}},
        {"class_start",             {1,     1,    {false,  do_class_start}}},
        {"class_stop",              {1,     1,    {false,  do_class_stop}}},
        ......
    };

看幾個常用的吧

  1. bootchart [start|stop]:開啟或關閉程序啟動時間記錄工具
    //init.rc file
    mkdir /data/bootchart 0755 shell shell
    bootchart start
  • Init程序中會啟動bootchart,預設不會執行時間採集
  • 當我們需要採集啟動時間時,需建立一個/data/bootchart/enabled檔案
  1. chmod <octal-mode> <path>:更改檔案許可權
chmode 0755 /metadata/keystone
  1. chown <owner> <group> <path>:更改檔案的所有者和組
chown system system /metadata/keystone
  1. mkdir <path> [mode] [owner] [group]:建立指定目錄
mkdir /data/bootchart 0755 shell shell
  1. trigger <event>:觸發某個事件(Action),用於將該事件排在某個事件之後
on late-init
    trigger early-fs
    trigger fs
    trigger post-fs
    trigger late-fs
    trigger post-fs-data
    trigger zygote-start
    trigger load_persist_props_action
    trigger early-boot
    trigger boot
  1. class_start <serviceclass>:啟動所有指定服務class下的未執行服務
    class_start main
    class_start late_start
  1. class_stop <serviceclass>:停止所有指定服務class下的已執行服務
    class_stop charger
  1. exec [ <seclabel> [ <user> [ <group>\* ] ] ] -- <command> [ <argument>\* ]:通過給定的引數fork和啟動一個命令。
  • 具體的命令在--後開始
  • 引數包括seclable(預設的話使用-)、usergroup
  • exec為阻塞式,在當前命令完成前,不會執行其它命令,此時Init程序暫停執行。
exec - system system -- /system/bin/tzdatacheck /system/usr/share/zoneinfo /data/misc/zoneinfo

還有很多指令就不一一介紹了,參考官方檔案和原始碼就好啦

init指令碼的解析

上面我們知道了,在init.cppmain函數中通過LoadBootScripts()來載入rc指令碼,我們簡單看下解析流程(註釋比較詳細啦)

/**
 * 7.0後,init.rc進行了拆分,每個服務都有自己的rc檔案
 * 他們基本上都被載入到/system/etc/init,/vendor/etc/init, /odm/etc/init等目錄
 * 等init.rc解析完成後,會來解析這些目錄中的rc檔案,用來執行相關的動作
 * ===============================================================================
 * 對於自定義的服務來說,我們只需要通過 LOCAL_INIT_RC 指定自己的rc檔案即可
 * 編譯時會根據分割區標籤將rc檔案拷貝到指定的partition/etc/init目錄
 */
static void LoadBootScripts(ActionManager& action_manager, ServiceList& service_list) {
    // 建立解析器
    Parser parser = CreateParser(action_manager, service_list);
    std::string bootscript = GetProperty("ro.boot.init_rc", "");
    if (bootscript.empty()) {
        // 如果沒有特殊設定ro.boot.init_rc,先解析 init.rc 檔案
        parser.ParseConfig("/init.rc");
        if (!parser.ParseConfig("/system/etc/init")) {
            late_import_paths.emplace_back("/system/etc/init");
        }
        if (!parser.ParseConfig("/product/etc/init")) {
            late_import_paths.emplace_back("/product/etc/init");
        }
        if (!parser.ParseConfig("/odm/etc/init")) {
            late_import_paths.emplace_back("/odm/etc/init");
        }
        if (!parser.ParseConfig("/vendor/etc/init")) {
            late_import_paths.emplace_back("/vendor/etc/init");
        }
    } else {
        // 直接解析 ro.boot.init_rc 中的資料
        parser.ParseConfig(bootscript);
    }
}

/**
 * 建立解析器,目前只有三種section:service、on、import
 * 與之對應的,函數中也出現了三種解析器:ServiceParser、ActionParser、ImportParser
 */
Parser CreateParser(ActionManager& action_manager, ServiceList& service_list) {
    Parser parser;
    parser.AddSectionParser("service", std::make_unique<ServiceParser>(&service_list, subcontexts));
    parser.AddSectionParser("on", std::make_unique<ActionParser>(&action_manager, subcontexts));
    parser.AddSectionParser("import", std::make_unique<ImportParser>(&parser));
    return parser;
}

init中啟動的守護行程

init.rc中定義了很多守護行程,9我們來看下相關內容:

# adb 守護行程放在了這裡
import /init.usb.rc
# service adbd /system/bin/adbd --root_seclabel=u:r:su:s0
#    class core
#    ......

on boot
    # Start standard binderized HAL daemons
    class_start hal
    # 啟動所有class core的服務
    # 像adb、console等
    class_start core

on eraly-init
    start ueventd

on post-fs
    # Load properties from
    #     /system/build.prop,
    #     /odm/build.prop,
    #     /vendor/build.prop and
    #     /factory/factory.prop
    load_system_props
    # start essential services
    # 這幾個 service 的.rc檔案都在對應專案中,通過LOCAL_INIT_RC來指定
    start logd
    # 啟動三個Binder服務管理相關的service
    # servicemanager用於框架/應用程序之間的 IPC,使用 AIDL 介面
    start servicemanager
    # hwservicemanager用於框架/供應商程序之間的 IPC,使用 HIDL 介面
    # 也可用於供應商程序之間的 IPC,使用 HIDL 介面
    start hwservicemanager
    # vndservicemanager用於供應商/供應商程序之間的 IPC,使用 AIDL 介面
    start vndservicemanager

on post-fs-data
    # 啟動 vold(Volume守護行程),負責系統擴充套件儲存的自動掛載
    # 這個程序後面詳解
    start vold

# 負責響應 uevent 事件,建立對應的裝置節點
service ueventd /sbin/ueventd
    class core
    ......
    
# 包含常用的shell命令,如ls、cd等
service console /system/bin/sh
    class core
    ......

# Zygote 程序在這裡匯入,現在支援32位元和64位元
import /init.${ro.zygote}.rc
# zygote中會啟動一些相關service,像media、netd、wificond等
# service zygote /system/bin/app_process64 -Xzygote /system/bin --zygote --start-system-server --socket-name=zygote
#    onrestart restart audioserver
#    onrestart restart cameraserver
#    onrestart restart media
#    onrestart restart netd
#    onrestart restart wificond
# 啟動 Zygote 程序,觸發這個action的位置在 on late-init 中
on zygote-start && property:ro.crypto.state=unencrypted
    start netd
    start zygote
    start zygote_secondary

啟動流程和需要啟動的service通過init.rc基本上就可以完成客製化。

感覺Init程序通過解析*.rc的方式大大簡化了開發,真的是6啊。設計這一套AIL的人是真滴猛。。。。。。。

Init程序對訊號的處理

Init程序是系統的一號程序,系統中的其他程序都是Init程序的後代.

按照Linux的設計,Init程序需要在這些後代死亡時負責清理它們,以防止它們變成殭屍程序

殭屍程序簡介

關於殭屍程序可以參考Wiki百科-殭屍程序

類UNIX系統中,殭屍程序是指完成執行(通過exit系統呼叫,或執行時發生致命錯誤或收到終止訊號所致),但在作業系統的程序表中仍然存在其過程控制塊,處於終止狀態的程序。

這發生於子程序需要保留表項以允許其父程序讀取子程序的退出狀態:一旦退出態通過wait系統呼叫讀取,殭屍程序條目就從程序表中刪除,稱之為回收(reaped)

正常情況下,程序直接被其父程序wait並由系統回收。

殭屍程序的避免

  • 父程序通過waitwaitpid等函數等待子程序結束,這會導致父程序掛起
  • 如果父程序很忙,那麼可以用signal函數為SIGCHLD安裝handler,因為子程序結束後, 父程序會收到該訊號,可以在handler中呼叫wait回收
  • 如果父程序不關心子程序什麼時候結束,那麼可以用signal(SIGCHLD,SIG_IGN) 通知核心,自己對子程序的結束不感興趣,那麼子程序結束後,核心會回收,並不再給父程序傳送訊號
  • 還有一些技巧,就是fork兩次,父程序fork一個子程序,然後繼續工作,子程序fork一個孫程序後退出,那麼孫程序init接管,孫程序結束後,Init會回收。不過子程序的回收 還要自己做

我們下面來看下Init程序怎麼處理的

初始化SIGCHLD訊號

Init程序的main函數中,在初始化第二階段,有這麼一個方法sigchld_handler_init()

// file : init.cpp
int main(int argc, char** argv) {
    ......
    sigchld_handler_init();
    ......
}
// file : sigchld_handler.cpp
void sigchld_handler_init() {
    // Create a signalling mechanism for SIGCHLD.
    // 建立一個socketpair,往一個socket中寫,就可以從另外一個通訊端中讀取到資料
    int s[2];
    socketpair(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0, s);
    signal_write_fd = s[0];
    signal_read_fd = s[1];

    // Write to signal_write_fd if we catch SIGCHLD.
    // 訊號初始化相關引數設定
    // 設定SIGCHLD_handler訊號處理常式
    // 設定SA_NOCLDSTOP標誌
    struct sigaction act;
    memset(&act, 0, sizeof(act));
    act.sa_handler = SIGCHLD_handler;
    act.sa_flags = SA_NOCLDSTOP;
    
    // 註冊SIGCHLD訊號
    sigaction(SIGCHLD, &act, 0);

    ReapAnyOutstandingChildren();
    
    // 註冊signal_read_fd到epoll_fd
    register_epoll_handler(signal_read_fd, handle_signal);
}
// file : sigchld_handler.cpp
/**
 * SIGCHLD_handler的作用是當init程序接收到SIGCHLD訊號時,往signal_write_fd中寫入資料
 * 這個時候通訊端對中的另外一個signal_read_fd就可讀了。
 */
static void SIGCHLD_handler(int) {
    if (TEMP_FAILURE_RETRY(write(signal_write_fd, "1", 1)) == -1) {
        PLOG(ERROR) << "write(signal_write_fd) failed";
    }
}
// file : sigchld_handler.cpp
/**
 * register_epoll_handler函數主要的作用是註冊屬性socket檔案描述符到輪詢描述符epoll_fd
 */
void register_epoll_handler(int fd, void (*fn)()) {
    epoll_event ev;
    ev.events = EPOLLIN;
    ev.data.ptr = reinterpret_cast<void*>(fn);
    if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &ev) == -1) {
        PLOG(ERROR) << "epoll_ctl failed";
    }
}

訊號的初始化是通過系統呼叫sigaction()來完成的

  • 引數act中:
    • sa_handler用來指定訊號的處理常式
    • sa_flags用來指定觸發標誌,SA_NOCLDSTOP標誌意味著當子程序終止時才接收SIGCHLD訊號

Linux系統中,訊號又稱為軟中斷,訊號的到來會中斷程序正在處理的工作,因此在訊號處理常式中不要去呼叫一些不可重入的函數。而且Linux不會對訊號排隊,不管是在訊號的處理期間再來多少個訊號,當前的訊號處理常式執行完後,核心只會再傳送一個訊號給程序,因此,為了不丟失訊號,我們的訊號處理常式執行得越快越好

而對於SIGCHLD訊號,父程序需要執行等待操作,這樣的話時間就比較長了,因此需要有辦法解決這個矛盾

  • 上面的程式碼中建立了一對本地socket用於程序間通訊
  • 當訊號到來時,SIGCHLD_handler處理常式只要向socket中的signal_write_fd寫入資料就可以
  • 這樣,訊號的處理就轉變到了socket的處理上了

此時,我們需要監聽signal_read_fd,並提供一個回撥函數,這就是register_epoll_handler()函數的作用

  • 函數中的EPOLLIN表示當檔案描述符可讀時才會觸發
  • *fn就是觸發後的回撥函數指標,賦值給了ev.data.ptr,請留意下這個指標變數,後面會用到
  • 提供的回撥函數就是handle_signal()

響應子程序的死亡事件

Init程序啟動完畢後,會監聽建立的socket,如果有資料到來,主執行緒會喚醒並呼叫處理常式handle_signal()

static void handle_signal() {
    // Clear outstanding requests.
    // 清空 signal_read_fd 中的資料
    char buf[32];
    read(signal_read_fd, buf, sizeof(buf));
    
    ReapAnyOutstandingChildren(); 
}
void ReapAnyOutstandingChildren() {
    while (ReapOneProcess()) {
    }
}
static bool ReapOneProcess() {
    siginfo_t siginfo = {};
    // This returns a zombie pid or informs us that there are no zombies left to be reaped.
    // It does NOT reap the pid; that is done below.
    // 查詢是否存在殭屍程序
    if (TEMP_FAILURE_RETRY(waitid(P_ALL, 0, &siginfo, WEXITED | WNOHANG | WNOWAIT)) != 0) {
        PLOG(ERROR) << "waitid failed";
        return false;
    }
    // 沒有殭屍程序直接返回
    auto pid = siginfo.si_pid;
    if (pid == 0) return false;

    // At this point we know we have a zombie pid, so we use this scopeguard to reap the pid
    // whenever the function returns from this point forward.
    // We do NOT want to reap the zombie earlier as in Service::Reap(), we kill(-pid, ...) and we
    // want the pid to remain valid throughout that (and potentially future) usages.
    // 等待子程序終止
    auto reaper = make_scope_guard([pid] { TEMP_FAILURE_RETRY(waitpid(pid, nullptr, WNOHANG)); });
    ......
    if (!service) return true;
    // 進行退出的其他操作
    service->Reap(siginfo);
    ......
}

當接收到子程序的SIGCHLD訊號後,會找出該程序對應的Service物件,然後呼叫Reap函數,我們看下函數內容:

void Service::Reap(const siginfo_t& siginfo) {
    // 如果 不是oneshot 或者 是重新啟動的子程序
    // 殺掉整個行程群組,思考了下,先殺掉為重新啟動做準備吧
    // 這樣當重新啟動的時候,就不會因為子程序已經存在而導致錯誤了
    if (!(flags_ & SVC_ONESHOT) || (flags_ & SVC_RESTART)) {
        KillProcessGroup(SIGKILL);
    }
    // 做一些當前程序的清理工作
    ......

    // Oneshot processes go into the disabled state on exit,
    // except when manually restarted.
    // 如果 是oneshot 或者 不是重新啟動的子程序,設定為SVC_DISABLED
    if ((flags_ & SVC_ONESHOT) && !(flags_ & SVC_RESTART)) {
        flags_ |= SVC_DISABLED;
    }

    // Disabled and reset processes do not get restarted automatically.
    // 如果是SVC_DISABLED或者SVC_RESET
    // 設定程序狀態為stopped,然後返回
    if (flags_ & (SVC_DISABLED | SVC_RESET))  {
        NotifyStateChange("stopped");
        return;
    }

    // If we crash > 4 times in 4 minutes, reboot into recovery.
    // 省略 crash 次數檢測
    ......
    // 省略一些程序狀態的設定,都是和重新啟動相關的
    
    // Execute all onrestart commands for this service.
    // 執行onrestart的指令
    onrestart_.ExecuteAllCommands();
    
    // 設定狀態為重新啟動中
    NotifyStateChange("restarting");
    return;
}

Reap()函數中

  • 會根據對應程序Service物件的flags_標誌位來判斷該程序能不能重新啟動
  • 如果需要重新啟動,就給flags_標誌位新增SVC_RESTARTING標誌位

到這裡,我們清楚了handle_signal()函數的內部流程,那麼它又是從哪裡被呼叫的呢?

我們再回到init.cppmain()方法中看看:

    while (true) {
        ......
        epoll_event ev;
        int nr = TEMP_FAILURE_RETRY(epoll_wait(epoll_fd, &ev, 1, epoll_timeout_ms));
        if (nr == -1) {
            PLOG(ERROR) << "epoll_wait failed";
        } else if (nr == 1) {
            ((void (*)()) ev.data.ptr)();
        }
    }

請注意ev.data.ptr,還記得register_epoll_handler()函數不

void register_epoll_handler(int fd, void (*fn)()) {
    ......
    ev.data.ptr = reinterpret_cast<void*>(fn);
    ......
}

epoll_wait有資料接收到時,就會執行((void (*)()) ev.data.ptr)();,也就是我們的回撥函數handle_signal()

咳咳咳,到這裡就把Init程序對子程序死亡通知的邏輯給梳理完了,原始碼一直在變,好在核心的邏輯沒有變化,且看且珍惜吧,哈哈哈~

屬性系統

簡介

屬性Android系統中大量使用,用來儲存系統設定或在程序間傳遞一些簡單的資訊

  • 每個屬性屬性名屬性值組成
  • 屬性名通常一長串以.分割的字串,這些名稱的字首有特定含義,不能隨便改動
  • 屬性值只能是字串

Java層可以通過如下方法來獲取和設定屬性:

//class android.os.SystemProperties
    @SystemApi
    public static String get(String key);
    @SystemApi
    public static String get(String key, String def);
    @hide
    public static void set(String key, String val);

native層可以使用:

android::base::GetProperty(key, "");
android::base::SetProperty(key, val);

對於系統中的每個程序來說:

  • 讀取屬性值對任何程序都是沒有限制的,直接由本程序從共用區域中讀取
  • 修改屬性值則必須通過Init程序完成,同時Init程序還需要檢查發起請求的程序是否具有相應的許可權

屬性值修改成功後,Init程序會檢查init.rc檔案中是否已經定義了和該屬性值匹配的trigger。如果有定義,則執行trigger下的命令。如:

on property:ro.debuggable=1
    start console

這個trigger的含義是:當屬性ro.debuggable被設定為1,則執行命令start console,啟動console

Android系統級應用和底層模組非常依賴屬性系統,常常依靠屬性值來決定它們的行為。

Android的系統設定程式中,很多功能的開啟和關閉都是通過某個特定的系統屬性值來控制。這也意味著隨便改變屬性值將會嚴重影響系統的執行,因此,對於屬性值的修改必須要有特定的許可權。對於許可權的設定,現在統一由SELinux來控制。

屬性服務的啟動流程

我們先看下屬性服務啟動的整體流程:

int main(int argc, char** argv) {
    ......
    // 屬性服務初始化
    property_init();
    ......
    // 啟動屬性服務
    start_property_service();
    ......
}

init.cppmain()函數中,通過property_init();來對屬性服務進行初始化

void property_init() {
    // 建立一個資料夾,許可權711,只有owner才可以設定
    mkdir("/dev/__properties__", S_IRWXU | S_IXGRP | S_IXOTH);
    // 讀取一些屬性檔案,將屬性值儲存在一個集合中
    CreateSerializedPropertyInfo();
    if (__system_property_area_init()) {// 建立屬性共用記憶體空間(這個函數是libc庫的部分)
        LOG(FATAL) << "Failed to initialize property area";
    }
    if (!property_info_area.LoadDefaultPath()) {// 載入預設路徑上的屬性到共用區域
        LOG(FATAL) << "Failed to load serialized property info file";
    }
}

然後通過start_property_service()函數啟動服務:

void start_property_service() {
    // 省略SELinux相關操作
    ......
    property_set("ro.property_service.version", "2");
    // 建立prop service對應的socket,並返回socket fd
    property_set_fd = CreateSocket(PROP_SERVICE_NAME, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, false, 0666, 0, 0, nullptr);
    // 省略建立失敗的異常判斷
    ......
    // 設定最大連線數量為8
    listen(property_set_fd, 8);
    // 註冊epolls事件監聽property_set_fd
    // 當監聽到資料變化時,呼叫handle_property_set_fd函數
    register_epoll_handler(property_set_fd, handle_property_set_fd);
}
  • socket描述符property_set_fd被建立後,用epoll來監聽property_set_fd
  • property_set_fd有資料到來時,init程序將呼叫handle_property_set_fd()函數來進行處理

我們再來看下handle_property_set_fd()函數

static void handle_property_set_fd() {
    static constexpr uint32_t kDefaultSocketTimeout = 2000; /* ms */
    // 省略一些異常判斷
    ......
    uint32_t cmd = 0;
    // 省略cmd讀取操作和一些異常判斷
    ......
    switch (cmd) {
    case PROP_MSG_SETPROP: {
        char prop_name[PROP_NAME_MAX];
        char prop_value[PROP_VALUE_MAX];
        // 省略字元資料的讀取組裝操作
        ......
        uint32_t result =
            HandlePropertySet(prop_name, prop_value, socket.source_context(), cr, &error);
        // 省略異常情況處理
        ......
        break;
      }

    case PROP_MSG_SETPROP2: {
        std::string name;
        std::string value;
        // 省略字串資料的讀取操作
        ......
        uint32_t result = HandlePropertySet(name, value, socket.source_context(), cr, &error);
        // 省略異常情況處理
        ......
        break;
      }
    default:
        LOG(ERROR) << "sys_prop: invalid command " << cmd;
        socket.SendUint32(PROP_ERROR_INVALID_CMD);
        break;
    }
}

Init程序在接收到設定屬性的cmd後,會執行處理常式HandlePropertySet()

uint32_t HandlePropertySet(const std::string& name, const std::string& value,
                           const std::string& source_context, 
                           const ucred& cr, std::string* error) {
    // 判斷要設定的屬性名稱是否合法
    // 相當於命名規則檢查
    if (!IsLegalPropertyName(name)) {
        // 不合法直接返回
        return PROP_ERROR_INVALID_NAME;
    }
    // 如果是ctl開頭,說明是控制類屬性
    if (StartsWith(name, "ctl.")) {
        // 檢查是否具有對應的控制許可權
        ......
        // 許可權通過後執行對應的控制指令
        // 其實控制指令就簡單的幾個start/stop等,大家可以在深入閱讀下這個函數
        HandleControlMessage(name.c_str() + 4, value, cr.pid);
        return PROP_SUCCESS;
    }
    const char* target_context = nullptr;
    const char* type = nullptr;
    // 獲取要設定的屬性的上下文和資料型別
    // 後面會對target_context和type進行比較判斷
    property_info_area->GetPropertyInfo(name.c_str(), &target_context, &type);
    // 檢查是否具有當前屬性的set許可權
    if (!CheckMacPerms(name, target_context, source_context.c_str(), cr)) {
        // 沒有直接返回
        return PROP_ERROR_PERMISSION_DENIED;
    }
    // 對屬性的型別和要寫入資料的型別進行判斷
    // 大家看看CheckType函數就明白了,其實只有一個string型別。。。。。
    if (type == nullptr || !CheckType(type, value)) {
        // 不合法,直接返回
        return PROP_ERROR_INVALID_VALUE;
    }
    // 如果是sys.powerctl屬性,需要做一些特殊處理
    if (name == "sys.powerctl") {
        // 增加一些額外列印
        ......
    }
    if (name == "selinux.restorecon_recursive") {
        // 特殊屬性,特殊處理
        return PropertySetAsync(name, value, RestoreconRecursiveAsync, error);
    }
    return PropertySet(name, value, error);
}

除了一些特殊的屬性外,真正設定屬性的函數是PropertySet

static uint32_t PropertySet(const std::string& name, const std::string& value, std::string* error) {
    size_t valuelen = value.size();
    // 判斷屬性名是否合法
    if (!IsLegalPropertyName(name)) {
        *error = "Illegal property name";
        return PROP_ERROR_INVALID_NAME;
    }
    // 判斷寫入的資料長度是否合法
    // 判斷屬性是否為唯讀屬性(ro)
    if (valuelen >= PROP_VALUE_MAX && !StartsWith(name, "ro.")) {
        *error = "Property value too long";
        return PROP_ERROR_INVALID_VALUE;
    }
    // 判斷要寫入資料的編碼格式
    if (mbstowcs(nullptr, value.data(), 0) == static_cast<std::size_t>(-1)) {
        *error = "Value is not a UTF8 encoded string";
        return PROP_ERROR_INVALID_VALUE;
    }
    // 根據屬性名獲取系統中存放的屬性物件
    prop_info* pi = (prop_info*) __system_property_find(name.c_str());
    if (pi != nullptr) {
        // ro.* properties are actually "write-once".
        // ro開頭的屬性只允許寫入一次
        if (StartsWith(name, "ro.")) {
            *error = "Read-only property was already set";
            return PROP_ERROR_READ_ONLY_PROPERTY;
        }
        // 如果已經存在,並且不是唯讀屬性,執行屬性更新函數
        __system_property_update(pi, value.c_str(), valuelen);
    } else {
        // 如果系統中不存在屬性,執行新增屬性新增函數
        int rc = __system_property_add(name.c_str(), name.size(), value.c_str(), valuelen);
        if (rc < 0) {
            *error = "__system_property_add failed";
            return PROP_ERROR_SET_FAILED;
        }
    }
    // Don't write properties to disk until after we have read all default
    // properties to prevent them from being overwritten by default values.
    // 如果是持久化的屬性,進行持久化處理
    if (persistent_properties_loaded && StartsWith(name, "persist.")) {
        WritePersistentProperty(name, value);
    }
    // 將屬性的變更新增到Action佇列中
    property_changed(name, value);
    return PROP_SUCCESS;
}

Init程序epoll屬性的socket,等待和處理屬性請求。

  • 如果有請求到來,則呼叫handle_property_set_fd來處理這個請求
  • handle_property_set_fd函數裡,首先檢查請求者的uid/gid看看是否有許可權,如果有許可權則調property_service.cpp中的PropertySet函數。

PropertySet函數中

  • 它先查詢就沒有這個屬性,如果找到,更改屬性。如果找不到,則新增新屬性。
  • 更改時還會判斷是不是ro屬性,如果是,則不能更改。
  • 如果是persist的話還會寫到/data/property/<name>中。

最後它會調property_changed函數,把事件掛到佇列裡

  • 如果有人註冊這個屬性的話(比如init.rcon property:ro.kernel.qemu=1),最終會觸發它

ueventdwatchdogd簡介

ueventd程序

守護行程ueventd的主要作用是接收ueventd來建立和刪除裝置中dev目錄下的裝置節點

ueventd程序和Init程序並不是一個程序,但是它們的二進位制檔案是相同的,只不過啟動時引數不一樣導致程式的執行流程不一樣。

init.rc檔案中

on early-init
    start ueventd

## Daemon processes to be run by init.
##
service ueventd /sbin/ueventd
    class core
    critical
    seclabel u:r:ueventd:s0
    shutdown critical

這樣Init程序在執行action eraly-init是就會啟動ueventd程序。

watchdogd程序

watchdogdueventd型別,都是獨立於Init的程序,但是程式碼和Init程序在一起。watchdogd是用來配合硬體看門狗的。

當一個硬體系統開啟了watchdog功能,那麼執行在這個硬體系統之上的軟體必須在規定的時間間隔內向watchdog傳送一個訊號。這個行為簡稱為喂狗(feed dog),以免watchdog記時超時引發系統重起。

現在的系統中很少有看到watchdogd程序了,不過這種模式還是很不錯、值得借鑑的