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();
  //进程IDHASH表初始化,这样可以提供通PID进行高效访问进程结构的信息。LINUX里共有四种类型的PID,因此就有四种HASH表相对应。

  vfs_caches_init_early();
  //虚拟文件系统的早期初始化

  sort_main_extable();
  //对内核内部的异常表进行堆排序,以便加速访问

  trap_init();
  //对异常进行初始化

  mm_init();
  //设置内核内存分配器

  sched_init();
  //这个函数主要作用是对进程调度器进行初始化,比如分配调度器占用的内存,初始化任务队列,设置当前任务的空线程,当前任务的调度策略为CFS调度器。
  preempt_disable();
  //这个函数主要作用是关闭优先级调度。由于每个进程任务都有优先级,目前系统还没有完全初始化,还不能打开优先级调度。
}

 

linux内核启动分析,古老的榕树,5-wow.com

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。