1、Android Init Language
Android Init Language,也就是Android初始化語言,就是init.rc文件的語法
Android 初始化語言包含五種主要的語句類:Actions, Commands, Services, Options, and Imports.(操作,命令,服務,選項和導入)
所有這些都是面向行的,由空格分隔的標記組成。可以使用c風格的反斜槓轉義將空白插入到令牌中。使用雙引號還可以用於防止空格將文本分割成多個標記。當反斜槓是一行中的最後一個字符時,它可以用於行摺疊。
以#開頭的行(允許前導空格)是註釋。可以使用語法擴展系統屬性“$ {property.name}”。這也適用於連接的上下文中需要,例如“import/init.recovery.${ro.hardware}.rc”。
Actions(操作)和Services(服務)隱式地聲明一個新的部分。所有命令或選項屬於最近聲明的部分。命令或第一部分之前的選項將被忽略。而服務有唯一的名稱。如果定義了第二個服務,如果名稱與現有名稱相同,則會忽略它並出現錯誤消息記錄。
2、Init .rc文件
init 語言在純文本文件中使用,該文件採用.rc文件擴展。通常有多個這些在多個系統上的位置,如下所述:
/init.rc 是主要的.rc文件,由init可執行文件在開始執行的時候進行加載,負責初始的建立系統。
通過第一階段 first stage 掛載機制掛載 /system,/vendor 的設備在加載完主要的/init.rc 之後就會去馬上加載 / {system,vendor,odm} / etc / init / 目錄下的所有文件
沒有第一階段掛載機制的舊式設備會執行以下操作:
1. /init.rc 中進行 import /init.${ro.hardware}.rc 去導入,這是主要的供應商提供的.rc文件。
2.在 mount \ _all 命令期間,init可執行文件將加載所有 / {system,vendor,odm} / etc / init / 目錄中包含的文件。
這些目錄適用於之後使用的所有操作和服務文件系統安裝。
可以在mount\_all命令行中指定路徑來導入它.rc文件,而不是上面列出的默認路徑。這主要用於支持工廠模式和其他非標準啓動模式。這三個默認路徑應該用於正常的引導過程。
這些目錄的目的是:
1. /system/etc/init/ 用於核心系統項,例如SurfaceFlinger、MediaService和logcatd。
2. /vendor/etc/init/ 是針對SoC供應商項目,例如action或核心SoC功能所需的守護進程。
3./odm/etc/init/ 是設備製造商的項目,如動作傳感器或其他外設所需的動作或守護進程功能。
所有二進制文件駐留在系統、供應商或odm分區上的服務都應該將其服務項放置到對應的init .rc文件,位於它們所在分區的/etc/init/目錄中。有一個構建系統宏LOCAL\_INIT\_RC,它爲開發人員處理這個問題。每個init .rc文件還應該包含與之相關的任何操作它的服務。
一個例子是位於 system/core/logcat 目錄中的 logcatd.rc 和 Android.mk 文件。在構建過程中,Android.mk 文件中的LOCAL\_INIT\_rc 宏將 logcatd.rc 放置在 /system/etc/init/ 中。init 在 mount\_all 命令期間加載 logcatd.rc,並允許在適當情況下運行服務和排隊操作。
根據init.rc文件的守護進程來拆分init.rc文件比以前使用的單塊init.rc文件更可取。此方法可確保 init 讀取的唯一服務項和 init 執行的唯一操作對應於文件系統上實際上存在二進制文件的服務,而單塊 init .rc 文件則不這樣。此外,當多個服務添加到系統時,這還有助於合併衝突解決,因爲每個服務都將進入單獨的文件。
mount\_all命令中有兩個選項“early”和“late”,可以在可選路徑之後設置。使用“--early”設置,init可執行文件將跳過帶有“latemount”標誌的掛載項,並觸發fs加密狀態事件。設置爲“--late”時,init可執行文件將只掛載帶有“latemount”標誌的項,但跳過導入RC文件。默認情況下,不設置任何選項,mount\_all將處理fstab中給定的所有條目。
- Actions(操作)
Actions 被命名爲命令序列。 Actions有一個trigger觸發器,該觸發器用於確定操作的執行時間。 當事件發生與Action的trigger觸發器匹配,該Action將添加到要執行的隊列的尾部(除非它已經在隊列)。
隊列中的每個 action 按順序排列,action 中的每個命令都按順序執行。init 在執行命令的活動之間處理其他活動(設備創建/銷燬、屬性設置、進程重新啓動)。
Actions 的格式:
on <trigger> [&& <trigger>]*
<command>
<command>
<command>
Actions 將添加到隊列中,並根據解析包含它們的文件的順序執行(請參閱Imports部分),然後在單個文件中依次執行。
例如如果一個文件包含:
on boot
setprop a 1
setprop b 2
on boot && property:true=true
setprop c 1
setprop d 2
on boot
setprop e 1
setprop f 2
然後,當‘boot`觸發器發生並且假設屬性’true‘等於`true’時,那麼執行的命令的順序是:
setprop a 1
setprop b 2
setprop c 1
setprop d 2
setprop e 1
setprop f 2
- Services (服務)
服務是 init 啓動和(可選)在退出時重新啓動的程序。
服務的形式有:
service <name> <pathname> [ <argument> ]*
<option>
<option>
...
- Options(選項)
Options(選項)是services的修飾符。它們影響init如何以及何時運行service。
`console [<console>]`
> 這個服務需要一個控制檯。可選的第二個參數選擇一個特定的控制檯,而不是默認的。
可以通過設置“androidboot.sole”內核參數來更改默認的“/dev/控制檯”。
在所有情況下,都應該省略前面的“/dev/”,因此“/dev/tty 0”將被指定爲“Console tty 0”。
`critical`
> 這是一個設備關鍵服務。如果在四分鐘內退出超過四次,設備將重新啓動進入恢復模式。
`disabled`
> 這個服務不會從它的類去自動啓動。它必須通過名稱顯式地啓動。
`setenv <name> <value>`
> 在啓動的進程中,將環境變量_name_設置爲_value_。
`socket <name> <type> <perm> [ <user> [ <group> [ <seclabel> ] ] ]`
> 創建一個名爲 /dev/socket/_name_的 unix 域套接字,並將其 fd 傳遞給啓動的進程。
_type_必須是“dgram”、“stream”或“seqpacket”。
user 和 group 默認爲0。
“seclabel” 是套接字的 SELinux 安全上下文。
它默認爲服務安全上下文,由 seclabel 指定或基於服務可執行文件安全上下文計算。
有關本機可執行文件,請參見 libcutils android\_get\_control\_socket()
`enter_namespace <type> <path>`
>進入位於_path_的_type_類型的名稱空間。將_type_設置爲“net”只支持網絡名稱空間。
注意,只能輸入給定_type_的一個名稱空間。
`file <path> <type>`
> 打開一個文件路徑並將其fd傳遞給啓動的進程。
_type_必須是“r”、“w”或“rw”。
有關本機可執行文件,請參見libcutils android\_get\_control\_file()。
`user <username>`
> 在執行此服務之前更改爲“username”。當前默認爲root。(???可能應該對任何人都不違約)。
對於Android M,進程應該使用這個選項,即使它們需要Linux功能。
以前,爲了獲得Linux功能,進程需要以根用戶身份運行,請求這些功能,然後將其放到所需的uid。
fs\_config提供了一種新的機制,允許設備製造商將Linux功能添加到應該使用的文件系統的特定二進制文件中。
這個機制在<http://source.android.com/devices/tech/config/filesystem.html>中描述。
在使用這種新機制時,進程可以使用user選項來選擇所需的uid,而不必作爲根用戶運行。
與Android O一樣,進程也可以直接在.rc文件中請求功能。
參見下面的“capabilities”(功能)選項。
`group <groupname> [ <groupname>\* ]`
> 在執行此服務之前更改爲"groupname"。
除第一個組名稱之外的其他組名稱用於設置進程的補充組(通過 setgroup())。
當前默認爲 root。 (???可能應該默認爲none)
`capabilities <capability> [ <capability>\* ]`
> 執行此服務時設置功能。
“capability”(功能)應該是一個沒有“CAP\_”前綴的Linux功能,比如“NET\_ADMIN”或“SETPCAP”。
有關Linux功能的列表,請參見http://man7.org/linux/man7/pages/man7/capabilities.7.html。
`setrlimit <resource> <cur> <max>`
> 將給定的rlimit應用於服務。
rlimit由子進程繼承,因此這可以有效地將給定的rlimit應用於此服務啓動的進程樹。
它的解析類似於下面指定的setrlimit命令。
`seclabel <seclabel>`
> 執行此服務前更改爲“seclabel”。
主要用於從rootfs運行的服務,如ueventd, adbd。
系統分區上的服務可以根據其文件安全上下文使用策略定義的轉換。
如果沒有指定,並且在策略中沒有定義轉換,則默認爲init上下文。
`oneshot`
> 服務退出時不要重新啓動服務。
`class <name> [ <name>\* ]`
> 爲服務指定類名。命名類中的所有服務都可以一起啓動或停止。
如果沒有通過類選項指定服務,則服務處於“默認”類中。
除了(必需的)第一個類名以外的其他類名用於對服務進行分組。
`animation class`
> “animation”類應該包括所有啓動動畫和關機動畫所需的服務。
由於這些服務可以在啓動過程中很早就啓動,並且可以運行到關閉的最後階段,因此無法保證對/數據分區的訪問。
這些服務可以檢查/data下的文件,但它不應該保持文件處於打開狀態,當/data不可用時應該可以工作。
`onrestart`
> 當服務重新啓動時,執行一個命令。
`writepid <file> [ <file>\* ]`
> 當它分叉派生出來時,將子進程的 pid 寫入給定的文件。
用於 cgroup/cpuset 的使用。
如果沒有指定 /dev/cpuset/ 下的文件,但是系統屬性 “ ro.cpuset.default ” 被設置爲非空的 cpuset 名稱
(比如:'/前臺')時,那麼將 pid 寫入文件 /dev/cpuset/_cpuset\_name_/tasks。
`priority <priority>`
> 調度服務進程的優先級。這個值必須在-20到19之間。默認優先級爲0。
通過setpriority()設置優先級。
`namespace <pid|mnt>`
> 在創建服務時輸入新的PID或裝載命名空間
`oom_score_adjust <value>`
> 將子程序的/proc/Self/oom\_core\_adj設置爲指定的值,該值必須從-1000到1000。
`memcg.swappiness <value>`
> 將子節點的 memory.swappiness 設置爲指定的值(僅在掛載memcg時),該值必須等於或大於0。
memcg可參考:https://blog.csdn.net/pillarbuaa/article/details/79207036 和 https://segmentfault.com/a/1190000008125359
memory.swappiness:
設置和顯示當前的swappiness,該文件的值默認和全局的swappiness
(/proc/sys/vm/swappiness)一樣,修改該文件只對當前cgroup生效,
其功能和全局的swappiness一樣.有一點和全局的swappiness不同,那就是:
如果這個文件被設置成0,就算系統配置的有交換空間,當前cgroup也不會使用交換空間
`memcg.soft_limit_in_bytes <value>`
> 將子節點的 memory.soft_limit_in_bytes 設置爲指定的值(僅在掛載memcg時),該值必須等於或大於0。
memory.soft_limit_in_bytes: 設置/顯示當前限制的內存軟額度
`memcg.limit_in_bytes <value>`
> 將子節點的 memory.limit_in_bytes 設置爲指定的值(僅在掛載memcg時),該值必須等於或大於0。 memory.limit_in_bytes :設置/顯示當前限制的內存額度
`shutdown <shutdown_behavior>`
> 設置服務進程的關閉行爲。
如果未指定此參數,則在關閉過程中使用SIGTERM和SIGKILL關閉服務。
在關機超時之前,具有shutdown_behavior爲“critical”的服務不會在關機期間被關閉。
當關機超時時,即使標記爲“關鍵關機”的服務也將被關閉。
當關閉啓動時標記爲“shutdown critical”的服務不運行時,它將被啓動。
- Triggers(觸發器)
觸發器是字符串,可以用來匹配某些類型的事件,並用於導致一個動作發生。
觸發器分爲事件觸發器和屬性觸發器。
事件觸發器是由 'trigger' 命令或 init 可執行文件中的 QueueEventTrigger() 函數觸發的字符串。
它們以簡單字符串的形式出現,如 'boot' 或 'late-init'。
屬性觸發器是在命名屬性將值更改爲給定的新值或命名屬性將值更改爲任何新值時觸發的字符串。
它們的形式分別是 'property:<name>=<value>' 和 'property:<name>=\*'。
屬性觸發器在init的初始啓動階段將被計算並相應地觸發。
一個操作可以有多個屬性觸發器,但可能只有一個事件觸發器。
例如:
`on boot && property:a=b` 定義了一個只有在 'boot' 事件觸發且屬性a=b時纔會執行的動作。
`on property:a=b && property:c=d` 定義了一個動作,執行三次:
1. 在初始啓動期間,如果屬性a=b和屬性c=d。
2. 當屬性a轉換爲值b時,而屬性c已經等於d。
3.當屬性c轉換爲值d時,而屬性a已經等於b。
- Commands(命令)
`bootchart [start|stop]`
> 啓動/停止bootchart。
這些在默認的init.rc文件中出現,但只有當 /data/bootchart/enabled 存在時,
bootchart 纔會被激活;否則,啓動/停止 bootchart 是無效的。
`chmod <octal-mode> <path>`
> 更改文件訪問權限
`chown <owner> <group> <path>`
> 更改文件所有者和組
`class_start <serviceclass>`
> 如果指定類的所有服務尚未運行,則啓動它們。
有關啓動服務的更多信息,請參見啓動條目。
`class_stop <serviceclass>`
> 如果指定類的所有服務當前正在運行,則停止並禁用他們
`class_reset <serviceclass>`
> 如果指定類的所有服務當前正在運行,則停止它們,但不禁用它們。
稍後可以使用' class_start '重新啓動它們。
`class_restart <serviceclass>`
> 重新啓動指定類的所有服務
`copy <src> <dst>`
> 拷貝一個文件。與寫類似,但對二進制/大量數據很有用。
對於src文件,不允許從符號鏈接文件和全局可寫或組可寫的文件中複製。
對於dst文件,如果它不存在,則創建的默認模式是0600。
如果dst文件是一個普通的常規文件並且已經存在,那麼它將被截斷。
`domainname <name>`
> 設置域名。
`enable <servicename>`
> 將禁用的服務轉換爲啓用的服務,就好像服務沒有指定禁用一樣。
如果服務應該正在運行,那麼它現在就會啓動。
通常在引導加載程序設置一個變量時使用,該變量指示應在需要時啓動特定的服務。如:
on property:ro.boot.myfancyhardware=1
enable my_fancy_service_for_my_fancy_hardware
`exec [ <seclabel> [ <user> [ <group>\* ] ] ] -- <command> [ <argument>\* ]`
> 使用給定的參數 fork 一個進程並且執行 command。
命令在 " -- " 之後開始,以便提供可選的安全上下文、用戶和補充組。
在這個命令完成之前,不會運行其他命令。
_seclabel_ 可以是一個-來表示默認值。
屬性在 _argument_ 中展開。
Init 暫停執行命令,直到 fork 的進程退出。
`exec_background [ <seclabel> [ <user> [ <group>\* ] ] ] -- <command> [ <argument>\* ]`
> 使用給定的參數 fork 一個進程並執行命令。
處理方式與 "exec" 命令類似。
區別在於,在進程退出 `exec_background`之前,init 不會停止執行命令。用的好的話,這裏可適當的減少系統啓動的時間
`exec_start <service>`
> 啓動一個給定的服務,並停止其他 init 命令的處理,直到它返回。
該命令的功能類似於' exec '命令,但使用現有的服務定義代替exec參數向量。
`export <name> <value>`
> 在全局環境中將環境變量 _name_ 設置爲 _value_ (執行此命令後啓動的所有進程都將繼承該變量)
`hostname <name>`
> 設置主機名
`ifup <interface>`
> 使網絡接口 _interface_ 聯機
`insmod [-f] <path> [<options>]`
> 使用指定的選項在 _path_ 安裝模塊。
-f :強制安裝模塊,即使運行的內核版本和編譯模塊的內核版本不匹配。
`load_all_props`
> 從/system、/vendor等加載屬性。 這包括在默認init.rc中。
`load_persist_props`
> 在/data 被解密後加載 persistent 持久屬性。這包括在默認init.rc中
`loglevel <level>`
> 將內核日誌級別設置爲level。屬性在_level_中展開
`mkdir <path> [mode] [owner] [group]`
> 在 _path_ 處創建一個目錄,可選地使用給定的模式、所有者和組。
如果沒有提供,則使用權限755創建目錄,並由根用戶和根組擁有。
如果提供了,如果目錄已經存在,模式、所有者和組將被更新。
`mount_all <fstab> [ <path> ]\* [--<option>]`
> 在給定的 fs\_mgr-format fstab 上調用 fs\_mgr\_mount\_all,並在指定的路徑上
(例如,在剛剛掛載的分區上)導入.rc文件,並使用可選選項“early”和“late”。
有關詳細信息,請參閱“Init .rc文件”一節
`mount <type> <device> <dir> [ <flag>\* ] [<options>]`
> 嘗試在_dir_目錄下掛載命名的device設備
_flag_s包括“ro”、“rw”、“remount”、“noatime”、…
_options_包括"barrier=1", "noauto\_da\_alloc", " ",… 作爲逗號分隔的字符串,
例如: barrier=1,noauto\_da\_alloc
`restart <service>`
> 停止並重新啓動正在運行的服務,如果服務當前正在重新啓動,則不執行任何操作,否則,它只是啓動該服務。
`restorecon <path> [ <path>\* ]`
> 將_path_命名的文件恢復到 file\_contexts 配置中指定的安全上下文。
init.rc 創建的目錄不需要,因爲這些會被init自動正確地標記。
`restorecon_recursive <path> [ <path>\* ]`
> 遞歸地將_path_命名的目錄樹恢復到 file\_contexts 配置中指定的安全上下文
`rm <path>`
> 調用給定路徑上的 unlink(2)。您可能想要使用“exec—rm…”來代替(假設系統分區已經掛載)
`rmdir <path>`
> 在給定的路徑上調用 rmdir(2)
`readahead <file|dir> [--fully]`
> 對給定目錄中的文件調用 readahead(2),也就是預讀取。使用選項--fully 讀取完整的文件內容
`setprop <name> <value>`
> 將系統屬性_name_設置爲_value_。屬性在_value_中展開
`setrlimit <resource> <cur> <max>`
> 設置資源的rlimit。這適用於在設置限制之後啓動的所有進程。它打算在init的早期設置並全局應用。
_resource_最好使用它的文本表示('cpu'、'rtio'等或'RLIM_CPU'、'RLIM_RTIO'等)來指定。
它也可以被指定爲資源enum所對應的int值。
`start <service>`
> 如果服務尚未運行,則啓動它
注意,這是不同步的,即使它是,也不能保證操作系統的調度器將充分執行服務,以保證服務狀態的任何信息。
> 這就產生了一個重要的結果,如果服務爲其他服務提供了功能,例如提供了一個通信通道,
那麼僅僅在這些服務之前啓動這個服務不足以保證在這些服務請求之前就已經設置好了這個通道。
必須有一個單獨的機制來做出這樣的保證。
`stop <service>`
> 如果服務當前正在運行,則停止它的運行
`swapon_all <fstab>`
> 在給定的fstab文件上調用fs\_mgr\_swapon\_all
`symlink <target> <path>`
> 使用_target_值在_path_處創建一個符號鏈接
`sysclktz <mins_west_of_gmt>`
> 設置系統時鐘基數(如果系統時鐘以GMT計時,則爲0)
`trigger <event>`
> 觸發一個事件。用於從另一個操作對一個操作進行排隊
`umount <path>`
> 卸載安裝在該路徑上的文件系統
`verity_load_state`
> 用於加載dm-verity狀態的內部實現細節
`verity_update_state <mount-point>`
> 內部實現細節,用於更新dm-verity狀態並設置 adb remount 所使用的
partition._mount-point_.verified屬性,因爲fs\_mgr不能自己直接設置它們
`wait <path> [ <timeout> ]`
> 輪詢給定文件是否存在,並在找到時返回,否則超時已到。如果未指定超時,則默認爲5秒。
`wait_for_prop <name> <value>`
> 等待系統屬性_name_變爲_value_。屬性在_value_中展開。
如果屬性_name_已經設置爲_value_,則立即繼續。
`write <path> <content>`
> 在_path_打開文件,用write(2)向它寫入一個字符串。
如果文件不存在,它將被創建。
如果它確實存在,它將被截斷。屬性在_content_中展開
- Imports(導入)
`import <path>`
> 解析 init 配置文件,擴展當前配置。
如果 _path_ 是一個目錄,則該目錄中的每個文件都被解析爲一個配置文件。
它不是遞歸的,嵌套目錄不會被解析。
import 關鍵字不是命令,而是它自己的部分,
這意味着它不是作爲操作的一部分發生的,而是作爲正在解析的文件處理的,並遵循以下邏輯。
init可執行文件只有三次導入.rc文件:
1.當它在初始引導期間導入 /init.rc 或屬性 'ro.boot.init_rc' 所指示的腳本時。
2.當它在導入 /init.rc 之後立即導入 /{ system,vendor,odm}/etc/init/ 用於第一階段安裝設備時。
3.在 mount_all 期間,當它在指定路徑上導入 /{system,vendor,odm}/etc/init/ 或 .rc 文件時。
由於遺留原因和保持向後兼容性,導入文件的順序有點複雜。它沒有得到嚴格的保證。
確保命令在一個不同的命令之前運行的唯一正確方法是:
1.將它放在一個帶有先前執行的觸發器的Action中,
或者
2、將它放在一個具有相同觸發器的Action中,該觸發器位於同一文件中的一個較早的行中。
儘管如此,第一階段安裝設備的實際順序是:
1. /init.rc 被解析,然後遞歸地解析它的每個導入。
2. /system/etc/init/ 的內容按字母順序排列和解析,每個文件解析後遞歸地進行導入。
3.對於 /vendor/etc/init 重複執行第2步,然後是 /odm/etc/init
以下僞代碼可以更清楚地解釋這一點:
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)
- Properties
Init 通過以下屬性提供有關它負責的服務的信息。
`init.svc.<name>`
• 命名服務的狀態("stopped", "stopping", "running", "restarting")
- Boot timing 啓動時間
Init在系統屬性中記錄一些啓動計時信息。
`ro.boottime.init`
> 在啓動後以ns爲單位(通過時鐘\_BOOTTIME時鐘),初始化的第一階段開始的時間。
`ro.boottime.init.selinux`
> 初始化SELinux花費了多長時間?
`ro.boottime.init.cold_boot_wait`
> init等待ueventd的冷啓動階段結束的時間。
`ro.boottime.<service-name>`
> 啓動後以ns爲單位(通過時鐘\_BOOTTIME時鐘),服務首次啓動的時間。
- Debugging init
默認情況下,由 init 執行的程序將會輸出 stdout 和 stderr 到/dev/null。
爲了幫助調試,您可以通過 Android 程序日誌包裝器執行程序。這將重定向到 Android 日誌記錄系統(通過 logcat 訪問)。
例如
service akmd /system/bin/logwrapper /sbin/akmd
init.rc文件加打印:
write /dev/kmsg "debug ***"
然後在kernel log中可以找到加的打印