Android系统学习(四)------关于init进程及开机自启动

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/Guet_Kite/article/details/87655437

你好!这里是风筝的博客,

欢迎和我一起交流。


  初入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 <trigger>就是一个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 <name> <pathname> [ <argument> ]*就是一个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:<key>=<value>			当属性值满足条件时触发 如:on property:ro.debuggable=1
fs								挂载mtd分区时触发
boot							基本网络的初始化,内存管理等时触发
post-fs							改变系统目录的访问权限时触发
device-added-<path>				设备节点添加时触发
device-removed-<path>			设备节点删除时触发
service-exited-<name>   		在特定服务(service)退出时触发


Command							说明
--------------------------------------------------------------------------------
import <filename>				导入init.XX.rc、xxx.conf等文件
chmod <octal-mode> <path>		更改文件访问权限
chown <owner> <group> <path>	更改文件所有者和组
chdir <directory>				变更工作目录
chroot <directory> 				改变进程根目录
insmod <path>					加载XX.ko驱动模块
start <service>					如果服务尚未运行,则启动服务
stop <service>					如果当前在运行则停止服务运行
class_start <serviceclass>		启动指定类的所有服务
class_stop <serviceclass>		停止指定类的所有服务
class_reset <serviceclass>		重启指定类的所有服务
setprop <name> <value>			设置系统属性< Name >至<Value>
export <name> <value> 			设置全局环境变量,这个变量值可以被所有进程访问(全局的,一直存在)
mkdir <path> [mode] [owner] [group]mkdir <path> [mode] [owner] [group]	创建目录,后面项缺省值为 mode,owner,group: 0755 root root
trigger <event>					触发一个事件。用于将action从另一个action 队列
exec <path> [ <argument> ]* 	执行<path>指定的Program,并可以带有执行参数,会阻塞init
ifup <interface>				启动某个网络接口,使其为up状态,通过netcfg可以查看,ifup eth0  等价于 netcfg eth0 up 功能一样
hostname <name>					设置设备的主机名
domainname <name>				设置网络域名localdomain
mount <type> <device> <dir> [ <mountoption> ]*	把device挂接到dir目录下面,文件系统类型为type
write <path> <string> [ <string> ]*				打开一个文件,利用write命令写入一个或多个字符串
loglevel <level>				设置log级别
symlink <target> <sym_link>		创建连接到<target>的<sym_link>符号链接


Option							说明
--------------------------------------------------------------------------------
class <class_name>		服务属于class_name这个类。缺省值service属于 “default” 类。同一个class下面的服务可以一起启动或停止
disabled 				服务不会随class自动启动,要用start server_name 或 property_set("ctl.start", server_name);才能启动
oneshot					当服务退出后,不会再重新启动,如果没有加这个option,则服务默认退出后又会重新重启
user <username>			声明服务的用户名,缺省值应该为root用户
group <groupname> [ <groupname> ]*		声明服务所属组名,可以一次声明属于多个组
onrestart   + command	服务重启的时,会执行onrestart后面的command
setenv <name> <value>	在当前服务进程中设置环境变量name的值为value,环境变量仅在本进程内生效
critical				声明为设备的循环服务。如果服务在四分钟内退出了四次,则设备会进入recovery模式
socket <name> <type> <perm> [ <user> [ <group> ] ] 	创建名为/dev/socket/<name>的unix domain socket ,并把它的句柄fd传给本服务进程
seclabel <securitycontext>							执行服务之前改变安全级别

参考:
android系统启动流程之init.rc详细分析笔记
init.rc内容解析可以看看这个:Android系统init进程启动及init.rc全解析

展开阅读全文

没有更多推荐了,返回首页