目录
前言
本文主要讲解Android源码之init.rc文件规则和init.c解析。
本文摘抄网上大牛的文章(链接文末),方便自己查阅。
多谢分享。
正文
init.c与init.rc在源码中的位置:
init.c : /system/core/init init.rc : /system/core/rootdir
.rc文件是 android系统一个十分重要的文件。
其是资源文件,包括比如对话框、菜单、图标、字符串等资源信息。
使用.rc资源文件的目的是为了对程序中用到的大量的资源进行统一的管理。
本文来了解文件的规则。
Android中init.rc文件简单介绍
init.rc 脚本是由Android中linux的第一个用户级进程init进行解析的。
init.rc 文件并不是普通的配置文件,而是由一种被称为“Android初始化语言”(Android Init Language,这里简称为AIL)的脚本写成的文件。
init.rc 脚本包括了启动脚本文件,主要完成一些初级的初始化,文件系统初始化。
主要是:
- 设置一些环境变量
- 创建system、sdcard、data、cache等目录(见案例1)
- 把一些文件系统mount到一些目录去,如,mount tmpfs tmpfs /sqlite_stmt_journals
- 设置一些文件的用户群组、权限
- 设置一些线程参数
- 设置TCP缓存大小
该文件在ROM中是只读的,即使有了root权限,可以修改该文件也没有。因为我们在根目录看到的文件只是内存文件的镜像。也就是说,android启动后,会将init.rc文件装载到内存。而修改init.rc文件的内容实际上只是修改内存中的init.rc文件的内容。一旦重启android,init.rc文件的内容又会恢复到最初的装载。想彻底修改init.rc文件内容的唯一方式是修改Android的ROM中的内核镜像(boot.img)。
如果想要修改启动过程只需要修改init.c(system/core/init)或者init.rc里的内容即可。
文件规则
类型
Android初始化语言包含了四种类型的声明 :
Actions(行为)、Commands(命令)、Services(服务)和Options(选项) Actions和Services声明一个新的分组Section。所有的命令或选项都属于最近声明的分组。位于第一个分组之前的命令或选项将会被忽略。
Actions和Services有唯一的名字。如果有重名的情况,第二个申明的将会被作为错误忽略。
注意:这个只是一个语法文件,就像一个xml文件一样,没有执行顺序的,解析器通过读这个文件获取想要的数据,包括service,action等
基本规则
- 以行为单位的,各种记号由空格来隔开。
- C语言风格的反斜杠号可用于在记号间插入空格。
- 双引号也可用于防止字符串被空格分割成多个记号。
- 行末的反斜杠用于折行,注释行以井号(#)开头(允许以空格开头)
关键字
关键字位于语句块的首部,决定了这个语句块的种类
Action : 动作 trigger : 触发器或者叫做触发条件 commands : 命令 service : 服务
动作(Action)
动作表示了一组命令(commands)组成。
动作包括一个触发器,决定了何时运行这个动作。
当触发器的条件满足时,这个动作会被增加到已被运行的队列尾。假设此动作在队列中已经存在,那么它将不会运行
on <trigger> ## 触发条件 <command> ##执行命令 <command1> ##可以执行多个命令
举个例子:
# /device/rockchip/rk322x/init.rc on property:vold.decrypt=trigger_encryption start surfaceflinger start encrypt
触发器(trigger)
在"动作"(action)里面的,on后面跟着的字符串是触发器(trigger),trigger是一个用于匹配某种事件类型的字符串,它将对应的Action的执行。
触发器(trigger)有几种格式:
- 最简单的一种是一个单纯的字符串。比如“on boot”。这种简单的格式可以使用命令"trigger"来触发。
- 还有一种常见的格式是"on property : <属性>=<值>"。如果属性值在运行时设成了指定的值,则"块"(action)中的命令列表就会执行。
常见的格式:
on early-init 在初始化早期阶段触发 on init 在初始化阶段触发 on late-init 在初始化晚期阶段触发 on boot/charger 当系统启动/充电时触发 on property 当属性值满足条件时触发
commands(命令)
command是action的命令列表中的命令,或者是service中的选项 onrestart 的参数命令。
命令将在所属事件发生时被一个个地执行。
常见命令:
exec <path> [ ]* 运行指定路径下的程序,并传递參数. export <name> <value> 设置全局环境參数。此參数被设置后对全部进程都有效. ifup <interface> 使指定的网络接口"上线",相当激活指定的网络接口 hostname <name> 设置主机名 chdir <directory> 改变工作文件夹. chmod <octal-mode> <path> 改变指定文件的读取权限. chown <owner> <group> <path> 改变指定文件的拥有都和组名的属性. chroot <directory> 改变进行的根文件夹. class_start <serviceclass> 启动指定类属的全部服务,假设服务已经启动,则不再反复启动. class_stop <serviceclass> 停止指定类属的所胡服务. domainname <name> 设置域名 insmod <path> 安装模块到指定路径. mkdir <path> [mode] [owner] [group] 用指定參数创建一个文件夹,在默认情况下,创建的文件夹读取权限为755.username为root,组名为root. mount<type> <device> <dir> [ <mountoption> ]* 类似于linux的mount指令 setprop <name> <value> 设置属性及相应的值. setrlimit <resource> <cur> <max> 设置资源的rlimit(资源限制),不懂就百度一下rlimit start <service> 假设指定的服务未启动,则启动它. stop <service> 假设指定的服务当前正在执行。则停止它. symlink <target> <path> 创建一个符号链接. sysclktz <mins_west_of_gmt> 设置系统基准时间. write <path> <string>\ [ <string> ]* 往指定的文件写字符串.
服务(services)
服务是指那些须要在系统初始化时就启动或退出时自己主动重新启动的程序.
service <name><pathname> [ <argument> ]* <option> <option>
name 表示此服务的名称 pathname 此服务所在路径因为是可执行文件,所以一定有存储路径。 argument 启动服务所带的参数 option 对此服务的约束选项
service gpio_read /system/bin/gpio_read.sh user root group root class main disabled oneshot
设置一个gpio_read服务对应可执行文件 gpio_read.sh脚本 。设置运行的user 和group,设置为不自动启动disabled,设置为退出之后不重启oneshot。
当属性 sys.wifi.on=true时,就会启动goio_read这个服务。
选项(option)
options是Service的修订项。它们决定一个服务何时以及如何运行。
critical 据设备相关的关键服务,如果在4分钟内,此服务重复启动了4次,那么设备将会重启进入还原模式。 disabled 服务不会自动运行,必须显式地通过服务器来启动。 setenv 设置环境变量 socket [ [ ] ] 在/dev/socket/下创建一个unix domain的socket,并传递创建的文件描述符fd给服务进程.其中type必须为dgram或stream,seqpacket. user 在执行此服务之前先切换用户名。当前默认为root. group [ ]* 类似于user,切换组名 oneshot 当此服务退出时不会自动重启. class 给服务指定一个类属,这样方便操作多个服务同时启动或停止.默认情况下为default. onrestart 当服务重启时执行一条指令,
示例详解
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 <filename>一个init配置文件,扩展当前配置。】" import /init.environ.rc import /init.usb.rc import /init.${ro.hardware}.rc import /init.${ro.zygote}.rc import /init.trace.rc "【触发条件early-init,在early-init阶段调用以下行】" on early-init # Set init and its forked children's oom_adj. write /proc/1/oom_score_adj -1000 "【打开路径为<path>的一个文件,并写入一个或多个字符串】" # Apply strict SELinux checking of PROT_EXEC on mmap/mprotect calls. write /sys/fs/selinux/checkreqprot 0 # Set the security context for the init process. # This should occur before anything else (e.g. ueventd) is started. "【这段脚本的意思是init进程启动之后就马上调用函数setcon将自己的安全上下文设置为“u:r:init:s0”,即将init进程的domain指定为init。】" setcon u:r:init:s0 # Set the security context of /adb_keys if present. "【恢复指定文件到file_contexts配置中指定的安全上线文环境】" restorecon /adb_keys "【执行start ueventd的命令。ueventd是一个service后面有定义】 " start ueventd "【mkdir <path> [mode] [owner] [group] //创建一个目录<path>,可以选择性地指定mode、owner以及group。如果没有指定,默认的权限为755,并属于root用户和root组。】" # create mountpoints mkdir /mnt 0775 root system on init "【设置系统时钟的基准,比如0代表GMT,即以格林尼治时间为准】" sysclktz 0 "【设置kernel日志等级】" loglevel 6 #### write /proc/bootprof "INIT: on init start" #### "【symlink <target> <path> //创建一个指向<path>的软连接<target>。】" # Backward compatibility symlink /system/etc /etc symlink /sys/kernel/debug /d # Right now vendor lives on the same filesystem as system, # but someday that may change. symlink /system/vendor /vendor "【创建一个目录<path>,可以选择性地指定mode、owner以及group。】" # Create cgroup mount point for cpu accounting mkdir /acct mount cgroup none /acct cpuacct mkdir /acct/uid "【mount <type> <device> <dir> [ <mountoption> ] //在目录<dir>挂载指定的设备。<device> 可以是以 mtd@name 的形式指定一个mtd块设备。<mountoption>包括 ro、rw、remount、noatime、 ...】" # 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 <owner> <group> <path> //改变文件的所有者和组。】" "【后面的一些行因为类似,就省略了】" ..... # 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 "【触发一个事件,将该action排在某个action之后(用于Action排队)】" trigger late-init # Load properties from /system/ + /factory after fs mount. on load_all_props_action "【从/system,/vendor加载属性。默认包含在init.rc】" load_all_props # 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 "【触发一个事件。用于将一个action与另一个 action排列。】" trigger early-fs trigger fs trigger post-fs trigger post-fs-data # 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_all_props_action # Remove a file to wake up anything waiting for firmware. trigger firmware_mounts_complete trigger early-boot trigger boot on post-fs ... "【一些创造目录,建立链接,更改权限的操作,这里省略】" on post-fs-data ... "【一些创造目录,建立链接,更改权限的操作,这里省略】" "【恢复指定文件到file_contexts配置中指定的安全上线文环境】" restorecon /data/mediaserver "【将系统属性<name>的值设置为<value>,即以键值对的方式设置系统属性】" # Reload policy from /data/security if present. setprop selinux.reload_policy 1 "【以递归的方式恢复指定目录到file_contexts配置中指定的安全上下文中】" # Set SELinux security contexts on upgrade or policy update. restorecon_recursive /data # 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 "【设置主机名为localhost】" hostname localhost "【设置域名localdomain】" domainname localdomain "【设置资源限制】" # set RLIMIT_NICE to allow priorities from 19 to -20 setrlimit 13 40 40 "【这里省略了一些chmod,chown,等操作,不多解释】" ... # Define default initial receive window size in segments. setprop net.tcp.default_init_rwnd 60 "【重启core服务】" 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 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 seclabel u:r:logd:s0 "【Healthd是android4.4之后提出来的一种中介模型,该模型向下监听来自底层的电池事件,向上传递电池数据信息给Framework层的BatteryService用以计算电池电量相关状态信息】" service healthd /sbin/healthd class core critical seclabel u:r:healthd:s0 "【控制台进程】" service console /system/bin/sh "【为当前service设定一个类别.相同类别的服务将会同时启动或者停止,默认类名是default】" class core "【服务需要一个控制台】" console "【服务不会自动启动,必须通过服务名显式启动】" disabled "【在执行此服务之前切换用户名,当前默认的是root.自Android M开始,即使它要求linux capabilities,也应该使用该选项.很明显,为了获得该功能,进程需要以root用户运行】" user shell 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 "【创建一个unix域下的socket,其被命名/dev/socket/<name>. 并将其文件描述符fd返回给服务进程.其中,type必须为dgram,stream或者seqpacke,user和group默认是0.seclabel是该socket的SELLinux的安全上下文环境,默认是当前service的上下文环境,通过seclabel指定】" 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 "【ServiceManager是一个守护进程,它维护着系统服务和客户端的binder通信。 在Android系统中用到最多的通信机制就是Binder,Binder主要由Client、Server、ServiceManager和Binder驱动程序组成。其中Client、Service和ServiceManager运行在用户空间,而Binder驱动程序运行在内核空间。核心组件就是Binder驱动程序了,而ServiceManager提供辅助管理的功能,无论是Client还是Service进行通信前首先要和ServiceManager取得联系。而ServiceManager是一个守护进程,负责管理Server并向Client提供查询Server的功能。】" service servicemanager /system/bin/servicemanager class core user system group system critical onrestart restart healthd "【servicemanager 服务启动时会重启zygote服务】" onrestart restart zygote onrestart restart media onrestart restart surfaceflinger onrestart restart drm "【Vold是Volume Daemon的缩写,它是Android平台中外部存储系统的管控中心,是管理和控制Android平台外部存储设备的后台进程】" service vold /system/bin/vold class core socket vold stream 0660 root mount ioprio be 2 "【Netd是Android系统中专门负责网络管理和控制的后台daemon程序】" 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 "【debuggerd是一个daemon进程,在系统启动时随着init进程启动。主要负责将进程运行时的信息dump到文件或者控制台中】" service debuggerd /system/bin/debuggerd class main service debuggerd64 /system/bin/debuggerd64 class main "【Android RIL (Radio Interface Layer)提供了Telephony服务和Radio硬件之间的抽象层】" # for using TK init.modem.rc rild-daemon setting #service ril-daemon /system/bin/rild # class main # socket rild stream 660 root radio # socket rild-debug stream 660 radio system # user root # group radio cache inet misc audio log "【提供系统 范围内的surface composer功能,它能够将各种应用 程序的2D、3D surface进行组合。】" service surfaceflinger /system/bin/surfaceflinger class core user system group graphics drmrpc onrestart restart zygote "【DRM可以直接访问DRM clients的硬件。DRM驱动用来处理DMA,内存管理,资源锁以及安全硬件访问。为了同时支持多个3D应用,3D图形卡硬件必须作为一个共享资源,因此需要锁来提供互斥访问。DMA传输和AGP接口用来发送图形操作的buffers到显卡硬件,因此要防止客户端越权访问显卡硬件。】" #make sure drm server has rights to read and write sdcard #### service drm /system/bin/drmserver class main user drm # group drm system inet drmrpc #### group drm system inet drmrpc sdcard_r #### "【媒体服务,无需多说】" service media /system/bin/mediaserver class main user root #### # google default #### # user media #### group audio camera inet net_bt net_bt_admin net_bw_acct drmrpc mediadrm media sdcard_r system net_bt_stack #### # google default #### # 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 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 #### group graphics media audio #### disabled oneshot "【在Android系统中,PackageManagerService用于管理系统中的所有安装包信息及应用程序的安装卸载,但是应用程序的安装与卸载并非PackageManagerService来完成,而是通过PackageManagerService来访问installd服务来执行程序包的安装与卸载的。】" service installd /system/bin/installd class main socket installd stream 600 system system service flash_recovery /system/bin/install-recovery.sh class main seclabel u:r:install_recovery:s0 oneshot "【vpn相关的服务】" 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 "【android中有mtpd命令可以连接vpn】" 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 "【可以用dumpstate 获取设备的各种信息】" service dumpstate /system/bin/dumpstate -s class main socket dumpstate stream 0660 shell log disabled oneshot "【mdnsd 是多播 DNS 和 DNS 服务发现的守护程序。】" service mdnsd /system/bin/mdnsd class main user mdnsr group inet net_raw socket mdnsd stream 0660 mdnsr inet disabled oneshot "【触发关机流程继续往下走】" service pre-recovery /system/bin/uncrypt class main disabled "【当服务退出时,不重启该服务】" oneshot
init.c全解析
int main( int argc, char **argv ) { #创 建一些linux根文件系统中的目录 mkdir( "/dev", 0755 ); mkdir( "/proc", 0755 ); mkdir( "/sys", 0755 ); mount( "tmpfs", "/dev", "tmpfs", 0, "mode=0755" ); mkdir( "/dev/pts", 0755 ); mkdir( "/dev/socket", 0755 ); mount( "devpts", "/dev/pts", "devpts", 0, NULL ); mount( "proc", "/proc", "proc", 0, NULL ); mount( "sysfs", "/sys", "sysfs", 0, NULL ); #init的 标准输入,标准输出,标准错误文件描述符定向到__null__,意味着没有输入和输出,它的输入和输出全部写入到Log中 open_devnull_stdio(); #初始化 log 写入init进 信息 log_init(); #读取并 且解析init.rc文件(这个文件在根目录下) parse_config_file( "/init.rc" ); #取得硬件 为打印我们的设备名fs100 get_hardware_name(); snprintf( tmp, sizeof(tmp), "/init.%s.rc", hardware ); #读取并 且解析硬件相关的init脚本文件, parse_config_file( tmp ); #触发在init脚本文件中名字为early-init的action,并且执行其commands,其实是: on early-init action_for_each_trigger( "early-init", action_add_queue_tail ); drain_action_queue(); #初始化动态设备管理,设备文件有变化时反应给内核,后面具体解释 device_fd = device_init(); # 初 始 化 设 备 管 理 务 #加载启动动画,如果动画打开失败,则在屏幕上打印: A N D R O I D字样。 if ( load_565rle_image( INIT_IMAGE_FILE ) ) { fd = open( "/dev/tty0", O_WRONLY ); if ( fd >= 0 ) { const char *msg; msg = "\n" "\n" "\n" 879 "\n" "\n" "\n" "\n" /* console is 40 cols x 30 lines */ "\n" "\n" "\n" "\n" "\n" "\n" "\n" /* " A N D R O I D ";开机动画 */ write( fd, msg, strlen( msg ) ); close( fd ); } } #触发 在init脚本文件中名字为init的action,并且执行其commands,其实是:on init action_for_each_trigger( "init", action_add_queue_tail ); drain_action_queue(); #启动系统属性服务: system property service property_set_fd = start_property_service(); #创建socket用来处理孤儿进程信号 if ( socketpair( AF_UNIX, SOCK_STREAM, 0, s ) == 0 ) { signal_fd = s[0]; signal_recv_fd = s[1]; fcntl( s[0], F_SETFD, FD_CLOEXEC ); fcntl( s[0], F_SETFL, O_NONBLOCK ); fcntl( s[1], F_SETFD, FD_CLOEXEC ); fcntl( s[1], F_SETFL, O_NONBLOCK ); } #触发 在init脚本文件中名字为early-boot和boot的action,并且执行其commands,其实是:on early-boot和on boot action_for_each_trigger( "early-boot", action_add_queue_tail ); action_for_each_trigger( "boot", action_add_queue_tail ); drain_action_queue(); #启动所有属性变化触发命令,其实是: on property:ro.xx.xx=xx queue_all_property_triggers(); drain_action_queue(); #进入 死循环() for (;; ) { #启 动所有init脚本中声明的service, #如 :266 service servicemanager /system/bin/servicemanager #user system #critical #onrestart restart zygote #onrestart restart media restart_processes(); #多路监听设备管理,子进程运行状态,属性服务 nr = poll( ufds, fd_count, timeout ); if ( nr <= 0 ) continue; if ( ufds[2].revents == POLLIN ) { read( signal_recv_fd, tmp, sizeof(tmp) ); while ( !wait_for_one_process( 0 ) ) ; continue; } if ( ufds[0].revents == POLLIN ) handle_device_fd( device_fd ); if ( ufds[1].revents == POLLIN ) handle_property_set_fd( property_set_fd ); if ( ufds[3].revents == POLLIN ) handle_keychord( keychord_fd ); } return(0); }
参考文章