安卓系列教程之ROM系统开发-百问100ask
系统:Android10.0
设备: FireFly RK3399 (ROC-RK3399-PC-PLUS)
本文通过代码梳理的方式,给大家介绍Android init祖先进程第一阶段的工作流程。
第一阶段整理框架:执行在boot.img(ramdisk)中,主要创建必须的文件夹,挂载虚拟文件系统,挂载system,vendor分区。
代码如下:system/core/init/first_stage_init.cpp
int FirstStageMain(int argc, char** argv) {
if (REBOOT_BOOTLOADER_ON_PANIC) {
InstallRebootSignalHandlers();
}
boot_clock::time_point start_time = boot_clock::now();
std::vector> errors;
#define CHECKCALL(x) \
if (x != 0) errors.emplace_back(#x " failed", errno);
// Clear the umask.
umask(0);
CHECKCALL(clearenv());
CHECKCALL(setenv("PATH", _PATH_DEFPATH, 1));
// 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.
//将tmpfs文件系统挂载到dev目录
CHECKCALL(mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755"));
// 创建dev/pts目录 :是远程登陆(telnet,ssh等)后创建的控制台设备文件所在的目录
CHECKCALL(mkdir("/dev/pts", 0755));
//创建dev/socket目录,rc脚本中service启动时,会创建很多域套接字在此目录
CHECKCALL(mkdir("/dev/socket", 0755));
CHECKCALL(mount("devpts", "/dev/pts", "devpts", 0, NULL));
#define MAKE_STR(x) __STRING(x)
CHECKCALL(mount("proc", "/proc", "proc", 0, "hidepid=2,gid=" MAKE_STR(AID_READPROC)));
#undef MAKE_STR
// Don't expose the raw commandline to unprivileged processes.
CHECKCALL(chmod("/proc/cmdline", 0440));
gid_t groups[] = {AID_READPROC};
CHECKCALL(setgroups(arraysize(groups), groups));
//将sysfs文件系统挂载到sys目录,用来访问内核和驱动的信息
CHECKCALL(mount("sysfs", "/sys", "sysfs", 0, NULL));
//将selinuxfs文件系统挂载到目录/sys/fs/selinux ,通过该路径可以完成和内核selinux模块交互
CHECKCALL(mount("selinuxfs", "/sys/fs/selinux", "selinuxfs", 0, NULL));
//创建/dev/kmsg设备节点,用于用户空间写入日志到内核日志缓冲区中
CHECKCALL(mknod("/dev/kmsg", S_IFCHR | 0600, makedev(1, 11)));
if constexpr (WORLD_WRITABLE_KMSG) {
CHECKCALL(mknod("/dev/kmsg_debug", S_IFCHR | 0622, makedev(1, 11)));
}
//节点/dev/random和/dev/urandom是Linux系统中提供的随机伪设备,
//这两个设备的任务,是提供永不为空的随机字节数据流。
//很多解密程序与安全应用程序(如SSH Keys,SSL Keys等)需要它们提供的随机数据流。
CHECKCALL(mknod("/dev/random", S_IFCHR | 0666, makedev(1, 8)));
CHECKCALL(mknod("/dev/urandom", S_IFCHR | 0666, makedev(1, 9)));
// This is needed for log wrapper, which gets called before ueventd runs.
// 创建伪终端
CHECKCALL(mknod("/dev/ptmx", S_IFCHR | 0666, makedev(5, 2)));
// 创建空设备
CHECKCALL(mknod("/dev/null", S_IFCHR | 0666, makedev(1, 3)));
// These below mounts are done in first stage init so that first stage mount can mount
// subdirectories of /mnt/{vendor,product}/. Other mounts, not required by first stage mount,
// should be done in rc files.
// Mount staging areas for devices managed by vold
// See storage config details at http://source.android.com/devices/storage/
//将tmpfs文件系统到mnt目录,这个目录正常是挂载光驱,usb设备的
CHECKCALL(mount("tmpfs", "/mnt", "tmpfs", MS_NOEXEC | MS_NOSUID | MS_NODEV,
"mode=0755,uid=0,gid=1000"));
// /mnt/vendor is used to mount vendor-specific partitions that can not be
// part of the vendor partition, e.g. because they are mounted read-write.
CHECKCALL(mkdir("/mnt/vendor", 0755));
// /mnt/product is used to mount product-specific partitions that can not be
// part of the product partition, e.g. because they are mounted read-write.
CHECKCALL(mkdir("/mnt/product", 0755));
// /apex is used to mount APEXes
CHECKCALL(mount("tmpfs", "/apex", "tmpfs", MS_NOEXEC | MS_NOSUID | MS_NODEV,
"mode=0755,uid=0,gid=0"));
// /debug_ramdisk is used to preserve additional files from the debug ramdisk
CHECKCALL(mount("tmpfs", "/debug_ramdisk", "tmpfs", MS_NOEXEC | MS_NOSUID | MS_NODEV,
"mode=0755,uid=0,gid=0"));
#undef CHECKCALL
//把标准输出,标准输入,标准错误输出重定向到dev/null
SetStdioToDevNull(argv);
// Now that tmpfs is mounted on /dev and we have /dev/kmsg, we can actually
// talk to the outside world...
//初始化init log并输出定向到dev/kmsg
InitKernelLogging(argv);
if (!errors.empty()) {
for (const auto& [error_string, error_errno] : errors) {
LOG(ERROR) << error_string << " " << strerror(error_errno);
}
LOG(FATAL) << "Init encountered errors starting first stage, aborting";
}
//打印第一阶段的标志性日志
LOG(INFO) << "init first stage started!";
auto old_root_dir = std::unique_ptr{opendir("/"), closedir};
if (!old_root_dir) {
PLOG(ERROR) << "Could not opendir(\"/\"), not freeing ramdisk";
}
struct stat old_root_info;
if (stat("/", &old_root_info) != 0) {
PLOG(ERROR) << "Could not stat(\"/\"), not freeing ramdisk";
old_root_dir.reset();
}
//对于将恢复用作 ramdisk 的设备,第一阶段 init 位于恢复 ramdisk 中的 /init。这些设备首先将根切换到
//first_stage_ramdisk,以便从环境中移除恢复组件,然后执行与具有 boot-ramdisk 的设备一样的操作
//(即,将 system.img 作为 /system 进行装载,切换根以将该装载移动到 /,然后在装载完成后释放 ramdisk 内容)。
//如果内核命令行中存在 androidboot.force_normal_boot=1,则设备会正常启动(启动到 Android)而不是启动到恢复模式。
//介绍:https://source.android.google.cn/devices/bootloader/system-as-root?hl=zh-cn#ramdisk
if (ForceNormalBoot()) {
mkdir("/first_stage_ramdisk", 0755);
// SwitchRoot() must be called with a mount point as the target, so we bind mount the
// target directory to itself here.
if (mount("/first_stage_ramdisk", "/first_stage_ramdisk", nullptr, MS_BIND, nullptr) != 0) {
LOG(FATAL) << "Could not bind mount /first_stage_ramdisk to itself";
}
SwitchRoot("/first_stage_ramdisk");
}
// If this file is present, the second-stage init will use a userdebug sepolicy
// and load adb_debug.prop to allow adb root, if the device is unlocked.
if (access("/force_debuggable", F_OK) == 0) {
std::error_code ec; // to invoke the overloaded copy_file() that won't throw.
if (!fs::copy_file("/adb_debug.prop", kDebugRamdiskProp, ec) ||
!fs::copy_file("/userdebug_plat_sepolicy.cil", kDebugRamdiskSEPolicy, ec)) {
LOG(ERROR) << "Failed to setup debug ramdisk";
} else {
// setenv for second-stage init to read above kDebugRamdisk* files.
setenv("INIT_FORCE_DEBUGGABLE", "true", 1);
}
}
// 读取/vendor/etc/fstab.${ro.hardware}的挂载表格进行挂载,主要是挂载system,vendor,odm等镜像分区。
//在初始化 SElinux 之前必须先装载 /system、/vendor 或 /odm,这个主要是因为打开了Treble的设备上,
//为了确保init能及时导入SELinux的配置文件(contexts/*.te),需要尽快的将system/vendor等分区挂载上。
//在Android 8.0之前,selinux的配置文件存放在boot.img(包含ramdisk)中,在内核初始化过程中,
//boot.img中的ramdisk已经挂载到rootfs了,相应的,配置文件也就可以从rootfs读取到。
//而Android 8.0开始,selinux配置文件放到了vendor/system分区了
if (!DoFirstStageMount()) {
LOG(FATAL) << "Failed to mount required partitions early ...";
}
//根目录发生变化,则释放ramdisk
struct stat new_root_info;
if (stat("/", &new_root_info) != 0) {
PLOG(ERROR) << "Could not stat(\"/\"), not freeing ramdisk";
old_root_dir.reset();
}
if (old_root_dir && old_root_info.st_dev != new_root_info.st_dev) {
FreeRamdisk(old_root_dir.get(), old_root_info.st_dev);
}
//Avb即Android Verfied boot,功能包括Secure Boot, verfying boot 和 dm-verity,
//原理都是对二进制文件进行签名,在系统启动时进行认证,确保系统运行的是合法的二进制镜像文件。
//其中认证的范围涵盖:bootloader,boot.img,system.img
//此处是在recovery模式下初始化avb的版本,不是recovery模式直接跳过
SetInitAvbVersionInRecovery();
static constexpr uint32_t kNanosecondsPerMillisecond = 1e6;
uint64_t start_ms = start_time.time_since_epoch().count() / kNanosecondsPerMillisecond;
setenv("INIT_STARTED_AT", std::to_string(start_ms).c_str(), 1);
const char* path = "/system/bin/init";
const char* args[] = {path, "selinux_setup", nullptr};
execv(path, const_cast(args));
// execv() only returns if an error happened, in which case we
// panic and never fall through this conditional.
PLOG(FATAL) << "execv(\"" << path << "\") failed";
return 1;
}
重要的虚拟文件系统说明:
tmpfs |
虚拟文件系统,tmpfs 是有内核直接管理的的,存在于内存,无需格式化,直接使用,并且是单一的内存到内存的访问,速度快,掉电丢失。可以直接申请一块内存进行挂载,如: mount -t tmpfs -o size=2M tmpfs /tmp, 在linux(android系统)中,/dev, /apex,/debug_ramdisk常被用来作为挂载点。 |
sysfs |
虚拟文件系统,也是存在于内存,这是内核为用户空间提供访问内核空间的一种接口,并且在该文件系统中,将设备和总线,模块相关信息组织起来,供用户查看,如block、bus、class、dev、devices、firmware、fs、kernel、module、power,一般挂载到/sys目录, |
proc |
虚拟文件系统,也是存在于内存,主要记录系统信息,如cpuinfo, meminfo, interrupt, cmdline,filesystems等信息。一般挂载到/proc目录, |
selinxfs |
虚拟文件系统,也是存在于内存,selinuxfs中提供了用户空间和内核安全模块的进行交互的接口, 如enforce就是用来配置selinux模式的节点。load节点用加载selinux策略二进制文件并写入内核,一般挂载到/sys/fs/selinux目录, |
devpts |
伪终端文件系统,用于模拟终端程序,一般挂载到/dev/pts目录,远程登陆(telnet,ssh等)后创建的控制台设备文件就在/dev/pts目录,该目录里面的文件是动态生成。 |
android fstab文件格式参考说明:
https://blog.csdn.net/baidu_40808339/article/details/108489583
其中rk3399 的如下:
qh100_rk3399:/ $ cat /vendor/etc/fstab.rk30board
# Android fstab file.
#
# The filesystem that contains the filesystem checker binary (typically /system) cannot
# specify MF_CHECK, and must come before any filesystems that do specify MF_CHECK
system /system ext4 ro,barrier=1 wait,logical,first_stage_mount
vendor /vendor ext4 ro,barrier=1 wait,logical,first_stage_mount
odm /odm ext4 ro,barrier=1 wait,logical,first_stage_mount
product /product ext4 ro,barrier=1 wait,logical,first_stage_mount
/dev/block/by-name/metadata /metadata ext4 nodev,noatime,nosuid,discard,sync wait,formattable,first_stage_mount
/dev/block/by-name/misc /misc emmc defaults defaults
/dev/block/by-name/cache /cache ext4 noatime,nodiratime,nosuid,nodev,noauto_da_alloc,discard wait,check
/devices/platform/*usb* auto vfat defaults voldmanaged=usb:auto
/dev/block/zram0 none swap defaults
zramsize=50%
# For sdmmc
/devices/platform/fe320000.dwmmc/mmc_host* auto auto defaults voldmanaged=sdcard1:auto,encryptable=userdata
# Full disk encryption has less effect on rk3326, so default to enable this.
#/dev/block/by-name/userdata /data f2fs noatime,nosuid,nodev,discard,reserve_root=32768,resgid=1065,fsync_mode=nobarrier latemount,wait,check,fileencryption=software,quota,formattable,reservedsize=128M,checkpoint=fs
# for ext4
/dev/block/by-name/userdata /data ext4 discard,noatime,nosuid,nodev,noauto_da_alloc,data=ordered,user_xattr,barrier=1 wait,formattable,check,fileencryption=software,quota,reservedsize=128M