第一阶段: Bootloader / UBoot
bootloader的实现依赖于CPU的体系结构,大多数bootloader都分为stage1和stage2两大部分。
2.1 stage1 (start.s代码结构) —- 基本的硬件初始化
U-Boot 的stage1代码通常放在start.s文件中,它用汇编语言写成,其主要代码部分如下:
手机上电后,硬件会从固定的地址(固化在ROM中)加载bootloader到RAM,然后跳转到bootloader的入口函数开始执行,
(1) 定义入口 。由于一个可执行的Image必须有一个入口点,并且只能有一个全局入口,通常这个入口放在ROM(Flash)的0x0地址,因此,必须通知编译器以使其知道这个入口,该工作可通过修改连接器脚本来完成。
(2)设置异常向量(ExceptionVector)。
(3)设置CPU的速度、时钟频率及中断控制寄存器。
(4)初始化(RAM)内存控制器 。
(5)将ROM中的程序复制到RAM中。
(6)初始化堆栈 。
(7)转到RAM中执行,该工作可使用指令ldrpc来完成。
2.2 stage2 C语言代码部分
libarm/board.c 中的start armboot 是C语言开始的函数,也是整个启动代码中C语言的主函数,同时还是整个U-boot(armboot)的主函数,该函数主要完成如下操作:
(1)调用一系列的初始化函数。
(2)初始化Flash设备。
(3)初始化系统内存分配函数。
(4)如果目标系统拥有NAND设备,则初始化NAND设备。
(5)如果目标系统有显示设备,则初始化该类设备。
(6)初始化相关网络设备,填写IP、MAC地址等。
(7)进入命令循环(即整个boot的工作循环),接受用户从串口输入的命令,然后进行相应的工作。
* 手机开机的第一个logo就是在这个阶段显示的,由于执行能力受限,只能是一张静态小图片*
曾经碰到开机白屏的问题,就是在此阶段修改屏驱解决的
uboot启动流程
uboot启动后的存储地址空间情况
第二阶段:Linux镜像加载与Linux启动阶段
uBoot 引导内核启动的最后一步是:通过一个函数指针 thekernel()带三个参数跳转到内核( zImage )入口点开始执行,此时, u-boot 的任务已经完成,控制权完全交给内核( zImage )。
1.zImage解压缩
2.Kernel的汇编启动阶段
\arch\arm\kernel\head.S
内核启动第二阶段主要完成的工作有,cpu ID检查,machine ID(也就是开发板ID)检查,创建初始化页表,设置C代码运行环境,跳转到内核第一个真正的C函数startkernel开始执行。
3.Kernel的C启动阶段
将会进入init/Main.c中的start_kernel()函数去继续执行
asmlinkage void __init start_kernel(void)
{
char * command_line;
extern const struct kernel_param __start___param[], __stop___param[];
smp_setup_processor_id();
/*
* Need to run as early as possible, to initialize the
* lockdep hash:
*/
lockdep_init();
debug_objects_early_init();
/*
* Set up the the initial canary ASAP:
*/
boot_init_stack_canary();
cgroup_init_early();
local_irq_disable();
early_boot_irqs_disabled = true;
/*
* Interrupts are still disabled. Do necessary setups, then
* enable them
*/
tick_init();
boot_cpu_init();
page_address_init();
printk(KERN_NOTICE "%s", linux_banner);
setup_arch(&command_line);
mm_init_owner(&init_mm, &init_task);
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 */
build_all_zonelists(NULL);
page_alloc_init();
printk(KERN_NOTICE "Kernel command line: %s\n", boot_command_line);
parse_early_param();
parse_args("Booting kernel", static_command_line, __start___param,
__stop___param - __start___param,
&unknown_bootoption);
/*
* 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();
//memblock_reserve((phys_addr_t)0x50000000,(phys_addr_t)0x100000);
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 (!irqs_disabled()) {
printk(KERN_WARNING "start_kernel(): bug: interrupts were "
"enabled *very* early, fixing it\n");
local_irq_disable();
}
idr_init_cache();
perf_event_init();
rcu_init();
radix_tree_init();
/* init some links before init_ISA_irqs() */
early_irq_init();
init_IRQ();
prio_tree_init();
init_timers();
hrtimers_init();
softirq_init();
timekeeping_init();
time_init();
profile_init();
call_function_init();
if (!irqs_disabled())
printk(KERN_CRIT "start_kernel(): bug: interrupts were "
"enabled early\n");
early_boot_irqs_disabled = false;
local_irq_enable();
/* Interrupts are enabled now so all GFP allocations are safe. */
gfp_allowed_mask = __GFP_BITS_MASK;
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(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) {
printk(KERN_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_cgroup_init();
enable_debug_pagealloc();
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();
#ifdef CONFIG_X86
if (efi_enabled)
efi_enter_virtual_mode();
#endif
thread_info_cache_init();
cred_init();
fork_init(totalram_pages);
proc_caches_init();
buffer_init();
key_init();
security_init();
dbg_late_init();
vfs_caches_init(totalram_pages);
signals_init();
/* rootfs populating might need page-writeback */
page_writeback_init();
#ifdef CONFIG_PROC_FS
proc_root_init();
#endif
cgroup_init();
cpuset_init();
taskstats_init_early();
delayacct_init();
check_bugs();
acpi_early_init(); /* before LAPIC and SMP init */
sfi_init_late();
ftrace_init();
/* Do the rest non-__init'ed, we're now alive */
//printk(KERN_INFO "[mjdbg]MEM Check4:0x%x : 0x%x.\n", (int *)(phys_to_virt(0x50000000)),*(int *)(phys_to_virt(0x50000000)));
rest_init();
}
1.打印版本信息,如内核、编译器、作者、日期。
2.setup_arch()主要做一些板级初始化
cpu初始化,tag参数解析,
u-boot传递的cmdline解析,
建立mmu工作页表(memtable_init),初始化内存布局,调用mmap_io建立GPIO,IRQ,MEMCTRL,UART,及其他外设的静态映射表,
对时钟,定时器,uart进行初始化,
cpu_init():打印一些关于cpu的信息,比如cpu id,cache 大小等。
另外重要的是设置了IRQ、ABT、UND三种模式的stack空间,分别都是12个字节。最后将系统切换到svc模式。
3.build_all_zonelists():建立系统内存页区(zone)链表
4.printk(KERN_NOTICE “Kernel command line: %s\n”, saved_command_line);打印出从uboot传递过来的command_line字符串,在setup_arch函数中获得的。
5.parse_early_param():这里分析的是系统能够辨别的一些早期参数(这个函数甚至可以去掉,__setup的形式的参数),而且在分析的时候并不是以setup_arch(&command_line)传出来的command_line为基础,而是以最原生态的saved_command_line为基础的。
6.parse_args(“Booting kernel”, command_line, __start___param, __stop___param - __start___param,&unknown_bootoption);
解析启动参数
7.sched_init():
初始化每个处理器的可运行队列,设置系统初始化进程即0号进程。
8.init_IRQ():
初始化系统中所有的中断描述结构数组:irq_desc[NR_IRQS]。
9.softirq_init():
内核的软中断机制初始化函数。
10.console_init():
初始化系统的控制台结构
12.rest_init():
调用kernel_thread()创建1号内核线程。
static noinline void __init_refok rest_init(void)
{
int pid;
//printk("**********************************************************\n");
//printk(" rest_init: 0x%x!!!\n",(*(int *)phys_to_virt(0x50000000)));
//printk("**********************************************************\n");
#ifdef CONFIG_KERNEL_PANIC_DUMP
panic_dump_test();
#endif
rcu_scheduler_starting();
/*
* We need to spawn init first so that it obtains pid 1, however
* the init task will end up wanting to create kthreads, which, if
* we schedule it before we create kthreadd, will OOPS.
*/
kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);
numa_default_policy();
pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
rcu_read_lock();
kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);
rcu_read_unlock();
complete(&kthreadd_done);
/*
* The boot idle thread must execute schedule()
* at least once to get things moving:
*/
init_idle_bootup_task(current);
preempt_enable_no_resched();
schedule();
preempt_disable();
/* Call into cpu_idle with preempt disabled */
cpu_idle();
}
13.kernel_init 1号线程初始化
最后对Linux应用程序进行初始化。
1号kernel_init进程完成linux的各项配置(包括启动AP)后,就会在/sbin,/etc,/bin寻找init程序来运行。
该init程序会替换kernel_init进程(注意:并不是创建一个新的进程来运行init程序,而是一次变身,使用sys_execve函数改变核心进程的正文段,将核心进程kernel_init转换成用户进程init),
此时处于内核态的1号kernel_init进程将会转换为用户空间内的1号进程init。
父进程init将根据/etc/inittab中提供的信息完成应用程序的初始化调用。
调用init_post()创建用户模式1号进程。
在init_post()中最终调用下面的任何一个入口(按顺序,第一个执行成功后将不返回)
static noinline int init_post(void)
{
/* need to finish all async __init code before freeing the memory */
async_synchronize_full();
free_initmem();
mark_rodata_ro();
system_state = SYSTEM_RUNNING;
numa_default_policy();
current->signal->flags |= SIGNAL_UNKILLABLE;
if (ramdisk_execute_command) {
run_init_process(ramdisk_execute_command);
printk(KERN_WARNING "Failed to execute %s\n",
ramdisk_execute_command);
}
/*
* 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) {
run_init_process(execute_command);
printk(KERN_WARNING "Failed to execute %s. Attempting "
"defaults...\n", execute_command);
}
run_init_process("/sbin/init");
run_init_process("/etc/init");
run_init_process("/bin/init");
run_init_process("/bin/sh");
panic("No init found. Try passing init= option to kernel. "
"See Linux Documentation/init.txt for guidance.");
}
第三阶段:Android内核启动
Android的启动过程是从进程init开始的,所以它是后续所有进程的祖先进程。(system/core/init)
1.重新设置子进程终止时信号SIGCHLD的处理函数。
2.将kernel启动过程中建立好的文件系统框架mount到相应目录。
3.open_devnull_stdio(),将init进程的标准输入、输出、出错设备设置为新建的设备节点/dev/null。
4.log_init(),创建并打开设备节点/dev/_kmsg_。
内核消息常用来调试
cat /dev/kmsg 查看实时内核打印消息
dmesg 查看过往内核消息
5.读取并解析init.rc配置文件
这在ROM开发中,是一个非常重要的节点,经常要查看与修改
先从文件/sys/class/BOOT/BOOT/boot/boot_mode读出启动方式:Factory Mode, ‘4’;ATE Factory Mode, ‘6’。看是否是facatory模式。
如果是的话,需要读取并解析两个文件:init.factory.rc和init.rc。
如果是正常启动,则暂时先读取init.rc。
这里在读取解析文件的时候,是以行为最小可执行单位在解析。
关于书写init.rc文件的脚本语言的规则,可以上网查找。
解析之后并不会马上执行,而是在init进入服务循环之前统一根据其命令本身所带的条件来执行。
6.导入kernel的cmdline,也就是u-boot传递给kernel的参数
7.读取特定平台相关的initrc文件,如init.angler.rc
8.检查解析出来的所有命令行当中是否有属于early-init的,如果有,将其提出来加入到链表action_queue之中,马上将其执行掉。
init.rc中有定义不同阶段的触发动作,early-init是最早的,其它还有on init, on fs等
9.device_init()函数将会打开uevent的netlink socket,遍历/sys/class、/sys/block、/sys/devices目录,检查各级目录的uevent文件,处理在vold服务起来之前由kernel所发出来的device add, remove等事件。
10.property_init(), 顾名思义,是属性初始化。
首先创建一个名字为system_properties的匿名共享内存区域,对并本init进程做mmap读写映射,其余共享它的进程只有读的权限。
然后将这个prop_area结构体通过全局变量system_property_area传递给property services。
接着调用函数load_properties_from_file(PROP_PATH_RAMDISK_DEFAULT)从/default.prop文件中加载编译时生成的属性。
start_property_service()函数首先会调用load_properties_from_file()函数,尝试加载一些属性脚本文件,并将其中的内容写入属性内存块里。从代码里可以看到,主要加载的文件有:
l /system/build.prop
l /system/default.prop(该文件不一定存在)
l /data/local.prop
l /data/property目录里的若干脚本
这也就是用getprop命令能获取到属性由来。
关于属性机制,展开又是一大篇
11.如果在root目录下有initlogo.rle文件存在,这个是两张android字样的缕空图片,将其读入fb中显示到LCD上。
这是android内置默认的logo机制,早被嫌弃
现在基本使用其第二分支,走动画流程,代码是BootAnimation.cpp,最终执行程序是可执行程序是/system/bin/bootanimation,关于动画制作,需绕道
12.设置相应的属性。
13.开始执行以init为trigger的命令行。
14.启动属性服务:property_set_fd = start_property_service();
15.创建一对socket,用来做信号方面的处理。
16.执行eraly-boot和boot为trigger的命令。
17.执行init.rc中以property:开头的属性设置语句,同时使能属性触发方式。
18.利用poll机制监听前面创建的几个fd的动态。
19.启动init.rc中的各种服务
其中,最重要的是zygote进程:
service zygote /system/bin/app_process -Xzygote /system/bin –zygote –start-system-server
class main
socket zygote stream 660 root system
onrestart write /sys/android_power/request_state wake
onrestart write /sys/power/state on
onrestart restart media
onrestart restart netd
详细见下篇,从zygote开始,将开启真正的Android系统的新世界