这篇文章讲的是盘古开天地的事。话说Mr. Process是一个现代人,但是,只要是人,总该有个祖先。人们总想知道自己从哪来,然后才可以估摸算一下自己将去向何方。所以咱也要了解一下Linux的世界里人类的起源。
按下电源开关的那个真实的人就是Linux世界里的上帝,他创造了Linux世界的一切。当他按下机箱上的电源开关时,主板开始供电,CPU上的Reset Pin被拉高,这会引起CPU的一系列动作,这些动作是芯片设计时就决定的。CPU中的一些寄存器被置为固定的值,因为这些寄存器可能在启动的过程中要使用,比如CS(代码段寄存器)和EIP(指针指令寄存器)。这一步完成之后,CPU就可开执行地址为0xfffffff0里的ROM中的程序。这段程序就是BIOS(Basic Input Output System)。
1.POST(Power-On Self Test):顾名思名,就是查查有什么设备,这些设备是不是正常。要是CPU有Advanced Configuration and Power Interface(ACPI )的支持,也在这个时候进行。ACPI是用来对电源进行管理的,用来节电之类的。
2.初始化设备:确保没有IRQ和IO冲突。
3.寻找OS/Bootloader。这一步后面点再细说
我们在BIOS的设置菜单里能够设置从何处启动,比如软盘,硬盘,光驱…BIOS会按我们设定的顺序搜索OS。
4.把Bootloader复制到RAM里(地址为0x00007c00),然后那个地址开始执行。
什么是Bootloader?
现在,我们只要关心的是:bootloader会找到OS,把OS内核COPY到RAM中。
1.POST(Power-On Self Test):顾名思名,就是查查有什么设备,这些设备是不是正常。要是CPU有Advanced Configuration and Power Interface(ACPI )的支持,也在这个时候进行。ACPI是用来对电源进行管理的,用来节电之类的。
2.初始化设备:确保没有IRQ和IO冲突。
3.寻找OS/Bootloader。这一步后面点再细说
我们在BIOS的设置菜单里能够设置从何处启动,比如软盘,硬盘,光驱…BIOS会按我们设定的顺序搜索OS。
4.把Bootloader复制到RAM里(地址为0x00007c00),然后那个地址开始执行。
什么是Bootloader?
现在,我们只要关心的是:bootloader会找到OS,把OS内核COPY到RAM中。
如上图所示,在硬盘的第一个sector,有一个分区表(记录了哪些分区上有操作系统)和一个小版的Bootloader。当这个BIOS被设置为从这里启动时,这个小版的bootloader被复制到RAM的0x00007c00。然后它会把自己又移动到0x00096a00。在这之后,它再把另一段Bootloader从硬盘上复制到0x00096c00,然后从那里开始执行。分作两段的原因是因为现在的bootloader太大了,在MBR上存不完那么多。
Bootloader会把OS的内核映像复制到RAM中。
1. 调用BIOS以显示“Loading Image”的消息。
2. 调用BIOS,把内核映像的前512字节复制到0x00090000,setup()函数在0x00090200。
3. 调用BIOS,把剩下的内核映像加载到0x00010000(小内核zImage)或0x00100000(大内核bzImage)
4. 跳到setup()开始执行。
setup()用来初始化设备。虽然BIOS已经做了一些初始化的工作,但是Linux关不依赖于他。setup()会初始化键盘,FPU等设备,并设置一些寄存器。在Setup()的最后,会调用startup_32()。
startup_32()
Linux里有两个startup_32()。
首先会执行的是arch/i386/boot/compressed/head.S里的那个。这个startup_32()的作用主要是解压内核。
第二个startup_32()是在arch/i386/kernel/head.S的。这个startup_32()的工作就是为Linux的第一个进程(就是Mr. Process的祖先)设置生存环境。最后跳到start_kernel()中去。
在Understanding the Linux Kernel 3rd 中的描述如下
10. Loads the gdtr and idtr registers with the addresses of the GDT and IDT tables.
11. Jumps to the start_kernel( ) function.
完成所有组件的初始化工作。
Understanding the Linux Kernel对这一段工作的描述如下:
¨ The scheduler is initialized by invoking the sched_init( ) function (see Chapter 7).
¨ The memory zones are initialized by invoking the build_all_zonelists( ) function (see the section “Memory Zones” in Chapter 8).
¨ The Buddy system allocators are initialized by invoking the page_alloc_init( ) and mem_init( ) functions (see the section “The Buddy System Algorithm” in Chapter 8).
¨ The final initialization of the IDT is performed by invoking trap_init( ) (see the section “Exception Handling” in Chapter 4) and init_IRQ( ) (see the section “IRQ data structures” in Chapter 4).
¨ The TASKLET_SOFTIRQ and HI_SOFTIRQ are initialized by invoking the softirq_init( ) function (see the section “Softirqs” in Chapter 4).
¨ The system date and time are initialized by the time_init( ) function (see the section “The Linux Timekeeping Architecture” in Chapter 6).
¨ The slab allocator is initialized by the kmem_cache_init( ) function (see the section “General and Specific Caches” in Chapter 8).
¨ The speed of the CPU clock is determined by invoking the calibrate_delay( ) function (see the section “Delay Functions” in Chapter 6).
¨ The kernel thread for process 1 is created by invoking the kernel_thread( ) function. In turn, this kernel thread creates the other kernel threads and executes the /sbin/init program, as described in the section “Kernel Threads” in Chapter 3.
以上几个函数的执行过程如下图:
第二个startup_32()和start_kernel()揭示了Linux一生的真谛。从这里面咱看到了Mr. Process(一个普通的进程)所拥有的一切是怎么得到的。弄清楚了这些,也就弄清楚了Linux。