linux内核启动分析
嵌入式Linux启动后,会先运行系统的bootloader,一般是用开源的U-boot;
Broadcom芯片的bootloader则是自己公司的CFE。
bootloader前面文章有大体介绍了一下,以下主要分析Linux内核的启动过程,以MIPS芯片为例。
内核版本:2.6.31
Bootloader将Linux内核映像拷贝到RAM中某个空闲地址处,然后一般有个内存移动操作,目的地址在
arch/mips/Makefile内指定:
load-$(CONFIG_MIPS_PB1550)
+= 0xFFFFFFFF80100000,
则最终bootloader定会将内核移到物理地址
0x00100000 处。
上面Makefile 里指定的的 load 地址,最后会被编译系统写入到 arch/mips/kernel/vmlinux.lds 中:
OUTPUT_ARCH(mips)
ENTRY(kernel_entry)
jiffies = jiffies_64;
SECTIONS
{
. = 0xFFFFFFFF80100000;
/* read-only */
_text = .; /* Text and read-only data */
.text : {
*(.text)
...
这个文件最终会以参数 -Xlinker
--script -Xlinker vmlinux.lds 的形式传给 gcc,并最终传给链接器 ld 来控制其行为。
ld 会将 .text 节的地址链接到
0xFFFFFFFF80100000 处。
关于内核 ELF 文件的入口地址(Entry point),即 bootloader 移动完内核后,直接跳转到的地址,由ld 写入 ELF的头中,
其会依次用下面的方法尝试设置入口点,当遇到成功时则停止:
a. 命令行选项 -e entry
b. 脚本中的
ENTRY(symbol)
c.
如果有定义 start 符号,则使用start符号(symbol)
d.
如果存在 .text 节,则使用第一个字节的地址。
e.
地址0
注意到上面的 ld script 中,用 ENTRY
宏设置了内核的 entry point 是 kernel_entry,
因此内核取得控制权后执行的第一条指令是在
kernel_entry 处。
linux
内核启动的第一个阶段是从 /arch/mips/kernel/head.s文件开始的。
而此处正是内核入口函数kernel_entry(),该函数定义在
/arch/mips/kernel/head.s文件里。
kernel_entry()函数是体系结构相关的汇编语言,它首先初始化内核堆栈段,来为创建系统中的第一个进程进行准备,
接着用一段循环将内核映像的未初始化数据段(bss段,在_edata和_end之间)清零,
最后跳转到 /init/main.c 中的
start_kernel()初始化硬件平台相关的代码。
/*asmlinkage: This is a #define for some gcc magic that tells the *compiler that the function should not expect to find any of its arguments *in registers (a common optimization), but only on the CPU‘s stack.意思是如 *果函数定义前加宏asmlinkage ,表示这些函数通过堆栈而不是通过寄存器传递参数。 *init: 宏定义__init,用于告诉编译器相关函数或变量的仅用于初始化。 *编译器将标有—init的所有代码存在初始化段中,初始化结束后就释放这段内存 */ asmlinkage void __init start_kernel(void) { char * command_line; extern struct kernel_param __start___param[], __stop___param[]; //定义了核的参数数据结构 smp_setup_processor_id(); //获取当前正在执行初始化的处理器ID,如果kernel是单处理器,则此函数是空的,不作任何处理 lockdep_init(); //初始化核依赖关系哈希表,classhash_table 和 chainhash_table
debug_objects_early_init();
//这个函数主要作用是对调试对象进行早期的初始化,其实就是HASH锁和静态对象池进行初始化。
boot_init_stack_canary();
//stack_canary的是带防止栈溢出攻击保护的堆栈
cgroup_init_early();
//控制组进行早期的初始化
local_irq_disable(); //关闭当前CPU所有中断响应
early_boot_irqs_off();
//标记内核还在早期初始化代码阶段,并且中断在关闭状态,如果有任何中断打开或请求中断的事情出现,都是会提出警告,以便跟踪代码错误情况。
//早期代码初始化结束之后,就会调用函数early_boot_irqs_on来设置这个标志为真。
early_init_irq_lock_class();
//每一个中断都有一个IRQ 描述符(struct irq_desc)来进行描述,这个函数的主要作用是设置所有的IRQ 描述符(struct irq_desc)的锁是统一的锁,
//还是每一个IRQ 描述符(struct irq_desc)都有一个小锁。
lock_kernel();
//这个函数主要作用是初始化大内核锁。在对称多处理器的系统里,每一个CPU都可以运行内核的代码,但有时需要只能一个CPU运行内核代码,那么怎么办呢?要解决这个问题,就需要给内核配备一把锁 //只要拥有这把锁的CPU才可以运行内核的代码,并且同一个CPU可以递归地运行内核。
tick_init();
//这个函数主要作用是初始化时钟事件管理器的回调函数,比如当时钟设备添加时处理
boot_cpu_init();
//这个函数主要作用是设置当前引导系统的CPU在物理上存在,在逻辑上可以使用,并且初始化准备好。
page_address_init();
//这个函数主要作用是初始化高端内存的映射表。在32位系统里,内核为了访问超过1G的物理内存空间,需要使用高端内存映射表。比如当内核需要读取1G的缓存数据时,就需要分配高端内存来使用,这 //样才可以管理起来。使用高端内存之后,32位的系统也可以访问达到64G内存。在移动操作系统里,目前还没有这个必要,最多才1G多内存。
printk(KERN_NOTICE "%s", linux_banner);
//输出终端上显示版本信息、编译的电脑用户名称、编译器版本、编译时间
setup_arch(&command_line);
//每种体系结构都有setup_arch函数,主要是再次获取CPU类型和系统架构,分析引导程序传入的命令行参数,进行页面内存初始化,处理器初始化,中断早期初始化等等。
mm_init_owner(&init_mm, &init_task);
//这个函数主要作用是设置最开始的初始化任务属于init_mm内存
setup_command_line(command_line);
//保存命令行
setup_nr_cpu_ids();
//设置最多有多少个nr_cpu_ids结构
setup_per_cpu_areas();
//设置SMP体系每个CPU使用的内存空间,同时拷贝初始化段里数据
smp_prepare_boot_cpu();
//为SMP系统里引导CPU进行准备工作
build_all_zonelists();
//初始化所有内存管理节点列表,以便后面进行内存管理初始化
page_alloc_init();
//设置内存页分配通知器
printk(KERN_NOTICE "Kernel command line: %s\n", boot_command_line);
//输出命令参数到显示终端
parse_early_param();
//分析命令行最早使用的参数
parse_args("Bootingkernel", static_command_line, __start___param, __stop___param - __start___param, &unknown_bootoption);
//对传入内核参数进行解释,如果不能识别的命令就调用最后参数的函数
pidhash_init();
//进程ID的HASH表初始化,这样可以提供通PID进行高效访问进程结构的信息。LINUX里共有四种类型的PID,因此就有四种HASH表相对应。
vfs_caches_init_early();
//虚拟文件系统的早期初始化
sort_main_extable();
//对内核内部的异常表进行堆排序,以便加速访问
trap_init();
//对异常进行初始化
mm_init();
//设置内核内存分配器
sched_init();
//这个函数主要作用是对进程调度器进行初始化,比如分配调度器占用的内存,初始化任务队列,设置当前任务的空线程,当前任务的调度策略为CFS调度器。
preempt_disable();
//这个函数主要作用是关闭优先级调度。由于每个进程任务都有优先级,目前系统还没有完全初始化,还不能打开优先级调度。
}
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。