Linux操作系统的引导
BIOS/Bootloader:
由PC机的BIOS (OXFFFFO是BIOS存储的总线地址)把bootsect从某个固定的地址拿到了内存中的某个固定地址(0x90000),并且进行了一系列的硬件初始化和参数设置bootsect.s
磁盘引导块程序,在磁盘的第一个扇区中的程序(0磁道0磁头1扇区)作用:首先将后续的setup.s代码从磁盘中加载到紧接着bootsect.s的地方。在显示屏上显示loading system再将system(操作系统)模块加载到0x10000的地方最后跳转到setup.s中去运行
setup .s
解析BIOS/BOOTLOADER传递来的参数
设置系统内核运行的LDT(局部描述符)IDT(中断描述符寄存器)全局描述符(设置全局描述符寄存器)设置中断控制芯片,进入保护模式运行(svc32保护模式,设置寄存器中的值)
跳转到system模块的最前面的代码运行(head.s)
head.s
加载内核运行时的各数据段寄存器,重新设置中断描述符表开启内核正常运行时的协处理器等资源
设置内存管理的分页机制
跳转到main.c开始运行
BOOTLOADER的启动内核代码创建
在uboot中有如下代码:
定义一个指针 void (*theKernel) (int zero,int arch,uint params) ; 把指针移到ih_ep上去,Linux的启动入口 theKernel = (void (*)(int, int,uint)) ntohl (hdr->ih_ep); /*执行linux并传入参数,bd->bi_arch_number称为process id即CPU的架构号 bd->bi_bootparams 为bootlader给linux传递的参数地址 */ theKernel (0,bd->bi_arch_number,bd->bi_bootparams);
带着上面的参数就会执行下面linux内核中head.S中的代码
//比对当前板子是否支持lInux系统,如果不支持则不启动直接跳转到error_p。正确则执行下面代码。 mrc p15, 0, r9, c0, c0 @ get processor id bl __lookup_processor_type @ r5=procinfo r9=cpuid movs r10, r5 @ invalid processor (r5=0)? THUMB( it eq ) @ force fixup-able long branch encoding beq __error_p //如果不正确则跳转 //设置物理内存页面 #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 //验证参数是否完整 bl __vet_atags //创建虚拟内存的页表 bl __create_page_tables /* 把函数__mmap_switched压入栈中,r13中存放sp*/ ldr r13, =__mmap_switched @ address to jump to after
需要注意的一点是在ARM的user模式下,ARM CPU有16个数据寄存器,被命名为R0~R15(这个要比X86多一些),它们均为32位寄存器,其中的R13~R15有特殊用途。寄存器R13保存堆栈指针SP,寄存器R14用作子程序链接寄存器,也称为LR,用以保存返回地址R15(PC)用作程序计数器。
在__mmap_switched中干的事情就是会将旧的地址转化为虚拟地址,即代码重定义,
会有如下一行代码
b start_kernel
该代码会跳转到 init/main.c(linux-3.4.2版本)下的start_kernel函数,该函数能对进程初始化c函数进行调用。如下进行各类初始化。
asmlinkage void __init start_kernel(void) { 。。。。。。。。 tick_init(); /*时钟初始化,初始化jiffies*/ boot_cpu_init(); page_address_init(); printk(KERN_NOTICE "%s", linux_banner); setup_arch(&command_line); mm_init_owner(&init_mm, &init_task); 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); page_alloc_init(); 。。。。。。。。。。。各种init /* Do the rest non-__init'ed, we're now alive */ rest_init(); }
再rest_init()函数中有如下代码,会创建一个线程调用kernel_init函数
kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);
以上这句话和linux-0.11版本main.c中的
if (!fork()) { /*创建一个0号进程,进程若创建成功会返回一个0*/ init(); }
是一个意思。init()函数和kernel_init()中干的事也有很多相同。比如打开各种控制tai
进入linux-3.4.2版本kernel_init函数
static int __init kernel_init(void * unused) { 。。。 。。。。。。。。。。。。 /* Open the /dev/console on the rootfs, this should never fail */ if (sys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0) /*打开标准输入控制台*/ printk(KERN_WARNING "Warning: unable to open an initial console.\n"); (void) sys_dup(0); /*拷贝标准输输入控制台*/ (void) sys_dup(0); /*拷贝标准错误控制台*/ 。。。。。。。。。。。。。。。。。。。。 init_post(); return 0; }
init_post()函数中有如下代码,最后会运行根文件系统。
run_init_process("/sbin/init"); run_init_process("/etc/init"); run_init_process("/bin/init"); run_init_process("/bin/sh"); /*运行跟文件系统*/
这与linux-0.11版本相似。
if (!(pid=fork())) { /*创建1号进程返回0则创建成功*/ close(0); /*关闭0号句柄,关闭了0号进程创建的输入输出*/ if (open("/etc/rc",O_RDONLY,0)) /*打开/etc/rc文件*/ _exit(1); execve("/bin/sh",argv_rc,envp_rc); /*执行shell程序*/ _exit(2); }