Hello,大家好!今天我们继续接着上回的来说。上回说到操作系统把需要的bootsect.s从硬盘挪到了0x7c00又挪到了0x90000,接着把setup.s从硬盘挪到了0x90200,head.s和其他的数据从硬盘挪到了0x10000。而且我们把bootsect.s已经解释完了。
OK,接下来又有哪些有趣的操作呢?
在setup.s中可以看到如下的代码。
start:
mov ax,#0x9000 ; this is done in bootsect already, but...
mov ds,ax
mov ah,#0x03 ; read cursor pos
xor bh,bh
int 0x10 ; save it in known place, con_init fetches
mov [0],dx ; it from 0x90000.
看到以上的汇编程序,大家应该都不陌生了吧。就是说吧0x9000赋值给ax,然后赋值给ds,把0x03赋值给ah。
【倒数第二行】有一个int指令,大家应该能想到是一个中断指令,并且是一个中断显示指令,ah是显示的读光标的位置。关于更多的BIOS的中断指令,大家可以自行查找资料。
int 0x10 中断程序执行完毕并返回时,dx 寄存器里的值表示光标的位置,具体说来其高八位 dh 存储了行号,低八位 dl 存储了列号。
【最后一行】把dx的数据放到内存为ds:[0]的地方,也就是0x90000的地方。(这里本来是存放bootsect.s的地方,也就是说这个bootsect.s这个数据不需要了,被破坏掉了,我们把它忘掉就可以了。)
比如获取内存信息。
; Get memory size (extended mem, kB)
mov ah,#0x88
int 0x15
mov [2],ax
获取显卡显示模式。
; Get video-card data:
mov ah,#0x0f
int 0x10
mov [4],bx ; bh = display page
mov [6],ax ; al = video mode, ah = window width
检查显示方式并取参数
; check for EGA/VGA and some config parameters
mov ah,#0x12
mov bl,#0x10
int 0x10
mov [8],ax
mov [10],bx
mov [12],cx
获取第一块硬盘的信息。
; Get hd0 data
mov ax,#0x0000
mov ds,ax
lds si,[4*0x41]
mov ax,#INITSEG
mov es,ax
mov di,#0x0080
mov cx,#0x10
rep
movsb
获取第二块硬盘的信息。
; Get hd1 data
mov ax,#0x0000
mov ds,ax
lds si,[4*0x46]
mov ax,#INITSEG
mov es,ax
mov di,#0x0090
mov cx,#0x10
rep
movsb
上面这一段代码是将一些东西进行了移动,都放到0x90000开头的位置上。我们直接看移动之后的结果就好了。
内存地址 长度(字节) 名称
0x90000 2 光标位置
0x90002 2 扩展内存数
0x90004 2 显示页面
0x90006 1 显示模式
0x90007 1 字符列数
0x90008 2 未知
0x9000A 1 显示内存
0x9000B 1 显示状态
0x9000C 2 显卡特性参数
0x9000E 1 屏幕行数
0x9000F 1 屏幕列数
0x90080 16 硬盘1参数表
0x90090 16 硬盘2参数表
0x901FC 2 根设备号
cli ; no interrupts allowed ;
这里的cli是关闭中断的意思,这是为什么呢?因为只要要将BIOS的中断向量表进行替换,所以不允许中断。
; first we move the system to it's rightful place
mov ax,#0x0000
cld ; 'direction'=0, movs moves forward
do_move:
mov es,ax ; destination segment
add ax,#0x1000
cmp ax,#0x9000
jz end_move
mov ds,ax ; source segment
sub di,di
sub si,si
mov cx,#0x8000
rep movsw
jmp do_move
; then we load the segment descriptors
end_move:
...
上面这段代码也是一个移动内存的操作,就是把0x10000到0x90000的数据都移动到内存最开始的位置上。
栈顶0x9FF00 |
---|
setup |
临时变量区 |
system |
也就说说现在内存区域大致变成了这个样子。 |
16位的实模式转换为32位的保护模式
这是一个历史遗留问题,intel的CPU以前是16的实模式,现在大多数是32位甚至64位的保护模式,为了实现兼容,需要进行模式的转化。
lidt idt_48 ; load idt with 0,0
lgdt gdt_48 ; load gdt with whatever appropriate
idt_48:
.word 0 ; idt limit=0
.word 0,0 ; idt base=0L
现在的CPU还处在16位的实模式下,大家可以设想一下,16位和32位有什么不同,很显然,内存地址的表示不同了,因此寻址的计算方式也就发生了变化,这是模式转换要完成的主要工作。
在实模式下,物理地址是段基址左移4位再加上偏移地址计算出来的。
在保护模式下,同样的代码物理地址的计算方式大不相同,我们来看一下!!!
ds寄存器中存的值叫做段选择子,存储着段描述符的索引
描述符索引 | TI | RPL |
---|---|---|
15 14 13 12 11 10 9 8 7 6 5 4 3 | 2 | 1 0 |
通过段描述符索引,可以从全局描述符表gdt中找到一个段描述符,段描述服里面存储这段基址。
段寄存器里面是段选择子,拿着段选择子去全局描述符表里面去找段描述符,从段描述符里面把段基址取出来,和偏移地址相加,得到物理地址。
那么,好了,全局描述符表在哪里,它是长什么样子呢???
在哪里: 在内存当中,操作系统通过gdtr寄存器存储着它存放的位置。
lgdt gdt_48
gdt_48:
.word 0x800 ; gdt limit=2048, 256 GDT entries
.word 512+gdt,0x9 ; gdt base = 0X9xxxx
lgdt这个命令把gdt_48放到gdtr寄存器当中,
gdt_48存放着一个48位的数据,高32位存放着全局描述符表gdt的内存地址。
0x90200+gdt
全局描述符表有三个段描述符,第一个为空,第二个是代码段描述符,第三个是数据段描述符。
代码段描述符和数据段描述符的段基址都是0
好的,OK,今天就说到这里吧!我会继续学习下去的,有什么问题欢迎评论区留言讨论!