Linux内核启动过程概述(3)

  mmap_switch做了很多工作,这里我们看到有复制数据段,清BSS段,保存CPU的ID,保存机器ID,清‘A’位,保存控制寄存器的值,然后就到了C语言段——start_kernel函数。

2.C语言段——start_kernel

asmlinkage void __init start_kernel(void)
{
local_irq_disable(); early_boot_irqs_off(); early_init_irq_lock_class(); /* * Interrupts are still disabled. Do necessary setups, then * enable them */ lock_kernel(); tick_init(); boot_cpu_init(); page_address_init(); printk(KERN_NOTICE); printk(linux_banner); setup_arch(&command_line); setup_command_line(command_line); 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); init_IRQ(); profile_init(); if (!irqs_disabled()) printk("start_kernel(): bug: interrupts were enabled early\n"); early_boot_irqs_on(); local_irq_enable(); console_init(); rest_init(); }

  接下来进入start_kernel启动内核的C函数。上面是start_kernel的部分代码。这部分代码的主要作用是处理uboot传递来的参数,设置与体系结构相关的环境,初始化控制台,最后执行应用程序,实现功能。这里我把start_kernel函数的几个主要功能的子函数逐层写出,帮助大家理解start_kernel的功能结构。

start_kernel setup_arch(&command_line); setup_command_line(command_line); unknown_bootoption obsolete_checksetup parse_early_param do_early_param rest_init; kernel_init prepare_namespace mount_root init_post

  这里每一个退格(TAB)都代表此函数被上一个函数调用(例如obsolete_checksetup是unknown_bootoption调用的函数)。setup_arch(&command_line)和setup_command_line(command_line)就是用来处理uboot传递进来的启动参数的(处理TAG)。obsolete_checksetup从__setup_start到 __setup_end,调用用非early标识的函数;do_early_param从__setup_start到 __setup_end,调用用early标识的函数(但因为__setup_param(str, fn, fn, 0)中early赋值为0,所以不在这里调用),所以我们主要用obsolete_checksetup。这在后面我们会提到。mount_root是挂载根文件系统,因为Linux上的应用程序最终要在根文件系统上运行。最后是init_post中运行应用程序。那么现在就有一个问题,Linux内核是如何接收uboot传来的根文件系统信息的呢?

bootcmd=nand read.jffs2 0x30007FC0 kernel; bootm 0x30007FC0 bootargs=noinitrd root=/dev/mtdblock3 init=/linuxrc console=ttySAC0

  上面是uboot启动时打印的环境变量。其中我们能够看到根文件系统挂载到第4个分区:root=/dev/mtdblock3 (从0分区开始)。上面我们提到过,setup_arch(&command_line)和setup_command_line(command_line)就是用来处理uboot传递进来的启动参数的(处理TAG)。但这个处理只是简单的复制粘贴而已,这两个函数将TAG保存,但并未进行真正的处理。那么真正告诉内核在哪里挂载的函数是什么呢?我们通过查看prepare_namespace可以看到一个saved_root_name。查找saved_root_name,发现在Do_mounts.c文件中有对它的调用:

static int __init root_dev_setup(char *line) { strlcpy(saved_root_name, line, sizeof(saved_root_name)); return 1; } __setup("root=", root_dev_setup); //传入一个字符串,一个函数

  根据我们之前的经验,我们可以猜测这个__setup宏,也是定义了一个结构体。通过查找__setup我们找到了它的宏定义:

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/cc21574c4bf00815f6cd498f490b0b50.html