換個角度看init (Android P 9.0)

換個角度看init

作者: WertherZhang
主頁: https://wertherzhang.com
鏈接: http://www.reibang.com/p/220cab3652d1
來源: 簡書
轉(zhuǎn)載請注明來源

本篇雖然歸屬于跟我讀源碼系列, 但實際是作者嘗試從非源碼的角度來進入init, 過程中作者會截取盡量少的源碼來輔助理解(所有的代碼鏈接指向 http://androidxref.com/9.0.0_r3/ ).

所有的UNIX系統(tǒng)都有一個特殊的進程, 它是內(nèi)核啟動完畢后, 用戶態(tài)中啟動的第一個進程, 它負(fù)責(zé)啟動系統(tǒng), 病作為系統(tǒng)中其他進程的祖先進程. 傳統(tǒng)上, 這個進程被稱為init. Android 也沿用了這個約定俗成的規(guī)矩.

不過Android中的init和UNIX或Linux中的init還是很大區(qū)別的, 其中最重要的不同之處在于, Android中的init支持系統(tǒng)屬性, 病使用一些指定的rc文件(來規(guī)定它的行為). 在介紹這兩個特性之后, 我們將拼湊出一副init運行流程的全景圖:它的初始化過程和主循環(huán)(run-loop)中要做的操作.

另外, init還扮演著其他角色 -- 它還要花生為ueventd, watchdogd 和 subcontext. 這三個重要的核心服務(wù)也是由init這個二進制可執(zhí)行文件來實現(xiàn)的 -- 通過符號鏈接的方式加載.

init的角色和任務(wù)

和大多數(shù)unix內(nèi)核一樣, linux內(nèi)核會去尋找一個路徑和文件名預(yù)先規(guī)定好的二進制可執(zhí)行文件, 并把它作為第一個用戶態(tài)進程執(zhí)行. 桌面linux系統(tǒng)中, 這個可執(zhí)行文件一般是 /sbin/init, 它會去讀取 /etc/inittab 文件的內(nèi)容, 以獲取所支持的 "運行級別"(run-levels), 運行時配置信息(單用戶,多用戶,網(wǎng)絡(luò)文件系統(tǒng)等), 需隨機啟動的進程以及當(dāng)用戶按下Ctrl+Alt+Del 組合鍵時該做出何種反應(yīng)等信息. Android 也使用這樣一個init程序, 但是Android中的init程序和傳統(tǒng)linux中的init程序的相似之處也就僅止于這個文件名了, 它們之間的區(qū)別如下表 所示

Linux的/sbin/init Android的 init
配置文件 /etc/inittab ro.boot.init_rc 指向的文件或者 /init.rc , /system/etc/init,/product/etc/init, /odm/etc/init,/vendor/etc/init 中指向的所有.rc 文件以及導(dǎo)入的文件
多種配置 支持"運行級別"概念,
每個運行級別都會從 /etc/底下加載不同的腳本
沒有運行級概念, 但是有觸發(fā)器(trigger)和系統(tǒng)屬性提供了配置選項
看門狗 支持通過respawn關(guān)鍵字定義過的守護進程會在退出時重啟 -- 除非該進程反復(fù)崩潰, 在這種情況下, 反復(fù)崩潰的進程會被掛起幾分鐘 服務(wù)默認(rèn)是自動重啟 除非啟動腳本中顯示使用了 oneshot 參數(shù). 服務(wù)還可以使用critical 參數(shù), 這會使系統(tǒng)在該服務(wù)連續(xù)崩潰時重啟系統(tǒng).
收容孤兒進程 支持: init會調(diào)用wait4()系統(tǒng)調(diào)用獲取返回碼, 并避免出現(xiàn)僵尸進程 支持: init注冊了一個SIGCHLD信號處理函數(shù). SIGCHLD 信號是在子進程退出后由內(nèi)核自動發(fā)送的.大多數(shù)進程會默默第調(diào)用wait(NULL)清理掉已退出進程, 而不去管退出碼是什么
系統(tǒng)屬性 不支持 init 通過共享一塊內(nèi)存區(qū)域的方式, 讓系統(tǒng)中的所有進程都能讀取系統(tǒng)屬性(通過 getprop), 病通過一個名為 "property_service" 的 socket 讓有權(quán)限的進程能夠(通過 setprop) 寫相關(guān)的屬性
分配socket 不支持 init 會綁定一個UNIX domain socket (支持dgram, stream 和 seqpacket)提供給子進程, 子進程可以通過 android_get_control_socket 函數(shù)獲取到它
觸發(fā)操作 不支持: linux只支持非常特殊的觸發(fā)操作, 比如 ctrl+alt+del 和 UPS 電源事件, 但是不允許任意的出發(fā)操作 支持: init 可以在任何一個系統(tǒng)屬性被修改時, 執(zhí)行記錄在trigger語句塊中的指令
uevent事件 linux依靠的是hotplug守護進程(通常為 udevd) init 會化身為 ueventd, 用專門的配置文件來指導(dǎo)其行為.

init 是靜態(tài)鏈接的二進制可執(zhí)行文件(可通過file命令查看). 也就是說在編譯時, 它的所有依賴庫都已經(jīng)被合并到這個二進制可執(zhí)行文件中了. 這樣做是為了防止僅僅因為缺少某個庫或者某個庫被破壞而造成系統(tǒng)無法啟動的情況發(fā)生. /init 在剛被執(zhí)行時, 只有和內(nèi)核一起被打包放在boot分區(qū)上的RAM disk(單分區(qū), AB分區(qū)是system, 此處不討論)被mount了上來, 換句話說, 系統(tǒng)中只有 / 和 /sbin, 沒有動態(tài)庫.

系統(tǒng)屬性

Android 的系統(tǒng)屬性提供了一個可全局訪問的配置設(shè)置倉庫(類似windows注冊表). 與init相關(guān)的源碼中的 property_service.c 中的代碼, 或按下表中給出的順序, 從多個文件中加載屬性.

文件 加載時機 內(nèi)容
$device_tree process_kernel_dt device tree 中定義的property, 全部轉(zhuǎn)換為 ro.boot. 前綴的property
cmdline process_kernel_cmdline cmdline中定義的property, 全部轉(zhuǎn)換為 ro.boot. 前綴的property
/system/etc/prop.default 或者 /default.prop , /product/build.prop, /odm/default.prop 和 /vendor/default.prop 在init的main函數(shù)中加載.除了從根讀取, 其余分區(qū)必須在device-tree中定義并在 DoFirstStageMount階段被掛載, 否則property加載失敗. 初始設(shè)置.
/system/build.prop, /odm/build.prop, /vendor/build.prop, factory/factory.prop init.rc 中 post-fs 階段 除了factory.prop 是只加載ro前綴, 其余全加載
/data/property/persist.* late-init 或者 data解密完成 重啟后不會丟失的property, 必須加上前綴persist

注意: 各個文件的加載順序是非常重要的, ro前綴的property早加載的生效, 而非ro前綴的property晚加載的生效.

因為init進程是系統(tǒng)中所有進程的祖先, 所以只有它才天生適合實現(xiàn)系統(tǒng)屬性的初始化. 在它剛開始初始化的時候, init中的代碼會調(diào)用 property_init()去安裝系統(tǒng)屬性. 這個函數(shù)(最終)會調(diào)用 map_prop_area_rw(), 打開 PROP_FILENAME ( 這個定義是 /dev/__properties__ ) 底下的所有文件, 在關(guān)閉文件之前, 調(diào)用mmap(2) 以讀寫的方式映射到內(nèi)存. 而需要讀的進程, 則是調(diào)用__system_properties_init , 在 bionic 中被調(diào)用(__libc_init_common). 下面是模擬器中的輸出.

1537789826206.png
1537789889464.png

上面是基于共享內(nèi)存實現(xiàn)了讀操作, 下面我看下寫操作. init 專門打開了一個專用的unix domain socket -- /dev/socket/property_service. 只要能連上這個socket, 任何人都能對它進行寫操作(0666, u:object_r:property_socket:s0). 這些寫入的命令會直接送給init, init會先檢查 socket 調(diào)用者有沒有設(shè)置屬性的權(quán)限(selinux/uid/gid), selinux的相關(guān)權(quán)限在 /property_contexts 中.

1538381595267.png

下面我們看下通過socket執(zhí)行proeprty set 的strace輸出, 其中涉及通信協(xié)議部分不過多介紹.

1538382197761.png

最終寫入的文件如下:

1538382637099.png

上面我們看到, persist屬性是寫入 /data/property, 這也是為什么persist屬性在重啟后依然生效. persist 這個關(guān)鍵字, 我們可以稱其為偽前綴, 真正property的前綴是 sys 這個關(guān)鍵字, 除了sys, 還有usb, radio等前綴關(guān)鍵字, 用來區(qū)分功能和權(quán)限的(比如前圖的selinux權(quán)限). 具體的前綴關(guān)鍵字此處不細(xì)說, 讀者可自行閱讀代碼研究, 下面介紹下幾個偽前綴.

  • persist 前綴. 保存在 /data/property 目錄, 重啟后依然生效.在data解密后或者late-init階段加載.
  • ro 前綴. 只讀屬性, 類似C/CPP中的常量定義, 它能且只能被設(shè)置1次, 這類屬性一般盡可能早設(shè)置. 依據(jù)前文的加載順序, 在有兩個相同的ro前綴property時, 前面加載的生效, 而非ro前綴的property, 后面加載的生效.
  • ctl 前綴. 這是方便控制init中的服務(wù)而設(shè)立的 -- 通過把相關(guān)服務(wù)的名字設(shè)置為 ctl.start 和 ctl.stop的值, 就能方便地啟動和停止指定服務(wù). (命令行中的start和stop實際上就是通過該property來設(shè)置.) 這種能力是受selinux控制的具體的權(quán)限定義在 property.te, 有興趣可以看下, 所有的property相關(guān)權(quán)限定義都在這個文件中.

這就是為啥前面說的, persist屬性的property必須在data分區(qū)解密完成后才能加載. 同樣帶來的一個問題是, core類別的服務(wù), 最好不要使用persist屬性控制其邏輯, 如果真有需要, 得考慮data解密的問題和property重新加載更新的情況.

下面, 我們依然用strace, 來跟一下 ctl.start 的strace

1538384398111.png

rc 文件

init的主要操作是加載它的配置文件, 并執(zhí)行配置文件中的相關(guān)命令. 傳統(tǒng)上的主要配置文件 /init.rc . 但是在 最新版本的系統(tǒng)上, 其配置文件的路徑還包含 /system/etc/init , /vendor/etc/init 和 /odm/etc/init, 配置文件模塊化了, 不同服務(wù)有單獨的rc配置文件.也就是說, 前面的三個路徑是init中寫死的導(dǎo)入路徑, 其余的rc文件可以通過關(guān)鍵字import導(dǎo)入.

1538460582540.png

從上面的截圖, 我們可以看到, 有參數(shù)有兩個對象, 分別是 ActionManager 和 ServiceList, 分別對應(yīng)init需要執(zhí)行的命令列表和管理的服務(wù)列表.

import trigger(action) and service

這里我們將trigger和action合并的原因是, trigger會觸發(fā)action, 本質(zhì)相同, 而且從代碼層面看, rc文件就是分為三大語句 塊, import, trigger 和 service.

1538460863080.png

import 關(guān)鍵字修飾的就是import語句塊, 其作用是導(dǎo)入其他rc并解析執(zhí)行.

on 關(guān)鍵字修飾的就是trigger語句塊, 后面跟著一個參數(shù) -- 這個參數(shù)既可以是預(yù)先規(guī)定的各個啟動階段(boot stage) 的名稱, 也可以是property的關(guān)鍵字, 后面跟冒號加 "屬性名稱=屬性值"這樣的表達式(這種情況的意思是, 如果property被設(shè)置為當(dāng)前值, 則觸發(fā)trigger定義的action). 預(yù)先定義的啟動階段如下, 此處只給出AOSP的標(biāo)準(zhǔn)流程, 各個廠商可能會改動. 后面會提供方法查看改動后的流程.

service 關(guān)鍵字, 定義的是由init啟動的各個進程服務(wù)名字, init會根據(jù)相關(guān)的命令啟動相關(guān)服務(wù)進程, 也可以根據(jù)參數(shù)(OPTIONS) 修改服務(wù)的狀態(tài). 前文實驗的bootanim服務(wù), 就是這里定義的.

下面我們對以上三個section, 簡單展開介紹下, 基本上了解了這三個, rc文件的結(jié)構(gòu)就了解了.

我們先看一下, import關(guān)鍵字解析rc文件的順序.

1538534200435.png

從上面代碼看, 每解析完成一個rc文件, 就會按照import順序解析其import進來的rc文件. 下面看個例子

# A.rc
import b.rc
import d.rc

# b.rc
import c.rc

其真實的加載順序是, a.rc -> b.rc -> c.rc -> d.rc

我們來看下init的README.md中關(guān)于import順序的偽代碼:

fn Import(file)
  Parse(file)
  for (import : file.imports)
    Import(import)

Import(/init.rc)
Directories = [/system/etc/init, /vendor/etc/init, /odm/etc/init]
for (directory : Directories)
  files = <Alphabetical order of directory's contents>
  for (file : files)
    Import(file)

下面看下 on 關(guān)鍵字, 也就是trigger. 首先看下aosp定義的啟動階段(基本的), 除了charger的階段, 其余階段存在先后順序:

啟動階段 內(nèi)容
early-init 初始化的第一個階段券犁,用于設(shè)置selinux 和 OOM
init 創(chuàng)建文件系統(tǒng), mount節(jié)點以及寫內(nèi)核變量
late-init 觸發(fā)各種trigger
early-fs 文件系統(tǒng)準(zhǔn)備被mount前需要完成的工作
fs 專門用于加載各個分區(qū)
post-fs 在各個文件系統(tǒng)(data除外)mount完畢后執(zhí)行的命令
post-fs-data 解密/data分區(qū)(如果需要), 并掛載
early-boot 在屬性服務(wù)(property service)初始化之后, 啟動剩余內(nèi)容之前需要完成的工作
boot 正常的啟動命令
charger 當(dāng)手機處于充電模式時(關(guān)機情況下充電), 需要執(zhí)行的命令

我們單獨把 late-init 拎出來看下, 有興趣的可以更深入看下每個trigger所執(zhí)行的代碼.

1538536421223.png

相信如果看過rc文件, 都會知道每個rc文件中都有trigger, 那么同名trigger的執(zhí)行先后順序是如何的呢? 下面給個結(jié)論, 和不需要看代碼就知道順序的方法.

結(jié)論: 按照rc文件的加載順序, 執(zhí)行同名trigger底下的命令.

查看方法: 通過命令 dmesg -w 監(jiān)控啟動流程, 然后我們過濾關(guān)鍵字 "processing action", 這個關(guān)鍵字即展示了各個trigger(啟動階段)的先后順序, 也展示了各個rc文件中同名trigger的執(zhí)行順序.

下面看下 proeprty的trigger, 一個典型的例子就是 usb config, 語法很簡單, 冒號后面的property同時為true, 觸發(fā)執(zhí)行命令.

1538537060554.png

看到上面的例子, 讀者一定很想問, 既然有 && 操作符, 是否有 || 操作符呢? 很抱歉, 沒有呢.

1538537397986.png

關(guān)于trigger下面掛的action (命令集), 我們在后文再看, 我們先把最后一個語句塊 service 過一遍.

還是以 bootanim 為例子:

# /system/etc/init/bootanim.rc
service bootanim /system/bin/bootanimation
    class core animation
    user graphics
    group graphics audio
    disabled
    oneshot
    writepid /dev/stune/top-app/tasks

其格式如下:

service name  binary_path
    options

其中 name, 就是 init 維護的服務(wù)列表, 可以通過 start 和 stop 啟動和停止服務(wù).

binary_path 就是真實的可執(zhí)行文件路徑.

optinos 主要是 class, user, group 等關(guān)鍵字, 會在后文對這些關(guān)鍵字做介紹.

所有 定義了的 service, 都會被init管理, 也就是說, init會監(jiān)聽其退出的消息, 并根據(jù)options, 執(zhí)行對應(yīng)的異常處理邏輯, 此處邏輯不細(xì)講, 讀者有興趣可以自行閱讀代碼.

trigger 命令集

trigger 命令集也叫init命令集, 其實就是init.rc中所有可執(zhí)行命令. 我們重點關(guān)注下, 定義了哪些命令, 并且命令是如何被使用的.

命令列表定義在 BuiltinFunctionMap::map, 下面看個例子, 具體命令列表大家自己跳轉(zhuǎn)到源碼:

1538539567761.png

如果想追尋各個參數(shù)的含義, 可以查看代碼 FindFunctionCommand.

后面如果遇到對應(yīng)的命令, 會進行簡單的介紹. 具體各個命令的用法, 請參考init的 README.md.

service 的 option 集

option 合集定義在 Service::OptionParserMap::map, 我們重點關(guān)注下幾乎所有service都會用到的option:

  • class . 該service所屬的類別, 字符串, 如果未設(shè)置, 則默認(rèn)為 default. 可通過 class_start 來啟動某個類別的服務(wù).
  • user. 指定該進程運行時所屬用戶
  • group. 指定該進程運行時所屬的組.
  • critical. 指定該進程屬于critical類型, 該類型的服務(wù)連續(xù)4次崩潰會導(dǎo)致系統(tǒng)重啟.
  • disabled. 指定該服務(wù)默認(rèn)不啟動. 如果不設(shè)置該屬性, 服務(wù)都會默認(rèn)啟動. 原理是, 前文的命令集中有命令 class_start, 通過 class_start core , class_start main 等方法啟動服務(wù), 而 class_start 命令無法啟動 disabled 服務(wù), 這是該命令區(qū)別于普通的 start 命令的地方.
  • oneshot. 指定該服務(wù)如果退出后不自動重啟(觸發(fā)位置為子進程退出的信號處理函數(shù)). 其他情況下(比如 start 或者 class_start 命令), 依然可以再次啟動該服務(wù).
  • onrestart. 該flag表示如果服務(wù)重啟, 則重啟onrestart后面的服務(wù). 但是通過 {class_}stop 或者 {class_}reset 命令導(dǎo)致的重啟, 不會重啟onrestart定義的相關(guān)服務(wù).
1538546376921.png

額外介紹下 shutdown 選項, 目前只有參數(shù) critical, 且如果被置上該選項, 在關(guān)機重啟的流程中, 最后被結(jié)束. 按理說, servicemanager 重啟, 所有注冊的binder服務(wù)都需要重啟, 但是在模擬器上測試發(fā)現(xiàn), 重啟 servicemanager, 系統(tǒng)就無法啟動了. 應(yīng)該是跟部分native進程通信方式從socket切換到binder有關(guān).

組合鍵 keychords

1538548688289.png

keychords 也是 service 的option 字段, 這里單獨拎出來講的原因是, 這是類似后門一樣的神奇功能, 它允許用戶在按下某幾個組合鍵時, 啟動某些服務(wù). "組合鍵" 當(dāng)前被定義為在adbd啟動的情況下同時按下某些按鍵. 每個按鍵在linux的輸入子系統(tǒng)上都有對應(yīng)的掃描碼, 此處 114, 115, 116 就對應(yīng)的 VOLUME_DOWN, VOLUME_UP 和 POWER 鍵的掃描碼(掃描碼和framework KEYCODE對應(yīng)表 Generic.kl).

要支持組合鍵, 則必須存在 /dev/keychord , 且adbd進程正在運行. 如果需要可以通過改代碼, 強制該后門全局啟用.

mount 文件系統(tǒng)

文件系統(tǒng)掛載順序

本文忽略AB系統(tǒng), 如果有興趣了解, 請閱讀作者的另一篇文章, 關(guān)于AB升級和AB分區(qū).

我們先來看一下模擬器中的分區(qū)掛載.

1538552703362.png

mount 命令實際讀取的是 /proc/mounts

system 分區(qū)之前的掛載, 在init的第一階段執(zhí)行, 不詳細(xì)說明了.

而system/vendor/data分區(qū)的掛載, 由于階段的不同, 掛載信息的來源不同, 此處僅針對模擬器中的情況來進行秒數(shù), 讀者可根據(jù)相關(guān)代碼在實際的設(shè)備中嘗試研究.

system 和 vendor 的分區(qū)掛載信息保存在 /sys/bus/platform/devices/ANDR0001:00/properties/android/fstab .

1538553017765.png

從上圖可以看到, 掛載的設(shè)備路徑, 信息和文件系統(tǒng)類型.

那么這些信息的路徑數(shù)據(jù)從哪里來的?

1538553102865.png

準(zhǔn)確地說, 是傳遞給kernel的參數(shù)中, 帶了該信息學(xué).

generic_x86_64:/ # cat /proc/cmdline                                           
qemu=1 androidboot.hardware=ranchu clocksource=pit android.qemud=1 console=0 android.checkjni=1 qemu.gles=1 qemu.encrypt=1 qemu.opengles.version=196608 cma=262M androidboot.android_dt_dir=/sys/bus/platform/devices/ANDR0001:00/properties/android/ ramoops.mem_address=0xff018000 ramoops.mem_size=0x10000 memmap=0x10000$0xff018000

也就是說 system 和 vendor 是在 DoFirstStageMount 時掛載, 也是最早掛載的.

cache 和 data 分區(qū)是定義在 fstab.{hardware} 中

1538553420032.png

此處忽略帶有voldmanaged關(guān)鍵字, 因為該關(guān)鍵字是指這條掛載信息由vold管理并掛載, 而不包含該關(guān)鍵字的都是由init管理并掛載.

那么fstab是什么時候被解析和掛載的呢? 在 init.{hardware}.rc 中 on fs 階段的 mount_all 命令掛載的.

如果data分區(qū)塊加密了呢? 剛開機的時候, 會先輸入密碼, 然后再出現(xiàn)開機動畫, 最后出來真實系統(tǒng). 也就是說, 如果data分區(qū)加密了, data分區(qū)就是最后被掛載的. 我們在下節(jié)介紹該過程中, 分區(qū)掛載的一些變化.

為什么無法掛著data會導(dǎo)致安全啟動

這個問題, 其實也是觸發(fā)安全啟動的條件.我們先來看下輸密碼界面的掛載信息.

1538554098516.png

關(guān)注下data分區(qū), 當(dāng)前是掛載成tmpfs, 也就是內(nèi)存文件系統(tǒng). 換言之, 安全啟動過程中, 無法掛載data分區(qū), 然后掛載了內(nèi)存分區(qū), 用于啟動一個小的ui系統(tǒng)輸入密碼. 那么什么條件下切換到安全啟動界面呢?正常情況下, 在檢查到fstab中有加密選項, 并且分區(qū)別檢測到加密, 就會進入安全啟動邏輯. 但是還有一種異常情況, fstab中有可加密選項, 但data掛載失敗.

1538554481738.png

此處, 如果data分區(qū)無法被掛載, 并且fstab中有標(biāo)記data分區(qū)是可以被加密的, 那么就會掛載tmpfs, 并嘗試解密處理.

解密和掛載data分區(qū)的流程在 cryptfs_restart_internal , 其中, 先停止main類型的服務(wù), 然后掛載data分區(qū), 最后再重新啟動main類型的服務(wù), 全程通過property vold.decrypt 來控制服務(wù)的啟動和配置. 我們簡單看下property的變化.

在解密分區(qū)前, 設(shè)置property為 trigger_reset_main

1538555188795.png

在解密掛載data分區(qū)

1538555207271.png

在解密掛載完成后, 設(shè)置property

1538555271446.png

這里可以對應(yīng)上前文的data分區(qū)掛載后, init程序會加載 persist 屬性的property.

感興趣的同學(xué)可自行閱讀下該property的變化和 rc文件中相關(guān)的trigger, 結(jié)合前文來回答下面的問題:

  1. 在掛載data分區(qū)之前是如何關(guān)閉所有需要用到data分區(qū)的服務(wù)的?
  2. 解密掛載分區(qū)后, 又是如何重啟服務(wù)的?
  3. 是會重啟所有的main類型服務(wù)嗎? 閱讀rc文件, 找出你覺得在解密后無法被重啟的服務(wù)(假設(shè)data解密前已經(jīng)啟動)
  4. onrestart 標(biāo)記的服務(wù)是否會被觸發(fā)重啟?

總結(jié)

作為大多數(shù)守護進程的樣板, init代碼的執(zhí)行流程完全遵循建立服務(wù)的經(jīng)典模式: 初始化, 然后陷入一個循環(huán)中, 而且永遠不退出.

  • 檢查自身的可執(zhí)行程序是不是被當(dāng)初ueventd, watchdogd 或者 subcontext, 如果是, 則轉(zhuǎn)到對應(yīng)守護進程的主循環(huán)函數(shù), 后續(xù)代碼流程不執(zhí)行
  • 創(chuàng)建/dev, /proc, /sys 目錄, 并掛載
  • 掛載設(shè)備樹中定義的fstab信息, 也就是前文提到的, 掛載system分區(qū)和vendor分區(qū)
  • selinux初始化
  • 創(chuàng)建 /dev/.booting 文件, 在啟動完畢后, 該文件會被刪除(在 firmware_mounts_complete, 也就是在 early-boot之前)
  • property_init . 初始化 property service
  • 將 dt 和 kernel cmdline的信息導(dǎo)出到proeprty, 規(guī)則是將所有的 androidboot.xxxx 導(dǎo)出成 ro.boot.xxx
  • 再次初始化 selinux
  • 初始化信號處理函數(shù), 用于處理子進程退出信號
  • 加載 一堆 default.prop 文件
  • 解析所有rc文件
  • 將early-init的trigger添加到trigger隊列中
  • 將 init 的trigger 添加到 trigger隊列中
  • 根據(jù) ro.boot.mode (androidboot.mode) 來判斷是不是處于關(guān)機充電模式, 如果是, 則不掛載文件系統(tǒng)且只啟動charger服務(wù).
  • 進入主循環(huán)開始執(zhí)行trigger的命令

下面看下主循環(huán), 會循環(huán)依次執(zhí)行下面的代碼

  • 執(zhí)行 HandlePowerctlMessage, 處理 property sys.powerctl, 比如shutdown 或者 reboot
  • am.ExecuteOneCommand() , 處理前文添加的trigger的action
  • RestartProcesses() , 檢查所有已經(jīng)注冊過的服務(wù), 必要時重啟服務(wù)
  • 輪詢?nèi)缦氯齻€fd:
    • property_set_fd : 也就是前文的 /dev/socket/proeprty_service, 這個socket是用來讓想要設(shè)置某個屬性的客戶端連接并發(fā)送對應(yīng)的key/value 給 init 進程.
    • keychord_fd : 用來處理啟動服務(wù)的組合鍵
    • signal_read_fd: 它是socketpair的一端, 另一端在信號處理函數(shù)里, 在收到子進程退出信號后, 寫入一端, 則epoll等待就會返回處理子進程退出邏輯.

init 的其他角色

多個角色共用一套代碼, 利用的是軟鏈接的機制, 跟busybox一樣, 通過 argv[0] 可以獲取到執(zhí)行的文件名字(也就是軟鏈接的文件名), 從而知道需要執(zhí)行的模塊代碼.

ueventd

作為ueventd執(zhí)行時, init這個程序是用來管理硬件設(shè)備的. 它需要響應(yīng)內(nèi)核的通知(netlink), 管理/sys偽文件系統(tǒng)中與各個設(shè)備對應(yīng)的文件并負(fù)責(zé)讓各個進程通過它在/dev/底下創(chuàng)建符號鏈接指向到這些文件. 為了完成這些操作, uevnetd使用另外的初始化腳本 uevnetd.rc 和 ueventd.{hardware}.rc .這些配置文件比init的配置文件簡單多了, 只記錄了哪些文件被配置成哪些權(quán)限. ueventd 會逐條處理rc文件, 并調(diào)用HandleDeviceEvent . uevent, 還負(fù)責(zé)處理fireware的加載, 在kernel 發(fā)送 add 消息后, uevent 調(diào)用 HandleFirmwareEvent 確認(rèn)子系統(tǒng)是 firmware, 加載數(shù)據(jù)并發(fā)送.

1538567103901.png

watchdogd

和ueventd一樣, watchdogd也是init的另一面. 這時, 它被用作硬件watchdog定時器(timer) (/dev/watchdog,如果有的話)在用戶態(tài)中的接口. 設(shè)置一個超時((timeout)變量, 每隔一段時 間就發(fā)送一個keepalive信號(一個""字節(jié))偎窘。如果超過了超時變量規(guī)定的時間舆驶,watchdogd 還沒能及時發(fā)送keepalive信號乘陪,硬件watchdog定時器就會發(fā)出一個中斷,要求內(nèi)核重啟。盡 管有那么點極端的感覺泽铛,但我們要知道唯一能讓watchdogd不能及時發(fā)送keepalive信號的原因 只有系統(tǒng)已經(jīng)掛機(hang)了―這時系統(tǒng)很可能已經(jīng)無法從掛機的狀態(tài)恢復(fù)過來了,重啟設(shè)備反而算是個比較簡單的解決方案. 作為watchdogd時钳降,這個守護進程只接受兩個命令行參數(shù):interval和margin―這兩個參 數(shù)的初始值都是10厚宰,單位是“秒”。設(shè)備的總超時時間((timeout變量的值)是這兩個參數(shù)之和 (即,默認(rèn)值為20秒).不過在超時沒有接收到信息后铲觉,系統(tǒng)并不會馬上重啟澈蝙,而是會再稍微等 上一小會(給系統(tǒng)最后一次補救的機會).

init subcontext

該模塊是在android P(9.0) 才引入的, 是為了完全隔離 system 和 vendor. 相信使用P的童鞋應(yīng)該都有經(jīng)歷過, 某系property 無法設(shè)置或者無法讀取了 (selinux權(quán)限限制), 這些都是這個模塊干的.


1538578048683.png

紅色框內(nèi)的就是該模塊.

從上圖看, 該模塊是由init創(chuàng)建的, 我們來看下該模塊是如何創(chuàng)建的和它的參數(shù)含義.

在init的main函數(shù)中, 有如下的代碼, 用于初始化 subcontext

subcontexts = InitializeSubcontexts();

該函數(shù)實現(xiàn)如下:

1538576209865.png

依據(jù)下面的路徑和信息, 創(chuàng)建對應(yīng)的 subcontexts, 此處會創(chuàng)建2個subcontexts進程, 分別用于處理 vendor 下的rc腳本和 odm 下的rc腳本. 其主要原理就是, 如果rc文件是在vendor/odm底下, 且命令需要subcontexts, 就將命令轉(zhuǎn)交給 對應(yīng)的 subcontexts 模塊處理.

1538576247139.png

我們看下subcontexts的Fork代碼.

1538576396389.png

通過socketpair, 創(chuàng)建init和 subcontexts 模塊間的通信.

下面我們看使用的地方, 按照我們的理解, 是要完全隔離system和vendor的權(quán)限, 那么, 主要動的是trigger.

1538576540373.png

這里是創(chuàng)建parser的地方, 我們看到, 主要動的其實是trigger和 service.

我們先看下 service , 主要控制的是哪些權(quán)限.

1538576693615.png

控制service重啟時, 所需要執(zhí)行的命令的權(quán)限.

我們再看看 ActionParser.

1538577672410.png

最終與service一樣, 如果是在vendor/odm 底下的rc中的action, 都會被指定 action_subcontext .

我們看了subcontexts 的啟動, 和 區(qū)分是普通init還是subcontexts執(zhí)行的標(biāo)準(zhǔn)后, 我們看下, 一個vendor底下的action, 如何被執(zhí)行的.

1538577857424.png

調(diào)用subcontexts 的 Execute 函數(shù), 其實就是通過socketpair, 通知 另一個進程幫忙執(zhí)行, 然后將結(jié)果返回.

execute_in_subcontext_ 該變量就是前文 [trigger 命令集] 中參數(shù)的第三列, true 為該命令需要通過subcontext執(zhí)行, false 為不用.

![1538578048683.png](https://upload-images.jianshu.io/upload_images/14428287-47b84b2009e1e0af.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

這就是為啥 剛從 O 升級 到 P后, property 包括 rc中的一些其他命令, 會出現(xiàn)selinux權(quán)限問題

type=1400 audit(1511821362.996:9): avc: denied { search } for pid=540 comm="init" name="nfc" dev="sda45" ino=1310721 scontext=u:r:vendor_init:s0 tcontext=u:object_r:nfc_data_file:s0 tclass=dir permissive=0
init: Command 'write /data/nfc/bad_file_access 1234' action=boot (/vendor/etc/init/hw/init.walleye.rc:422) took 2ms and failed: Unable to write to file '/data/nfc/bad_file_access': open() failed: Permission denied
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市撵幽,隨后出現(xiàn)的幾起案子灯荧,更是在濱河造成了極大的恐慌,老刑警劉巖盐杂,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件逗载,死亡現(xiàn)場離奇詭異,居然都是意外死亡链烈,警方通過查閱死者的電腦和手機厉斟,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來强衡,“玉大人擦秽,你說我怎么就攤上這事′銮冢” “怎么了感挥?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長越败。 經(jīng)常有香客問我触幼,道長,這世上最難降的妖魔是什么究飞? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任置谦,我火速辦了婚禮,結(jié)果婚禮上噪猾,老公的妹妹穿的比我還像新娘霉祸。我一直安慰自己,他們只是感情好袱蜡,可當(dāng)我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布丝蹭。 她就那樣靜靜地躺著,像睡著了一般坪蚁。 火紅的嫁衣襯著肌膚如雪奔穿。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天敏晤,我揣著相機與錄音贱田,去河邊找鬼。 笑死嘴脾,一個胖子當(dāng)著我的面吹牛男摧,可吹牛的內(nèi)容都是我干的蔬墩。 我是一名探鬼主播,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼耗拓,長吁一口氣:“原來是場噩夢啊……” “哼拇颅!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起乔询,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤樟插,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后竿刁,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體黄锤,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年食拜,在試婚紗的時候發(fā)現(xiàn)自己被綠了鸵熟。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡负甸,死狀恐怖旅赢,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情惑惶,我是刑警寧澤,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布短纵,位于F島的核電站带污,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏香到。R本人自食惡果不足惜鱼冀,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望悠就。 院中可真熱鬧千绪,春花似錦、人聲如沸梗脾。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽炸茧。三九已至瑞妇,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間梭冠,已是汗流浹背辕狰。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留控漠,地道東北人蔓倍。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親偶翅。 傳聞我的和親對象是個殘疾皇子默勾,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,577評論 2 353

推薦閱讀更多精彩內(nèi)容