主頁 >  其他 > 深入Android系統(六)第一個用戶行程-Init行程

深入Android系統(六)第一個用戶行程-Init行程

2020-10-27 00:44:37 其他

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

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行程了,不過這種模式還是很不錯、值得借鑒的

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/192767.html

標籤:其他

上一篇:計算機組成原理---計算機系統的發展趨勢

下一篇:CodeForces 1421-A XORwice

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 網閘典型架構簡述

    網閘架構一般分為兩種:三主機的三系統架構網閘和雙主機的2+1架構網閘。 三主機架構分別為內端機、外端機和仲裁機。三機無論從軟體和硬體上均各自獨立。首先從硬體上來看,三機都用各自獨立的主板、記憶體及存盤設備。從軟體上來看,三機有各自獨立的作業系統。這樣能達到完全的三機獨立。對于“2+1”系統,“2”分為 ......

    uj5u.com 2020-09-10 02:00:44 more
  • 如何從xshell上傳檔案到centos linux虛擬機里

    如何從xshell上傳檔案到centos linux虛擬機里及:虛擬機CentOs下執行 yum -y install lrzsz命令,出現錯誤:鏡像無法找到軟體包 前言 一、安裝lrzsz步驟 二、上傳檔案 三、遇到的問題及解決方案 總結 前言 提示:其實很簡單,往虛擬機上安裝一個上傳檔案的工具 ......

    uj5u.com 2020-09-10 02:00:47 more
  • 一、SQLMAP入門

    一、SQLMAP入門 1、判斷是否存在注入 sqlmap.py -u 網址/id=1 id=1不可缺少。當注入點后面的引數大于兩個時。需要加雙引號, sqlmap.py -u "網址/id=1&uid=1" 2、判斷文本中的請求是否存在注入 從文本中加載http請求,SQLMAP可以從一個文本檔案中 ......

    uj5u.com 2020-09-10 02:00:50 more
  • Metasploit 簡單使用教程

    metasploit 簡單使用教程 浩先生, 2020-08-28 16:18:25 分類專欄: kail 網路安全 linux 文章標簽: linux資訊安全 編輯 著作權 metasploit 使用教程 前言 一、Metasploit是什么? 二、準備作業 三、具體步驟 前言 Msfconsole ......

    uj5u.com 2020-09-10 02:00:53 more
  • 游戲逆向之驅動層與用戶層通訊

    驅動層代碼: #pragma once #include <ntifs.h> #define add_code CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS) /* 更多游戲逆向視頻www.yxfzedu.com ......

    uj5u.com 2020-09-10 02:00:56 more
  • 北斗電力時鐘(北斗授時服務器)讓網路資料更精準

    北斗電力時鐘(北斗授時服務器)讓網路資料更精準 北斗電力時鐘(北斗授時服務器)讓網路資料更精準 京準電子科技官微——ahjzsz 近幾年,資訊技術的得了快速發展,互聯網在逐漸普及,其在人們生活和生產中都得到了廣泛應用,并且取得了不錯的應用效果。計算機網路資訊在電力系統中的應用,一方面使電力系統的運行 ......

    uj5u.com 2020-09-10 02:01:03 more
  • 【CTF】CTFHub 技能樹 彩蛋 writeup

    ?碎碎念 CTFHub:https://www.ctfhub.com/ 筆者入門CTF時時剛開始刷的是bugku的舊平臺,后來才有了CTFHub。 感覺不論是網頁UI設計,還是題目質量,賽事跟蹤,工具軟體都做得很不錯。 而且因為獨到的金幣制度的確讓人有一種想去刷題賺金幣的感覺。 個人還是非常喜歡這個 ......

    uj5u.com 2020-09-10 02:04:05 more
  • 02windows基礎操作

    我學到了一下幾點 Windows系統目錄結構與滲透的作用 常見Windows的服務詳解 Windows埠詳解 常用的Windows注冊表詳解 hacker DOS命令詳解(net user / type /md /rd/ dir /cd /net use copy、批處理 等) 利用dos命令制作 ......

    uj5u.com 2020-09-10 02:04:18 more
  • 03.Linux基礎操作

    我學到了以下幾點 01Linux系統介紹02系統安裝,密碼啊破解03Linux常用命令04LAMP 01LINUX windows: win03 8 12 16 19 配置不繁瑣 Linux:redhat,centos(紅帽社區版),Ubuntu server,suse unix:金融機構,證券,銀 ......

    uj5u.com 2020-09-10 02:04:30 more
  • 05HTML

    01HTML介紹 02頭部標簽講解03基礎標簽講解04表單標簽講解 HTML前段語言 js1.了解代碼2.根據代碼 懂得挖掘漏洞 (POST注入/XSS漏洞上傳)3.黑帽seo 白帽seo 客戶網站被黑帽植入劫持代碼如何處理4.熟悉html表單 <html><head><title>TDK標題,描述 ......

    uj5u.com 2020-09-10 02:04:36 more
最新发布
  • 2023年最新微信小程式抓包教程

    01 開門見山 隔一個月發一篇文章,不過分。 首先回顧一下《微信系結手機號資料庫被脫庫事件》,我也是第一時間得知了這個訊息,然后跟蹤了整件事情的經過。下面是這起事件的相關截圖以及近日流出的一萬條資料樣本: 個人認為這件事也沒什么,還不如關注一下之前45億快遞資料查詢渠道疑似在近日復活的訊息。 訊息是 ......

    uj5u.com 2023-04-20 08:48:24 more
  • web3 產品介紹:metamask 錢包 使用最多的瀏覽器插件錢包

    Metamask錢包是一種基于區塊鏈技術的數字貨幣錢包,它允許用戶在安全、便捷的環境下管理自己的加密資產。Metamask錢包是以太坊生態系統中最流行的錢包之一,它具有易于使用、安全性高和功能強大等優點。 本文將詳細介紹Metamask錢包的功能和使用方法。 一、 Metamask錢包的功能 數字資 ......

    uj5u.com 2023-04-20 08:47:46 more
  • vulnhub_Earth

    前言 靶機地址->>>vulnhub_Earth 攻擊機ip:192.168.20.121 靶機ip:192.168.20.122 參考文章 https://www.cnblogs.com/Jing-X/archive/2022/04/03/16097695.html https://www.cnb ......

    uj5u.com 2023-04-20 07:46:20 more
  • 從4k到42k,軟體測驗工程師的漲薪史,給我看哭了

    清明節一過,盲猜大家已經無心上班,在數著日子準備過五一,但一想到銀行卡里的余額……瞬間心情就不美麗了。最近,2023年高校畢業生就業調查顯示,本科畢業月平均起薪為5825元。調查一出,便有很多同學表示自己又被平均了。看著這一資料,不免讓人想到前不久中國青年報的一項調查:近六成大學生認為畢業10年內會 ......

    uj5u.com 2023-04-20 07:44:00 more
  • 最新版本 Stable Diffusion 開源 AI 繪畫工具之中文自動提詞篇

    🎈 標簽生成器 由于輸入正向提示詞 prompt 和反向提示詞 negative prompt 都是使用英文,所以對學習母語的我們非常不友好 使用網址:https://tinygeeker.github.io/p/ai-prompt-generator 這個網址是為了讓大家在使用 AI 繪畫的時候 ......

    uj5u.com 2023-04-20 07:43:36 more
  • 漫談前端自動化測驗演進之路及測驗工具分析

    隨著前端技術的不斷發展和應用程式的日益復雜,前端自動化測驗也在不斷演進。隨著 Web 應用程式變得越來越復雜,自動化測驗的需求也越來越高。如今,自動化測驗已經成為 Web 應用程式開發程序中不可或缺的一部分,它們可以幫助開發人員更快地發現和修復錯誤,提高應用程式的性能和可靠性。 ......

    uj5u.com 2023-04-20 07:43:16 more
  • CANN開發實踐:4個DVPP記憶體問題的典型案例解讀

    摘要:由于DVPP媒體資料處理功能對存放輸入、輸出資料的記憶體有更高的要求(例如,記憶體首地址128位元組對齊),因此需呼叫專用的記憶體申請介面,那么本期就分享幾個關于DVPP記憶體問題的典型案例,并給出原因分析及解決方法。 本文分享自華為云社區《FAQ_DVPP記憶體問題案例》,作者:昇騰CANN。 DVPP ......

    uj5u.com 2023-04-20 07:43:03 more
  • msf學習

    msf學習 以kali自帶的msf為例 一、msf核心模塊與功能 msf模塊都放在/usr/share/metasploit-framework/modules目錄下 1、auxiliary 輔助模塊,輔助滲透(埠掃描、登錄密碼爆破、漏洞驗證等) 2、encoders 編碼器模塊,主要包含各種編碼 ......

    uj5u.com 2023-04-20 07:42:59 more
  • Halcon軟體安裝與界面簡介

    1. 下載Halcon17版本到到本地 2. 雙擊安裝包后 3. 步驟如下 1.2 Halcon軟體安裝 界面分為四大塊 1. Halcon的五個助手 1) 影像采集助手:與相機連接,設定相機引數,采集影像 2) 標定助手:九點標定或是其它的標定,生成標定檔案及內參外參,可以將像素單位轉換為長度單位 ......

    uj5u.com 2023-04-20 07:42:17 more
  • 在MacOS下使用Unity3D開發游戲

    第一次發博客,先發一下我的游戲開發環境吧。 去年2月份買了一臺MacBookPro2021 M1pro(以下簡稱mbp),這一年來一直在用mbp開發游戲。我大致分享一下我的開發工具以及使用體驗。 1、Unity 官網鏈接: https://unity.cn/releases 我一般使用的Apple ......

    uj5u.com 2023-04-20 07:40:19 more