阅读报告——Linux2.6.16的启动过程
从按下启动按钮,到登陆界面的出现,这期间就是
Linux
的启动过程。显然这期间有两个重要的任务要完成:初始化各种硬件、载入
kernel
映像并有
kernel
接管系统。研究
Linux
的启动过程对嵌入式开发有着不言而喻的重要性——因为这期间是和硬件关系最紧密的时候,是移植
kernel
到不同硬件平台的第一个关键环节。参考
Kernel 2.6.16
源代码和《
Understanding the Linux Kernel
,
3th Edition
》,总结出如下步骤:
1.
BIOS启动部分
:
Reset
引脚一旦有信号,
cpu
就开始执行一段在
rom
中的只读代码,完成一些基本的硬件检测,所以被称为
BIOS(Basic Input/Output System)
。这些都是以中断形式提供的服务,只能运行在实模式。
BIOS
把检测硬件的结构打印到屏幕的同时存在一些
table
里面,最后根据配置从启动介质里面读取第一个扇区的内容到
RAM
的
0x00007c00处,并跳转到那里执行。通常情况,就是我们的磁盘的第一个扇区,也即传统上的MBR,存放的代码是一个BootLoader。
2.BootLoader:
顾名思义,
BootLoader
就是负责引导操作系统的。对
IBM PC
来说,
LILO
和
Grub
是最流行的两个
bootloader
,并且后者更加高级,因为它能够识别多种文件系统。通常说来,一个
sector
是放不下一个
bootloader
的,故而分成两部分:以
LILO
为例,放在
MBR
的一部分把自己拷贝到
0x00096a00,并且在0x00098000
和
0x000969ff之间建立一个实模式下的堆栈,把第二部分拷贝到0x00096c00处,并跳转到那执行。第二部分代码允许用户选择一个操作系统来启动,或者直接启动默认的操作系统。如果是Linux操作系统,LILO使用BIOS提供的服务,把kernel Image的前面几个扇区(bootsect.S和Setup.S)的内容拷贝到0x00090000,然后把Image的其余部分(被压缩)拷贝到0x00010000或者0x00100000,最后一步跳转到0x00090200处执行,也就是Setup.S的第一条指令。可以看到使用BootLoader之后,kernel里bootsec.S提供的功能被BootLoader替代了。
3.setup.S(arch\i386\boot\)
:
这个文件里面的汇编代码的主要作用是重新初始化一些硬件设备,并且为
kernel
的运行准备必要的环境。
a)
从
BIOS
里面取出物理内存的描述,并保存在一块安全的内存区域里。
b)
设定键盘的速度。
c)
初始化显卡。
d)
初始化硬盘。
e)
检查
PS/2
总线。
f)
检查高级电源管理
(APM)
。
g)
初始化
GDT
和
IDT
。
lidt idt_48 # load idt with 0,0
lgdt gdt_48 # load gdt
|
h)
切换到保护模式
movw $1, %ax # protected mode (PE) bit
lmsw %ax # This is it!
|
i)
跳转到
startup_32
.byte 0×66, 0xea # prefix + jmpi-opcode
code32:
.long 0×1000 # will be set to 0×100000
# for big kernels
.word __BOOT_CS
|
这段代码表明上是数据,实际上构造了一条跳转指令
jmpi 0×100000,__BOOT_CS
。
4.startup_32:(arch/i386/boot/compressed/head.S)
正如名字所示,这些汇编代码是在
32
位的保护模式下执行。首先它用初始化段寄存器
DS,ES,FS,GS:
movl $(__BOOT_DS),%eax
movl %eax,%ds
movl %eax,%es
movl %eax,%fs
movl %eax,%gs
lss stack_start,%esp
|
紧接着把
BSS
的内容用
0
填充:
xorl %eax,%eax #
置eax为0
movl $_edata,%edi #_edata
为起始地址
movl $_end,%ecx #_end
为结束地址
subl %edi,%ecx #
循环次数
cld
rep #
置BSS区域为0
|
调用
decompress_kernel
函数
(arch/i386/boot/compressed/misc.c)
来解压内核,最好跳转到解压之后的内核所在的
0x00100000处执行。这个地方的代码以arch/i386/kernel/head.S开始。
5.Head.S(arch/i386/kernel/head.S):
这个文件也是以
startup_32
标号开始第一条指令
。完成一下内容:
a)
初始化
gtdr
、段寄存器和内核里面的
BSS
段
。
b)
建立一个内核页表,并把
PGD
的地址存入
cr3
,然后使能
paging
单元。
movl $swapper_pg_dir-__PAGE_OFFSET,%eax #PGT
地址
movl %eax,%cr3 #
存入
cr3
中
/* set the page table pointer.. */
movl %cr0,%eax
orl $0×80000000,%eax #
置
cr0
的
PG
位
movl %eax,%cr0 /* ..and set paging (PG) bit */
|
c)
初始化
esp
:
lss stack_start,%esp
d)
调用
setup_idt()函数。这个函数也在这个文件里面,每一个interrupt只是打印一句话就返回。
e)
调用
init/main.c
的
start_kernel
函数。
6.start_kernel(init/main.c):
到现在为止,
kernel
进入
c
语言的函数,
start_kernel
需要完成内核的所有初始化工作,包括:
a) setup_arch()(arch\i386\kernel\setup.c)->paging_init()(arch\i386\mm\init.c)
->pagetable_init().
b)
初始化
schedule: sched_init();
c)
初始化
memory zones: build_all_zonelists();
d)
初始化伙伴系统:
page_alloc_init();
e)
处理命内核参数。
f)
初始化中断,异常,软中断:
trap_init()
,
init_IRQ(),softirq_init();
g)
初始化系统时间和日期:
time_init();
h)
初始化
slab
算法:
kmem_cache_init();
i)
初始化
pid
的
hash
表:
pidmap_init();
j)
初始化信号系统:
signals_init();
k)
最后调用
rest_init()
函数。
7.rest_init(init/main.c):
a)
首先创建
1
号进程
init
:
kernel_thread(init, NULL, CLONE_FS | CLONE_SIGHAND);
b)
然后调用
schedule()
。这样
init
函数就得到执行,根据配置,在
init
函数里面最终会执行文件系统的某一个可执行文件。