Android如何配置init.rc中的開機啟動程序(service)
轉載。 http://blog.csdn.net/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。大體上有哪些知識點
大體上有哪些知識點,我畫了以下一張圖來描述:這麼一看,一目瞭然,我們需要將我們自己寫的程式(示例中我用的是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,也就是迴圈的意思,程式碼如下: [cpp] view plain copy print ?- #include<stdio.h>
- int main(){
- int i=0;
- for(i;i<100;i++)
- {
- sleep(180);
- printf(”I am a process\n”);
- }
- return 0;
- }
#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小時內用命令列終端登入開發板,然後輸入命令檢視當前活動的程序了。 現在我們要把這個寫好的.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:= (CLEAR_VARS)開始,以include $(BUILD_XXX)結束。然後在這兩程式碼中間我們找到LOCAL_SRC_FILES:= \xxx,後面的xxx就表示你要編譯的原始檔,比如我剛才寫的程式叫loop,這裡就寫loop.c;我們還會看到LOCAL_MODULE:= xxx,這裡表示編譯的模組的名字,在這裡我們把xxx換成loop。由於我們這個程式非常簡單,所以更多的屬性在這裡就不介紹了,感興趣的可以去下面這篇文章去查閱: http://blog.chinaunix.net/uid-25838286-id-3204120.html你如果不知道怎麼建立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 zipsudo 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 clone https://github.com/friendlyarm/sd-fuse_s5p6818.git
cd sd-fuse_s5p6818
意思就是時候用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的詳細介紹,大家可以參考這篇文章:
我們這裡只對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兩個典型的定義:
[plain] view plain copy print ?- 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 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下面增加幾個配置屬性: [plain] view plain copy print ?- service qya /system/bin/loop
- class main
- console
- 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行為的定義: [plain] view plain copy print ?
- on property:vold.decrypt=trigger_restart_min_framework
- 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的文章地址如下: http://blog.csdn.net/luoshengyang/article/details/35392905http://blog.csdn.net/innost/article/details/19299937/
其中,羅昇陽老師的文章比較長,分析的比較理論,有助於你全面而細緻的瞭解SEAndroid。而阿拉神農的文章則更加實用,讓你快速能看懂這個東西,但是理論上並沒有羅老師的深,大家可以各取所需。 我在這裡越俎代庖的稍微講一下什麼是SEAndroid。老習慣,一句話:Android的系統安全機制。它來源於Linux系統中的SELinux。關於它們的歷史我這裡也都不講了,總之SEAndroid是這樣管理許可權的:凡是任何想要執行的程序,想要做任何事情,都必須在安全策略配置檔案中賦予許可權,如果沒有宣告某個許可權,那它就沒有這個許可權。要理解其實很容易,做應用開發的時候,我們常常需要在AndroidManifest.xml檔案中賦予應用許可權。比如,如果你的應用想要讀寫磁碟資料,那你就要寫permission語句,賦予它讀寫磁碟的語句,如果你的應用想要訪問網路,那你就需要寫一條關於網路的permission語句,以准許它訪