Android架构纵横谈之——软件自愈能力 (1)

标签: androidservicelinuxsocketsignal多线程
17294人阅读 评论(18) 收藏 举报
分类:

特别声明:本系列文章LiAnLab.org著作权所有,转载请注明出处。by  @宋宝华Barry

           笔者决定,从今天开始,连载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与SystemServer。

           今天累了,欲知后事如何,请听下回分解。我们赶着芒果台新版《天涯明月刀》前一天开始播放Android架构纵横谈,也算是抢一点收视率。老版《天涯明月刀》,我的童年,那些逝去的年华啊!



谨以本回,献给看《天涯明月刀》长大的一代人!如今我们已经而立或不惑,我们逝去的青春,就像一首歌。

古刹空潭犹自远,长阶醉卧知谁见。
天涯明月箫声断,刀映孤星,已是离人倦。

——宋宝华


22
2

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:1010631次
    • 积分:8568
    • 等级:
    • 排名:第2337名
    • 原创:63篇
    • 转载:4篇
    • 译文:3篇
    • 评论:608条
    新浪微博