【Android底层学习总结】2. 安卓系统内核的Bring Up

您所在的位置:网站首页 android内核linux 【Android底层学习总结】2. 安卓系统内核的Bring Up

【Android底层学习总结】2. 安卓系统内核的Bring Up

2024-01-26 23:32| 来源: 网络整理| 查看: 265

0 目录 1 前言2 简介3 启动流程3.1 上电3.2 Boot Loader3.3 Kernel的初始化 4 总结

1 前言

上节我们学习了驱动开发基础,这节我们继续学习,这节我们主要来了解安卓系统是怎么启动的,以及内核的初始化工作。

2 简介

Android采用分层的架构设计,从高到低分别是应用层,Java API 框架层,系统运行层(包括Android Runtime和原生态的C/C++库)、硬件抽象层、Linux内核层。而我们这篇文章将从上电讲到Kernel的启动,大致流程如下。

Boot Rom Boot Loader Kernel 上电加载 初始化启动 Boot Rom Boot Loader Kernel 3 启动流程 3.1 上电

首先,得要给板子上电才会有后面的事情,一般的个人电脑上电后会加载BIOS进行初始化然后跳入到系统,具体过程就是将非易失性存储器中的系统内核拷贝到RAM执行init代码从而启动内核。安卓作为ARM架构的一个操作系统和个人电脑有所不同,但总体原理一致。 在安卓中,上电后在PC(程序计数器)中取得的第一个地址指向的是Boot Rom(比如Flash)中的第一段程序Bootloader(Boot Loader有很多种,这里不展开讲),然后由Bootloader将内核镜像拷贝至运存,然后启动内核的第一行代码。

3.2 Boot Loader

Boot Loader是嵌入式系统的引导加载程序,它是系统上电后运行的第一段程序,其作用类似于 PC 机上的 BIOS。 不同的处理器上电或复位后执行的第一条指令地址并不相同,对于 ARM 处理器来说,该地址为 0x00000000。对于一般的嵌入式系统,通常把 Flash 等非易失性存储器映射到这个地址处,而 Boot loader就位于该存储器的最前端,所以系统上电或复位后执行的第一段程序便是Bootloader.

Boot Loader启动后接下来要做的事主要分下面几个步骤:

初始化 RAM 因为 Linux 内核一般都会在 RAM 中运行,所以在调用 Linux 内核之前 boot loader 必须设置和初始化 RAM,为调用 Linux内核做好准备。初始化 RAM 的任务包括设置CPU 的控制寄存器参数,以便能正常使用 RAM以及检测RAM大小等。初始化串口 串口在 Linux 的启动过程中有着非常重要的作用,它是 Linux内核和用户交互的方式之一。Linux 在启动过程中可以将信息通过串口输出,这样便可清楚的了解 Linux 的启动过程。虽然它并不是 Bootloader 必须要完成的工作,但是通过串口输出信息是调试Bootloader 和Linux内核的强有力的工具,所以一般的 Bootloader 都会在执行过程中初始化一个串口做为调试端口。检测处理器类型 Bootloader在调用 Linux内核前必须检测系统的处理器类型,并将其保存到某个常量中提供给 Linux 内核。Linux 内核在启动过程中会根据该处理器类型调用相应的初始化程序设置linux启动参数调用linux内核映像 Bootloader完成的最后一项工作便是调用 Linux内核。如果 Linux 内核存放在 Flash 中,并且可直接在上面运行(这里的 Flash 指 Nor Flash),那么可直接跳转到内核中去执行。但由于在 Flash 中执行代码会有种种限制,而且速度也远不及 RAM 快,所以一般的嵌入式系统都是将 Linux内核拷贝到 RAM 中,然后跳转到 RAM 中去执行。

以上步骤是bootloader的一般动作(上述BootLoader功能的内容转自此链接),实际上由于芯片厂商的不同,Bootloader的具体工作也是不同的,有些会在启动Kernel前执行安全固件的程序进行安全检查,然后启动little kernel,最后再由little kernel启动Kernel.

3.3 Kernel的初始化

内核的启动主要分为以下几个步骤:

检查硬件和CPU类型初始化堆栈、MMU(Memory Management Unit的缩写,中文名是内存管理单元,它是一种负责处理中央处理器(CPU)的内存访问请求的计算机硬件)等程序.初始化各种模块挂接根文件系统执行init程序,启动关键服务Zygote 注:在Android系统中,zygote所有应用进程的父进程,app的进程都是由这个zygote分裂出来的。zygote则是由Linux系统用户空间的第一个进程init进程,通过fork的方式创建的。 以上过程的代码可以在kernel的init文件夹中main.c文件中进行了解,以下贴出不完全的start_kernel函数。注kernel版本:4.9 asmlinkage __visible void __init start_kernel(void) { char *command_line; // 命令行,用来存放 bootloader 传递过来的参数 char *after_dashes; /* 函数参数:init_task即手工创建的PCB,0号进程就是最终的idle进程,它是个结构体:struct task_struct init_task = INIT_TASK(init_task); INIT_TASK 是一个宏用来设置拼接第一个PCB,Base=0, limit=0x1fffff (=2MB) 函数功能:获取栈边界地址,然后把 STACK_END_MAGIC这个宏设置为栈溢出的标志。 */ set_task_stack_end_magic(&init_task); /*针对SMP处理器,用于获取当前CPU的硬件ID ,如果不是多核,函数为空*/ smp_setup_processor_id(); /*初始化哈希桶 (hash buckets) 并将 static object 和 pool object 放 入 poll 列表,这样堆栈就可以完全操作了 【这个函数的主要作用就是对调试对象进行早期的初始化,就是HASH锁和静态对象池进行初始化,执行完后,object tracker已经开始完全运作了*/ debug_objects_early_init(); /* * 初始化堆栈保护的加纳利值,防止栈溢出攻击的堆栈保护关键字 */ boot_init_stack_canary(); /*在系统启动时初始化 cgroups ,同时初始化需要 early_init 的子系统 【这个函数作用是控制组 (control groups) 早期的初始化, 控制组就是 定义一组进程具有相同资源的占有程度, 比如,可以指定一组进程使用 CPU 为 30% ,磁盘 IO 为 40% ,网络带宽为 50%, 目的就是为了把所有进程分配不同的资源*/ cgroup_init_early(); /*关闭当前CPU的所有中断响应,操作CPSR寄存器。对应后面的系统中断关闭标志,当early_init完毕后,会恢复中断设置标志为false 。*/ 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); /*内核架构相关初始化函数,是非常重要的一个初始化步骤。 其中包含了处理器相关参数的初始化、内核启动参数 (tagged list)的获取 和前期处理、内存子系统的早期初始化(bootmem 分配器)*/ setup_arch(&command_line); ... page_alloc_init(); ... /*初始化中断向量*/ trap_init(); /*内存管理模块初始化*/ mm_init(); /*调度模块初始化*/ sched_init(); ... /*剩下的初始化工作*/ rest_init(); }

对于start_kernel最后一行,对于我们后续研究比较重要 现在我们跟随源码在main.c文件中依次进入以下函数:

rest_init-> kernel_thread(kernel_init, NULL, CLONE_FS)-> kernel_init-> kernel_init_freeable()-> do_basic_setup(): /* 到这一步,系统的内核初始化就已经差不多完成了,CPU 的子系统已经启动并运行, 且内存和处理器管理系统已经在工作了。但还有些设置未完成,这些 设置对于驱动工程师比较重要,特别是driver_init和do_initcalls函数 */ static void __init do_basic_setup(void) { // 针对SMP系统,初始化内核control group的cpuset子系统。如果非SMP,此函数为空。 cpuset_init_smp(); shmem_init(); // 初始化驱动模型中的各子系统,可见的现象是在/sys中出现的目录和文件 driver_init(); // 在proc文件系统中创建irq目录,并在其中初始化系统中所有中断对应的目录。 init_irq_proc(); // 调用链接到内核中的所有构造函数,也就是链接进.ctors段中的所有函数。 do_ctors(); // 启用用户态的帮助器 usermodehelper_enable(); // 调用所有编译内核的驱动模块中的初始化函数。 do_initcalls(); }

至此我们本节的内容基本结束,内核也完成了初始化,至于更多的内容需要对源码进行更深的研究,后期有机会会出几篇特定的源码解析。

4 总结

本节,主要研究了安卓系统内核从上电到启动之后初始化的全过程,并对一些函数进行了解析,但还不够完善,一些比较重要的函数源码还没有进行研究,下节将对driver_init进行深入研究。

本系列链接传送: 【Android底层学习总结】1. 驱动开发基础 【Android底层学习总结】3. 内核中driver_init函数源码解析



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3