Coder Social home page Coder Social logo

foxleezh / aosp Goto Github PK

View Code? Open in Web Editor NEW
1.0K 61.0 165.0 1.04 MB

这是一个连载的博文系列,我将持续为大家提供尽可能透彻的Android源码分析

C++ 63.07% Assembly 0.80% C 29.53% CMake 0.14% Java 6.38% Makefile 0.08%
aosp android source-code analysis

aosp's Introduction

Android 源码分析

关于我

前言

AOSP的源码是非常庞大的,里面的语言主要有C/C++,Java, 汇编,为了让大家能有更好的阅读体验,我专门写了篇文章作为导读

如何阅读Android源码

系统结构

Android系统结构很复杂,可能大家平常都是应用层开发比较多,我们开发的app属于最上层

应用层下面有个应用框架层(framework),也就是我们经常在Android Studio中可以直接点击看到那些源码,应用层和应用框架层都是用Java写的

在应用框架层下面是Native层,也就是我们调用的native方法的实现层,这一层主要是用C/C++写的

在Native层再往下就是Linux内核层,这一层主要是用C和汇编来写的

在Native层和Linux内核层之间还有个硬件抽象层,准确讲它应该属于Linux内核层,因为都是些驱动相关的 但是因为Linux是开源的,如果放在Linux内核层就必须开源代码,为了保护厂商的驱动源码,所以将这些代码专门提出来,放到了一个硬件抽象层

可以看到,Android系统用了Java,C/C++,汇编,这些不同的语言和层级是如何打通的呢?这里主要涉及到JNI和Syscall, 我们知道Java是运行在虚拟机中的,在Native层就有一个专门的虚拟机,dex代码和so动态链接库都会加载到这个虚拟机中, 使用同一个进程空间,dex代码和so动态链接库之间定义的相同的接口,就像我们平时写服务器和客户端用相同的接口字段一样, 这样Java和C/C++之间就可以相互通信了,而这套机制就叫JNI(Java Native Interface)

Native层是运行在用户空间的,Linux内核层是运行在内核空间,一般情况下,用户进程是不能访问内核的, 它既不能访问内核所在的内存空间,也不能调用内核中的函数. 而Syscall就是专门用来让Native访问Linux内核的, 在/platform/bionic/libc/kernel/uapi/asm-generic/unistd.h中,在这个文件中为每一个系统调用规定了唯一的编号,叫做系统调用号

#define __NR_epoll_create1 20
#define __NR_epoll_ctl 21
#define __NR_epoll_pwait 22
#define __NR_dup 23

这里面每一个宏就是一个系统调用号,每一个调用号都会对应Linux内核的一个操作. Syscall是单向的,只能是Native调用Linux内核, JNI却是双向的,Java可以调用C++,C++也可以调用Java.那么Linux内核如何调用Native呢,其实也很简单, 内核直接运行一个可执行程序就可以了,比如native中的init进程就是这样调用的

通信方式

Android系统中有许多通信方式,最常见就是Binder和Handler,这是我们平常开发中用到的,另外底层常见的还有Socket, pipe,signal 除了Handler只能用于线程间通信外,其他都可以进行进程间通信,当然你也可以用来做线程间通信,只是有点杀鸡用牛刀的感觉.

Binder作为Android系统提供的一种IPC机制,无论从系统开发还是应用开发,都是Android系统中最重要的组成. Binder通信采用c/s架构,从组件视角来说,包含Client、Server、ServiceManager以及binder驱动, 其核心实现原理是用系统调用ioctl在内核空间进行进程间通信,它还做了许多良好的封装,比如如果发现调用者和接收者是同一进程,就不会去走系统调用,而是直接调用

Handler消息机制我们比较熟悉,Handler消息机制是由一组MessageQueue、Message、Looper、Handler共同组成的,为了方便且称之为Handler消息机制. 其核心实现原理是共享内存,由于工作线程与主线程共享地址空间,即Handler实例对象mHandler位于线程间共享的内存堆上,工作线程与主线程都能直接使用该对象. Handler最经典的地方就是消息队列,MessageQueue存放要处理的消息Message,Looper无限循环从MessageQueue中取Message发到对应线程处理

内容分类

本项目以android-8.0.0_r17和kernel/msm(高通内核android-8.0.0_r0.16)为基础,重点分析跟应用程序相关的源码,主要内容如下:

  • Android系统启动流程,应用启动流程,四大组件启动流程,这将列入系统启动篇
  • 系统常用服务ActivityManagerService,WindowManagerService等,这将列入系统服务篇
  • 通信机制,主要是Binder和Handler,这将列入通信篇
  • 进程和线程的创建,运行,销毁,这将列入进程篇
  • View的绘制和显示流程,事件分发机制,这将列入图形绘制篇
  • Android虚拟机ART运行机制,类加载机制,Java注解,Java反射,这将列入虚拟机篇
  • Android对于Java集合的优化算法,这将列入Java基础篇

我将持续更新本项目,尽可能地为大家提供透彻的Android源码分析,每篇文章我会挂在issue上,方便大家探讨并提出问题

工具篇

系统启动篇

aosp's People

Contributors

foxleezh avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

aosp's Issues

C++语言知识整理

为了方便大家理解C/C++的语法,我将源码中涉及到的一些小知识整理一下,以源码分析的顺序列出,我会在知识点下列出出现的地方,大家也可以对照着看。

1.String相关函数

1.1 strcmp

比较两个字符串,设这两个字符串为str1,str2

若str1==str2,则返回零

若str1 < str2,则返回负数

若str1 > str2,则返回正数

1.2 clear

清空字符串

1.3 reserve

函数reserve()将字符串的容量设置为至少size. 如果size指定的数值要小于当前字符串中的字符数, 容量将被设置为可以恰好容纳字符的数值.

1.4 strcspn

strcspn用于返回字符所在下标,相当于String的indexof

size_t entry_key_len = strcspn(ENV[n], "=");

2.文件读写

2.1 access

判断文件是否存在,并判断文件读写权限
第一个参数是文件路径,第二个是读写权限,如果返回-1表示出错

2.2 open(const char* pathname, int flags, ...)

打开文件,第一个参数是文件路径,第二个是模式,常见的有

参数 含义
O_RDONLY 只读模式
O_WRONLY 只写模式
O_CREAT 如果文件不存在就创建一个
O_CLOEXEC 即当调用exec()函数成功后,文件描述符会自动关闭,且为原子操作。
O_BINARY 以二进制方式打开

Android系统启动流程之init进程(一)

前言

上一篇中讲到,Linux系统执行完初始化操作最后会执行根目录下的init文件,init是一个可执行程序,
它的源码在platform/system/core/init/init.cpp。
之前我们讲过init进程是用户空间的第一个进程,我们熟悉的app应用程序都是以它为父进程的,
init进程入口函数是main函数,这个函数做的事情还是比较多的,主要分为三个部分

  • init进程第一阶段
  • init进程第二阶段
  • init.rc文件解析

由于内容比较多,所以对于init的讲解,我分为三个章节来讲,本文只讲解第一阶段,第一阶段主要有以下内容

  • ueventd/watchdogd跳转及环境变量设置
  • 挂载文件系统并创建目录
  • 初始化日志输出、挂载分区设备
  • 启用SELinux安全策略
  • 开始第二阶段前的准备

本文涉及到的文件

platform/system/core/init/init.cpp
platform/system/core/init/ueventd.cpp
platform/system/core/init/watchdogd.cpp
platform/system/core/init/log.cpp
platform/system/core/base/logging.cpp
platform/system/core/init/init_first_stage.cpp
platform/external/selinux/libselinux/src/callbacks.c
platform/external/selinux/libselinux/src/load_policy.c
platform/external/selinux/libselinux/src/getenforce.c
platform/external/selinux/libselinux/src/setenforce.c
platform/external/selinux/libselinux/src/android/android.c

一、ueventd/watchdogd跳转及环境变量设置

/*
 * 1.C++中主函数有两个参数,第一个参数argc表示参数个数,第二个参数是参数列表,也就是具体的参数
 * 2.init的main函数有两个其它入口,一是参数中有ueventd,进入ueventd_main,二是参数中有watchdogd,进入watchdogd_main
 */
int main(int argc, char** argv) {

    /*
     * 1.strcmp是String的一个函数,比较字符串,相等返回0
     * 2.C++中0也可以表示false
     * 3.basename是C库中的一个函数,得到特定的路径中的最后一个'/'后面的内容,
     * 比如/sdcard/miui_recovery/backup,得到的结果是backup
     */
    if (!strcmp(basename(argv[0]), "ueventd")) { //当argv[0]的内容为ueventd时,strcmp的值为0,!strcmp为1
    //1表示true,也就执行ueventd_main,ueventd主要是负责设备节点的创建、权限设定等一些列工作
        return ueventd_main(argc, argv);
    }

    if (!strcmp(basename(argv[0]), "watchdogd")) {//watchdogd俗称看门狗,用于系统出问题时重启系统
        return watchdogd_main(argc, argv);
    }

    if (REBOOT_BOOTLOADER_ON_PANIC) {
        install_reboot_signal_handlers(); //初始化重启系统的处理信号,内部通过sigaction 注册信号,当监听到该信号时重启系统
    }

    add_environment("PATH", _PATH_DEFPATH);//注册环境变量PATH
    //#define	_PATH_DEFPATH	"/sbin:/system/sbin:/system/bin:/system/xbin:/odm/bin:/vendor/bin:/vendor/xbin"

1.1 ueventd_main

定义在platform/system/core/init/ueventd.cpp

Android根文件系统的映像中不存在“/dev”目录,该目录是init进程启动后动态创建的。

因此,建立Android中设备节点文件的重任,也落在了init进程身上。为此,init进程创建子进程ueventd,并将创建设备节点文件的工作托付给ueventd。
ueventd通过两种方式创建设备节点文件。

第一种方式对应“冷插拔”(Cold Plug),即以预先定义的设备信息为基础,当ueventd启动后,统一创建设备节点文件。这一类设备节点文件也被称为静态节点文件。

第二种方式对应“热插拔”(Hot Plug),即在系统运行中,当有设备插入USB端口时,ueventd就会接收到这一事件,为插入的设备动态创建设备节点文件。这一类设备节点文件也被称为动态节点文件。

int ueventd_main(int argc, char **argv)
{
    /*
     * init sets the umask to 077 for forked processes. We need to
     * create files with exact permissions, without modification by
     * the umask.
     */
    umask(000); //设置新建文件的默认值,这个与chmod相反,这里相当于新建文件后的权限为666

    /* Prevent fire-and-forget children from becoming zombies.
     * If we should need to wait() for some children in the future
     * (as opposed to none right now), double-forking here instead
     * of ignoring SIGCHLD may be the better solution.
     */
    signal(SIGCHLD, SIG_IGN);//忽略子进程终止信号

    InitKernelLogging(argv); //初始化日志输出

    LOG(INFO) << "ueventd started!";

    selinux_callback cb;
    cb.func_log = selinux_klog_callback;
    selinux_set_callback(SELINUX_CB_LOG, cb);//注册selinux相关的用于打印log的回调函数

    ueventd_parse_config_file("/ueventd.rc"); //解析.rc文件,这个后续再讲
    ueventd_parse_config_file("/vendor/ueventd.rc");
    ueventd_parse_config_file("/odm/ueventd.rc");

    /*
     * keep the current product name base configuration so
     * we remain backwards compatible and allow it to override
     * everything
     * TODO: cleanup platform ueventd.rc to remove vendor specific
     * device node entries (b/34968103)
     */
    std::string hardware = android::base::GetProperty("ro.hardware", "");
    ueventd_parse_config_file(android::base::StringPrintf("/ueventd.%s.rc", hardware.c_str()).c_str());

    device_init();//创建一个socket来接收uevent,再对内核启动时注册到/sys/下的驱动程序进行“冷插拔”处理,以创建对应的节点文件。

    pollfd ufd;
    ufd.events = POLLIN;
    ufd.fd = get_device_fd();//获取device_init中创建出的socket

    while (true) {//开户无限循环,随时监听驱动
        ufd.revents = 0;
        int nr = poll(&ufd, 1, -1);//监听来自驱动的uevent
        if (nr <= 0) {
            continue;
        }
        if (ufd.revents & POLLIN) {
            handle_device_fd();//驱动程序进行“热插拔”处理,以创建对应的节点文件。
        }
    }

    return 0;
}

1.2 watchdogd_main

定义在platform/system/core/init/watchdogd.cpp

"看门狗"本身是一个定时器电路,内部会不断的进行计时(或计数)操作,计算机系统和"看门狗"有两个引脚相连接,
正常运行时每隔一段时间就会通过其中一个引脚向"看门狗"发送信号,"看门狗"接收到信号后会将计时器清零并重新开始计时,
而一旦系统出现问题,进入死循环或任何阻塞状态,不能及时发送信号让"看门狗"的计时器清零,当计时结束时,
"看门狗"就会通过另一个引脚向系统发送“复位信号”,让系统重启

watchdogd_main主要是定时器作用,而DEV_NAME就是那个引脚

int watchdogd_main(int argc, char **argv) {
    InitKernelLogging(argv);

    int interval = 10;
    /*
     * C++中atoi作用是将字符串转变为数值
     */
    if (argc >= 2) interval = atoi(argv[1]);

    int margin = 10;
    if (argc >= 3) margin = atoi(argv[2]);

    LOG(INFO) << "watchdogd started (interval " << interval << ", margin " << margin << ")!";

    int fd = open(DEV_NAME, O_RDWR|O_CLOEXEC); //打开文件 /dev/watchdog
    if (fd == -1) {
        PLOG(ERROR) << "Failed to open " << DEV_NAME;
        return 1;
    }

    int timeout = interval + margin;
    /*
     * ioctl是设备驱动程序中对设备的I/O通道进行管理的函数,WDIOC_SETTIMEOUT是设置超时时间
     */
    int ret = ioctl(fd, WDIOC_SETTIMEOUT, &timeout);
    if (ret) {
        PLOG(ERROR) << "Failed to set timeout to " << timeout;
        ret = ioctl(fd, WDIOC_GETTIMEOUT, &timeout);
        if (ret) {
            PLOG(ERROR) << "Failed to get timeout";
        } else {
            if (timeout > margin) {
                interval = timeout - margin;
            } else {
                interval = 1;
            }
            LOG(WARNING) << "Adjusted interval to timeout returned by driver: "
                         << "timeout " << timeout
                         << ", interval " << interval
                         << ", margin " << margin;
        }
    }

    while (true) {//每间隔一定时间往文件中写入一个空字符,这就是看门狗的关键了
        write(fd, "", 1);
        sleep(interval);
    }
}

1.3 install_reboot_signal_handlers

定义在platform/system/core/init/init.cpp

这个函数主要作用将各种信号量,如SIGABRT,SIGBUS等的行为设置为SA_RESTART,一旦监听到这些信号即执行重启系统

static void install_reboot_signal_handlers() {
    // Instead of panic'ing the kernel as is the default behavior when init crashes,
    // we prefer to reboot to bootloader on development builds, as this will prevent
    // boot looping bad configurations and allow both developers and test farms to easily
    // recover.
    struct sigaction action;
    memset(&action, 0, sizeof(action));
    sigfillset(&action.sa_mask);//将所有信号加入至信号集
    action.sa_handler = [](int) {
        // panic() reboots to bootloader
        panic(); //重启系统
    };
    action.sa_flags = SA_RESTART;
    sigaction(SIGABRT, &action, nullptr);
    sigaction(SIGBUS, &action, nullptr);
    sigaction(SIGFPE, &action, nullptr);
    sigaction(SIGILL, &action, nullptr);
    sigaction(SIGSEGV, &action, nullptr);
#if defined(SIGSTKFLT)
    sigaction(SIGSTKFLT, &action, nullptr);
#endif
    sigaction(SIGSYS, &action, nullptr);
    sigaction(SIGTRAP, &action, nullptr);
}

1.4 add_environment

定义在platform/system/core/init/init.cpp

这个函数主要作用是将一个键值对放到一个Char数组中,如果数组中有key就替换,没有就插入,跟Java中的Map差不多

/* add_environment - add "key=value" to the current environment */
int add_environment(const char *key, const char *val)
{
    size_t n;
    size_t key_len = strlen(key);

    /* The last environment entry is reserved to terminate the list */
    for (n = 0; n < (arraysize(ENV) - 1); n++) {

        /* Delete any existing entry for this key */
        if (ENV[n] != NULL) {
        /*
         * C++中strcspn用于返回字符所在下标,相当于String的indexof
         */
            size_t entry_key_len = strcspn(ENV[n], "=");
            if ((entry_key_len == key_len) && (strncmp(ENV[n], key, entry_key_len) == 0)) { //如果key相同,删除对应数据
                free((char*)ENV[n]);
                ENV[n] = NULL;
            }
        }

        /* Add entry if a free slot is available */
        if (ENV[n] == NULL) { //如果没有对应key,则插入数据
            char* entry;
            asprintf(&entry, "%s=%s", key, val);
            ENV[n] = entry;
            return 0;
        }
    }

    LOG(ERROR) << "No env. room to store: '" << key << "':'" << val << "'";

    return -1;
}

二、 挂载文件系统并创建目录

    bool is_first_stage = (getenv("INIT_SECOND_STAGE") == nullptr);//查看是否有环境变量INIT_SECOND_STAGE

    /*
     * 1.init的main方法会执行两次,由is_first_stage控制,first_stage就是第一阶段要做的事
     */
    if (is_first_stage) {//只执行一次,因为在方法体中有设置INIT_SECOND_STAGE
        boot_clock::time_point start_time = boot_clock::now();

        // Clear the umask.
        umask(0); //清空文件权限

        // Get the basic filesystem setup we need put together in the initramdisk
        // on / and then we'll let the rc file figure out the rest.
        mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755");
        mkdir("/dev/pts", 0755);
        mkdir("/dev/socket", 0755);
        mount("devpts", "/dev/pts", "devpts", 0, NULL);
        #define MAKE_STR(x) __STRING(x)
        mount("proc", "/proc", "proc", 0, "hidepid=2,gid=" MAKE_STR(AID_READPROC));
        // Don't expose the raw commandline to unprivileged processes.
        chmod("/proc/cmdline", 0440);
        gid_t groups[] = { AID_READPROC };
        setgroups(arraysize(groups), groups);
        mount("sysfs", "/sys", "sysfs", 0, NULL);
        mount("selinuxfs", "/sys/fs/selinux", "selinuxfs", 0, NULL);
        mknod("/dev/kmsg", S_IFCHR | 0600, makedev(1, 11));
        mknod("/dev/random", S_IFCHR | 0666, makedev(1, 8));
        mknod("/dev/urandom", S_IFCHR | 0666, makedev(1, 9));

        ...

    }

   ...


}

2.1 mount

mount是用来挂载文件系统的,mount属于Linux系统调用

int mount(const char *source, const char *target, const char *filesystemtype,
unsigned long mountflags, const void *data);

参数:

source:将要挂上的文件系统,通常是一个设备名。

target:文件系统所要挂载的目标目录。

filesystemtype:文件系统的类型,可以是"ext2","msdos","proc","ntfs","iso9660"。。。

mountflags:指定文件系统的读写访问标志,可能值有以下

参数 含义
MS_BIND 执行bind挂载,使文件或者子目录树在文件系统内的另一个点上可视。
MS_DIRSYNC 同步目录的更新。
MS_MANDLOCK 允许在文件上执行强制锁。
MS_MOVE 移动子目录树。
MS_NOATIME 不要更新文件上的访问时间。
MS_NODEV 不允许访问设备文件。
MS_NODIRATIME 不允许更新目录上的访问时间。
MS_NOEXEC 不允许在挂上的文件系统上执行程序。
MS_NOSUID 执行程序时,不遵照set-user-ID和set-group-ID位。
MS_RDONLY 指定文件系统为只读。
MS_REMOUNT 重新加载文件系统。这允许你改变现存文件系统的mountflag和数据,而无需使用先卸载,再挂上文件系统的方式。
MS_SYNCHRONOUS 同步文件的更新。
MNT_FORCE 强制卸载,即使文件系统处于忙状态。
MNT_EXPIRE 将挂载点标记为过时。

data:文件系统特有的参数

在init初始化过程中,Android分别挂载了tmpfs,devpts,proc,sysfs,selinuxfs这5类文件系统。

tmpfs是一种虚拟内存文件系统,它会将所有的文件存储在虚拟内存中,
如果你将tmpfs文件系统卸载后,那么其下的所有的内容将不复存在。
tmpfs既可以使用RAM,也可以使用交换分区,会根据你的实际需要而改变大小。
tmpfs的速度非常惊人,毕竟它是驻留在RAM中的,即使用了交换分区,性能仍然非常卓越。
由于tmpfs是驻留在RAM的,因此它的内容是不持久的。
断电后,tmpfs的内容就消失了,这也是被称作tmpfs的根本原因。

devpts文件系统为伪终端提供了一个标准接口,它的标准挂接点是/dev/ pts。
只要pty的主复合设备/dev/ptmx被打开,就会在/dev/pts下动态的创建一个新的pty设备文件。

proc文件系统是一个非常重要的虚拟文件系统,它可以看作是内核内部数据结构的接口,
通过它我们可以获得系统的信息,同时也能够在运行时修改特定的内核参数。

与proc文件系统类似,sysfs文件系统也是一个不占有任何磁盘空间的虚拟文件系统。
它通常被挂接在/sys目录下。sysfs文件系统是Linux2.6内核引入的,
它把连接在系统上的设备和总线组织成为一个分级的文件,使得它们可以在用户空间存取

selinuxfs也是虚拟文件系统,通常挂载在/sys/fs/selinux目录下,用来存放SELinux安全策略文件

2.2 mknod

mknod用于创建Linux中的设备文件

int mknod(const char* path, mode_t mode, dev_t dev) {

}

参数:
path:设备所在目录
mode:指定设备的类型和读写访问标志
可能的类型

参数 含义
S_IFMT type of file ,文件类型掩码
S_IFREG regular 普通文件
S_IFBLK block special 块设备文件
S_IFDIR directory 目录文件
S_IFCHR character special 字符设备文件
S_IFIFO fifo 管道文件
S_IFNAM special named file 特殊文件
S_IFLNK symbolic link 链接文件

dev 表示设备,由makedev(1, 9) 函数创建,9为主设备号、1为次设备号

2.3 其他命令

mkdir也是Linux系统调用,作用是创建目录,第一个参数是目录路径,第二个是读写权限

chmod用于修改文件/目录的读写权限

setgroups 用来将list 数组中所标明的组加入到目前进程的组设置中

这里我解释下文件的权限,也就是类似0755这种,要理解权限首先要明白「用户和组」的概念

Linux系统可以有多个用户,多个用户可以属于同一个组,用户和组的概念就像我们人和家庭一样,人属于家庭的一分子,用户属于一个组,我们一般在Linux终端输入ls -al之后会有如下结果

drwxr-xr-x  7 foxleezh foxleezh   4096 2月  24 14:31 .android

第一个foxleezh表示所有者,这里的foxleezh表示一个用户,类似foxleezh这个人

第二个foxleezh表示文件所有用户组,这里的foxleezh表示一个组,类似foxleezh这个家庭

然后我们来看下dwxr-xr-x,这个要分成四部分来理解,d表示目录(文件用 - 表示),wxr表示所有者权限,xr表示文件所有用户组的权限,x表示其他用户的权限

  • w- 表示写权限,用2表示
  • x- 表示执行权限,用1表示
  • r- 表示读取权限,用4表示
    那么dwxr-xr-x还有种表示方法就是751,是不是感觉跟0755差不多了,那0755前面那个0表示什么意思呢?

0755前面的0跟suid和guid有关

  • suid意味着其他用户拥有和文件所有者一样的权限,用4表示
  • guid意味着其他用户拥有和文件所有用户组一样的权限,用2表示

三、 初始化日志输出、挂载分区设备

if (is_first_stage) {

          ...

        // Now that tmpfs is mounted on /dev and we have /dev/kmsg, we can actually
        // talk to the outside world...
        InitKernelLogging(argv);

        LOG(INFO) << "init first stage started!";

        if (!DoFirstStageMount()) {
            LOG(ERROR) << "Failed to mount required partitions early ...";
            panic();//重启系统
        }

        ...
    }

3.1 InitKernelLogging

定义在platform/system/core/init/log.cpp

InitKernelLogging首先是将标准输入输出重定向到"/sys/fs/selinux/null",然后调用InitLogging初始化log日志系统

void InitKernelLogging(char* argv[]) {
    // Make stdin/stdout/stderr all point to /dev/null.
    int fd = open("/sys/fs/selinux/null", O_RDWR); //打开文件
    if (fd == -1) {
        int saved_errno = errno;
        android::base::InitLogging(argv, &android::base::KernelLogger);
        errno = saved_errno;
        PLOG(FATAL) << "Couldn't open /sys/fs/selinux/null";
    }
    /*
     * dup2(int old_fd, int new_fd) 的作用是复制文件描述符,将old复制到new,下文中将
     *  0、1、2绑定到null设备上,通过标准的输入输出无法输出信息
     */
    dup2(fd, 0); //重定向标准输入stdin
    dup2(fd, 1);//重定向标准输出stdout
    dup2(fd, 2);//重定向标准错误stderr
    if (fd > 2) close(fd);

    android::base::InitLogging(argv, &android::base::KernelLogger);//初始化log
}

3.2 InitLogging

定义在platform/system/core/base/logging.cpp

InitLogging主要工作是设置logger和aborter的处理函数,然后设置日志系统输出等级

void InitLogging(char* argv[], LogFunction&& logger, AbortFunction&& aborter) {
/*
 * C++中foo(std::forward<T>(arg))表示将arg按原本的左值或右值,传递给foo方法,
   LogFunction& 这种表示是左值,LogFunction&&这种表示是右值
 */
  SetLogger(std::forward<LogFunction>(logger)); //设置logger处理函数
  SetAborter(std::forward<AbortFunction>(aborter));//设置aborter处理函数

  if (gInitialized) {
    return;
  }

  gInitialized = true;

  // Stash the command line for later use. We can use /proc/self/cmdline on
  // Linux to recover this, but we don't have that luxury on the Mac/Windows,
  // and there are a couple of argv[0] variants that are commonly used.
  if (argv != nullptr) {
    std::lock_guard<std::mutex> lock(LoggingLock());
    ProgramInvocationName() = basename(argv[0]);
  }

  const char* tags = getenv("ANDROID_LOG_TAGS");//获取系统当前日志输出等级
  if (tags == nullptr) {
    return;
  }

  std::vector<std::string> specs = Split(tags, " "); //将tags以空格拆分成数组
  for (size_t i = 0; i < specs.size(); ++i) {
    // "tag-pattern:[vdiwefs]"
    std::string spec(specs[i]);
    if (spec.size() == 3 && StartsWith(spec, "*:")) { //如果字符数为3且以*:开头
     //那么根据第三个字符来设置日志输出等级(比如*:d,就是DEBUG级别)
      switch (spec[2]) {
        case 'v':
          gMinimumLogSeverity = VERBOSE;
          continue;
        case 'd':
          gMinimumLogSeverity = DEBUG;
          continue;
        case 'i':
          gMinimumLogSeverity = INFO;
          continue;
        case 'w':
          gMinimumLogSeverity = WARNING;
          continue;
        case 'e':
          gMinimumLogSeverity = ERROR;
          continue;
        case 'f':
          gMinimumLogSeverity = FATAL_WITHOUT_ABORT;
          continue;
        // liblog will even suppress FATAL if you say 's' for silent, but that's
        // crazy!
        case 's':
          gMinimumLogSeverity = FATAL_WITHOUT_ABORT;
          continue;
      }
    }
    LOG(FATAL) << "unsupported '" << spec << "' in ANDROID_LOG_TAGS (" << tags
               << ")";
  }
}

3.3 KernelLogger

定义在platform/system/core/base/logging.cpp

在InitKernelLogging方法中有句调用

android::base::InitLogging(argv, &android::base::KernelLogger);

这句的作用就是将KernelLogger函数作为log日志的处理函数,KernelLogger主要作用就是将要输出的日志格式化之后写入到 /dev/kmsg 设备中

void KernelLogger(android::base::LogId, android::base::LogSeverity severity,
                  const char* tag, const char*, unsigned int, const char* msg) {
  // clang-format off
  static constexpr int kLogSeverityToKernelLogLevel[] = {
      [android::base::VERBOSE] = 7,              // KERN_DEBUG (there is no verbose kernel log
                                                 //             level)
      [android::base::DEBUG] = 7,                // KERN_DEBUG
      [android::base::INFO] = 6,                 // KERN_INFO
      [android::base::WARNING] = 4,              // KERN_WARNING
      [android::base::ERROR] = 3,                // KERN_ERROR
      [android::base::FATAL_WITHOUT_ABORT] = 2,  // KERN_CRIT
      [android::base::FATAL] = 2,                // KERN_CRIT
  };
  // clang-format on
  static_assert(arraysize(kLogSeverityToKernelLogLevel) == android::base::FATAL + 1,
                "Mismatch in size of kLogSeverityToKernelLogLevel and values in LogSeverity");
  //static_assert是编译断言,如果第一个参数为true,那么编译就不通过,这里是判断kLogSeverityToKernelLogLevel数组个数不能大于7

  static int klog_fd = TEMP_FAILURE_RETRY(open("/dev/kmsg", O_WRONLY | O_CLOEXEC)); //打开 /dev/kmsg 文件
  if (klog_fd == -1) return;

  int level = kLogSeverityToKernelLogLevel[severity];//根据传入的日志等级得到Linux的日志等级,也就是kLogSeverityToKernelLogLevel对应下标的映射

  // The kernel's printk buffer is only 1024 bytes.
  // TODO: should we automatically break up long lines into multiple lines?
  // Or we could log but with something like "..." at the end?
  char buf[1024];
  size_t size = snprintf(buf, sizeof(buf), "<%d>%s: %s\n", level, tag, msg);//格式化日志输出
  if (size > sizeof(buf)) {
    size = snprintf(buf, sizeof(buf), "<%d>%s: %zu-byte message too long for printk\n",
                    level, tag, size);
  }

  iovec iov[1];
  iov[0].iov_base = buf;
  iov[0].iov_len = size;
  TEMP_FAILURE_RETRY(writev(klog_fd, iov, 1));//将日志写入到 /dev/kmsg 中
} 

3.3 DoFirstStageMount

定义在platform/system/core/init/init_first_stage.cpp

主要作用是初始化特定设备并挂载

bool DoFirstStageMount() {
    // Skips first stage mount if we're in recovery mode.
    if (IsRecoveryMode()) { //如果是刷机模式,直接跳过挂载
        LOG(INFO) << "First stage mount skipped (recovery mode)";
        return true;
    }

    // Firstly checks if device tree fstab entries are compatible.
    if (!is_android_dt_value_expected("fstab/compatible", "android,fstab")) { //如果fstab/compatible的值不是android,fstab,直接跳过挂载
        LOG(INFO) << "First stage mount skipped (missing/incompatible fstab in device tree)";
        return true;
    }

    std::unique_ptr<FirstStageMount> handle = FirstStageMount::Create();
    if (!handle) {
        LOG(ERROR) << "Failed to create FirstStageMount";
        return false;
    }
    return handle->DoFirstStageMount(); //主要是初始化特定设备并挂载
} 

3.4 handle->DoFirstStageMount

定义在platform/system/core/init/init_first_stage.cpp

这里主要作用是去解析/proc/device-tree/firmware/android/fstab,然后得到"/system", "/vendor", "/odm"三个目录的挂载信息

FirstStageMount::FirstStageMount()
    : need_dm_verity_(false), device_tree_fstab_(fs_mgr_read_fstab_dt(), fs_mgr_free_fstab) {
    if (!device_tree_fstab_) {
        LOG(ERROR) << "Failed to read fstab from device tree";
        return;
    }
    for (auto mount_point : {"/system", "/vendor", "/odm"}) {
        fstab_rec* fstab_rec =
            fs_mgr_get_entry_for_mount_point(device_tree_fstab_.get(), mount_point); //这里主要是把挂载的信息解析出来
        if (fstab_rec != nullptr) {
            mount_fstab_recs_.push_back(fstab_rec);//将挂载信息放入数组中存起来
        }
    }
} 

四、启用SELinux安全策略

SELinux是「Security-Enhanced Linux」的简称,是美国国家安全局「NSA=The National Security Agency」
和SCC(Secure Computing Corporation)开发的 Linux的一个扩张强制访问控制安全模块。
在这种访问控制体系的限制下,进程只能访问那些在他的任务中所需要文件

if (is_first_stage) {

          ...
          
        //Avb即Android Verfied boot,功能包括Secure Boot, verfying boot 和 dm-verity, 
        //原理都是对二进制文件进行签名,在系统启动时进行认证,确保系统运行的是合法的二进制镜像文件。
        //其中认证的范围涵盖:bootloader,boot.img,system.img
        SetInitAvbVersionInRecovery();//在刷机模式下初始化avb的版本,不是刷机模式直接跳过

        // Set up SELinux, loading the SELinux policy.
        selinux_initialize(true);//加载SELinux policy,也就是安全策略,
        

        // We're in the kernel domain, so re-exec init to transition to the init domain now
        // that the SELinux policy has been loaded.

        /*
         * 1.这句英文大概意思是,我们执行第一遍时是在kernel domain,所以要重新执行init文件,切换到init domain,
         * 这样SELinux policy才已经加载进来了
         * 2.后面的security_failure函数会调用panic重启系统
         */
        if (restorecon("/init") == -1) { //restorecon命令用来恢复SELinux文件属性即恢复文件的安全上下文
            PLOG(ERROR) << "restorecon failed";
            security_failure(); //失败则重启系统
        }

        ...
    }

4.1 selinux_initialize

定义在platform/system/core/init/init.cpp

static void selinux_initialize(bool in_kernel_domain) {
    Timer t;

    selinux_callback cb;
    cb.func_log = selinux_klog_callback;
    selinux_set_callback(SELINUX_CB_LOG, cb); //设置selinux的日志输出处理函数
    cb.func_audit = audit_callback;
    selinux_set_callback(SELINUX_CB_AUDIT, cb);//设置selinux的记录权限检测的处理函数

    if (in_kernel_domain) {//这里是分了两个阶段,第一阶段in_kernel_domain为true,第二阶段为false
        LOG(INFO) << "Loading SELinux policy";
        if (!selinux_load_policy()) {  //加载selinux的安全策略
            panic();
        }

        bool kernel_enforcing = (security_getenforce() == 1); //获取当前kernel的工作模式
        bool is_enforcing = selinux_is_enforcing(); //获取工作模式的配置
        if (kernel_enforcing != is_enforcing) { //如果当前的工作模式与配置的不同,就将当前的工作模式改掉
            if (security_setenforce(is_enforcing)) {
                PLOG(ERROR) << "security_setenforce(%s) failed" << (is_enforcing ? "true" : "false");
                security_failure();
            }
        }

        if (!write_file("/sys/fs/selinux/checkreqprot", "0")) {
            security_failure();
        }

        // init's first stage can't set properties, so pass the time to the second stage.
        setenv("INIT_SELINUX_TOOK", std::to_string(t.duration_ms()).c_str(), 1);
    } else {
        selinux_init_all_handles(); //第二阶段时初始化处理函数
    }
} 

4.2 selinux_set_callback

定义在platform/external/selinux/libselinux/src/callbacks.c

主要就是根据不同的type设置回调函数,selinux_log,selinux_audit这些都是函数指针

void selinux_set_callback(int type, union selinux_callback cb)
{
	switch (type) {
	case SELINUX_CB_LOG:
		selinux_log = cb.func_log;
		break;
	case SELINUX_CB_AUDIT:
		selinux_audit = cb.func_audit;
		break;
	case SELINUX_CB_VALIDATE:
		selinux_validate = cb.func_validate;
		break;
	case SELINUX_CB_SETENFORCE:
		selinux_netlink_setenforce = cb.func_setenforce;
		break;
	case SELINUX_CB_POLICYLOAD:
		selinux_netlink_policyload = cb.func_policyload;
		break;
	}
} 

4.3 selinux_load_policy

定义在platform/system/core/init/init.cpp

这里区分了两种情况,这两种情况只是区分从哪里加载安全策略文件,第一个是从 /vendor/etc/selinux/precompiled_sepolicy 读取
,第二个是从 /sepolicy 读取,他们最终都是调用selinux_android_load_policy_from_fd方法

static bool selinux_load_policy() {
    return selinux_is_split_policy_device() ? selinux_load_split_policy()
                                            : selinux_load_monolithic_policy();
} 

4.4 selinux_android_load_policy_from_fd

定义在platform/external/selinux/libselinux/src/android/android.c

这个函数主要作用是设置selinux_mnt 的值为/sys/fs/selinux ,然后调用security_load_policy

int selinux_android_load_policy_from_fd(int fd, const char *description)
{
	int rc;
	struct stat sb;
	void *map = NULL;
	static int load_successful = 0;

	/*
	 * Since updating policy at runtime has been abolished
	 * we just check whether a policy has been loaded before
	 * and return if this is the case.
	 * There is no point in reloading policy.
	 */
	if (load_successful){
	  selinux_log(SELINUX_WARNING, "SELinux: Attempted reload of SELinux policy!/n");
	  return 0;
	}

	set_selinuxmnt(SELINUXMNT); //SELINUXMNT的值为 /sys/fs/selinux 
	if (fstat(fd, &sb) < 0) {
		selinux_log(SELINUX_ERROR, "SELinux:  Could not stat %s:  %s\n",
				description, strerror(errno));
		return -1;
	}
	/*
	 * mmap 的作用是将一个文件或者其它对象映射进内存
	 */
	map = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0); 
	if (map == MAP_FAILED) {
		selinux_log(SELINUX_ERROR, "SELinux:  Could not map %s:  %s\n",
				description, strerror(errno));
		return -1;
	}

	rc = security_load_policy(map, sb.st_size);
	if (rc < 0) {
		selinux_log(SELINUX_ERROR, "SELinux:  Could not load policy:  %s\n",
				strerror(errno));
		munmap(map, sb.st_size);
		return -1;
	}

	munmap(map, sb.st_size);
	selinux_log(SELINUX_INFO, "SELinux: Loaded policy from %s\n", description);
	load_successful = 1;
	return 0;
} 

4.5 security_load_policy

定义在platform/external/selinux/libselinux/src/load_policy.c

这个函数主要作用就是写入data到/sys/fs/selinux,data其实就是之前找的那些策略文件,由此我们知道,看起来selinux_load_policy调用这么多代码,
其实只是将策略文件拷贝到 /sys/fs/selinux 目录下

int security_load_policy(void *data, size_t len)
{
	char path[PATH_MAX];
	int fd, ret;

	if (!selinux_mnt) { //selinux_mnt的值为 /sys/fs/selinux 
		errno = ENOENT;
		return -1;
	}

	snprintf(path, sizeof path, "%s/load", selinux_mnt);
	fd = open(path, O_RDWR); //打开 /sys/fs/selinux ,然后将data的值写入
	if (fd < 0)
		return -1;

	ret = write(fd, data, len);
	close(fd);
	if (ret < 0)
		return -1;
	return 0;
} 

4.6 security_setenforce

定义在platform/external/selinux/libselinux/src/setenforce.c

selinux有两种工作模式:

  • permissive,所有的操作都被允许(即没有MAC),但是如果违反权限的话,会记录日志,一般eng模式用
  • enforcing,所有操作都会进行权限检查。一般user和user-debug模式用

不管是security_setenforce还是security_getenforce都是去操作/sys/fs/selinux/enforce 文件, 0表示permissive 1表示enforcing

int security_setenforce(int value)
{
	int fd, ret;
	char path[PATH_MAX];
	char buf[20];

	if (!selinux_mnt) {
		errno = ENOENT;
		return -1;
	}

	snprintf(path, sizeof path, "%s/enforce", selinux_mnt);
	fd = open(path, O_RDWR); //打开 /sys/fs/selinux/enforce 文件
	if (fd < 0)
		return -1;

	snprintf(buf, sizeof buf, "%d", value);
	ret = write(fd, buf, strlen(buf)); //将value的值写入文件
	close(fd);
	if (ret < 0)
		return -1;

	return 0;
} 

五、开始第二阶段前的准备

这里主要就是设置一些变量如INIT_SECOND_STAGE,INIT_STARTED_AT,为第二阶段做准备,然后再次调用init的main函数,启动用户态的init进程

if (is_first_stage) {

          ...

        setenv("INIT_SECOND_STAGE", "true", 1);

        static constexpr uint32_t kNanosecondsPerMillisecond = 1e6;
        uint64_t start_ms = start_time.time_since_epoch().count() / kNanosecondsPerMillisecond;
        setenv("INIT_STARTED_AT", StringPrintf("%" PRIu64, start_ms).c_str(), 1);//记录第二阶段开始时间戳

        char* path = argv[0];
        char* args[] = { path, nullptr };
        execv(path, args); //重新执行main方法,进入第二阶段

        // execv() only returns if an error happened, in which case we
        // panic and never fall through this conditional.
        PLOG(ERROR) << "execv(\"" << path << "\") failed";
        security_failure();
    }

小结

init进程第一阶段做的主要工作是挂载分区,创建设备节点和一些关键目录,初始化日志输出系统,启用SELinux安全策略

如何阅读Android源码

前言

当我们把源码下载下来之后,会感到茫然无措,因为AOSP的源码实在是太多了,这里我们需要明确一些问题:

  • 要阅读哪些源码
  • 阅读源码的顺序和方式
  • 用什么工具来阅读

下面我将从这三个问题一一展开

一、要阅读哪些源码

这个问题是比较个性化的,因为不同的人从事着不同的工作,有的人从事应用开发,可能对Java层东西感兴趣;有的人从事Framework开发,可能对Framework层感兴趣;有的从事硬件开发,可能对底层实现感兴趣。

这个都因人而异,但是有一点,不能盲目地毫无目的地看源码,因为这样的话最终你会淹没在AOSP的大海里,看了一年半截啥都看了,却又感觉都没看透,别人问你源码的东西,都能说个一二,但是一往深了说,就不知所以了。

所以对于AOSP源码,不在于多,而在于精,你不要试图把所有的源码都看懂,你只要对自己感兴趣的那部分深入研究就可以,因为即便是Google工程师也不可能把AOSP全部读完。

对于我而言,我是从事应用层开发的,我主要会了解以下几个方面的源码:

  • Android系统启动流程,应用启动流程,四大组件启动流程,这将列入系统启动篇
  • 系统常用服务ActivityManagerService,WindowManagerService等,这将列入系统服务篇
  • 通信机制,主要是Binder和Handler,这将列入通信篇
  • 进程和线程的创建,运行,销毁,这将列入进程篇
  • View的绘制和显示流程,事件分发机制,这将列入图形绘制篇
  • Android虚拟机ART运行机制,类加载机制,Java注解,Java反射,这将列入虚拟机篇
  • Android对于Java集合的优化算法,这将列入Java基础篇

二、阅读源码的顺序和方式

2.1 阅读顺序

读源码是一个日积月累的过程,不可能一蹴而就,当我们列出自己感兴趣的源码后,我们需要制定一个阅读计划,先读什么再读什么。这个也是因人而异,根据自己的兴趣来就是,你最想读什么,那就排前面。

我一直在说兴趣,因为兴趣是最好的老师,只有你对一样东西感兴趣了,才会有动力去学,去研究,才会不觉得累,如果一开始就去啃一些你不感兴趣的东西,到头来也是乏味不专注的,理解的程度也是不深,而且很有可能失去信心,最后放弃阅读。

当然,如果你对好几样东西都感兴趣,那就有一些原则了:

  • 事物都讲究先后,就像树木扎根大地一样,先有大地,才有树木,基础的东西先看
  • 相互有关联的东西一起看,不要一会儿看系统启动,突然又去看事件分发什么的

2.2 阅读方式

Android系统涵盖的范围很广,从上层的应用程序,到Framework,再到Libraries以至硬件,从Java层到C++,就像一座几十层的大厦一样,每层都有楼梯,也有电梯,我们需要做的就是在大厦里上下穿梭。

当我们阅读某一个知识点源码的时候,不同的知识点有不同的阅读方式,有些适合从下往上读,比如系统启动流程,我是从事件开始的地方开始读,从init.cpp开始,然后到zygote进程,到Java虚拟机,最后到Luncher;

有些适合从上往下读,比如Activity的启动,我是从startActivity方法开始读,然后到ActivityThread,然后到ActivityManagerService;

有些适合两头从中间读,比如Binder,我是从Java层看到C++层,但是看到驱动那儿看不动了,然后就从接收Binder的地方往回看,最后在两端集中在驱动的地方前后对比,才将Binder看通。

这里还是有个好的方式,就是从事件触发的地方开始看是比较合适的。

三、用什么工具来阅读

Android 源码阅读神器当然是Source Insight

Source Insight的好处:

  • 支持方法跳转,类跳转,并且对C++支持很好
  • 支持文件搜索,java,c++,xml都支持,并且支持内容搜索
  • 支持一键导入,随时配置路径
  • 而且最重要的,导入文件数多的时候不卡

下面我讲讲如何使用Source Insight

3.1 下载安装Source Insight

下载地址 http://download.csdn.net/download/foxlee1991/9882553 ,我还专门配置了一个跟Android Studio一样的Darcula主题,下载地址 http://download.csdn.net/download/foxlee1991/9882535

3.2 导入AOSP源码

我目前还没有下载完整的AOSP源码,只是先下载了几个重要的源码。打开Source Insight,选择Project -> New Project,取个名字比如叫AOSP,点击OK

选择你要查看的源码目录,点击OK

选择需要将哪些目录下的源码导入,点击Add Tree

导入成功后会有很多文件列在下方,点击Close

3.3查看源码

现在进入项目还是一片空白,需要把工具栏打开,然后就可以看源码了

左边是方法和成员变量搜索,右边Project File是搜索类名,Project Symbol是内容搜索

还有一些快捷键,比如Ctrl+左键可以方法跳转,左上角有前进和后退,Ctrl+G 是跳转到指定行,Ctrl+F 搜索内容,有时我们会遇到方法无法跳转,这时我们需要点击Project,选择Synchronize Files,全局关联一下,如图

这里要注意导入的文件不要太多,太多会导致Synchronize失败,我们可以选择性地导入一些目录

我们在导入源码的时候,有时一些汇编的源码(以.s或.S结尾)无法导入,这时我们需要点击Options,选择File Type Options,在C/C++里添加.s和.S的支持,然后Close,如图

然后我们重新追加一些目录,点击Project,选择Add and Remove Projec Files,选择对应目录Add Tree即可,同时我们也可以选择Remove Tree删除对应目录源码,操作如下

四、其他

在真正开始阅读Android源码之前,最好是去了解一些C/C++的语法知识,因为源码核心的部分都是用C/C++写的,如果你对一些基础语法不太了解,会看得云里雾里的,这里我给大家推荐两本书《C标准库 中文版》《C++标准库 中文第2版》,另外一些学习网站也不错:

commons-compress.jar无法下载

你好,我在我的AS上提示如下错误:Could not download commons-compress.jar (org.apache.commons:commons-compress:1.8.1): No cached version available for offline mode
请问有什么解决方案?能否设置自行下载使其在本地找这个包?

Android系统启动流程之Linux内核

前言

Android本质上就是一个基于Linux内核的操作系统,与Ubuntu Linux、Fedora Linux类似,我们要讲Android,必定先要了解一些Linux内核的知识。

Linux内核的东西特别多,我也不可能全部讲完,由于本文主要讲解Android系统启动流程,所以这里主要讲一些内核启动相关的知识。

Linux内核启动主要涉及3个特殊的进程,idle进程(PID = 0), init进程(PID = 1)和kthreadd进程(PID = 2),这三个进程是内核的基础。

  • idle进程是Linux系统第一个进程,是init进程和kthreadd进程的父进程
  • init进程是Linux系统第一个用户进程,是Android系统应用程序的始祖,我们的app都是直接或间接以它为父进程
  • kthreadd进程是Linux系统内核管家,所有的内核线程都是直接或间接以它为父进程

本文将以这三个进程为线索,主要讲解以下内容:

  • idle进程启动
  • kthreadd进程启动
  • init进程启动

本文涉及到的文件

msm/arch/arm64/kernel/head.S
msm/init/main.c
msm/kernel/rcutree.c
msm/kernel/fork.c
msm/mm/mempolicy.c
msm/kernel/kthread.c
msm/include/linux/kthread.h
msm/include/linux/rcupdate.h
msm/kernel/rcupdate.c
msm/kernel/pid.c
msm/include/linux/sched.h
msm/kernel/sched/core.c
msm/kernel/cpu/idle.c
msm/drivers/base/init.c

一、idle进程启动

很多文章讲Android都从init进程讲起,它的进程号是1,既然进程号是1,那么有没有进程号是0的进程呢,其实是有的。

这个进程名字叫init_task,后期会退化为idle,它是Linux系统的第一个进程(init进程是第一个用户进程),也是唯一一个没有通过fork或者kernel_thread产生的进程,它在完成初始化操作后,主要负责进程调度、交换。

idle进程的启动是用汇编语言写的,对应文件是msm/arch/arm64/kernel/head.S,因为都是用汇编语言写的,我就不多介绍了,具体可参考 kernel 启动流程之head.S ,这里面有一句比较重要

340 	str	x22, [x4]			// Save processor ID
341 	str	x21, [x5]			// Save FDT pointer
342 	str	x24, [x6]			// Save PHYS_OFFSET
343 	mov	x29, #0
344 	b	start_kernel        //跳转start_kernel函数

第344行,b start_kernel,b 就是跳转的意思,跳转到start_kernel.h,这个头文件对应的实现在msm/init/main.c,start_kernel函数在最后会调用rest_init函数,这个函数开启了init进程和kthreadd进程,我们着重分析下rest_init函数。

在讲源码前,我先说明下我分析源码的写作风格:

  • 一般我会在函数下面写明该函数所在的位置,比如定义在msm/init/main.c中,这样大家就可以去项目里找到源文件
  • 我会把源码相应的英文注释也一并copy进来,这样方便英文好的人可以看到原作者的注释
  • 我会尽可能将函数中每一行代码的作用注释下(一般以//的形式注释在代码结尾),大家在看源码的同时就可以理解这段代码作用,这也是我花时间最多的,请大家务必认真看。我也想过在源码外部统一通过行号来解释,但是感觉这样需要大家一会儿看源码,一会儿看解释,上下来回看不方便,所以干脆写在一起了
  • 为了大家更好地阅读注释,我会手动做换行处理,//形式注释可能会换行到句首,也就是可能会出现在代码下方
  • 在函数结尾我尽可能总结下这个函数做了些什么,以及这个函数涉及到的一些知识
  • 对于重要的函数,我会将函数中每一个调用的子函数再单独拿出来讲解
  • 考虑到大家都是开发Android的比较多,对C/C++不太了解,在注释中我也会讲一些C/C++的知识,方便大家理解,C语言注释我一般用/** */的形式注释在代码顶头
  • 为了更好的阅读体验,希望大家可以下载一下Source Insight同步看代码,使用教程 ,可以直接将项目中app/src/main/cpp作为目录加入到Source Insight中

1.1 rest_init

定义在msm/init/main.c中

/*
 * 1.C语言oninline与inline是一对意义相反的关键字,inline的作用是编译期间直接替换代码块,也就是说编译后就没有这个方法了,
 * 而是直接把代码块替换调用这个函数的地方,oninline就相反,强制不替换,保持原有的函数
 * 2.__init_refok是__init的扩展,__init 定义的初始化函数会放入名叫.init.text的输入段,当内核启动完毕后,
 * 这个段中的内存会被释放掉,在本文中有讲,关注3.5 free_initmem
 * 3.不带参数的方法会加一个void参数
 */
static noinline void __init_refok rest_init(void)
{
	int pid;
	/*
	 * 1.C语言中const相当于Java中的final static, 表示常量
	 * 2.struct是结构体,相当于Java中定义了一个实体类,里面只有一些成员变量,{.sched_priority =1 }相当于new,
	 * 然后将成员变量sched_priority的值赋为1
	 */
	const struct sched_param param = { .sched_priority = 1 }; //初始化优先级为1的进程调度策略,
	//取值1~99,1为最小

	rcu_scheduler_starting(); //启动RCU机制,这个与后面的rcu_read_lock和rcu_read_unlock是配套的,用于多核同步
	/*
	 * We need to spawn init first so that it obtains pid 1, however
	 * the init task will end up wanting to create kthreads, which, if
	 * we schedule it before we create kthreadd, will OOPS.
	 */

	/*
     * 1.C语言中支持方法传参,kernel_thread是函数,kernel_init也是函数,但是kernel_init却作为参数传递了过去,
     * 其实传递过去的是一个函数指针,参考[函数指针](http://www.cnblogs.com/haore147/p/3647262.html)
     * 2.CLONE_FS这种大写的一般就是常量了,跟Java差不多
     */
	kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND); //用kernel_thread方式创建init进程,
	//CLONE_FS 子进程与父进程共享相同的文件系统,包括root、当前目录、umask,
	//CLONE_SIGHAND  子进程与父进程共享相同的信号处理(signal handler)表

	numa_default_policy(); // 设定NUMA系统的默认内存访问策略
	pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);//用kernel_thread方式创建kthreadd进程,
	//CLONE_FILES  子进程与父进程共享相同的文件描述符(file descriptor)表

	rcu_read_lock(); //打开RCU读取锁,在此期间无法进行进程切换
	/*
	 * C语言中&的作用是获得变量的内存地址,参考[C指针](http://www.runoob.com/cprogramming/c-pointers.html)
	 */
	kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);// 获取kthreadd的进程描述符,
	//期间需要检索进程pid的使用链表,所以要加锁

	rcu_read_unlock(); //关闭RCU读取锁
	sched_setscheduler_nocheck(kthreadd_task, SCHED_FIFO, &param); //设置kthreadd的进程调度策略,
	//SCHED_FIFO 实时调度策略,即马上调用,先到先服务,param的优先级之前定义为1

	complete(&kthreadd_done); // complete和wait_for_completion是配套的同步机制,跟java的notify和wait差不多,
	//之前kernel_init函数调用了wait_for_completion(&kthreadd_done),
	//这里调用complete就是通知kernel_init进程kthreadd进程已创建完成,可以继续执行

	/*
	 * The boot idle thread must execute schedule()
	 * at least once to get things moving:
	 */
	init_idle_bootup_task(current);//current表示当前进程,当前0号进程init_task设置为idle进程
	schedule_preempt_disabled(); //0号进程主动请求调度,让出cpu,1号进程kernel_init将会运行,并且禁止抢占
	/* Call into cpu_idle with preempt disabled */
	cpu_startup_entry(CPUHP_ONLINE);// 这个函数会调用cpu_idle_loop()使得idle进程进入自己的事件处理循环
}

rest_init的字面意思是剩余的初始化,但是它却一点都不剩余,它创建了Linux系统中两个重要的进程init和kthreadd,并且将init_task进程变为idle进程,接下来我将把rest_init中的方法逐个解析,方便大家理解。

1.2 rcu_scheduler_starting

定义在msm/kernel/rcutree.c

/*
 * This function is invoked towards the end of the scheduler's initialization
 * process.  Before this is called, the idle task might contain
 * RCU read-side critical sections (during which time, this idle
 * task is booting the system).  After this function is called, the
 * idle tasks are prohibited from containing RCU read-side critical
 * sections.  This function also enables RCU lockdep checking.
 */
void rcu_scheduler_starting(void)
{
	WARN_ON(num_online_cpus() != 1); //WARN_ON相当于警告,会打印出当前栈信息,不会重启,
	//num_online_cpus表示当前启动的cpu数

	WARN_ON(nr_context_switches() > 0); // nr_context_switches 进行进程切换的次数
	rcu_scheduler_active = 1; //启用rcu机制
}

1.3 kernel_thread

定义在msm/kernel/fork.c

/*
 * Create a kernel thread.
 */
 
/*
 * 1.C语言中 int (*fn)(void *)表示函数指针的定义,int是返回值,void是函数的参数,fn是名字
 * 2.C语言中 * 表示指针,这个用法很多
 * 3.unsigned表示无符号,一般与long,int,char等结合使用,表示范围只有正数,
 * 比如init表示范围-2147483648~2147483647 ,那unsigned表示范围0~4294967295,足足多了一倍
 */
pid_t kernel_thread(int (*fn)(void *), void *arg, unsigned long flags)
{
	return do_fork(flags|CLONE_VM|CLONE_UNTRACED, (unsigned long)fn,
		(unsigned long)arg, NULL, NULL);
}

do_fork函数用于创建进程,它首先调用copy_process()创建新进程,然后调用wake_up_new_task()将进程放入运行队列中并启动新进程。
kernel_thread的第一个参数是一个函数引用,它相当于Java中的构造函数,会在创建进程后执行,第三个参数是创建进程的方式,具体如下:

参数名 作用
CLONE_PARENT 创建的子进程的父进程是调用者的父进程,新进程与创建它的进程成了“兄弟”而不是“父子”
CLONE_FS 子进程与父进程共享相同的文件系统,包括root、当前目录、umask
CLONE_FILES 子进程与父进程共享相同的文件描述符(file descriptor)表
CLONE_NEWNS 在新的namespace启动子进程,namespace描述了进程的文件hierarchy
CLONE_SIGHAND 子进程与父进程共享相同的信号处理(signal handler)表
CLONE_PTRACE 若父进程被trace,子进程也被trace
CLONE_UNTRACED 若父进程被trace,子进程不被trace
CLONE_VFORK 父进程被挂起,直至子进程释放虚拟内存资源
CLONE_VM 子进程与父进程运行于相同的内存空间
CLONE_PID 子进程在创建时PID与父进程一致
CLONE_THREAD Linux 2.4中增加以支持POSIX线程标准,子进程与父进程共享相同的线程群

1.4 kernel_init

定义在msm/init/main.c

这个函数比较重要,负责init进程的启动,我将放在第三节重点讲,这个函数首先调用kernel_init_freeable函数

static noinline void __init kernel_init_freeable(void)
{
	/*
	 * Wait until kthreadd is all set-up.
	 */
	wait_for_completion(&kthreadd_done);

	...
}

wait_for_completion之前讲了,与complete是配套的同步机制,这里就是等待&kthreadd_done这个值complete,然后就可以继续执行

1.5 numa_default_policy

定义在msm/mm/mempolicy.c

/* Reset policy of current process to default */
void numa_default_policy(void)
{
	do_set_mempolicy(MPOL_DEFAULT, 0, NULL); //设定NUMA系统的内存访问策略为MPOL_DEFAULT
}

1.6 kthreadd

定义在msm/kernel/kthread.c中

kthreadd进程我将在第二节中重点讲,它是内核中重要的进程,负责内核线程的调度和管理,内核线程基本都是以它为父进程的

1.7 rcu_read_lock & rcu_read_unlock

定义在msm/include/linux/rcupdate.h和msm/kernel/rcupdate.c中

RCU(Read-Copy Update)是数据同步的一种方式,在当前的Linux内核中发挥着重要的作用。RCU主要针对的数据对象是链表,目的是提高遍历读取数据的效率,为了达到目的使用RCU机制读取数据的时候不对链表进行耗时的加锁操作。这样在同一时间可以有多个线程同时读取该链表,并且允许一个线程对链表进行修改(修改的时候,需要加锁)

static inline void rcu_read_lock(void)
{
	__rcu_read_lock();
	__acquire(RCU);
	rcu_lock_acquire(&rcu_lock_map);
	rcu_lockdep_assert(!rcu_is_cpu_idle(),
			   "rcu_read_lock() used illegally while idle");
}

static inline void rcu_read_unlock(void)
{
	rcu_lockdep_assert(!rcu_is_cpu_idle(),
			   "rcu_read_unlock() used illegally while idle");
	rcu_lock_release(&rcu_lock_map);
	__release(RCU);
	__rcu_read_unlock();
}

1.8 find_task_by_pid_ns

定义在msm/kernel/pid.c中

task_struct叫进程描述符,这个结构体包含了一个进程所需的所有信息,它定义在msm/include/linux/sched.h文件中。

它的结构十分复杂,本文就不重点讲了,可以参考Linux进程描述符task_struct结构体详解

/*
 * Must be called under rcu_read_lock().
 */
struct task_struct *find_task_by_pid_ns(pid_t nr, struct pid_namespace *ns)
{
	rcu_lockdep_assert(rcu_read_lock_held(),
			   "find_task_by_pid_ns() needs rcu_read_lock()"
			   " protection"); //必须进行RCU加锁
	return pid_task(find_pid_ns(nr, ns), PIDTYPE_PID);
}

struct pid *find_pid_ns(int nr, struct pid_namespace *ns)
{
	struct upid *pnr;

	hlist_for_each_entry_rcu(pnr,
			&pid_hash[pid_hashfn(nr, ns)], pid_chain)
			/*
			 * C语言中 -> 用于指向结构体 struct 中的数据
			 */
		if (pnr->nr == nr && pnr->ns == ns)
			return container_of(pnr, struct pid,
					numbers[ns->level]); //遍历hash表,找到struct pid

	return NULL;
}

struct task_struct *pid_task(struct pid *pid, enum pid_type type)
{
	struct task_struct *result = NULL;
	if (pid) {
		struct hlist_node *first;
		first = rcu_dereference_check(hlist_first_rcu(&pid->tasks[type]),
					      lockdep_tasklist_lock_is_held());
		if (first)
			result = hlist_entry(first, struct task_struct, pids[(type)].node); //从hash表中找出struct task_struct
	}
	return result;
}

find_task_by_pid_ns的作用就是根据pid,在hash表中获得对应pid的task_struct

1.9 sched_setscheduler_nocheck

定义在msm/kernel/sched/core.c中

int sched_setscheduler_nocheck(struct task_struct *p, int policy,
			       const struct sched_param *param)
{
	struct sched_attr attr = {
		.sched_policy   = policy,
		.sched_priority = param->sched_priority
	};
	return __sched_setscheduler(p, &attr, false); //设置进程调度策略
}

linux内核目前实现了6种调度策略(即调度算法), 用于对不同类型的进程进行调度, 或者支持某些特殊的功能

  • SCHED_FIFO和SCHED_RR和SCHED_DEADLINE则采用不同的调度策略调度实时进程,优先级最高

  • SCHED_NORMAL和SCHED_BATCH调度普通的非实时进程,优先级普通

  • SCHED_IDLE则在系统空闲时调用idle进程,优先级最低

1.10 init_idle_bootup_task

定义在msm/kernel/sched/core.c中

void __cpuinit init_idle_bootup_task(struct task_struct *idle)
{
	idle->sched_class = &idle_sched_class; //设置进程的调度器类为idle_sched_class
}

Linux依据其调度策略的不同实现了5个调度器类, 一个调度器类可以用一种种或者多种调度策略调度某一类进程, 也可以用于特殊情况或者调度特殊功能的进程.

其所属进程的优先级顺序为

stop_sched_class -> dl_sched_class -> rt_sched_class -> fair_sched_class -> idle_sched_class

可见idle_sched_class的优先级最低,只有系统空闲时才调用idle进程

1.11 schedule_preempt_disabled

定义在msm/kernel/sched/core.c中

/**
 * schedule_preempt_disabled - called with preemption disabled
 *
 * Returns with preemption disabled. Note: preempt_count must be 1
 */
void __sched schedule_preempt_disabled(void)
{
	sched_preempt_enable_no_resched(); //开启内核抢占
	schedule();  // 并主动请求调度,让出cpu
	preempt_disable(); // 关闭内核抢占
}

1.9到1.11都涉及到Linux的进程调度问题,可以参考 Linux用户抢占和内核抢占详解

1.12 cpu_startup_entry

定义在msm/kernel/cpu/idle.c中

void cpu_startup_entry(enum cpuhp_state state)
{
	/*
	 * This #ifdef needs to die, but it's too late in the cycle to
	 * make this generic (arm and sh have never invoked the canary
	 * init for the non boot cpus!). Will be fixed in 3.11
	 */
	 
	 
	 /*
	  * 1.C语言中#ifdef和#else、#endif是条件编译语句,也就是说在满足某些条件的时候,
	  * 夹在这几个关键字中间的代码才编译,不满足就不编译
	  * 2.下面这句话的意思就是如果定义了CONFIG_X86这个宏,就把boot_init_stack_canary这个代码编译进去
	  */
#ifdef CONFIG_X86
	/*
	 * If we're the non-boot CPU, nothing set the stack canary up
	 * for us. The boot CPU already has it initialized but no harm
	 * in doing it again. This is a good place for updating it, as
	 * we wont ever return from this function (so the invalid
	 * canaries already on the stack wont ever trigger).
	 */
	boot_init_stack_canary();//只有在x86这种non-boot CPU机器上执行,该函数主要用于初始化stack_canary的值,用于防止栈溢出
#endif
	__current_set_polling(); //设置本架构下面有标示轮询poll的bit位,保证cpu进行重新调度。
	arch_cpu_idle_prepare(); //进行idle前的准备工作,ARM64中没有实现
	per_cpu(idle_force_poll, smp_processor_id()) = 0;
	cpu_idle_loop(); //进入idle进程的事件循环
}

1.13 cpu_idle_loop

定义在msm/kernel/cpu/idle.c中

/*
 * Generic idle loop implementation
 */
static void cpu_idle_loop(void)
{
	while (1) { //开启无限循环,进行进程调度
		tick_nohz_idle_enter(); //停止周期时钟

		while (!need_resched()) { //判断是否有设置TIF_NEED_RESCHED,只有系统没有进程需要调度时才执行while里面操作
			check_pgt_cache();
			rmb();

			local_irq_disable(); //关闭irq中断
			arch_cpu_idle_enter();

			/*
			 * In poll mode we reenable interrupts and spin.
			 *
			 * Also if we detected in the wakeup from idle
			 * path that the tick broadcast device expired
			 * for us, we don't want to go deep idle as we
			 * know that the IPI is going to arrive right
			 * away
			 */
			if (cpu_idle_force_poll ||
			    tick_check_broadcast_expired() ||
			    __get_cpu_var(idle_force_poll)) {
				cpu_idle_poll(); //进入 CPU 的poll mode模式,避免进入深度睡眠,可以处理 处理器间中断
			} else {
				if (!current_clr_polling_and_test()) {
					stop_critical_timings();
					rcu_idle_enter();
					arch_cpu_idle(); //进入 CPU 的 idle 模式,省电
					WARN_ON_ONCE(irqs_disabled());
					rcu_idle_exit();
					start_critical_timings();
				} else {
					local_irq_enable();
				}
				__current_set_polling();
			}
			arch_cpu_idle_exit();
		}
		tick_nohz_idle_exit(); //如果有进程需要调度,则先开启周期时钟
		schedule_preempt_disabled(); //让出cpu,执行调度
		if (cpu_is_offline(smp_processor_id())) //如果当前cpu处理offline状态,关闭idle进程
			arch_cpu_idle_dead();

	}
}

idle进程并不执行什么复杂的工作,只有在系统没有其他进程调度的时候才进入idle进程,而在idle进程中尽可能让cpu空闲下来,连周期时钟也关掉了,达到省电目的。当有其他进程需要调度的时候,马上开启周期时钟,然后让出cpu。

小结

idle进程是Linux系统的第一个进程,进程号是0,在完成系统环境初始化工作之后,开启了两个重要的进程,init进程和kthreadd进程,执行完创建工作之后,开启一个无限循环,负责进程的调度。

二、kthreadd进程启动

之前在rest_init函数中启动了kthreadd进程

pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);

进程创建成功后会执行kthreadd函数

2.1 kthreadd

定义在msm/kernel/kthread.c中

int kthreadd(void *unused)
{
	struct task_struct *tsk = current;

	/* Setup a clean context for our children to inherit. */
	set_task_comm(tsk, "kthreadd");
	ignore_signals(tsk);
	set_cpus_allowed_ptr(tsk, cpu_all_mask); //  允许kthreadd在任意CPU上运行
	set_mems_allowed(node_states[N_MEMORY]);

	current->flags |= PF_NOFREEZE;

	for (;;) {
		set_current_state(TASK_INTERRUPTIBLE); //首先将线程状态设置为 TASK_INTERRUPTIBLE,
		//如果当前没有要创建的线程则主动放弃 CPU 完成调度.此进程变为阻塞态

		if (list_empty(&kthread_create_list)) //  没有需要创建的内核线程
			schedule(); //   执行一次调度, 让出CPU
		__set_current_state(TASK_RUNNING);//  运行到此表示 kthreadd 线程被唤醒(就是我们当前),设置进程运行状态为 TASK_RUNNING
		spin_lock(&kthread_create_lock); //spin_lock和spin_unlock是配套的加锁机制,spin_lock是加锁
		while (!list_empty(&kthread_create_list)) {
			struct kthread_create_info *create;

			create = list_entry(kthread_create_list.next,
					    struct kthread_create_info, list); //kthread_create_list是一个链表,
					    //从链表中取出下一个要创建的kthread_create_info,即线程创建信息

			list_del_init(&create->list); //删除create中的list
			spin_unlock(&kthread_create_lock); //解锁

			create_kthread(create); //创建线程

			spin_lock(&kthread_create_lock); 
		}
		spin_unlock(&kthread_create_lock);
	}

	return 0;
}

kthreadd函数的作用就是循环地从kthread_create_list链表中取出要创建的线程,然后执行create_kthread函数,直到kthread_create_list为空,让出CPU,进入睡眠,我们来看下create_kthread函数

2.2 create_kthread

定义在msm/kernel/kthread.c中

static void create_kthread(struct kthread_create_info *create)
{
	int pid;

#ifdef CONFIG_NUMA
	current->pref_node_fork = create->node;
#endif
	/* We want our own signal handler (we take no signals by default). */
	pid = kernel_thread(kthread, create, CLONE_FS | CLONE_FILES | SIGCHLD);
	if (pid < 0) {
		create->result = ERR_PTR(pid);
		complete(&create->done);
	}
}

其实这里面就是调用kernel_thread函数创建进程,然后执行kthread函数,注意不要搞混了,之前那个函数叫kthreadd,接下来看看kthread函数

2.3 kthread

定义在msm/kernel/kthread.c中

static int kthread(void *_create)
{
	/* Copy data: it's on kthread's stack */
	struct kthread_create_info *create = _create;  // create 就是之前kthreadd函数循环取出的 kthread_create_info
	int (*threadfn)(void *data) = create->threadfn; //新线程工作函数
	void *data = create->data;
	struct kthread self;
	int ret;

	self.flags = 0;
	self.data = data;
	init_completion(&self.exited);
	init_completion(&self.parked);
	current->vfork_done = &self.exited;

	/* OK, tell user we're spawned, wait for stop or wakeup */
	__set_current_state(TASK_UNINTERRUPTIBLE);
	create->result = current;
	complete(&create->done); //表示线程创建完毕
	schedule(); //让出CPU,注意这里并没有执行新线程的threadfn函数就直接进入睡眠了,然后等待线程被手动唤醒,然后才执行threadfn

	ret = -EINTR;

	if (!test_bit(KTHREAD_SHOULD_STOP, &self.flags)) {
		__kthread_parkme(&self);
		ret = threadfn(data);
	}
	/* we can't just return, we must preserve "self" on stack */
	do_exit(ret);
}

2.4 kthread_create & kthread_run

定义在msm/include/linux/kthread.h

kthreadd创建线程是遍历kthread_create_list列表,那kthread_create_list列表中的值是哪儿来的呢?我们知道Linux创建内核线程有两种方式,kthread_create和kthread_run

#define kthread_create(threadfn, data, namefmt, arg...) \
	kthread_create_on_node(threadfn, data, -1, namefmt, ##arg)

#define kthread_run(threadfn, data, namefmt, ...)			   \
({									   \
	struct task_struct *__k						   \
		= kthread_create(threadfn, data, namefmt, ## __VA_ARGS__); \
	if (!IS_ERR(__k))						   \
		wake_up_process(__k);	//手动唤醒新线程				   \
	__k;								   \
})

kthread_create和kthread_run并不是函数,而是宏,宏相当于Java中的final static定义,在编译时会替换对应代码,宏的参数没有类型定义,多行宏的定义会在行末尾加上\

这两个宏最终都是调用kthread_create_on_node函数,只是kthread_run在线程创建完成后会手动唤醒,我们来看看kthread_create_on_node函数

2.5 kthread_create_on_node

定义在msm/kernel/kthread.c中

/**
 * kthread_create_on_node - create a kthread.
 * @threadfn: the function to run until signal_pending(current).
 * @data: data ptr for @threadfn.
 * @node: memory node number.
 * @namefmt: printf-style name for the thread.
 *
 * Description: This helper function creates and names a kernel
 * thread.  The thread will be stopped: use wake_up_process() to start
 * it.  See also kthread_run().
 *
 * If thread is going to be bound on a particular cpu, give its node
 * in @node, to get NUMA affinity for kthread stack, or else give -1.
 * When woken, the thread will run @threadfn() with @data as its
 * argument. @threadfn() can either call do_exit() directly if it is a
 * standalone thread for which no one will call kthread_stop(), or
 * return when 'kthread_should_stop()' is true (which means
 * kthread_stop() has been called).  The return value should be zero
 * or a negative error number; it will be passed to kthread_stop().
 *
 * Returns a task_struct or ERR_PTR(-ENOMEM).
 */
struct task_struct *kthread_create_on_node(int (*threadfn)(void *data),
					   void *data, int node,
					   const char namefmt[],
					   ...)
{
	struct kthread_create_info create;

	create.threadfn = threadfn;
	create.data = data;
	create.node = node;
	init_completion(&create.done);  //初始化&create.done,之前讲过completion和wait_for_completion同步

	spin_lock(&kthread_create_lock);  //加锁,之前也讲过
	list_add_tail(&create.list, &kthread_create_list);  //将要创建的线程加到kthread_create_list链表尾部
	spin_unlock(&kthread_create_lock);

	wake_up_process(kthreadd_task);  //唤醒kthreadd进程,开启列表循环创建线程
	wait_for_completion(&create.done);  //当&create.done complete时,会继续往下执行

	if (!IS_ERR(create.result)) {
		static const struct sched_param param = { .sched_priority = 0 };
		va_list args;  //不定参数定义,相当于Java中的... ,定义多个数量不定的参数

		va_start(args, namefmt);
		vsnprintf(create.result->comm, sizeof(create.result->comm),
			  namefmt, args);
		va_end(args);
		/*
		 * root may have changed our (kthreadd's) priority or CPU mask.
		 * The kernel thread should not inherit these properties.
		 */
		sched_setscheduler_nocheck(create.result, SCHED_NORMAL, &param);  //create.result类型为task_struct,
		//该函数作用是设置新线程调度策略,SCHED_NORMAL 普通调度策略,非实时,
		//优先级低于实时调度策略SCHED_FIFO和SCHED_RR,param的优先级上面定义为0

		set_cpus_allowed_ptr(create.result, cpu_all_mask); //允许新线程在任意CPU上运行
	}
	return create.result;
}

kthread_create_on_node主要作用就是在kthread_create_list链表尾部加上要创建的线程,然后唤醒kthreadd进程进行具体创建工作

小结

kthreadd进程由idle通过kernel_thread创建,并始终运行在内核空间, 负责所有内核线程的调度和管理,所有的内核线程都是直接或者间接的以kthreadd为父进程。

  • kthreadd进程会执行一个kthreadd的函数,该函数的作用就是遍历kthread_create_list链表,从链表中取出需要创建的内核线程进行创建, 创建成功后会执行kthread函数。

  • kthread函数完成一些初始赋值后就让出CPU,并没有执行新线程的工作函数,因此需要手工 wake up被唤醒后,新线程才执行自己的真正工作函数。

  • 当我们调用kthread_create和kthread_run创建的内核线程会被加入到kthread_create_list链表,kthread_create不会手动wake up新线程,kthread_run会手动wake up新线程。

其实这就是一个典型的生产者消费者模式,kthread_create和kthread_run负责生产各种内核线程创建需求,kthreadd开启循环去消费各种内核线程创建需求。

三、init进程启动

init进程分为前后两部分,前一部分是在内核启动的,主要是完成创建和内核初始化工作,内容都是跟Linux内核相关的;后一部分是在用户空间启动的,主要完成Android系统的初始化工作。

我这里要讲的是前一部分,后一部分将在下一篇文章中讲述。

之前在rest_init函数中启动了init进程

kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);

在创建完init进程后,会调用kernel_init函数

3.1 kernel_init

定义在msm/init/main.c中

/*
 * __ref 这个跟之前讲的__init作用一样
 */
static int __ref kernel_init(void *unused)
{
	kernel_init_freeable(); //进行init进程的一些初始化操作
	/* need to finish all async __init code before freeing the memory */
	async_synchronize_full();// 等待所有异步调用执行完成,,在释放内存前,必须完成所有的异步 __init 代码
	free_initmem();// 释放所有init.* 段中的内存
	mark_rodata_ro(); //arm64空实现
	system_state = SYSTEM_RUNNING;// 设置系统状态为运行状态
	numa_default_policy(); // 设定NUMA系统的默认内存访问策略

	flush_delayed_fput(); // 释放所有延时的struct file结构体

	if (ramdisk_execute_command) { //ramdisk_execute_command的值为"/init"
		if (!run_init_process(ramdisk_execute_command)) //运行根目录下的init程序
			return 0;
		pr_err("Failed to execute %s\n", ramdisk_execute_command);
	}

	/*
	 * We try each of these until one succeeds.
	 *
	 * The Bourne shell can be used instead of init if we are
	 * trying to recover a really broken machine.
	 */
	if (execute_command) { //execute_command的值如果有定义就去根目录下找对应的应用程序,然后启动
		if (!run_init_process(execute_command))
			return 0;
		pr_err("Failed to execute %s.  Attempting defaults...\n",
			execute_command);
	}
	if (!run_init_process("/sbin/init") || //如果ramdisk_execute_command和execute_command定义的应用程序都没有找到,
	//就到根目录下找 /sbin/init,/etc/init,/bin/init,/bin/sh 这四个应用程序进行启动

	    !run_init_process("/etc/init") ||
	    !run_init_process("/bin/init") ||
	    !run_init_process("/bin/sh"))
		return 0;

	panic("No init found.  Try passing init= option to kernel. "
	      "See Linux Documentation/init.txt for guidance.");
}

kernel_init主要工作是完成一些init的初始化操作,然后去系统根目录下依次找ramdisk_execute_command和execute_command设置的应用程序,如果这两个目录都找不到,就依次去根目录下找 /sbin/init,/etc/init,/bin/init,/bin/sh 这四个应用程序进行启动,只要这些应用程序有一个启动了,其他就不启动了

ramdisk_execute_command和execute_command的值是通过bootloader传递过来的参数设置的,ramdisk_execute_command通过"rdinit"参数赋值,execute_command通过"init"参数赋值

ramdisk_execute_command如果没有被赋值,kernel_init_freeable函数会赋一个初始值"/init"

3.2 kernel_init_freeable

定义在msm/init/main.c中

static noinline void __init kernel_init_freeable(void)
{
	/*
	 * Wait until kthreadd is all set-up.
	 */
	wait_for_completion(&kthreadd_done); //等待&kthreadd_done这个值complete,这个在rest_init方法中有写,在ktreadd进程启动完成后设置为complete

	/* Now the scheduler is fully set up and can do blocking allocations */
	gfp_allowed_mask = __GFP_BITS_MASK;//设置bitmask, 使得init进程可以使用PM并且允许I/O阻塞操作

	/*
	 * init can allocate pages on any node
	 */
	set_mems_allowed(node_states[N_MEMORY]);//init进程可以分配物理页面
	/*
	 * init can run on any cpu.
	 */
	set_cpus_allowed_ptr(current, cpu_all_mask); //init进程可以在任意cpu上执行

	cad_pid = task_pid(current); //设置到init进程的pid号给cad_pid,cad就是ctrl-alt-del,设置init进程来处理ctrl-alt-del信号

	smp_prepare_cpus(setup_max_cpus);//设置smp初始化时的最大CPU数量,然后将对应数量的CPU状态设置为present

	do_pre_smp_initcalls();//调用__initcall_start到__initcall0_start之间的initcall_t函数指针
	lockup_detector_init(); //开启watchdog_threads,watchdog主要用来监控、管理CPU的运行状态

	smp_init();//启动cpu0外的其他cpu核
	sched_init_smp(); //进程调度域初始化

	do_basic_setup();//初始化设备,驱动等,这个方法比较重要,将在下面单独讲

	/* Open the /dev/console on the rootfs, this should never fail */
	if (sys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0) // 打开/dev/console,
	//文件号0,作为init进程标准输入

		pr_err("Warning: unable to open an initial console.\n");

	(void) sys_dup(0);// 标准输入
	(void) sys_dup(0);// 标准输出
	/*
	 * check if there is an early userspace init.  If yes, let it do all
	 * the work
	 */

	if (!ramdisk_execute_command)  //如果 ramdisk_execute_command 没有赋值,则赋值为"/init",之前有讲到
		ramdisk_execute_command = "/init";

	if (sys_access((const char __user *) ramdisk_execute_command, 0) != 0) {
	// 尝试进入ramdisk_execute_command指向的文件,如果失败则重新挂载根文件系统

		ramdisk_execute_command = NULL;
		prepare_namespace();
	}

	/*
	 * Ok, we have completed the initial bootup, and
	 * we're essentially up and running. Get rid of the
	 * initmem segments and start the user-mode stuff..
	 */

	/* rootfs is available now, try loading default modules */
	load_default_modules(); // 加载I/O调度的电梯算法
}

kernel_init_freeable函数做了很多重要的事情

  • 启动了smp,smp全称是Symmetrical Multi-Processing,即对称多处理,是指在一个计算机上汇集了一组处理器(多CPU),各CPU之间共享内存子系统以及总线结构。
  • 初始化设备和驱动程序
  • 打开标准输入和输出
  • 初始化文件系统

3.3 do_basic_setup

定义在msm/init/main.c中

/*
 * Ok, the machine is now initialized. None of the devices
 * have been touched yet, but the CPU subsystem is up and
 * running, and memory and process management works.
 *
 * Now we can finally start doing some real work..
 */
static void __init do_basic_setup(void)
{
	cpuset_init_smp();//针对SMP系统,初始化内核control group的cpuset子系统。
	usermodehelper_init();// 创建khelper单线程工作队列,用于协助新建和运行用户空间程序
	shmem_init();// 初始化共享内存
	driver_init();// 初始化设备驱动,比较重要下面单独讲
	init_irq_proc();//创建/proc/irq目录, 并初始化系统中所有中断对应的子目录
	do_ctors();// 执行内核的构造函数
	usermodehelper_enable();// 启用usermodehelper
	do_initcalls();//遍历initcall_levels数组,调用里面的initcall函数,这里主要是对设备、驱动、文件系统进行初始化,
	//之所有将函数封装到数组进行遍历,主要是为了好扩展

	random_int_secret_init();//初始化随机数生成池
}

3.4 driver_init

定义在msm/drivers/base/init.c中

/**
 * driver_init - initialize driver model.
 *
 * Call the driver model init functions to initialize their
 * subsystems. Called early from init/main.c.
 */
void __init driver_init(void)
{
	/* These are the core pieces */
	devtmpfs_init();// 注册devtmpfs文件系统,启动kdevtmpfs进程
	devices_init();// 初始化驱动模型中的部分子系统,kset:devices 和 kobject:dev、 dev/block、 dev/char
	buses_init();// 初始化驱动模型中的bus子系统,kset:bus、devices/system
	classes_init();// 初始化驱动模型中的class子系统,kset:class
	firmware_init();// 初始化驱动模型中的firmware子系统 ,kobject:firmware
	hypervisor_init();// 初始化驱动模型中的hypervisor子系统,kobject:hypervisor

	/* These are also core pieces, but must come after the
	 * core core pieces.
	 */
	platform_bus_init();// 初始化驱动模型中的bus/platform子系统,这个节点是所有platform设备和驱动的总线类型,
	//即所有platform设备和驱动都会挂载到这个总线上

	cpu_dev_init(); // 初始化驱动模型中的devices/system/cpu子系统,该节点包含CPU相关的属性
	memory_dev_init();//初始化驱动模型中的/devices/system/memory子系统,该节点包含了内存相关的属性,如块大小等
}

这个函数完成驱动子系统的构建,实现了Linux设备驱动的一个整体框架,但是它只是建立了目录结构,具体驱动的装载是在do_initcalls函数,之前有讲

kernel_init_freeable函数告一段落了,我们接着讲kernel_init中剩余的函数

3.5 free_initmem

定义在msm/arch/arm64/mm/init.c中中

void free_initmem(void)
{
	poison_init_mem(__init_begin, __init_end - __init_begin);
	free_initmem_default(0);
}

所有使用__init标记过的函数和使用__initdata标记过的数据,在free_initmem函数执行后,都不能使用,它们曾经获得的内存现在可以重新用于其他目的。

3.6 flush_delayed_fput

定义在msm/arch/arm64/mm/init.c中,它执行的是delayed_fput(NULL)

static void delayed_fput(struct work_struct *unused)
{
	LIST_HEAD(head);
	spin_lock_irq(&delayed_fput_lock);
	list_splice_init(&delayed_fput_list, &head);
	spin_unlock_irq(&delayed_fput_lock);
	while (!list_empty(&head)) {
		struct file *f = list_first_entry(&head, struct file, f_u.fu_list);
		list_del_init(&f->f_u.fu_list); //删除fu_list
		__fput(f); //释放struct file
	}
}

这个函数主要用于释放&delayed_fput_list这个链表中的struct file,struct file即文件结构体,代表一个打开的文件,系统中的每个打开的文件在内核空间都有一个关联的 struct file。

3.7 run_init_process

定义在msm/init/main.c中

static int run_init_process(const char *init_filename)
{
	argv_init[0] = init_filename;
	return do_execve(init_filename,
		(const char __user *const __user *)argv_init,
		(const char __user *const __user *)envp_init); //do_execve就是执行一个可执行文件
}

run_init_process就是运行可执行文件了,从kernel_init函数中可知,系统会依次去找根目录下的init,execute_command,/sbin/init,/etc/init,/bin/init,/bin/sh这六个可执行文件,只要找到其中一个,其他就不执行。

Android系统一般会在根目录下放一个init的可执行文件,也就是说Linux系统的init进程在内核初始化完成后,就直接执行init这个文件,这个文件的源代码在platform/system/core/init/init.cpp,下一篇文章中我将从这个文件为入口,讲解Android系统的init进程。

Linux常见内核函数

Linux系统有提供许多方便的API,就像Andoird中TextView的setText方法一样,我们只需要简单调用就可以实现一些功能,为了方便大家阅读Linux源码,我将一些常用的API列举出来

我先大致分个类吧

  • 进程与进程调度
  • 同步与锁
  • 内存与内存策略

一、进程与进程调度

1.1 kernel_thread

出现在《Android系统启动流程之Linux内核》

kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);

这个函数作用是启动进程

  • 第一个参数表示新进程工作函数,相当于Java的构造函数
  • 第二个参数是工作函数的参数,相当于Java带参构造函数的参数
  • 第三个参数表示启动方式
参数名 作用
CLONE_PARENT 创建的子进程的父进程是调用者的父进程,新进程与创建它的进程成了“兄弟”而不是“父子”
CLONE_FS 子进程与父进程共享相同的文件系统,包括root、当前目录、umask
CLONE_FILES 子进程与父进程共享相同的文件描述符(file descriptor)表
CLONE_NEWNS 在新的namespace启动子进程,namespace描述了进程的文件hierarchy
CLONE_SIGHAND 子进程与父进程共享相同的信号处理(signal handler)表
CLONE_PTRACE 若父进程被trace,子进程也被trace
CLONE_UNTRACED 若父进程被trace,子进程不被trace
CLONE_VFORK 父进程被挂起,直至子进程释放虚拟内存资源
CLONE_VM 子进程与父进程运行于相同的内存空间
CLONE_PID 子进程在创建时PID与父进程一致
CLONE_THREAD Linux 2.4中增加以支持POSIX线程标准,子进程与父进程共享相同的线程群

1.2 sched_setscheduler_nocheck

出现在《Android系统启动流程之Linux内核》

sched_setscheduler_nocheck(kthreadd_task, SCHED_FIFO, &param);

这个函数作用是设置进程调度策略

  • 第一个参数是进程的task_struct
  • 第二个是进程调度策略
  • 第三个是进程优先级

进程调度策略如下:

  • SCHED_FIFO和SCHED_RR和SCHED_DEADLINE则采用不同的调度策略调度实时进程,优先级最高

  • SCHED_NORMAL和SCHED_BATCH调度普通的非实时进程,优先级普通

  • SCHED_IDLE则在系统空闲时调用idle进程,优先级最低

参考Linux进程调度器的设计

二、同步与锁

2.1 rcu_read_lock、rcu_read_unlock

出现在《Android系统启动流程之Linux内核》

	rcu_read_lock(); 
	kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);
	rcu_read_unlock();

RCU(Read-Copy Update)是数据同步的一种方式,在当前的Linux内核中发挥着重要的作用。RCU主要针对的数据对象是链表,目的是提高遍历读取数据的效率,为了达到目的使用RCU机制读取数据的时候不对链表进行耗时的加锁操作。这样在同一时间可以有多个线程同时读取该链表,并且允许一个线程对链表进行修改(修改的时候,需要加锁)

参考Linux 2.6内核中新的锁机制--RCU

三、内存与内存策略

3.1 numa_default_policy

设定NUMA系统的默认内存访问策略

3.2 mmap

将一个文件或者其它对象映射进内存

map = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0); 

start:映射区的开始地址,设置为0时表示由系统决定映射区的起始地址。

length:映射区的长度。长度单位是 以字节为单位,不足一内存页按一内存页处理

prot:期望的内存保护标志,不能与文件的打开模式冲突。是以下的某个值,可以通过or运算合理地组合在一起

参数 含义
PROT_EXEC 页内容可以被执行
PROT_READ 页内容可以被读取
PROT_WRITE 页可以被写入
PROT_NONE 页不可访问

flags:指定映射对象的类型,映射选项和映射页是否可以共享。它的值可以是一个或者多个以下位的组合体

参数 含义
MAP_FIXED 使用指定的映射起始地址,如果由start和len参数指定的内存区重叠于现存的映射空间,重叠部分将会被丢弃。如果指定的起始地址不可用,操作将会失败。并且起始地址必须落在页的边界上
MAP_SHARED 与其它所有映射这个对象的进程共享映射空间。对共享区的写入,相当于输出到文件。直到msync()或者munmap()被调用,文件实际上不会被更新
MAP_PRIVATE 建立一个写入时拷贝的私有映射。内存区域的写入不会影响到原文件。这个标志和以上标志是互斥的,只能使用其中一个
MAP_DENYWRITE 这个标志被忽略
MAP_EXECUTABLE 同上
MAP_NORESERVE 不要为这个映射保留交换空间。当交换空间被保留,对映射区修改的可能会得到保证。当交换空间不被保留,同时内存不足,对映射区的修改会引起段违例信号
MAP_LOCKED 锁定映射区的页面,从而防止页面被交换出内存
MAP_GROWSDOWN 用于堆栈,告诉内核VM系统,映射区可以向下扩展
MAP_ANONYMOUS 匿名映射,映射区不与任何文件关联
MAP_ANON MAP_ANONYMOUS的别称,不再被使用
MAP_FILE 兼容标志,被忽略
MAP_32BIT 将映射区放在进程地址空间的低2GB,MAP_FIXED指定时会被忽略。当前这个标志只在x86-64平台上得到支持
MAP_POPULATE 为文件映射通过预读的方式准备好页表。随后对映射区的访问不会被页违例阻塞
MAP_NONBLOCK 仅和MAP_POPULATE一起使用时才有意义。不执行预读,只为已存在于内存中的页面建立页表入口

fd:有效的文件描述词。一般是由open()函数返回,其值也可以设置为-1,此时需要指定flags参数中的MAP_ANON,表明进行的是匿名映射

off_toffset:被映射对象内容的起点。

四、通信

4.1 int socketpair(int d, int type, int protocol, int sv[2])

创建一对socket,用于本机内的进程通信

参数分别是:

  • d 套接口的域 ,一般为AF_UNIX,表示Linux本机
  • type 套接口类型,参数比较多

    SOCK_STREAM或SOCK_DGRAM,即TCP或UDP

    SOCK_NONBLOCK read不到数据不阻塞,直接返回0

    SOCK_CLOEXEC 设置文件描述符为O_CLOEXEC
  • protocol 使用的协议,值只能是0
  • sv 指向存储文件描述符的指针

4.2 int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);

  • signum:要操作的信号。
  • act:要设置的对信号的新处理方式。
  • oldact:原来对信号的处理方式。
  • 返回值:0 表示成功,-1 表示有错误发生。

struct sigaction 类型用来描述对信号的处理,定义如下:
struct sigaction
{
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer)(void);
};

sa_handler 是一个函数指针,其含义与 signal 函数中的信号处理函数类似
sa_sigaction 则是另一个信号处理函数,它有三个参数,可以获得关于信号的更详细的信息。当 sa_flags 成员的值包含了 SA_SIGINFO 标志时,系统将使用 sa_sigaction 函数作为信号处理函数,否则使用 sa_handler 作为信号处理函数。在某些系统中,成员 sa_handler 与 sa_sigaction 被放在联合体中,因此使用时不要同时设置。
sa_mask 成员用来指定在信号处理函数执行期间需要被屏蔽的信号,特别是当某个信号被处理时,它自身会被自动放入进程的信号掩码,因此在信号处理函数执行期间这个信号不会再度发生。
sa_flags 成员用于指定信号处理的行为,它可以是一下值的“按位或”组合。

参数名 作用
SA_RESTART 使被信号打断的系统调用自动重新发起
SA_NOCLDSTOP 使父进程在它的子进程暂停或继续运行时不会收到 SIGCHLD 信号
SA_NOCLDWAIT 使父进程在它的子进程退出时不会收到 SIGCHLD 信号,这时子进程如果退出也不会成为僵尸进程
SA_NODEFER 使对信号的屏蔽无效,即在信号处理函数执行期间仍能发出这个信号
SA_RESETHAND 信号处理之后重新设置为默认的处理方式
SA_SIGINFO 使用 sa_sigaction 成员而不是 sa_handler 作为信号处理函数

Andorid常见内核函数

Android在C/C++封装了许多好用的API,记录一下,方便阅读

1.android::base::Split

分割字符串,用法

std::vector<std::string> pieces = android::base::Split(entry, "=");
        if (pieces.size() == 2) {
            fn(pieces[0], pieces[1], in_qemu);
        } 

如何下载Android源码

前言

源码下载是我们分析源码的开始,Android源码可以全量下载,也可以单个下载,我们先介绍全量下载

全量下载

官方文档 https://source.android.com/source/downloading ,只要按照上面一步步做就可以了,但是由于需要翻墙,国内无法直接访问,而整个Android项目源码巨大,即便是翻墙后下载也很慢,所以还是使用国内镜像比较好。

我推荐清华大学开源镜像,地址 https://mirrors.tuna.tsinghua.edu.cn/help/AOSP/ ,这上面也是有完整的教程,我就不复制粘贴了,但是有一点要注意,你一定要备一个比较大的磁盘,至少60个G吧,还不算后期编译的。

我们分析源码其实是不需要全部代码的,因为AOSP不仅包括系统源码,还有些工具代码,如aapt,adb等,这些我们根本不需要,而且即便是系统源码,也不是所有我们都需要看,如果真的全部看,你这辈子都看不完,所以我还是推荐大家单个下载。

单个下载

官方地址 https://android.googlesource.com/ ,比如我们要下载platform/frameworks/base/目录下的代码,我们可以git clone https://android.googlesource.com/platform/frameworks/base ,不过这个还是会遇到翻墙的问题,当然我们也可以用镜像。

镜像地址 https://aosp.tuna.tsinghua.edu.cn/ ,比如我们要下载platform/frameworks/base/目录,就用git clone https://aosp.tuna.tsinghua.edu.cn/platform/frameworks/base ,如果你带宽够的话,一般几分钟就可以下载好你想要的单个源码了。

如果你想下载单个文件,或者搜索文件名及代码,可以访问 http://androidxref.com/ ,这里有部分Android的源码

目录结构

先上一张图,整个Android项目的架构图

我们都知道Android系统从上到下大致分为这四层,所以我们以这四层为基础,讲解下AOSP的目录结构:

  • 第一层:应用程序层(applications)对应根目录下platform/packages/apps
  • 第二层:应用程序框架层(application framework)对应根目录下的platform/frameworks
  • 第三层:运行库层包括运行库(libraries)和android运行时环境(android runtime)
  • 第四层:Linux内核层对应根目录下的kernel,每一个目录对应了一个kernel的版本,因为Android要兼容各种芯片,我们主要看的有两个,一是goldfish,这是模拟器用的内核,一是msm,这个是高通的内核,下面罗列一下:
    • goldfish 项目包含适用于所模拟的平台的内核源代码。
    • msm 项目包含适用于 ADP1、ADP2、Nexus One、Nexus 4、Nexus 5、Nexus 6、Nexus 5X、Nexus 6P、Nexus 7 (2013)、Pixel 和 Pixel XL 的源代码,可用作使用 Qualcomm MSM 芯片组的起点。
    • omap 项目用于 PandaBoard 和 Galaxy Nexus,可用作使用 TI OMAP 芯片组的起点。
    • samsung 项目用于 Nexus S,可用作使用 Samsung Hummingbird 芯片组的起点。
    • tegra 项目用于 Xoom、Nexus 7 (2012)、Nexus 9,可用作使用 NVIDIA Tegra 芯片组的起点。
    • exynos 项目包含适用于 Nexus 10 的内核源代码,可用作使用 Samsung Exynos 芯片组的起点。
    • x86_64 项目包含适用于 Nexus Player 的内核源代码,可用作使用 Intel x86_64 芯片组的起点。
    • hikey-linaro 项目用于 HiKey 参考板,可用作使用 HiSilicon 620 芯片组的起点。
  • 三、四层中间还有个硬件抽象层(HAL)对应根目录下的platform/hardware

目前我下载的目录如下:

git clone https://aosp.tuna.tsinghua.edu.cn/platform/packages/apps/Launcher2

git clone https://aosp.tuna.tsinghua.edu.cn/platform/frameworks/base

git clone https://aosp.tuna.tsinghua.edu.cn/platform/frameworks/native

git clone https://aosp.tuna.tsinghua.edu.cn/platform/system/core

git clone https://aosp.tuna.tsinghua.edu.cn/platform/bionic

git clone https://aosp.tuna.tsinghua.edu.cn/platform/libcore

git clone https://aosp.tuna.tsinghua.edu.cn/platform/art

git clone https://aosp.tuna.tsinghua.edu.cn/kernel/msm

C语言知识整理

为了方便大家理解C/C++的语法,我将源码中涉及到的一些小知识整理一下,以源码分析的顺序列出,我会在知识点下列出出现的地方,大家也可以对照着看。

1.oninline、inline、__init、void

出现在《Android系统启动流程之Linux内核》

/*
 * C语言oninline与inline是一对意义相反的关键字,inline的作用是编译期间直接替换代码块,也就是说编译后就没有这个方法了,而是直接把代码块替换调用这个函数的地方,oninline就相反,强制不替换,保持原有的函数
 * __init_refok是__init的扩展,__init 定义的初始化函数会放入名叫.init.text的输入段,当内核启动完毕后,这个段中的内存会被释放掉,在本文中有讲,关注3.5 free_initmem。
 * 不带参数的方法会加一个void参数
 */
static noinline void __init_refok rest_init(void)
{
 }

更多使用参考GCC特性之__init修饰解析

2.struct

出现在《Android系统启动流程之Linux内核》

/*
 * C语言中const相当于Java中的final static, 表示常量
 * struct是结构体,相当于Java中定义了一个实体类,里面只有一些成员变量,{.sched_priority =1 }相当于new,然后将成员变量sched_priority的值赋为1
 */
const struct sched_param param = { .sched_priority = 1 }; 

3.函数指针

出现在《Android系统启动流程之Linux内核》

/*
 * C语言中支持方法传参,kernel_thread是函数,kernel_init也是函数,但是kernel_init却作为参数传递了过去,其实传递过去的是一个函数指针
 */
kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);

更多使用参考函数指针

4. &用法

出现在《Android系统启动流程之Linux内核》

/*
 * C语言中&的作用是获得变量的内存地址
 */
kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);

更多使用参考C指针

5. 函数指针定义,* 指针,unsigned

出现在《Android系统启动流程之Linux内核》

/*
 * C语言中 int (*fn)(void *)表示函数指针的定义,int是返回值,void是函数的参数,fn是名字
 * C语言中 * 表示指针,这个用法很多
 * unsigned表示无符号,一般与long,int,char等结合使用,表示范围只有正数,比如init表示范围-2147483648~2147483647 ,那unsigned表示范围0~4294967295,足足多了一倍
 */
pid_t kernel_thread(int (*fn)(void *), void *arg, unsigned long flags)
{
return do_fork(flags|CLONE_VM|CLONE_UNTRACED, (unsigned long)fn,
(unsigned long)arg, NULL, NULL);
}

更多使用参考 深入解析C语言中函数指针C语言入门之指针用法教程

6. ->

出现在《Android系统启动流程之Linux内核》

/*
 * C语言中 -> 用于指向结构体 struct 中的数据
 */
if (pnr->nr == nr && pnr->ns == ns)

更多使用参考 C语言中 -> 是什么意思

6. #ifdef、#else、#endif(条件编译)

出现在《Android系统启动流程之Linux内核》

	 /*
	  * C语言中#ifdef和#else、#endif是条件编译语句,也就是说在满足某些条件的时候,夹在这几个关键字中间的代码才编译,不满足就不编译
	  * 下面这句话的意思就是如果定义了CONFIG_X86这个宏,就把boot_init_stack_canary这个代码编译进去
	  */
#ifdef CONFIG_X86
	/*
	 * If we're the non-boot CPU, nothing set the stack canary up
	 * for us. The boot CPU already has it initialized but no harm
	 * in doing it again. This is a good place for updating it, as
	 * we wont ever return from this function (so the invalid
	 * canaries already on the stack wont ever trigger).
	 */
	boot_init_stack_canary();//只有在x86这种non-boot CPU机器上执行,该函数主要用于初始化stack_canary的值,用于防止栈溢出
#endif

更多使用参考 条件编译#ifdef的妙用详解_透彻

Android系统启动流程之init进程(二)

前言

上一篇中讲了init进程的第一阶段,我们接着讲第二阶段,主要有以下内容

  • 创建进程会话密钥并初始化属性系统
  • 进行SELinux第二阶段并恢复一些文件安全上下文
  • 新建epoll并初始化子进程终止信号处理函数
  • 设置其他系统属性并开启系统属性服务

本文涉及到的文件

platform/system/core/init/init.cpp
platform/system/core/init/keyutils.h
platform/system/core/init/property_service.cpp
platform/external/selinux/libselinux/src/label.c
platform/system/core/init/signal_handler.cpp
platform/system/core/init/service.cpp
platform/system/core/init/property_service.cpp

一、创建进程会话密钥并初始化属性系统

第二阶段一开始会有一个is_first_stage的判断,由于之前第一阶段最后有设置INIT_SECOND_STAGE,
因此直接跳过一大段代码。从keyctl开始才是重点内容,我们一一展开来看

int main(int argc, char** argv) {

    //同样进行ueventd/watchdogd跳转及环境变量设置

    ...

    //之前准备工作时将INIT_SECOND_STAGE设置为true,已经不为nullptr,所以is_first_stage为false
    bool is_first_stage = (getenv("INIT_SECOND_STAGE") == nullptr);

    //is_first_stage为false,直接跳过
    if (is_first_stage) {
        ...
    }

    // At this point we're in the second stage of init.
    InitKernelLogging(argv); //上一节有讲,初始化日志输出
    LOG(INFO) << "init second stage started!";

    // Set up a session keyring that all processes will have access to. It
    // will hold things like FBE encryption keys. No process should override
    // its session keyring.
    keyctl(KEYCTL_GET_KEYRING_ID, KEY_SPEC_SESSION_KEYRING, 1); //初始化进程会话密钥

    // Indicate that booting is in progress to background fw loaders, etc.
    close(open("/dev/.booting", O_WRONLY | O_CREAT | O_CLOEXEC, 0000));//创建 /dev/.booting 文件,就是个标记,表示booting进行中

    property_init();//初始化属性系统,并从指定文件读取属性

    //接下来的一系列操作都是从各个文件读取一些属性,然后通过property_set设置系统属性

    // If arguments are passed both on the command line and in DT,
    // properties set in DT always have priority over the command-line ones.
    /*
     * 1.这句英文的大概意思是,如果参数同时从命令行和DT传过来,DT的优先级总是大于命令行的
     * 2.DT即device-tree,中文意思是设备树,这里面记录自己的硬件配置和系统运行参数,参考http://www.wowotech.net/linux_kenrel/why-dt.html
     */

    process_kernel_dt();//处理DT属性
    process_kernel_cmdline();//处理命令行属性

    // Propagate the kernel variables to internal variables
    // used by init as well as the current required properties.
    export_kernel_boot_props();//处理其他的一些属性

    // Make the time that init started available for bootstat to log.
    property_set("ro.boottime.init", getenv("INIT_STARTED_AT"));
    property_set("ro.boottime.init.selinux", getenv("INIT_SELINUX_TOOK"));

    // Set libavb version for Framework-only OTA match in Treble build.
    const char* avb_version = getenv("INIT_AVB_VERSION");
    if (avb_version) property_set("ro.boot.avb_version", avb_version);

    // Clean up our environment.
    unsetenv("INIT_SECOND_STAGE"); //清空这些环境变量,因为之前都已经存入到系统属性中去了
    unsetenv("INIT_STARTED_AT");
    unsetenv("INIT_SELINUX_TOOK");
    unsetenv("INIT_AVB_VERSION");

    ...

1.1 keyctl

定义在platform/system/core/init/keyutils.h

keyctl将主要的工作交给__NR_keyctl这个系统调用,keyctl是Linux系统操纵内核的通讯密钥管理工具

我们分析下 keyctl(KEYCTL_GET_KEYRING_ID, KEY_SPEC_SESSION_KEYRING, 1)

  • KEYCTL_GET_KEYRING_ID 表示通过第二个参数的类型获取当前进程的密钥信息
  • KEY_SPEC_SESSION_KEYRING 表示获取当前进程的SESSION_KEYRING(会话密钥环)
  • 1 表示如果获取不到就新建一个

参考linux手册

这里并没有拿返回值,估计就是为了新建会话密钥环了,从注释Set up a session keyring也可看出

static inline long keyctl(int cmd, ...) {
    va_list va;
    unsigned long arg2, arg3, arg4, arg5;

    //va_start,va_arg,va_end是配合使用的,用于将可变参数从堆栈中读取出来
    va_start(va, cmd); //va_start是获取第一个参数地址
    arg2 = va_arg(va, unsigned long); //va_arg 遍历参数
    arg3 = va_arg(va, unsigned long);
    arg4 = va_arg(va, unsigned long);
    arg5 = va_arg(va, unsigned long);
    va_end(va); //va_end 恢复堆栈
    return syscall(__NR_keyctl, cmd, arg2, arg3, arg4, arg5); //系统调用
}

1.2 property_init

定义在 platform/system/core/init/property_service.cpp

直接交给 __system_property_area_init 处理

void property_init() {
    if (__system_property_area_init()) {
        LOG(ERROR) << "Failed to initialize property area";
        exit(1);
    }
}

__system_property_area_init 定义在/bionic/libc/bionic/system_properties.cpp

看名字大概知道是用来初始化属性系统区域的,应该是分门别类更准确些,首先清除缓存,这里主要是清除几个链表以及在内存中的映射,新建property_filename目录,这个目录的值为 /dev/_properties_
然后就是调用initialize_properties加载一些系统属性的类别信息,最后将加载的链表写入文件并映射到内存

int __system_property_area_init() {
  free_and_unmap_contexts();//清除一些缓存
  mkdir(property_filename, S_IRWXU | S_IXGRP | S_IXOTH);//新建目录property_filename,权限是rwx-x-x
  if (!initialize_properties()) { //读取一些文件,把键值信息存入到链表中
    return -1;
  }
  bool open_failed = false;
  bool fsetxattr_failed = false;
  list_foreach(contexts, [&fsetxattr_failed, &open_failed](context_node* l) {
    if (!l->open(true, &fsetxattr_failed)) {
    //将contexts链表中的数据写入到property_filename目录下文件中,每种context对应一个文件,并通过mmap映射进内存中
      open_failed = true;
    }
  });
  if (open_failed || !map_system_property_area(true, &fsetxattr_failed)) {//增加 properties_serial的映射,跟contexts中的一样
    free_and_unmap_contexts();//映射失败清除缓存
    return -1;
  }
  initialized = true;
  return fsetxattr_failed ? -2 : 0;
}

1.3 initialize_properties

定义在/bionic/libc/bionic/system_properties.cpp

交给 initialize_properties_from_file 处理,指定了一些文件路径

static bool initialize_properties() {
  // If we do find /property_contexts, then this is being
  // run as part of the OTA updater on older release that had
  // /property_contexts - b/34370523
  if (initialize_properties_from_file("/property_contexts")) {
    return true;
  }

  // Use property_contexts from /system & /vendor, fall back to those from /
  if (access("/system/etc/selinux/plat_property_contexts", R_OK) != -1) {
    if (!initialize_properties_from_file("/system/etc/selinux/plat_property_contexts")) {
      return false;
    }
    // Don't check for failure here, so we always have a sane list of properties.
    // E.g. In case of recovery, the vendor partition will not have mounted and we
    // still need the system / platform properties to function.
    initialize_properties_from_file("/vendor/etc/selinux/nonplat_property_contexts");
  } else {
    if (!initialize_properties_from_file("/plat_property_contexts")) {
      return false;
    }
    initialize_properties_from_file("/nonplat_property_contexts");
  }

  return true;
}

1.4 initialize_properties_from_file

定义在/bionic/libc/bionic/system_properties.cpp

这个函数主要工作是解析属性类别文件,对属性做一下分类,具体就是一行行解析,过滤 # 开头的、只读到key的、从ctl.开头的,然后将解析出来的键值对放到两个链表中

prefixes链表存放key(其实是一些key的前缀),contexts链表存放value(其实是对应key应当属于那些类别的信息),这样的好处是将庞杂的属性根据前缀分类,存储到不同的context中,
查找和修改是非常高效的,类似map的做法

static bool initialize_properties_from_file(const char* filename) {
  FILE* file = fopen(filename, "re");
  if (!file) {
    return false;
  }

  char* buffer = nullptr;
  size_t line_len;
  char* prop_prefix = nullptr;
  char* context = nullptr;

  while (getline(&buffer, &line_len, file) > 0) { //一行一行读取,然后将结果放到buffer中
    int items = read_spec_entries(buffer, 2, &prop_prefix, &context);
    //将buffer的数据,按空格作为区分,key赋值给prop_prefix,value赋值给context

    if (items <= 0) { //没有读取到,比如 # 这种是注释
      continue;
    }
    if (items == 1) { //只读取到key,释放key的内存
      free(prop_prefix);
      continue;
    }
    /*
     * init uses ctl.* properties as an IPC mechanism and does not write them
     * to a property file, therefore we do not need to create property files
     * to store them.
     */
    if (!strncmp(prop_prefix, "ctl.", 4)) { //以ctl.开头忽略掉,因为这个不属于属性,主要用于IPC机制
      free(prop_prefix);
      free(context);
      continue;
    }

    /*
     * C++中[ arg1,arg2,... ](T param, T param1,... ){ commond} 这个是lambda表达式,也可以看作一个函数指针
     * []中是引用外部参数
     *()中是参数定义,这个跟普通方法的()一样
     * {}中是方法体
     */
    auto old_context =
        list_find(contexts, [context](context_node* l) { return !strcmp(l->context(), context); });

    // list_find主要是循环contexts这个链表,如果发现context的值在链表里已经有,就将对应的链表结构context_node返回
    if (old_context) {
      list_add_after_len(&prefixes, prop_prefix, old_context);
      //list_add_after_len 主要作用是将prop_prefix和old_context按顺序放到prefixes链表里
    } else {
      list_add(&contexts, context, nullptr);//将context的值放到contexts链表里
      list_add_after_len(&prefixes, prop_prefix, contexts);
    }
    free(prop_prefix); //释放资源
    free(context);
  }

  free(buffer);
  fclose(file);

  return true;
}

1.5 链表结构

定义在/bionic/libc/bionic/system_properties.cpp

之前我们看到有两个重要的链表prefixs和contexts,frefixs存key(其实是一些key的前缀),contexts存value(其实是对应key应当属于那些类别的信息),接下来我们看下这两个链表的结构

context_node中有三个比较重要的属性context_、_pa和next,context_用来存类别信息,_pa是存具体key-value节点的,next是链表下一个节点

prefix_node中有三个重要属性prefix,context和next,prefix用来存key,context用来存关联的context_node,next是链表下一个节点

prop_area 这个在context_node里引用,属性data是具体key-value的数据库,里面是用 hybrid trie/binary tree(字典树)这种结构存储的,也就是一对多,我给张图就明白了

prop_info 就是具体的key-value了,这个是从prop_area解析出来的

class context_node {
 public:
  /*
   * C++中构造函数后面接 :(冒号) 表示对属性赋初始值
   */
  context_node(context_node* next, const char* context, prop_area* pa)
      : next(next), context_(strdup(context)), pa_(pa), no_access_(false) {

    lock_.init(false);
  }

  ...

  context_node* next;

 private:
  bool check_access();
  void unmap();

  Lock lock_;
  char* context_;
  prop_area* pa_;
  bool no_access_;
};

struct prefix_node {
  prefix_node(struct prefix_node* next, const char* prefix, context_node* context)
      : prefix(strdup(prefix)), prefix_len(strlen(prefix)), context(context), next(next) {
  }
  ~prefix_node() {
    free(prefix);
  }
  char* prefix;
  const size_t prefix_len;
  context_node* context;
  struct prefix_node* next;
};

class prop_area {
 public:
  prop_area(const uint32_t magic, const uint32_t version) : magic_(magic), version_(version) {
    atomic_init(&serial_, 0);
    memset(reserved_, 0, sizeof(reserved_));
    // Allocate enough space for the root node.
    bytes_used_ = sizeof(prop_bt);
  }

  ....

 private:

  ...

  uint32_t bytes_used_;
  atomic_uint_least32_t serial_;
  uint32_t magic_;
  uint32_t version_;
  uint32_t reserved_[28];
  char data_[0];

  DISALLOW_COPY_AND_ASSIGN(prop_area);
};

struct prop_info {
  atomic_uint_least32_t serial;
  // we need to keep this buffer around because the property
  // value can be modified whereas name is constant.
  char value[PROP_VALUE_MAX];
  char name[0];

  prop_info(const char* name, uint32_t namelen, const char* value, uint32_t valuelen) {
    memcpy(this->name, name, namelen);
    this->name[namelen] = '\0';
    atomic_init(&this->serial, valuelen << 24);
    memcpy(this->value, value, valuelen);
    this->value[valuelen] = '\0';
  }

 private:
  DISALLOW_IMPLICIT_CONSTRUCTORS(prop_info);
};

之前有个list_add函数,这个函数是一个模板函数,与Java中的泛型类似,List 和 Args相当于T和T1,这个函数主要作用就是调用T的构造函数,
把list,可变参数args作为参数传进去

template <typename List, typename... Args>
static inline void list_add(List** list, Args... args) {
  *list = new List(*list, args...);
}

1.6 process_kernel_dt

定义在platform/system/core/init/init.cpp

读取DT(设备树)的属性信息,然后通过 property_set 设置系统属性

static void process_kernel_dt() {
    if (!is_android_dt_value_expected("compatible", "android,firmware")) {
    //判断 /proc/device-tree/firmware/android/compatible 文件中的值是否为 android,firmware
        return;
    }

    std::unique_ptr<DIR, int (*)(DIR*)> dir(opendir(kAndroidDtDir.c_str()), closedir);
    // kAndroidDtDir的值为/proc/device-tree/firmware/android

    if (!dir) return;

    std::string dt_file;
    struct dirent *dp;
    while ((dp = readdir(dir.get())) != NULL) { //遍历dir中的文件
        if (dp->d_type != DT_REG || !strcmp(dp->d_name, "compatible") || !strcmp(dp->d_name, "name")) {
            //跳过 compatible和name文件
            continue;
        }

        std::string file_name = kAndroidDtDir + dp->d_name;

        android::base::ReadFileToString(file_name, &dt_file); //读取文件内容
        std::replace(dt_file.begin(), dt_file.end(), ',', '.'); //替换 , 为 .

        std::string property_name = StringPrintf("ro.boot.%s", dp->d_name);
        property_set(property_name.c_str(), dt_file.c_str()); // 将 ro.boot.文件名 作为key,文件内容为value,设置进属性
    }
}

1.7 property_set

定义在/bionic/libc/bionic/system_properties.cpp

property_set用的地方特别多,作用是设置系统属性,具体就是通过遍历之前的prefixs链表找到对应的context_node,然后通过context_node的_pa属性找到对应key-value节点prop_info,能找到就更新value,找不到就设置新值,
另外就是调用property_changed方法触发trigger,trigger后续讲.rc解析时再详细讲,trigger可以触发一系列活动

uint32_t property_set(const std::string& name, const std::string& value) {
    size_t valuelen = value.size();

    if (!is_legal_property_name(name)) { //检查key合法性,大概就是 xx.xx.xx 这种 ,xx只能是字母、数字、_、-、@
        LOG(ERROR) << "property_set(\"" << name << "\", \"" << value << "\") failed: bad name";
        return PROP_ERROR_INVALID_NAME;
    }

    if (valuelen >= PROP_VALUE_MAX) {//不能超过最大长度 92
        LOG(ERROR) << "property_set(\"" << name << "\", \"" << value << "\") failed: "
                   << "value too long";
        return PROP_ERROR_INVALID_VALUE;
    }

    if (name == "selinux.restorecon_recursive" && valuelen > 0) { // 跳过selinux,不允许修改
        if (restorecon(value.c_str(), SELINUX_ANDROID_RESTORECON_RECURSE) != 0) {
            LOG(ERROR) << "Failed to restorecon_recursive " << value;
        }
    }

    prop_info* pi = (prop_info*) __system_property_find(name.c_str()); //找到key对应节点
    if (pi != nullptr) { //如果对应节点存在就更新
        // ro.* properties are actually "write-once".
        if (android::base::StartsWith(name, "ro.")) {
            LOG(ERROR) << "property_set(\"" << name << "\", \"" << value << "\") failed: "
                       << "property already set";
            return PROP_ERROR_READ_ONLY_PROPERTY;
        }

        __system_property_update(pi, value.c_str(), valuelen);
    } else { //没有对应节点就新建
        int rc = __system_property_add(name.c_str(), name.size(), value.c_str(), valuelen);
        if (rc < 0) {
            LOG(ERROR) << "property_set(\"" << name << "\", \"" << value << "\") failed: "
                       << "__system_property_add failed";
            return PROP_ERROR_SET_FAILED;
        }
    }

    // Don't write properties to disk until after we have read all default
    // properties to prevent them from being overwritten by default values.
    if (persistent_properties_loaded && android::base::StartsWith(name, "persist.")) {
    //如果以persist开头的,将值写入文件
        write_persistent_property(name.c_str(), value.c_str());
    }
    property_changed(name, value); //触发trigger
    return PROP_SUCCESS;
}

1.8 其他属性设置

后续的一些函数或代码都是直接或间接调用 property_set 设置系统属性

static void process_kernel_cmdline() {
    // The first pass does the common stuff, and finds if we are in qemu.
    // The second pass is only necessary for qemu to export all kernel params
    // as properties.
    import_kernel_cmdline(false, import_kernel_nv);
    if (qemu[0]) import_kernel_cmdline(true, import_kernel_nv);
}

static void import_kernel_nv(const std::string& key, const std::string& value, bool for_emulator) {
    if (key.empty()) return;

    if (for_emulator) {
        // In the emulator, export any kernel option with the "ro.kernel." prefix.
        property_set(StringPrintf("ro.kernel.%s", key.c_str()).c_str(), value.c_str());
        return;
    }

    if (key == "qemu") {
        strlcpy(qemu, value.c_str(), sizeof(qemu));
    } else if (android::base::StartsWith(key, "androidboot.")) {
        property_set(StringPrintf("ro.boot.%s", key.c_str() + 12).c_str(), value.c_str());
    }
}
static void export_kernel_boot_props() {
    struct {
        const char *src_prop;
        const char *dst_prop;
        const char *default_value;
    } prop_map[] = {
        { "ro.boot.serialno",   "ro.serialno",   "", },
        { "ro.boot.mode",       "ro.bootmode",   "unknown", },
        { "ro.boot.baseband",   "ro.baseband",   "unknown", },
        { "ro.boot.bootloader", "ro.bootloader", "unknown", },
        { "ro.boot.hardware",   "ro.hardware",   "unknown", },
        { "ro.boot.revision",   "ro.revision",   "0", },
    };
    for (size_t i = 0; i < arraysize(prop_map); i++) {
        std::string value = GetProperty(prop_map[i].src_prop, "");
        property_set(prop_map[i].dst_prop, (!value.empty()) ? value.c_str() : prop_map[i].default_value);
    }
}

二、进行SELinux第二阶段并恢复一些文件安全上下文

    // Now set up SELinux for second stage.
    selinux_initialize(false); //第二阶段初始化SELinux policy
    selinux_restore_context();//恢复安全上下文

2.1 selinux_initialize

定义在platform/system/core/init/init.cpp

第二阶段只是执行 selinux_init_all_handles

static void selinux_initialize(bool in_kernel_domain) {

    ... //和之前一样设置回调函数

    if (in_kernel_domain) {//第二阶段跳过
       ...
    } else {
        selinux_init_all_handles();
    }
}

2.2 selinux_init_all_handles

定义在platform/system/core/init/init.cpp

这里是创建SELinux的处理函数,selinux_android_file_context_handle和selinux_android_prop_context_handle内部实现差不多,其实就是传递不同的文件路径给selabel_open

static void selinux_init_all_handles(void)
{
    sehandle = selinux_android_file_context_handle(); //创建context的处理函数
    selinux_android_set_sehandle(sehandle);//将刚刚新建的处理赋值给fc_sehandle
    sehandle_prop = selinux_android_prop_context_handle();//创建prop的处理函数
}

2.2 selabel_open

定义在platform/external/selinux/libselinux/src/label.c

首先创建一个selabel_handle结构体,然后根据backend的类型将处理函数映射给initfuncs数组中的值,将参数opts传递过去

这个opts只是包含一个简单的路径,比如 /system/etc/selinux/plat_file_contexts ,而initfuncs负责去解析它

struct selabel_handle *selabel_open(unsigned int backend,
				    const struct selinux_opt *opts,
				    unsigned nopts)
{
	struct selabel_handle *rec = NULL;

	if (backend >= ARRAY_SIZE(initfuncs)) {
		errno = EINVAL;
		goto out;
	}

	if (!initfuncs[backend]) {
		errno = ENOTSUP;
		goto out;
	}

	rec = (struct selabel_handle *)malloc(sizeof(*rec));
	if (!rec)
		goto out;

	memset(rec, 0, sizeof(*rec));
	rec->backend = backend;
	rec->validating = selabel_is_validate_set(opts, nopts);

	rec->subs = NULL;
	rec->dist_subs = NULL;
	rec->digest = selabel_is_digest_set(opts, nopts, rec->digest);

	if ((*initfuncs[backend])(rec, opts, nopts)) { //
		selabel_close(rec);
		rec = NULL;
	}
out:
	return rec;
}

2.3 initfuncs

定义在platform/external/selinux/libselinux/src/label.c

这些数组对应backend的6种可能的值

/* file contexts */
#define SELABEL_CTX_FILE	0
/* media contexts */
#define SELABEL_CTX_MEDIA	1
/* x contexts */
#define SELABEL_CTX_X		2
/* db objects */
#define SELABEL_CTX_DB		3
/* Android property service contexts */
#define SELABEL_CTX_ANDROID_PROP 4
/* Android service contexts */
#define SELABEL_CTX_ANDROID_SERVICE 5

initfuncs数组中每一项都对应一个init函数,init函数主要作用是解析传进来的文件,这些传进来的文件定义了哪些进程可以访问哪些文件,执行哪些操作
SELinux的内容比较多,由于篇幅就暂时不深入了
可以参考老罗的SEAndroid安全机制框架分析

static selabel_initfunc initfuncs[] = {
	&selabel_file_init,
	CONFIG_MEDIA_BACKEND(selabel_media_init),
	CONFIG_X_BACKEND(selabel_x_init),
	CONFIG_DB_BACKEND(selabel_db_init),
	CONFIG_ANDROID_BACKEND(selabel_property_init),
	CONFIG_ANDROID_BACKEND(selabel_service_init),
};

2.3 selinux_restore_context

定义在 platform/system/core/init/init.cpp

主要就是恢复这些文件的安全上下文,因为这些文件是在SELinux安全机制初始化前创建,所以需要重新恢复下安全性

static void selinux_restore_context() {
    LOG(INFO) << "Running restorecon...";
    restorecon("/dev");
    restorecon("/dev/kmsg");
    restorecon("/dev/socket");
    restorecon("/dev/random");
    restorecon("/dev/urandom");
    restorecon("/dev/__properties__");

    restorecon("/file_contexts.bin");
    restorecon("/plat_file_contexts");
    restorecon("/nonplat_file_contexts");
    restorecon("/plat_property_contexts");
    restorecon("/nonplat_property_contexts");
    restorecon("/plat_seapp_contexts");
    restorecon("/nonplat_seapp_contexts");
    restorecon("/plat_service_contexts");
    restorecon("/nonplat_service_contexts");
    restorecon("/plat_hwservice_contexts");
    restorecon("/nonplat_hwservice_contexts");
    restorecon("/sepolicy");
    restorecon("/vndservice_contexts");

    restorecon("/sys", SELINUX_ANDROID_RESTORECON_RECURSE);
    restorecon("/dev/block", SELINUX_ANDROID_RESTORECON_RECURSE);
    restorecon("/dev/device-mapper");
}

三、新建epoll并初始化子进程终止信号处理函数

    epoll_fd = epoll_create1(EPOLL_CLOEXEC);//创建epoll实例,并返回epoll的文件描述符

    if (epoll_fd == -1) {
        PLOG(ERROR) << "epoll_create1 failed";
        exit(1);
    }

    signal_handler_init();//主要是创建handler处理子进程终止信号,创建一个匿名socket并注册到epoll进行监听

3.1 epoll_create1

EPOLL类似于POLL,是Linux中用来做事件触发的,跟EventBus功能差不多

linux很长的时间都在使用select来做事件触发,它是通过轮询来处理的,轮询的fd数目越多,自然耗时越多,对于大量的描述符处理,EPOLL更有优势

epoll_create1是epoll_create的升级版,可以动态调整epoll实例中文件描述符的个数
EPOLL_CLOEXEC这个参数是为文件描述符添加O_CLOEXEC属性,参考http://blog.csdn.net/gqtcgq/article/details/48767691

3.2 signal_handler_init

定义在platform/system/core/init/signal_handler.cpp

这个函数主要的作用是注册SIGCHLD信号的处理函数

init是一个守护进程,为了防止init的子进程成为僵尸进程(zombie process),
需要init在子进程在结束时获取子进程的结束码,通过结束码将程序表中的子进程移除,
防止成为僵尸进程的子进程占用程序表的空间(程序表的空间达到上限时,系统就不能再启动新的进程了,会引起严重的系统问题)

在linux当中,父进程是通过捕捉SIGCHLD信号来得知子进程运行结束的情况,SIGCHLD信号会在子进程终止的时候发出,了解这些背景后,我们来看看init进程如何处理这个信号

首先,调用socketpair,这个方法会返回一对文件描述符,这样当一端写入时,另一端就能被通知到,
socketpair两端既可以写也可以读,这里只是单向的让s[0]写,s[1]读

然后,新建一个sigaction结构体,sa_handler是信号处理函数,指向SIGCHLD_handler,
SIGCHLD_handler做的事情就是往s[0]里写个"1",这样s1就会收到通知,SA_NOCLDSTOP表示只在子进程终止时处理,
因为子进程在暂停时也会发出SIGCHLD信号

sigaction(SIGCHLD, &act, 0) 这个是建立信号绑定关系,也就是说当监听到SIGCHLD信号时,由act这个sigaction结构体处理

ReapAnyOutstandingChildren 这个后文讲

最后,register_epoll_handler的作用就是signal_read_fd(之前的s[1])收到信号,触发handle_signal

终上所述,signal_handler_init函数的作用就是,接收到SIGCHLD信号时触发handle_signal

void signal_handler_init() {
    // Create a signalling mechanism for SIGCHLD.
    int s[2];
    if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0, s) == -1) { //创建socket并返回文件描述符
        PLOG(ERROR) << "socketpair failed";
        exit(1);
    }

    signal_write_fd = s[0];
    signal_read_fd = s[1];

    // Write to signal_write_fd if we catch SIGCHLD.
    struct sigaction act;
    memset(&act, 0, sizeof(act));
    act.sa_handler = SIGCHLD_handler; //act处理函数
    act.sa_flags = SA_NOCLDSTOP;
    sigaction(SIGCHLD, &act, 0);

    ServiceManager::GetInstance().ReapAnyOutstandingChildren();//具体处理子进程终止信号

    register_epoll_handler(signal_read_fd, handle_signal);//注册signal_read_fd到epoll中
}

3.3 handle_signal

定义在platform/system/core/init/signal_handler.cpp

首先清空signal_read_fd中的数据,然后调用ReapAnyOutstandingChildren,之前在signal_handler_init中调用过一次,
它其实是调用ReapOneProcess

static void handle_signal() {
    // Clear outstanding requests.
    char buf[32];
    read(signal_read_fd, buf, sizeof(buf));

    ServiceManager::GetInstance().ReapAnyOutstandingChildren();
}

3.4 ReapOneProcess

定义在platform/system/core/init/service.cpp

这是最终的处理函数了,这个函数先用waitpid找出挂掉进程的pid,然后根据pid找到对应Service,最后调用Service的Reap方法清除资源,根据进程对应的类型,决定是否重启机器或重启进程

bool ServiceManager::ReapOneProcess() {
    int status;
    pid_t pid = TEMP_FAILURE_RETRY(waitpid(-1, &status, WNOHANG));
    //用waitpid函数获取状态发生变化的子进程pid
    //waitpid的标记为WNOHANG,即非阻塞,返回为正值就说明有进程挂掉了

    if (pid == 0) {
        return false;
    } else if (pid == -1) {
        PLOG(ERROR) << "waitpid failed";
        return false;
    }

    Service* svc = FindServiceByPid(pid);//通过pid找到对应的Service

    std::string name;
    std::string wait_string;
    if (svc) {
        name = android::base::StringPrintf("Service '%s' (pid %d)",
                                           svc->name().c_str(), pid);
        if (svc->flags() & SVC_EXEC) {
            wait_string =
                android::base::StringPrintf(" waiting took %f seconds", exec_waiter_->duration_s());
        }
    } else {
        name = android::base::StringPrintf("Untracked pid %d", pid);
    }

    if (WIFEXITED(status)) {
        LOG(INFO) << name << " exited with status " << WEXITSTATUS(status) << wait_string;
    } else if (WIFSIGNALED(status)) {
        LOG(INFO) << name << " killed by signal " << WTERMSIG(status) << wait_string;
    } else if (WIFSTOPPED(status)) {
        LOG(INFO) << name << " stopped by signal " << WSTOPSIG(status) << wait_string;
    } else {
        LOG(INFO) << name << " state changed" << wait_string;
    }

    if (!svc) { //没有找到,说明已经结束了
        return true;
    }

    svc->Reap();//清除子进程相关的资源

    if (svc->flags() & SVC_EXEC) {
        exec_waiter_.reset();
    }
    if (svc->flags() & SVC_TEMPORARY) {
        RemoveService(*svc);
    }

    return true;
}

四、设置其他系统属性并开启系统属性服务

    property_load_boot_defaults();//从文件中加载一些属性,读取usb配置
    export_oem_lock_status();//设置ro.boot.flash.locked 属性
    start_property_service();//开启一个socket监听系统属性的设置
    set_usb_controller();//设置sys.usb.controller 属性

4.1 设置其他系统属性

property_load_boot_defaults,export_oem_lock_status,set_usb_controller这三个函数都是调用property_set设置一些系统属性

void property_load_boot_defaults() {
    if (!load_properties_from_file("/system/etc/prop.default", NULL)) { //从文件中读取属性
        // Try recovery path
        if (!load_properties_from_file("/prop.default", NULL)) {
            // Try legacy path
            load_properties_from_file("/default.prop", NULL);
        }
    }
    load_properties_from_file("/odm/default.prop", NULL);
    load_properties_from_file("/vendor/default.prop", NULL);

    update_sys_usb_config();
}

static void export_oem_lock_status() {
    if (!android::base::GetBoolProperty("ro.oem_unlock_supported", false)) {
        return;
    }

    std::string value = GetProperty("ro.boot.verifiedbootstate", "");

    if (!value.empty()) {
        property_set("ro.boot.flash.locked", value == "orange" ? "0" : "1");
    }
}

static void set_usb_controller() {
    std::unique_ptr<DIR, decltype(&closedir)>dir(opendir("/sys/class/udc"), closedir);
    if (!dir) return;

    dirent* dp;
    while ((dp = readdir(dir.get())) != nullptr) {
        if (dp->d_name[0] == '.') continue;

        property_set("sys.usb.controller", dp->d_name);
        break;
    }
}

4.2 start_property_service

定义在platform/system/core/init/property_service.cpp

之前我们看到通过property_set可以轻松设置系统属性,那干嘛这里还要启动一个属性服务呢?这里其实涉及到一些权限的问题,不是所有进程都可以随意修改任何的系统属性,
Android将属性的设置统一交由init进程管理,其他进程不能直接修改属性,而只能通知init进程来修改,而在这过程中,init进程可以进行权限控制,我们来看看这些是如何实现的

首先创建一个socket并返回文件描述符,然后设置最大并发数为8,其他进程可以通过这个socket通知init进程修改系统属性,
最后注册epoll事件,也就是当监听到property_set_fd改变时调用handle_property_set_fd

void start_property_service() {
    property_set("ro.property_service.version", "2");

    property_set_fd = create_socket(PROP_SERVICE_NAME, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK,
                                    0666, 0, 0, NULL);//创建socket用于通信
    if (property_set_fd == -1) {
        PLOG(ERROR) << "start_property_service socket creation failed";
        exit(1);
    }

    listen(property_set_fd, 8);//监听property_set_fd,设置最大并发数为8

    register_epoll_handler(property_set_fd, handle_property_set_fd);//注册epoll事件
}

4.3 handle_property_set_fd

定义在platform/system/core/init/property_service.cpp

这个函数主要作用是建立socket连接,然后从socket中读取操作信息,根据不同的操作类型,调用handle_property_set做具体的操作

static void handle_property_set_fd() {
    static constexpr uint32_t kDefaultSocketTimeout = 2000; /* ms */

    int s = accept4(property_set_fd, nullptr, nullptr, SOCK_CLOEXEC);//等待客户端连接
    if (s == -1) {
        return;
    }

    struct ucred cr;
    socklen_t cr_size = sizeof(cr);
    if (getsockopt(s, SOL_SOCKET, SO_PEERCRED, &cr, &cr_size) < 0) {//获取连接到此socket的进程的凭据
        close(s);
        PLOG(ERROR) << "sys_prop: unable to get SO_PEERCRED";
        return;
    }

    SocketConnection socket(s, cr);// 建立socket连接
    uint32_t timeout_ms = kDefaultSocketTimeout;

    uint32_t cmd = 0;
    if (!socket.RecvUint32(&cmd, &timeout_ms)) { //读取socket中的操作信息
        PLOG(ERROR) << "sys_prop: error while reading command from the socket";
        socket.SendUint32(PROP_ERROR_READ_CMD);
        return;
    }

    switch (cmd) { //根据操作信息,执行对应处理,两者区别一个是以char形式读取,一个以String形式读取
    case PROP_MSG_SETPROP: {
        char prop_name[PROP_NAME_MAX];
        char prop_value[PROP_VALUE_MAX];

        if (!socket.RecvChars(prop_name, PROP_NAME_MAX, &timeout_ms) ||
            !socket.RecvChars(prop_value, PROP_VALUE_MAX, &timeout_ms)) {
          PLOG(ERROR) << "sys_prop(PROP_MSG_SETPROP): error while reading name/value from the socket";
          return;
        }

        prop_name[PROP_NAME_MAX-1] = 0;
        prop_value[PROP_VALUE_MAX-1] = 0;

        handle_property_set(socket, prop_value, prop_value, true);
        break;
      }

    case PROP_MSG_SETPROP2: {
        std::string name;
        std::string value;
        if (!socket.RecvString(&name, &timeout_ms) ||
            !socket.RecvString(&value, &timeout_ms)) {
          PLOG(ERROR) << "sys_prop(PROP_MSG_SETPROP2): error while reading name/value from the socket";
          socket.SendUint32(PROP_ERROR_READ_DATA);
          return;
        }

        handle_property_set(socket, name, value, false);
        break;
      }

    default:
        LOG(ERROR) << "sys_prop: invalid command " << cmd;
        socket.SendUint32(PROP_ERROR_INVALID_CMD);
        break;
    }
}

4.4 handle_property_set

定义在platform/system/core/init/property_service.cpp

这就是最终的处理函数,以"ctl."开头的key就做一些Service的Start,Stop,Restart操作,其他的就是调用property_set进行属性设置,
不管是前者还是后者,都要进行SELinux安全性检查,只有该进程有操作权限才能执行相应操作

static void handle_property_set(SocketConnection& socket,
                                const std::string& name,
                                const std::string& value,
                                bool legacy_protocol) {
  const char* cmd_name = legacy_protocol ? "PROP_MSG_SETPROP" : "PROP_MSG_SETPROP2";
  if (!is_legal_property_name(name)) { //检查key的合法性
    LOG(ERROR) << "sys_prop(" << cmd_name << "): illegal property name \"" << name << "\"";
    socket.SendUint32(PROP_ERROR_INVALID_NAME);
    return;
  }

  struct ucred cr = socket.cred(); //获取操作进程的凭证
  char* source_ctx = nullptr;
  getpeercon(socket.socket(), &source_ctx);

  if (android::base::StartsWith(name, "ctl.")) { //如果以ctl.开头,就执行Service的一些控制操作
    if (check_control_mac_perms(value.c_str(), source_ctx, &cr)) {//SELinux安全检查,有权限才进行操作
      handle_control_message(name.c_str() + 4, value.c_str());
      if (!legacy_protocol) {
        socket.SendUint32(PROP_SUCCESS);
      }
    } else {
      LOG(ERROR) << "sys_prop(" << cmd_name << "): Unable to " << (name.c_str() + 4)
                 << " service ctl [" << value << "]"
                 << " uid:" << cr.uid
                 << " gid:" << cr.gid
                 << " pid:" << cr.pid;
      if (!legacy_protocol) {
        socket.SendUint32(PROP_ERROR_HANDLE_CONTROL_MESSAGE);
      }
    }
  } else { //其他的属性调用property_set进行设置
    if (check_mac_perms(name, source_ctx, &cr)) {//SELinux安全检查,有权限才进行操作
      uint32_t result = property_set(name, value);
      if (!legacy_protocol) {
        socket.SendUint32(result);
      }
    } else {
      LOG(ERROR) << "sys_prop(" << cmd_name << "): permission denied uid:" << cr.uid << " name:" << name;
      if (!legacy_protocol) {
        socket.SendUint32(PROP_ERROR_PERMISSION_DENIED);
      }
    }
  }

  freecon(source_ctx);
}

小结

init进程第二阶段主要工作是初始化属性系统,解析SELinux的匹配规则,处理子进程终止信号,启动系统属性服务,可以说每一项都很关键,如果说第一阶段是为属性系统,SELinux做准备,那么第二阶段就是真正去把这些落实的,下一篇我们将讲解.rc文件的解析

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.