Android/inux系统开机启动流程

一、android启动流程介绍

Android开机启动流程是一个复杂且有序的过程,涉及到多个阶段的启动,可以大致概括为以下几个阶段:

1、Bootrom阶段:这是芯片上电之后启动的第一段程序,主要目的是用于引导bootloader程序,这段程序该段程序在芯片制造过程中固化到其内部的ROM空间,只读不可修改。

2、Bootloader阶段:android系统普遍使用的是u-boot,是一个开源的项目,其主要目的是做好linux内核启动前的准备,如初始化设备硬件,传递cmdline信息等,随后依据分区表信息,找到内核image,启动linux内核。

3、Linux 内核阶段:操作系统内核是一个系统最核心的部分,系统所有核心的子系统将在这个阶段启动,包括内存管理,进程调度,文件系统管理,设备驱动初始化等等,启动完成之后,解压根文件系统,并执行所有进程的祖先进程init,进入到下一个阶段。

4、Android init阶段:init阶段主要的目的是启动android系统的基础服务程序,如Zygote进程,以及后续的PowerManager服务、Launcher桌面app等。

到此为止,android系统的整个启动流程就到此为止了,详细每个阶段的任务可以看下文的详细描述。

二、Bootrom阶段

Bootrom(或Boot ROM)是嵌入处理器芯片内的一小块掩模ROM或写保护闪存。它包含处理器在上电或复位时执行的第一个代码,执行地址指向0x00000000或0xffff0000。

根据硬件引脚配置,它可以决定从哪里加载要执行的代码的下一部分以及如何或是否验证其正确性或有效性。

下图是rockchip芯片的一个关于bootrom的启动流程:

1、启动片内bootrom程序后,根据设置的启动模式,决定从什么介质启动,sd-card或emmc;所有的存储设备的bootloader都无法校验通过,则会自动进入MaskRom模式

2、拷贝外部flash的代码(U-BOOT BL1)到SRAM

3、初始化ddr

4拷贝bootloader(U-BOOT BL2)到DDR(RAM),运行下一阶段的程序。

三、Bootloader阶段(u-boot)

1、u-boot介绍

u-boot 是一个主要用于嵌入式系统的引导加载程序,可以支持多种不同的计算机系统结构。u-boot最先是由德国DENX软件中心团队开发,后续众多有志于开放源码bootloader移植工作的嵌入式开发人员将各个不同系列嵌入式处理器的移植工作不断展开和深入,以支持了更多的嵌入式操作系统的装载与引导。

2、u-boot目录介绍

arch:各种架构的启动初始化流程代码,链接脚本等均在此目录对应的架构中存放;
board:包含了大部分厂商的board初始化代码,基本平台化相关的代码都在对应的board目录中;
cmd:包含了大量实用的u-boot命令的实现,比如md,cp,cmp,tftp,fastboot,ext4load等命令的实现,我们也可以在此处添加自己实现的命令;
common:包含了u-boot的核心初始化代码,包括board_f,board_r,spl等一系列代码;
configs:包含了所有board的配置文件,可直接使用;
drivers:大量驱动代码的存放处;
dts:编译生成dtb,内嵌dtb到u-boot的编译规则定义目录;
env:环境变量功能实现代码;
fs:文件系统读写功能的实现,里面包含了各类文件系统的实现;
include:所有公用头文件的存放路径;
lib:大量通用功能实现,提供给各个模块使用;
net:网络相关功能的实现;
scripts:编译,配置文件的脚本文件存放处;
tools:测试和实用工具的实现,比如mkimage的实现代码在此处;

3、u-boot启动流程

u-boot启动流程可以分为两个阶段。

BL1(汇编编写):初始化ddr,关中断,关看门狗,设置C语言环境,如堆栈等

BL2(C编写):初始化该阶段要使用的硬件,搬运kernel的image,跳转到kernel阶段;

3.1 u-boot入口分析

U-Boot 通过链接脚本可以找到程序的入口,链接脚本为arch/arm/cpu/u-boot.lds,它描述了如何生成最终的二进制文件,其中就包含程序入口u-boot.lds。

u-boot.lds:文件所在位置arch/arm/cpu/u-boot.lds,从这里我们知道u-boot的入口在_start。

_start 在文件 arch/arm/lib/vectors.S 中有定义,可以知道u-boot入口代码位于start.S

#include <config.h>
#include <asm/psci.h>
...
ENTRY(_start)
...
3.2 u-boot BL1阶段

1、初始化CPU,进入特权SVC模式

2、初始化串口,DDR

3、拷贝BL2代码到DDR

4、准备C语言环境(_main),跳转到BL2

详细如下:

知识拓展: 为什么需要重定位(relocate_code)?
在嵌入式系统中,由于资源限制或启动过程的需要,U-Boot的初始代码可能存储在非易失性存储器中。然而,这些存储器的访问速度通常比RAM慢得多,不适合长时间运行操作系统或执行复杂任务。因此,一旦U-Boot启动并初始化了必要的硬件,包括RAM,它就需要将自身复制到RAM中,以便更快地执行后续操作。
3.3 u-boot BL2阶段

1、重新进入特权SVC模式

2、初始化CPU,时钟等

3、初始化需要用到的硬件设备,如网卡、显示、音频设备等

4、显示开机logo

5、准备uboot bootargs也就是kernel的cmdline,然后跳转到kernel

=> board_init_r: init_sequence_r[]
        initr_caches          // 使能MMU和I/Dcache
        initr_malloc
        bidram_initr
        sysmem_initr
        initr_of_live         // 初始化of_live
        initr_dm              // 初始化dm框架
        board_init            // 平台初始化,最核心部分
            board_debug_uart_init     // 串口iomux、clk配置
            init_kernel_dtb           // 初始化dtb
            clks_probe                // 初始化系统频率
            regulators_enable_boot_on // 初始化系统电源
            io_domain_init            //io-domain初始化
            set_armclk_rate
            dvfs_init 
            rk_board_init 
        console_init_r
        board_late_init               // 平台late初始化
            rockchip_set_ethaddr      // 设置mac地址
            rockchip_set_serialno     // 设置serialno 
            setup_boot_mode           // 解析reboot XX命令
            charge_display                               
            rockchip_show_logo        // 显示开机logo
            soc_clk_dump              // 打印clk tree
            rk_board_late_init
                boot_jump_linux       // uboot 跳转到linux内核
        run_main_loop                 // 进入命令行模式,或执行启动命令

四、kernel启动流程

启动代码位置:init/main.c

asmlinkage __visible void __init start_kernel(void){
	char *command_line;
	char *after_dashes;
    // 设置任务栈结束魔术数,用于栈溢出检测
	set_task_stack_end_magic(&init_task);   
    //设置处理器 ID
	smp_setup_processor_id();
    //debug 初始化
	debug_objects_early_init();
    //cgroup 初始化, cgroup 用于控制 Linux 系统资源
	cgroup_init_early();
    //关闭当前 CPU 中断
	local_irq_disable();
	early_boot_irqs_disabled = true;
    //CPU 初始化
	boot_cpu_init();
    //页地址相关的初始化
	page_address_init();
	pr_notice("%s", linux_banner);
    // 架构相关的初始化,此函数会解析uboot传递进来的参数,读取并解析dtb内容,初始化内存等
    setup_arch(&command_line);
    //初始化内存相关
	mm_init_cpumask(&init_mm);
	setup_command_line(command_line);
    //如果只是 SMP(多核 CPU)的话,此函数用于获取 * CPU 核心数量, CPU 数量保存在变量
	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);    //建立系统内存页区(zone)链表
	page_alloc_init();
       ...
       ->trap_init();    //完成对系统保留中断向量的初始化
	mm_init();    //内存管理初始化 
    ...
	init_IRQ();    //中断初始化
	tick_init();    //tick初始化
	rcu_init_nohz();
	init_timers();    //初始化定时器
	hrtimers_init();    
	softirq_init();    //软中断初始化
	timekeeping_init();
    ...
	early_boot_irqs_disabled = false;
	local_irq_enable();    //中断使能
	kmem_cache_init_late(); //slab 初始化,slab 是 Linux 内存分配器
	console_init();    //console init
    ...
    kmemleak_init();    //kmemleak 初始化, kmemleak 用于检查内存泄漏
	proc_caches_init();
	uts_ns_init();
	buffer_init();    //初始化缓冲区
    ...
    rest_init();    //初始化第一个init进程
     ->kernel_init();
       ->do_basic_setup(); //设备驱动初始化
         -> do_initcalls; //依次执行moudule_init
	prevent_tail_call_optimization();
}

start kernel具体工作:

  1) 调用setup_arch()函数进行与体系结构相关的第一个初始化工作;对不同的体系结构来说该函数有不同的定义。

  2) 创建异常向量表和初始化中断处理函数;

  3) 初始化系统核心进程调度器和时钟中断处理机制;

  4) 初始化串口控制台(console_init);

  5) 创建和初始化系统cache,为各种内存调用机制提供缓存,包括;动态内存分配,虚拟文件系统(VirtualFile System)及页缓存。

  6) 初始化内存管理,检测内存大小及被内核占用的内存情况;

  7) 初始化系统的进程间通信机制(IPC); 当以上所有的初始化工作结束后,start_kernel()函数会调用rest_init()函数来进行最后的初始化,包括创建系统的第一个进程-init进程来结束内核的启动。

8)挂载根文件系统并启动initLinux内核启动的下一过程是启动第一个进程init,但必须以根文件系统为载体,所以在启动init之前,还要挂载根文件系统

  根文件系统至少包括以下目录:

  /etc/:存储重要的配置文件。

  /bin/:存储常用且开机时必须用到的执行文件。

  /sbin/:存储着开机过程中所需的系统执行文件。

  /lib/:存储/bin/及/sbin/的执行文件所需的链接库,以及Linux的内核模块。

  /dev/:存储设备文件。

  注:五大目录必须存储在根文件系统上,缺一不可。

五、android init进程

android init进程是android系统启动过程中的第一个进程,它负责android的初始化,通过init.rc来启动其他必要的系统服务和进程,确保系统能够正常运行。下面我们开始分析一下init进程在启动过程中做了那些事情。

1、init进程代码分析

1.1 FirstStage阶段

FirstStage主要任务:

1、文件系统的初始化,如sysfs、proc、devfs等

2、android log日志系统的启用

./init   (system/core/init/main.cpp)
  ->FirstStageMain();
    //devfs,proc,tmpfs文件系统挂载
    ->CHECKCALL(mount("sysfs", "/sys", "sysfs", 0, NULL));
    ...
    //初始化android日志系统
    ->InitKernelLogging();
    //启动selinux
    ->execv("/system/bin/init","selinux_setup");

   ./init selinux_setup  (system/core/init/main.cpp)
   ->SetupSelinux(argv);
     //进入second_stage阶段
     ->execv("/system/bin/init","second_stage"); 
1.2 SecondStage阶段

SecondStage主要任务:

1、初始化系统属性服务

2、根据init.rc脚本规则依次启动系统服务

./init second_stage  (system/core/init/main.cpp)
  ->SecondStageMain(); (system/core/init/init.cpp)
    //再次初始化log系统日志
    ->InitKernelLogging();
    //初始化系统属性服务
    ->PropertyInit();
    ->StartPropertyService(&property_fd);
    //初始化action、service管理服务,解析init.rc
    ->ActionManager& am = ActionManager::GetInstance();
    ->ServiceList& sm = ServiceList::GetInstance();
    ->LoadBootScripts(am, sm);
    //执行rc文件标记为early-init、init、late-init服务
    ->am.QueueEventTrigger("early-init");
    ->am.QueueEventTrigger("init");
    ->am.QueueEventTrigger("late-init");
    //死循环EPOLL处理子进程状态
    ->while(true){ ... }

2、init.rc 脚本分析

从上文中android init进程分析中,我们知道,init进程在最后阶段通过解析init.rc脚本来启用了其它一些重要的进程服务,接下来,我们就针对init.rc的规则和启用了那些重要的服务进行分析。

2.1 .rc文件的存放目录及用途

init 进程执行的第一个init.rc文件位于/system/etc/init/hw/init.rc,位于源码目录的system/core/rootdir/init.rc.

由于android系统在启动过程中,会用到很多.rc文件,它们都有不同的功能,因此android源码在编译过程中会进行分类,分类如下:

/system/etc/init/:用于核心系统项,例如 SurfaceFlinger, MediaService和logd

/vendor/etc/init/:针对SoC供应商的项目,如SoC核心功能所需的actions或守护进程

/odm/etc/init/:用于设备制造商的项目,actions或其他外围功能所需的守护进程。

2.2 .rc文件的语法

init.rc主要包含五种类型语句:Action、Command、Service、Option、Import

以下解释一些较为重要的类型定义说明,完整的请查阅:

https://android.googlesource.com/platform/system/core/+/master/init/README.md

①Action
action的格式如下:

on <trigger> [&& <trigger>]*
    <command>
    <command>
如:
on early-init
    start ueventd
也可以是属性,如:
//表示当sys.boot_from_charger_mode的值通过property_set设置为1时触发
on property:sys.boot_from_charger_mode=1
//*表示任意值触发
on property:sys.sysctl.tcp_def_init_rwnd=*
②service
service的格式如下:

/*以service开头,name是指定这个服务的名称,pathname表示这个服务的执行文件路径,argument表示执行文件带的参数,option表示这个服务的一些配置。*/
service <name> <pathname> [ <argument> ]*
       <option>
       ...
例如:
service zygote /system/bin/app_process64 -Xzygote /system/bin --zygote --start-system-server --socket-name=zygote
    class main
    priority -20
    user root
    group root readproc
    socket zygote stream 660 root system
    onrestart write /sys/android_power/request_state wake
    onrestart write /sys/power/state on
    onrestart restart audioserver
    onrestart restart cameraserver
    onrestart restart media
    onrestart restart netd
    onrestart restart wificond
    writepid /dev/cpuset/foreground/tasks
其中:
zygote 为进程名称
/system/bin/app_process64 为进程执行路径
-Xzygote /system/bin --zygote --start-system-server --socket-name=zygote 为执行参数
③Options

Options是Services的参数配置. 它们影响Service如何运行及运行时机

disabled:
表示Service不能以class的形式启动,只能以name的形式启动

socket:
socket <name> <type> <perm> [ <user> [ <group> [ <seclabel> ] ] ]
创建一个unix域的socket,名字叫/dev/socket/name , 并将fd返回给Service. type 只能是 "dgram", "stream" or "seqpacket".User 和 group 默认值是 0. 'seclabel' 是这个socket的SELinux安全上下文,它的默认值是service安全策略或者基于其可执行文件的安全上下文.它对应的本地实现在libcutilsandroid_get_control_socket

user:
user <username>
在启动Service前将user改为username,默认启动时user为root(或许默认是无)

group:
group <groupname> [ <groupname>\* ]
在启动Service前将group改为第一个groupname,第一个groupname是必须有的,默认值为root(或许默认值是无),第二个groupname可以不设置,用于追加组(通过setgroups).

oneshot:
oneshot
开机只启动一次,退出后不再重启

class:
class <name> [ <name>\* ]
为Service指定class名字. 同一个class名字的Service会被一起启动或退出,默认值是"default",第二个name可以不设置,用于service组.

onrestart:
onrestart 
在Service重启时执行命令.

priority:
priority <priority>
设置进程优先级. 在-20~19之间,默认值是0,能过setpriority实现



④Commands
class_start/class_stop:
class_start/class_stop <serviceclass>
启动/停止所有以serviceclass命名的未启动的service,这里的serviceclass中class中的name

exec_start:
exec_start <service>
启动一个service,只有当执行结果返回,init进程才能继续执行. 

export:
export <name> <value>
设置环境变量name-value. 这个环境变量将被所有已经启动的service继承

ifup:
ifup <interface>
开启指定的网络接口

start/stop:
start/stop <service> 
启动/中止一个service

trigger:
trigger <event>
触发事件event,由一个action触发到另一个action队列
2.3 init.rc中关键服务的启动流程

在init.rc中,各个服务的启动流程顺序不同,根据init代码,其大致的启动流程如下:

early_init > ueventd->init->logd->servicemanager-> late_init > early-fs > fs > post-fs > late_fs > post-fs-data > zygote > early-boot > boot

其中启动比较重要的进程有:ueventd、logd、servicemanager、zygote等,其中servicemanager又包含以下服务的启动:

healthd zygote audioserver media surfaceflinger inputflinger drm cameraserver keystore gatekeeperd thermalservice
2.4 zygote

在Android中,负责孵化新进程的这个进程叫做Zygote,安卓上其他的应用进程都是由它孵化的。Zygote进程是由init进程启用,在android的启动流程中,它主要做了以下事情。

1、启用虚拟机

2、创建并启动SystemServer进程

3、创建Server端的Socket,孵化新的应用进程

Zygote进程在Android启动过程中扮演了孵化器和资源预加载者的角色

zygote和service manager之间的关系如下图所示:

2.5 launcher

启动 Launcher 是 Android 系统启动过程中的最后一步,它标志着整个系统已经初始化完成,并提供了用户与设备交互的入口。

启动 Launcher 的过程通常是由系统服务(System Service)负责调用。在系统启动的最后阶段,当其他系统组件已经准备就绪后,系统服务会启动 Launcher 进程,并加载 Launcher 应用程序的主要组件,例如主活动(MainActivity)。一旦 Launcher 进程启动并加载完成,它就会显示在屏幕上,并呈现用户熟悉的桌面界面。用户可以通过在桌面上滑动、点击图标等方式与 Launcher 进行交互,启动其他应用程序、访问设备设置等操作。

参考文章

1、https://www.geeksforgeeks.org/android-boot-process/

2、https://www.tutorialsfreak.com/app-penetration-testing-tutorial/android-boot-process

3、https://android.googlesource.com/platform/system/core/+/master/init/README.md

4、https://blog.csdn.net/mafei852213034/article/details/106055725

5、https://zhuanlan.zhihu.com/p/633773454

6、https://proandroiddev.com/how-android-boot-up-9864376d911c

  • 0
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
课程简述  Android是目前最为流行的移动操作系统之一,它的开发涉及到多个知识领域。本课程将深入介绍Android系统启动过程中的重要组成部分——init进程,并探讨与之相关的多项关键技术。我们还将提供实际的开发案例,以RK3399开发板为例,通过演示Android产品配置、init启动流程、selinux权限管理、init.rc启动脚本定制等实际案例,让学员深入理解这些技术在实际产品开发中的应用场景和实现方法,提高学员的实际开发能力和经验,从而更好地应对实际产品开发中遇到的问题和挑战。知识运用方向学习Android启动方面的知识,可以参与如下实际开发工作项:启动流程定制: 根据产品需求调整Android启动流程,包括修改init.rc脚本、修改启动顺序和等待时间、加入自定义服务等。属性系统定制: 通过Android属性系统定制化启动流程,例如增加产品版本信息、定制开机音量等。日志系统分析: 掌握日志的捕捉、分析和排错技术,在启动过程中,需要加入调试信息来方便开发人员进行调试,同时需要进行日志的优化,避免日志输出过多占用过多的系统资源。selinux安全策略定制:在Android系统中,selinux是一种安全机制,用于保护系统的敏感资源和数据。在实际开发中,可能需要对selinux策略进行定制,以确保系统的安全性和稳定性。课程内容主要内容简述1, RK3399 开发板操作这部分内容重点介绍如何在FIreFly开发板上将Android 10系统运行起来, 包含编译FireFly的Android源码下载和编译, 镜像烧录运行,内核和模块编译,以及RK3399内核启动init进程的过程。2, 产品定制这部分讲解获取到方案商或者原厂提供的源码后, 如何定制一个新的产品,产品配置文件和模型, 原始代码中的配置文件和定制化东西3, Android日志代码编写之前讲过Android的日志系统, 并没涉及到代码编写, 这个部分重点讲解C/C++, java代码编写日志的API和代码4, 属性系统Android中,属性使用的非常频繁的,可以用来作为进程间通信,也可以用于一些行为控制, 这个部分会重点介绍属性系统框架, API接口, 属性文件等知识点5,selinux进程对文件进行访问时,Android 4.3就开始集成了selinux权限管控, 如果需要启动某个脚本或者服务, selinux的配置就避免不了,并且Android8之后, Android系统对进程访问的权限管控的非常严格。6, init.rc脚本Android定义的一种脚本, 改脚本是有init进程启动, 是非常重要的一个脚本, 会包含系统中的其他很多脚本, 在我们系统开发时, 我们经常通过这个脚本进行一些定制化动作。7, init进程代码分析想要了解一个系统,就必须对源码进行分析和理解, 这个章节,带大家去跟读init进程代码, 这样,换了另外一个Android版本,完全就可以去读代码, 知道有什么变化。 
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值