本次系列的內容如下:
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中定義的一些常見事件
針對這些事件,如下表命令可供使用
(三)、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中可用選項如下表
default: 意味著disabled=false,oneshot=false,critical=false。
三、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"所屬的行來處理。
(二)、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的comand的時候,就回去去執行parse_line_action()的函數
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子進程。
流程圖如下:
上一篇文章 Android系統啟動——2 init進程
下一篇文章 Android系統啟動——4 zyogte進程 (C篇)