-
一、概述
- 本文针对arm linux的启动过程。以后陆续会更新start_kernel()中每个函数做的事情,这样就清楚linux启动过程中都做了哪些工作。本文及以后的文章代码均采用目前linux最新版本V3.17.
-
为了启动ARM Linux,你需要在内核之前运行一个引导程序(Boot loader).Boot loader的作用是初始化各种设备,调用Linux内核并给内核传递相关的消息。
-
依赖于CPU体系结构的代码(如设备初始化代码等)通常都放在stage1且可以用汇编语言来实现,而stage2则通常用C语言来实现,这样可以实现复杂的功能,而且有更好的可读性和移植性。
-
1、Stage1
-
start.S代码结构 u-boot的stage1代码通常放在start.S文件中,用汇编语言写成,其主要代码部分如下
-
(1)定义入口:该工作通过修改连接器脚本来完成。
-
(2)设置异常向量(Exception Vector)。
-
(3)设置CPU的速度、时钟频率及终端控制寄存器。
-
(4)初始化内存控制器。
-
(5)将ROM中的程序(即uboot的stage2的代码)复制到RAM中。
-
(6)初始化堆栈。
-
(7)转到RAM中执行stage2,该工作可使用指令ldr pc来完成。
-
2、Stage2
-
C语言代码部分 lib_arm/board.c中的start arm boot是C语言开始的函数也是整个启动代码中C语言的主函数,同时还是整个u-boot(armboot)的主函数,该函数只要完成如下操作:
-
(1)调用一系列的初始化函数。
-
(2)初始化Flash设备。
-
(3)初始化系统内存分配函数。
-
(4)如果目标系统拥有NAND设备,则初始化NAND设备。
-
(5)如果目标系统有显示设备,则初始化该类设备。
-
(6)初始化相关网络设备,填写IP、MAC地址等。
-
(7)将kernl和跟文件系统映射从flash读到ram中。
-
(8)设定内核启动参数和调用内核。
-
-
二、Linux内核入口
-
Linux 非压缩内核的入口位于文件/arch/arm/kernel/head.S 中的stext段。该段的基地址就是压缩内核解压后的跳转地址。如果系统中加载的内核是非压缩的 Image,那么bootloader将内核从 Flash中拷贝到 RAM 后将直接跳到该地址处,从而启动 Linux 内核。
-
stext函数定义在arch/arm/kernel/head.S,它的功能是获取处理器类型和机器类型信息,并创建临时的页表,然后开启MMU功能,并跳进第一个C语言函数start_kernel。
-
stext函数的在前置条件是:MMU, D-cache, 关闭; r0 = 0, r1 = machine nr, r2 = atags prointer.
-
-
ENTRY(stext)
-
ARM_BE8(setend be ) //设置 CPSR 中的端标记位,大端存储。
-
/*
-
分析如下宏定义可知,内核在GCC版本大于4.0后,可通过配置宏CONFIG_THUMB2_KERNEL来选择编译成THUMB指令集内核或ARM指令集内核。在编译成ARM指令集内核时,THUMB宏定义的代码行是不可见的。同理编译成THUMB指令集内核时,ARM宏定义的代码行无效。BSYM宏是为在THUMB指令集时访问标号处理而定义的宏。#define BSYM(sym) sym。所以在arm指令集编译时,下边THUMB代码是不可见的。
-
*/
-
THUMB( adr r9, BSYM(1f) ) //Kernel is always entered in ARM.
-
THUMB( bx r9 ) //If this is a Thumb-2 kernel,
-
THUMB( .thumb ) //switch to Thumb now.
-
THUMB(1: )
-
-
#ifdef CONFIG_ARM_VIRT_EXT
-
bl __hyp_stub_install
-
#endif
-
//确保kernel运行在SVC模式下,并且IRQ和FIRQ中断已经关闭
-
safe_svcmode_maskall r9
-
-
//从arm协处理器里面读到CPU ID存储到r9,这里的CPU主要是指arm架构相关的CPU型号,比如ARM9,ARM11等等。
-
mrc p15, 0, r9, c0, c0
-
//通过从__lookup_processor_type_data中获取处理器信息位置,通过轮询查找该内核是否支持该本处理器。其中r9是通过processor id。
-
bl __lookup_processor_type
-
//r5保存处理器信息,若为空则出错__error_p
-
movs r10, r5
-
THUMB( it eq )
-
beq __error_p
-
-
#ifdef CONFIG_ARM_LPAE
-
mrc p15, 0, r3, c0, c1, 4 @ read ID_MMFR0
-
and r3, r3, #0xf @ extract VMSA support
-
cmp r3, #5 @ long-descriptor translation table format?
-
THUMB( it lo ) @ force fixup-able long branch encoding
-
blo __error_lpae @ only classic page table format
-
#endif
-
-
#ifndef CONFIG_XIP_KERNEL
-
adr r3, 2f
-
ldmia r3, {r4, r8}
-
sub r4, r3, r4 @ (PHYS_OFFSET - PAGE_OFFSET)
-
add r8, r8, r4 @ PHYS_OFFSET
-
#else
-
ldr r8, =PLAT_PHYS_OFFSET @ always constant in this case
-
#endif
-
-
/*
-
* r1 = machine no, r2 = atags or dtb,
-
* r8 = phys_offset, r9 = cpuid, r10 = procinfo
-
*/
-
//检查atags parameter的有效性
-
bl __vet_atags
-
#ifdef CONFIG_SMP_ON_UP
-
bl __fixup_smp
-
#endif
-
#ifdef CONFIG_ARM_PATCH_PHYS_VIRT
-
bl __fixup_pv_table
-
#endif
-
//创建临时页表,它所要做的工作就是将RAM基地址开始的4M空间的物理地址映射到0xC0000000开始的虚拟地址处
-
bl __create_page_tables
-
-
//将__mmap_switched这个symbol的链接地址放在sp里面
-
ldr r13, =__mmap_switched //address to jump to after
-
-
//把标号1处地址赋给lr //mmu has been enabled
-
adr lr, BSYM(1f) //return (PIC) address
-
mov r8, r4 //set TTBR1 to swapper_pg_dir
-
ARM( add pc, r10, #PROCINFO_INITFUNC )
-
THUMB( add r12, r10, #PROCINFO_INITFUNC )
-
THUMB( mov pc, r12 )
-
1:
-
b __enable_mmu
-
ENDPROC(stext)
-
-
//从__lookup_processor_type_data中获取处理器信息位置
-
__lookup_processor_type:
-
adr r3, __lookup_processor_type_data //R3指向类型数据指针
-
////读取R3指向地址三个WORD数据到R4,R5,R6,R4=标号3处的虚拟地址,r5=__arch_info_begin,r6=__arch_info_end。
-
ldmia r3, {r4 - r6}
-
sub r3, r3, r4 //物理地址-虚拟地址get offset between virt&phys
-
add r5, r5, r3 //将R5的虚拟地址转换成物理地址,R5为类型数据起始地址
-
add r6, r6, r3 //将R6的虚拟地址转换成物理地址,R6为类型数据结束地址
-
1:
-
ldmia r5, {r3, r4} //从R5指向的地址读取两个WORD到R3,R4
-
and r4, r4, r9 //获取判断位
-
teq r3, r4 //判断本处理器类型是否已找到
-
beq 2f //若已找到,跳转到2:执行
-
add r5, r5, #PROC_INFO_SZ //若未找到,R5指针增到一个类型数据长度sizeof(proc_info_list)
-
cmp r5, r6 //是否已查找完所有处理器类型
-
blo 1b //未查找完返回1标号处继续循环
-
mov r5, #0 //若未找到本处理器类型信息,返回值R5设置为NULL
-
2:
-
mov pc, lr
-
ENDPROC(__lookup_processor_type)
-
-
//__lookup_processor_type_data数据结构定义位于文件\linux-xlnx\arch\arm\kernel\head-common.S,参见汇编代码可知,该数据结构有三个数据,数据内容参见代码注释。
-
.align 2
-
.type __lookup_processor_type_data, %object
-
__lookup_processor_type_data:
-
.long . //当前内存地址(此处为虚拟地址)
-
.long __proc_info_begin //处理器类型信息起始地址
-
.long __proc_info_end //处理器类型信息结束地址
-
.size __lookup_processor_type_data, . - __lookup_processor_type_data
-
-
//_proc_info_begin,__proc_info_end定义位于\linux-xlnx\arch\arm\kernel\vmlinux.lds.S,它用于存储.proc.info.init段的起始地址及结束地址。
-
VMLINUX_SYMBOL(__proc_info_begin) = .; \
-
*(.proc.info.init) \
-
VMLINUX_SYMBOL(__proc_info_end) = .;
-
//.proc.info.init段定义位于\linux-xlnx\arch\arm\mm\proc-v7.S,该文件定义了本内核版本支持的ARM v7架构下处理器类型等。
-
-
//检查bootloader传入的参数列表atags的合法性
-
__vet_atags:
-
tst r2, #0x3 //r2指向该参数链表的起始位置,此处判断它是否字对齐
-
bne 1f
-
-
ldr r5, [r2, #0] //获取第一个tag结构的size
-
#ifdef CONFIG_OF_FLATTREE
-
ldr r6, =OF_DT_MAGIC //is it a DTB?
-
cmp r5, r6
-
beq 2f
-
#endif
-
//#define??ATAG_CORE_SIZE??((2*4??+??3*4)??>>??2)??判断该tag的长度是否合法
-
cmp r5, #ATAG_CORE_SIZE
-
cmpne r5, #ATAG_CORE_SIZE_EMPTY
-
bne 1f
-
ldr r5, [r2, #4] //??获取第一个tag结构体的标记
-
ldr r6, =ATAG_CORE //判断第一个tag结构体的标记是不是ATAG_CORE
-
cmp r5, r6
-
bne 1f
-
2:
-
mov pc, lr //正常退出
-
1:
-
mov r2, #0
-
mov pc, lr //参数链表不正确
-
ENDPROC(__vet_atags)
-
-
__mmap_switched:
-
adr r3, __mmap_switched_data
-
-
ldmia {r4, r5, r6, r7}
-
cmp r4, r5 @ Copy data segment if needed
-
1: cmpne r5, r6
-
ldrne fp, [r4], #4
-
strne fp, [r5], #4
-
bne 1b
-
-
mov fp, #0 @ Clear BSS (and zero fp)
-
1: cmp r6, r7
-
strcc fp, [r6],#4
-
bcc 1b
-
-
ARM( ldmia r3, {r4, r5, r6, r7, sp})
-
THUMB( ldmia r3, {r4, r5, r6, r7} )
-
THUMB( ldr sp, [r3, #16] )
-
str r9, [r4] @ Save processor ID
-
str r1, [r5] @ Save machine type
-
str r2, [r6] @ Save atags pointer
-
cmp r7, #0
-
bicne r4, r0, #CR_A @ Clear 'A' bit
-
stmneia r7, {r0, r4} @ Save control register values
-
b start_kernel //跳转到start_kernel继续初始化
-
ENDPROC(__mmap_switched)
-
-
二、start kernel初始化
-
asmlinkage __visible void __init start_kernel(void)
-
{
-
char * command_line;
-
//地址指针,指向内核启动参数在内存中的位置(虚拟地址)
-
extern const struct kernel_param __start___param[], __stop___param[];
-
-
//对记录各锁之间依赖关系的结构体进行初始化???!!!
-
lockdep_init();
-
-
//指定当前的cpu的逻辑号,这个函数对应于对称多处理器的设置,当系统中只有一个cpu的情况,此函数为空
-
smp_setup_processor_id();
-
-
//用于内核的对象调试。依赖配置CONFIG_DEBUG_OBJECTS
-
debug_objects_early_init();
-
-
//初始化防止栈溢出攻击保护的堆栈。
-
boot_init_stack_canary();
-
-
//一组进程的行为控制,数据结构和其中链表的初始化
-
cgroup_init_early();
-
-
//关闭本地中断,因为尚未对中断代码和向量表、中断处理器进行初始化,所以必须关中断
-
local_irq_disable();
-
-
//用来协助调试,执行当前初始启动代码时,对无效的中断激活事件输出警告
-
early_boot_irqs_disabled = true;
-
-
//激活当前CPU
-
boot_cpu_init();
-
-
//如果有高端内存,则初始化高端内存page_address_htable数组
-
page_address_init();
-
pr_notice("%s", linux_banner);
-
-
//架构初始化相关
-
setup_arch(&command_line);
-
-
//初始化init_mm结构体,owner指向init_task,即init_mm->owner = init_task;
-
mm_init_owner(&init_mm, &init_task);
-
-
//设置init_mm的cpu_mask
-
mm_init_cpumask(&init_mm);
-
-
//对command_line进行备份与保存
-
setup_command_line(command_line);
-
-
//设置nr_cpu_ids变量,即cpu id最大值
-
setup_nr_cpu_ids();
-
-
//用于为每个cpu的per-cpu变量副本分配空间,注意这时alloc内存分配器还没建立起来,通过memblock为初始化期间的这些变量副本分配物理空间。
-
setup_per_cpu_areas();//参考per-cpu.c文档
-
-
//设置arm的TPIDRPRW寄存器
-
smp_prepare_boot_cpu();
-
-
//因为前边在setup_arch中已将节点描述符、管理区描述符和页描述符做了初始化,这里进一步的建立系统内存页区(zone)链表
-
build_all_zonelists(NULL, NULL);
-
-
//会调用hotcpu_notifier函数。在编译选项CONFIG_HOTPLUG_CPU起作用时,这个函数才有效。这个编译选项就热插拔技术,而且是CPU的热插拔
-
page_alloc_init();
-
-
pr_notice("Kernel command line: %s\n", boot_command_line);
-
-
//再次处理early_param定义的参数,在setup_arch调用过一次,可参考
-
parse_early_param();
-
-
//解析Booting kernel传进来的参数
-
parse_args("Booting kernel", static_command_line, __start___param,__stop___param - __start___param,-1, -1, &unknown_bootoption);
-
-
//处理静态定义的jump label,执行跳转指令或是nop指令
-
jump_label_init();
-
-
//使用memblock分配一个启动信息的缓冲区
-
setup_log_buf(0);
-
-
//使用bootmem分配并初始化PID散列表
-
pidhash_init();
-
-
//虚拟文件系统的早期初始化有函数vfs_caches_init_early()实现,主要负责dentry和inode的hashtable的初始化工作。
-
vfs_caches_init_early();
-
-
//对内核异常表进行排序
-
sort_main_extable();
-
-
//对内核陷阱异常经行初始化,ARM架构中位空函数
-
trap_init();
-
-
//初始化内核内存分配器,过度到伙伴系统,启动slab机制,初始化非连续内存区
-
//以后分配内存不能使用memblock来分配了
-
mm_init();
-
-
//初始化调度器数据结构并创建运行队列???
-
sched_init();
-
-
//禁止内核抢占,关中断
-
preempt_disable();
-
if (WARN(!irqs_disabled(), "Interrupts were enabled *very* early, fixing it\n"))
-
local_irq_disable();
-
-
//创建idr layer的高速缓存cache,参考idr机制文档
-
idr_init_cache();
-
-
//初始化系统中"读-写-拷贝"同步机制???
-
rcu_init();
-
-
//cpu处在idle状态时关闭tick中断的优化初始化
-
tick_nohz_init();
-
-
//没有设置CONFIG_CONTEXT_TRACKING_FORCE宏,不会调用该函数
-
context_tracking_init();
-
-
//radix树的初始化,供页面查找
-
radix_tree_init();
-
-
//中断初始化,初始化irq_desc数组
-
early_irq_init();
-
-
//中断初始化,调用特定平台的中断初始化的init_irq()函数,参考early_irq_init.c
-
init_IRQ();
-
-
//时钟滴答的初始化,见timer_init.c
-
tick_init();
-
-
//初始化本CPU上的软件时钟相关的数据结构,注册时钟软中断TIMER_SOFTIRQ,见timer_init.c
-
init_timers();
-
-
//高分辨率定时器框架初始化,见timer_init.c
-
hrtimers_init();
-
-
//软中断初始化,参考软中断文档
-
softirq_init();
-
-
//初始化需要和时钟代码共同管理的时间相关值
-
timekeeping_init();
-
-
//调用平台设置的time初始化函数
-
time_init();
-
-
//进程调度时钟初始化
-
sched_clock_postinit();
-
-
//CPU性能监视机制初始化,此机制包括CPU同一时间执行指令数,cache??miss数,分支预测失败次数等性能参数???
-
perf_event_init();
-
-
//对内核的profile(一个内核性能调式工具)功能进行初始化,分配好存放profile信息的内存
-
profile_init();
-
-
//smp下跨cpu的函数传递初始化,参考smp文档
-
call_function_init();
-
WARN(!irqs_disabled(), "Interrupts were enabled early\n");
-
early_boot_irqs_disabled = false;
-
-
//使能本地中断
-
local_irq_enable();
-
-
//slab分配器后期初始化
-
kmem_cache_init_late();
-
-
//初始化控制台,参考console驱动文档
-
console_init();
-
-
//检查内核恐慌标准,如果有问题,打印信息
-
if (panic_later)
-
panic("Too many boot %s vars at `%s'", panic_later,panic_param);
-
-
//打印lockdep调试模块信息
-
lockdep_info();
-
-
//锁测试
-
locking_selftest();
-
-
//检查initrd的位置是否符合要求
-
#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_cgroup_init();
-
-
//调试相关,参考debug_objects_early_init文档
-
debug_objects_mem_init();
-
-
//内存泄露检测机制的初始化
-
kmemleak_init();
-
-
//为zone中用于实现单一页框的特殊高速缓存设置单一页面数量
-
setup_per_cpu_pageset();
-
-
//一致性内存访问(NUMA)初始化,设置NUMA系统中的内存策略。arm非NUMA系统,不考虑
-
numa_policy_init();
-
-
//arm中为NULL,不掉用
-
if (late_time_init)
-
late_time_init();
-
-
//初始化调度时钟
-
sched_clock_init();
-
-
//用于BogoMIPS,该值显示1jiffy内消耗多少cpu
-
calibrate_delay();
-
-
//进程PID位图分配映射初始化
-
pidmap_init();
-
-
//创建anon_vma的slab缓存
-
anon_vma_init();
-
-
//acpi相关初始化,忽略
-
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_info的slab高速缓存
-
thread_info_cache_init();
-
-
//为对象的每个用户赋予资格
-
cred_init();
-
-
//进程创建机制初始化
-
fork_init(totalram_pages);
-
-
//创建进程所需的各结构体slab缓存
-
proc_caches_init();
-
-
//为buffer_head结构体创建slab高速缓存
-
buffer_init();
-
-
//准备密钥
-
key_init();
-
-
//内核安全框架初始化
-
security_init();
-
-
//内核调试系统后期初始化,kgdb初始化
-
dbg_late_init();
-
-
//初始化VFS中使用的多种缓存
-
vfs_caches_init(totalram_pages);
-
-
//创建sigqueue结构高速缓存
-
signals_init();
-
-
//页回写机制初始化
-
page_writeback_init();
-
-
//注册并初始化proc文件系统
-
proc_root_init();
-
-
//注册未能初始化的cgroup子系统
-
cgroup_init();
-
-
//注册cpuset文件系统
-
cpuset_init();
-
-
//初始化任务统计信息接口
-
taskstats_init_early();
-
-
//为管理延迟信息做准备
-
delayacct_init();
-
-
//检查写缓冲一致性
-
check_bugs();
-
-
//simple firmware interface
-
sfi_init_late();
-
-
if (efi_enabled(EFI_RUNTIME_SERVICES)) {
-
efi_late_init();
-
efi_free_boot_services();
-
}
-
-
//trace初始化
-
ftrace_init();
-
-
//剩余的初始化
-
rest_init();
-
}
-
-
-
static inline void mm_init_cpumask(struct mm_struct *mm)
-
{
-
#ifdef CONFIG_CPUMASK_OFFSTACK
-
mm->cpu_vm_mask_var = &mm->cpumask_allocation;
-
#endif
-
}
-
-
static void __init setup_command_line(char *command_line)
-
{
-
//把刚才在setup_arch()中拷贝进来的command_line,拷贝到全局变量saved_command_line和static_command_line所指向的内存单元中。
-
//通过memblock分配内存空间,参考memblock文档
-
saved_command_line = memblock_virt_alloc(strlen(boot_command_line) + 1, 0);
-
initcall_command_line = memblock_virt_alloc(strlen(boot_command_line) + 1, 0);
-
static_command_line = memblock_virt_alloc(strlen(command_line) + 1, 0);
-
-
strcpy (saved_command_line, boot_command_line);
-
strcpy (static_command_line, command_line);
-
}
-
-
//nr_cpu_ids是一个特殊的值,在单CPU情况下是1;而SMP情况下,又是一个全局变量,被find_last_bit函数设置.
-
void __init setup_nr_cpu_ids(void)
-
{
-
nr_cpu_ids = find_last_bit(cpumask_bits(cpu_possible_mask),NR_CPUS) + 1;
-
}
-
-
//根据smp和cpu架构
-
void __init smp_prepare_boot_cpu(void)
-
{
-
set_my_cpu_offset(per_cpu_offset(smp_processor_id()));
-
}
-
-
#if defined(CONFIG_SMP) && !defined(CONFIG_CPU_V6)
-
static inline void set_my_cpu_offset(unsigned long off)
-
{
-
/* Set TPIDRPRW */
-
asm volatile("mcr p15, 0, %0, c13, c0, 4" : : "r" (off) : "memory");
-
}
-
#else
-
#define set_my_cpu_offset(x) do {} while(0)
- #endif /* CONFIG_SMP */