[Linux Kernel]Linux内核启动过程(2.6.23)
(2012-08-22 10:34:56)
标签:
linux
kernel
boot
it
内核版本号:2.6.23
当PC按下电源,CPU加电后,自动从0xFFFF0处开始执行代码,这个地址位于BIOS中。接着
BIOS开始一系列系统检测,并在内存物理地址0处初
始化中断向量,供Linux内核启动过程中进行调用。此后,它将启动设备的第一个扇区(磁盘引导扇区,512Bytes)读入内存绝对地址0x7C00
处,并跳到这个地方开始执行(arch/i386/boot/header.s)。
注意,现在程序现在运行在16位实模式下,使用 64k的段。 segment(段地址) : offset(偏移)构成了
逻辑地址,段地址乘以 16 再加上 偏移 ,就得到 linear
address(线性地址)。header.s(arch/i386/boot/header.s)中包含了大量的.h(头文件)。
18 #include
19 #include
20 #include
21 #include
22 #include
23 #include
24 #include "boot.h"
这些头文件包含了许多重要的宏和声明,在Linux 内核的其他程序中,都会用到。
.section ".header", "a"
.globl hdr
hdr:
上面开始了header.s的最重要的部分,hdr数据段存储了Linux kernel启动过程所需的信息数据,它的结构struct
boot_params定义在 include/asm-i386/bootparam.h中。struct
boot_params非常重要,它包含了其他的一些struct,并不是很复杂。下面开始了header.s程序,开始了header.s的真正的执
行。
.section ".inittext", "ax"
start_of_setup:
它首先初始化 Disk Controller(磁盘控制器),是通过 int 0x13进行的。然后设置寄存器,Zero the
bss(初始化数据段),接着 call main(调用
main)跳转到:arch/i386/boot/main.c的main()函数开始执行。(终于见到我们的可亲可爱的C了,不过别高兴太早喔:~)
下面我们看看main()都做了什么(注:linux kernel
中有太多main函数,程序并不是总是从main开始执行)
copy_boot_params(); 复制 boot header 到 "zeropage"
validate_cpu(); 确保支持当前运行的CPU
set_bios_mode(); 告诉BIOS什么CPU我们将要去运行
detect_memory(); 检测Memory
keyboard_set_repeat(); 设置键盘 repeat rate (Why ?)
set_video(); 设置 Video mode
query_mca(); 获得 MCA 信息
query_voyager(); Voyager ?
query_ist(); 获得 Query Intel SpeedStep (IST) 信息
query_apm_bios(); 获得APM 信息
query_edd(); 获得EDD信息
go_to_protected_mode(); 最后一件事,也是最重要的一件事,进入保护模式
上面只是函数调用的大致顺序,并不是真正的程序,通过这些函数我们可以一目了然地看到内核的执行过程(还是C好啊,UGG boots,
不像汇编,得一行一行地看),当然你要了解更多的细节,可以追踪到每一个函数中去。它通过上方面的检测,不断地填充struct
boot_params结构,记住,struct boot_params 是很重要的喔
。其他函数我们就不研究了,单看最后一个,go_to_protected_mode(),究竟在哪儿?
arch/i386/boot/pm.c
void go_to_protected_mode(void)
依次调用了如下函数:
realmode_switch_hook(); Hook before leaving realmode
move_kernel_around(); 把 Kernel/setup 移到 它们最终的地方
enable_a20() 开启 a20 门
reset_coprocessor(); 重置 coprocessor(协处理器)
mask_all_interrupts(); Mask(屏蔽所有中断)
setup_idt(); 开始转入 保护模式……
setup_gdt();
protected_mode_jump(boot_params.hdr.code32_start,
(u32)&boot_params + (ds()
<< 4));
protected_mode_jump,传入了boot_params->hdr
->code32_start作为第一个参数, 该参数在header.s
中(arch/i386/boot/header.s) 设置如下:
code32_start: # here loaders can put a different
# start address for 32-bit code.
#ifndef __BIG_KERNEL__
.long 0x1000 # 0x1000 = default for zImage
#else
.long 0x100000 # 0x100000 = default for big kernel
#endif
显然是跳转到 0x1000或0x100000处继续执行。
第二个参数,就是 boot_params的线性地址,注意,现在仍是实地址模式,线性地址为段地址乘16加上偏移。
(啊,天哪,又到 汇编里去了,AT&T汇编),
在arch/i386/boot/pmjump.S中,
.globl protected_mode_jump
.type protected_mode_jump, @function
.code16
终于找到了,arch/i386/boot/pmjump.S并不大,只有54行。我们看看它做了什么:
xorl �x, �x # Flag to indicate this is a boot
movl �x, %esi # 传递过来的boot_params地址,转移到 ESI寄存器中
movl �x, 2f # Patch ljmpl instruction 要跳转的地址 放到 2f 处
........
1:
movw $__BOOT_DS, %cx
movl %cr0, �x
orb $1, %dl # Protected mode (PE) bit 设置CR0的PE位,进入保护模式!!
movl �x, %cr0
很简单嘛 :~)
# Jump to the 32-bit entrypoint 进入32位程序段
.byte 0x66, 0xea # ljmpl opcode 跳转指令码 (为什么不用汇编呢?不太清楚)
2: .long 0 # offset 偏移地址
.word __BOOT_CS # segment 段 类似于 ljmp segment : offset;
.size protected_mode_jump, .-protected_mode_jump
现在我们到哪儿了呢,想必大家都迷住了吧。我找了半天,终于找到了:
arch/i386/boot/compressed/head.s
********** head.S contains the 32-bit startup code.
NOTE!!! Startup happens at absolute address 0x00001000, which is
also where
* the page directory will exist. The startup code will be
overwritten by
* the page directory.
必定是跳转到这里了,Startup 被加载到绝对地址
0x00001000处。head.s的作用想必大家已经猜到了吧。既然在compressed文件夹中,肯定是和压缩有关的啦。对!
.text
#include
#include
#include
#include
.section ".text.head","ax",@progbits
.globl startup_32 定义了startup_32函数,从这儿开始执行
startup_32: 程序开始……
接下来,startup_32要检测是不是被加载到预定的位置了呢,怎么检查呢,这里用了一个技巧:
leal (0x1e4+4)(%esi), %esp 还记得 ESI中存的是boot_params的地址吗?这条指令就是把
栈指针指向boot_params的一个成员,而这个成员 u32 scratch就是专为程序检查是否被加载到预定位置设置的作
为栈使用的
call 1f 当 call时,一条指令的指针要入栈,就是保存到 scratch中了
1: popl �p 把指令指针放到 EBP中,EBP存的是当前的位置
subl $1b, �p 1b指的是指令在程序段中的偏移,两者之差,就是程序被加载的address了
* �p contains the address we are loaded at by the boot loader and
�x
* contains the address where we should move the kernel image
temporarily
* for safe in-place decompression.
* 现在 EBP中含有我们被加载的地址,而 EBX 中含有我们应该暂时把内核 image移动到的安全位置的地址
接着,就开始复制并移动内核,接着跳到移动后的地址开始执行,并为解压过程设置栈(stack)
* Do the decompression, and jump to the new kernel..
*开始解压缩,然后跳到新的内核开始执行
movl output_len(�x), �x #将参数依次入栈
pushl �x
pushl �p # output address
movl input_len(�x), �x
pushl �x # input_len
leal input_data(�x), �x
pushl �x # input_data
leal _end(�x), �x
pushl �x # end of the image as third argument
pushl %esi # real mode pointer as second arg
call decompress_kernel
addl $20, %esp 恢复栈
popl �x
如果需要,还要再移动内核,然后:
* Jump to the decompressed kernel.
xorl �x,�x
jmp *�p EBP 中有解压后新内核的地址
先说下内核的的解压缩。
内核解压缩的 decompress_kernel
在arch/i386/boot/compressed/misc.c中定义,
asmlinkage void decompress_kernel(void *rmode, unsigned long
end,
uch *input_data, unsigned long input_len, uch *output)
{
……
……
putstr("Uncompressing Linux... "); 很高兴啊,Uncompressing
Linux...
gunzip(); 是专门解压内核的gzip,它涉及到同目录下的relocs.c文件,
还有 lib/inflate.c(主要的解压例程)
putstr("Ok, booting the kernel.\n"); 很熟悉吧,Ok,booting the
kernel.
return;
}
好了,继续下去,现在Linux应该是跳转到新的解压缩后的位置开始执行了吧(跳来跳去真辛苦啊)
到哪儿了呢? 还是汇编~:( arch/i386/kernel/head.s
head.s文件,578L(行代码,不小啊,不过这算是最后一个啦,坚持哈)
head.s 虽然大,但很有条理:
重新设置段
把BSS段设置为0
把 boot_params的参数重新COPY到安全的地方
Initialize page tables 初始化页表
为SMP设置页表(如果有的话)
* Enable paging
movl $swapper_pg_dir-__PAGE_OFFSET,�x
movl �x,%cr3 # set the page table pointer..设置页指针
movl %cr0,�x
orl $0x80000000,�x
movl �x,%cr0 # ..and set paging (PG) bit 启动分页
ljmp $__BOOT_CS,$1f # Clear prefetch and normalize %eip
设置EIP
1:
lss stack_start,%esp #Set up the stack pointer 设置栈 指针
pushl $0 #Initialize eflags. 把EFLAGES设置为0
popfl
检测 CPU 类型
setup_idt 设置 IDT表
include arch/i386/xen/xen-head.S
…………
360(Line) jmp start_kernel 跳转到 init/main.c
现在,终于熬过了 AT&T汇编 ,是不是很有成就感呢,下面的start_kernel要是也讲习
懂了,Linux内核的其他部分,就不在话下了,加油啊,我现在水平还不够,只能列出start_kernel源码,大家一同分析
513 asmlinkage void __init start_kernel(void)
........
至此,内核正常启动起来了。太高兴了,现在还没有研究到 start_kernel 内部,再接再厉!!
在此,记录一篇比较详细的文章:
linux 启动过程综述:
startup/index.html
分享:
喜欢
0
赠金笔
加载中,请稍候......
评论加载中,请稍候...
发评论
登录名: 密码: 找回密码 注册记住登录状态
昵 称:
评论并转载此博文
发评论
以上网友发言只代表其个人观点,不代表新浪网的观点或立场。