Linux内核启动过程分析
http://www.arm9home.net/simple/index.php?t5645.html
http://womendu.iteye.com/blog/1069948
http://blog.csdn.net/zhoudaxia/article/details/6666683
1、Linux内核启动协议
阅读文档\linux-2.6.35\Documentation\x86\boot.txt
传统支持Image和zImage内核的启动装载内存布局(2.4以前的内核装载就是这样的布局):
| |
0A0000 +------------------------+
| Reserved for BIOS | Do not use. Reserved for BIOS EBDA.
09A000 +------------------------+
| Command line |
| Stack/heap | For use by the kernel real-mode code.
098000 +------------------------+
| Kernel setup | The kernel real-mode code.
090200 +------------------------+
| Kernel boot sector | The kernel legacy boot sector.
090000 +------------------------+
| Protected-mode kernel | The bulk of the kernel image.
010000 +------------------------+
| Boot loader | <- Boot sector entry point 0000:7C00
001000 +------------------------+
| Reserved for MBR/BIOS |
000800 +------------------------+
| Typically used by MBR |
000600 +------------------------+
| BIOS use only |
000000 +------------------------+
当使用bzImage时,保护模式的内核会被重定位到0x1000000(高端内存),内核实模式的代码(boot sector,setup和stack/heap)会被编译成可重定位到0x100000与低端内存底端之间的任何地址处。不幸的是,在2.00和2.01版的引导协议中,0x90000+的内存区域仍然被使用在内核的内部。2.02版的引导协议解决了这个问题。boot loader应该使BIOS的12h中断调用来检查低端内存中还有多少内存可用。
人们都希望“内存上限”,即boot loader触及的低端内存最高处的指针,尽可能地低,因为一些新的BIOS开始分配一些相当大的内存,所谓的扩展BIOS数据域,几乎快接近低端内存的最高处了。
不幸的是,如果BIOS 12h中断报告说内存的数量太小了,则boot loader除了报告一个错误给用户外,什么也不会做。因此,boot loader应该被设计成占用尽可能少的低端内存。对zImage和以前的bzImage,这要求数据能被写到x090000段,boot loader应该确保不会使用0x9A000指针以上的内存;很多BIOS在这个指针以上会终止。
对一个引导协议>=2.02的现代bzImage内核,其内存布局使用以下格式:
| Protected-mode kernel |
100000 +------------------------+
| I/O memory hole |
0A0000 +------------------------+
| Reserved for BIOS | Leave as much as possible unused
~ ~
| Command line | (Can also be below the X+10000 mark)
X+10000 +------------------------+
| Stack/heap | For use by the kernel real-mode code.
X+08000 +------------------------+
| Kernel setup | The kernel real-mode code.
| Kernel boot sector | The kernel legacy boot sector.
X +------------------------+
| Boot loader | <- Boot sector entry point 0000:7C00
001000 +------------------------+
| Reserved for MBR/BIOS |
000800 +------------------------+
| Typically used by MBR |
000600 +------------------------+
| BIOS use only |
000000 +------------------------+
这里程序段地址是由grub的大小来决定的。地址X应该在bootloader所允许的范围内尽可能地低。
2、BIOS POST过程
传统意义上,由于CPU加电之后,CPU只能访问ROM或者RAM里的数据,而这个时候是没有计算机操作系统的,所以需要有一段程序能够完成加载存储在非易失性存储介质(比如硬盘)上的操作系统到RAM中的功能。这段程序存储在ROM里,BIOS就是这类程序中的一种。对于BIOS,主要由两家制造商制造,驻留在主板的ROM里。有了BIOS,硬件制造商可以只需要关注硬件而不需要关注软件。BIOS的服务程序,是通过调用中断服务程序来实现的。BIOS加载bootloader程序,Bootloader也可以通过BIOS提供的中断,向BIOS获取系统的信息。整个过程如下:
(1)电源启动时钟发生器并在总线上产生一个#POWERGOOD的中断。
(2)产生CPU的RESET中断(此时CPU处于8086工作模式)。
(3)进入BIOS POST代码处:%ds=%es=%fs=%gs=%ss=0,%cs=0xFFFF0000,%eip = 0x0000FFF0 (ROM BIOS POST code,指令指针eip,数据段寄存器ds,代码段寄存器cs)。
(4)在中断无效状态下执行所有POST检查。
(5)在地址0初始化中断向量表IVT。
(6)0x19中断:以启动设备号为参数调用BIOS启动装载程序。这个程序从启动设备(硬盘)的0扇面1扇区读取数据到内存物理地址0x7C00处开始装载。这个0扇面1扇区称为Boot sector(引导扇区),共512字节,也称为MBR。
就是说,CPU 在 BIOS 的入口(CS:IP=FFFF:0000)处执行BIOS的汇编程序,BIOS程序功能有系统硬件的检测,提供中断访问接口以访问硬件。而后被BIOS程序通过中断0x19调用磁盘MBR上的bootloader程序,将bootloader程序加载到ox7c00处,而后跳转到0x7c00,这样,位于 0x7c00处的bootloader程序,就可以执行了。
从BIOS执行MBR中的bootloader程序开始,就是linux的代码在做的事情了。
3、Bootloader过程
bootloader程序是为计算机加载(load)计算机操作系统的。boot(引导)是bootstrap的简写,bootstrap是引导指令的意思。bootloader程序通常位于硬盘上,被BIOS调用,用于加载内核。在PC机上常见的bootloader主要有grub、lilo、syslinux等。
GRUB(GRand Unified Bootloader)是当前linux诸多发行版本默认的引导程序。嵌入式系统上,最常见的bootloader是U-BOOT。这样的bootloader一般位于MBR的最前部。在linux系统中,bootloader也可以写入文件系统所在分区中。比如,grub程序就非常强大。Gurb运行后,将初始化设置内核运行所需的环境。然后加载内核镜像。
grub磁盘引导全过程:
(1)stage1: grub读取磁盘第一个512字节(硬盘的0道0面1扇区,被称为MBR(主引导记录),也称为bootsect)。MBR由一部分bootloader的引导代码、分区表和魔数三部分组成。
(2)stage1_5: 识别各种不同的文件系统格式。这使得grub识别到文件系统。
(3)stage2: 加载系统引导菜单(/boot/grub/menu.lst或grub.lst),加载内核vmlinuz和RAM磁盘initrd。
4、内核启动过程
内核映像文件vmlinuz:包含有linux内核的静态链接的可执行文件,传统上,vmlinux被称为可引导的内核镜像。vmlinuz是vmlinux的压缩文件。其构成如下:
(1)第一个512字节(以前是在arch/i386/boot/bootsect.S);
(2)第二个,一段代码,若干个不多于512字节的段(以前是在arch/i386/boot/setup.S);
(3)保护模式下的内核代码(在arch/x86/boot/main.c)。
bzImage文件:使用make bzImage命令编译内核源代码,可以得到采用zlib算法压缩的zImage文件,即big zImage文件。老的zImage解压缩内核到低端内存,bzImage则解压缩内核到高端内存(1M(0x100000)以上),在保护模式下执行。bzImage文件一般包含有vmlinuz、bootsect.o、setup.o、解压缩程序misc.o、以及其他一些相关文件(如piggy.o)。注意,在Linux 2.6内核中,bootsect.S和setup.S被整合为header.S。
initramfs(或initrd)文件:initrd是initialized ram disk的意思。主要用于加载硬件驱动模块,辅助内核的启动,挂载真正的根文件系统。
例如,我电脑上的grub启动项如下(在/boot/grub/grub.lst中):
- title Fedora (2.6.35.10-74.fc14.i686)
- root (hd0,0)
- kernel /vmlinuz-2.6.35.10-74.fc14.i686 ro root=/dev/mapper/VolGroup-lv_root rd_LVM_LV=VolGroup/lv_root rd_LVM_LV=VolGroup/lv_swap rd_NO_LUKS rd_NO_MD rd_NO_DM LANG=zh_CN.UTF-8 KEYBOARDTYPE=pc KEYTABLE=us rhgb quiet
- initrd /initramfs-2.6.35.10-74.fc14.i686.img
title Fedora (2.6.35.10-74.fc14.i686) root (hd0,0) kernel /vmlinuz-2.6.35.10-74.fc14.i686 ro root=/dev/mapper/VolGroup-lv_root rd_LVM_LV=VolGroup/lv_root rd_LVM_LV=VolGroup/lv_swap rd_NO_LUKS rd_NO_MD rd_NO_DM LANG=zh_CN.UTF-8 KEYBOARDTYPE=pc KEYTABLE=us rhgb quiet initrd /initramfs-2.6.35.10-74.fc14.i686.img
内核的执行参数可以控制内核的行为,比如ro参数告诉内核,以只读方式挂载根分区,而quiet则告诉内核,启动的时候不要打印任何信息。这些参数不光影响内核的执行,大多数的发行版也使用这些参数控制启动完毕以后后续的动作。这些参数可以在任何时候从/proc/cmdline 这个文件中获得。现在,grub找到了内核(hd0,0)/boot/vmlinuz-2.6.35.10-74.fc14.i686,它将整个电脑的控制权交给了这个程序,内核开始进行各种初始化的动作,你可以将quiet参数去掉,以便看看内核都做了哪些事情,也可以在系统启动成功以后,使用dmesg这个命令查看内核启动的时候,都打印了哪些东西。
启动过程是和体系结构相关的,对于2.6内核,x86体系结构,CPU在上电初始化时,指令寄存器CS:EIP总是被初始化为固定值,这就是CPU复位后的第一条指令的地址。对于32位地址总线的系统来说,4GB的物理空间至少被划分为两个部分,一部分是内存的地址空间,另外一部分地址空间用于对BIOS芯片存储单元进行寻址。x86复位后工作在实模式下,该模式下CPU的寻址空间为1MB。CS:IP的复位值是FFFF:0000,物理地址为FFFF0。主板设计者必须保证把这个物理地址映射到BIOS芯片上,而不是RAM上。
装载Linux内核的第一步应该是加载实模式代码(boot sector和setup代码),然后检查偏移0x01f1处的头部(header)中的各个参数值。实模式的代码总共有32K,但是boot loader可以选择只装载前面的两个扇区(1K),然后检查bootup扇区的大小。
header中各个域的格式如下:
- Offset/Size Proto Name Meaning
- 01F1/1 ALL(1 setup_sects The size of the setup in sectors
- 01F2/2 ALL root_flags If set, the root is mounted readonly
- 01F4/4 2.04+ syssize The size of the 32-bit code in 16-byte paras
- 01F8/2 ALL ram_size DO NOT USE - for bootsect.S use only
- 01FA/2 ALL vid_mode Video mode control
- 01FC/2 ALL root_dev Default root device number
- 01FE/2 ALL boot_flag 0xAA55 magic number
- 0200/2 2.00+ jump Jump instruction
- 0202/4 2.00+ header Magic signature "HdrS"
- 0206/2 2.00+ version Boot protocol version supported
- 0208/4 2.00+ realmode_swtch Boot loader hook (see below)
- 020C/2 2.00+ start_sys_seg The load-low segment (0x1000) (obsolete)
- 020E/2 2.00+ kernel_version Pointer to kernel version string
- 0210/1 2.00+ type_of_loader Boot loader identifier
- 0211/1 2.00+ loadflags Boot protocol option flags
- 0212/2 2.00+ setup_move_size Move to high memory size (used with hooks)
- 0214/4 2.00+ code32_start Boot loader hook (see below)
- 0218/4 2.00+ ramdisk_image initrd load address (set by boot loader)
- 021C/4 2.00+ ramdisk_size initrd size (set by boot loader)
- 0220/4 2.00+ bootsect_kludge DO NOT USE - for bootsect.S use only
- 0224/2 2.01+ heap_end_ptr Free memory after setup end
- 0226/1 2.02+ ext_loader_ver Extended boot loader version
- 0227/1 2.02+ ext_loader_type Extended boot loader ID
- 0228/4 2.02+ cmd_line_ptr 32-bit pointer to the kernel command line
- 022C/4 2.03+ ramdisk_max Highest legal initrd address
- 0230/4 2.05+ kernel_alignment Physical addr alignment required for kernel
- 0234/1 2.05+ relocatable_kernel Whether kernel is relocatable or not
- 0235/1 2.10+ min_alignment Minimum alignment, as a power of two
- 0236/2 N/A pad3 Unused
- 0238/4 2.06+ cmdline_size Maximum size of the kernel command line
- 023C/4 2.07+ hardware_subarch Hardware subarchitecture
- 0240/8 2.07+ hardware_subarch_data Subarchitecture-specific data
- 0248/4 2.08+ payload_offset Offset of kernel payload
- 024C/4 2.08+ payload_length Length of kernel payload
- 0250/8 2.09+ setup_data 64-bit physical pointer to linked list of struct setup_data
- 0258/8 2.10+ pref_address Preferred loading address
- 0260/4 2.10+ init_size Linear memory required during initialization
Offset/Size Proto Name Meaning 01F1/1 ALL(1 setup_sects The size of the setup in sectors 01F2/2 ALL root_flags If set, the root is mounted readonly 01F4/4 2.04+ syssize The size of the 32-bit code in 16-byte paras 01F8/2 ALL ram_size DO NOT USE - for bootsect.S use only 01FA/2 ALL vid_mode Video mode control 01FC/2 ALL root_dev Default root device number 01FE/2 ALL boot_flag 0xAA55 magic number 0200/2 2.00+ jump Jump instruction 0202/4 2.00+ header Magic signature "HdrS" 0206/2 2.00+ version Boot protocol version supported 0208/4 2.00+ realmode_swtch Boot loader hook (see below) 020C/2 2.00+ start_sys_seg The load-low segment (0x1000) (obsolete) 020E/2 2.00+ kernel_version Pointer to kernel version string 0210/1 2.00+ type_of_loader Boot loader identifier 0211/1 2.00+ loadflags Boot protocol option flags 0212/2 2.00+ setup_move_size Move to high memory size (used with hooks) 0214/4 2.00+ code32_start Boot loader hook (see below) 0218/4 2.00+ ramdisk_image initrd load address (set by boot loader) 021C/4 2.00+ ramdisk_size initrd size (set by boot loader) 0220/4 2.00+ bootsect_kludge DO NOT USE - for bootsect.S use only 0224/2 2.01+ heap_end_ptr Free memory after setup end 0226/1 2.02+ ext_loader_ver Extended boot loader version 0227/1 2.02+ ext_loader_type Extended boot loader ID 0228/4 2.02+ cmd_line_ptr 32-bit pointer to the kernel command line 022C/4 2.03+ ramdisk_max Highest legal initrd address 0230/4 2.05+ kernel_alignment Physical addr alignment required for kernel 0234/1 2.05+ relocatable_kernel Whether kernel is relocatable or not 0235/1 2.10+ min_alignment Minimum alignment, as a power of two 0236/2 N/A pad3 Unused 0238/4 2.06+ cmdline_size Maximum size of the kernel command line 023C/4 2.07+ hardware_subarch Hardware subarchitecture 0240/8 2.07+ hardware_subarch_data Subarchitecture-specific data 0248/4 2.08+ payload_offset Offset of kernel payload 024C/4 2.08+ payload_length Length of kernel payload 0250/8 2.09+ setup_data 64-bit physical pointer to linked list of struct setup_data 0258/8 2.10+ pref_address Preferred loading address 0260/4 2.10+ init_size Linear memory required during initialization
每个域的具体细节可参考boot.txt文档。
BIOS把Boot Loader加载到0x7C00的地方并跳转到这里继续执行之后,BootLoader就会把实模式代码setup加载到0x07C00之上的某个地址上,其中setup的前512个字节是boot sector(引导扇区),现在这个引导扇区的作用并不是用来引导系统,而是为了兼容及传递一些参数。之后Boot Loader跳转到setup的入口点,入口点为_start例程(根据arch/x86/boot/setup.ld可知)。
注意,bzImage由setup和vmlinux两部分组成,setup是实模式下的代码,vmlinux是保护模式下的代码。
实模式设置(setup)阶段用于体系结构相关的硬件初始化工作,涉及的文件有arch/x86/boot/header.S、链接脚本setup.ld、arch/x86/boot/main.c。header.S第一部分定义了bstext、.bsdata、.header这3个节,共同构成了vmlinuz的第一个512字节(即引导扇区的内容)。常量BOOTSEG和SYSSEG定义了引导扇区和内核的载入地址。下面是header.S的代码:
- BOOTSEG = 0x07C0 /* 引导扇区的原始地址 */
- SYSSEG = 0x1000 /* 历史的载入地址>>4 */
- #ifndef SVGA_MODE
- #define SVGA_MODE ASK_VGA
- #endif
- #ifndef RAMDISK
- #define RAMDISK 0
- #endif
- #ifndef ROOT_RDONLY
- #define ROOT_RDONLY 1
- #endif
- .code16
- .section ".bstext", "ax"
- .global bootsect_start
- bootsect_start:
- # 使开始地址正常化
- ljmp $BOOTSEG, $start2
- start2:
- movw %cs, %ax
- movw %ax, %ds
- movw %ax, %es
- movw %ax, %ss
- xorw %sp, %sp
- sti
- cld
- movw $bugger_off_msg, %si
- msg_loop:
- lodsb
- andb %al, %al
- jz bs_die
- movb $0xe, %ah
- movw $7, %bx
- int $0x10
- jmp msg_loop
- bs_die:
- # 允许用户按一个键,然后重启
- xorw %ax, %ax
- int $0x16
- int $0x19
- # 0x19中断绝不会返回,无论它做什么
- # 调用BIOS复位代码,便CPU工作在实模式下
- ljmp $0xf000,$0xfff0
- .section ".bsdata", "a"
- bugger_off_msg:
- .ascii "Direct booting from floppy is no longer supported.\r\n"
- .ascii "Please use a boot loader program instead.\r\n"
- .ascii "\n"
- .ascii "Remove disk and press any key to reboot . . .\r\n"
- .byte 0
- # 下面设置内核的一些属性,setup需要。这是header的第一部分,来自以前的boot sector
- .section ".header", "a"
- .globl hdr
- hdr:
- setup_sects: .byte 0 /* 被build.c填充 */
- root_flags: .word ROOT_RDONLY
- syssize: .long 0 /* 被build.c填充 */
- ram_size: .word 0 /* 已过时 */
- vid_mode: .word SVGA_MODE
- root_dev: .word 0 /* 被build.c填充 */
- boot_flag: .word 0xAA55
- # 偏移512处,setup的入口点
- .globl _start
- _start:
- # Explicitly enter this as bytes, or the assembler
- # tries to generate a 3-byte jump here, which causes
- # everything else to push off to the wrong offset.
- .byte 0xeb # short (2-byte) jump
- .byte start_of_setup-1f
- 1:
- # header的第二部分,来自以前的setup.S:设置头部header,包括大量的bootloader参数,如header版本、内核版本字符串指针、bootloader类型、
- # 内核装载时的很多标志、堆栈尾部地址指针、内核命令行地址指针和大小、32位保护模式入口地址、ramdisk地址和大小等
- code32_start: # 这里对32位的代码,装载器可以设置可设置一个不同的入口地址
- .long 0x100000 # 0x100000 = 为大内核的默认入口地址(保护模式)
- # ............ (省略)
- # End of setup header #####################################################
- .section ".entrytext", "ax"
- start_of_setup:
- #ifdef SAFE_RESET_DISK_CONTROLLER
- # 重置磁盘控制器
- movw $0x0000, %ax # 重置磁盘控制器
- movb $0x80, %dl # 所有的的磁盘控制器All disks
- int $0x13
- #endif
- # ............(省略)
- # 让%ss无效,创建一个新的栈
- movw $_end, %dx
- testb $CAN_USE_HEAP, loadflags
- jz 1f
- movw heap_end_ptr, %dx
- 1: addw $STACK_SIZE, %dx
- jnc 2f
- xorw %dx, %dx # Prevent wraparound
- 2: # 现在%dx应该指向我们栈空间的尾部
- andw $~3, %dx # dword对齐
- jnz 3f
- movw $0xfffc, %dx # 确保不是0
- 3: movw %ax, %ss
- movzwl %dx, %esp # 清除%esp的上半部分
- sti # 现在我们应该有一个工作空间
- # 我们将进入%cs=%ds+0x20,设置好%cs
- pushw %ds
- pushw $6f
- lretw
- 6:
- # 在setup终止时检查签名
- cmpl $0x5a5aaa55, setup_sig
- jne setup_bad
- # 对BSS(Block Started by Symbol)清零
- movw $__bss_start, %di
- movw $_end+3, %cx
- xorl %eax, %eax
- subw %di, %cx
- shrw $2, %cx
- rep; stosl
- # 跳转到C代码(不会返回)
- calll main
- # ............(省略)
BOOTSEG = 0x07C0 /* 引导扇区的原始地址 */ SYSSEG = 0x1000 /* 历史的载入地址>>4 */ #ifndef SVGA_MODE #define SVGA_MODE ASK_VGA #endif #ifndef RAMDISK #define RAMDISK 0 #endif #ifndef ROOT_RDONLY #define ROOT_RDONLY 1 #endif .code16 .section ".bstext", "ax" .global bootsect_start bootsect_start: # 使开始地址正常化 ljmp $BOOTSEG, $start2 start2: movw %cs, %ax movw %ax, %ds movw %ax, %es movw %ax, %ss xorw %sp, %sp sti cld movw $bugger_off_msg, %si msg_loop: lodsb andb %al, %al jz bs_die movb $0xe, %ah movw $7, %bx int $0x10 jmp msg_loop bs_die: # 允许用户按一个键,然后重启 xorw %ax, %ax int $0x16 int $0x19 # 0x19中断绝不会返回,无论它做什么 # 调用BIOS复位代码,便CPU工作在实模式下 ljmp $0xf000,$0xfff0 .section ".bsdata", "a" bugger_off_msg: .ascii "Direct booting from floppy is no longer supported.\r\n" .ascii "Please use a boot loader program instead.\r\n" .ascii "\n" .ascii "Remove disk and press any key to reboot . . .\r\n" .byte 0 # 下面设置内核的一些属性,setup需要。这是header的第一部分,来自以前的boot sector .section ".header", "a" .globl hdr hdr: setup_sects: .byte 0 /* 被build.c填充 */ root_flags: .word ROOT_RDONLY syssize: .long 0 /* 被build.c填充 */ ram_size: .word 0 /* 已过时 */ vid_mode: .word SVGA_MODE root_dev: .word 0 /* 被build.c填充 */ boot_flag: .word 0xAA55 # 偏移512处,setup的入口点 .globl _start _start: # Explicitly enter this as bytes, or the assembler # tries to generate a 3-byte jump here, which causes # everything else to push off to the wrong offset. .byte 0xeb # short (2-byte) jump .byte start_of_setup-1f 1: # header的第二部分,来自以前的setup.S:设置头部header,包括大量的bootloader参数,如header版本、内核版本字符串指针、bootloader类型、 # 内核装载时的很多标志、堆栈尾部地址指针、内核命令行地址指针和大小、32位保护模式入口地址、ramdisk地址和大小等 code32_start: # 这里对32位的代码,装载器可以设置可设置一个不同的入口地址 .long 0x100000 # 0x100000 = 为大内核的默认入口地址(保护模式) # ............ (省略) # End of setup header ##################################################### .section ".entrytext", "ax" start_of_setup: #ifdef SAFE_RESET_DISK_CONTROLLER # 重置磁盘控制器 movw $0x0000, %ax # 重置磁盘控制器 movb $0x80, %dl # 所有的的磁盘控制器All disks int $0x13 #endif # ............(省略) # 让%ss无效,创建一个新的栈 movw $_end, %dx testb $CAN_USE_HEAP, loadflags jz 1f movw heap_end_ptr, %dx 1: addw $STACK_SIZE, %dx jnc 2f xorw %dx, %dx # Prevent wraparound 2: # 现在%dx应该指向我们栈空间的尾部 andw $~3, %dx # dword对齐 jnz 3f movw $0xfffc, %dx # 确保不是0 3: movw %ax, %ss movzwl %dx, %esp # 清除%esp的上半部分 sti # 现在我们应该有一个工作空间 # 我们将进入%cs=%ds+0x20,设置好%cs pushw %ds pushw $6f lretw 6: # 在setup终止时检查签名 cmpl $0x5a5aaa55, setup_sig jne setup_bad # 对BSS(Block Started by Symbol)清零 movw $__bss_start, %di movw $_end+3, %cx xorl %eax, %eax subw %di, %cx shrw $2, %cx rep; stosl # 跳转到C代码(不会返回) calll main # ............(省略)
由setup.ld中的ENTRY(_start)可知,_start汇编例程是bzImage内核映像开始执行的入口点,即引导扇区之后的开始处(偏移512字节处),它会准备大量的bootloader参数。最后的call main跳转到arch/x86/boot/main.c:main()函数处执行,这就是众所周知的main函数,它们都工作在实模式下。main函数先调用copy_boot_params函数把位于第一个扇区的参数复制到boot_params变量中,boot_params位于setup的数据段,然后调用链为arch/x86/boot/pm.c:go_to_protected_mode(void)
--->arch/x86/boot/pmjump.S:protected_mode_jump()。
实模式的protected_mode_jump执行后,跳出了bzImage的第一部分,BootLoader默认把第二部分放在0x100000处,这个入口处是startup_32,先执行arch/x86/boot/compressed/head_32.S中的startup_32(保护模式下的入口函数),然后执行arch/x86/kernel/head_32.S中的startup_32(32位内核的入口函数),这里会拷贝boot_params以及boot_command_line, 初始化页表,开启分页机制。
startup_32()函数会调用head32.c:i386_start_kernel()函数,它会调用init/main.c:start_kernel()函数,这是Linux内核的启动函数。init/main.c文件是整个Linux内核的中央联结点。每种体系结构都会执行一些底层设置函数,然后执行名为start_kernel的函数(在init/main.c中可以找到这个函数)。可以认为main.c是内核的“粘合剂(glue)”,之前执行的代码都是各种体系结构相关的代码,一旦到达start_kernel(),就与体系结构无关了。
start_kernel()会调用一系列初始化函数来设置中断,执行进一步的内存配置,解析内核命令行参数。然后调用fs/dcache.c:vfs_caches_init()--->fs/namespace.c:mnt_init()创建基于内存的rootfs文件系统(是一个虚拟的内存文件系统,称为VFS),这是系统初始化时的根结点,即"/"结点,后面VFS会指向真实的文件系统。注意在Linux系统中,目录结构与Windows上有较大的不同。系统中只有一个根目录,路径是“/”,而其它的分区只是挂载在根目录中的一个文件夹内,如“/proc”和“/sys”等,这里的“/”就是Linux中的根目录。
下面是mnt_init()的代码:
- void __init mnt_init(void)
- {
- unsigned u;
- int err;
- init_rwsem(&namespace_sem);
- mnt_cache = kmem_cache_create("mnt_cache", sizeof(struct vfsmount),
- 0, SLAB_HWCACHE_ALIGN | SLAB_PANIC, NULL);
- mount_hashtable = (struct list_head *)__get_free_page(GFP_ATOMIC);
- if (!mount_hashtable)
- panic("Failed to allocate mount hash table\n");
- printk("Mount-cache hash table entries: %lu\n", HASH_SIZE);
- for (u = 0; u < HASH_SIZE; u++)
- INIT_LIST_HEAD(&mount_hashtable[u]);
- err = sysfs_init();
- if (err)
- printk(KERN_WARNING "%s: sysfs_init error: %d\n",
- __func__, err);
- fs_kobj = kobject_create_and_add("fs", NULL);
- if (!fs_kobj)
- printk(KERN_WARNING "%s: kobj create error\n", __func__);
- init_rootfs();
- init_mount_tree();
- }
void __init mnt_init(void) { unsigned u; int err; init_rwsem(&namespace_sem); mnt_cache = kmem_cache_create("mnt_cache", sizeof(struct vfsmount), 0, SLAB_HWCACHE_ALIGN | SLAB_PANIC, NULL); mount_hashtable = (struct list_head *)__get_free_page(GFP_ATOMIC); if (!mount_hashtable) panic("Failed to allocate mount hash table\n"); printk("Mount-cache hash table entries: %lu\n", HASH_SIZE); for (u = 0; u < HASH_SIZE; u++) INIT_LIST_HEAD(&mount_hashtable[u]); err = sysfs_init(); if (err) printk(KERN_WARNING "%s: sysfs_init error: %d\n", __func__, err); fs_kobj = kobject_create_and_add("fs", NULL); if (!fs_kobj) printk(KERN_WARNING "%s: kobj create error\n", __func__); init_rootfs(); init_mount_tree(); }
这里fs/ramfs/inode.c:init_rootfs()会调用fs/filesystems.c:register_filesystem()注册rootfs。然后fs/namespace.c:init_mount_tree()调用fs/super.c:do_kern_mount()在内核中挂载rootfs,调用fs/fs_struct.c:set_fs_root()将当前的rootfs文件系统配置为根文件系统。
为什么不直接把真实的文件系统配置为根文件系统?答案很简单,内核中没有真实根文件系统设备(如硬盘,USB)的驱动,而且即便你将根文件系统的设备驱动编译到内核中,此时它们还尚未加载,实际上所有内核中的驱动是由后面的kernel_init线程进行加载。另外,我们的root设备都是以设备文件的方式指定的,如果没有根文件系统,设备文件怎么可能存在呢?
注意根据调用链do_kern_mount()--->vfs_kern_mount(type)--->type->get_sb()--->fs/ramfs/inode.c:rootfs_get_sb()--->ramfs_fill_super()--->fs/dcache.c:d_alloc_root(),函数d_alloc_root分配最终的根结点,代码如下:
- struct dentry * d_alloc_root(struct inode * root_inode)
- {
- struct dentry *res = NULL;
- if (root_inode) {
- static const struct qstr name = { .name = "/", .len = 1 };
-  
(转发请注明转自:学PHP)
相关推荐- linux下修改MAC地址方法-大全 (2012-09-16 17:08:10)
- Linux系统管理入门必须经历的三步 (2012-09-16 17:08:14)
- 从min&max一窥linux设计 (2012-09-16 17:08:16)
- VM linux网络设置 (2012-09-16 17:08:18)
- Linux的NAT实现浅谈 (2012-09-17 15:08:14)
- vim 要注意的 (2012-09-10 23:14:04)
- redhat6.1安装oracle11g (2012-09-10 23:13:54)
- linux 的三种服务器的搭建 (2012-09-04 19:08:07)
- 如何维护Linux/Unix服务器 (2012-09-04 19:08:07)
- 更改Linux开机密码 (2012-09-04 19:08:07)
- 1楼 -1'说: 2015-11-10 15:22:40
-
1
发表评论