Linux内核启动过程分析(2)

每个域的具体细节可参考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

# ............(省略)

由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();
}

这里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文件系统配置为根文件系统。

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

转载注明出处:http://www.heiqu.com/17407.html