Android系統啟動——3init.rc解析

本次系列的內容如下:

Android啟動流程——1 序言、bootloader引導與Linux啟動
Android系統啟動——2 init進程
Android系統啟動——3 init.rc解析
Android系統啟動——4 zyogte進程
Android系統啟動——5 zyogte進程(Java篇)
Android系統啟動——6 SystemServer啟動
Android系統啟動——7 附錄1:Android屬性系統
Android系統啟動——8 附錄2:相關守護進程簡介

本篇文章的主要內容如下:

  • 1、init.rc文件格式
  • 2、init.rc腳本語法簡介
  • 3、init.rc
  • 4、init.rc文件的解析
  • 5、init.rc腳本語法簡介
  • 6、init總結

一、init.rc文件格式

init.rc文件是以“塊”(section)為單位服務的,,一個“塊”(section)可以包含多行。“塊”(section)分成兩大類:一類稱為"動作(action)",另一類稱為“服務(service)”。

  • 動作(action):以關鍵字"on" 開頭,表示一堆命令
  • 服務(service):以關鍵字“service”開頭,表示啟動某個進程的方式和參數

"塊"(section)以關鍵字"on"或者"service"開始,直到下一個"on"或者"service"結束,中間所有行都屬于這個"塊"(section)

PS:空行或注釋行沒有分割作用,注釋用'#'開始

無論是“動作(action)”塊還是“服務(service)”塊,并不是按照文件中的編碼排序逐一執行的

二、init.rc腳本語法簡介

上面說到了init.rc的腳本語法很簡答,那我們就來簡單的了解下

為了讓我們更好的理解init.rc腳本,我們先來回顧下init.rc腳本的語法。關于init.rc的腳本介紹比較少,目前網絡的主流推薦的文檔就是init/readme.txt,大家可以點擊進去看下。

一個init.rc腳本由4個類型的聲明組成,即

  • Action——行為/動作
  • commands——命令/啟動
  • services—— 服務
  • Options—— 選項

Action(動作)和service(服務)暗示著一個新語句的開始,這兩個關鍵字后面跟著commands(命令)或者options(選項)都屬于這個新語句。

PS:如果Action(動作)和services(服務)有唯一的名字,如果出現和已有動作或服務重名的,將會被當成錯誤忽略掉。

這里補充一下語法:

  • C語言風格的反斜杠轉義字符("")可以用來為參數添加空格。
  • 關鍵字和參數以空格分隔,每個語句以行為單位。
  • 行尾的反斜杠用來表示下面一行是同一行
  • 為了防止字符串中的空格把其切割成多個部分,我們需要對其使用雙引號
  • 注釋以“#”開頭

下面我們就來依次簡單介紹下

(一)、Action(動作)

動作的一般格式如下:

on  <trigger>           ## 觸發條件
      <command>     ##執行命令
      <command1>   ##可以執行多個命令

從上面,我們可以知道,一個Action其實就是響應某事件的過程。即當<trigger>所描述的觸發事件產生時,依次執行各種command(同一事件可以對應多個命令)。從源碼實現的角度來說,就會把它加入"命令執行隊列"的尾部(除非這個Action在隊列中已經存在了),然后系統再對這些命令按順序執行。

1、觸發器(trigger)

在"動作"(action)里面的,on后面跟著的字符串是觸發器(trigger),trigger是一個用于匹配某種事件類型的字符串,它將對應的Action的執行。

觸發器(trigger)有幾種格式:

  • 1、最簡單的一種是一個單純的字符串。比如“on boot”。這種簡單的格式可以使用命令"trigger"來觸發。
  • 2、還有一種常見的格式是"on property<屬性>=<值>"。如果屬性值在運行時設成了指定的值,則"塊"(action)中的命令列表就會執行。

常見的如下:

  • on on early-init:在初始化早期階段觸發
  • on init:在初始化階段觸發
  • on late-init:在初始化晚期階段觸發
  • on boot/charger:當系統啟動/充電時觸發
  • on property:當屬性值滿足條件時觸發

(二)、commands(命令)

command是action的命令列表中的命令,或者是service中的選項 onrestart 的參數命令
命令將在所屬事件發生時被一個個地執行
下面羅列出init中定義的一些常見事件


image.png

針對這些事件,如下表命令可供使用


image.png

(三)、services 服務

"服務"(service)的關鍵字 service后面跟著的是服務名稱。我們可以使用"start"命令加"服務名稱"來啟動一個服務。關鍵字以下的行稱為"選項",沒一個選項占用一行,選項也有多種,常見的"class"表示服務所屬的類別。

services 其實是可執行程序,它們在特定選項的約束下是被init程序運行或者重啟(service可以在配置中指定是否需要退出重啟,這樣當service出現異常crash時就可以有機會復原)

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

我們簡單的解釋上面的參數

  • <name>:表示此service的名稱
  • <pathname>:此service所在路徑。因為是可執行文件,所以一定有存儲路徑
  • <argument>:啟動service所帶的參數
  • <option>:對此service的約束選項

(四)、options(選項)

options是Service的修訂項。它們決定一個Service何時以及如何運行。
services中可用選項如下表


image.png

default: 意味著disabled=false,oneshot=false,critical=false。

三、init.rc

init.rc地址

# Copyright (C) 2012 The Android Open Source Project
#
# IMPORTANT: Do not create world writable files or directories.
# This is a common source of Android security bugs.
#

import /init.environ.rc
import /init.usb.rc
import /init.${ro.hardware}.rc
import /init.usb.configfs.rc
import /init.${ro.zygote}.rc
import /init.trace.rc

on early-init
    # Set init and its forked children's oom_adj.
    write /proc/1/oom_score_adj -1000

    # Set the security context of /adb_keys if present.
    restorecon /adb_keys

    start ueventd

on init
    sysclktz 0

    # Backward compatibility.
    symlink /system/etc /etc
    symlink /sys/kernel/debug /d

    # Link /vendor to /system/vendor for devices without a vendor partition.
    symlink /system/vendor /vendor

    # Create cgroup mount point for cpu accounting
    mkdir /acct
    mount cgroup none /acct cpuacct
    mkdir /acct/uid

    # Create cgroup mount point for memory
    mount tmpfs none /sys/fs/cgroup mode=0750,uid=0,gid=1000
    mkdir /sys/fs/cgroup/memory 0750 root system
    mount cgroup none /sys/fs/cgroup/memory memory
    write /sys/fs/cgroup/memory/memory.move_charge_at_immigrate 1
    chown root system /sys/fs/cgroup/memory/tasks
    chmod 0660 /sys/fs/cgroup/memory/tasks
    mkdir /sys/fs/cgroup/memory/sw 0750 root system
    write /sys/fs/cgroup/memory/sw/memory.swappiness 100
    write /sys/fs/cgroup/memory/sw/memory.move_charge_at_immigrate 1
    chown root system /sys/fs/cgroup/memory/sw/tasks
    chmod 0660 /sys/fs/cgroup/memory/sw/tasks

    mkdir /system
    mkdir /data 0771 system system
    mkdir /cache 0770 system cache
    mkdir /config 0500 root root

    # Mount staging areas for devices managed by vold
    # See storage config details at http://source.android.com/tech/storage/
    mkdir /mnt 0755 root system
    mount tmpfs tmpfs /mnt mode=0755,uid=0,gid=1000
    restorecon_recursive /mnt

    mkdir /mnt/secure 0700 root root
    mkdir /mnt/secure/asec 0700 root root
    mkdir /mnt/asec 0755 root system
    mkdir /mnt/obb 0755 root system
    mkdir /mnt/media_rw 0750 root media_rw
    mkdir /mnt/user 0755 root root
    mkdir /mnt/user/0 0755 root root
    mkdir /mnt/expand 0771 system system

    # Storage views to support runtime permissions
    mkdir /storage 0755 root root
    mkdir /mnt/runtime 0700 root root
    mkdir /mnt/runtime/default 0755 root root
    mkdir /mnt/runtime/default/self 0755 root root
    mkdir /mnt/runtime/read 0755 root root
    mkdir /mnt/runtime/read/self 0755 root root
    mkdir /mnt/runtime/write 0755 root root
    mkdir /mnt/runtime/write/self 0755 root root

    # Symlink to keep legacy apps working in multi-user world
    symlink /storage/self/primary /sdcard
    symlink /mnt/user/0/primary /mnt/runtime/default/self/primary

    # memory control cgroup
    mkdir /dev/memcg 0700 root system
    mount cgroup none /dev/memcg memory

    write /proc/sys/kernel/panic_on_oops 1
    write /proc/sys/kernel/hung_task_timeout_secs 0
    write /proc/cpu/alignment 4

    # scheduler tunables
    # Disable auto-scaling of scheduler tunables with hotplug. The tunables
    # will vary across devices in unpredictable ways if allowed to scale with
    # cpu cores.
    write /proc/sys/kernel/sched_tunable_scaling 0
    write /proc/sys/kernel/sched_latency_ns 10000000
    write /proc/sys/kernel/sched_wakeup_granularity_ns 2000000
    write /proc/sys/kernel/sched_compat_yield 1
    write /proc/sys/kernel/sched_child_runs_first 0

    write /proc/sys/kernel/randomize_va_space 2
    write /proc/sys/kernel/kptr_restrict 2
    write /proc/sys/vm/mmap_min_addr 32768
    write /proc/sys/net/ipv4/ping_group_range "0 2147483647"
    write /proc/sys/net/unix/max_dgram_qlen 300
    write /proc/sys/kernel/sched_rt_runtime_us 950000
    write /proc/sys/kernel/sched_rt_period_us 1000000

    # reflect fwmark from incoming packets onto generated replies
    write /proc/sys/net/ipv4/fwmark_reflect 1
    write /proc/sys/net/ipv6/fwmark_reflect 1

    # set fwmark on accepted sockets
    write /proc/sys/net/ipv4/tcp_fwmark_accept 1

    # disable icmp redirects
    write /proc/sys/net/ipv4/conf/all/accept_redirects 0
    write /proc/sys/net/ipv6/conf/all/accept_redirects 0

    # Create cgroup mount points for process groups
    mkdir /dev/cpuctl
    mount cgroup none /dev/cpuctl cpu
    chown system system /dev/cpuctl
    chown system system /dev/cpuctl/tasks
    chmod 0666 /dev/cpuctl/tasks
    write /dev/cpuctl/cpu.shares 1024
    write /dev/cpuctl/cpu.rt_runtime_us 800000
    write /dev/cpuctl/cpu.rt_period_us 1000000

    mkdir /dev/cpuctl/bg_non_interactive
    chown system system /dev/cpuctl/bg_non_interactive/tasks
    chmod 0666 /dev/cpuctl/bg_non_interactive/tasks
    # 5.0 %
    write /dev/cpuctl/bg_non_interactive/cpu.shares 52
    write /dev/cpuctl/bg_non_interactive/cpu.rt_runtime_us 700000
    write /dev/cpuctl/bg_non_interactive/cpu.rt_period_us 1000000

    # sets up initial cpusets for ActivityManager
    mkdir /dev/cpuset
    mount cpuset none /dev/cpuset

    # this ensures that the cpusets are present and usable, but the device's
    # init.rc must actually set the correct cpus
    mkdir /dev/cpuset/foreground
    write /dev/cpuset/foreground/cpus 0
    write /dev/cpuset/foreground/mems 0
    mkdir /dev/cpuset/foreground/boost
    write /dev/cpuset/foreground/boost/cpus 0
    write /dev/cpuset/foreground/boost/mems 0
    mkdir /dev/cpuset/background
    write /dev/cpuset/background/cpus 0
    write /dev/cpuset/background/mems 0

    # system-background is for system tasks that should only run on
    # little cores, not on bigs
    # to be used only by init, so don't change system-bg permissions
    mkdir /dev/cpuset/system-background
    write /dev/cpuset/system-background/cpus 0
    write /dev/cpuset/system-background/mems 0

    # change permissions for all cpusets we'll touch at runtime
    chown system system /dev/cpuset
    chown system system /dev/cpuset/foreground
    chown system system /dev/cpuset/foreground/boost
    chown system system /dev/cpuset/background
    chown system system /dev/cpuset/tasks
    chown system system /dev/cpuset/foreground/tasks
    chown system system /dev/cpuset/foreground/boost/tasks
    chown system system /dev/cpuset/background/tasks
    chmod 0664 /dev/cpuset/foreground/tasks
    chmod 0664 /dev/cpuset/foreground/boost/tasks
    chmod 0664 /dev/cpuset/background/tasks
    chmod 0664 /dev/cpuset/tasks


    # qtaguid will limit access to specific data based on group memberships.
    #   net_bw_acct grants impersonation of socket owners.
    #   net_bw_stats grants access to other apps' detailed tagged-socket stats.
    chown root net_bw_acct /proc/net/xt_qtaguid/ctrl
    chown root net_bw_stats /proc/net/xt_qtaguid/stats

    # Allow everybody to read the xt_qtaguid resource tracking misc dev.
    # This is needed by any process that uses socket tagging.
    chmod 0644 /dev/xt_qtaguid

    # Create location for fs_mgr to store abbreviated output from filesystem
    # checker programs.
    mkdir /dev/fscklogs 0770 root system

    # pstore/ramoops previous console log
    mount pstore pstore /sys/fs/pstore
    chown system log /sys/fs/pstore/console-ramoops
    chmod 0440 /sys/fs/pstore/console-ramoops
    chown system log /sys/fs/pstore/pmsg-ramoops-0
    chmod 0440 /sys/fs/pstore/pmsg-ramoops-0

    # enable armv8_deprecated instruction hooks
    write /proc/sys/abi/swp 1

# Healthd can trigger a full boot from charger mode by signaling this
# property when the power button is held.
on property:sys.boot_from_charger_mode=1
    class_stop charger
    trigger late-init

# Load properties from /system/ + /factory after fs mount.
on load_system_props_action
    load_system_props

on load_persist_props_action
    load_persist_props
    start logd
    start logd-reinit

# Indicate to fw loaders that the relevant mounts are up.
on firmware_mounts_complete
    rm /dev/.booting

# Mount filesystems and start core system services.
on late-init
    trigger early-fs
    trigger fs
    trigger post-fs

    # Load properties from /system/ + /factory after fs mount. Place
    # this in another action so that the load will be scheduled after the prior
    # issued fs triggers have completed.
    trigger load_system_props_action

    # Now we can mount /data. File encryption requires keymaster to decrypt
    # /data, which in turn can only be loaded when system properties are present
    trigger post-fs-data
    trigger load_persist_props_action

    # Remove a file to wake up anything waiting for firmware.
    trigger firmware_mounts_complete

    trigger early-boot
    trigger boot


on post-fs
    start logd
    # once everything is setup, no need to modify /
    mount rootfs rootfs / ro remount
    # Mount shared so changes propagate into child namespaces
    mount rootfs rootfs / shared rec
    # Mount default storage into root namespace
    mount none /mnt/runtime/default /storage slave bind rec

    # We chown/chmod /cache again so because mount is run as root + defaults
    chown system cache /cache
    chmod 0770 /cache
    # We restorecon /cache in case the cache partition has been reset.
    restorecon_recursive /cache

    # Create /cache/recovery in case it's not there. It'll also fix the odd
    # permissions if created by the recovery system.
    mkdir /cache/recovery 0770 system cache

    #change permissions on vmallocinfo so we can grab it from bugreports
    chown root log /proc/vmallocinfo
    chmod 0440 /proc/vmallocinfo

    chown root log /proc/slabinfo
    chmod 0440 /proc/slabinfo

    #change permissions on kmsg & sysrq-trigger so bugreports can grab kthread stacks
    chown root system /proc/kmsg
    chmod 0440 /proc/kmsg
    chown root system /proc/sysrq-trigger
    chmod 0220 /proc/sysrq-trigger
    chown system log /proc/last_kmsg
    chmod 0440 /proc/last_kmsg

    # make the selinux kernel policy world-readable
    chmod 0444 /sys/fs/selinux/policy

    # create the lost+found directories, so as to enforce our permissions
    mkdir /cache/lost+found 0770 root root

on post-fs-data
    # We chown/chmod /data again so because mount is run as root + defaults
    chown system system /data
    chmod 0771 /data
    # We restorecon /data in case the userdata partition has been reset.
    restorecon /data

    # Emulated internal storage area
    mkdir /data/media 0770 media_rw media_rw

    # Make sure we have the device encryption key
    start logd
    start vold
    installkey /data

    # Start bootcharting as soon as possible after the data partition is
    # mounted to collect more data.
    mkdir /data/bootchart 0755 shell shell
    bootchart_init

    # Avoid predictable entropy pool. Carry over entropy from previous boot.
    copy /data/system/entropy.dat /dev/urandom

    # create basic filesystem structure
    mkdir /data/misc 01771 system misc
    mkdir /data/misc/adb 02750 system shell
    mkdir /data/misc/bluedroid 02770 bluetooth net_bt_stack
    # Fix the access permissions and group ownership for 'bt_config.conf'
    chmod 0660 /data/misc/bluedroid/bt_config.conf
    chown bluetooth net_bt_stack /data/misc/bluedroid/bt_config.conf
    mkdir /data/misc/bluetooth 0770 system system
    mkdir /data/misc/keystore 0700 keystore keystore
    mkdir /data/misc/gatekeeper 0700 system system
    mkdir /data/misc/keychain 0771 system system
    mkdir /data/misc/net 0750 root shell
    mkdir /data/misc/radio 0770 system radio
    mkdir /data/misc/sms 0770 system radio
    mkdir /data/misc/zoneinfo 0775 system system
    mkdir /data/misc/vpn 0770 system vpn
    mkdir /data/misc/shared_relro 0771 shared_relro shared_relro
    mkdir /data/misc/systemkeys 0700 system system
    mkdir /data/misc/wifi 0770 wifi wifi
    mkdir /data/misc/wifi/sockets 0770 wifi wifi
    mkdir /data/misc/wifi/wpa_supplicant 0770 wifi wifi
    mkdir /data/misc/ethernet 0770 system system
    mkdir /data/misc/dhcp 0770 dhcp dhcp
    mkdir /data/misc/user 0771 root root
    mkdir /data/misc/perfprofd 0775 root root
    # give system access to wpa_supplicant.conf for backup and restore
    chmod 0660 /data/misc/wifi/wpa_supplicant.conf
    mkdir /data/local 0751 root root
    mkdir /data/misc/media 0700 media media
    mkdir /data/misc/vold 0700 root root

    # For security reasons, /data/local/tmp should always be empty.
    # Do not place files or directories in /data/local/tmp
    mkdir /data/local/tmp 0771 shell shell
    mkdir /data/data 0771 system system
    mkdir /data/app-private 0771 system system
    mkdir /data/app-asec 0700 root root
    mkdir /data/app-lib 0771 system system
    mkdir /data/app 0771 system system
    mkdir /data/property 0700 root root
    mkdir /data/tombstones 0771 system system

    # create dalvik-cache, so as to enforce our permissions
    mkdir /data/dalvik-cache 0771 root root
    mkdir /data/dalvik-cache/profiles 0711 system system

    # create resource-cache and double-check the perms
    mkdir /data/resource-cache 0771 system system
    chown system system /data/resource-cache
    chmod 0771 /data/resource-cache

    # create the lost+found directories, so as to enforce our permissions
    mkdir /data/lost+found 0770 root root

    # create directory for DRM plug-ins - give drm the read/write access to
    # the following directory.
    mkdir /data/drm 0770 drm drm

    # create directory for MediaDrm plug-ins - give drm the read/write access to
    # the following directory.
    mkdir /data/mediadrm 0770 mediadrm mediadrm

    mkdir /data/adb 0700 root root

    # symlink to bugreport storage location
    symlink /data/data/com.android.shell/files/bugreports /data/bugreports

    # Separate location for storing security policy files on data
    mkdir /data/security 0711 system system

    # Create all remaining /data root dirs so that they are made through init
    # and get proper encryption policy installed
    mkdir /data/backup 0700 system system
    mkdir /data/media 0770 media_rw media_rw
    mkdir /data/ss 0700 system system
    mkdir /data/system 0775 system system
    mkdir /data/system/heapdump 0700 system system
    mkdir /data/user 0711 system system

    setusercryptopolicies /data/user

    # Reload policy from /data/security if present.
    setprop selinux.reload_policy 1

    # Set SELinux security contexts on upgrade or policy update.
    restorecon_recursive /data

    # Check any timezone data in /data is newer than the copy in /system, delete if not.
    exec - system system -- /system/bin/tzdatacheck /system/usr/share/zoneinfo /data/misc/zoneinfo

    # If there is no fs-post-data action in the init.<device>.rc file, you
    # must uncomment this line, otherwise encrypted filesystems
    # won't work.
    # Set indication (checked by vold) that we have finished this action
    #setprop vold.post_fs_data_done 1

on boot
    # basic network init
    ifup lo
    hostname localhost
    domainname localdomain

    # set RLIMIT_NICE to allow priorities from 19 to -20
    setrlimit 13 40 40

    # Memory management.  Basic kernel parameters, and allow the high
    # level system server to be able to adjust the kernel OOM driver
    # parameters to match how it is managing things.
    write /proc/sys/vm/overcommit_memory 1
    write /proc/sys/vm/min_free_order_shift 4
    chown root system /sys/module/lowmemorykiller/parameters/adj
    chmod 0664 /sys/module/lowmemorykiller/parameters/adj
    chown root system /sys/module/lowmemorykiller/parameters/minfree
    chmod 0664 /sys/module/lowmemorykiller/parameters/minfree

    # Tweak background writeout
    write /proc/sys/vm/dirty_expire_centisecs 200
    write /proc/sys/vm/dirty_background_ratio  5

    # Permissions for System Server and daemons.
    chown radio system /sys/android_power/state
    chown radio system /sys/android_power/request_state
    chown radio system /sys/android_power/acquire_full_wake_lock
    chown radio system /sys/android_power/acquire_partial_wake_lock
    chown radio system /sys/android_power/release_wake_lock
    chown system system /sys/power/autosleep
    chown system system /sys/power/state
    chown system system /sys/power/wakeup_count
    chown radio system /sys/power/wake_lock
    chown radio system /sys/power/wake_unlock
    chmod 0660 /sys/power/state
    chmod 0660 /sys/power/wake_lock
    chmod 0660 /sys/power/wake_unlock

    chown system system /sys/devices/system/cpu/cpufreq/interactive/timer_rate
    chmod 0660 /sys/devices/system/cpu/cpufreq/interactive/timer_rate
    chown system system /sys/devices/system/cpu/cpufreq/interactive/timer_slack
    chmod 0660 /sys/devices/system/cpu/cpufreq/interactive/timer_slack
    chown system system /sys/devices/system/cpu/cpufreq/interactive/min_sample_time
    chmod 0660 /sys/devices/system/cpu/cpufreq/interactive/min_sample_time
    chown system system /sys/devices/system/cpu/cpufreq/interactive/hispeed_freq
    chmod 0660 /sys/devices/system/cpu/cpufreq/interactive/hispeed_freq
    chown system system /sys/devices/system/cpu/cpufreq/interactive/target_loads
    chmod 0660 /sys/devices/system/cpu/cpufreq/interactive/target_loads
    chown system system /sys/devices/system/cpu/cpufreq/interactive/go_hispeed_load
    chmod 0660 /sys/devices/system/cpu/cpufreq/interactive/go_hispeed_load
    chown system system /sys/devices/system/cpu/cpufreq/interactive/above_hispeed_delay
    chmod 0660 /sys/devices/system/cpu/cpufreq/interactive/above_hispeed_delay
    chown system system /sys/devices/system/cpu/cpufreq/interactive/boost
    chmod 0660 /sys/devices/system/cpu/cpufreq/interactive/boost
    chown system system /sys/devices/system/cpu/cpufreq/interactive/boostpulse
    chown system system /sys/devices/system/cpu/cpufreq/interactive/input_boost
    chmod 0660 /sys/devices/system/cpu/cpufreq/interactive/input_boost
    chown system system /sys/devices/system/cpu/cpufreq/interactive/boostpulse_duration
    chmod 0660 /sys/devices/system/cpu/cpufreq/interactive/boostpulse_duration
    chown system system /sys/devices/system/cpu/cpufreq/interactive/io_is_busy
    chmod 0660 /sys/devices/system/cpu/cpufreq/interactive/io_is_busy

    # Assume SMP uses shared cpufreq policy for all CPUs
    chown system system /sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq
    chmod 0660 /sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq

    chown system system /sys/class/timed_output/vibrator/enable
    chown system system /sys/class/leds/keyboard-backlight/brightness
    chown system system /sys/class/leds/lcd-backlight/brightness
    chown system system /sys/class/leds/button-backlight/brightness
    chown system system /sys/class/leds/jogball-backlight/brightness
    chown system system /sys/class/leds/red/brightness
    chown system system /sys/class/leds/green/brightness
    chown system system /sys/class/leds/blue/brightness
    chown system system /sys/class/leds/red/device/grpfreq
    chown system system /sys/class/leds/red/device/grppwm
    chown system system /sys/class/leds/red/device/blink
    chown system system /sys/class/timed_output/vibrator/enable
    chown system system /sys/module/sco/parameters/disable_esco
    chown system system /sys/kernel/ipv4/tcp_wmem_min
    chown system system /sys/kernel/ipv4/tcp_wmem_def
    chown system system /sys/kernel/ipv4/tcp_wmem_max
    chown system system /sys/kernel/ipv4/tcp_rmem_min
    chown system system /sys/kernel/ipv4/tcp_rmem_def
    chown system system /sys/kernel/ipv4/tcp_rmem_max
    chown root radio /proc/cmdline

    # Define default initial receive window size in segments.
    setprop net.tcp.default_init_rwnd 60

    class_start core

on nonencrypted
    class_start main
    class_start late_start

on property:vold.decrypt=trigger_default_encryption
    start defaultcrypto

on property:vold.decrypt=trigger_encryption
    start surfaceflinger
    start encrypt

on property:sys.init_log_level=*
    loglevel ${sys.init_log_level}

on charger
    class_start charger

on property:vold.decrypt=trigger_reset_main
    class_reset main

on property:vold.decrypt=trigger_load_persist_props
    load_persist_props
    start logd
    start logd-reinit

on property:vold.decrypt=trigger_post_fs_data
    trigger post-fs-data

on property:vold.decrypt=trigger_restart_min_framework
    class_start main

on property:vold.decrypt=trigger_restart_framework
    class_start main
    class_start late_start

on property:vold.decrypt=trigger_shutdown_framework
    class_reset late_start
    class_reset main

on property:sys.powerctl=*
    powerctl ${sys.powerctl}

# system server cannot write to /proc/sys files,
# and chown/chmod does not work for /proc/sys/ entries.
# So proxy writes through init.
on property:sys.sysctl.extra_free_kbytes=*
    write /proc/sys/vm/extra_free_kbytes ${sys.sysctl.extra_free_kbytes}

# "tcp_default_init_rwnd" Is too long!
on property:sys.sysctl.tcp_def_init_rwnd=*
    write /proc/sys/net/ipv4/tcp_default_init_rwnd ${sys.sysctl.tcp_def_init_rwnd}


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

service logd /system/bin/logd
    class core
    socket logd stream 0666 logd logd
    socket logdr seqpacket 0666 logd logd
    socket logdw dgram 0222 logd logd
    group root system
     writepid /dev/cpuset/system-background/tasks

service logd-reinit /system/bin/logd --reinit
    oneshot
    writepid /dev/cpuset/system-background/tasks
    disabled

service healthd /sbin/healthd
    class core
    critical
    seclabel u:r:healthd:s0
    group root system

service console /system/bin/sh
    class core
    console
    disabled
    user shell
    group shell log
    seclabel u:r:shell:s0

on property:ro.debuggable=1
    start console

# adbd is controlled via property triggers in init.<platform>.usb.rc
service adbd /sbin/adbd --root_seclabel=u:r:su:s0
    class core
    socket adbd stream 660 system system
    disabled
    seclabel u:r:adbd:s0

# adbd on at boot in emulator
on property:ro.kernel.qemu=1
    start adbd

service lmkd /system/bin/lmkd
    class core
    critical
    socket lmkd seqpacket 0660 system system
    writepid /dev/cpuset/system-background/tasks

service servicemanager /system/bin/servicemanager
    class core
    user system
    group system
    critical
    onrestart restart healthd
    onrestart restart zygote
    onrestart restart media
    onrestart restart surfaceflinger
    onrestart restart drm

service vold /system/bin/vold \
        --blkid_context=u:r:blkid:s0 --blkid_untrusted_context=u:r:blkid_untrusted:s0 \
        --fsck_context=u:r:fsck:s0 --fsck_untrusted_context=u:r:fsck_untrusted:s0
    class core
    socket vold stream 0660 root mount
    socket cryptd stream 0660 root mount
    ioprio be 2

service netd /system/bin/netd
    class main
    socket netd stream 0660 root system
    socket dnsproxyd stream 0660 root inet
    socket mdns stream 0660 root system
    socket fwmarkd stream 0660 root inet

service debuggerd /system/bin/debuggerd
    class main
    writepid /dev/cpuset/system-background/tasks

service debuggerd64 /system/bin/debuggerd64
    class main
    writepid /dev/cpuset/system-background/tasks

service ril-daemon /system/bin/rild
    class main
    socket rild stream 660 root radio
    socket sap_uim_socket1 stream 660 bluetooth bluetooth
    socket rild-debug stream 660 radio system
    user root
    group radio cache inet misc audio log

service surfaceflinger /system/bin/surfaceflinger
    class core
    user system
    group graphics drmrpc
    onrestart restart zygote
    writepid /dev/cpuset/system-background/tasks

service drm /system/bin/drmserver
    class main
    user drm
    group drm system inet drmrpc

service media /system/bin/mediaserver
    class main
    user media
    group audio camera inet net_bt net_bt_admin net_bw_acct drmrpc mediadrm
    ioprio rt 4

# One shot invocation to deal with encrypted volume.
service defaultcrypto /system/bin/vdc --wait cryptfs mountdefaultencrypted
    disabled
    oneshot
    # vold will set vold.decrypt to trigger_restart_framework (default
    # encryption) or trigger_restart_min_framework (other encryption)

# One shot invocation to encrypt unencrypted volumes
service encrypt /system/bin/vdc --wait cryptfs enablecrypto inplace default noui
    disabled
    oneshot
    # vold will set vold.decrypt to trigger_restart_framework (default
    # encryption)

service bootanim /system/bin/bootanimation
    class core
    user graphics
    group graphics audio
    disabled
    oneshot

service gatekeeperd /system/bin/gatekeeperd /data/misc/gatekeeper
    class late_start
    user system

service installd /system/bin/installd
    class main
    socket installd stream 600 system system

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

service racoon /system/bin/racoon
    class main
    socket racoon stream 600 system system
    # IKE uses UDP port 500. Racoon will setuid to vpn after binding the port.
    group vpn net_admin inet
    disabled
    oneshot

service mtpd /system/bin/mtpd
    class main
    socket mtpd stream 600 system system
    user vpn
    group vpn net_admin inet net_raw
    disabled
    oneshot

service keystore /system/bin/keystore /data/misc/keystore
    class main
    user keystore
    group keystore drmrpc

service dumpstate /system/bin/dumpstate -s
    class main
    socket dumpstate stream 0660 shell log
    disabled
    oneshot

service mdnsd /system/bin/mdnsd
    class main
    user mdnsr
    group inet net_raw
    socket mdnsd stream 0660 mdnsr inet
    disabled
    oneshot

service uncrypt /system/bin/uncrypt
    class main
    disabled
    oneshot

service pre-recovery /system/bin/uncrypt --reboot
    class main
    disabled
    oneshot

service perfprofd /system/xbin/perfprofd
    class late_start
    user root
    oneshot
    writepid /dev/cpuset/system-background/tasks

on property:persist.logd.logpersistd=logcatd
    # all exec/services are called with umask(077), so no gain beyond 0700
    mkdir /data/misc/logd 0700 logd log
    # logd for write to /data/misc/logd, log group for read from pstore (-L)
    exec - logd log -- /system/bin/logcat -L -b all -v threadtime -v usec -v printable -D -f /data/misc/logd/logcat -r 64 -n 256
    start logcatd

service logcatd /system/bin/logcat -b all -v threadtime -v usec -v printable -D -f /data/misc/logd/logcat -r 64 -n 256
    class late_start
    disabled
    # logd for write to /data/misc/logd, log group for read from log daemon
    user logd
    group log
    writepid /dev/cpuset/system-background/tasks

下面我就把init.rc粘貼過來,然后加上一定的注釋讓大家很好的理解
按照上面的"塊"(section)來舉例說明,我們就用最前面的

1、on early-init 塊

on early-init

    # 調用 do_write函數 ,將oom_score_adj寫為-1000  
    # Set init and its forked children's oom_adj.
    write /proc/1/oom_score_adj -1000
 
    # 為adb_keys重置安全上下文
    # Set the security context of /adb_keys if present.
    restorecon /adb_keys

     #調用函數do_start 啟動服務do_start
    start ueventd

上面代碼的 trigger為early-init,在init.cpp的main函數(1082行)設置過。

下面對所有的命令進行下講解

  • bootchart_init:初始化bootchart,用于獲取開機過程的系統信息
  • chmod <octal-mode> <path>:改變文件的權限
  • chown <owner> <group> <path>:改變文件的群組
  • class_start <serviceclass>:啟動所有具有特定class的services
  • class_stop <serviceclass>:將具有特定的class所有運行中的services給停止或者disable
  • class_reset <serviceclass>:先將services stop掉,然后再重新啟動
  • copy <src> <dst>:復制文件
  • domainname <name>:設置域名
  • enable <servicename>:如果service沒有disable,就將他設為enable
  • exec [ <seclabel> [ <user> [ <group> ]* ] ] -- <command> [ <argument> ]* :創建執行程序,比較重要,后面啟動service要用到
  • export <name> <value>:在全局設置環境變量
  • hostname <name>:設置主機名稱
  • ifup <interface>:啟動網絡接口
  • insmod <path>:在某個路徑安裝一個模塊
  • load_all_props:加載所有的配置
  • load_persist_props:當data加密時加載一些配置
  • loglevel <level>:設置kernel log level
  • mkdir <path> [mode] [owner] [group]:創建文件夾
  • mount_all <fstab> [ <path> ]:掛載
  • mout <type> <device> <dir> [ <flag> ]* [ <options> ]:在dir文件夾下面掛載設備
  • restart <service>:重啟服務
  • restorecon <path> [ <path> ]:重置文件的安全上下文
  • restorecon_recursive <path> [ <path> ]*:一般都是selinux完成初始化之后又創建、或者改變的目錄
  • rm <path>:刪除文件
  • rmdir <path>:刪除文件夾
  • setprop <name> <value>:設置屬性
  • setrlimit <resource> <cur> <max>:設置進程資源限制
  • start <service>:如果service沒有啟動,就將他啟動起來
  • stop <service>:將運行的服務停掉
  • swapon_all <fstab>:在指定文件上調用 fs_mgr_swapon_all
  • symlink <target> <path>:創建一個指向 <path>的符號鏈接
  • sysclktz <mins_west_of_gmt>:指定系統時鐘基準,比如0代表GMT,即以格林尼治時間為準
  • trigger <event>:觸發一個事件
  • verity_load_state:用于加載dm-verity狀態的內部實現
  • verity_update_state <mount_point>:用于更新dm-verity狀態并設置分區的內部實現細節,由于fs_mgsr不能直接設置它們本身,所以通過使用adb remount來驗證屬性
  • wait <path> [ <timeout> ]:等待指定路徑的文件創建出來,創建完成就停止等待,或者等到超時時間到。如果未指定超時時間,缺省是5秒。
  • write <path> <content> [ <string> ]*:打開指定的文件,并寫入一個或多個字符串。

四、init.rc文件的解析

上面的一片文章介紹了,在init.cpp里面會調用init_parse_config_file("/init.rc");函數來解析init.rc,而init_parse_config_file("/init.rc");函數其實是init_parser.cpp里面的,那我們就從這里開始。

(一)、init_parse_config_file函數解析

代碼在init_parser.cpp 441行

int init_parse_config_file(const char* path) {
    INFO("Parsing %s...\n", path);
    Timer t;
    std::string data;
    if (!read_file(path, &data)) {
        return -1;
    }

    data.push_back('\n'); // TODO: fix parse_config.

    // 實際解析init.rc文件的代碼
    parse_config(path, data);
    dump_parser_state();

    NOTICE("(Parsing %s took %.2fs.)\n", path, t.duration());
    return 0;
}

init_parse_config_file()通過read_file()函數把整個配置文件讀入內存后,調用parse_config()函數來解析配置文件。那我們先來看下read_file()函數的解析。

1、read_file函數解析

代碼在util.cpp152行

bool read_file(const char* path, std::string* content) {
    content->clear();

    //打開path的文件夾,即打開了init.rc
    int fd = TEMP_FAILURE_RETRY(open(path, O_RDONLY|O_NOFOLLOW|O_CLOEXEC));

    // 如果打開失敗,則返回
    if (fd == -1) {
        return false;
    }

   // For security reasons, disallow world-writable
    // or group-writable files.
    struct stat sb;

    // 得到fd文件描述符所指向文件的文件信息,并填充sb的狀態結構體。如果獲取失敗的話,會返回-1
    if (fstat(fd, &sb) == -1) {
        ERROR("fstat failed for '%s': %s\n", path, strerror(errno));
        return false;
    }

    // 如果其他用戶,或者用戶組有可以寫入這個文件的權限的話,則error
    if ((sb.st_mode & (S_IWGRP | S_IWOTH)) != 0) {
        ERROR("skipping insecure file '%s'\n", path);
        return false;
    }

    // 從文件fd中,讀取其內容,然后保存在content里面,
    bool okay = android::base::ReadFdToString(fd, content); 

    // 讀取完了文件,則要關閉這個文件的描述符
    close(fd);
    return okay;
}

通過上面代碼的分析,我們知道,readfile函數里面將init.rc內部全部讀取到content里面,然后進行返回。下面我們來看下parse_config函數的解析

2、parse_config函數解析

代碼在init_parser.cpp385行

// 針對init.rc來說,fn指向的內容為init.rc這個文件,data就是init.rc里面的內容映射到內存的數據
static void parse_config(const char *fn, const std::string& data)
{

    struct listnode import_list;
    struct listnode *node;
    char *args[INIT_PARSER_MAXARGS];

    int nargs = 0;
    parse_state state;
 
    // 表明解析的是init.rc的文件
    state.filename = fn;

    // 將初始化的line設為0
    state.line = 0;

    // ptr指向s的第一個元素
    state.ptr = strdup(data.c_str());  // TODO: fix this code!

    // 設置nexttoken為0
    state.nexttoken = 0;

    // 設置解析的方式為parse_line_no_op,即為不需要處理。需要注意的為parse_line是一個函數指針。
    state.parse_line = parse_line_no_op;

   // 初始化前面的import的鏈表
    list_init(&import_list);

    // 將priv指向了初始化后的import的鏈表
    state.priv = &import_list;

   // 開始解析文件
    for (;;) {
        // next _token的函數原理是,針對state->ptr的指針進行解析,依次向后讀取data數組中的內容
       // 如果讀取到"\n","0"的話,返回T_EOF和T_NEWLINE
       // 如果讀取出來的是一個詞的話,則將內容保存在args的數組中,內容依次向后
        switch (next_token(&state)) {
 
        // 如果是文件讀取結束
        case T_EOF:

            // 如果文件是空的,那么執行的function是parse_line_no_op
             // 如果不是空的,則執行parse_line_action或者service
             // 如果nargs是0的話,都會返回掉
            state.parse_line(&state, 0, 0);
            goto parser_done;

        case T_NEWLINE:

            // 如果遇到"\n"的話,state.line會+1行
            state.line++;

            // 如果nargs有值的話,說明這一行需要解析了
            if (nargs) {
                //獲取這一行的第一個關鍵字,即args[0],獲取kw
                int kw = lookup_keyword(args[0]);

                // 如果這個kw是一個SECTION的話,怎會返回true,如果不是,則反之
                if (kw_is(kw, SECTION)) {

                    // 清楚掉現在的parse_line,開啟一個新的
                    state.parse_line(&state, 0, 0);
                    parse_new_section(&state, kw, nargs, args);
                } else {

                    // 如果不是一個section的話,則將nargs與args作為參數傳遞到parse_line對應的函數中區
                    state.parse_line(&state, nargs, args);
                }
                // 在執行完一行之后,由于有新的內容需要讀取到args中,所以將nargs設置為0
                nargs = 0;
            }
            break;
        case T_TEXT:
            if (nargs < INIT_PARSER_MAXARGS) {
 
                // 每取出來一個token,就會將其放入到args的數組中,且nargs會自動+1
                args[nargs++] = state.text;
            }
            break;
        }
    }


// 文件結束的時候,會去執行parse_done
parser_done:

    // 這里會去遍歷所有import_list的節點
    list_for_each(node, &import_list) {
 
         // 取出這些import的節點
         struct import *import = node_to_item(node, struct import, list);
         int ret;

         // 繼續對這些文件進行解析
         ret = init_parse_config_file(import->filename);
         if (ret)
             ERROR("could not import file '%s' from '%s'\n",
                   import->filename, fn);
    }
}

parse_config()函數解析腳本文件的邏輯過程可以用一張流程圖來表示,如下圖所示。通過調用next_token()函數的作用就是尋找單詞結束標志或行結束標志。如果是單詞結束符,就先存放在數組args中,如果找到的是行結束符,則根據行中的第一個單詞來判斷是否是一個"section","section"的標志有3個,關鍵字"on","service","import"。如果是"section"則調用函數parse_new_section來開啟一個新"section"的處理,否則把這一行繼續作為當前"section"所屬的行來處理。

流程圖.png

(二)、init.rc具體解析

上面的代碼看到了,主要解析是的函數是parse_new_section函數,那我們就來看下這個函數
代碼在init_parser.cpp358行

static void parse_new_section(struct parse_state *state, int kw,
                       int nargs, char **args)
{
    printf("[ %s %s ]\n", args[0],
           nargs > 1 ? args[1] : "");
    switch(kw) {

    // 如果是 service
    case K_service:
        state->context = parse_service(state, nargs, args);
        if (state->context) {
            state->parse_line = parse_line_service;
            return;
        }
        break;
   // 如果是 on
    case K_on:
        state->context = parse_action(state, nargs, args);
        if (state->context) {
            state->parse_line = parse_line_action;
            return;
        }
        break;

    // 如果是 import
    case K_import:
        parse_import(state, nargs, args);
        break;
    }
    state->parse_line = parse_line_no_op;
}

parse_new_section()函數,這個函數根據3個關鍵字來分別處理。即"on""service""import"。分別對應parse_action函數、parse_service函數和parse_import函數。下面我們就依次來看下這個三個函數的具體實現

1、解析action

我們看到上面代碼在case K_on里面首先是調用parse_action函數。那我們就來先看下parse_action函數

代碼在init_parser.cpp946行

static void *parse_action(struct parse_state *state, int nargs, char **args)
{
    struct trigger *cur_trigger;
    int i;

    // 檢查是否存在 trgger
    if (nargs < 2) {
        parse_error(state, "actions must have a trigger\n");
        return 0;
    }
 
    // 初始化 結構體 action
    action* act = (action*) calloc(1, sizeof(*act));
    list_init(&act->triggers);

    for (i = 1; i < nargs; i++) {
        if (!(i % 2)) {
            if (strcmp(args[i], "&&")) {
                struct listnode *node;
                struct listnode *node2;
                parse_error(state, "& is the only symbol allowed to concatenate actions\n");
                list_for_each_safe(node, node2, &act->triggers) {
                    struct trigger *trigger = node_to_item(node, struct trigger, nlist);
                    free(trigger);
                }
                free(act);
                return 0;
            } else
                continue;
        }
        cur_trigger = (trigger*) calloc(1, sizeof(*cur_trigger));
        cur_trigger->name = args[i];
        list_add_tail(&act->triggers, &cur_trigger->nlist);
    }

    // 初始化action的commands這條結構體的內部鏈表
    list_init(&act->commands);

    //  初始化qlist這條結構體內部的鏈表
    list_init(&act->qlist);

    //將當前的這個結構體加入到以action_list為哨兵節點的鏈表中
    list_add_tail(&action_list, &act->alist);
        /* XXX add to hash */
    return act;
}

這里初始化的內容全部都是鏈表的操作。為了更好的理解,我們來看下action的結構體。

1.1、action結構體

代碼在init.h47行

struct action {

        /* node in list of all actions */
    // 使用這個listnode將其加入"action_list"
    struct listnode alist;

    // 掛接全局執行列表 "action_queue"節點
        /* node in the queue of pending actions */
    struct listnode qlist;

        /* node in list of actions for a trigger */
    struct listnode tlist;

    unsigned hash;

    // action的trigger字符串
        /* list of actions which triggers the commands*/
    struct listnode triggers;

    // action的命令列表的表頭
    struct listnode commands;
    struct command *current;
};

好的,總結一下,經過parse_action之后,當前的action會被加入到action_list為節點的鏈表中。并且初始化了commands以及qlist這兩條結構內部的鏈表。在parse_new_section中,我們看到,在初始化完之后,會將當前state的parse_line設置為parse_line_action。

1.2、listnode結構體

這里簡單的說一下listnode這個數據結構
代碼在list.h

struct listnode
{
    struct listnode *next;
    struct listnode *prev;
};

listnode的定義和我們一般理解的隊列節點有點不同,一般的節點都有指向節點的數據的指針,但是listnode的定義居然只有兩個用于隊列連接的指針,如果把這樣一個節點插入了隊列,將來又如何從隊列中找到對應的的action呢?其實只要明白了腳本的解析中所使用的工作原理,就容易理解了。

在init_parser.cpp中定義了3個全局列表service_list、action_list和action_queue。
代碼init_parser.cpp 38行

static list_declare(service_list);
static list_declare(action_list);
static list_declare(action_queue);

其中service_list列表包括了啟動腳本所有"service",action_list列表包括了啟動腳本中所有"action",init腳本的解析結果就是生成這兩個列表。action_queue列表則保存正在執行中的"action",init的main()函數會把需要執行的action插入到action_queue中。

list_declare是一個宏,定義并初始化了列表的頭節點
代碼在list.h
35行

#define list_declare(name) \
    struct listnode name = { \
        .next = &name, \
        .prev = &name, \
    }

初始化后,頭節點的next指針和prev指針都指向自身。這說明通過listnode組成的隊列是一個雙向鏈表。service_list、action_list和action_queue都是頭節點。

列表的插入,是通過list_add_tail函數來完成的,代碼如下:
代碼在list.h 58行

static inline void list_add_tail(struct listnode *head, struct listnode *item)
{
    item->next = head;
    item->prev = head->prev;
    head->prev->next = item;
    head->prev = item;
}

結構如下:


action_list結構.png

然后在接下來的action的comand的時候,就回去去執行parse_line_action()的函數

代碼在init_parser.cpp

                if (kw_is(kw, SECTION)) {
                    state.parse_line(&state, 0, 0);
                    parse_new_section(&state, kw, nargs, args);
                } else {
                     //實質是調用parse_line_action函數 
                    state.parse_line(&state, nargs, args);
                }

那我們來看下parse_line_action函數的實現。

1.3、parse_line_action()函數

解析完action關鍵字之后,接下來就是對每個命令行進行解析。命令行解析函數是parse_line_action()
代碼在init_parser.cpp 985行

static void parse_line_action(struct parse_state* state, int nargs, char **args)
{

    // 通過 state ->context 得到了剛才正在解析的action
    struct action *act = (action*) state->context;
    int kw, n;

    // 如果判斷為空,則沒有要執行的command的話,就會直接返回
    if (nargs == 0) {
        return;
    }

    // 得到ke,原理和得到SECTION的一致
    kw = lookup_keyword(args[0]);
    if (!kw_is(kw, COMMAND)) {

        // 如果這個命令 不是一個command的話,則返回error
        parse_error(state, "invalid command '%s'\n", args[0]);
        return;
    }

    // 從keywords里面得到這個command需要幾個參數,是在初始化數組的第三項目 nargs
    n = kw_nargs(kw);
    if (nargs < n) {

         // 如果需要的參數沒有滿足的話,則會返回錯誤
        parse_error(state, "%s requires %d %s\n", args[0], n - 1,
            n > 2 ? "arguments" : "argument");
        return;
    }

    // 對action的結構體中的cmmand結構體進行初始化
    command* cmd = (command*) malloc(sizeof(*cmd) + sizeof(char*) * nargs);
 
     //得到這個command需要執行的函數,并將其放在func的這個指針里面
    cmd->func = kw_func(kw);

    // 得到這個command是在文件中的哪一樣
    cmd->line = state->line;

     // 是哪個文件的commands
    cmd->filename = state->filename;

    //  這個commands的參數有幾個
    cmd->nargs = nargs;

    // 將這幾個參數都copy到commands的數組里面
    memcpy(cmd->args, args, sizeof(char*) * nargs);

    // 將當前要執行的commands,加入到action的結構體中,listnode為commands的鏈表中
    list_add_tail(&act->commands, &cmd->clist);
}
2、解析service

上面分析解析init.rc的action之后,剩下的一部分就是解析service了。按照我們前面解析action的流程。我們還是要回到parse_config()函數里面來。根據前面的知識,我們知道,在關鍵字為“service”的時候,會進入到parse_new_section函數,然后將service以及后面的option設置為執行"parse_line",又會執行"parse_line:parse_line_service"。為了讓大家更好的理解,我們先來service的結構體。

2.1、service結構體

代碼在init.h 95行

struct service {
    void NotifyStateChange(const char* new_state);

   // listnode的節點
        /* list of all services */
    struct listnode slist;

    // 服務的名字
    char *name;

    // 服務的類名
    const char *classname;

    unsigned flags;

    // service 所在進程的pid
    pid_t pid;

   
    time_t time_started;    /* time of last start */
    time_t time_crashed;    /* first crash within inspection window */
    int nr_crashed;         /* number of times crashed within window */

    uid_t uid;
    gid_t gid;
    gid_t supp_gids[NR_SVC_SUPP_GIDS];
    size_t nr_supp_gids;

    const char* seclabel;

    // 為service 創建的Sockets
    struct socketinfo *sockets;

    // 為service設置的環境變量
    struct svcenvinfo *envvars;

    struct action onrestart;  /* Actions to execute on restart. */

    std::vector<std::string>* writepid_files_;

    /* keycodes for triggering this service via /dev/keychord */
    int *keycodes;
    int nkeycodes;
    int keychord_id;

    IoSchedClass ioprio_class;
    int ioprio_pri;

    int nargs;
    /* "MUST BE AT THE END OF THE STRUCT" */
    char *args[1];
}; /*     ^-------'args' MUST be at the end of this struct! */

這個結構體相比較而言就比較簡單了,除了service的本身屬性之外,對于數據結構方面就只有一個listnode了。
關于listnode上面已經講解,我這里就不講解了。

下面我們來看下parse_service函數的解析

2.2、parse_service()函數

代碼在init_parser.cpp 728行

static void *parse_service(struct parse_state *state, int nargs, char **args)
{
    // 如果service的參數小于3個的話,我們會認為service是一個不正常的service。
     // service最少的nargs也是3,分別是service 關鍵字、service的名字、service啟動的時候要執行的命令
    if (nargs < 3) {
        parse_error(state, "services must have a name and a program\n");
        return 0;
    }

    // 如果service的name為不標準的名字的話,含有其他的符號的話,我們認為這個service是不規范的service。
    if (!valid_name(args[1])) {
        parse_error(state, "invalid service name '%s'\n", args[1]);
        return 0;
    }
   
    // 會從已經存在的service_list里面去查找,是否已經有同名的service的存在
    service* svc = (service*) service_find_by_name(args[1]);
    if (svc) {

        // 如果發現有同名的service存在的話,則會返回errror
        parse_error(state, "ignored duplicate definition of service '%s'\n", args[1]);
        return 0;
    }

    // 去除service關鍵字 與 service的name
    nargs -= 2;
    svc = (service*) calloc(1, sizeof(*svc) + sizeof(char*) * nargs);
    if (!svc) {

        // 如果分配失敗,就提示out of memory
        parse_error(state, "out of memory\n");
        return 0;
    }

    // 設置service的name為service關鍵字 后的第一個參數
    svc->name = strdup(args[1]);

    // 默認的classname為default
    svc->classname = "default";

    // 將args剩余的參數復制到svc的args里面
    memcpy(svc->args, args + 2, sizeof(char*) * nargs);


    trigger* cur_trigger = (trigger*) calloc(1, sizeof(*cur_trigger));

    // 給 args的最后一項設置為0
    svc->args[nargs] = 0;

    // 參數的數目等于傳進來的參數的數目
    svc->nargs = nargs;

    list_init(&svc->onrestart.triggers);

    // 設置 onrestart.name為onrestart
    cur_trigger->name = "onrestart";


    list_add_tail(&svc->onrestart.triggers, &cur_trigger->nlist);

    // 初始化onrestart的鏈表
    list_init(&svc->onrestart.commands);

    // 將當前的service的結構體加入到service_list的鏈表里面
    list_add_tail(&service_list, &svc->slist);

    return svc;
}

從上面我們知道,在執行完parse_service之后,會初始化service的一些屬性,將servicename作為args進行保存。然后將這個解析出來的service加入到service_list的鏈表里面。

具體解析service中的每一行的函數是parse_line_service函數,代碼在init_parser.cpp 765行,因為代碼有點多,粘貼過來,就滿了,我就簡單說下其內容,其函數內部,就是解析service的每一行,將其對應進不同的case里面,進行service結構體的填充。在service的解析后,會生成一條鏈表保存service的結構體,然后service的結構體里面自己運行維護一個action。

3、解析import

import的解析,只是把文件名插入了import_list列表中。對import列表的處理是在parse_config()函數里面的結尾部分

代碼在init_parser.cpp 431行

         struct import *import = node_to_item(node, struct import, list);
         int ret;

這段代碼的作用是取出import_list每個節點的文件夾名,然后遞歸調用init_parse_config_file()函數。init_parse_config_file()函數就是解析配置文件的入口函數,前面已經講解了,這里就不說了。

PS:無論import語句在腳本文件的哪一行,都是在所屬文件解析完之后才開始解析它引入的文件。

4、執行

上面講了很多,主要就是解析,我們知道解析過程,只不過是把腳本文件中的"action"和"service"解析出來放到各自的隊列中。action的執行是在init代碼中指定的。通過前面的內容,我們知道, init進程的main()函數中會通過調用action_for_each_trigger()函數來把需要執行的"action"加入到執行列表的action_queue中。

五、init.rc命令執行的順序

我們知道解析init.rc會把一條條命令映射到內存中,然后依次啟動。那啟動順序是什么?
即是按照init.rc里面的順序大致順序如下:

  • on early-init
  • on init
  • on late-init //掛載文件系統,啟動核心服務
    trigger post-fs
    trigger load_system_props_action
    trigger post-fs-data //掛載data
    trigger load_persist_props_action
    trigger firmware_mounts_complete
    trigger boot
  • on post-fs
    start logd
    mount rootfs rootfs / ro remount
    mount rootfs rootfs / shared rec
    mount none /mnt/runtime/default /storage slave bind rec
    ...
  • on post-fs-data
    start logd
    start vold //啟動vold
    ...
  • on boot
    ...
    class_start core //啟動core class

即如下順序
首先是 on early-init -> init -> late -init -> boot

六、init總結

這里里面總結下init里面main方法做的事情如下:

  • first stage 初始化環境變量和各種文件系統目錄,klog初始化等
  • selinux相關初始化完成,然后切換second stage 重啟init進程
  • 屬性服務初始化,將各種系統屬性默認值填充到屬性Map中
  • 創建epoll描述符結合注冊socket監聽,處理顯示啟動進程和掛掉的子進程重啟
  • 解析init.rc。把各種action、service等解析出來的填充到相應鏈表容器管理
  • 有序將early-init、init等各種cmd加入到執行隊列action_queue鏈表中
  • 進入while()循環依次取出執行隊列action_queue中的command執行,fork包括app_process在內的各種進程,epoll阻塞監聽處理來自掛掉的子進程的消息,根據設定策略restart子進程。

流程圖如下:


image.png

上一篇文章 Android系統啟動——2 init進程
下一篇文章 Android系統啟動——4 zyogte進程 (C篇)

官人[飛吻],你都把臣妾從頭看到尾了,喜歡就點個贊唄(眉眼)!!!!

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,797評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,179評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,628評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,642評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,444評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,948評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,040評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,185評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,717評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,602評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,794評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,316評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,045評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,418評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,671評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,414評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,750評論 2 370

推薦閱讀更多精彩內容