By LiAnLab.org / 宋寶華
筆者決定,從今天開始,連載Android架構縱橫談系列。之所以叫縱橫談而不是叫別的題目,是因爲整個系列是橫着豎着亂彈琴,可以說是陰陽不分,黑白顛 倒,望湘園裏望湘園。我不談任何一個小的點,比如啓動過程、某個HAL移植、一個具體的native service或者Java service,我要談的是橫穿在其中的設計思想,因此,我談的任何一個方面,都有可能涉及到Android從內核到應用的所有層面。
歡迎廣大讀者通過本博客交流,也可透過我的新浪微薄“@宋寶華Barry”討論,親,記得給好評嘍!這個縱橫談既然決定亂彈,我們就不要那麼嚴肅了,人民 郵電出版社的老黃讓我在《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:
static void sigchld_handler(int s) { write(signal_fd, &s, 1); }
說到這裏,我們要特別點名批評一下信號啊。這位同學很不厚道啊,作爲一個異步闖入的事件,經常在別的同學“工程進行中”的時候跑進來,把人嚇成這樣了:
我在一些公司上Linux課的時候,很多人估計不記得學什麼了,都還給我了。但是不曉得還記住我的一句話不,“中斷是萬惡之源”,一是耗油耗電,二是非法 侵佔。你想啊,凡是異步的都是恐怖的,都是打斷正常業務邏輯的,你下X片,他給你罰款3000,這種屬於亂髮中斷搶錢吧?信號之於用戶空間多線程,正如中 斷之於內核,所以,你要特別注意線程與信號間的安全和死鎖啊。多線程編程裏面,上了信號就不是鬧着好玩的。一般有線程訪問臨界資源時屏蔽信號、信號處理線 程化等方式處理。特別要批評很多長着大腦從不想問題的同學啊,很多同學那就是一瞎寫啊,應該怎麼規劃線程,怎麼用信號那都是隨心所欲啊。你看人家 sigchld_handler(),幹完一票就跑,那就是正確的信號處理函數偉大的游擊戰術啊。你千萬不要在信號處理函數裏搞什麼會戰啊。
看看init進程如何處理收到的消息呢,源代碼情景分析這樣的文章最噁心,你看了五年跟沒有看一樣,基本上不知所云,所以我們不搞那一套。中國的 Linux開發者太痛苦,很多人把源代碼情景分析當小說在讀,一定從頭讀到尾,其實那個最多一新華字典,應該是參考書而非通讀讀物,讀完了,除了精神崩潰 以外,就是精神徹底崩潰,天天讓你背新華字典你還活地下去不?所以,你還是看我的《Linux設備驅動開發詳解》,看我的縱橫談比較實惠也比較符合人性。
init在這裏接收消息並進一步處理:
if (ufds[2].revents == POLLIN) { /* we got a SIGCHLD - reap and restart as needed */ read(signal_recv_fd, tmp, sizeof(tmp)); while (!wait_for_one_process(0)) ; continue; }
好吧,進去看看wait_for_one_process()吧,請注意我不是要搞情景分析,我只抓一點點代碼 出來。情景分析之類的恐怖片看多了,會影響三觀。各位同學在豎立正確三觀的前提下,請自行研讀android/system/core/init。所謂正 確的三觀,就是不是爲了讀代碼而讀代碼,而是爲了分析問題而找代碼啃。情景分析爲什麼三觀不正,是因爲完全不先提出問題,分析問題,最後研讀代碼,而是上 來一錘子拿代碼砸暈你先。絕望啊!
static int wait_for_one_process(int block) { … while ( (pid = waitpid(-1, &status, block ? 0 : WNOHANG)) == -1 && errno == EINTR ); // 幹掉殭屍 svc = service_find_by_pid(pid); if (!svc) { // 無主殭屍 ERROR("untracked pid %d exited\n", pid); return 0; } NOTICE("process '%s', pid %d exited\n", svc->name, pid); … /* oneshot processes go into the disabled state on exit */ if (svc->flags & SVC_ONESHOT) { //不投胎的殭屍 svc->flags |= SVC_DISABLED; } /* disabled processes do not get restarted automatically */ if (svc->flags & SVC_DISABLED) { notify_service_state(svc->name, "stopped"); return 0; } now = gettime(); if (svc->flags & SVC_CRITICAL) { //重要殭屍 //短時間內反覆掛,重啓 if (svc->time_crashed + CRITICAL_CRASH_WINDOW >= now) { if (++svc->nr_crashed > CRITICAL_CRASH_THRESHOLD) { … sync(); __reboot(LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2, LINUX_REBOOT_CMD_RESTART2, "recovery"); return 0; } } else { svc->time_crashed = now; svc->nr_crashed = 1; } } svc->flags |= SVC_RESTARTING; //標記重啓中 /* Execute all onrestart commands for this service. */ list_for_each(node, &svc->onrestart.commands) { //執行onrestart裏面的命令 cmd = node_to_item(node, struct command, clist); cmd->func(cmd->nargs, cmd->args); } notify_service_state(svc->name, "restarting"); return 0; }
特別留意加中文註釋的幾個地方:
1. 無主遊魂
掛的時候有“untracked pid %d exited”打印的進程屬於init的子進程,但是並沒有在 init.rc裏面註冊service。典型地,那些先父仙遊後被託孤給init的進程!實在是太悲慘了,這樣的進程,直到死都還沒戶口,實在是屌絲中的 蟬絲。看到這種進程,讀書人一聲長嘆啊!咱們滬上海漂估計就是這種了。誰叫你不是土生土長的上海人fork出來的呢?
土生土長的上海人,有戶口的service是這樣的:
service adbd /sbin/adbd user adb group adb
2. 拿一次性護照的ONESHOT service
untracked的進程像沒有戶口的上海工程師,ONESHOT的進程像拿single entry簽證赴美的人,搞一把沒第二次機會除非再籤。需要在service下面加上oneshot,這樣的service掛了就掛了,不會想着投胎,正如:
servicebootsound /system/bin/playmp3 user media group audio oneshot
此外,如果service被加了“disabled”標記,也不會自動啓動而需要顯示地啓動。
3. 可以投胎的女鬼
其他的進程就爽了,因爲被“svc->flags |= SVC_RESTARTING”蓋章了,死了會重新投胎, 下次init執行到restart_processes()的時候就可以重啓之,而且之前順帶還可以在投胎的時候執行點什麼,如想投胎個好人傢什麼、定點 空投到某人等,就像聊齋之小謝、聊齋之魯公女等。咱們就不要像蒲松齡同學那麼yy了,美麗女鬼投胎來報答我們的機率不高了,好像我們前世沒做過什麼好事? 不過我們一直在期待!
這個動作通過onrestart指定:
service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server socket zygote stream 666 onrestart write /sys/android_power/request_state wake onrestart write /sys/power/state on onrestart restart media onrestart restart netd
在這裏,我們要哀嘆一下, 蘭若寺真不是個好地方,Android就好多了,只要你有戶口,又不是ONESHOT,你就可以馬上重新投胎,聶小倩啊聶小倩,你真是死不逢地啊!於是你 造就了一段蕩氣迴腸的流傳千古的愛戀。張國榮《倩女幽魂》,我最愛的電影之一。
中國廣電總局真是個奇怪的地方,硬是把聶小倩從女鬼變成了狐妖,因爲他們說這個世界沒有鬼,難道這個世界就有妖嗎?我懂了,鬼是精神的,妖是物質的,俺們 那個是唯物論的。電視劇里人鬼戀的意境全無,本文忠實於蒲松齡同學原著,聶小倩爲女鬼,鑑定完畢。
4. 壓死Android的最後一根稻草
如果一個service是critical的,而它又在短時間內反覆掛,restart後又總是夭折,我們很可能不能再讓它這麼痛苦下去了,唯一的方法就是讓它解脫,於是整個系統重啓。這樣的service包括:
service ueventd /sbin/ueventd critical service servicemanager /system/bin/servicemanager user system critical onrestart restart zygote onrestart restart media
Android,第一隻狗,就是這樣負責一個掛掉的service的重啓的。下集我們講第二隻狗:生死與共的Zygote與system_server。
今天累了,欲知後事如何,請聽下回分解。我們趕着芒果臺新版《天涯明月刀》前一天開始播放Android架構縱橫談,也算是搶一點收視率。老版《天涯明月刀》,我的童年,那些逝去的年華啊!
謹以本回,獻給看《天涯明月刀》長大的一代人!如今我們已經而立或不惑,我們逝去的青春,就像一首歌。
古剎空潭猶自遠,長階醉臥知誰見。
天涯明月簫聲斷,刀映孤星,已是離人倦。
——宋寶華