Android7.0 init进程源码分析

Android 7.0 init进程详解
本文详细分析了Android 7.0系统中init进程的启动过程,包括创建文件系统目录、屏蔽标准输入输出、初始化内核log系统、属性域、SELinux、信号处理器等,以及解析init.rc文件和启动服务的流程。通过分析,揭示了init进程在Android系统中的重要作用和工作原理。

目的

看过一些blog和相关的书籍,大多数文章在介绍init进程时,参考的代码比较久远,同时不同文章行文的重点不太一样,因此决定自己试着来分析一下,并作相应的记录。

背景

当linux内核启动之后,运行的第一个进程是init,这个进程是一个守护进程,它的生命周期贯穿整个linux 内核运行的始终, linux中所有其它的进程的共同始祖均为init进程。
Android系统是运作在linux内核上的,为了启动并运行整个android系统,google实现了android系统的init进程。

Android init进程的入口文件在system/core/init/init.cpp中。在main函数中,按顺序主要进行了以下工作:

1、创建文件系统目录并挂载相关的文件系统

//清除屏蔽字(file mode creation mask),保证新建的目录的访问权限不受屏蔽字影响。
umask(0);

add_environment("PATH", _PATH_DEFPATH);

bool is_first_stage = (argc == 1) || (strcmp(argv[1], "--second-stage") != 0);

// Get the basic filesystem setup we need put together in the initramdisk
if (is_first_stage) {
	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));
	mount("sysfs", "/sys", "sysfs", 0, NULL);
}

如上所示,该部分主要用于创建和挂载启动所需的文件目录。
需要注意的是,在编译Android系统源码时,在生成的根文件系统中,并不存在这些目录,它们是系统运行时的目录,即当系统终止时,就会消失。

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

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内核引入的,它把连接在系统上的设备和总线组织成为一个分级的文件,使得它们可以在用户空间存取。

2、屏蔽标准的输入输出

open_devnull_stdio();

前文生成/dev目录后,init进程将调用open_devnull_stdio函数,屏蔽标准的输入输出。
open_devnull_stdio函数会在/dev目录下生成__null__设备节点文件,并将标准输入、标准输出、标准错误输出全部重定向到__null__设备中。

void open_devnull_stdio(void)
{
    // Try to avoid the mknod() call if we can. Since SELinux makes
    // a /dev/null replacement available for free, let's use it.
    int fd = open("/sys/fs/selinux/null", O_RDWR);
    if (fd == -1) {
        // OOPS, /sys/fs/selinux/null isn't available, likely because
        // /sys/fs/selinux isn't mounted. Fall back to mknod.
        static const char *name = "/dev/__null__";
        if (mknod(name, S_IFCHR | 0600, (1 << 8) | 3) == 0) {
            fd = open(name, O_RDWR);
            unlink(name);
        }
        if (fd == -1) {
            exit(1);
        }
    }

    dup2(fd, 0);
    dup2(fd, 1);
    dup2(fd, 2);
    if (fd > 2) {
        close(fd);
    }
}

open_devnull_stdio函数定义于system/core/init/util.cpp中。

这里需要说明的是,dup2函数的作用是用来复制一个文件的描述符,通常用来重定向进程的stdin、stdout和stderr。它的函数原形是:

int dup2(int oldfd, int targetfd)

该函数执行后,targetfd将变成oldfd的复制品。

因此上述过程其实就是:创建出__null__设备后,将0、1、2绑定到__null__设备上。因此init进程调用open_devnull_stdio函数后,通过标准的输入输出无法输出信息。

3、初始化内核log系统
我们继续回到init进程的main函数,init进程通过klog_init函数,提供输出log信息的设备。

klog_init();
klog_set_level(KLOG_NOTICE_LEVEL)
void klog_init(void) {
    if (klog_fd >= 0) return; /* Already initialized */

    klog_fd = open("/dev/kmsg", O_WRONLY | O_CLOEXEC);
    if (klog_fd >= 0) {
        return;
    }

    static const char* name = "/dev/__kmsg__";
    if (mknod(name, S_IFCHR | 0600, (1 << 8) | 11) == 0) {
        klog_fd = open(name, O_WRONLY | O_CLOEXEC);
        unlink(name);
    }
}

klog_init函数定义于system/core/libcutils/klog.c中。通过klog_init函数,init进程生成kmsg设备节点文件。该设备可以调用内核信息输出函数printk,以输出log信息。

4、初始化属性域

if (!is_first_stage) {
	.......
	property_init();
	.......
}

调用property_init初始化属性域。在Android平台中,为了让运行中的所有进程共享系统运行时所需要的各种设置值,系统开辟了属性存储区域,并提供了访问该区域的API。

这里存在一个问题是,在init进程中有部分代码块以is_first_stage标志进行区分,决定是否需要进行初始化。
is_first_stage的值,由init进程main函数的入口参数决定,之前不太明白具体的含义。
后来写博客后,有朋友留言,在引入selinux机制后,有些操作必须要在内核态才能完成;
但init进程作为android的第一个进程,又是运行在用户态的。
于是,最终设计为用is_first_stage进行区分init进程的运行状态。init进程在运行的过程中,会完成从内核态到用户态的切换。

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

property_init函数定义于system/core/init/property_service.cpp中,如上面代码所示,最终调用_system_property_area_init函数初始化属性域。

5、完成SELinux相关工作

// Set up SELinux, including loading the SELinux policy if we're in the kernel domain.
selinux_initialize(is_first_stage);

init进程进程调用selinux_initialize启动SELinux。从注释来看,init进程的运行确实是区分用户态和内核态的。

static void selinux_initialize(bool in_kernel_domain) {
    Timer t;

    selinux_callback cb;
    //用于打印log的回调函数
    cb.func_log = selinux_klog_callback;
    selinux_set_callback(SELINUX_CB_LOG, cb);
    //用于检查权限的回调函数
    cb.func_audit = audit_callback;
    selinux_set_callback(SELINUX_CB_AUDIT, cb);

    if (in_kernel_domain) {
        //内核态处理流程
        INFO("Loading SELinux policy...\n");
        //用于加载sepolicy文件。该函数最终将sepolicy文件传递给kernel,这样kernel就有了安全策略配置文件,后续的MAC才能开展起来。
        if (selinux_android_load_policy() < 0) {
            ERROR("failed to load policy: %s\n", strerror(errno));
            security_failure();
        }
		//内核中读取的信息
        bool kernel_enforcing = (security_getenforce() == 1);
        //命令行中得到的数据
        bool is_enforcing = selinux_is_enforcing();
        if (kernel_enforcing != is_enforcing) {
	        //用于设置selinux的工作模式。selinux有两种工作模式:
	        //1、”permissive”,所有的操作都被允许(即没有MAC),但是如果违反权限的话,会记录日志
	        //2、”enforcing”,所有操作都会进行权限检查。在一般的终端中,应该工作于enforing模式
	        if(security_setenforce(is_enforcing)) {
		        ........
		        //将重启进入recovery mode
		        security_failure();
	        }
		}
        if (write_file("/sys/fs/selinux/checkreqprot", "0") == -1) {
            security_failure();
        }

        NOTICE("(Initializing SELinux %s took %.2fs.)\n",
               is_enforcing ? "enforcing" : "non-enforcing", t.duration());
    } else {
        selinux_init_all_handles();
    }
}

6、重新设置属性

// If we're in the kernel domain, re-exec init to transition to the init domain now that the SELinux policy has been loaded.
if (is_first_stage) {
	//按selinux policy要求,重新设置init文件属性
	if (restorecon("/init") == -1) {
		ERROR("restorecon failed: %s\n", strerror(errno));
		security_failure();
	}
	char* path = argv[0];
	char* args[] = { path, const_cast<char*>("--second-stage"), nullptr };
	//这里就是前面所说的,启动用户态的init进程,即second-stage
	if (execv(path, args) == -1) {
		ERROR("execv(\"%s\") failed: %s\n", path, strerror(errno));
		security_failure();
	}
}

// These directories were necessarily created before initial policy load
// and therefore need their security context restored to the proper value.
// This must happen before /dev is populated by ueventd.
INFO("Running restorecon...\n");
restorecon("/dev");
restorecon("/dev/socket");
restorecon("/dev/__properties__");
restorecon_recursive("/sys");

上述文件节点在加载Sepolicy之前已经被创建了,因此在加载完Sepolicy后,需要重新设置相关的属性。

7、创建epoll句柄
如下面代码所示,init进程调用epoll_create1创建epoll句柄。

epoll_fd = epoll_create1(EPOLL_CLOEXEC);
if (epoll_fd == -1) {
	ERROR("epoll_create1 failed: %s\n", strerror(errno));
	exit(1);
}

在linux的网络编程中,很长的时间都在使用select来做事件触发。在linux新的内核中,有了一种替换它的机制,就是epoll。相比于select,epoll最大的好处在于它不会随着监听fd数目的增长而降低效率。因为在内核中的select实现中,它是采用轮询来处理的,轮询的fd数目越多,自然耗时越多。

<
<think>嗯,用户想要一个Android系统构建系统的学习计划,我得好好梳理一下。用户提供的引用内容很有帮助,从Android构建系统的发展历程到当前使用的工具都有涵盖。比如引用[2]提到早期用Makefile(Android.mk),后来因为性能问题转向Soong;引用[4]详细说明了Soong引入的时间和特点;而引用[3]则强调了Gradle在应用层面的重要性。这些都需要整合进学习路径里。 首先得明确学习的分层结构。构建系统在Android里是分层的:底层是Soong和Ninja,中间层有Kati转换Makefile,上层则是开发者熟悉的Gradle。用户的引用材料正好对应了这几个层面,可以分阶段设计。 第一阶段应该打基础,重点讲清楚演进历史。根据引用[2]和[4],必须解释为什么Google要弃用Makefile——性能瓶颈和大项目管理困难,这时候引入Soong(Android 7.0)和Blueprint语法就很自然了。练习部分可以让用户实际创建Android.bp文件,对比新旧配置差异。 第二阶段深入Soong机制。引用[4]提到Blueprint的JSON风格语法,这里要详细展开模块定义和依赖关系写法。同时引用[1]里张大伟的经验可能隐含了系统定制时的构建修改实践,可以加入裁剪AOSP的例子,模拟真实工作场景。调试方面得教用户用m/mmm命令和ninja文件分析技巧。 第三阶段聚焦Gradle。引用[3]强调它对开发效率的重要性,这部分需要区分Gradle在应用层和系统层的不同作用:应用层用Android Plugin管理构建,系统层用Gradle驱动整体编译。变体处理和多模块管理是关键,用户可能混淆这里的层次关系。 最后项目实践设计要覆盖全流程。从AOSP下载到自定义模块开发,再到产物分析,确保用户能看到从代码到镜像的完整链条。调试构建错误作为独立环节很重要——毕竟实际开发中最头疼的就是构建失败。 学习资源方面,"Android构建系统官方文档"和《深入理解Android架构》是核心,引用[1]的《Android系统源代码情景分析》作为补充也很合适。另外引用材料没提到的CTS验证环节也得补充,这是产品化必经步骤。 用户可能是中高级开发者,想要转向系统开发领域。所以计划里加入了系统定制和优化内容,比如裁剪AOSP和构建加速,这比单纯的应用构建更有深度。时间安排上给出弹性区间比较合理,毕竟每个人基础不同。 要注意避免知识断层。比如在讲Soong之前得铺垫Makefile的缺点,引用[2]正好提供了这点。另外所有技术名词如Kati/Ninja都需要首次出现时解释,防止用户迷失在术语里。 </think>基于Android构建系统的技术演进和分层架构,以下是为期8-12周的系统性学习计划,融合底层机制与上层工具链: ### 第一阶段:构建系统基础(1-2周) 1. **历史演进与核心问题** - 学习传统Makefile(Android.mk)的局限性:性能瓶颈、依赖管理复杂(引用[2]) - 理解Soong引入的必要性:面向大型项目的编译加速(引用[4]) - 关键组件关系图: ``` Gradle → Soong → Blueprint → Ninja ↑ Kati(兼容Android.mk) ``` 2. **环境搭建与实践** ```bash # 初始化AOSP环境 repo init -u https://android.googlesource.com/platform/manifest # 编译验证基础环境 source build/envsetup.sh && lunch aosp_x86_64-eng make -j8 # 触发传统Make流程 ``` ### 第二阶段:Soong构建系统深度(3-4周) 1. **Blueprint语法精要** - Android.bp文件编写规范(对比Android.mk): ```python cc_binary { name: "my_binary", srcs: ["src/*.cpp"], shared_libs: ["libutils"], cflags: ["-DDEBUG"] } ``` 2. **Soong工作机制** - 源码分析路径:`build/soong/`(Go语言实现) - 模块依赖解析:`bpfmt`工具验证BP文件有效性 - 与Ninja的协作:`out/soong/build.ninja`生成机制 3. **混合构建实践** - Android.mk向Android.bp迁移策略 - 使用`androidmk`工具转换: ```bash androidmk Android.mk > Android.bp ``` ### 第三阶段:Gradle与产品化构建(2-3周) 1. **Gradle的多层角色** - 应用层:`com.android.application`插件管理APK构建 - 系统层:`AOSP/build.gradle`驱动全系统编译 - 定制技巧:构建变体(product flavors)与多模块管理 2. **性能优化实战** - 增量编译:`--profile`生成构建报告 - 缓存优化:配置Gradle守护进程 ```gradle // gradle.properties org.gradle.daemon=true org.gradle.caching=true ``` ### 第四阶段:高级主题与项目实战(2-3周) 1. **定制化构建开发** - 添加新设备支持:`device/{vendor}/{product}` - 覆盖模块默认行为: ```python // Android.bp override cc_defaults { name: "my_overrides", cflags: ["-O3"], } ``` 2. **构建问题调试** - 关键日志路径:`out/build_{product}.log` - 依赖可视化:`ninja deps | dot -Tpng > deps.png` 3. **持续集成实践** - AOSP云编译环境配置(Jenkins/Buildkite) - 构建缓存服务器部署 ### 学习资源 1. **官方文档** - [Android构建系统架构](https://source.android.com/docs/setup/build) - [Android.bp语言参考](https://ci.android.com/builds/latest/branches/aosp-build-tools/targets/ack_blueprint/linux/latest/view) 2. **推荐书籍** - 《深入理解Android架构》第5章(关联引用[1]) - 《Gradle权威指南》(补充引用[3]) 3. **实践项目** - 裁剪AOSP基础系统(<500MB) - 为新硬件平台添加构建支持 - 实现构建速度优化50%+ > **关键学习要点**:理解构建系统分层(上图)、掌握从Make到Soong的演进本质(引用[2][4])、区分Gradle在应用与系统开发中的不同角色(引用[3])。建议每周投入15-20小时,重点分析`build/soong`源码实现。
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值