安卓之启动

xiaoxiao2021-02-27  492

本源码介绍是基于安卓4.2.2_r1和安卓安卓4.1的,这里我交叉讲解是因为我的安卓4.2.2_r1代码下载的不全,所有交叉引用讲解。

本文结构约定:

         1大标题用标题1格式,次表次按标题2,标题3以此类推。

         2每章节开篇会有简单的关于该章的介绍,每章后面会对该章的讲解进行一个总结。

         3在开篇介绍后面会列出,该章涉及到的重要的源文件。

         4在讲解代码的时候,因为代码会有嵌套关系,代码层次会根据颜色进行标示,例如

         第一层

                   第二层

                            第三层

                                     第四层

                                               第五层现在之定义到五层,第一层浅灰、浅黄、浅蓝

    5含有代码嵌套的会用一整行六号n(代表嵌套的层,n=1,2,3,4,5)#分割以示这是代码嵌套,我不会但是我尽量对代码进行排版。

    6代码注释的解释用暗红,对于疑点用绿色。对于重要的话语用深蓝。代码用小五号字

对于引用的用橙色,更重要语句或者解析或者代码用红色。重要的话会用粗体加粗。甚至有些用下划线或者斜体表示。某些代码中如果有重要的语句可能会用比当前大一号的字显示,还可能加粗显示。对于特定宏才执行的代码背景用浅绿标示,这段代码不是一定会执行。

在分析代码的时候Java、c、c++代码穿插,完全看引用关系,代码的重要注释我会从源文件一起粘贴过来,我会对注释给一个我理解的翻译,当然仁者见仁。还有我在分析代码的时候并不是解释代码,我只是对某些代码加以注释,还有我认为代码从来不出错,所以我会屏蔽掉一些异常处理,而且我不对代码做{}处理,也就是说你拿到这些代码放到编译器上也不一定能运行。想看真正的文件要去源代码中查找,我这里会列出一大部分代码。

    7有些时候为了便于查看代码的方法,我会用编译器的截图来展示文件中包含的方法。

    9我会尽量对我写的文档进行排版,但不保证它真的看起来很舒服,因为排版是一件很难的事情,而且我又不擅长排版。

   10 写这个算一个学习过程也算是一个交流过程,我尽量写一些对我们有意义有用的东西,我不会像其他大师一样扯一些高深的云里雾里的东西,尊重开源尊重共享。也希望看过我写过的学习历程的人,在我的代码和解说中找到错误,我们一起提高。

序-安卓系统介绍

    我想了很久要不要再这一章节里添加一副安卓架构的图片,但是我最终还是决定不用图片去诱惑我自己和大家(加了图你们就不认真读我写的东西了,很辛苦的)。就让我们脑袋一片空白的学习比较好。因为有些时候似乎我们什么都明白都懂了,但是真的让我们去描述去做的时候又感觉到脑袋一片空白。

    题外话就扯到这里,现在我开始按照我自己现在的水平来说一下安卓系统(当然随着我的积累增强,我或许事后会对该章进行修改,见谅,为了不受到干扰,大家可以越过这章)。

    我个人按照自己的倾向喜欢把安卓分为三层,并且按照我自己的喜欢把这三层称为:

    1 Linux内核层

    2 framework下层,linux上层,的中间层。

    3 framework

    我为什么要这么分层而且这么称谓是有原因的,我是想让大家对一些东西分清界限,有些东西本身属于linux的语义,没必要非的和安卓扯在一起,例如基于linux的驱动模块,基于linux的内存管理,线程调度等。这样分清界限后,你就知道哪些东西可以在分析的时候从安卓这个吓人的世界里走出来,而且你还能知道你应该去哪个世界了解那些让你迷茫但并不属于或者说和安卓系统本身无关不随安卓存在而早就存在的东西。

 linux内核层包含了一个操作系统应该具有的线程调度、内存管理、驱动程序等。了解过IPC进程间通信的人都知道,进程间通讯有例如无名管道、有名管道、消息队列、共享数据、信号、Unix TCP/IP 套接字等方式,但是忽然有一天某一个人(当然不是我)写了一个驱动程序叫做OpenBinder,它是基于类似RPC远程调用的驱动程序,并且可以用于进程间通讯,如果google(暂时这么称谓因为发现它的应该是早期开发安卓的人)没有发现OpenBinder的话它也行还安静的在liunx世界里默默的休眠,不过它很幸运被google发现变成了今天的Binder,并且用于“安卓系统”大部分进程间通讯的机制。但我还是要重复的强调一次,驱动的开发是属于操作系统或者说是嵌入式等的范畴的,它和安卓扯不上任何关系,它的出现是随着操作系统或者不知道什么玩意一起产生的,早于安卓太多年了。曾经看有些书上说安卓系统把底层日志也做成了驱动的方式,至于是不是真的,应该不会是假的,不过我到今天还没有机会看这些东西,暂时留给以后吧。google还做了很多他们还对内核做了很多定制和阉割处理(有c、c++、还有一些脚本,但是都是linux老套路)。

    夹在framework和linux内核层中间的层,分层的目的就是让职能更加明确。那么这层主要干什么呢,因为它在linux层之上,所以在这一层上按理来说我们可以做像在linux系统上一样的事情。例如基于标准的libc、libstdc++编写一个c、c++程序,而且我们还可以装一个java虚拟机编写一个java程序,并且我还可以把这个java程序做的很炫,例如写一个eclipse。

而且还可以写一个基于JNI的java程序,让我的java程序即能跑java还能跑和linux系统相关的c或者c++程序。擦,结果当我还没有学好c、c++的时候这种想法又被google收购的一个公司发现了。可恶的他们真的这么干了,他们做的比我想象的要狠很多。他们让这个linux只能跑一个程序,他们把它叫安卓,他们让这个安卓有了至高无上的权利。并且他们同样装了一个虚拟机只不过这个虚拟机不是sun的而是apache的它叫Dalvik,并且它的执行效率更高,当然我对此表示怀疑。接着他们又编写了一些c、c++程序作为JNI,在后面肆无忌惮的跑(安卓放话了给我跑)。并且似乎还扮演一些中介的角色。但是谷歌发现不能让开发者肆无忌惮用标准库编写程序,so给你们NDK,你们开发者想编写c、c++要乖乖在我们的掌握之中。

         framework层。大家光有地基那不叫房子,只有把房子在地基上建起来,房子才叫房子,才有了房子的功能。那么房子应该存在门,窗子,墙,地板,天花板,卫生间,厨房等。

这些是构成房子的基本元素。对于安卓系统,地基就好比底层内核层,和夹在中间的中间层。构成房子的元素就好比,安卓系统环境构成安卓应用运行在framework层上的依赖的实现,例如ActivitiesServicesBroadcastreceiversContent providers、文件管理器、任务管理器、安装服务的底层支持和实现,各种随init一起启动的作为安卓应用运行的依赖的服务等。各种上层应用运行的依赖。

         为了让房子更完美,我们给房子铺了木地板,按照了美丽的灯,安装了暖器,空调,冰箱等,不过这些并不是房子的必须品,而这些东西就好比一个安卓系统的一些预置应用。例如地图,例如电话本,拨打电话,发送短信(你会问为什么它算预置应用,答案是有些pad并不具备打电话的功能,所以它算应用不算服务)等。

         这就是一个安卓系统,我这样讲你能明白了吗。记住安卓可是在linux上唯舞独尊的程序哦,挡我者死,挡我我就不干活。

安卓系统启动流程

安卓系统是怎么启动的呢,就现在我的水平来说我也描述不清楚,以后我会和改序一同改写这里。不过这次我先从网上摘了一段。

启动流程

1.    系统引导Bootloader

 1)        源码:bootable/bootloader/* 

2)        说明:加电后,

CPU将先执行bootloader程序,此处有三种选择

a)        开机按

Camera+Power启动到fastboot,即命令或SD卡烧写模式,不加载内核及文件系统,此处可以进行工厂模式的烧写

b)        开机按Home+Power启动到recovery模式,加载recovery.img,recovery.img包含内核,基本的文件系统,用于工程模式的烧写

c)        开机按Power,正常启动系统,加载boot.img,boot.img包含内核,基本文件系统,用于正常启动手机(以下只分析正常启动的情况)

2.    内核kernel 

1)        源码:kernel/* 

2)        说明:kernel由bootloader加载

3.    文件系统及应用init 

1)        源码:system/core/init/* 

2)        配置文件:

system/rootdir/init.rc,摘几行我关注的

……

service servicemanager/system/bin/servicemanager

启动servicemanmager

……

service zygote /system/bin/app_process-Xzygote /system/bin --zygote --start-system-server

启动zygote—即app_process

3)        说明:

init是一个由内核启动的用户级进程,它按照init.rc中的设置执行:启动服务(这里的服务指Linux底层服务,如adbd提供adb支持,vold提供SD卡挂载等),执行命令和按其中的配置语句执行相应功能(init过程)

4.    重要的后台程序

zygote 

1)        源码:frameworks/base/cmds/app_main.cpp

2)        说明:zygote是一个在init.rc中被指定启动的服务,该服务对应的命令是/system/bin/app_process 

a)        建立Java Runtime,建立虚拟机

b)        建立Socket接收ActivityManangerService的请求,用于Fork应用程序

c)        启动System Server 

5.    系统服务

system server 

1)        源码:frameworks/base/services/java/com/Android/server/SystemServer.java 

2)        说明:被zygote启动,通过System Manager管理android的服务(这里的服务指frameworks/base/services下的服务,如卫星定位服务,剪切板服务等)

6.    桌面launcher 

1)        源码:ActivityManagerService.java为入口,packages/apps/launcher*实现

2)        说明:系统启动成功后SystemServer使用xxx.systemReady()通知各个服务,系统已经就绪,桌面程序Home就是在ActivityManagerService.systemReady()通知的过程中建立的,最终调用launcher()启动

7.    解锁

1)        源码:frameworks/policies/base/phone/com/android/internal/policy/impl/*lock* 

2)        说明:系统启动成功后SystemServer调用wm.systemReady()通知WindowManagerService,进而调用PhoneWindowManager,最终通过LockPatternKeyguardView显示解锁界面,跟踪代码可以看到解锁界面并不是一个Activity,这是只是向特定层上绘图,其代码了存放在特殊的位置

8.    开机自启动的第三方应用程序

1)        源码frameworks/base/services/java/com/android/server/am/ActivityManagerService.java

2)        说明:系统启动成功后SystemServerActivityManagerNative.getDefault().systemReady()通知ActivityManager启动成功,ActivityManager会通过置变量mBooting,通知它的另一线程,该线程会发送广播android.intent.action.BOOT_COMPLETED以告知已注册的第三方程序在开机时自动启动。

9.    总结

综上所述,

系统层次关于启动最核心的部分是

zygote(即app_process)和system server,zygote它负责最基本的虚拟机的建立,以支持各个应用程序的启动,而system server用于管理android后台服务,启动步骤及顺序。

接下来后面将尽量详细描述以上流程,主要讲解frameworks层。

接下来我就要先讲init过程了,涉及到的文件(以后代码讲解主要集中在framework层)安卓源码最外层目录结构如图:

涉及init过程的重要文件

         可执行文件位于/sbin/init

system/core/rootdir/init.rc         init启动的配置文件

system/core/rootdir/ueventd.rc    

system/core/init/init.h

system/core/init/init.c             init中最重要的文件init的入口

system/core/init/init_parser.h

system/core/init/init_parser.c

以及其他关联的在system/core/init/*下的源文件,这里不一一列举。

了解linux系统启动过程的人都知道,linux系统首先进行自举内核(该过程包含加载底层设备驱动程序),当内核自举成功后,内核调用/etc/initinit进程id1,是用户进程但是以超级管理员权限运行,它会启动一个linux操作系统(该过程可以加载一些用户自定义驱动程序建立设备映射文件等,但以root权限加载)。

安卓的init进程做了以下事情以下四点都是我在网上搜索到的,就我现在水平还不能讲解清楚这些,后面我分析的代码会有帮助,但是我们必须知道init进程做了些什么工作):

一、处理子进程终止

二、生成设备驱动节点

三、提供属性服务

四、保存系统所需的环境变量

其实大家都知道安卓只是一个运行在linux环境上的程序,只不过这个程序很复杂,而且这个程序几乎完全占据了linux的运行环境,唯我独尊。由此可知底层linux系统的启动一定在安卓系统启动之前。

我更喜欢说安卓是linux上的一个process。

代码解析

首先引入依赖文件init.rc,这个是init执行依赖的文件。还有uevetd.rc,其他rc文件我就不在这里列举了。关于以rc为后缀的文件的说明请看,它里面介绍了The Android Init Language consists of four broad classes ofstatements,

which are Actions, Commands, Services, and Optionsinit.h头文件也有定义结构。

然后看一个重要的文件Makefile--Android.mk。我摘几行重要的代码:

要进行编译的源文件

LOCAL_SRC_FILES:= \

         builtins.c \

         init.c \

         devices.c \

         property_service.c \

         util.c \

         parser.c \

         logo.c \

         keychords.c \

         signal_handler.c \

         init_parser.c \

         ueventd.c \

         ueventd_parser.c \

         watchdogd.c

ifeq ($(strip$(INIT_BOOTCHART)),true)  可能会编译的文件条件是ifeq成立

LOCAL_SRC_FILES +=bootchart.c

endif

LOCAL_MODULE:= init 编译出来的模块,看见了嘛,这个就是那个可执行的init

# Make a symlink from /sbin/ueventdand /sbin/watchdogd to /init 创建一个符号链接从/sbin/ueventd /sbin/watchdogd  /init

SYMLINKS :=\

    $(TARGET_ROOT_OUT)/sbin/ueventd\

$(TARGET_ROOT_OUT)/sbin/watchdogd

然后让我们看一下init.c和其他文件

init.c 三个并列程序流程 uevent、watchdog、init

int main(intargc, char**argv)

{//init的入口,一切从这里开始

   …………

   if(!strcmp(basename(argv[0]),"ueventd"))basename函数作用是去掉输入string中的/。如果传入参数去掉/不等于ueventd则执行ueventd_main

        return 1ueventd_main(argc,argv);

网上说这实际是init启动的自己的进程,它的主要作用根据uevent是创建或删除/dev/xxx(xxx设备名),我们知道在Linux下面创建设备节点的接口mknod       

ueventd_main这个函数就是init启动的自己的进程的入口。这个函数比较复杂,并且代码涉及大部分底层API,我个人看起来还是比较吃力的,不过我尽量去给源码加上自己合理的注释,并且粘贴一些我在网上搜索出来的认为比较好的说明。

1###################################################################################################

int ueventd_main(int argc, char **argv)

{//ueventd入口,在ueventd.c

 …………

   import_kernel_cmdline(0, import_kernel_nv);//从设备文件/dev/cmdline(内核读取命令行参数方式)读取hardware硬件名称

   get_hardware_name(hardware, &revision);//变成软件可识别硬件名字,因为需要带有设备号

   ueventd_parse_config_file("/ueventd.rc");//解析配置文件uevevtd.rc

   snprintf(tmp,sizeof(tmp),"/ueventd.%s.rc",hardware);   ueventd_parse_config_file(tmp); //解析ueventd.xxx.rc,xxx是刚刚获取的hardware

device_init();//这个函数比较重要这里做一个流程描述,由于代码过多我就不粘贴出来了。

这里是方法跳转的顺序流,且不是在同一个文件中,大家可以去查看相关源码/system/core/init/*

devicd_init流程(|表示并列、||表示一种流程结束、|表示有先后关系,粗体字表示重要的方法)__start uevent_open_socket(注册socketHandle,为和内核打交道)(system/core/libcutils/uevetd.c)------->

   | coldboot("/sys/class");       |

    | coldboot("/sys/block");       |

    | coldboot("/sys/devices");     |--------> coldboot ----------->do_coldboot--->

    | handle_device_fd(重要的设备处理入口)|---> parse_event -------->

    |handle_firmware_event  |-------->process_firmware_event------>

    | open底层设备驱动open,打开驱动。firmware流程结束||

    |handle_device_event    |--------->

    | fixup_sys_perms   uevent->action={add,change} 安装系统权限/sys下的------------>

    | handle_block_device_event  uevent->subsystem={block} 处理/dev/block下驱------>

    | parse_device_name------->parse_platform_block_device-------->handle_device--->

    | make_device ----->get_device_perm(设置设备权限)----->makedev(等同于linux创建设备                      MKDEV,创建设备dev_tdev)----->setegid(gid)(设置有效组id)------>mknod(path, mode, dev)(真正创建特殊设备文件)------>chown(path, uid, -1)(修改属组)--->setegid(AID_ROOT)|

remove_link删除文件(此方法在make_device后执行)||

    | handle_platform_device_event   uevent->subsystem={platform},处理/devices/platform-------> add_platform_device(添加/devices/platform下设备)------>remove_platform_device(移除相应设备)||

    | handle_generic_device_eventuevent->subsystem ={!platform,!block},处理常规的驱动和相应的驱动映射文件/dev,其中包含usbmnt,相机,input,图形等不一一列举,详细看源代码。--->

handle_device_fd(同上)||

回顾

至此这个device_init就讲完了,流程比较复杂,现在我们在稍微回顾一下:

    首先注册了一个套接字socket和内核互发消息(这个我没太自信看,不是非常确认),之后触发/sys/class,/sys/block,/sys/devices这三个目录及其子目录下的uevent,然后接受并创建设备节点,至此设备节点才算创建。最终handle_device_fd这才是真正处理驱动的入口的开始,会最终调用mknod创建设备节点或者open打开真正的驱动(firmware),流程如下:

handle_device_fd->handle_device_event-> make_device-> mknodfirmware不同,见上面)。

 

继续下面代码:

   ufd.events= POLLIN;

   ufd.fd =get_device_fd();

   while(1){

        ufd.revents = 0;

        nr = poll(&ufd, 1, -1);//Unix 经典poll模式和select模式相似

        if (nr <= 0)

            continue;

        if (ufd.revents == POLLIN)

               handle_device_fd();//接受kernel传过来的uevent,动态创建或删除节点,同上

   }

}

1###################################################################################################

   if(!strcmp(basename(argv[0]),"watchdogd"))同上

        return 2watchdogd_main(argc,argv);启动watchdog,相关看门狗介绍可以google

   umask(0);//把文件掩码设置为空

   //以下是生成安卓系统中的一些基本的系统目录并挂载对应的文件系统3它完成工作和ueventd相似,但是包含一些系统服务进程的启动。

   mkdir("/dev",0755);

   mkdir("/proc",0755);

   mkdir("/sys",0755);

   mount("tmpfs""/dev""tmpfs", MS_NOSUID, "mode=0755");

   mkdir("/dev/pts", 0755);

   mkdir("/dev/socket",0755);

   mount("devpts""/dev/pts""devpts", 0, NULL);

   mount("proc""/proc""proc", 0, NULL);

   mount("sysfs""/sys""sysfs", 0, NULL);

   close(open("/dev/.booting",O_WRONLY | O_CREAT, 0000));

   open_devnull_stdio();//生成类似linux/dev/null的设备

   klog_init();//日志系统

   property_init();//打开/dev/__properties__,使之成为共享区域,用来保存一些属性

   get_hardware_name(hardware, &revision);//同上面讲到的

   process_kernel_cmdline();//同上

#ifdef HAVE_SELINUX //是否启用了安全linux,如果启动了设置一些SELinux处理器

    union selinux_callback cb;

    cb.func_log = klog_write;

    selinux_set_callback(SELINUX_CB_LOG, cb);

    cb.func_audit = audit_callback;

    selinux_set_callback(SELINUX_CB_AUDIT, cb);

    if (selinux_android_load_policy() <0) {

            selinux_enabled = 0;        } else {

            selinux_init_all_handles();

    }

    restorecon("/dev");

    restorecon("/dev/socket");

#endif

   is_charger = !strcmp(bootmode, "charger");

   property_load_boot_defaults();//设置加载boot默认properties,放在共享区域

    // 解析 /init.rc 和 /init.$hardware.rc 脚本,其中 $hardware 参数从 /proc/cpuinfo 中读取

   init_parse_config_file("/init.rc");//我们终于看到这个了init.rc

   //寻找 early-init 触发器,加到 action_queue

   action_for_each_trigger("early-init", action_add_queue_tail);

  //等待完成coldboot动作同上ueventd

   queue_builtin_action(wait_for_coldboot_done_action, "wait_for_coldboot_done");

   queue_builtin_action(keychord_init_action, "keychord_init");//keychord初始化

   queue_builtin_action(console_init_action, "console_init");//控制台初始化

    //为开始执行所有引导的actions,执行action(不管is_charger是什么模式)initproperty_service_initsignal_initcheck_startupchargerqueue_property_triggers,非is_charger模式下下执行early-fsfspost-fspost-fs-dataearly-bootboot

   action_for_each_trigger("init", action_add_queue_tail);

    if(!is_charger) {

      action_for_each_trigger("early-fs",action_add_queue_tail);

      action_for_each_trigger("fs",action_add_queue_tail);

      action_for_each_trigger("post-fs",action_add_queue_tail);

      action_for_each_trigger("post-fs-data",action_add_queue_tail);

   }

   queue_builtin_action(property_service_init_action, "property_service_init");

   queue_builtin_action(signal_init_action, "signal_init");

   queue_builtin_action(check_startup_action, "check_startup");

   if(is_charger) {

        action_for_each_trigger("charger",action_add_queue_tail);

   } else{

        action_for_each_trigger("early-boot",action_add_queue_tail);

        action_for_each_trigger("boot", action_add_queue_tail);

   }

   queue_builtin_action(queue_property_triggers_action, "queue_property_triggers");

#if BOOTCHART //是否启动bootchart_init,这里就不介绍了

    queue_builtin_action(bootchart_init_action,"bootchart_init");

#endif

//init进程开始执行逻辑

   for(;;){

        int nr, i, timeout = -1;

        execute_one_command();//command是存放在队列中的,具体actioncommandservice等信息定义在init.h头中,我不一一详细介绍了,可以查看相关文档和代码。但是这一个函数就是执行一条command,这个时候就启动了如下的服务,摘自init.rc

service adbd /sbin/adbd  //adb

service servicemanager/system/bin/servicemanager //servicemanager

service surfaceflinger/system/bin/surfaceflinger//触摸

service zygote/system/bin/app_process -Xzygote /system/bin --zygote --start-system-server  //zygote就在这个时候启动的                          

        restart_processes();//重启进程

        if (!property_set_fd_init &&get_property_set_fd() > 0) {//启动相关计数

            ufds[fd_count].fd =get_property_set_fd();

            ufds[fd_count].events = POLLIN;

            ufds[fd_count].revents = 0;

            fd_count++;

            property_set_fd_init = 1;

        }

        if (!signal_fd_init && get_signal_fd()> 0) {//信号处理相关计数

            ufds[fd_count].fd = get_signal_fd();

            ufds[fd_count].events = POLLIN;

            ufds[fd_count].revents = 0;

            fd_count++;

            signal_fd_init = 1;

        }

        if (!keychord_fd_init &&get_keychord_fd() > 0) {//keychord相关计数

            ufds[fd_count].fd = get_keychord_fd();

            ufds[fd_count].events = POLLIN;

            ufds[fd_count].revents = 0;

            fd_count++;

            keychord_fd_init = 1;

        }

        …………

       nr = poll(ufds, fd_count, timeout);//开始进入poll模式,注册各类handle,都很复杂

        if (nr <= 0)

            continue;

        for (i = 0; i < fd_count; i++) {

            if (ufds[i].revents == POLLIN) {

                if (ufds[i].fd == get_property_set_fd())

                    handle_property_set_fd();

                else if (ufds[i].fd ==get_keychord_fd())

                    handle_keychord();

                else if (ufds[i].fd ==get_signal_fd())

                    handle_signal();

            }

        }

   }

   return0;

}

 

回顾

         至此init相关的代码我们就差不多讲完了,大家都看了init在安卓启动过程中都做了些什么,而且都是怎么做的。

         我稍作说明:

         引自ueventd.rc

    /dev/binder               0666   root      root

    看到binder驱动映射的时候了吧,呵呵。

         引自init.rc

    serviceadbd /sbin/adbd 

         service servicemanager/system/bin/servicemanager

         service zygote /system/bin/app_process-Xzygote /system/bin --zygote --start-system-server

看到adb启动了吧,看到servicemanager启动了吧,看到zygote启动了吧。所以我们的疑惑是不是打开了呢,我们不管底层linux做了什么有一些什么,但是我们经常会听到别人提前什么binder驱动binder通讯,什么zygoteservicemanager等,今天我们终于知道它是在init的时候启动的了。并且按照我上面代码流程,你会在源文件中看见详细的启动过程。

补充总结

         有源代码的好处就是让我们能够通过读取源代码而了解一个可执行的最小单元的逻辑流程,但是大家都会经常犯一个错误,当然也包括我自己,那就是一旦我们自己拿起了代码,就深陷进了代码跳来跳去的泥潭了。一旦陷入了这个深渊,即使我们熟读代码,也很难和真正的“事实”(流程本身,逻辑本身等)联系起来。所以建议大家应该先对“事实”有个了解在去读代码,可是你要问我了,要是知道了这个“事实”还需要读代码嘛,恩,确实不需要,所以这个“事实”或许你知道,或许你需要去按照实际情况去猜

         举个例子吧:

    当我兴冲冲的读完了init程序相关的代码时,我看到了执行命令的方式都是通过调用main函数,输入相关参数执行的。但是我却找不到它们之间的关联,main啥时候被调用,于是我在代码中找呀找找要找,还是找不到。例如从init中我看到了zygote的启动,于是我去看zytoge的代码,不过我还看不到关联。我陷入了迷茫。

         想必大家都遇到过这种小错误,那问题出在哪里呢,就是出自你在看代码的时候忘记了“事实”。让事实和真实断开了。

         在阐述这个事实之前我先像大家在介绍一下Linux相关的东西:

         守护进程,它在系统自举时启动,仅在系统关闭时停止,它们不会控制终端,之后再后台运行,负责处理日常进程。init的一个功能就是开启这些守护进程。

         linux shell,一个可以用来通过传递命令和参数与内核打交道的工具,通过这个shell你可以执行自己的程序,执行一些你想执行的程序,甚至可以通过向这个shell发送数据来执行程序。

         init程序在系统上实际上一定是一个编译好的可执行文件(excuteable file),并且其他大部分需要启动的服务都是编译好的可执行文件。被放到了相关的目录下。

         ok知道这些就够了,其实大家都知道,但是可能从来都没有和真实联系在一起。

    好那现在看一下“事实”:

    有一个程序它包含 A B C D E 个执行体(都是可执行文件),执行流程是:

    A ----|-----B  |____D------E

            |-----C  |

     程序先执行A,之后B、C几乎同时执行,B、C执行完毕后执行D,D执行完毕后执行E,流程结束。

我们假设A就是Linux Kernel,B是进程ID为0的调度进程叫做swaper,C为进程ID为1的进程叫做init,我们叫D为servicemanager,E为特定的Servcie。

和“事实”的关系:

A--对应于linus他们发布的linux某个版本的源码编译后的内核文件。

B--对应于内核之中的一个可执行程序,调度进程。

C--init对应于/sbin/init,是由/system/core/init/下文件编译成的可执行文件。

D--servicemanager对应于/system/bin/servicemanager,是由framework/base/servicemanager下相关文件编译的可执行程序。

E--对应于framework各种service编译后可执行文件。

上面已经讲到了linux的守护进程,和基于shell和内核通讯的机制,并且知道了init会初始化这些守护进程。

ok,现在让我们来阐述一下“事实”,但是阐述的时候要抛离开代码:

好,A(内核加载)启动了,紧接着A启动完毕,A要求B(swaper)和C(init)马上启动干活,C知道它启动后要负责D(servicemanager)启动,而且D的启动会引起E(各种真正service)的相关启动。

事实既然如此,执行设置好约定,什么时候调用什么可执行文件就可以了,纯净的系统怎么会有源代码呢对吧(可能有但无关紧要)。你让我关闭热水器,我关闭就是了,它就在那里,我干嘛需弄清楚它是怎么做的,我又不是生产热水器的厂商。所以你别拉我进入那个泥潭。

好,寻找“事实”:

某一天热水器(犹如init,app_process这样的可执行程序)坏了(或者我想对我的热水器进行我自己的人工升级),而且过了保修期,巧合我得到了一份关于热水器详细生产和使用说明书(犹如原代码)。我自信的研读这些,终于知道了它的原理(init可执行程序的流程,通过研读相关源代码),修好了它。

好转了一圈,弄清楚了吗,在思考问题的时候要先去想一下“事实”,我们通过已经了解的知识有这个能力了解了事实,在去读真实的代码,就不会在乱套了。

你问我zygote怎么运行的,我以事实回答你的问题。首先init启动,启动过程它要依赖init.rc文件,它找到了service zygote/system/bin/app_process -Xzygote /system/bin --zygote --start-system-server 这句,然后它通过解析发现它是readme.txt提到的service 后面是service名称zygote,它是位于/system/bin/的可执行文件app_process 并且带有一些参数,并且它同时用/system/bin 通过zygote启动了一个system-server。那这一行只是一个文本,它可不会执行哦,对的,现在需要将它通过shell或者其他通讯方式传给后端或者内核执行了。好,手工。

你明白这个事实了嘛,ok找源代码也不在话下了吧,好,我们要通过这种方式去读代码,要不很容易陷入代码的深渊里,而忘了读属于谁的代码,执行什么逻辑。

init流程是以上事实的集合,什么启动后台守护进程了,建立相应文件系统,建立驱动文件映射,打开某些驱动,设置相应权限,设置一些共享变量,启动用户级进程,设置网络,还真多呢……

还有一点需要注意的地方,就是init在执行的过程中驱动相关呀、adb相关、servicemanagerservice相关、zygote相关、system application 相关都已经伴随着init一起启动了,可想而知它们的和构成了init的流程,而不是init完成之后,zygote等才启动。

 unix 一切皆文件  ,这个文件系统和 VFS 文件系统稍稍不同 ,但是有些需要vfs提供支持, 所以驱动程序呀、硬件呀、硬件信息呀、都需要以文件方式挂载 并挂载在VFS的文件系统上 ,有些文件例如/proc下的一般都在内存中 ,那么我们在用户空间  就可以访问这些文件来达到访问硬件和驱动的目的了。 另外shell 提供了我们与内核交互的功能,所以 init 做了这些工作,为我们事后访问内核与内核交互提供了保障,至于非内核空间的东西  我们以普通方式访问 就是了。  这是我讲init 没有提到的 。记住挂载文件系统 和建立驱动设备 硬件等文件映射 是一定要做的 ,我们看到的linux 是启动后的 文件系统已经mount  ,而且每次启动都要去执行这些操作 每次都要去mount ,而且 向那些设备驱动文件 本身是不存在的  关机之后 就消失了  ,切记。每次都mount ,所以init.rc里有那么多的mount

此是初版,后期会进行更正。请斧正。

转自:http://blog.csdn.net/ylcangel/article/details/18144769

转载请注明原文地址: https://www.6miu.com/read-1994.html

最新回复(0)