91年16bit实模式的引导代码是Minix上的as86编译器编译的,现在改为as编译,进入32bit保护模式下后就是gas(现as)编译
将自己从0x7c00:0移动到0x9000:0处,并且读入setup.s(4*512=2kb)到自己后面
将system模块读入到0x1000:0处,然后控制转移给setup.s
通过BIOS中断得到磁盘和内存的信息,以及设置当时的显示卡,并且将数据放在bootsect.s处(0x9000:0)
移动system模块到0x0000:0开始,
加载临时的GDT和IDT(head.s中重新设置)
通过键盘控制器端口开启A20地址线,
设置中断控制器8259A,
硬件中断从0x20开始,从片接IR2引脚
设置完后屏蔽所有中断,置位ISR寄存器
CR0,第0位置位(进入保护模式),jmp刷新流水线,并且将控制转移到system模块的head.s
关于显示卡设置略过,没意义
ICW1:设置成中断沿边沿触发,多片级联
ICW2:设置中断0-7对应中断号0x20-0x27,从片0x28-0x2f
ICW3:设置中断优先级0->(8-15)->(3-7),因为IR2连从片
ICW4:设置中断非自动结束,需要中断处理过程发送EOI
大致原理:
- 设置初始化命令字
- 写入操作命令字
优先级请求被选中,8259A发出INT信号给CPU,CPU发出的INTA信号响应,当CPU发出第2个INTA信号,通知8259A在数据总线上给出中断号
- 自动结束中断
- 手动(发送EOI命令),来复位ISR(正在服务寄存器)对应的中断位,从片也要发
#include "../include/linux/config.h"
INITSEG = DEF_INITSEG
SYSSEG = DEF_SYSSEG
SETUPSEG= DEF_SETUPSEG
.globl begtext,begdata,begbss,endtext,enddata,endbss
.text
begtext:
.data
begdata:
.bss
begbss:
.text
entry start
start:
mov ax,#INITSEG
mov ds,ax
!int 0x15功能0x88取得扩展内存存在0x90002处
mov ah,#0x88
int 0x15
mov [2],ax
!0x12功能,获取EGA配置信息
mov ah,#0x12
mov bl,#0x10
int 0x10
mov [8],ax
mov [10],bx
mov [12],cx
!
mov ax,#0x5019
cmp bl,#0x10
je novga
call chsvga
novga:
mov [14],ax
mov ah,#0x03
xor bh,bh
int 0x10
mov [0],dx
!取显卡当前显示模式
mov ah,#0x0f
int 0x10
mov [4],bx
mov [6],ax
!取第一个硬盘的参数
mov ax,#0x0000
mov ds,ax
lds si,[4*0x41] !中断向量表中参数表地址hd0
mov ax,#INITSEG
mov es,ax
mov di,#0x0080 !存放到0x9000:0080
mov cx,#0x10
rep
movsb
!hd1参数表地址
mov ax,#0x0000
mov ds,ax
lds si,[4*0x46]
mov ax,#INITSEG
mov es,ax
mov di,#0x0090
mov cx,#0x10
rep
movsb
!检查系统是否有第2个硬盘
mov ax,#0x1500
mov dl,#0x81 !驱动器号0x81第一个硬盘
int 0x13
jc no_disk1
cmp ah,#3 !是否是硬盘
je is_disk1
no_disk1:
mov ax,#INITSEG !第2个存储介质不存在,
mov es,ax
mov di,#0x0090
mov cx,#0x10
mov ax,#0x00
rep
stosb
is_disk1:
cli !禁止外部中断
!首先将system移动到从内存地址0x0000开始
mov ax,#0x0000
cld
do_move:
mov es,ax
add ax,#0x1000
cmp ax,#0x9000
jz end_move
mov ds,ax !初始化为0x1000:0x0
sub di,di
sub si,si
mov cx,#0x8000 !
rep
movsw
jmp do_move
!加载段描述符
end_move:
mov ax,#SETUPSEG
mov ds,ax !指向本程序
lidt idt_48 !加载IDT寄存器
lgdt gdt_48 !加载GDT寄存器
!开启A20地址线
call empty_8042 !键盘芯片,等待输入缓冲区为空
mov al,#0xD1
out #0x64,al
call empty_8042
mov al,#0xDF
out #0x60,al
call empty_8042 !
!主片端口0x20,0x21,从片端口0xA0-0xA1,
mov al,#0x11
out #0x20,al !ICW1=0x11
.word 0x00eb,0x00eb
out #0xA0,al !从片
.word 0x00eb,0x00eb
!Liux系统硬件中断从0x20开始
mov al,#0x20
out #0x21,al !ICW2=0x20(对应中断号从0x20开始
.word 0x00eb,0x00eb
mov al,#0x28
out #0xA1,al !从片ICW2
.word 0x00eb,0x00eb
mov al,#0x04 !主片ICW3=0x04
out #0x21,al !
.word 0x00eb,0x00eb
mov al,#0x02
out #0xA1,al !从片ICW3
.word 0x00eb,0x00eb
mov al,#0x01
out #0x21,al !主片ICW4=0x01
.word 0x00eb,0x00eb
out #0xA1,al !从片ICW4
.word 0x00eb,0x00eb
mov al,#0xFF !屏蔽所有的中断(主+从
out #0x21,al
.word 0x00eb,0x00eb
out #0xA1,al
!修改cr0,进入保护模式
mov ax,#0x0001
lmsw ax
jmpi 0,8 !第一个段选择子=代码段描述符
!从上面开始机器去执行system中的代码
!当输入缓冲器为空时,执行写命令,(键盘控制器的端口作为与A20地址线,
empty_8042:
.word 0x00eb,0x00eb
in al,#0x64 !读取AT键盘控制器状态寄存器
test al,#2 !
jnz empty_8042
ret
!显示卡部分未写
gdt:
.word 0,0,0,0
!内核代码段选择符0x08
.word 0x07FF
.word 0x0000
.word 0x9A00
.word 0x00C0
!内核数据段 0x10
.word 0x07FF
.word 0x0000
.word 0x9200
.word 0x00C0
idt_48:
.word 0
.word 0,0
gdt_48:
.word 0x800
.word 512+gdt,0x9 !0x9000:512+gdt
msg1:
.ascii "Press to see SVGA-modes available or any other key to continue."
db 0x0d,0x0a,0x0a,0x00
msg2:
.ascii "Mode: COLSxROWS:"
db 0x0d,0x0a,0x0a,0x00
msg3:
.ascii "Choose mode by pressing the corresponding number."
db 0x0d,0x0a,0x00
!显卡特征数据串
idati:
.ascii "761295520"
idcandt:
.byte 0xa5
idgenoa:
.byte 0x77,0x00,0x66,0x99
idparadise:
.ascii "VGA="
!..................
.text
endtext:
.data
enddata:
.bss
endbss:
后面的都是AT&T格式,采用gas编译,第6个扇区开始,
重新设置IDT,IDT用简单中断处理过程填满IDT表,避免找不到中断处理历程出错,(后续在改IDT)
重新设置GDT,仍是一个内核代码段一个数据段描述符,段长修改为16MB,和IDT一起放在head.s末尾
内核栈定义在/kernel/sched.c中(此时我还没看main.c后面代码)
初始化协处理器(浮点运算)
转跳到0x0540:0x0000处执行页目录和页表初始化
0x0000:0x0到0x0500:0作为1个页目录和4个页表的内存空间,紧跟1024个字节作为软盘缓冲区,(原来是head.s代码,现在接下来代码写覆盖了)
通过设置前16MB的线性地址和物理地址是一样的(经过分页部件转换后也一样)
将main参数和main地址入栈,控制转移到main中
.text
.globl _idt,_gdt,_pg_dir,_tmp_floppy_area
_pg_dir:
startup_32:
movl $0x10,%eax #数据段选择子
mov %ax,%ds
mov %ax,%es
mov %ax,%fs
mov %ax,%gs
#设置内核的栈,_stack_start定义在 kernel/sched.c中
lss _stack_start,%esp
#重新设置IDT和GDT
call setup_idt
call setup_gdt
movl $0x10,%eax
mov %ax,%ds
mov %ax,%es
mov %ax,%fs
mov %ax,%gs
#重新设置内核栈
lss _stack_start,%esp
xorl %eax,%eax
1:
incl %eax
movl %eax,0x000000 #向0x0地址写入eax值
cmpl %eax,0x100000 #判断地址1M是否也是这个值,
je 1b
movl %cr0,%eax
andl $0x80000011,%eax #PG,PE,ET位
orl $2,%eax #set MP位
movl %eax,%cr0
call check_x87
jmp after_page_tables
#
check_x87:
fninit #初始化协处理器
fstsw %ax
cmpb $0,%al
je 1f
movl %cr0,%eax
xorl $6,%eax
movl %eax,%cr0
ret
.align 2
1: .byte 0xDB,0xE4
ret
#设置中断门描述符,然后填入IDT表中256项
setup_idt:
lea ignore_int,%edx
movl $0x00080000,%eax #GDT代码段选择符
movw %dx,%ax #低位是过程入口点0-15bit
movw $0x8E00,%dx #中断门描述符属性
lea _idt,%edi
mov $256,%ecx
rp_sidt:
movl %eax,(%edi) #
movl %edx,4(%edi)
addl $8,%edi
dec %ecx
jne rp_sidt #!=0就转跳
lidt idt_descr #加载IDTR
ret
setup_gdt:
lgdt gdt_descr
ret
.org 0x1000
pg0:
.org 0x2000
pg1:
.org 0x3000
pg2:
.org 0x4000
pg3:
.org 0x5000
_tmp_floppy_area:
.fill 1024,1,0
after_page_tables:
pushl $0
pushl $0
pushl $0
pushl $L6
pushl $_main
jmp setup_paging
L6:
jmp L6
int_msg:
.asciz "Unknown interrupt\n\r" #每个字符串后面会添加NULL字符
.align 2 #2^2=4字节对齐
ignore_int:
pushl %eax
pushl %ecx
pushl %edx
push %ds
push %es
push %fs
movl $0x10,%eax #数据段选择子
mov %ax,%ds
mov %ax,%es
mov %ax,%fs
pushl $int_msg #int_msg地址入栈
call _printk #此函数在/kernel/printk.c中
popl %eax #int_msg地址给eax寄存器
pop %fs
pop %es
pop %ds
popl %edx
popl %ecx
popl %eax
iret #通用中断返回
.align 2
setup_paging:
movl $1024*5,%ecx #1页目录+4页表清零
xorl %eax,%eax
xorl %edi,%edi
cld;rep;stosl #将EAX填入ES:EDI中
#填写页目录前4项,分配所有的16Mb物理内存空间
movl $pg0+7,_pg_dir #+7表示3个1的属性
movl $pg1+7,_pg_dir+4
movl $pg2+7,_pg_dir+8
movl $pg3+7,_pg_dir+12
#
movl $pg3+4092,%edi
movl $0xfff007,%eax #16Mb-4096+7
std
1:
stosl
subl $0x1000,%eax
jge 1b
#设置页目录表基础地址
xorl %eax,%eax
movl %eax,%cr3 #设置CR3,页目录基地址
movl %cr0,%eax
orl $0x80000000,%eax
movl %eax,%cr0
ret #转跳到main函数
.align 2
.word 0 #4字节对齐,和下面组合起来=4字节
idt_descr:
.word 256*8-1
.long _idt
.align 2
.word 0
gdt_descr:
.word 256*8-1
.long _gdt
.align 3
_idt:
.fill 256,8,0
_gdt:
.quad 0x0000000000000000
.quad 0x00c09a0000000fff #修改代码段段长度=16MB(下同
.quad 0x00c0920000000fff
.quad 0x0000000000000000
.fill 252,8,0
关于A20地址线和8259A编程大致了解就好
参考<
seg指定段超越前缀
BIOS磁盘服务int 0x13
汇编jXX转移指令参考
BIOS int 0x10中断
Intel 8042键盘控制器详细介绍
IA-32指令手册