初入Android,本篇文章不过是拾人牙慧,见笑了~
init进程,它是内核启动的第一个用户级进程,进程号为1。它通过解析init.rc脚本来构建出系统的初始形态,它的生命周期贯穿整个linux 内核运行的始终。
核心代码在system/core/init/init.cpp
程序解读不再累述,可以看这篇文章:Android Init进程源码分析
大概会做这些事情:
启动守护进程ueventd
设置环境变量
创建一些基本目录,并挂载("/dev", “/dev/pts”,"/dev/pts",,,,)
把标准输入、标准输出和标准错误重定向到空设备文件"/dev/null"
启动kernel log(创建节点/dev/kmsg)
初始化Android的属性系统
解析DT和命令行中的kernel启动参数(dt优先级大于命令行)
设置系统属性
调用selinux_initialize函数启动SELinux
创建epoll,初始化signal(子进程终止信号处理)
加载默认属性,启动属性服务(build.prop等文件)
解析init.rc文件
把rc文件中action加入执行队列中,稍后会启动trigger为"early-init"等的action
最后init进程会进入一个死循环,在这个无限循环中,init进程会做以下事:
1.执行action_queue列表的action
2.检查系统中是否有进程需要重启
3.处理系统属性变化事件
4.处理子进程的终止(signal方式,会产生SIGCHLD信号发送给init进程),回收僵尸进程。
其中,在 Android中使用启动脚本init.rc,可以在系统的初始化过程中进行一些简单的初始化操作。这个脚本被直接安装到目标系统的根文件系统中,被 init可执行程序解析。 init.rc是在init启动后被执行的启动脚本
init.rc的使用方法,可以参考说明文件system/core/init/readme.txt。
init.rc语法基本由四个部分组成:
Actions、Commands、Services、Options
#这里on 就是一个Actions
#write 、restorecon 、start 都是它的Commands
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
#这里service [ ]*就是一个Services
#class、critical、seclabel 都是它的Options
service healthd /sbin/healthd
class core
critical
seclabel u:r:healthd:s0
每个Actions 和 Services都表示一个新的段落section的开始,所有的commands和options 都是归属于上方最近的一个段落。
而且每个rc文件里又可以import 导入其他的init.xx.rc文件。
这里试着尝试通过配置init.rc文件实现开机自启动脚本:
脚本实现每次开机后台抓取dmesg写入文本。
修改system/core/rootdir/init.rc:
diff --git a/rootdir/init.rc b/rootdir/init.rc
+
+service kmsg_log /system/bin/kmsg.sh
+ class late_start
+ seclabel u:r:kmsg_log:s0
+ #实测,seclabel 这句很重要,必加
修改device/rockchip/rk3399/device.mk:
拷贝脚本到系统目录中:PRODUCT_COPY_FILES 就是将kmsg.sh文件从源码树中拷贝到镜像文件系统的system/bin路径下
diff --git a/device.mk b/device.mk
+
+PRODUCT_COPY_FILES += \
+ device/rockchip/rk3399/kmsg.sh:system/bin/kmsg.sh
编写device/rockchip/rk3399/kmsg.sh:
#!/system/bin/sh
count=3
for i in `seq $(($count+1))`
do
#echo "i is $i"
if [ ! -f "/data/logs/kmsg_$i.log" ]; then
break
fi
done
if [ $i -eq $(($count+1)) ]; then
rm /data/logs/kmsg_1.log
i=$count
for j in `seq $(($count-1))`
do
#echo "/data/log/kmsg_$(($j+1)).log /data/log/kmsg_$j.log"
mv /data/logs/kmsg_$(($j+1)).log /data/logs/kmsg_$j.log
done
fi
#save date, otherwise the date of file will be flushed.
date > /data/logs/kmsg_$i.log
dmesg >> /data/logs/kmsg_$i.log
cat /proc/kmsg >> /data/logs/kmsg_$i.log
脚本参考:[RK3399][Android7.1] 调试笔记 — 开机后台抓取kmsg log
不过很遗憾,实践时会发现不会成功,有错误:
init: Service kmsg_log does not have a SELinux domain defined.
这里就涉及一个很重要的东西,就是SELinux!
这是一种系统安全机制,如果没有在SELinux里给予service对应的权限规则,那么系统是不会让service正常启动的!
所以我们需要定义一个SELinux domain,添加对应的权限。
添加device/rockchip/common/sepolicy/kmsg-log.te文件:
前面几句是SELinux 域的初始模板,可以根据该可执行文件执行的具体操作为该模板添加规则
type kmsg_log, domain;
type kmsg_log_exec, exec_type, file_type;
type kmsg_log-cnf, file_type;
init_daemon_domain(kmsg_log)
allow kmsg_log kmsg_log-cnf:file read;
allow kmsg_log rootfs:lnk_file getattr;
allow kmsg_log kernel:system syslog_read;
allow kmsg_log kmsg_device:chr_file { read open };
allow kmsg_log kmsg_log-cnf:file { getattr open };
修改device/rockchip/common/sepolicy/file_contexts文件:
diff --git a/sepolicy/file_contexts b//sepolicy/file_contexts
+
+/system/bin/kmsg_log.sh u:object_r:kmsg_log_exec:s0
这里就不用修改Android.mk添加te文件了,因为在
system/sepolicysystem/sepolicy/Android.mk文件里条注释:
# Builds paths for all policy files found in BOARD_SEPOLICY_DIRS and the LOCAL_PATH.
就是说会自动去编译BOARD_SEPOLICY_DIRS路径下的te文件,我们搜索可知,BOARD_SEPOLICY_DIRS就是device/rockchip/common/sepolicy/
关于SELinux domain的编写,
参考:Android init.rc启动服务
深入理解SELinux SEAndroid(第一部分)
SEAndroid安全机制简要介绍和学习计划
最后把boot.img和system.img烧写进系统,就可以看到脚本开机自启了。
init.c 、init.rc init.xx.rc 等最终会编译到ramdisk.img(根文件系统)中,和kernel一起打包成boot.img。android启动后每次都会从boot.img中解压出init.c等文件到内存,所以要修改必须修改替换boot.img
另外:通过getprop | grep init.svc 可查看所有的service运行状态。状态总共分为:running, stopped, restarting
如果文件没有执行权限的话,可以在system/coce/libcutils/fs_config.c文件的android_files[]数组里添加权限
最后给出一些参数
Triggers 说明
--------------------------------------------------------------------------------
early-init 在初始化早期阶段触发
late-init 在初始化晚期阶段触发
init 在初始化阶段触发
late-init 在初始化晚期阶段触发
boot/charger 当系统启动/充电时触发
property:= 当属性值满足条件时触发 如:on property:ro.debuggable=1
fs 挂载mtd分区时触发
boot 基本网络的初始化,内存管理等时触发
post-fs 改变系统目录的访问权限时触发
device-added- 设备节点添加时触发
device-removed- 设备节点删除时触发
service-exited- 在特定服务(service)退出时触发
Command 说明
--------------------------------------------------------------------------------
import 导入init.XX.rc、xxx.conf等文件
chmod 更改文件访问权限
chown 更改文件所有者和组
chdir 变更工作目录
chroot 改变进程根目录
insmod 加载XX.ko驱动模块
start 如果服务尚未运行,则启动服务
stop 如果当前在运行则停止服务运行
class_start 启动指定类的所有服务
class_stop 停止指定类的所有服务
class_reset 重启指定类的所有服务
setprop 设置系统属性< Name >至
export 设置全局环境变量,这个变量值可以被所有进程访问(全局的,一直存在)
mkdir [mode] [owner] [group]mkdir [mode] [owner] [group] 创建目录,后面项缺省值为 mode,owner,group: 0755 root root
trigger 触发一个事件。用于将action从另一个action 队列
exec [ ]* 执行指定的Program,并可以带有执行参数,会阻塞init
ifup 启动某个网络接口,使其为up状态,通过netcfg可以查看,ifup eth0 等价于 netcfg eth0 up 功能一样
hostname 设置设备的主机名
domainname 设置网络域名localdomain
mount [ ]* 把device挂接到dir目录下面,文件系统类型为type
write [ ]* 打开一个文件,利用write命令写入一个或多个字符串
loglevel 设置log级别
symlink 创建连接到的符号链接
Option 说明
--------------------------------------------------------------------------------
class 服务属于class_name这个类。缺省值service属于 “default” 类。同一个class下面的服务可以一起启动或停止
disabled 服务不会随class自动启动,要用start server_name 或 property_set("ctl.start", server_name);才能启动
oneshot 当服务退出后,不会再重新启动,如果没有加这个option,则服务默认退出后又会重新重启
user 声明服务的用户名,缺省值应该为root用户
group [ ]* 声明服务所属组名,可以一次声明属于多个组
onrestart + command 服务重启的时,会执行onrestart后面的command
setenv 在当前服务进程中设置环境变量name的值为value,环境变量仅在本进程内生效
critical 声明为设备的循环服务。如果服务在四分钟内退出了四次,则设备会进入recovery模式
socket [ [ ] ] 创建名为/dev/socket/的unix domain socket ,并把它的句柄fd传给本服务进程
seclabel 执行服务之前改变安全级别
参考:
android系统启动流程之init.rc详细分析笔记
init.rc内容解析可以看看这个:Android系统init进程启动及init.rc全解析