我们假定本书所用的计算机是基于 IA—32 系列 CPU, 安装了标准单色显示器、 标准键 盘、一个软驱、一块硬盘、16 MB 内存,在内存中开辟了 2 MB 内存作为虚拟盘,并在 BIOS 中设置软驱为启动设备。后续所有的讲解都以此为基础。
目前处于实模式下,内存地址为0x00000~0xFFFFF,共1MB,20位地址线,BIOS所占地址为0xFE000~0xFFFFF,在最末尾。开机加电,CS为0xF000,IP为0xFFF0,所以程序从0xFFFF0处开始执行。BIOS程序在0x00000~0x003FF放置了中断向量表,共4*256=1023个字节,所以共有256个中断向量。在0x00400~0x004FF放置了BIOS数据区,在0x0E05B~0x0FFFE处放置了中断服务程序。如下图所示:
计算机硬件体系结构的设计与BIOS联手操作,会让CPU接收一个int 0x19的中断,CPU指向0x0E6F2,开始执行中断处理程序,将软驱0 号磁头对应盘面的 0 磁道 1 扇区的内容复制至内存0x07C00 ~0x07E00处。
程序从0x07C00处开始执行bootsect.s
entry _start _start: mov ax,#BOOTSEG mov ds,ax !起始段寄存器 mov ax,#INITSEG mov es,ax !目的段寄存器 mov cx,#256 !移动的次数 sub si,si !起始段偏移 sub di,di !目的段偏移 rep movw
BOOTSEG为0x07C0,INITSEG为0x9000,将ds:si内存地址的内容,移动到es:di内存地址处,一共移动256次,一次是一个字,最后一共移了256*2=512字节。把0x07C00 ~0x07E00移动到0x90000~0x90200
跳转,并重新设置段寄存器
jmpi go,INITSEG go: mov ax,cs mov ds,ax mov es,ax ! put stack at 0x9ff00. mov ss,ax mov sp,#0xFF00 ! arbitrary value >>512当时 CS 的值为 0x07C0,执行完 这个跳 转后,CS 值变为 0x9000(INITSEG),IP 的值为从 0x9000(INITSEG)到 go: mov ax, cs 这一行对应指令的偏 移。所以程序就转到执行 0x90000 这边的代码了。
上述代码的作用是通过 ax,用 CS 的值 0x9000 来把数据段寄存器(DS)、附加段寄存器 、栈基址寄存器(SS)设置成与代码段寄存器(CS)相同的位置,并将栈顶指针 SP 指 (ES) 向偏移地址为 0xFF00 处。
读入第二扇区开始的4个扇区
load_setup: mov dx,#0x0000 ! drive 0, head 0 mov cx,#0x0002 ! sector 2, track 0 mov bx,#0x0200 ! address = 512, in INITSEG mov ax,#0x0200+SETUPLEN ! service 2, nr of sectors int 0x13 ! read it jnc ok_load_setup ! ok - continue mov dx,#0x0000 mov ax,#0x0000 ! reset the diskette int 0x13 j load_setup ok_load_setup:
上面的int 0x19中断是机器自动产生的,现在是我们自己手动配置int 0x13参数,将软盘第二扇区开始的 4 个扇区,即 setup.s 对应的程序加载至内存的0x90200~0x90A00处。
然后又调用int 0x13终端,软盘第六扇区开始的 约 240 个扇区(共120KB)的 system 模块加载至内存的 0x10000~0x2E000中。
获取根设备号
seg cs mov ax,root_dev !cs:root_dev地址内容付给ax cmp ax,#0 !此时并不为0,为0x306,所以跳转到root_defined jne root_defined seg cs mov bx,sectors !如果没有设置,那么由扇区数去决定 mov ax,#0x0208 ! /dev/ps0 - 1.2Mb cmp bx,#15 je root_defined mov ax,#0x021c ! /dev/PS0 - 1.44Mb cmp bx,#18 je root_defined undef_root: jmp undef_root root_defined: seg cs mov root_dev,ax
.org 508 !偏移是508 root_dev: .word ROOT_DEV boot_flag: .word 0xAA55 !引导盘最后两个字节必须是0xAA55
跳转到setup.s执行
jmpi 0,SETUPSEG
setup.s首先做的第一件事情就是利用 BIOS 提供的中断服务程序从设备 上提取内核运行所需的机器系统数据,这些机器系统数据被加载到内存的0x90000 ~ 0x901FC (覆盖了0x90000~0x90200)位置
关中断
cli;如果没有 cli,又恰好发生中断,如用户不 小心碰了一下键盘,中断就要切进来,就不得不面对实模式的中断机制已经废除、保护模式 的中断机制尚未完成的尴尬局面,结果就是系统崩溃。
移动system模块
mov ax,#0x0000 cld ! ‘direction‘=0, movs moves forward do_move: mov es,ax ! destination segment add ax,#0x1000 cmp ax,#0x9000 !末尾是0x80000~0x8FFFF jz end_move mov ds,ax ! source segment sub di,di sub si,si mov cx,#0x8000 !每次共移动32764*2=64KB rep movsw !移动一个字 jmp do_move将ds:si内存地址的内容,移动到es:di内存地址处,一共移动32764*8次,一次是一个字,最后一共移了32764*8*2=512KB。把0x10000 ~0x8FFFF(共512KB)移动到0x00000~0x7FFFF(共512KB)。在本例中实际上就是0x10000~0x2E000移动到0x00000~0x1E000处。
1)废除 BIOS 的中断向量表,等同于废除了 BIOS 提供的实模式下的中断服务程序。
2)收回刚刚结束使用寿命的程序所占内存空间。
3)让内核代码占据内存物理地址最开始的、天然的、有利的位置。
对中断描 述符表寄存器(IDTR)和全局描述符表寄存器(GDTR)进行初始化设置
end_move: mov ax,#SETUPSEG ! right, forgot this at first. didn‘t work :-) mov ds,ax lidt idt_48 ! load idt with 0,0 lgdt gdt_48 ! load gdt with whatever appropriate
gdt: .word 0,0,0,0 ! dummy .word 0x07FF ! 8Mb - limit=2047 (2048*4096=8Mb) .word 0x0000 ! base address=0 .word 0x9A00 ! code read/exec .word 0x00C0 ! granularity=4096, 386 .word 0x07FF ! 8Mb - limit=2047 (2048*4096=8Mb) .word 0x0000 ! base address=0 .word 0x9200 ! data read/write .word 0x00C0 ! granularity=4096, 386 idt_48: .word 0 ! idt limit=0 .word 0,0 ! idt base=0L gdt_48: .word 0x800 ! 每个是8个字节,一共256个,所以是2048字节,一共可以有256个gdt .word 512+gdt,0x9 ! 偏移为0x9<<16+0x200+gdt
Segment Base(23-0) 0x000000
Arributes 0xC09A
G=1 表示界限粒度为4K 字节
D=1 表示是32位
P=1 表示描述符对地址转换是有效的,或者说该描述符所描述的段存在,即在内存中
DPL为00,表示内核态
TYPE为A ,表示代码段,执行/读
Segment Base(31-24) 0x00
此部分请参考http://blog.csdn.net/jltxgcy/article/details/865610
所以0x8选择子对应代码段描述符,0x10选择子对应数据段描述符,选择子的格式请参考http://blog.csdn.net/jltxgcy/article/details/8656101
打开A20地址线
call empty_8042 mov al,#0xD1 ! command write out #0x64,al call empty_8042 mov al,#0xDF ! A20 on out #0x60,al call empty_8042
设置中断,参考http://blog.csdn.net/jltxgcy/article/details/8661959
mov al,#0x11 !往端口20h(主片)写入ICW1 out #0x20,al .word 0x00eb,0x00eb out #0xA0,al !往端口A0h(从片)写入ICW1 .word 0x00eb,0x00eb mov al,#0x20 !往端口21h(主片)写入ICW2 out #0x21,al .word 0x00eb,0x00eb mov al,#0x28 !往端口A1h(从片)写入ICW2 out #0xA1,al .word 0x00eb,0x00eb mov al,#0x04 !往端口21h(主片)写入ICW3 out #0x21,al .word 0x00eb,0x00eb mov al,#0x02 !往端口A1h(从片)写入ICW3 out #0xA1,al .word 0x00eb,0x00eb mov al,#0x01 !往端口21h(主片)写入ICW4 out #0x21,al .word 0x00eb,0x00eb out #0xA1,al !往端口A1h(从片)写入ICW4 .word 0x00eb,0x00eb mov al,#0xFF !遮蔽主8259所有中断,写入OCW1 out #0x21,al .word 0x00eb,0x00eb !遮蔽从8259所有终端,写入OCW1 out #0xA1,al
将 CR0 寄存器第 0 位 (PE)置 1,即设定处理器工作方式为保护模式
mov ax,#0x0001 ! protected mode (PE) bit lmsw ax ! This is it!
jmpi 0,8 ! jmp offset 0 of segment 8 (cs)
开始执行head.s
Linux内核设计的艺术-从开机加电到执行main函数之前的过程
原文:http://blog.csdn.net/jltxgcy/article/details/19324619