Android如何配置init.rc中的开机启动进程(service)

本文介绍如何通过修改Android操作系统源码来配置自定义开机启动进程。主要内容包括:编译Android源码、编写Android.mk文件、修改init.rc配置文件以及理解和配置SEAndroid安全策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

转载。 http://blog.youkuaiyun.com/qq_28899635/article/details/56289063

开篇:为什么写这篇文章

先说下我自己的情况,我是个普通的学生,之前在学校一直做Android应用开发,找实习的时候也一直想找相关的工作,来到现在这家公司以后,由于业务调整,被领导安排去做底层开发,本来我对底层的东西一无所知,加上其实并不感兴趣,其实一开始感觉还是很难的,不过刚刚工作,只有小孩子才在乎喜欢不喜欢,成年人只在乎是否有利。我本着技多不压身的心态,开始了底层开发学习之旅,做Android底层开发的人其实相对应用开发来说少很多,所以网上相关的文章和学习资料也是非常的有限,我在最近两个月时间总算琢磨出点东西,打算写这篇文章分享给大家,给底层的世界填一分彩,不过本人才疏学浅,尚在学习当中,如果有什么错误,请大家不吝赐教。

本文到底是讲什么的?

首先我先来解释一下本文到底讲什么的。用一句话来说:本文讲解的主要内容是,如何通过修改Android操作系统源码,来配置一个自定义的开机启动进程。有些人也许会问,这有什么用?问的好,一项实用的技术必然要有用处才会有价值。首先说明的是,如果你的工作或项目只是做一个应用程序app,那本文确实没有什么用处。但如果你的公司做的是Android系统开发,或者本身就是一家做硬件的公司,那本文可能就会有不少用处了;举个例子:假如你们公司做了一台搭载Android的嵌入式设备,这台设备有某个特殊的传感器是一般手机没有的,传感器属于硬件,那想让硬件工作就必然有驱动程序,现在我们想让这个传感器在设备一开机的时候就立刻启动,那我们就需要知道如何配置一个Android的开机启动进程了。而本文正是讲这部分内容的。如果用最简单的一句话描述本文是讲什么的,那其实只要文章题目的一句话,但是其实涉及到的知识又多又杂,加上本人也处在底层开发的探索阶段,所以,涉及到的知识我只讲到我们能用到的深度为止,如果想深入学习,我会附上其他博主对这些知识深入分析的文章的链接。

需要做哪些准备工作?

首先,在硬件上,我希望你能有一块能搭载Android系统的嵌入式开发板,比如我用的是Friendly-arm的NanoPi M3,也许你会问,一台普通的Android手机行不行,这个说实话我没有试过,但是市面上的手机有很多权限是被厂商限制的,所以如果用手机来测试,可能会发生很多迷之问题,所以我建议还是使用一块开发板,不一定非要和我的型号一样,只要能跑Android就行。其次,在软件上,我希望你准备好了一份Android系统的源码,关于如何获取Android的源码我这里就不讲了,网上有很多文章讲这个,你可以从Google官方的渠道获取,如果你使用开发板,一般开发板的提供商也会提供定制的Android源码,那我们就直接从开发板的提供商的网站获取就行了。最后,我希望你有一台装有mac OS或者Linux操作系统的电脑,本文所开展的工作只支持以上两种系统,不支持Windows,所以我希望你拥有一台这样的电脑,其实我比较推荐unbuntu 14.04,这也是我使用的开发板的官方文档所推荐的,你当然也可以用Mac,但是说实话,我最开始开展工作时用的是搭载mac OS 10.12.02的Macbook pro 2016,各种发生迷之错误,其实按道理来说是可以解决的,但是由于做底层开发的人本来就不多,以及用Mac做底层开发的人就更少了,所以网上关于解决这些问题的学习资料以及文章少之又少,导致问题长时间无法解决,浪费时间,所以还是推荐使用ubuntu 14.04。
最后,阅读本文需要哪些知识?本文尽量讲的通俗且浅显易懂,你不需要有任何底层开发的前置知识,你只需要对Android系统有一个比较全面的认识就行,你说你写App的水平不高?这些都没关系,都不会影响你阅读这篇文章。但是我希望你能了解最起码的Linux命令,不用太复杂,最基本的就行,实在不行,Windows的cmd命令总用过吧,只要会用命令行工具进入硬盘下某个目录就行。

大体上有哪些知识点

大体上有哪些知识点,我画了以下一张图来描述:

这么一看,一目了然,我们需要将我们自己写的程序(示例中我用的是C语言)将它配置给init.rc这个文件;然后通过编写Android.mk来对它进行编译描述,什么是编译描述呢?我们可以这么理解,只要我们写的这个程序的所在目录下有Android.mk文件,它就可以和Android源码一样用make命令编译;然后我们要通过SEAndroid给我们的程序授权,否则程序会因为权限问题而无法执行;以上这些都是在Android源码中进行修改的;最后,我们将源码编译,刷机,运行,你亲自编译的Android操作系统就可以运行在你的开发板上,同时,你的C程序也可以跟随Android系统的启动而实现开机启动。

Android源码目录结构介绍

我相信读者手里已经拿到一份Android源码了,无论它是从哪里获得的(哪怕是同学同事拷贝给你的)。我的源码版本是5.1.1,源码版本对本文的影响不大,只要版本不是太老就行。我们先来对Android源码做个简要的介绍。打开源码的文件夹,我们会发现有很多目录,让我们眼花缭乱,确实,Android系统是一个非常复杂的软件,它必须面面俱到,所以这里面的源码覆盖了方方面面,想要全部把它看完是不可能的,我相信有不少人看过Linux内核源码,光是Linux内核的源码可能就能让一个人一生都无法穷尽,而Linux内核只是Android的最底层,可想而知Android的源码数量有多么庞大,所以我在这里只说几个我们最常用的目录以及本文会涉及到的目录。
不少人做Android都是做应用开发,做应用开发入门以后很多人都想要进阶,这时候就会买一些进阶的书,这些进阶的书很多都涉及Android源码分析,比如比较火的《Android开发艺术探索》,《Andoird源码设计模式解析与实战》等等,我们刚学Android的时候就知道,Android系统分为四层,我们开发的应用处于Application层,而Application层的下面一层是framework层,想做好应用开发,了解一下下层机制是必不可少的,所以我刚才说的源码分析的书籍所讲解的源码都在framework层,大家其实也能发现,这些书里所讲的源码都是java代码。那打开Android源码目录,我们能看到一个framework文件夹,所以framework层的源码就在这个文件夹下,你进去找找,可以找到不少应用开发时常用的API。
上面说的是闲话,现在我来介绍一个等一下会多次使用的一个目录——device,点开这个目录,我们会看到很多手机品牌的名字,比如htc啊,moto啊,samsung啊等等。这是什么意思呢?Android系统会运行在各种品牌的手机上,但是各种品牌的手机在硬件上都会有差异,因此,很多厂商都会对源码进行定制,以修改它的某些部分来配合自家硬件的特性,比如我有某个传感器,别的厂商没有,我的摄像头比较特殊,运行起来比较复杂,这些都属于硬件差异。由于我使用的Friendly-arm提供的源码,所以我可以在这个目录下看到一个friendly-arm的文件夹,里面是针对friendly-arm的开发板定制的一些代码,等会儿我们会多次访问这里。
还有一些等下我们会常用的目录,比如system,external,out等等,这些我们等会儿用到的时候再说。


如何在源码中添加自己的可执行文件

我们如果想要一个属于自己的开机启动进程,那首先就要一个我们自己编写的程序了,一般来说,在实际项目中这个程序就是我们想要开机启动的驱动,正如文章开头所说的那样,但是在我们这个例子中,我就不搞那么复杂了,写一个最简单的C语言程序,让它作为我们的开机启动进程。我给这个程序命名为loop,也就是循环的意思,代码如下:
  1. #include<stdio.h>  
  2.   
  3. int main(){  
  4.   
  5.     int i=0;  
  6.   
  7.     for(i;i<100;i++)  
  8.   
  9.     {  
  10.   
  11.       sleep(180);  
  12.   
  13.       printf(”I am a process\n”);  
  14.   
  15.     }  
  16.   
  17.     return 0;  
  18.   
  19. }  
#include<stdio.h>

int main(){

int i=0;

for(i;i&lt;100;i++)

{

  sleep(180);

  printf("I am a process\n");

}

return 0;

}


怎么样,是不是非常简单,任何有任何语言编程基础的人都能看懂,我们设置了一个循环,每次在执行循环体的内容前,将主线程休眠180秒,一共循环100次,而循环的内容就是输出一条语句。我为什么要这么做,主要是因为,如果这个程序一下子就执行完成,这个进程就死掉了,那我们就不能在命令行终端中看到这个进程,所以每次循环的时候我都会让它休眠180秒,这样算下来,这个进程理论上可以保活5小时,嗯,够长了,手速再慢的人也能在5小时内用命令行终端登入开发板,然后输入命令查看当前活动的进程了。
现在我们要把这个写好的.c文件放到Android源码目录下,进入Android源码目录,找到vendor文件夹,然后新建一个自己的文件夹,我给它起名叫“while-process”,我们就把它放在这个里面。那么如何才能让它和源码一起编译呢,这时就涉及到了一个知识点——Android.mk。

如何编写自己的Android.mk

这里简单介绍下如何编译Android源码,等下还会详细介绍。编译Android源码简单来说,就是做一大堆准备工作,然后在命令行工具中使用make命令进行编译,make命令会在源码目录中遍历所有目录,找到里面的Android.mk文件,然后根据Android.mk文件的内容编译当前目录下的代码。所以说我们可以理解Android.mk配置文件是一个编译指南。关于Android.mk文件所能扩展出来的知识点也是非常的多,这里我也只介绍一些我们最需要和最基本的东西。首先你需要随便找一个Android.mk文件然后用记事本之类的软件打开它。
首先,这行代码必须有:“LOCAL_PATH:= (callmydir)Android.mk</span></div><div><spanstyle="fontsize:14px;">Android.mkinclude ( c a l l m y − d i r ) ” , 这 行 表 示 所 要 编 译 的 内 容 在 本 A n d r o i d . m k 文 件 所 在 的 目 录 下 。 < / s p a n >< / d i v >< d i v >< s p a n s t y l e =" f o n t − s i z e : 14 p x ; "> 然 后 , A n d r o i d . m k 文 件 中 可 以 指 定 多 个 编 译 模 块 , 每 个 编 译 模 块 都 是 以 i n c l u d e (CLEAR_VARS)开始,以include $(BUILD_XXX)结束。然后在这两代码中间我们找到LOCAL_SRC_FILES:= \xxx,后面的xxx就表示你要编译的源文件,比如我刚才写的程序叫loop,这里就写loop.c;我们还会看到LOCAL_MODULE:= xxx,这里表示编译的模块的名字,在这里我们把xxx换成loop。由于我们这个程序非常简单,所以更多的属性在这里就不介绍了,感兴趣的可以去下面这篇文章去查阅:
你如果不知道怎么创建Android.mk文件,最简单的方法就是随便找一个Android.mk文件拷贝过来,然后删掉些对你没用的语句,增加些对你有用的语句,然后改改某些语句的值或者变量名,就可以用了,如果编译失败,就按照提示进行修改,多试几次就可以了。

开始编译Android源码

由于讲解了Android.mk,所以我这里就先讲一下Android源码的编译,不过其实这是最后几步才要做的事情,但是先编译也是有好处的,因为第一次编译的时候时间会非常的长,你可以一边让它编译着,一边了解下面的知识点。当你后面的工作全部完成以后再次编译的时候,只要你不清空之前的编译结果,它就会进行增量编译,也就是只编译修改过的地方,这样只需要几分钟就能完成了。
Android源码怎么在ubuntu下编译,这个网上的文章有很多,我这里简要的说明一下。如果你使用的是开发板,请找到开发板对应的官方文档,然后严格按照文档中的教程一步一步来一般不会遇到什么问题。比如说我用的开发板的文档中给出的步骤大致如下:
1.搭建编译环境。这里需要一堆支持包,你可以先不用管这些支持包是什么,按照如下命令安装即可:
sudo apt-get install bison g++-multilib git gperf libxml2-utils make python-networkx zip
sudo apt-get install flex libncurses5-dev zlib1g-dev gawk minicom

2.使用命令行工具的cd命令进入Android源码目录,然后依次执行以下三个命令:
source build/envsetup.sh
lunch aosp_nanopi3-userdebug
make -j8
这三行命令第一行是设置编译环境,第二行是选择编译方案,也就是我刚才所说的选择厂商定制的方案,可以看到nanopi3是我的开发板的型号,所以就选择这个方案,第三行是开始编译,后面的-j8代表的是使用8线程同时编译,使用几个线程同时编译要看你的电脑配置,一般来说和你电脑的处理器有关。例如你的电脑装有四核处理器,每个核有两个线程,那你就可以使用j8,也就是4*2。选对同时编译的线程数量合适,编译的速度就能成倍增长。如果你是第一次编译源码,最慢的话时间可能长达一晚上之久,如果你之前成功编译过,那这次编译就是增量编译,系统只会编译你修改过的地方,很人性化,只需要几分钟。
编译过程中也许会遇到一些问题从而停止编译,比如你无权操作某些文件等等,这些都会在命令行工具中有英文提示,看着提示改就行了,这里的提示的英文也不复杂。建议在执行命令前先进入root用户,也就是先使用su命令。
编译成功以后,我们先进入Android源码目录,然后进入以下这个目录:out/target/product/nanopi3;还是那句话,根据你选择的编译方案,即你使用的开发板型号不同目录会有区别。进入这个目录下面以后我们能看到很多个img镜像文件。

3.现在我们把我们编译好的系统刷到我们的开发板上,在这之前先准备一张足够大的SD卡,然后用读卡器把SD卡连接到电脑上。
我使用的方式是使用刷机脚本来刷。首先执行以下两条命令:
意思就是时候用git下载这个刷机工具,然后进入这个刷机工具的目录。执行以下两条命令:
su
./fusing.sh /dev/sdx
这两条命令的意思是,首先进入root用户,如果你刚才已经进入root用户了,可以不用执行su这条命令,下面一行就是开始刷写,其中sdx是你的sd卡的设备名,请把sdx换成你的设备名,比如我的sd卡叫做sdb,就把sdx换成sdb。
刷写成功后,我们把卡插入开发板,然后用USB线把开发板和电脑连接,然后执行命令:
adb remount
adb shell
这时我们就已经使用命令行工具登入到开发板了,现在我们要找到我们写的loop程序。比如我把它放在了system/bin目录下,使用cd命令进入这个目录,然后使用ls命令就可以看到它存在,如果想要执行它,就使用./命令,loop这个程序就执行了,执行以后我们能看到每三分钟就可以在命令行终端中看到它输出一行语句,但是我们需要确保这个loop这个进程确实存在,那我们就需要用ps -Z命令查看它,但是当前这个命令行工具窗口正在运行程序,无法使用命令,所以我们就新建一个命令行终端窗口,像刚才一样使用adb shell命令登入开发板,然后使用命令ps -Z。就能看到它正在运行了。
编译Android源码这一块不是我们本讲的重点,这一块没有什么概念需要理解,就是一些操作步骤的流水账,我写的比较简单,中间难免有疏漏会造成一些小问题,如果读者出现了问题,可以根据具体问题去网上搜索或者给我留言,网上关于如何编译Android源码如何编译的文章是非常多的,不过我还是建议你参照你使用的开发板的官方文档,严格按照上面的步骤一步一步来,这样发生不必要的问题的概率会小一些。

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两个典型的定义:

  1. on early-init  
  2.     # Set init and its forked children’s oom_adj.  
  3.     write /proc/1/oom_score_adj -1000  
  4.   
  5.   
  6.     # Apply strict SELinux checking of PROT_EXEC on mmap/mprotect calls.  
  7.     write /sys/fs/selinux/checkreqprot 0  
  8.   
  9.   
  10.     # Set the security context for the init process.  
  11.     # This should occur before anything else (e.g. ueventd) is started.  
  12.     setcon u:r:init:s0  
  13.       
  14.     # Set the security context of /adb_keys if present.  
  15.     restorecon /adb_keys  
  16.   
  17.   
  18.     start ueventd  
  19.     # create mountpoints  
  20.     mkdir /mnt 0775 root system  
  21.   
  22.   
  23.     ……  
  24.     ……  
  25.     ……  
  26.   
  27.   
  28. service ueventd /sbin/ueventd  
  29.     class core  
  30.     critical  
  31.     seclabel u:r:ueventd:s0  
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

一上午已经过去了,不知不觉我已经写了这么多,但是重点才刚刚要开始。
我们先来理一理,我们现在通过上文的讲解已经得到了什么。我们已经编写了一个自己的C语言程序loop,并且把它放在了Android的源码中,Android的源码也编译好了,如何把编译好的Android系统刷到开发板上并启动我们也已经学会了。但是最重要的目的还没实现,那就是让loop这个可执行文件开机就可以被启动。而现在我们正要做的就是这件事。
首先打开Android源码目录,进入device/friendly-arm/nanopi3文件夹下,然后打开inti.rc文件,我们正式开始配置。
首先定义一个service,还记得service是怎么定义的吗,我这里定义的语句如下:service qya system/bin/loop。相信不难理解吧,我们定义了一个服务叫qya,它对应的程序是system/bin目录下的loop。这些上面都讲了。然后我在这个service下面增加几个配置属性:
  1. service qya /system/bin/loop  
  2.     class main  
  3.     console  
  4.     oneshot  
service qya /system/bin/loop 
class main
console
oneshot

其中console表示服务需要并运行在控制台,oneshot表示服务只运行一次,在退出时将服务设置为禁用。你可以根据你的需要来增加这些属性,我写的两个属性并不一定都是必须的。《构建嵌入式Android系统》这本书的第六章对这些属性参数介绍的非常详细,如果网上找不到相应的文章可以拿这本书看看。
也许有人会发现,class main这是个什么东西,好像没讲,然而我并不是忘了,我在这里再详细讲讲。我之前讲过start命令是在on中启动serice,但是通览整个init.rc文件,我们会发现,直接使用start命令启动service的情况非常少,我之前也用这种方式试过几次,但都未能成功。所以我在仔细阅读和查询资料后发现,大多数inti进程fork出来的开机启动进程都是用另外一种方式来启动。我们来看看下面一个on行为的定义:
  1. on property:vold.decrypt=trigger_restart_min_framework  
  2.     class_start main  
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?

看起来我们最后一步已经做了,把我们的loop程序成功添加到init.rc的文件中。你以为这样你就成功了吗?图样图森破!我们将会面临本文从开头到现在为止最大的挑战。我们的进程会被SEAndroid这个东西禁止掉,从而你无论怎么ps -Z你也不会看到它的存在。那么,SEAndroid到底是个什么东西?如果你想深入研究它,那可得好好花上一段时日了,我在这里给出两位大神的系列文章,专门分析什么是SEAndroid。这两位大神分别是罗升阳前辈和阿拉神农前辈,两人讲解SEAndroid的文章地址如下:
其中,罗升阳老师的文章比较长,分析的比较理论,有助于你全面而细致的了解SEAndroid。而阿拉神农的文章则更加实用,让你快速能看懂这个东西,但是理论上并没有罗老师的深,大家可以各取所需。
我在这里越俎代庖的稍微讲一下什么是SEAndroid。老习惯,一句话:Android的系统安全机制。它来源于Linux系统中的SELinux。关于它们的历史我这里也都不讲了,总之SEAndroid是这样管理权限的:凡是任何想要运行的进程,想要做任何事情,都必须在安全策略配置文件中赋予权限,如果没有声明某个权限,那它就没有这个权限。要理解其实很容易,做应用开发的时候,我们常常需要在AndroidManifest.xml文件中赋予应用权限。比如,如果你的应用想要读写磁盘数据,那你就要写permission语句,赋予它读写磁盘的语句,如果你的应用想要访问网络,那你就需要写一条关于网络的permission语句,以准许它访问网络。SEAndroid也是类似的东西,你的进程想要干什么,你就得给它写一个.te文件,然后在文件中使用allow语句赋予它权限,不同的是SEAndroid的安全策略文件.te比AndroidManifest.xml可是难写多了。
我这里对编写.te文件和上面Android.mk一样,不做深入介绍。想详细了解如何编写.te文件,就去参考阿拉神农前辈的文章。我这里只做一个简单的介绍。
我们随便来找一条.te文件中的allow语句:allow netd proc:file write。这条语句是阿拉神农的文章中给出的示例,他在文章详细分析了这条语句每个词是干什么的,但我在这里也不多说,还是老惯例,一句话:允许netd进程对proc type的file进行写(write)操作。
我们这里把这条语句换一下:写成:allow a b:c d。那意思就是允许a进程,对c这个b类型的objec class进行d操作。是不是一下子就理解了?不过这里的类型(type)是SEAndroid中的概念,很难三言两语说清楚,所以请看上面的文章,而file也不是我们通常理解的文件,而是一种object class,表示一种可以被操作的对象,比如,除了file以外还有Dir,Socket等等,由系统规定,这里也不展开了。
到这里我再废话两句,SEAndroid把操作系统中的东西分为两种,能发起动作的进程,以及只能被进程操作的文件,而allow语句则就是规定允许谁对谁做什么的。其实SEAndroid的知识远不止这些,还有例如MLS分级系统等等,不过这里就不讨论了,再提示一下,想深入了解的,看上面两篇文章吧!
好的,现在我们要开始真正动手了。首先进入Android源码目录,然后进入:external/sepolicy。还是和上面一样,我也不知道怎么创建一个.te文件,我们直接随便找个.te文件,不过说是随便,我们不能真的随便,我们也要想一想,怎么做成功率高,记得我说过我们自定义的service是属于main这个class的吧,那我们就找一个同样在main这个class下面的其他服务的.te文件来修改,因为在同一个class中的进程,所需的权限应该是相近的。
好,我们找到一个,然后把它的名字改成loop.te。注意啊,我们的service的名字叫qya,但是我们要执行的程序叫loop,SEAndroid是赋予进程权限而不是赋予service权限,所以我们的.te名字叫loop.te而不是qya.te。.te文件中除了allow语句以外还有一些其它语句,比如type等等,它们是什么意思,大家就自己去查一查吧,
我这里给出大家一个投机取巧的办法吗,如果你不想现在了解SEAndroid,那我就教你一个办法,比如我的.te文件是复制uncrypt的,那进到这个文件里面以后,就把文件中所有写uncrypt的地方全部改成loop,一般来说就可以了。如果编译的时候不通过,你就仔细看错误提示,我当时不通过的原因是有两行allow语句和domain.te文件中的neverallow语句相冲突,看字面意思也能明白,neverallow的意思是从不允许,如果这里允许了一件事情,那里又不允许这件事情,势必发生冲突,那我们只需要把我们的.te文件中的相关allow语句注释掉或者删掉就行了。

最后一步:重新编译刷机并运行

现在我们该修改的东西终于全部改完了,那我们要做的就是重新编译,怎么编译上面已经介绍过了,由于这次只是增量编译,所以只需几分钟就OK,我们把它刷到开发板上,然后给开发板插上USB线和电脑连接,然后在命令行工具中执行两条命令:
adb remount
adb shell
现在我们的命令行工具已经登入到开发板上了,直接使用命令ps -Z,我们就能在窗口中看到我们的进程正在后台运行了,我们没有用./命令去执行loop,所以它就是被init进程fork出来的。命令行窗口的截图如下所示:


我们可以看到最下面的我们最熟悉的进程zygote,它的上面就是我们自定义的loop进程,自此,自定义int.rc开机启动service成功,我们大功告成。

总结:

按照惯例,还是得总结一下。我们来理一理本文所涉及到的知识点,有:编译Android源码,Android.mk文件的编写,修改Android源码,init.rc介绍,SEAndroid。每个知识点都是为了我们能成功运行自定义service而讲解,所以讲解的深度其实都是不够的,我在文中不少地方都已经贴出了详细介绍每个知识点的博文的地址供大家后续学习。加上本文篇幅也算比较长的了,加上介绍的知识又多又杂,难免有疏漏之处,如果你按照本文的方法在配置过程中出现了失败的地方,可以根据具体问题上网搜索,只要坚持,一定可以解决。Android底层的知识的特点就是多而杂,所以做底层开发最需要的就是耐心,祝所有走在底层开发的小伙伴都能学习顺利!


<think>好的,我现在需要处理用户关于在Android 13的init.rc启动一个app服务的问题。首先,我需要回忆一下Android启动流程中init.rc的作用。init.rcAndroid初始化进程init配置文件,用于定义系统启动时的服务和执行的动作。用户可能想通过修改init.rc来在系统启动时自动启动自己的应用程序或服务。 接下来,我需要确定在Android 13中如何正确配置init.rc启动app服务。首先,应该考虑权限问题,因为普通应用程序可能没有系统级权限,无法直接由init进程启动。可能需要将应用作为系统应用安装,或者赋予适当的权限。 然后,需要了解init.rc的语法结构。服务定义通常包括服务名、可执行路径、用户、组以及其他参数。例如: service myservice /system/bin/myapp user system group system seclabel u:r:system_app:s0 oneshot 但这里用户可能想启动的是Android应用组件,如Activity或Service,而不是一个可执行文件。这时候可能需要通过am命令(Activity Manager)来启动应用。例如使用`am start`命令。 因此,在init.rc中可能需要添加一个oneshot服务,执行相应的am命令。例如: service start_myapp /system/bin/sh /path/to/script.sh user root group root oneshot 然后在script.sh中写入: #!/system/bin/sh am start -n com.example.myapp/.MainActivity 不过,这里有几个问题需要考虑。首先,am命令在init的上下文中是否可用,可能需要等待zygote和系统服务启动完成。因此,可能需要设置适当的触发条件,比如在`boot_complete`之后执行。 此外,权限方面,init.rc服务通常以root或系统用户运行,但使用am命令启动应用可能需要额外的权限,或者可能需要等待用户解锁。这可能导致在启动时无法正确启动应用,或者需要处理用户界面相关的限制。 另外,Android 13可能对init.rc的修改有更严格的限制,特别是考虑到项目Treble和系统分区的只读属性。用户可能需要通过系统镜像的构建流程,将自定义的init.rc修改打包到系统分区,或者使用Magisk等工具进行系统less的修改。 还需要考虑SELinux策略的问题。即使服务配置正确,SELinux可能会阻止执行am命令或访问相关资源。需要检查avc denials并添加相应的策略。 总结步骤可能包括: 1. 编写init.rc服务条目,设置正确的触发时机和执行命令。 2. 处理权限和SELinux策略。 3. 将修改集成到系统镜像或通过其他方式持久化。 4. 测试和调试,确保服务在启动时正确运行。 需要注意的是,直接修改系统init.rc可能不适用于非系统开发者,普通用户可能需要root权限或其他变通方法。此外,从Android版本升级的角度,每次系统更新可能会覆盖init.rc,导致修改丢失,因此需要考虑如何持久化这些更改。 可能还需要参考AOSP文档中关于init.rc的部分,以及Android系统服务的启动顺序,确保服务在适当的时机被触发。例如,使用`on property:sys.boot_completed=1`作为触发器,但init.rc中的服务可能需要结合trigger和command的配置。 最后,应该提醒用户谨慎修改系统文件,避免导致设备无法启动,并建议在具备恢复能力的情况下进行操作,例如通过自定义Recovery或具备备份机制。</think>在Android 13中通过`init.rc`启动应用服务需遵循系统级配置规则,以下是分步说明: --- ### **1. 理解限制与前提** - **系统级权限要求**:需将应用声明为系统应用(`/system/priv-app`或`/vendor`分区),否则普通应用无法通过`init`直接启动。 - **SELinux策略**:需配置SELinux规则,否则可能被拦截。 - **触发时机**:需等待关键服务(如`zygote`)就绪,通常在`boot_completed`阶段执行。 --- ### **2. 编写`init.rc`服务** 在设备对应的`init.rc`文件(如`/system/etc/init/`或`/vendor/etc/init/`)中添加服务: ```rc # 定义服务名称和启动命令 service my_app_service /system/bin/sh /system/bin/start_my_app.sh class main user root group root seclabel u:r:system_app:s0 # 根据SELinux上下文调整 oneshot # 只执行一次 disabled # 不随class自动启动 # 设置触发条件(在系统启动完成后执行) on property:sys.boot_completed=1 start my_app_service ``` --- ### **3. 创建启动脚本** 编写脚本`/system/bin/start_my_app.sh`,使用`am`命令启动应用组件: ```sh #!/system/bin/sh # 等待关键服务就绪(可选) sleep 5 # 启动Activity或Service am start -n com.example.myapp/.MainActivity # 或启动Service am startservice -n com.example.myapp/.MyService ``` 赋予脚本执行权限: ```sh chmod 755 /system/bin/start_my_app.sh ``` --- ### **4. 处理SELinux策略** - **步骤1**:触发测试,查看拒绝日志: ```sh adb shell dmesg | grep avc ``` - **步骤2**:在设备SELinux策略文件(如`my_app.te`)中添加规则: ```te # 允许init执行shell脚本 allow init shell_exec:file { execute }; # 允许脚本调用am命令 allow system_app activity_service:service_manager find; ``` --- ### **5. 集成到系统镜像** - **AOSP编译**:将`init.rc`修改和脚本放入设备`makefile`的`PRODUCT_COPY_FILES`中。 - **Magisk模块**:通过Magisk挂载自定义`init.rc`脚本(需处理合并冲突)。 --- ### **6. 测试与调试** - **查看日志**: ```sh adb logcat | grep "my_app_service" ``` - **验证服务状态**: ```sh adb shell getprop | grep boot_completed # 确认触发条件 adb shell ps | grep myapp # 检查进程是否存在 ``` --- ### **常见问题** 1. **应用未启动**:检查`am`命令路径、包名是否正确,或添加`sleep`等待依赖服务。 2. **SELinux拒绝**:根据`avc`日志补充策略规则。 3. **兼容性问题**:不同Android版本可能调整`init.rc`语法,需参考AOSP对应分支代码。 --- ### **总结** 通过`init.rc`启动应用服务需系统级适配,重点在于权限控制、触发时机和SELinux策略。非系统开发者建议通过`BroadcastReceiver`监听开机广播实现自启(需用户授权)。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值