第三章 android启动流程 内核启动

This commit is contained in:
dqzg12300 2023-02-21 23:14:28 +08:00
parent 2b1c9a7e0d
commit db43dc471a
5 changed files with 495 additions and 16 deletions

View File

@ -96,7 +96,7 @@ wsl --import ubuntu22 E:\wsl2\ubuntu22_wsl E:\wsl2\ubuntu22.tar
![image-20230102194722427](.\images\image-20230102194722427.png)
7、虚拟硬盘分配这里至少分配300G的空间考虑到性能我选择的是单文件吗这里如果选择立即分配所有磁盘空间能提高一定的性能。如果你的电脑配置不是很高建议你选择立即分配。
7、虚拟硬盘分配这里至少分配500G的空间考虑到性能我选择的是单文件吗这里如果选择立即分配所有磁盘空间能提高一定的性能。如果你的电脑配置不是很高建议你选择立即分配。
![image-20230102194952517](.\images\image-20230102194952517.png)
@ -1166,15 +1166,29 @@ device/mediatek/wembley-sepolicy: sleeping 4.0 seconds before retrying
<project name="device/mediatek/wembley-sepolicy" path="device/mediatek/wembley-sepolicy" groups="device"/>
~~~
然后看了创建仓库和批量提交代码的逻辑就明白了是的name和path的顺序反了导致正则表达式未能成功匹配到这个仓库所以我们调整一下name和path的顺序即可。到这里就完成了gitlab源码管理android源码开发了。最后如何使用git提交和查看历史记录我就不在这里叙述了。
然后看了创建仓库和批量提交代码的逻辑就明白了是的name和path的顺序反了导致正则表达式未能成功匹配到这个仓库所以我们调整一下name和path的顺序即可。
成功拉取完成后,如果在编译时碰到找不到文件的问题,这是由于有些子模块仓库下的子目录中有`.gitignore`文件将一些应该提交的文件给过滤掉了。就回到我们同步代码的目录中找到指定的git仓库使用下面的方式重新提交一下。然后回到我们同步下来的代码处重新拉取更新的代码。
~~~
// 进入缺少文件的子模块仓库目录
cd ~/external/angle/
git add . -f
git commit -m "init"
git push -u origin master
cd ~/android_src/myandroid12/
repo sync -j8
~~~
到这里就完成了gitlab源码管理android源码开发了。最后如何使用git提交和查看历史记录我就不在这里叙述了。
## 2.10 小结
在这一章里主要讲述了如何从零开始搭建一个编译Android源码的环境以及如何选择编译的版本和完整的编译Android源码并使用自己编译的内核然后将这个我们编译好的镜像尝试多种方式刷入测试手机中。为了后续开发和阅览代码的便利又讲述了如何使用Android Studio和Clion导入源码。最后为了便于长期维护和持续性的开发我们又搭建了gitlab+repo管理Android源码。终于将一切准备就绪了。那么接下来让我们在下一章开始解开Android源码神秘的面纱。
在这一章里主要讲述了如何从零开始搭建一个编译Android源码的环境以及如何选择编译的版本和完整的编译Android源码并使用自己编译的内核然后将这个我们编译好的镜像尝试多种方式刷入测试手机中。为了后续开发和阅览代码的便利又讲述了如何使用Android Studio和Clion导入源码。最后为了便于长期维护和持续性的开发我们又搭建了gitlab+repo管理Android源码。终于将一切准备就绪了。
如果你理解了源码同步的方式那么你可以延申一下如何将源码直接同步到github中呢请自己动手试试吧。
TODO 你看看这里是不是小结写的有点简单。看着应该怎么丰富一下。

View File

@ -10,7 +10,7 @@
2、bionic这是Android的C库包含了很多标准的C库函数和头文件还有一些Android特有的函数和头文件。
3、bootable该目录包含了引导程序,这些引导程序用于从系统启动,并初始化硬件, 例如Bootloader和Recovery程序
3、bootable包含了一些用于生成引导程序相关代码的工具和脚本,以及引导程序相关的一些源代码,但并不包含完整的引导程序代码
4、build该目录包含了编译Android源代码所需要的脚本包括makefile文件和一些构建工具。
@ -62,4 +62,466 @@
![image-20230219161123065](.\images\image-20230219161123065.png)
下载两个驱动文件后我们将文件放到源码根目录中解压并且执行相应的sh脚本进行导出到了这里我们了解到vendor中是硬件厂商提供的摄像头蓝牙之类的驱动程序。那么我们就可以观察到脚本执行后实际就是将驱动文件放到了对应目录中。
下载两个驱动文件后我们将文件放到源码根目录中解压并且执行相应的sh脚本进行导出到了这里我们了解到vendor中是硬件厂商提供的摄像头蓝牙之类的驱动程序。那么我们就可以观察到脚本执行后实际就是将驱动文件放到了对应目录中。对根目录结构有一个简单的了解之后我们就可以开始翻阅源码翻阅源码我们可以通过前面搭建好的开发环境或者是使用在线的源码查看网站http://aospxref.com/。
## 3.2 Android启动流程
Android启动流程主要分为四个阶段Bootloader阶段、Kernel阶段、Init进程阶段和System Server启动阶段首先我们先简单介绍下这几个阶段的启动流程。
1. Bootloader阶段 当手机或平板电脑开机时首先会执行引导加载程序Bootloader它会在手机的ROM中寻找启动内核Kernel的镜像文件并将其加载进RAM中。在这个阶段中Android系统并没有完全启动只是建立了基本的硬件和内核环境。
2. Kernel阶段 Kernel阶段是Android启动流程的第二阶段它主要负责初始化硬件设备、加载驱动程序、设置内存管理等。此外Kernel还会加载initramfs它是一个临时文件系统包含了init程序和一些设备文件。
3. Init进程阶段 Kernel会启动init进程它是Android系统中的第一个用户空间进程。Init进程的主要任务是读取init.rc文件并根据该文件中的配置信息启动和配置Android系统的各个组件。在这个阶段中系统会依次启动各个服务和进程包括启动Zygote进程和创建System Server进程。
4. System Server启动阶段 System Server是Android系统的核心服务进程它会启动所有的系统服务。其中包括Activity Manager、Package Manager、Window Manager、Location Manager、Telephony Manager、Wi-Fi Service、Bluetooth Service等。System Server启动后Android系统就完全启动了用户可以进入桌面开始使用各种应用程序。
## 3.3 内核启动
Bootloader其实就是一段程序这个程序的主要功能就是用来引导系统启动所以我们也称之为引导程序而这个引导程序是存放在一个只读的寄存器中从物理地址0开始的一段空间分配给了这个只读存储器来存放引导程序。
Bootloader会初始化硬件设备并准备内存空间映射为启动内核准备环境。然后寻找内核的镜像文件验证boot分区和recovery分区的完整性然后将其加载到内存中最后开始执行内核。我们可以通过命令`adb reboot bootloader`直接重启进入引导程序。
Bootloader 将件初始化完成后,会在特定的物理地址处查找 EFI 引导头efi_head。如果查找到 EFI 引导头bootloader 就会加载 EFI 引导头指定的 EFI 引导程序,然后开始执行 EFI 引导程序,以进行后续的 EFI 引导流程。而这个efi_head就是linux内核最早的入口了。
这里注意,我们现在并需要完全看懂内核中的汇编部分代码,主要是为了解执行的流程,所以并不需要你有汇编的功底,只需要能看懂简单的几个指令即可,接下来打开我们编译内核源码时的目录,找到文件`~/android_src/android-kernel/private/msm-google/arch/arm64/kernel/head.S`查看汇编代码如下。
~~~assembly
__HEAD
_head:
/*
* DO NOT MODIFY. Image header expected by Linux boot-loaders.
*/
#ifdef CONFIG_EFI
/*
* This add instruction has no meaningful effect except that
* its opcode forms the magic "MZ" signature required by UEFI.
*/
add x13, x18, #0x16
b stext
#else
b stext // branch to kernel start, magic
~~~
在arm指令集中指令b表示跳转所以我们继续找到stext的定义部分。
~~~assembly
/*
* The following callee saved general purpose registers are used on the
* primary lowlevel boot path:
*
* Register Scope Purpose
* x21 stext() .. start_kernel() FDT pointer passed at boot in x0
* x23 stext() .. start_kernel() physical misalignment/KASLR offset
* x28 __create_page_tables() callee preserved temp register
* x19/x20 __primary_switch() callee preserved temp registers
*/
ENTRY(stext)
bl preserve_boot_args //把引导程序传的4个参数保存在全局数组boot_args
bl el2_setup // Drop to EL1, w0=cpu_boot_mode
adrp x23, __PHYS_OFFSET
and x23, x23, MIN_KIMG_ALIGN - 1 // KASLR offset, defaults to 0
bl set_cpu_boot_mode_flag
bl __create_page_tables // 创建页表映射 x25=TTBR0, x26=TTBR1
/*
* The following calls CPU setup code, see arch/arm64/mm/proc.S for
* details.
* On return, the CPU will be ready for the MMU to be turned on and
* the TCR will have been set.
*/
bl __cpu_setup // // 初始化处理器 initialise processor
b __primary_switch
ENDPROC(stext)
~~~
能看到最后一行是跳转到__primary_switch接下来继续看它的实现代码
~~~assembly
__primary_switch:
#ifdef CONFIG_RANDOMIZE_BASE
mov x19, x0 // preserve new SCTLR_EL1 value
mrs x20, sctlr_el1 // preserve old SCTLR_EL1 value
#endif
bl __enable_mmu
#ifdef CONFIG_RELOCATABLE
bl __relocate_kernel
#ifdef CONFIG_RANDOMIZE_BASE
ldr x8, =__primary_switched //将x8设置成__primary_switched的地址
adrp x0, __PHYS_OFFSET
blr x8 //调用__primary_switched
/*
* If we return here, we have a KASLR displacement in x23 which we need
* to take into account by discarding the current kernel mapping and
* creating a new one.
*/
msr sctlr_el1, x20 // disable the MMU
isb
bl __create_page_tables // recreate kernel mapping
tlbi vmalle1 // Remove any stale TLB entries
dsb nsh
isb
msr sctlr_el1, x19 // re-enable the MMU
isb
ic iallu // flush instructions fetched
dsb nsh // via old mapping
isb
bl __relocate_kernel
#endif
#endif
ldr x8, =__primary_switched
adrp x0, __PHYS_OFFSET
br x8
ENDPROC(__primary_switch)
~~~
接着我们继续跟踪__primary_switched函数然后我们就能看到一个重点函数start_kernel了。
~~~assembly
__primary_switched:
adrp x4, init_thread_union
add sp, x4, #THREAD_SIZE
adr_l x5, init_task
msr sp_el0, x5 // Save thread_info
adr_l x8, vectors // load VBAR_EL1 with virtual
msr vbar_el1, x8 // vector table address
isb
stp xzr, x30, [sp, #-16]!
mov x29, sp
#ifdef CONFIG_SHADOW_CALL_STACK
adr_l x18, init_shadow_call_stack // Set shadow call stack
#endif
str_l x21, __fdt_pointer, x5 // Save FDT pointer
ldr_l x4, kimage_vaddr // Save the offset between
sub x4, x4, x0 // the kernel virtual and
str_l x4, kimage_voffset, x5 // physical mappings
// Clear BSS
adr_l x0, __bss_start
mov x1, xzr
adr_l x2, __bss_stop
sub x2, x2, x0
bl __pi_memset
dsb ishst // Make zero page visible to PTW
#ifdef CONFIG_KASAN
bl kasan_early_init
#endif
#ifdef CONFIG_RANDOMIZE_BASE
tst x23, ~(MIN_KIMG_ALIGN - 1) // already running randomized?
b.ne 0f
mov x0, x21 // pass FDT address in x0
mov x1, x23 // pass modulo offset in x1
bl kaslr_early_init // parse FDT for KASLR options
cbz x0, 0f // KASLR disabled? just proceed
orr x23, x23, x0 // record KASLR offset
ldp x29, x30, [sp], #16 // we must enable KASLR, return
ret // to __primary_switch()
0:
#endif
b start_kernel // 内核的入口函数
ENDPROC(__primary_switched)
~~~
上面能看到最后一个指令就是start_kernel了这个函数是内核的入口函数同时也是c语言部分的入口函数。接下来我们查看文件`~/android_src/android-kernel/private/msm-google/init/main.c`可以看到其中大量的init初始化各种子系统的函数调用。
~~~c
asmlinkage __visible void __init start_kernel(void)
{
char *command_line;
char *after_dashes;
set_task_stack_end_magic(&init_task);
scs_set_init_magic(&init_task);
smp_setup_processor_id();
debug_objects_early_init();
cgroup_init_early();
local_irq_disable();
early_boot_irqs_disabled = true;
/*
* Interrupts are still disabled. Do necessary setups, then
* enable them
*/
boot_cpu_init();
page_address_init();
pr_notice("%s", linux_banner);
setup_arch(&command_line);
/*
* Set up the the initial canary ASAP:
*/
boot_init_stack_canary();
mm_init_cpumask(&init_mm);
setup_command_line(command_line);
setup_nr_cpu_ids();
setup_per_cpu_areas();
smp_prepare_boot_cpu(); /* arch-specific boot-cpu hooks */
boot_cpu_hotplug_init();
build_all_zonelists(NULL, NULL, false);
page_alloc_init();
pr_notice("Kernel command line: %s\n", boot_command_line);
/* parameters may set static keys */
jump_label_init();
parse_early_param();
after_dashes = parse_args("Booting kernel",
static_command_line, __start___param,
__stop___param - __start___param,
-1, -1, NULL, &unknown_bootoption);
if (!IS_ERR_OR_NULL(after_dashes))
parse_args("Setting init args", after_dashes, NULL, 0, -1, -1,
NULL, set_init_arg);
/*
* These use large bootmem allocations and must precede
* kmem_cache_init()
*/
setup_log_buf(0);
pidhash_init();
vfs_caches_init_early();
sort_main_extable();
trap_init();
mm_init();
/*
* Set up the scheduler prior starting any interrupts (such as the
* timer interrupt). Full topology setup happens at smp_init()
* time - but meanwhile we still have a functioning scheduler.
*/
sched_init();
/*
* Disable preemption - early bootup scheduling is extremely
* fragile until we cpu_idle() for the first time.
*/
preempt_disable();
if (WARN(!irqs_disabled(),
"Interrupts were enabled *very* early, fixing it\n"))
local_irq_disable();
idr_init_cache();
/*
* Allow workqueue creation and work item queueing/cancelling
* early. Work item execution depends on kthreads and starts after
* workqueue_init().
*/
workqueue_init_early();
rcu_init();
/* trace_printk() and trace points may be used after this */
trace_init();
context_tracking_init();
radix_tree_init();
/* init some links before init_ISA_irqs() */
early_irq_init();
init_IRQ();
tick_init();
rcu_init_nohz();
init_timers();
hrtimers_init();
softirq_init();
timekeeping_init();
time_init();
sched_clock_postinit();
printk_nmi_init();
perf_event_init();
profile_init();
call_function_init();
WARN(!irqs_disabled(), "Interrupts were enabled early\n");
early_boot_irqs_disabled = false;
local_irq_enable();
kmem_cache_init_late();
/*
* HACK ALERT! This is early. We're enabling the console before
* we've done PCI setups etc, and console_init() must be aware of
* this. But we do want output early, in case something goes wrong.
*/
console_init();
if (panic_later)
panic("Too many boot %s vars at `%s'", panic_later,
panic_param);
lockdep_info();
/*
* Need to run this when irqs are enabled, because it wants
* to self-test [hard/soft]-irqs on/off lock inversion bugs
* too:
*/
locking_selftest();
#ifdef CONFIG_BLK_DEV_INITRD
if (initrd_start && !initrd_below_start_ok &&
page_to_pfn(virt_to_page((void *)initrd_start)) < min_low_pfn) {
pr_crit("initrd overwritten (0x%08lx < 0x%08lx) - disabling it.\n",
page_to_pfn(virt_to_page((void *)initrd_start)),
min_low_pfn);
initrd_start = 0;
}
#endif
page_ext_init();
debug_objects_mem_init();
kmemleak_init();
setup_per_cpu_pageset();
numa_policy_init();
if (late_time_init)
late_time_init();
sched_clock_init();
calibrate_delay();
pidmap_init();
anon_vma_init();
acpi_early_init();
#ifdef CONFIG_X86
if (efi_enabled(EFI_RUNTIME_SERVICES))
efi_enter_virtual_mode();
#endif
#ifdef CONFIG_X86_ESPFIX64
/* Should be run before the first non-init thread is created */
init_espfix_bsp();
#endif
thread_stack_cache_init();
cred_init();
fork_init();
proc_caches_init();
buffer_init();
key_init();
security_init();
dbg_late_init();
vfs_caches_init();
pagecache_init();
signals_init();
proc_root_init();
nsfs_init();
cpuset_init();
cgroup_init();
taskstats_init_early();
delayacct_init();
check_bugs();
acpi_subsystem_init();
sfi_init_late();
if (efi_enabled(EFI_RUNTIME_SERVICES)) {
efi_late_init();
efi_free_boot_services();
}
ftrace_init();
/* Do the rest non-__init'ed, we're now alive */
rest_init();
prevent_tail_call_optimization();
}
~~~
这里我们继续追踪关键的函数rest_init就是在这里开启的内核初始化线程以及创建内核线程。
~~~c
static noinline void __ref rest_init(void)
{
...
kernel_thread(kernel_init, NULL, CLONE_FS);
numa_default_policy();
pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
...
}
~~~
接着看看kernel_init线程执行的内容。
~~~c
static int __ref kernel_init(void *unused)
{
int ret;
kernel_init_freeable();
/* need to finish all async __init code before freeing the memory */
async_synchronize_full();
free_initmem();
mark_readonly();
system_state = SYSTEM_RUNNING;
numa_default_policy();
rcu_end_inkernel_boot();
if (ramdisk_execute_command) {
ret = run_init_process(ramdisk_execute_command);
if (!ret)
return 0;
pr_err("Failed to execute %s (error %d)\n",
ramdisk_execute_command, ret);
}
/*
* 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) {
ret = run_init_process(execute_command);
if (!ret)
return 0;
panic("Requested init %s failed (error %d).",
execute_command, ret);
}
if (!try_to_run_init_process("/sbin/init") ||
!try_to_run_init_process("/etc/init") ||
!try_to_run_init_process("/bin/init") ||
!try_to_run_init_process("/bin/sh"))
return 0;
panic("No working init found. Try passing init= option to kernel. "
"See Linux Documentation/init.txt for guidance.");
}
~~~
在这里我们看到了原来init进程是用try_to_run_init_process启动的并且会依次执行上面的4个进程。我们继续看看这个函数是如何启动进程的。
~~~c
static int try_to_run_init_process(const char *init_filename)
{
int ret;
ret = run_init_process(init_filename);
if (ret && ret != -ENOENT) {
pr_err("Starting init: %s exists but couldn't execute it (error %d)\n",
init_filename, ret);
}
return ret;
}
~~~
这里简单包装调用的run_init_process继续看下面的代码
~~~c
static int run_init_process(const char *init_filename)
{
argv_init[0] = init_filename;
return do_execve(getname_kernel(init_filename),
(const char __user *const __user *)argv_init,
(const char __user *const __user *)envp_init);
}
~~~
这里能看到最后是通过execve拉起来的init进程。到这里内核就成功拉起了在最后我们总结内核启动的简单流程图如下。
![startkernel](.\images\startkernel.png)
## 3.4 Init进程启动

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

View File

@ -0,0 +1 @@
<mxfile host="Electron" modified="2023-02-21T15:02:33.543Z" agent="5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/13.9.9 Chrome/85.0.4183.121 Electron/10.1.5 Safari/537.36" etag="sEMhMPYwBkhyji3gxQ8t" version="13.9.9" type="device"><diagram id="QhyC4PTY8g-ZMk1R3MFR" name="第 1 页">1Vlbj9o4GP01fuwoFxzsx4RLV9qtVO087HZfkId4SaYhRo4p0F+/nx2HJBimzJaZ0NEI2Z8vOOec7+KAwsl6/1GyTfZJpLxAgZfuUThFQUBwCJ/acKgNI0xrw0rmaW3yW8Nj/p1bo2et2zzlVW+iEqJQ+aZvXIqy5EvVszEpxa4/7V9R9L91w1bcMTwuWeFa/8pTlVmrH9F24DeerzL71SQY1wNr1ky2T1JlLBW7jimcoXAihVB1a72f8EJj1+BSr5tfGD0eTPJSXbPgOfrzeR9m4tPvz0XC/3kKP5b7D5adSh2aB+YpPL/tCqkysRIlK2atNZFiW6Zc7+pBr53zhxAbMPpgfOZKHSyZbKsEmDK1Luwo3+fqb738Advel87IdG93Np2D7dTn1Ie7+PjWVImtXPIXntk/gg+i5WLNlTzAOskLpvJv/f2Zlc/qOO+49LPI4ZsDzyrdx/gh7P5ZHTS6D73+jorJFVd2k5Y2aHRO1ZoMma8g1p7/Gyu29onQLEKEIpLoBp2gJEYz6HqIQmOM6AglGM0wSibaeJxzqo8++7ssV/xxwwzgO4gAfabtIbhUfP8yby4fdsHY6+EYRra/63hjMyfrOGIYXWawB/ZrkSUusgP40A39AduwagT5wrzoJ/3mp1DHZ/SMEYlRDFolKJnCfwKxtBAs5dKMgdUzDdC9UXY8RZRYN4jHelnsIzK5N4kfQ8VgEm9y2MAaB/jkQSeKD96D548ay5c6dYyixtBmDNM7dHufucwBFJDEzfNIdKXf0Fvkm1hKduhM2OjEUV1OR0HUFxX2ab86eN18aNQnuGmSiq5wapOSKEqiM47bOneklyTk3lwZe0O7cogH91yP9j0XKqNhHZde6bjBaMiMR38cg99b3QE9KcYoceQdnJN30Bz+5ig198QOTEwuM9iLyXUEBM6/clnqO+k84yx9eHRABDhUHylW5KsS2kuAResv0aDlcCeM7cA6T9PaUXiVf2dPZitNgY3KsC9OEJ7qvcA3qtpNbkRCiE8rYpcEH58jwXsrDkYOBwsNNpjOhGiwgIzieRPqibl9xIiMjAXKNFOskTmK3dvpeys+HJ3UZs2zDhbQfbcSroyEz4NN9K0uGZvyd2wufyOdVMlUow6NeHZ/GI+HTpq+W/8uFhvIR0weFtUuV8sM8AmiQgeOJwmtlW4ZbAHwOC9zZaCfI5pooEHkGnqi4U5mLfQzo/a6yAHCSGSZI7WbTOwNhk71lfzeeMKD31N8N0ca6GEvlpcPSyCGrTUQ5VO1MSCc9n/xXBAR/MNcQN4zFQRuKhhct/j/Vi2h91ZVS+AKt4JyVC2aYsUNLefyKIQJOu2EdqwvRRS8bWKzrM6pYzM6bV6AeCb6hCgxgQk+6bz2mYFZiu4u04ZuaQk+rhY1XNczZIYA6CTuVDpj3YX80CYHpyy6O0bI0PE+dN9q1w7zak7OeY3Lyb0xgPHgDLivbGDbhRILuS0NC1AniSWvqvNs9CV+oU66N9ij4O1gh277E2D9Xq39HTWc/Qc=</diagram></mxfile>

View File

@ -32,17 +32,19 @@
第3章 认识系统组件
3.1 源码结构介绍
3.2 进程启动流程
3.2 Android的启动流程
3.3 init进程
3.3.1 initrc配置文件
3.3 内核启动
3.4 init进程启动
3.4.1 initrc配置文件
...
3.3.x initrc添加xxx
3.4 认识services
3.5 认识framework
3.6 认识libcore
3.7 认识sepolicy
3.8 认识linker
3.4.x initrc添加xxx
3.5 认识services
3.6 认识framework
3.7 认识libcore
3.8 认识sepolicy
3.9 认识linker
第4章 系统美化
4.1 常见的系统美化方式
@ -139,4 +141,4 @@
第12章 基于定制系统的逆向实战
12.1 案例1
12.2 案例2
12.3 小结
12.3 小结