本系列2012年的時候發表在我的blog上面,現搬到公眾號
筆者決定,從今天開始,連載Android架構縱橫談系列。之所以叫縱橫談而不是叫別的題目,是因為整個系列是橫著豎著亂彈琴,可以說是陰陽不分,黑白顛倒,望湘園裡望湘園。我不談任何一個小的點,比如啟動過程、某個HAL移植、一個具體的native service或者Java service,我要談的是橫穿在其中的設計思想,因此,我談的任何一個方面,都有可能涉及到Android從核心到應用的所有層面。
這個縱橫談既然決定亂彈,我們就不要那麼嚴肅了,人民郵電出版社的老黃讓我在《Linux裝置驅動開發詳解》裡一直嚴肅著,讓我裝深沉,我鬱悶啊,我獨立的人格不為人們所理解啊。這個系列沒人管,我們放開了整。《Linux裝置驅動開發詳解》曾經是年度10大暢銷經典,這個系列怎麼也是年度10大瞎扯吧?
今天我們要整的是Android軟體架構的超強自愈能力。自愈說白了,就是不小心被人k了,不進醫院,自己躺了幾天,並且輔助心靈阿q精神勝利療法, 就又活蹦亂跳了。作為一個屌絲,咱們也只能這樣了。咱們像小強一樣活著,不斷自愈。Android 估計也是個資深屌絲啊,它可以說處處都自愈 。你想啊,一個Android,啥都要整啊,裡面多少元件啊,Zygote啊,Dalvik啊,SystemServer啊,各種service和框架啊,你妹的,丈母孃要買房,老婆要買車,隨時要跪搓衣板,不具備自愈能力還能混嗎?
說到這裡,我們要稍微嚴肅點,這邊開始嚴肅認真地進行打劫活動。自愈能力,其實是電信、工控、航天等嵌入式系統的基本要求,Android 作為大型軟體,掛的時候我們往往看到一個桌面退隱江湖,新的桌面馬上起來,而不是直接宕機了。死了個東方不敗,又來個任我行啊,這就是Android的江湖規則。自愈極其重要,我們平時說的看門狗就是典型的自愈方式之一。看門狗乾什麼的,就是在那蹲點,情況不對比如動車要撞了,就開始“汪汪”了。沒有看門狗是不行的,如果神九的軟體跑飛了,不恢復回來,那我們的宇航員就變太空垃圾了。有人要問了,軟體不跑飛不就行了嗎?軟體不跑飛,我已經失業,哪裡還有機會來這裡縱橫亂彈琴呢?
Android裡面有幾個地方都有東西“蹲點”,雖然不是明確的看門狗。
第一隻狗:帶你去投胎
Android的第一隻狗在init行程裡面,init行程會透過捕獲SIGCHLD得知其子行程的死亡,並據情況決定是否重啟之。記住,在Linux的世界裡,在子行程死亡的時候,父行程會收到一個SIGCHLD訊號,而後父行程透過wait()系統呼叫,清理子行程僵屍。大家都知道,一個行程死亡的時候,如果它還有子行程,典型地,子行程會被託孤給init行程,這種情況非常普遍,所以任何一個Linux系統,它的init程式,少不了要做一件事情,就是反覆透過wait()清理僵屍,否則Linux系統就會屍橫遍野,整個一部生化危機啊有木有?Linux是個怎樣殘酷的世界啊,我艱於呼吸視聽啊,哪裡還能有什麼言語?永遠都是白髮人送黑髮人,父行程清理子行程。
Android的init行程為SIGCHLD系結了訊號處理函式sigchld_handler(),並建立了一個socket用於接收該函式中傳送的socket訊息。sigchld_handler()函式只是簡單的派送一個訊息到該socket:
說到這裡,我們要特別點名批評一下訊號啊。這位同學很不厚道啊,作為一個非同步闖入的事件,經常在別的同學“工程進行中”的時候跑進來,把人嚇成這樣了:
我在一些公司上Linux課的時候,很多人估計不記得學什麼了,都還給我了。但是不曉得還記住我的一句話不,“中斷是萬惡之源”,一是耗油耗電,二是非法侵佔。你想啊,凡是非同步的都是恐怖的,都是打斷正常業務邏輯的,你下X片,他給你罰款3000,這種屬於亂髮中斷搶錢吧?訊號之於使用者空間多執行緒,正如中斷之於核心,所以,你要特別註意執行緒與訊號間的安全和死鎖啊。多執行緒程式設計裡面,上了訊號就不是鬧著好玩的。一般有執行緒訪問臨界資源時遮蔽訊號、訊號處理執行緒化等方式處理。特別要批評很多長著大腦從不想問題的同學啊,很多同學那就是一瞎寫啊,應該怎麼規劃執行緒,怎麼用訊號那都是隨心所欲啊。你看人家sigchld_handler(),幹完一票就跑,那就是正確的訊號處理函式偉大的遊擊戰術啊。你千萬不要在訊號處理函式裡搞什麼會戰啊。
看看init行程如何處理收到的訊息呢,原始碼情景分析這樣的文章最噁心,你看了五年跟沒有看一樣,基本上不知所云,所以我們不搞那一套。中國的Linux開發者太痛苦,很多人把原始碼情景分析當小說在讀,一定從頭讀到尾,其實那個最多一新華字典,應該是參考書而非通讀讀物,讀完了,除了精神崩潰以外,就是精神徹底崩潰,天天讓你背新華字典你還活地下去不?
init在這裡接收訊息併進一步處理:
好吧,進去看看wait_for_one_process()吧,請註意我不是要搞情景分析,我只抓一點點程式碼出來。情景分析之類的恐怖片看多了,會影響三觀。各位同學在豎立正確三觀的前提下,請自行研讀android/system/core/init。所謂正確的三觀,就是不是為了讀程式碼而讀程式碼,而是為了分析問題而找程式碼啃。情景分析為什麼三觀不正,是因為完全不先提出問題,分析問題,最後研讀程式碼,而是上來一鎚子拿程式碼砸暈你先。絕望啊!
特別留意加中文註釋的幾個地方:
1. 無主遊魂
掛的時候有“untracked pid %d exited”列印的行程屬於init的子行程,但是並沒有在 init.rc裡面註冊service。典型地,那些先父仙遊後被託孤給init的行程!實在是太悲慘了,這樣的行程,直到死都還沒戶口,實在是屌絲中的蠶絲。看到這種行程,讀書人一聲長嘆啊!咱們滬上海漂估計就是這種了。誰叫你不是土生土長的上海人fork出來的呢?
土生土長的上海人,有戶口的service是這樣的:
2. 拿一次性簽證的ONESHOT service
untracked的行程像沒有戶口的上海工程師,ONESHOT的行程像拿single entry簽證赴美的人,搞一把沒第二次機會除非再簽。需要在service下麵加上oneshot,這樣的service掛了就掛了,不會想著投胎,正如:
此外,如果service被加了“disabled”標記,也不會自動啟動而需要顯示地啟動。
3. 可以投胎的女鬼
其他的行程就爽了,因為被“svc->flags |= SVC_RESTARTING”蓋章了,死了會重新投胎, 下次init執行到restart_processes()的時候就可以重啟之,而且之前順帶還可以在投胎的時候執行點什麼,如想投胎個好人傢什麼、定點空投到某人等,就像聊齋之小謝、聊齋之魯公女等。咱們就不要像蒲松齡同學那麼yy了,美麗女鬼投胎來報答我們的機率不高了,好像我們前世沒做過什麼好事?不過我們一直在期待!
這個動作透過onrestart指定:
在這裡,我們要哀嘆一下, 蘭若寺真不是個好地方,Android就好多了,只要你有戶口,又不是ONESHOT,你就可以馬上重新投胎,聶小倩啊聶小倩,你真是死不逢地啊!於是你造就了一段盪氣迴腸的流傳千古的愛戀。張國榮《倩女幽魂》,我最愛的電影之一。
4. 壓死Android的最後一根稻草
如果一個service是critical的,而它又在短時間內反覆掛,restart後又總是夭折,我們很可能不能再讓它這麼痛苦下去了,唯一的方法就是讓它解脫,於是整個系統重啟。這樣的service包括:
Android,第一隻狗,就是這樣負責一個掛掉的service的重啟的。下集我們講第二隻狗:生死與共的Zygote與SystemServer。
今天累了,欲知後事如何,請聽下回分解。我們趕著芒果臺新版《天涯明月刀》前一天開始播放Android架構縱橫談,也算是搶一點收視率。老版《天涯明月刀》,我的童年,那些逝去的年華啊!
謹以本回,獻給看《天涯明月刀》長大的一代人!如今我們已經而立或不惑,我們逝去的青春,就像一首歌。
古剎空潭猶自遠,長階醉臥知誰見。
天涯明月簫聲斷,刀映孤星,已是離人倦。