开篇:为什么写这篇文章
本文到底是讲什么的?
首先我先来解释一下本文到底讲什么的。用一句话来说:本文讲解的主要内容是,如何通过修改Android操作系统源码,来配置一个自定义的开机启动进程。有些人也许会问,这有什么用?问的好,一项实用的技术必然要有用处才会有价值。首先说明的是,如果你的工作或项目只是做一个应用程序app,那本文确实没有什么用处。但如果你的公司做的是Android系统开发,或者本身就是一家做硬件的公司,那本文可能就会有不少用处了;举个例子:假如你们公司做了一台搭载Android的嵌入式设备,这台设备有某个特殊的传感器是一般手机没有的,传感器属于硬件,那想让硬件工作就必然有驱动程序,现在我们想让这个传感器在设备一开机的时候就立刻启动,那我们就需要知道如何配置一个Android的开机启动进程了。而本文正是讲这部分内容的。如果用最简单的一句话描述本文是讲什么的,那其实只要文章题目的一句话,但是其实涉及到的知识又多又杂,加上本人也处在底层开发的探索阶段,所以,涉及到的知识我只讲到我们能用到的深度为止,如果想深入学习,我会附上其他博主对这些知识深入分析的文章的链接。
需要做哪些准备工作?
大体上有哪些知识点
Android源码目录结构介绍
如何在源码中添加自己的可执行文件
我们如果想要一个属于自己的开机启动进程,那首先就要一个我们自己编写的程序了,一般来说,在实际项目中这个程序就是我们想要开机启动的驱动,正如文章开头所说的那样,但是在我们这个例子中,我就不搞那么复杂了,写一个最简单的C语言程序,让它作为我们的开机启动进程。我给这个程序命名为loop,也就是循环的意思,代码如下:
-
#include<stdio.h>
-
-
int main(){
-
-
int i=
0;
-
-
for(i;i<
100;i++)
-
-
{
-
-
sleep(
180);
-
-
printf(
"I am a process\n");
-
-
}
-
-
return
0;
-
-
}
怎么样,是不是非常简单,任何有任何语言编程基础的人都能看懂,我们设置了一个循环,每次在执行循环体的内容前,将主线程休眠180秒,一共循环100次,而循环的内容就是输出一条语句。我为什么要这么做,主要是因为,如果这个程序一下子就执行完成,这个进程就死掉了,那我们就不能在命令行终端中看到这个进程,所以每次循环的时候我都会让它休眠180秒,这样算下来,这个进程理论上可以保活5小时,嗯,够长了,手速再慢的人也能在5小时内用命令行终端登入开发板,然后输入命令查看当前活动的进程了。
如何编写自己的Android.mk
开始编译Android源码
sudo apt-get install flex libncurses5-dev zlib1g-dev gawk minicom
source build/envsetup.sh
lunch aosp_nanopi3-userdebug
make -j8
cd sd-fuse_s5p6818
./fusing.sh /dev/sdx
init.rc介绍
我先来做个名词解释,什么是init.rc,那就要从什么是init说起。init是由Android的Linux内核启动的第一个第一个进程,这个进程非常特殊,它的PID永远是1,并且这个进程是不会死亡的,如果它死亡,内核就会崩溃。init进程启动后会fork出很多及其重要的系统进程,比如我们做应用开发的时候都耳熟能详的zygote进程,我们所有的应用程序的进程都由zygote拉起。解释完了init进程,我们再说init.rc,init.rc是一个规定init进程行为和动作的配置文件。init进程可以做哪些事情,都由它规定。关于init.rc的详细介绍,大家可以参考这篇文章:
http://qiangbo.space/2017-01-28/AndroidAnatomy_Init/?hmsr=toutiao.io&utm_medium=toutiao.io&utm_source=toutiao.io。
我们这里只对init.rc做一个简单的介绍,init.rc文件中只包含两种声明,on和service,我们可以把on称为行为,把service称为服务(这里的服务和应用开发中四大组件中的服务以及通过context.getSystemService()所得到的系统服务都不是一个东西,我一直不知道该怎么给它起名,姑且叫它init服务)。service声明了服务以及服务的各种行为。我们标题中说的开机启动进程就是这里的init服务。service只定义服务,但不能让服务做任何事情,如果你需要服务能够产生启动或者停止等相关动作,你就需要on,每个on下面的有各种命令,其中就包括很多对init服务的操作。这里要提到的是,我们要修改的init.rc文件在device/friendly-arm/nanopi3目录下,也就是厂商定制的版本,如果你使用的是别的开发板,可以去相应的目录找找。我们来看看init.rc中on和service两个典型的定义:
on early-init
# Set init and its forked children's oom_adj.
write /proc/1/oom_score_adj -1000
# 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.
setcon u:r:init:s0
# Set the security context of /adb_keys if present.
restorecon /adb_keys
start ueventd
# create mountpoints
mkdir /mnt 0775 root system
…
…
…
service ueventd /sbin/ueventd
class core
critical
seclabel u:r:ueventd:s0
#后面的一行是注释,这个不用我多说。我们看到里面有很多东西感觉很晕,现在我们只重点关注几行,我们看到on下面有一行是“start ueventd”,而下面service的名字也是ueventd,这表明什么,我想大家都猜到了,那就是在early-init这个on启动了ueventd这个service。对,on就是这样启动sercice的,当然,on还有例如restart以及stop等等其它对service的操作,分别是让service停止并重新启动以及停止,不过on并不只是为service而生的,它还有许多其它的命令,在这里我就不详细介绍了,大家可以去网上搜索相关文章或者看这本书《构建嵌入式Android系统》。
我们看完了on再看看service,service我也只是简单介绍一下,service关键字声明了你要定义一个service,而ueventd就是这个service的名字,至于后面的目录则是这个service对应的可执行文件在系统中的位置。注意:这里是说在系统中的位置,也就是在开发板运行你的Android源码编译的系统后的目录,而不是源码的目录,至于Android源码的编译,等下再讲。接下来,我们可以看到service下面也有很多东西,这里我们不叫它们命令了,叫属性或者参数也许会比较好,其中比较重要的是class core,表示这个service属于core这个class,class我们不需要深入去管它,只要把它理解成一组service的集合就行,至于后面的属性,等下我们开始配置service的时候再说。
正式开始在init.rc中配置service
service qya /system/bin/loop
class main
console
oneshot
其中console表示服务需要并运行在控制台,oneshot表示服务只运行一次,在退出时将服务设置为禁用。你可以根据你的需要来增加这些属性,我写的两个属性并不一定都是必须的。《构建嵌入式Android系统》这本书的第六章对这些属性参数介绍的非常详细,如果网上找不到相应的文章可以拿这本书看看。
on property:vold.decrypt=trigger_restart_min_framework
class_start main
我在这里再简单说一下on,on分为两种,第一种一共有7个,它们是一定会随inti进程的启动而执行的,比如我们上面介绍init.rc的时候展示的early-init正是这7个on中的第一个。而第二种on则是在满足某些特定条件时才会启动的,比如我们这里的这个on就是第二种。我们看到它下面有一条class_start main命令,而我们的service下面第一个属性正是class main。所以可以理解了,这个class_start main命令是启动main下面所有service。通览init.rc文件,我们会发现属于main的service非常多,所以我们的service也就搭一趟便车,挤上main的队伍。
什么是SELinux/SEAndroid?
最后一步:重新编译刷机并运行
总结:
按照惯例,还是得总结一下。我们来理一理本文所涉及到的知识点,有:编译Android源码,Android.mk文件的编写,修改Android源码,init.rc介绍,SEAndroid。每个知识点都是为了我们能成功运行自定义service而讲解,所以讲解的深度其实都是不够的,我在文中不少地方都已经贴出了详细介绍每个知识点的博文的地址供大家后续学习。加上本文篇幅也算比较长的了,加上介绍的知识又多又杂,难免有疏漏之处,如果你按照本文的方法在配置过程中出现了失败的地方,可以根据具体问题上网搜索,只要坚持,一定可以解决。Android底层的知识的特点就是多而杂,所以做底层开发最需要的就是耐心,祝所有走在底层开发的小伙伴都能学习顺利!