Android 12 S 系统开机流程分析-FirstStageMain(一)

开机有好几种方式启动,本文主要讲的是按Power键开机流程。

本文参考AOSP 12原生代码,链接为:AOSP 12 Searchicon-default.png?t=N7T8http://aospxref.com/android-12.0.0_r3/

目录

1. BootLoader加载

2. kernel启动

3. init进程启动

3.1 FirstStageMain

3.1.1 init若崩溃会重新启动BootLoader

3.1.2 创建以及挂载文件系统

3.1.3 kernel log初始化

3.1.4 加载/lib/moudles/下的ko文件, 打开串口log

3.1.5 拷贝ramdisk prop文件,创建new ramdisk, 删除old ramdisk

3.1.6 重新执行init进程并携带参数selinux_setup


1. BootLoader加载

当按下设备电源键时,最先运行的就是 bootloader(固化在ROM的程序),bootloader 的主要作用就是硬件设备(如 CPU、flash、内存)的初始化并加载到RAM,通过建立内存空间映射,为装载 Linux 内核做好准备,。如果 bootloader 在运行期间,按下预定义的组合按键,可以进入系统fastboot模式 或者 Receiver 模式。

1)当用户按下开机键时,引导芯片代码开始从预定义的地方(固定在ROM中)开始执行,加载BootLoader到内存中执行。

2)BootLoader是在操作系统内核运行之前运行的一段小程序,通过这段小程序初始化硬件设备、建立内存空间映射图,从而将系统的软硬件环境带到一个合适状态,以便为最终调用操作系统内核准备好正确的环境,最终目标是将系统OS拉起并运行。

3)整个系统的加载任务都是由BootLoader完成的。

2. kernel启动

在编译完AOSP时会生成boot.img或者boot_debug.img,该镜像就是 Linux 内核和根文件系统,bootloader 会把该镜像装载到内存中,然后 linux 内核会执行整个系统的初始化,完成后装载根文件系统,最后启动 init 进程。

1)当内核启动时,设置缓存、被保护存储器、计划列表、加载驱动。

2)在内核完成系统设置后,它首先在系统文件中寻找init.rc,并启动init进程

kernel会去启动init,启动log如下

01-05 23:45:42.396     1     1 I         : Run /init as init process
01-05 23:45:42.396     1     1 D with arguments:  
01-05 23:45:42.396     1     1 D         : /init
01-05 23:45:42.396     1     1 D with environment:  
01-05 23:45:42.396     1     1 D         : HOME=/
01-05 23:45:42.396     1     1 D         : TERM=linux

3. init进程启动

init是被kernel拉起来的。

init进程是Android系统启动后,由内核启动的第一个用户级进程,init的进程号为1.

Android中所有进程都是由init进程创建并运行的。

init启动主要有三大步骤,分别为

    1. FirstStageMain(第一阶段):主要挂载基本的文件系统,挂载特定分区等

    2. SetupSelinux(Selinux配置阶段)

    3. SecondStageMain(第二阶段)

如果抓一份开机的log,会发现有如下log输出。分别对应各个步骤的log,且可以看到pid为1。

01-05 23:45:42.397     1     1 I init    : init first stage started!
01-05 23:45:42.812     1     1 I init    : Opening SELinux policy
01-05 23:45:42.819     1     1 I init    : Loading SELinux policy
01-05 23:45:42.943     1     1 I init    : init second stage started!

init相关代码目录为:

/system/core/init

init进程启动后,首先会走它的main方法,首先会设置init进程优先级为最大,再根据携带的参数决定走哪个方法,由于kernel启动的时候没有携带参数,所以argc是1。

代码如下

/system/core/init/main.cpp
int main(int argc, char** argv) {
#if __has_feature(address_sanitizer)
    __asan_set_error_report_callback(AsanReportCallback);
#endif
//setpriority(int which, int who, int prio);
//prio范围为:-20~20,prio越小,优先级越大。
//which为PRIO_PROCESS时,含义为设置进程号为who的优先级为prio
//设置init进程的优先级为最高优先级
    setpriority(PRIO_PROCESS, 0, -20);
    if (!strcmp(basename(argv[0]), "ueventd")) {
        return ueventd_main(argc, argv);
    }

    if (argc > 1) {
        if (!strcmp(argv[1], "subcontext")) {
            android::base::InitLogging(argv, &android::base::KernelLogger);
            const BuiltinFunctionMap& function_map = GetBuiltinFunctionMap();

            return SubcontextMain(argc, argv, &function_map);
        }

        if (!strcmp(argv[1], "selinux_setup")) {
            return SetupSelinux(argv);
        }

        if (!strcmp(argv[1], "second_stage")) {
            return SecondStageMain(argc, argv);
        }
    }
//由于kernel启动init的时候没有携带任何参数,所以会首先走入此方法
    return FirstStageMain(argc, argv);
}

3.1 FirstStageMain

接着讲FirstStageMain函数具体做了什么。

由于执行的步骤太多,下面一步步来分析具体做了什么

3.1.1 init若崩溃会重新启动BootLoader

当init崩溃时,为了防止内核进入panic状态,会重新启动BootLoader。

int FirstStageMain(int argc, char** argv) {
//1
    if (REBOOT_BOOTLOADER_ON_PANIC) {
        InstallRebootSignalHandlers();
    }

1)InstallRebootSignalHandlers

InstallRebootSignalHandlers中主要做了以下几件事:

1. 初始化了一个自定义信号集,将其所有信号都填充满,即将信号集中的所有的标志位都置为1,使得这个集合包含所有可接受的信号,也就是阻塞所有信号。这个函数可以用于快速创建一个包含所有信号的信号集,然后可以根据需要删除其中的某些信号。

2. init创建出来的子进程不做处理,直接exit;如果不是子进程,则代表是init进程,则执行InitFatalReboot

3. 通过syscall向内核发送重启命令

4. 捕获一些信号

void InstallRebootSignalHandlers() {
    struct sigaction action;
    memset(&action, 0, sizeof(action));
//用于初始化一个自定义信号集,将其所有信号都填充满,也就是将信号集中的所有的
//标志位置为1,使得这个集合包含所有可接受的信号
//这个函数可以用于快速创建一个包含所有信号的信号集,然后可以根据需要删除其中的某些信号。
    sigfillset(&action.sa_mask);
    action.sa_handler = [](int signal) {
//从init派生的进程也会捕获这些信号处理程序,但是我们不希望它们触发重新启动,
//所以我们在这里直接为子进程调用_exit()。
        if (getpid() != 1) {
            _exit(signal);
        }

//调用DoReboot()或LOG(FATAL)不是一个好的选择,因为这是一个信号处理程序。
//RebootSystem使用syscall()
        InitFatalReboot(signal);
    };
    action.sa_flags = SA_RESTART;
//sigaction(int signum, const struct sigaction *act,struct sigaction *oldact)
//参数1:要捕获的信号
//参数2:接收到信号之后对信号进行处理的结构体
//参数3:接收到信号之后,保存原来对此信号处理的各种方式与信号(可用来做备份)。如果不需要备份,此处可以填NULL
    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);
}

2)InitFatalReboot

InitFatalReboot做了以下几件事:

1. 判断init是否可以创建子进程,如果不能创建,则直接重启;如果能创建,则sleep 5ms,再重启子进程。

2. 如果是init进程,则获取异常的backtrace。

3. 如果init进程被标记为异常了,则往/proc/sysrq-trigger写c,让系统陷入崩溃,然后直接退出。

echo b > /proc/sysrq-trigger    立即重启
echo o > /proc/sysrq-trigger    立即关机
echo c > /proc/sysrq-trigger    立即让系统崩溃
echo t > /proc/sysrq-trigger    导出线程状态信息
echo u > /proc/sysrq-trigger    立即重新挂载所有的文件系统为只读

4.如果init没有被标记为异常,则重启init进程。

void __attribute__((noreturn)) InitFatalReboot(int signal_number) {
    auto pid = fork();//创建子进程,成功0,失败-1

    if (pid == -1) {
        //不能创建子进程,不用尝试获取backtrace,直接重启
        RebootSystem(ANDROID_RB_RESTART2, init_fatal_reboot_target);
    } else if (pid == 0) {
        // 可以创建子进程,说明当前在子进程上,并且子进程需确保能重启
        sleep(5);
        //子进程重启
        RebootSystem(ANDROID_RB_RESTART2, init_fatal_reboot_target);
    }

   //尝试获取init的backtrace再关机重启
    LOG(ERROR) << __FUNCTION__ << ": signal " << signal_number;
    std::unique_ptr<Backtrace> backtrace(
            Backtrace::Create(BACKTRACE_CURRENT_PROCESS, BACKTRACE_CURRENT_THREAD));
    if (!backtrace->Unwind(0)) {
        LOG(ERROR) << __FUNCTION__ << ": Failed to unwind callstack.";
    }
    for (size_t i = 0; i < backtrace->NumFrames(); i++) {
        LOG(ERROR) << backtrace->FormatFrameData(i);
    }
//判断init是否被标记为异常
//异常时:/proc/cmdline中有节点androidboot.init_fatal_panic,并且其值为true
    if (init_fatal_panic) {
        LOG(ERROR) << __FUNCTION__ << ": Trigger crash";
//往/proc/sysrq-trigger写c
        android::base::WriteStringToFile("c", PROC_SYSRQ);
        LOG(ERROR) << __FUNCTION__ << ": Sys-Rq failed to crash the system; fallback to exit().";
        _exit(signal_number);
    }
//init重启
    RebootSystem(ANDROID_RB_RESTART2, init_fatal_reboot_target);
}

3)RebootSystem

    走到这个函数,说明就要发送重启命令了,是通过syscall去发送的。

    在用户空间和内核空间之间,通过syscall(系统调用, system call)的中间层来通信,连接用户态和内核态的桥梁。用户空间通过向内核空间发出Syscall,产生软中断,从而让程序陷入内核态,执行相应的操作。

RebootSystem如下:

void __attribute__((noreturn)) RebootSystem(unsigned int cmd, const std::string& rebootTarget) {
    LOG(INFO) << "Reboot ending, jumping to kernel";

    if (!IsRebootCapable()) {
        // On systems where init does not have the capability of rebooting the
        // device, just exit cleanly.
        exit(0);
    }

    switch (cmd) {
        case ANDROID_RB_POWEROFF:
            reboot(RB_POWER_OFF);
            break;
//前面传递的cmd是ANDROID_RB_RESTART2,发送重启命令
//Syscall是连接用户态和内核态的桥梁。
//用户空间通过向内核空间发出Syscall,产生软中断,从而让程序陷入内核态,执行相应的操作
        case ANDROID_RB_RESTART2:
            syscall(__NR_reboot, LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2,
                    LINUX_REBOOT_CMD_RESTART2, rebootTarget.c_str());
            break;

        case ANDROID_RB_THERMOFF:
            if (android::base::GetBoolProperty("ro.thermal_warmreset", false)) {
                LOG(INFO) << "Try to trigger a warm reset for thermal shutdown";
                static constexpr const char kThermalShutdownTarget[] = "shutdown,thermal";
                syscall(__NR_reboot, LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2,
                        LINUX_REBOOT_CMD_RESTART2, kThermalShutdownTarget);
            } else {
                reboot(RB_POWER_OFF);
            }
            break;
    }
    // In normal case, reboot should not return.
    PLOG(ERROR) << "reboot call returned";
    abort();
}

3.1.2 创建以及挂载文件系统

第二步,主要是创建和挂载文件系统

//设置允许当前进程创建文件或者目录最大可操作的权限
    umask(0);

//清楚环境变量
    CHECKCALL(clearenv());
//设置环境变量
    CHECKCALL(setenv("PATH", _PATH_DEFPATH, 1));
//设置/dev为tmpfs类型,并挂载,且权限为0755,本进程可读可写可执行,本组和其他组只能读写
    CHECKCALL(mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755"));
    CHECKCALL(mkdir("/dev/pts", 0755));
    CHECKCALL(mkdir("/dev/socket", 0755));
    CHECKCALL(mkdir("/dev/dm-user", 0755));
//设置/dev/pts为devpts类型,并挂载
    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
// 读取操作系统的启动参数
    CHECKCALL(chmod("/proc/cmdline", 0440));
    std::string cmdline;
    android::base::ReadFileToString("/proc/cmdline", &cmdline);
    // Don't expose the raw bootconfig to unprivileged processes.
    chmod("/proc/bootconfig", 0440);
    std::string bootconfig;
    android::base::ReadFileToString("/proc/bootconfig", &bootconfig);
    gid_t groups[] = {AID_READPROC};
    CHECKCALL(setgroups(arraysize(groups), groups));
    CHECKCALL(mount("sysfs", "/sys", "sysfs", 0, NULL));
    CHECKCALL(mount("selinuxfs", "/sys/fs/selinux", "selinuxfs", 0, NULL));
//创建kernel log节点
    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)));
    }
//随机数
    CHECKCALL(mknod("/dev/random", S_IFCHR | 0666, makedev(1, 8)));
    CHECKCALL(mknod("/dev/urandom", S_IFCHR | 0666, makedev(1, 9)));

    CHECKCALL(mknod("/dev/ptmx", S_IFCHR | 0666, makedev(5, 2)));
    CHECKCALL(mknod("/dev/null", S_IFCHR | 0666, makedev(1, 3)));

    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));

    // /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"));

    // /second_stage_resources is used to preserve files from first to second
    // stage init
    CHECKCALL(mount("tmpfs", kSecondStageRes, "tmpfs", MS_NOEXEC | MS_NOSUID | MS_NODEV,
                    "mode=0755,uid=0,gid=0"))
#undef CHECKCALL

查看机器的mount挂载详情表:   adb shell mount

可以看到有很多种文件类型。

$:/ # mount
tmpfs on /dev type tmpfs (rw,seclabel,nosuid,relatime,size=7984680k,nr_inodes=1996170,mode=755)
devpts on /dev/pts type devpts (rw,seclabel,relatime,mode=600,ptmxmode=000)
proc on /proc type proc (rw,relatime,gid=3009,hidepid=invisible)
sysfs on /sys type sysfs (rw,seclabel,relatime)
selinuxfs on /sys/fs/selinux type selinuxfs (rw,relatime)
tmpfs on /mnt type tmpfs (rw,seclabel,nosuid,nodev,noexec,relatime,size=7984680k,nr_inodes=1996170,mode=755,gid=1000)
/dev/block/sda9 on /metadata type ext4 (rw,seclabel,nosuid,nodev,noatime,discard)
/dev/block/dm-2 on / type ext4 (ro,seclabel,nodev,relatime,discard)
/dev/block/dm-3 on /system_ext type ext4 (ro,seclabel,relatime,discard)
/dev/block/dm-1 on /product type ext4 (ro,seclabel,relatime,discard)
/dev/block/dm-4 on /vendor type ext4 (ro,seclabel,relatime,discard)
/dev/block/dm-5 on /vendor_dlkm type ext4 (ro,seclabel,relatime,discard)
/dev/block/dm-0 on /odm type ext4 (ro,seclabel,relatime,discard)
/dev/block/dm-6 on /mnt/scratch type f2fs (rw,sync,lazytime,seclabel,noatime,background_gc=on,nodiscard,no_heap,user_xattr,inline_xattr,acl,inline_data,inline_dentry,flush_merge,extent_cache,mode=adaptive,active_logs=6,alloc_mode=reuse,checkpoint_merge,fsync_mode=posix,memory=normal)
overlay on /system type overlay (ro,seclabel,noatime,lowerdir=/system,upperdir=/mnt/scratch/overlay/system/upper,workdir=/mnt/scratch/overlay/system/work,override_creds=off)
overlay on /system_ext type overlay (ro,seclabel,noatime,lowerdir=/system_ext,upperdir=/mnt/scratch/overlay/system_ext/upper,workdir=/mnt/scratch/overlay/system_ext/work,override_creds=off)
overlay on /product type overlay (ro,seclabel,noatime,lowerdir=/product,upperdir=/mnt/scratch/overlay/product/upper,workdir=/mnt/scratch/overlay/product/work,override_creds=off)
overlay on /vendor type overlay (ro,seclabel,noatime,lowerdir=/vendor,upperdir=/mnt/scratch/overlay/vendor/upper,workdir=/mnt/scratch/overlay/vendor/work,override_creds=off)
overlay on /vendor_dlkm type overlay (ro,seclabel,noatime,lowerdir=/vendor_dlkm,upperdir=/mnt/scratch/overlay/vendor_dlkm/upper,workdir=/mnt/scratch/overlay/vendor_dlkm/work,override_creds=off)
overlay on /odm type overlay (ro,seclabel,noatime,lowerdir=/odm,upperdir=/mnt/scratch/overlay/odm/upper,workdir=/mnt/scratch/overlay/odm/work,override_creds=off)
...
...

讲一下这些都代表什么意思

tmpfs on /dev type tmpfs (rw,seclabel,nosuid,relatime,size=7984680k,nr_inodes=1996170,mode=755)

tmpfs 挂载点

tmpfs:文件系统类型

/dev:设备文件名

ro: readonly,只读挂载;
rw: read and write, 读写挂载

nosuid 关闭set-user-identifier(设置用户ID)与set-group-identifer(设置组ID)设置位。

mode=755:挂载权限为0755,代表rwxr-xr-x.

权限说明:

权限:0755
含义当前用户组用户group其他用户
可拥有的权限rwxr-xr-x

 一般赋予目录0755权限,文件0644权限。

每个组group的权限范围为0-7,含义如下

ls -l结果二进制含义
---0000no excute , no write ,no read
--x1001excute, (no write, no read)
-w-2010write 
-wx3011write, excute
r--4100read
r-x5101read, excute
rw-  6110read, write
rwx7111read, write , excute

查看设备节点内存,可以看到system,vendor,product都挂载到了哪里。

 adb shell df -h

$:/ # df -h
Filesystem       Size Used Avail Use% Mounted on
tmpfs            7.6G 1.6M  7.6G   1% /dev
tmpfs            7.6G    0  7.6G   0% /mnt
/dev/block/sda9   11M 188K   11M   2% /metadata
/dev/block/dm-2  914M 912M  2.7M 100% /
/dev/block/dm-6  1.9G 230M  1.7G  12% /mnt/scratch
overlay          1.9G 230M  1.7G  12% /system
overlay          1.9G 230M  1.7G  12% /system_ext
overlay          1.9G 230M  1.7G  12% /product
overlay          1.9G 230M  1.7G  12% /vendor
overlay          1.9G 230M  1.7G  12% /vendor_dlkm
overlay          1.9G 230M  1.7G  12% /odm
tmpfs            7.6G 8.0K  7.6G   1% /apex
tmpfs            7.6G 576K  7.6G   1% /linkerconfig
/dev/block/sda2   27M 1.1M   26M   5% /mnt/vendor/persist
/dev/block/sde6  300M  54M  246M  19% /vendor/firmware_mnt
/dev/block/sde11  59M  39M   20M  67% /vendor/dsp
/dev/block/sde7   64M 2.9M   61M   5% /vendor/bt_firmware
/dev/block/sde26  30M    0   30M   0% /mnt/vendor/qmcs
/dev/block/dm-7   45G 4.7G   40G  11% /data
tmpfs            7.6G    0  7.6G   0% /data_mirror 

3.1.3 kernel log初始化

    SetStdioToDevNull(argv);
//可以输出kernel log了,前面已经创建了/dev/dmsg
    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系统已经可以用了
    LOG(INFO) << "init first stage started!";

3.1.4 加载/lib/moudles/下的ko文件, 打开串口log

这一部分的代码主要功能如下:

1)打开根目录/

2)加载/lib/moudles/下的ko文件

01-05 23:45:42.399     1     1 I init    : Loading module /lib/modules/qcom_hwspinlock.ko with args ''
01-05 23:45:42.401     1     1 I init    : Loaded kernel module /lib/modules/qcom_hwspinlock.ko
01-05 23:45:42.401     1     1 I init    : Loading module /lib/modules/smem.ko with args ''
01-05 23:45:42.403     1     1 I init    : Loaded kernel module /lib/modules/smem.ko
01-05 23:45:42.403     1     1 I init    : Loading module /lib/modules/minidump.ko with args ''
01-05 23:45:42.408     1     1 I init    : Loaded kernel module /lib/modules/minidump.ko
01-05 23:45:42.408     1     1 I init    : Loading module /lib/modules/qcom-scm.ko with args ''
01-05 23:45:42.416     1     1 I init    : Loaded kernel module /lib/modules/qcom-scm.ko
01-05 23:45:42.416     1     1 I init    : Loading module /lib/modules/qcom_wdt_core.ko with args ''

3)打开串口log

//打开根目录/
    auto old_root_dir = std::unique_ptr<DIR, decltype(&closedir)>{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();
    }

    auto want_console = ALLOW_FIRST_STAGE_CONSOLE ? FirstStageConsole(cmdline, bootconfig) : 0;

    boot_clock::time_point module_start_time = boot_clock::now();
    int module_count = 0;
//加载/lib/modules下的ko文件
    if (!LoadKernelModules(IsRecoveryMode() && !ForceNormalBoot(cmdline, bootconfig), want_console,
                           module_count)) {
        if (want_console != FirstStageConsoleParam::DISABLED) {
            LOG(ERROR) << "Failed to load kernel modules, starting console";
        } else {
            LOG(FATAL) << "Failed to load kernel modules";
        }
    }
    if (module_count > 0) {
        auto module_elapse_time = std::chrono::duration_cast<std::chrono::milliseconds>(
                boot_clock::now() - module_start_time);
        setenv(kEnvInitModuleDurationMs, std::to_string(module_elapse_time.count()).c_str(), 1);
        LOG(INFO) << "Loaded " << module_count << " kernel modules took "
                  << module_elapse_time.count() << " ms";
    }


    bool created_devices = false;
//根据ALLOW_FIRST_STAGE_CONSOLE(want_console)决定是否打开串口log
    if (want_console == FirstStageConsoleParam::CONSOLE_ON_FAILURE) {
        if (!IsRecoveryMode()) {
            created_devices = DoCreateDevices();
            if (!created_devices){
                LOG(ERROR) << "Failed to create device nodes early";
            }
        }
        StartConsole(cmdline);
    }

3.1.5 拷贝ramdisk prop文件,创建new ramdisk, 删除old ramdisk

这一部分的代码主要功能如下:

1)打开/system/etc/ramdisk/build.prop文件,如果可以打开,则创建/second_stage_resources/system/etc/ramdisk/build.prop文件,并将/system/etc/ramdisk/build.prop拷贝到second_stage_resources/system/etc/ramdisk/build.prop下。

2)打开/force_debugable, 如果存在“/force_debugable”,则第二阶段init将使用userdebug sepolicy并加载adb_debug.prop以允许adb root

3)创建/first_stage_ramdisk并挂载,然后将根目录切换到/first_stage_ramdisk

4)挂载 system、vendor 、product等系统分区

5)free old ramdisk

//打开/system/etc/ramdisk/build.prop文件
    if (access(kBootImageRamdiskProp, F_OK) == 0) {
//生成/second_stage_resources/system/etc/ramdisk/build.prop
//constexpr const char kSecondStageRes[] = "/second_stage_resources";
//constexpr const char kBootImageRamdiskProp[] = "/system/etc/ramdisk/build.prop";
//inline std::string GetRamdiskPropForSecondStage() {
//    return std::string(kSecondStageRes) + kBootImageRamdiskProp;
//}
        std::string dest = GetRamdiskPropForSecondStage();
        std::string dir = android::base::Dirname(dest);
        std::error_code ec;
        if (!fs::create_directories(dir, ec) && !!ec) {
            LOG(FATAL) << "Can't mkdir " << dir << ": " << ec.message();
        }
        if (!fs::copy_file(kBootImageRamdiskProp, dest, ec)) {
            LOG(FATAL) << "Can't copy " << kBootImageRamdiskProp << " to " << dest << ": "
                       << ec.message();
        }
        LOG(INFO) << "Copied ramdisk prop to " << dest;
    }

    // If "/force_debuggable" 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.
   // 如果存在“/force_debugable”,则第二阶段init将使用userdebug sepolicy并加载adb_debug.prop以允许adb root
    // /userdebug_plat_sepolicy.cil属于selinux策略里的规则
    // 如果设备unlocked(解锁了),则会修改selinux规则,放大用户权限
    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.
//在second init阶段可以用到
            setenv("INIT_FORCE_DEBUGGABLE", "true", 1);
        }
    }

    if (ForceNormalBoot(cmdline, bootconfig)) {
        mkdir("/first_stage_ramdisk", 0755);
//挂载first_stage_ramdisk到first_stage_ramdisk
        if (mount("/first_stage_ramdisk", "/first_stage_ramdisk", nullptr, MS_BIND, nullptr) != 0) {
            LOG(FATAL) << "Could not bind mount /first_stage_ramdisk to itself";
        }
//将根目录切换到/first_stage_ramdisk
        SwitchRoot("/first_stage_ramdisk");
    }
//挂载 system、vendor 、product等系统分区
    if (!DoFirstStageMount(!created_devices)) {
        LOG(FATAL) << "Failed to mount required partitions early ...";
    }

    struct stat new_root_info;
//new_root_info是/first_stage_ramdisk
    if (stat("/", &new_root_info) != 0) {
        PLOG(ERROR) << "Could not stat(\"/\"), not freeing ramdisk";
        old_root_dir.reset();
    }
//old_root_info是/
    if (old_root_dir && old_root_info.st_dev != new_root_info.st_dev) {
        FreeRamdisk(old_root_dir.get(), old_root_info.st_dev);
    }

    SetInitAvbVersionInRecovery();
//设置环境变量
    setenv(kEnvFirstStageStartedAt, std::to_string(start_time.time_since_epoch().count()).c_str(),
           1);

3.1.6 重新执行init进程并携带参数selinux_setup

    const char* path = "/system/bin/init";
    const char* args[] = {path, "selinux_setup", nullptr};
    auto fd = open("/dev/kmsg", O_WRONLY | O_CLOEXEC);
    dup2(fd, STDOUT_FILENO);
    dup2(fd, STDERR_FILENO);
    close(fd);
//重新执行/system/bin/init,并携带了参数selinux_setup
    execv(path, const_cast<char**>(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;

后文接着讲后续流程

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
在MT6737 Android N平台上,录音到播放录音的流程可以分为以下几个步骤: 1. 打开录音设备 首先,需要打开录音设备并设置相关参数。在Android系统中,可以通过AudioRecord类来实现录音设备的打开和设置。例如: ``` int sampleRateInHz = 44100; // 采样率 int channelConfig = AudioFormat.CHANNEL_IN_MONO; // 声道数 int audioFormat = AudioFormat.ENCODING_PCM_16BIT; // 采样精度 int bufferSizeInBytes = AudioRecord.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat); // 缓冲区大小 AudioRecord recorder = new AudioRecord(MediaRecorder.AudioSource.MIC, sampleRateInHz, channelConfig, audioFormat, bufferSizeInBytes); recorder.startRecording(); // 开始录音 ``` 2. 录制音频数据 接下来,需要不断地读取录音设备中的音频数据,并保存到一个缓冲区中。在Android系统中,可以使用AudioRecord类的read方法来读取音频数据。例如: ``` byte[] buffer = new byte[1024]; while (isRecording) { // isRecording为标志位,表示是否正在录音 int len = recorder.read(buffer, 0, buffer.length); // 读取音频数据 // 将读取到的音频数据写入到文件或网络等 } ``` 3. 停止录音设备 当需要停止录音时,需要停止录音设备,并释放相关资源。在Android系统中,可以使用AudioRecord类的stop和release方法来实现。例如: ``` recorder.stop(); // 停止录音 recorder.release(); // 释放资源 ``` 4. 播放录音数据 在播放录音时,需要打开播放设备并设置相关参数。在Android系统中,可以使用AudioTrack类来打开播放设备并设置参数。例如: ``` int streamType = AudioManager.STREAM_MUSIC; // 音频流类型 int sampleRateInHz = 44100; // 采样率 int channelConfig = AudioFormat.CHANNEL_OUT_MONO; // 声道数 int audioFormat = AudioFormat.ENCODING_PCM_16BIT; // 采样精度 int bufferSizeInBytes = AudioTrack.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat); // 缓冲区大小 AudioTrack player = new AudioTrack(streamType, sampleRateInHz, channelConfig, audioFormat, bufferSizeInBytes, AudioTrack.MODE_STREAM); player.play(); // 开始播放 ``` 5. 播放录音数据 接下来,需要将录音数据写入到播放设备中,以实现播放录音的效果。在Android系统中,可以使用AudioTrack类的write方法来将录音数据写入到播放设备中。例如: ``` byte[] buffer = new byte[1024]; while (isPlaying) { // isPlaying为标志位,表示是否正在播放 // 从文件或网络等读取录音数据 int len = ...; // 将读取到的录音数据写入到播放设备中 player.write(buffer, 0, len); } ``` 6. 停止播放设备 当需要停止播放录音时,需要停止播放设备,并释放相关资源。在Android系统中,可以使用AudioTrack类的stop和release方法来实现。例如: ``` player.stop(); // 停止播放 player.release(); // 释放资源 ``` 以上就是录音到播放录音的流程分析。需要注意的是,在实际的开发中还需要考虑很多细节问题,例如音频格式的选择、缓冲区大小的计算、线程的管理等等。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值