用大白话说操作系统(三)

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寄存器中存的值叫做段选择子,存储着段描述符的索引

描述符索引TIRPL
15 14 13 12 11 10 9 8 7 6 5 4 321 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,今天就说到这里吧!我会继续学习下去的,有什么问题欢迎评论区留言讨论!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

神仙诙谐代码

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值