Android启动大致分为三个阶段
其中Android启动又可以分为下面的步骤
大致过程如下图所示:
其中1,2,3是linux启动过程,4,5,6是android启动过程。
init进程是Android系统中用户空间的第一个进程,作为第一个进程,它被赋予了很多极其重要的工作职责,比如创建zygote(孵化器)和属性服务等。init进程是由多个源文件共同组成的,这些文件位于源码目录system/core/init。
也就是说,init进程负责两件事
在Android中,虚拟机,应用程序进程以及运行系统的关键服务的SystemServer进程都是由Zygote进程来创建的,我们也将它称为孵化器。它通过fock(复制进程)的形式来创建应用程序进程和SystemServer进程,由于Zygote进程在启动时会创建DVM,因此通过fock而创建的应用程序进程和SystemServer进程可以在内部获取一个DVM的实例拷贝。
也就是说,Zygote进程主要负责下面三件事
SystemServer的进程名实际上叫做“system_server”,通常简称为SS。
系统中的服务驻留在其中,常见的比如WindowManagerServer(Wms)、ActivityManagerSystemService(AmS)、 PackageManagerServer(PmS)等,这些系统服务都是以一个线程的方式存在于SystemServer进程中。
也就是说,在SystemServer中,主要负责各种服务的创建。
当按开机键的时候,引导芯片开始从固化在ROM的预设代码开始执行,然后加载引导程序到RAM。这个引导程序就是BootLoader()。因为笔者没有做过单片机的相关学习,所以下面的内容是我根据网上的资料整理而来。文末会附上链接。
BootLoaderd 分为两个阶段,一个阶段是汇编部分,一个阶段是C语言部分.
另外,关于BootLoader有很多的实现,下面以一个比较通用的BootLoader实现 uBoot为例
目录:cpu/arm920t/start.S
a.设置CPU进入SVC模式(系统管理模式),cpsr[4:0]=0xd3
#include
#include
//u-boot的主入口,跳入了后面的start_code
.globl _start
//这些是跳转向量表,和芯片的体系结构有关
// ldr语句的意思是将第二个操作数(如:_undefined_instruction)指向的地址数据传给PC
_start: b start_code
ldr pc, _undefined_instruction
ldr pc, _software_interrupt
ldr pc, _prefetch_abort
ldr pc, _data_abort
ldr pc, _not_used
ldr pc, _irq
ldr pc, _fiq
//.word 为定义一个4字节的空间 undefined_instruction 为地址, 即后面标号所对的偏移地址数据
//undefined_instruction 为地址, 即后面标号所对的偏移地址数据
_undefined_instruction: .word undefined_instruction
_software_interrupt: .word software_interrupt
_prefetch_abort: .word prefetch_abort
_data_abort: .word data_abort
_not_used: .word not_used
_irq: .word irq
_fiq: .word fiq
//16字节对齐,并以0xdeadbeef填充,它是个Magic number 。
.balignl 16,0xdeadbeef
b.接着进入Start_code中:设置CPU进入SVC模式。
/*
* the actual start code
*/
start_code:
/*
* set the cpu to SVC32 mode
*/
mrs r0, cpsr
bic r0, r0, #0x1f
orr r0, r0, #0xd3
msr cpsr, r0
bl coloured_LED_init
bl red_LED_on
c.关看门狗,WTCON = 0x0,并设置寄存器地址
”watchdog”,俗称“看门狗”。“Watchdog” 在实现上可以是硬件电路也可以是软件定时器,能够在系统出现故障时自动重新启动系统。
复制代码
/* turn off the watchdog */
# if defined(CONFIG_S3C2400)
# define pWTCON 0x15300000
# define INTMSK 0x14400008 /* Interupt-Controller base addresses */
# define CLKDIVN 0x14800014 /* clock divisor register */
#else
# define pWTCON 0x53000000
# define INTMSK 0x4A000008 /* Interupt-Controller base addresses */
# define INTSUBMSK 0x4A00001C
# define CLKDIVN 0x4C000014 /* clock divisor register */
# endif
//关看门狗
ldr r0, =pWTCON
mov r1, #0x0
str r1, [r0]
d.关中断,INTMSK=0xFFFFFFFF, INTSUBMSK=0x3FF。
/*
* mask all IRQs by setting all bits in the INTMR - default
*/
mov r1, #0xffffffff
ldr r0, =INTMSK
str r1, [r0]
# if defined(CONFIG_S3C2410)
ldr r1, =0x3ff
ldr r0, =INTSUBMSK
str r1, [r0]
# endif
e.时钟设置CLKDIVN=0x3 , FCLK:HCLK:PCLK = 1:2:4
/* FCLK:HCLK:PCLK = 1:2:4 */
/* default FCLK is 120 MHz ! */
ldr r0, =CLKDIVN
mov r1, #3
str r1, [r0]
#endif /* CONFIG_S3C24X0 */
f.询问是否进行CPU初始化
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
bl cpu_init_crit
#endif
g.初始化堆栈,如果要在C语言环境下执行代码,就必须初始化堆栈
/* Set up the stack */
stack_setup:/* 设置栈指针 */
ldr r0, _TEXT_BASE /* upper 128 KiB: relocated uboot */
sub r0, r0, #CONFIG_SYS_MALLOC_LEN /* malloc area */
sub r0, r0, #CONFIG_SYS_GBL_DATA_SIZE /* bdinfo */
#ifdef CONFIG_USE_IRQ
sub r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ)
#endif
sub sp, r0, #12 /* leave 3 words for abort-stack */
clear_bss:
ldr r0, _bss_start /* find start of bss segment */
ldr r1, _bss_end /* stop here */
mov r2, #0x00000000 /* clear */
clbss_l:str r2, [r0] /* clear loop... */
add r0, r0, #4
cmp r0, r1
ble clbss_l
只要将sp指针指向一段没有被使用的内存就完成栈的设置了。根据上面的代码可以知道U-Boot内存使用情况了,如下图所示:
h.CPU的初始化,即cpu_init_crit函数,完成以后回到主函数
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
cpu_init_crit:
/*
* flush v4 I/D caches
*/
mov r0, #0
mcr p15, 0, r0, c7, c7, 0 /* flush v3/v4 cache */
mcr p15, 0, r0, c8, c7, 0 /* flush v4 TLB */
/*
* disable MMU stuff and caches
*/
mrc p15, 0, r0, c1, c0, 0
bic r0, r0, #0x00002300 @ clear bits 13, 9:8 (--V- --RS)
bic r0, r0, #0x00000087 @ clear bits 7, 2:0 (B--- -CAM)
orr r0, r0, #0x00000002 @ set bit 2 (A) Align
orr r0, r0, #0x00001000 @ set bit 12 (I) I-Cache
mcr p15, 0, r0, c1, c0, 0
/*
* before relocating, we have to setup RAM timing
* because memory timing is board-dependend, you will
* find a lowlevel_init.S in your board directory.
*/
mov ip, lr
bl lowlevel_init
mov lr, ip
mov pc, lr
#endif /* CONFIG_SKIP_LOWLEVEL_INIT */
i.清除bss段
clear_bss: /* 清除bss段 */
ldr r0, _bss_start /* r0 = bss段的起始位置 */
ldr r1, _bss_end @ stop here /* r1 = bss段结束位置 */
mov r2, #0x0 @ clear value /* r2 = 0 */
clbss_l:
str r2, [r0] @ clear BSS location /* 先将r2,即0x0,存到地址为r0的内存中去 */
cmp r0, r1 @ are we at the end yet /* 比较r0地址和r1地址,即比较当前地址是否到了bss段的结束位置 */
add r0, r0, #4 @ increment clear index pointer /* 然后r0地址加上4 */
bne clbss_l @ keep clearing till at end /* 如果不等于,那么就跳到clbss_l,即接着这几个步骤,直到地址超过了bss的_end位置,即实现了将整个bss段,都清零。*/
j.从这里跳转到第二阶段C代码中去
ldr pc, _start_armboot
_start_armboot: .word start_armboot
汇编第一阶段的代码主要可以分为以下部分:
目录:u-boot-2010.06\arch\arm\lib\board.c
第二阶段主要用到了两个数据结构即 gd_t 和 bd_t,这两个类型变量记录了刚启动时的信息,还将记录作为引导内核和文件系统的参数,如 bootargs 等,并且将来还会在启动内核时,由 uboot 交由 kernel 时会有所用。其定义如下:
gd_t :
#### u-boot-2010.06\arch\arm\include\asm\global_data.h ####
/* U-Boot使用了一个存储在寄存器中的指针gd来记录全局数据区的地址,这个指针存放在指定的寄存器r8中 */
typedef struct global_data { /* 全局数据结构 */
bd_t *bd; /* 指向板级信息结构 */
unsigned long flags; /* 标记位 */
unsigned long baudrate; /* 串口波特率 */
unsigned long have_console; /* serial_init() was called */
unsigned long env_addr; /* 环境参数地址 */
unsigned long env_valid; /* 环境参数 CRC 校验有效标志 */
unsigned long fb_base; /* fb 起始地址 */
#ifdef CONFIG_VFD
unsigned char vfd_type; /* 显示器类型(VFD代指真空荧光屏) */
#endif
#ifdef CONFIG_FSL_ESDHC /* 宏未定义 */
unsigned long sdhc_clk;
#endif
#if 0 /* 未定义 */
unsigned long cpu_clk; /* cpu 频率*/
unsigned long bus_clk; /* bus 频率 */
phys_size_t ram_size; /* ram 大小 */
unsigned long reset_status; /* reset status register at boot */
#endif
void **jt; /* 跳转函数表 */
} gd_t;
bd_t
typedef struct bd_info { /* 板级信息结构 */
int bi_baudrate; /* 波特率 */
unsigned long bi_ip_addr; /* IP地址 */
struct environment_s *bi_env; /* 板子的环境变量 */
ulong bi_arch_number; /* 板子的 id */
ulong bi_boot_params; /* 板子的启动参数 */
struct /* RAM 配置 */
{
ulong start;
ulong size;
} bi_dram[CONFIG_NR_DRAM_BANKS];
} bd_t;
a.tart_armboot 首先为全局数据结构和板级信息结构分配内存
gd = (gd_t*)(_armboot_start - CONFIG_SYS_MALLOC_LEN - sizeof(gd_t));
/* compiler optimization barrier needed for GCC >= 3.4 */
__asm__ __volatile__("": : :"memory"); /* 内存屏障,防止编译器优化 */
memset ((void*)gd, 0, sizeof (gd_t)); /* 将指定的内存地址清零( 将全局数据清零 ) */
gd->bd = (bd_t*)((char*)gd - sizeof(bd_t)); /* gd->bd指向一块地址( 取得板级信息数据结构的起始地址 ) */
memset (gd->bd, 0, sizeof (bd_t)); /* gd->db指向地址中的内容清零( 将板级信息清零 ) */
gd->flags |= GD_FLG_RELOC; /* 标记为代码已经转移到 RAM */
b.调用 init_sequence数组中的函数指针完成各部分的初始化
init_fnc_t *init_sequence[] = {
#if defined(CONFIG_ARCH_CPU_INIT)
arch_cpu_init, /* 基本的处理器相关配置 -- basic arch cpu dependent setup */
#endif
timer_init, /* 初始化定时器 -- initialize timer before usb init */
board_init, /* 板级特殊设备初始化(很重要) -- basic board dependent setup */
#if defined(CONFIG_USE_IRQ)
interrupt_init, /* 中断初始化 -- set up exceptions */
#endif
// timer_init, /* 初始化定时器 */
#ifdef CONFIG_FSL_ESDHC
get_clocks,
#endif
env_init, /* 初始化环境变量(默认的环境变量) -- initialize environment */
init_baudrate, /* 初始化波特率设置 -- initialze baudrate settings */
serial_init, /* 初始化串口 */
console_init_f, /* 控制台初始化 -- stage 1 init of console */
display_banner, /* 打印uboot版本信息 -- say that we are here */
#if defined(CONFIG_DISPLAY_CPUINFO)
print_cpuinfo, /* 显示cpu信息 -- display cpu info (and speed) */
#endif
#if defined(CONFIG_DISPLAY_BOARDINFO)
checkboard, /* 显示板级信息 -- display board info */
#endif
#if defined(CONFIG_HARD_I2C) || defined(CONFIG_SOFT_I2C)
init_func_i2c, /* 初始化IIC,hard:真正iic,soft:gpio模拟iic */
#endif
dram_init, /* 配置可用RAM -- configure available RAM banks */
#if defined(CONFIG_CMD_PCI) || defined (CONFIG_PCI)
arm_pci_init, /* 初始化pci */
#endif
NULL,
};
/* 函数指针,执行指针数组中的内容(实际内容为函数指针),初始化cpu、总线、设备等等*/
for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
if ((*init_fnc_ptr)() != 0) {
hang ();
}
}
void hang (void) {
puts ("### ERROR ### Please RESET the board ###\n");
for (;;);
}
c.start_armboot 在接下来的流程中还做了如下操作:
void start_armboot (void)
{
…
nand_init(); /* 初始化 NAND */
…
mmc_initialize(0); /* 初始化MMC */
mmc_flash_init(0);
env_relocate () /* 重定位环境变量,将其从 NAND 拷贝到内存中 */
…
gd->bd->bi_ip_addr = getenv_IPaddr ("ipaddr"); /* 设置IP地址 */
stdio_init (); /* 初始化外设 */
jumptable_init (); /* 初始化跳转函数表 */
…
console_init_r (); /* 控制台初始化第二阶段 */
…
misc_init_r (); /* 杂项设备初始化, eg:battery */
…
enable_interrupts (); /* 使能中断 */
#ifdef CONFIG_KEDACOM_E2PROM
extern int kd_set_ethaddr();
kd_set_ethaddr();
#endif
…
/* 如果存在则从环境变量中读取装载地址,其默认为 ulong load_addr = CONFIG_SYS_LOAD_ADDR; */
if ((s = getenv ("loadaddr")) != NULL) {
load_addr = simple_strtoul (s, NULL, 16);
}
#if defined(CONFIG_CMD_NET)
if ((s = getenv ("bootfile")) != NULL) {
copy_filename (BootFile, s, sizeof (BootFile));
}
#endif
…
#if defined(CONFIG_CMD_NET)
…
eth_initialize(gd->bd); /* 网络初始化 */
…
#endif
#if defined(CONFIG_BOOTROM_SUPPORT)
extern void download_boot(const int (*handle)(void));
download_boot(NULL);
#endif
product_control();
…
#ifdef CONFIG_PARTTAB_ON_FLASH
partition_check_update_flags();
#endif
/* main_loop() can return to retry autoboot, if so just run it again. */
for (;;) {
main_loop (); /* 进入主循环 common/main.c */
}
}
d.main_loop 函数
#### u-boot-2010.06\arch\arm\lib\board.c ####
void main_loop (void)
{
…
setenv ("ver", version_string); /* 设置版本信息 */
…
update_tftp ();
….
#if defined(CONFIG_BOOTDELAY) && (CONFIG_BOOTDELAY >= 0)
s = getenv ("bootdelay"); /* 获取bootdelay环境变量的值 */
bootdelay = s ? (int)simple_strtol(s, NULL, 10) : CONFIG_BOOTDELAY; /* 将字符串转换为long类型变量 */
debug ("### main_loop entered: bootdelay=%d\n\n", bootdelay);
debug ("### main_loop: bootcmd=\"%s\"\n", s ? s : "" );
/* 倒数读秒,如果delay时间内没有操作,执行run_command命令 */
if (bootdelay >= 0 && s && !abortboot (bootdelay)) {
run_command (s, 0);
}
#endif /* CONFIG_BOOTDELAY */
…
for (;;) {
…
len = readline (CONFIG_SYS_PROMPT); /* 读取输入 */
flag = 0; /* assume no special flags for now */
if (len > 0)
strcpy (lastcommand, console_buffer); /* 将输入保存到历史记录中 */
else if (len == 0)
flag |= CMD_FLAG_REPEAT; /* 如果没有输入则重复上次 */
…
if (len == -1)
puts ("\n" );
else
rc = run_command(lastcommand, flag); /* 执行命令 */
lastcommand[0] = 0; /* 将命令置无效命令令其不可重复 */
}
}
e.通过一个函数指针 thekernel()带三个参数跳转到内核( zImage )入口点开始执行
此时, u-boot 的任务已经完成,控制权完全交给内核( zImage )。在 uBoot 的文件lib_arm\bootm.c中定义了 thekernel, 并在 do_bootm_linux 的最后执行 thekernel。定义thekernel函数指针,获取bootargs参数给commandline指针。theKernel (0, machid, bd->bi_boot_params);第一个参数必须为0,第二个参数为机器类型ID,第三个参数为传递给内核参数的起始地址0x30000100
int do_bootm_linux(int flag, int argc, char *argv[], bootm_headers_t *images)
{
bd_t *bd = gd->bd;
char *s;
int machid = bd->bi_arch_number;
void (*theKernel)(int zero, int arch, uint params);
int ret;
#ifdef CONFIG_CMDLINE_TAG
char *commandline = getenv ("bootargs");
#endif
if ((flag != 0) && (flag != BOOTM_STATE_OS_GO))
return 1;
theKernel = (void (*)(int, int, uint))images->ep;
s = getenv ("machid");
if (s) {
machid = simple_strtoul (s, NULL, 16);
printf ("Using machid 0x%x from environment\n", machid);
}
ret = boot_get_ramdisk(argc, argv, images, IH_ARCH_ARM,
&(images->rd_start), &(images->rd_end));
if(ret)
printf("[err] boot_get_ramdisk\n");
show_boot_progress (15);
debug ("## Transferring control to Linux (at address %0 lx) ...\n",
(ulong) theKernel);
#if defined (CONFIG_SETUP_MEMORY_TAGS) || \
defined (CONFIG_CMDLINE_TAG) || \
defined (CONFIG_INITRD_TAG) || \
defined (CONFIG_SERIAL_TAG) || \
defined (CONFIG_REVISION_TAG) || \
defined (CONFIG_LCD) || \
defined (CONFIG_VFD)
setup_start_tag (bd);
#ifdef CONFIG_SERIAL_TAG
setup_serial_tag (¶ms);
#endif
#ifdef CONFIG_REVISION_TAG
setup_revision_tag (¶ms);
#endif
#ifdef CONFIG_SETUP_MEMORY_TAGS
setup_memory_tags (bd);
#endif
#ifdef CONFIG_CMDLINE_TAG
setup_commandline_tag (bd, commandline);
#endif
#ifdef CONFIG_INITRD_TAG
if (images->rd_start && images->rd_end)
setup_initrd_tag (bd, images->rd_start, images->rd_end);
#endif
#if defined (CONFIG_VFD) || defined (CONFIG_LCD)
setup_videolfb_tag ((gd_t *) gd);
#endif
setup_end_tag (bd);
#endif
/* we assume that the kernel is in place */
printf ("\nStarting kernel ...\n\n");
#ifdef CONFIG_USB_DEVICE
{
extern void udc_disconnect (void);
udc_disconnect ();
}
#endif
cleanup_before_linux ();
theKernel (0, machid, bd->bi_boot_params);
/* does not return */
return 1;
}
小结,第二阶段代码可以分为下面部分
为gd、bd数据结构分配地址,并清零
关于Linux系统启动主要分为三个阶段,第一个阶段是自解压过程,第二个是设置ARM处理器的工作模式、设置一级页表等,第三个阶段主要是C代码,包括Android的初始化的全部工作。
* Non-board-specific low-level startup code
*
* Copyright (C) 2004-2006 Atmel Corporation
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include
#include
.section .init.text,"ax"
.global kernel_entry
kernel_entry:
/* Start the show */
lddpc pc, kernel_start_addr
.align 2
kernel_start_addr:
.long start_kernel
从上面的代码我们看出,最终调用了start_kernel方法
asmlinkage __visible void __init start_kernel(void)
{
char *command_line;
char *after_dashes;
/*
* Need to run as early as possible, to initialize the lockdep hash:
* 需要尽早运行,初始化lockdep散列:
*/
lockdep_init();
set_task_stack_end_magic(&init_task);
smp_setup_processor_id();
debug_objects_early_init();
/*
* Set up the the initial canary ASAP:
* 尽快设置初始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
*/
boot_cpu_init();
page_address_init();
pr_notice("%s", linux_banner);
setup_arch(&command_line);
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, NULL);
page_alloc_init();
pr_notice("Kernel command line: %s\n", boot_command_line);
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);
jump_label_init();
/*
* 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.
*/
//初始化每个处理器的可运行队列,设置系统初始化进程即0号进程
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();
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();
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();
signals_init();
/* rootfs populating might need page-writeback */
page_writeback_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 */
//调用kernel_thread()创建1号内核线程
rest_init();
}
start_kernel()函数中执行了大量的初始化操作:
Linux下有三个特殊的进程,idle(swapper)进程(PID = 0),init进程(PID = 1)和看threadd(PID = 2)
前面的文章主要分析了2,3过程。
即BootLoader和Linux内核的启动过程。
下一篇介绍Android初始化语言。
之后继续分析init ,zygote,SystemService进程
《Android高级进阶》
《深入理解android 卷一》
U-Boot – uboot代码深度解析
Android启动流程简析