内核启动之进入真正内核之前

        在内核启动的时候首先执行的是header.S(arch/i386/boot/header.S),它使用AT&T的汇编格式编写,以前内核的启动时最开始执行的是一个成为bootsect的程序,它就是MBR的前512字节,会被BIOS程序装载到0x00007C00处然后执行,这样装入过程完全由内核自己完成,但是随着越来越复杂的bootloader的使用,内核也放弃了从软盘启动的方式。

        header.S的前512字节的内容是为了兼容使用软驱启动而存在的,他正好存放在磁盘的第一个扇区之内。如果将其作为入口代码被执行的时候会在屏幕上打印出错代码“Direct booting from floppy is no longer supported. Please use a boot loader program instead. Remove disk and press any key to reboot . . .",512字节中还包括一些kernel的属性,如下:

	# Kernel attributes; used by setup.  This is part 1 of the
	# header, from the old boot sector.

	.section ".header", "a"
	.globl	hdr
hdr:
setup_sects:	.byte 0			/* Filled in by build.c */
root_flags:	.word ROOT_RDONLY
syssize:	.long 0			/* Filled in by build.c */
ram_size:	.word 0			/* Obsolete */
vid_mode:	.word SVGA_MODE
root_dev:	.word 0			/* Filled in by build.c */
boot_flag:	.word 0xAA55

	# offset 512, entry point
这些字段对应着struct setup_header 结构体的内容,真正的入口是从第二个512字节开始的,bootloader会将控制权交给它,在这里是一条跳转指令,跳转到start_of_setup。       P.S.header.S之后的部分还是对应着 struct setup_header 结构体的内容。

	.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
它对应的是 struct setup_header 结构的jump字段,它是一条16位的指令,前8位是操作码(jmp),后面8位是地址,于是从这条指令开始执行会跳转到start_of_setup处开始执行。它完成的操作如下:

·使用BIOS的0x13H中断复位磁盘系统(入口参数为AH = 0x00H,DL = 0x80H表示硬盘)。

·初始化寄存器

·检查setup最后面的signature

·初始化bss段(清零)

·跳转到main函数执行。

# Jump to C code (should not return)
	calll	main
从这里就脱离了header.S的执行了,来到了main函数的执行,位于arch/i386/boot/main.c中。它依次完成如下操作:

·copy_boot_params()          /* First, copy the boot header into the "zeropage" */

·console_init()              /* Initialize the early-boot console */

·init_heap()                 /* End of heap check */

·validate_cpu()              /* Make sure we have all the proper CPU support */

·set_bios_mode()             /* Tell the BIOS what CPU mode we intend to run in. */

·detect_memory()             /* Detect memory layout */

·keyboard_set_repeat()       /* Set keyboard repeat rate (why?) */

·query_mca()                 /* Query MCA information */

·query_ist()                 /* Query Intel SpeedStep (IST) information */

·set_video()                 /* Set the video mode */

·go_to_protected_mode()      /* Do the last things and invoke protected mode */

    因为当前系统运行在实模式之下(这是因为CPU启动的时候自动进入实模式),以上的操作是为了进入保护模式做准备,前面的每个函数是对硬件系统的检查和测试,并将测试得到的保存在一个全局的struct boot_params类型的变量中,定义如下:

struct boot_params boot_params __attribute__((aligned(16)));
go_to_protected_mode函数之前的每个函数都会填充这个结构体,最后调用go_to_protected_mode函数进入保护模式,它位于arch/i386/boot/pm.c中。它依次完成如下操作:

·realmode_switch_hook()       /* Hook before leaving real mode, also disables interrupts */   /*这里主要是关中断(包括外部中断和NMI)*/

·enable_a20()                 /* Enable the A20 gate */           /* 打开A20地址线 */

·reset_coprocessor()          /* Reset coprocessor (IGNNE#) */    /* 重置协处理器 */

·mask_all_interrupts()        /* Mask all interrupts in the PIC */  /* 屏蔽PIC的所有中断线 */

·setup_idt();   setup_gdt()   /* Actual transition to protected mode... */    /* 设置IDT和GDT */

·protected_mode_jump(boot_params.hdr.code32_start,(u32)&boot_params + (ds() << 4))    /* 跳转并开启保护模式 */

对于保护模式有两个至关重要的表:GDT(全局描述符表)和IDT(中断描述符表),它们的初始化如下:

static void setup_gdt(void)
{
	/* There are machines which are known to not boot with the GDT
	   being 8-byte unaligned.  Intel recommends 16 byte alignment. */
	static const u64 boot_gdt[] __attribute__((aligned(16))) = {
		/* CS: code, read/execute, 4 GB, base 0 */
		[GDT_ENTRY_BOOT_CS] = GDT_ENTRY(0xc09b, 0, 0xfffff),
		/* DS: data, read/write, 4 GB, base 0 */
		[GDT_ENTRY_BOOT_DS] = GDT_ENTRY(0xc093, 0, 0xfffff),
		/* TSS: 32-bit tss, 104 bytes, base 4096 */
		/* We only have a TSS here to keep Intel VT happy;
		   we don't actually use it for anything. */
		[GDT_ENTRY_BOOT_TSS] = GDT_ENTRY(0x0089, 4096, 103),
	};
	/* Xen HVM incorrectly stores a pointer to the gdt_ptr, instead
	   of the gdt_ptr contents.  Thus, make it static so it will
	   stay in memory, at least long enough that we switch to the
	   proper kernel GDT. */
	static struct gdt_ptr gdt;

	gdt.len = sizeof(boot_gdt)-1;
	gdt.ptr = (u32)&boot_gdt + (ds() << 4);

	asm volatile("lgdtl %0" : : "m" (gdt));
}
这里设置了三个描述符,分别是(标志:0xC09B , 基地址:0 , 限长:0xFFFFF)、 (标志:0xC093 , 基地址:0 , 限长:0xFFFFF)和(标志:0x0089 , 基地址:4096 , 限长:103),它们分别用于代码段、数据段和TSS段的描述符,然后将这个表的内容装入到GDTR中,完成了GDT的初步初始化。

static void setup_idt(void)
{
	static const struct gdt_ptr null_idt = {0, 0};
	asm volatile("lidtl %0" : : "m" (null_idt));
}
这个函数初始化了IDT,可以看出初始化的IDT表中不保存任何内容,在后来的执行过程中再进一步初始化,因为这时候所有的中断都已经被关闭了。

接下来在protected_mode_jump函数中,切换到保护模式,它位于arch/i386/boot/pmjump.S中

	movl	%cr0, %edx
	orb	$X86_CR0_PE, %dl	# Protected mode
	movl	%edx, %cr0
这里开启了保护模式,然后在初始化各个段寄存器,最后再跳转到 code32_start段保存的地址中处执行,这个是全局的参数表,它的初始化在header.S中,如下:

疑问:这里的参数是怎么传递的?为什么不是在堆栈上,而是通过eax传递呢?

code32_start:				# here loaders can put a different
					# start address for 32-bit code.
		.long	0x100000	# 0x100000 = default for big kernel

    所以这时候内核的控制权交付到了0x100000的地方开始执行,这个地址存放的是什么代码呢?它在arch/i386/boot/compressed/head_32.S,它完成以下操作:

·初始化段寄存器和一个临时堆栈

·初始化BSS段
·调用解压缩函数decompress_kernel解压缩内核,并将其放到绝对位置0x100000处
·最后再跳转到真正的内核开始处(0x100000)执行

    解压缩之后的内核开始处放的是arch/i386/kernel/head_32.S的程序,所以这时候要从段代码开始执行,它的入口函数是startup_32(这时候算是真正的内核开始了,所以要好好看一下这段代码究竟完成了什么操作)。
    这段执行的内容主要是参考网上的资料,执行的过程的确有点让人蛋疼,不过我的理解是这样的,从BIOS开始接管计算机的执行权之后,首先它会执行一些自检工作,然后将MBR的扇区拷贝到内存的固定位置(0x7C00)处并且跳转到该处执行,这里包含着LILO之类的bootloader程序,它会将操作系统所在扇区的引导磁盘装载到0x90000处,将接下来的部分装载到0x90200处,并将内核映像装载到0x100000处,然后控制权交给0x90200处的代码执行,这里算是真正的操作系统开始控制计算机了。它执行一些基本的初始化工作之后跳转到main函数处执行,这个函数会执行对硬件的检查和测试并将得到的参数记录在一个全局的结构体中,并且开启保护模式(这时候的IDT和GDT都是临时的)。然后它会跳转到0x100000地址处(这个地址是预选设定的)执行,这时候就进入了内核映像,但是这个是经过压缩的内核,所以首先要对它解压缩,解压缩之后的内核仍然会放在0x100000的地方,最后跳转到解压缩之后的内核开始处。
接下来就进入了内核,它会完成什么初始化工作呢?下面再继续看代码吧。。。

    上面的内容都是参考网上的别人分析的结果,自己也基本上是粗略了浏览一下代码,找到一些关键的跳转点等。这种学习态度还是有点太懒了,总是不愿意学习一些新的知识,可能和心情有关吧,遇到困难的时候总是希望不去理他,而不是自己想办法解决,上面的内容相比较于真正的内核执行的操作的确不是很重要,但是它牵扯到一些计算机很低层的东西,还是值得学习一下的,但是自己总是在遇到困难的时候退缩,这样很不好,希望以后的代码学习中要敢于挑战,敢于尝试,不懂得就要自己查资料,静下心来,慢慢分析,我一直觉得没有什么是学不会的,只要自己有毅力,能够静下心来好好学,肯定可以的,加油吧!!!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值