【日拱一卒行而不辍20220921】自制操作系统

8086内存安排

8086实模式的寻址范围只有1MB。其中:

系统硬件使用的存储器地址被安排在高端,地址从0xA0000H(684KB)开始的384KB中,其中有用于显示的视频缓冲区;

内存低端安排了中断向量表和BIOS数据区;

剩下的从0x00500H开始的到0xA0000H总共不到640KB的内存是操作系统和应用程序所能够使用的,应用程序不能够使用这600KB以外的内存,这就是著名的“640KB限制”。

【日拱一卒行而不辍20220921】自制操作系统_第1张图片

 彩显缓冲区

从上图可以看出,0xB0000H开始到0xB8000H为单色字符模式视频缓冲区;0xB8000H到C0000为彩色字符模式视频缓冲区。

内存地址空间从B8000H~BFFFFH 共32KB空间([B800:0000]到[B800:8FFF]),称为80x25彩色字符模式显示缓冲区,向这个地址写入的数据会立即出现在显示器上。

窗口大小为160x25个字节(4000个字节),每两个字节为一个字符的参数:

偶数地址存放字符的ascii码,奇数地址存放字符属性。

一行可以存放80个字符一共25行,共可以存放80x25个字符2000个字符。每个字符可以有FF=2^8=256种属性。

第7位:代表闪烁效果
第6、5、4位:代表背景色的rgb=red green blue 红绿蓝
第3位:高亮
第2、1、0 位:字符颜色的rgb
对应颜色设置成1就变成对应颜色
0010 0100 绿底红字 十六进制=0x24
0100 0001 红底蓝字 十六进制=0x41
0001 0010 蓝底绿字 十六进制=0x12

对于昨天的第二扇区的代码,直接把数据放到了0xB8000H开始的彩色字符模式视频缓冲区内。

【日拱一卒行而不辍20220921】自制操作系统_第2张图片

 昨天的运行效果如下。

【日拱一卒行而不辍20220921】自制操作系统_第3张图片

 根据如下代码,可以看出每行用160字节可以显示80个字符。

【日拱一卒行而不辍20220921】自制操作系统_第4张图片

【日拱一卒行而不辍20220921】自制操作系统_第5张图片

单色缓冲区

将把上节中的es由0xB800改为0xB000后,qemu没有进行显示。所以后续还是用es=0xB800H吧。

【日拱一卒行而不辍20220921】自制操作系统_第6张图片

int13/02H中断

根据int13/02H调用格式:

AH = 02h
AL = number of sectors to read (must be nonzero)
CH = low eight bits of cylinder number
CL = sector number 1-63 (bits 0-5)
high two bits of cylinder (bits 6-7, hard disk only)
DH = head number
DL = drive number (bit 7 set for hard disk)
ES:BX -> data buffer

下述代码将第二扇区复制进了内存es:bx=[new:0]位置

        mov ah, 0x02
        mov al, 1
        mov ch, 0
        mov cl, 2
        mov dh, 0
        mov bx, new
        mov es, bx
        xor bx, bx
        int 0x13

下述代码令cs:ip=[new:0]

        jmp new:0

通过上述两部操作,把第二扇区的代码复制到了[new:0]的位置,同时也让指令指针指向了此位置,从而可以继续运行代码。

去掉冗余代码,最精简的可以使用第二扇区代的代码如下。

[BITS 16]
org 0x7C00
start:
        mov ah, 0x02
        mov al, 1
        mov ch, 0
        mov cl, 2
        mov dh, 0
        mov bx, new
        mov es, bx
        xor bx, bx
        int 0x13
        jmp new:0
data:
        new equ 0x0500
times   510-($-$$) db 0
dw      0xaa55
sect2:
        mov ax, 0xB800
        mov es, ax
	mov byte [es:0x0000], '0'
	mov byte [es:0x0001], 0x48
	mov byte [es:0x00A0], '1'
	mov byte [es:0x00A1], 0x48
	mov byte [es:0x0140], '2'
	mov byte [es:0x0141], 0x48
	mov byte [es:0x00A0], '1'
	mov byte [es:0x00A1], 0x48
	mov byte [es:0x00A0], '1'
	mov byte [es:0x00A1], 0x48
        mov byte [es:420], 'H'
        mov byte [es:421], 0x48
        mov byte [es:422], 'E'
        mov byte [es:423], 0x68
        mov byte [es:424], 'L'
        mov byte [es:425], 0x28
        mov byte [es:426], 'L'
        mov byte [es:427], 0x38
        mov byte [es:428], 'O'
        mov byte [es:429], 0x18
        mov byte [es:430], '!'
        mov byte [es:431], 0x58
        hlt

GDT

全局描述符表Global Descriptor Table,表中每个元素8个字节,每个元素表示一个段(代码段,数据段,栈段)的信息,且GDT在进入保护模式之前必须存在,所以它必须位于1MB以下。

GDTR中存放的是GDT在内存中的基地址和其表长界限。

【日拱一卒行而不辍20220921】自制操作系统_第7张图片

 LDT和GDT从本质上说是相同的,只是LDT嵌套在GDT之中。

LDTR记录局部描述符表的起始位置,与GDTR不同,LDTR的内容是一个段选择子。

由于LDT本身同样是一段内存,也是一个段,所以它也有个描述符描述它,这个描述符就存储在GDT中,对应这个表述符也会有一个选择子,LDTR装载的就是这样一个选择子。

LDTR可以在程序中随时改变,通过使用lldt指令。如上图,如果装载的是Selector 2则LDTR指向的是表LDT2。

由于每个进程都有自己的一套程序段、数据段、堆栈段,有了局部描述符表LDT则可以将每个进程的程序段CS、数据段DS、堆栈段SS封装在一起,只要改变LDTR就可以实现对不同进程的段进行访问。

如下代码为建立一个GDT的示例,首先定义一段连续内存。本示例构建了3个元素CODE_DESC、DATA_DESC、VIDEO_DESC,并预留了60个元素。在lgdt命令中将GDT_LIMIT和GDT_BASE赋给了寄存器GDTR。

其中:DD 的意思是define double(4-bytes),与之相似的还有db(byte)dw(word,2-bytes)

; gdt
GDT_BASE:
  dd 0x00000000
  dd 0x00000000

CODE_DESC:
  dd DESC_CODE_LOW_32
  dd DESC_CODE_HIGH_32

DATA_DESC:
  dd DESC_DATA_LOW_32
  dd DESC_DATA_HIGH_32

VIDEO_DESC:
  dd DESC_VIDEO_LOW_32
  dd DESC_VIDEO_HIGH_32

; reserve 60 gdt entries space
times 60 dq 0

GDT_SIZE    equ   $ - GDT_BASE
GDT_LIMIT   equ   GDT_SIZE - 1

gdt_ptr:
  dw GDT_LIMIT
  dd GDT_BASE
  ; enable A20
  in al, 0x92
  or al, 0000_0010b
  out 0x92, al

  ; load GDT
  lgdt [gdt_ptr]

  ; open protection mode - set cr0 bit 0
  mov eax, cr0
  or eax, 0x00000001
  mov cr0, eax

  ; refresh pipeline
  jmp dword SELECTOR_CODE:protection_mode_entry

其中,SELECTOR_CODE等的定义如下:

;*************************** segment selector *********************************;
RPL0 equ 00b
RPL1 equ 01b
RPL2 equ 10b
RPL3 equ 11b

TI_GDT equ 000b
TI_LDT equ 100b

SELECTOR_CODE    equ (0x0001 << 3) + TI_GDT + RPL0
SELECTOR_DATA    equ (0x0002 << 3) + TI_GDT + RPL0
SELECTOR_VIDEO   equ (0x0003 << 3) + TI_GDT + RPL0

jmp dword SELECTOR_CODE:protection_mode_entry

执行这一句会把SELECTOR_CODE装入CS,并跳转到SELECTOR_CODE:protection_mode_entry处。

LDT

LDT示例代码如下所示

; LDT  
[SECTION .ldt]  
ALIGN   32  
LABEL_LDT:  
;                            段基址       段界限      属性  
LABEL_LDT_DESC_CODEA: Descriptor 0, CodeALen - 1, DA_C + DA_32 ; Code, 32 位  
LABEL_LDT_DESC_CODEB: Descriptor 0, CodeBLen - 1, DA_C + DA_32 ; Code, 32 位  
LDTLen      equ $ - LABEL_LDT ;LDT表长度  
  
; LDT 选择子  
SelectorLDTCodeA    equ LABEL_LDT_DESC_CODEA    - LABEL_LDT + SA_TIL  
SelectorLDTCodeB    equ LABEL_LDT_DESC_CODEB    - LABEL_LDT + SA_TIL  
; END of [SECTION .ldt]


; Load LDT  
mov ax, SelectorLDT     
lldt    ax      ;加载LDT在GDT中的描述符

你可能感兴趣的:(Linux,OS,服务器,运维)