Android架構縱橫談之——軟件自愈能力 (1)

                                                                                      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架構縱橫談,也算是搶一點收視率。老版《天涯明月刀》,我的童年,那些逝去的年華啊!


謹以本回,獻給看《天涯明月刀》長大的一代人!如今我們已經而立或不惑,我們逝去的青春,就像一首歌。

古剎空潭猶自遠,長階醉臥知誰見。
天涯明月簫聲斷,刀映孤星,已是離人倦。

——宋寶華


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章