操作系统制作(2)nasm改写linux0.00

环境:
virtual-box:版本 6.0.10 r132072 (Qt5.6.2)运行的的ubuntu18.04系统。
nasm汇编器:NASM version 2.13.02

讲述bochs运行一个简单AB任务切换的例子,运行效果如下:

一、源代码
1)bootsect.s代码

; boot.s 程序
; 首先利用 BIOS 中断把内核代码( head 代码)加载到内存 0x10000 处,然后移动到内存 0 处。
; 最后进入保护模式,并跳转到内存 0( head 代码)开始处继续运行。
BOOTSEG equ 0x07c0 ; 引导扇区(本程序)被 BIOS 加载到内存 0x7c00 处。
SYSSEG equ 0x1000 ; 内核( head)先加载到 0x10000 处,然后移动到 0x0 处。
SYSLEN equ 17 ; 内核占用的最大磁盘扇区数。
start:
        jmp BOOTSEG:go ; 段间跳转至 0x7c0:go 处。当本程序刚运行时所有段寄存器值

go:
        mov ax,cs ; 均为 0。该跳转语句会把 CS 寄存器加载为 0x7c0(原为 0)。
        mov ds,ax ; 让 DS 和 SS 都指向 0x7c0 段。
        mov ss,ax
        mov sp,0x400 ; 设置临时栈指针。其值需大于程序末端并有一定空间即可。

; 加载内核代码到内存 0x10000 开始处。
load_system:
        mov dx,0x0000 ; 利用 BIOS 中断 int 0x13 功能 2 从启动盘读取 head 代码。
        mov cx,0x0002 ; DH - 磁头号; DL - 驱动器号; CH - 10 位磁道号低 8 位;
        mov ax,SYSSEG ; CL - 位 7、 6 是磁道号高 2 位,位 5-0 起始扇区号(从 1 计)。
        mov es,ax ; ES:BX - 读入缓冲区位置( 0x1000:0x0000) 。
        xor bx,bx ; AH - 读扇区功能号; AL - 需读的扇区数( 17)。
        mov ax,0x200+SYSLEN
        int 0x13
        jnc ok_load ; 若没有发生错误则跳转继续运行,否则死循环。
die:
        jmp die 

; 把内核代码移动到内存 0 开始处。共移动 8KB 字节(内核长度不超过 8KB)。
ok_load:
        cli ; 关中断。
        mov ax, SYSSEG ; 移动开始位置 DS:SI = 0x1000:0;目的位置 ES:DI=0:0。
        mov ds, ax
        xor ax, ax
        mov es, ax
        mov cx, 0x1000 ; 设置共移动 4K 次,每次移动一个字(dw) 。
        sub si,si
        sub di,di
        rep movsw ; 执行重复移动指令。
        ; 加载 IDT 和 GDT 基地址寄存器 IDTR 和 GDTR。
        mov ax, BOOTSEG
        mov ds, ax ; 让 DS 重新指向 0x7c0 段。
        lidt [idt_48] ; 加载 IDTR。 6 字节操作数: 2 字节表长度, 4 字节线性基地址。
        lgdt [gdt_48] ; 加载 GDTR。 6 字节操作数: 2 字节表长度, 4 字节线性基地址。

        ; 设置控制寄存器 CR0(即机器状态字),进入保护模式。段选择符值 8 对应 GDT 表中第 2 个段描述符。
        mov ax,0x0001 ; 在 CR0 中设置保护模式标志 PE(位 0)。
        lmsw ax ; 然后跳转至段选择符值指定的段中,偏移 0 处。
        jmp 8:0 ; 注意此时段值已是段选择符。该段的线性基地址是 0。

; 下面是全局描述符表 GDT 的内容。其中包含 3 个段描述符。第 1 个不用,另 2 个是代码和数据段描述符。
;---------------------------------------------------------------------------------------------
;      |31|30|29|28|27|26|25|24|23|22|21|20|19|18|17|16|15|14|13|12|11|10|9|8|7|6|5|4|3|2|1|0|
;high  |_____________________word3_____________________|__________________word2______________|      
;low   |_____________________word1_____________________|__________________word0______________|
;
;---------------------------------------------------------------------------------------------
;
;---------------------------------------------------------------------------------------------
;      |31|30|29|28|27|26|25|24|23|22|21|20|19|18|17|16|15|14|13|12|11|10|9|8|7|6|5|4|3|2|1|0|
;high  |____Base_addr(31~24)___|___________|limit(19~16|___________|___TYPE__|__addr(23~16)__|      
;low   |_______________Base_addr(15~0)_________________|____________Seg_limit(15~0)__________|

gdt:
        dw 0,0,0,0 ; 段描述符 0,不用。每个描述符项占 8 字节。

        dw 0x07FF ; 段描述符 1。 8Mb - 段限长值=2047 (2048*4096=8MB)。
        dw 0x0000 ; 段基地址=0x00000。
        dw 0x9A00 ; 是代码段,可读/执行。
        dw 0x00C0 ; 段属性颗粒度=4KB, 80386。

        dw 0x07FF ; 段描述符 2。 8Mb - 段限长值=2047 (2048*4096=8MB)。
        dw 0x0000 ; 段基地址=0x00000。
        dw 0x9200 ; 是数据段,可读写。
        dw 0x00C0 ; 段属性颗粒度=4KB, 80386。

; 下面分别是 LIDT 和 LGDT 指令的 6 字节操作数。
idt_48:
        dw 0 ; IDT 表长度是 0。
        dw 0,0 ; IDT 表的线性基地址也是 0。
gdt_48:
        dw 0x7ff ; GDT 表长度是 2048 字节,可容纳 256 个描述符项。
        dw 0x7c00+gdt,0 ; GDT 表的线性基地址在 0x7c0 段的偏移 gdt 处。

end:
        times 510-($-$$) db 0
        dw 0xAA55 ; 引导扇区有效标志。必须处于引导扇区最后 2 字节处。                                                   

2)head.s代码

; head.s 包含 32 位保护模式初始化设置代码、时钟中断代码、系统调用中断代码和两个任务的代码。
; 在初始化完成之后程序移动到任务 0 开始执行,并在时钟中断控制下进行任务 0 和 1 之间的切换操作。
LATCH equ 11930		; 定时器初始计数值,即每隔 10 毫秒发送一次中断请求。
		;0x08	;00001 0 00b	--->GDT1	; 是代码段选择符.
		;0x10	;00010 0 00b	--->GDT2	; 是数据段描述符.
SCRN_SEL equ 0x18 	;00011 0 00b	--->GDT3	; 屏幕显示内存段选择符。
TSS0_SEL equ 0x20 	;00100 0 00b	--->GDT4	; 任务 0 的 TSS 段选择符。
LDT0_SEL equ 0x28	;00101 0 00b 	--->GDT5	; 任务 0 的 LDT 段选择符。
TSS1_SEL equ 0X30 	;00110 0 00b	--->GDT6	; 任务 1 的 TSS 段选择符。
LDT1_SEL equ 0x38 	;00111 0 00b	--->GDT7	; 任务 1 的 LDT 段选择符。

bits 32			;nasm instruction:32 bits model.
startup_32:
; 首先加载数据段寄存器 DS、堆栈段寄存器 SS 和堆栈指针 ESP。所有段的线性基地址都是 0。
	mov eax,0x10 	; 0x10 是 GDT 中数据段选择符。00010 0 00b --->GDT2
	mov ds,ax
	lss esp,[init_stack]
; 在新的位置重新设置 IDT 和 GDT 表。
	call setup_idt	; 设置 IDT。先把 256 个中断门都填默认处理过程的描述符。
	call setup_gdt	; 设置 GDT。
	mov eax,0x10	; 在改变了 GDT 之后重新加载所有段寄存器。
	mov ds,ax
	mov es,ax
	mov fs,ax
	mov gs,ax
	lss esp,[init_stack]
; 设置 8253 定时芯片。把计数器通道 0 设置成每隔 10 毫秒向中断控制器发送一个中断请求信号。
	mov al,0x36	; 控制字:设置通道 0 工作在方式 3、 计数初值采用二进制。
	mov edx,0x43	; 8253 芯片控制字寄存器写端口。
	out dx,al
	mov eax,LATCH	; 初始计数值设置为 LATCH( 1193180/100) ,即频率 100HZ。
	mov edx,0x40	; 通道 0 的端口。
	out dx,al	; 分两次把初始计数值写入通道 0。
	mov al,ah
	out dx,al
; 在 IDT 表第 8 和第 128( 0x80) 项处分别设置定时 中断门 描述符 和 系统调用 陷阱门 描述符。
	mov eax,0x00080000	; 中断程序属内核,即 EAX 高字是内核代码段选择符 0x0008。
	mov ax,timer_interrupt	; 设置定时中断门描述符。取定时中断处理程序地址。
	mov dx,0x8E00		; 中断门类型是 14(屏蔽中断),特权级 0 或硬件使用。
	mov ecx,0x08		; 开机时 BIOS 设置的时钟中断向量号 8。这里直接使用它。
	lea esi,[idt+ecx*8]	; 把 IDT 描述符 0x08 地址放入 ESI 中,然后设置该描述符。idt+64bytes offset
	mov [esi],eax
	mov [esi+4],edx

	mov ax,system_interrupt ; 设置系统调用陷阱门描述符。取系统调用处理程序地址。
	mov dx,0xef00		; 陷阱门类型是 15,特权级 3 的程序可执行。
	mov ecx,0x80		; 系统调用向量号是 0x80。
	lea esi,[idt+ecx*8]	; 把 IDT 描述符项 0x80 地址放入 ESI 中,然后设置该描述符。
	mov [esi],eax
	mov [esi+4],edx
; 好了,现在我们为移动到任务 0(任务 A)中执行来操作堆栈内容,在堆栈中人工建立中断返回时的场景。
	pushf			; 复位标志寄存器 EFLAGS 中的嵌套任务标志。
	and dword [esp],0xffffbfff
	popf
	mov eax,TSS0_SEL	; 把任务 0 的 TSS 段选择符加载到任务寄存器 TR。
	ltr ax
	mov eax,LDT0_SEL	; 把任务 0 的 LDT 段选择符加载到局部描述符表寄存器 LDTR。
	lldt ax			; TR 和 LDTR 只需人工加载一次,以后 CPU 会自动处理。
	mov dword[current],0
	sti			; 现在开启中断,并在栈中营造中断返回时的场景。
	push long 0x17		; 把任务 0 当前局部空间数据段(堆栈段)选择符入栈。
	push long init_stack	; 把堆栈指针入栈(也可以直接把 ESP 入栈)。
	pushf			; 把标志寄存器值入栈。
	push long 0x0f		; 把当前局部空间代码段选择符入栈。
	push long task0		; 把代码指针入栈。
	iret			; 执行中断返回指令,从而切换到特权级 3 的任务 0 中执行。

; 以下是设置 GDT 和 IDT 中描述符项的子程序。
setup_gdt:			; 使用 6 字节操作数 lgdt_48 设置 GDT 表位置和长度。
	lgdt [lgdt_48]
	ret

; 段代码暂时设置 IDT 表中所有 256 个中断门描述符都为同一个默认值,均使用默认的中断处理过程
;gnore_int。设置的具体方法是:首先在 eax 和 edx 寄存器对中分别设置好默认中断门描述符的 0-3
; 节和 4-7 字节的内容,然后利用该寄存器对循环往 IDT 表中填充默认中断门描述符内容。
setup_idt:			; 把所有 256 个中断门描述符设置为使用默认处理过程。
	lea edx,[ignore_int]	; 设置方法与设置定时中断门描述符的方法一样。
	mov eax,0x00080000	; 选择符为 0x0008。
	mov ax,dx
	mov dx,0x8E00		; 中断门类型,特权级为 0。
	lea edi,[idt]
	mov ecx,256		; 循环设置所有 256 个门描述符项。
rp_idt:
       	mov [edi],eax
	mov [edi+4],edx
	add edi,8
	dec ecx
	jne rp_idt
	lidt [lidt_48]		; 最后用 6 字节操作数加载 IDTR 寄存器。
	ret

; 显示字符子程序。取当前光标位置并把 AL 中的字符显示在屏幕上。整屏可显示 80 X 25 个字符。
write_char:
	push gs			; 首先保存要用到的寄存器, EAX 由调用者负责保存。
	push ebx
	mov ebx,SCRN_SEL	; 然后让 GS 指向显示内存段( 0xb8000) 。
	mov gs,bx
	mov bx,[scr_loc]	; 再从变量 scr_loc 中取目前字符显示位置值。
	shl ebx,1		; 因为在屏幕上每个字符还有一个属性字节,因此字符
	mov [gs:ebx],al		; 实际显示位置对应的显示内存偏移地址要乘 2。
	shr ebx,1		; 把字符放到显示内存后把位置值除 2 加 1,此时位置值对
	inc ebx			; 应下一个显示位置。如果该位置大于 2000,则复位成 0。
	cmp ebx,2000
	jb .1
	mov ebx,0
.1:
	mov [scr_loc],ebx	; 最后把这个位置值保存起来( scr_loc),
	pop ebx			; 并弹出保存的寄存器内容,返回。
	pop gs
	ret

; 以下是 3 个中断处理程序:默认中断、定时中断和系统调用中断。
; ignore_int 是默认的中断处理程序,若系统产生了其他中断,则会在屏幕上显示一个字符'C'。
align 4
ignore_int:
	push ds
	push eax
	mov eax,0x10		; 首先让 DS 指向内核数据段,因为中断程序属于内核。
	mov ds,ax
	mov eax,67		; 在 AL 中存放字符'C'的代码,调用显示程序显示在屏幕上。
	call write_char
	pop eax
	pop ds
	iret

; 这是定时中断处理程序。其中主要执行任务切换操作。
align 4
timer_interrupt:
	push ds
	push eax
	mov eax,0x10		; 首先让 DS 指向内核数据段。
	mov ds,ax
	mov al,0x20		; 然后立刻允许其他硬件中断,即向 8259A 发送 EOI 命令。
	out 0x20,al
	mov eax,1		; 接着判断当前任务,若是任务 1 则去执行任务 0,或反之。
	cmp [current],eax
	je .1
	mov [current],eax	; 若当前任务是 0,则把 1 存入 current,并跳转到任务 1
	jmp TSS1_SEL:0		; 去执行。注意跳转的偏移值无用,但需要写上。
	jmp .2
.1:
	mov dword[current],0
	jmp TSS0_SEL:0 
.2:
	pop eax
	pop ds
	iret

; 系统调用中断 int 0x80 处理程序。该示例只有一个显示字符功能。
align 4
system_interrupt:
	push ds
	push edx
	push ecx
	push ebx
	push eax
	mov edx,0x10		; 首先让 DS 指向内核数据段。
	mov ds,dx
	call write_char		; 然后调用显示字符子程序 write_char,显示 AL 中的字符。
	pop eax
	pop ebx
	pop ecx
	pop edx
	pop ds
	iret

;/*********************************************/
current:dd 0 ; 当前任务号( 0 或 1)。
scr_loc:dd 0 ; 屏幕当前显示位置。按从左上角到右下角顺序显示。

align 4
lidt_48:
	dw 256*8-1 ; 加载 IDTR 寄存器的 6 字节操作数:表长度和基地址。
	dd idt
lgdt_48:
	dw (end_gdt-gdt)-1 ; 加载 GDTR 寄存器的 6 字节操作数:表长度和基地址。
	dd gdt

align 8
; LDT
;---------------------------------------------------------------------------------------------
;      |31|30|29|28|27|26|25|24|23|22|21|20|19|18|17|16|15|14|13|12|11|10|9|8|7|6|5|4|3|2|1|0|
;high  |_____________________word3_____________________|__________________word2______________|
;low   |_____________________word1_____________________|__________________word0______________|
;
;---------------------------------------------------------------------------------------------
;
;---------------------------------------------------------------------------------------------
;      |31|30|29|28|27|26|25|24|23|22|21|20|19|18|17|16|15|14|13|12|11|10|9|8|7|6|5|4|3|2|1|0|
;high  |___________entry_offset_addr(31~16)____________|_P|_DPL_|____________________________|
;low   |_______________Seg_select______________________|_______entry_offset_addr(15~0)_______|

idt:
	times 256 dq 0 ; IDT 空间。共 256 个门描述符,每个 8 字节,占用 2KB。

; 下面是全局描述符表 GDT 的内容。其中包含 3 个段描述符。第 1 个不用,另 2 个是代码和数据段描述符。
;---------------------------------------------------------------------------------------------
;      |31|30|29|28|27|26|25|24|23|22|21|20|19|18|17|16|15|14|13|12|11|10|9|8|7|6|5|4|3|2|1|0|
;high  |_____________________word3_____________________|__________________word2______________|
;low   |_____________________word1_____________________|__________________word0______________|
;
;---------------------------------------------------------------------------------------------
;
;---------------------------------------------------------------------------------------------
;      |31|30|29|28|27|26|25|24|23|22|21|20|19|18|17|16|15|14|13|12|11|10|9|8|7|6|5|4|3|2|1|0|
;high  |____Base_addr(31~24)___|___________|limit(19~16|___________|___TYPE__|__addr(23~16)__|
;low   |_______________Base_addr(15~0)_________________|____________Seg_limit(15~0)__________|

gdt:
	dq 0x0000000000000000 ; GDT 表。第 1 个描述符不用。
	dq 0x00c09a00000007ff ; 第 2 个是内核代码段描述符。其选择符是 0x08。
	dq 0x00c09200000007ff ; 第 3 个是内核数据段描述符。其选择符是 0x10。
	dq 0x00c0920b80000002 ; 第 4 个是显示内存段描述符。其选择符是 0x18。
	dw 0x68, tss0, 0xe900, 0x0 ; 第 5 个是 TSS0 段的描述符。其选择符是 0x20
	dw 0x40, ldt0, 0xe200, 0x0 ; 第 6 个是 LDT0 段的描述符。其选择符是 0x28
	dw 0x68, tss1, 0xe900, 0x0 ; 第 7 个是 TSS1 段的描述符。其选择符是 0x30
	dw 0x40, ldt1, 0xe200, 0x0 ; 第 8 个是 LDT1 段的描述符。其选择符是 0x38
end_gdt:
	times 128 dd 0 ; 初始内核堆栈空间。
init_stack: ; 刚进入保护模式时用于加载 SS:ESP 堆栈指针值。
	dd init_stack ; 堆栈段偏移位置。
	dw 0x10 ; 堆栈段同内核数据段。

; 下面是任务 0 的 LDT 表段中的局部段描述符。
align 8
ldt0:
	dq 0x0000000000000000 ; 第 1 个描述符,不用。
	dq 0x00c0fa00000003ff ; 第 2 个局部代码段描述符,对应选择符是 0x0f。
	dq 0x00c0f200000003ff ; 第 3 个局部数据段描述符,对应选择符是 0x17。
; 下面是任务 0 的 TSS 段的内容。注意其中标号等字段在任务切换时不会改变。
tss0:
	dd 0			; back link
	dd krn_stk0, 0x10	; esp0, ss0
	dd 0, 0, 0, 0, 0	; esp1, ss1, esp2, ss2, cr3
	dd 0, 0, 0, 0, 0	; eip, eflags, eax, ecx, edx
	dd 0, 0, 0, 0, 0	; ebx esp, ebp, esi, edi
	dd 0, 0, 0, 0, 0, 0	; es, cs, ss, ds, fs, gs
	dd LDT0_SEL, 0x8000000	; ldt, trace bitmap

times 128 dd 0 ; 这是任务 0 的内核栈空间。
krn_stk0:

; 下面是任务 1 的 LDT 表段内容和 TSS 段内容。
align 8
ldt1:
	dq 0x0000000000000000 ; 第 1 个描述符,不用。
	dq 0x00c0fa00000003ff ; 选择符是 0x0f,基地址 equ 0x00000。
	dq 0x00c0f200000003ff ; 选择符是 0x17,基地址 equ 0x00000。

tss1:
	dd 0					;/* back link */
	dd krn_stk1, 0x10			;/* esp0, ss0 */
	dd 0, 0, 0, 0, 0			;/* esp1, ss1, esp2, ss2, cr3 */
	dd task1, 0x200				;/* eip, eflags */
	dd 0, 0, 0, 0				;/* eax, ecx, edx, ebx */
	dd usr_stk1, 0, 0, 0			;/* esp, ebp, esi, edi */
	dd 0x17,0x0f,0x17,0x17,0x17,0x17	;/* es, cs, ss, ds, fs, gs */
	dd LDT1_SEL, 0x8000000			;/* ldt, trace bitmap */

	times 128 dd 0				; 这是任务 1 的内核栈空间。其用户栈直接使用初始栈空间。
krn_stk1:

; 下面是任务 0 和任务 1 的程序,它们分别循环显示字符'A'和'B'。
task0:
	mov eax,0x17				; 首先让 DS 指向任务的局部数据段。
	mov ds,ax				; 因为任务没有使用局部数据,所以这两句可省略。
	mov al,65				; 把需要显示的字符'A'放入 AL 寄存器中。
	int 0x80				; 执行系统调用,显示字符。
	mov ecx,0xfff				; 执行循环,起延时作用。
.1:
	loop .1
	jmp task0				; 跳转到任务代码开始处继续显示字符。
task1:
	mov al,66				; 把需要显示的字符'B'放入 AL 寄存器中。
	int 0x80				; 执行系统调用,显示字符。
	mov ecx,0xfff				; 延时一段时间,并跳转到开始处继续循环显示。
.1:
	loop .1
	jmp task1

	times 128 dd 0				; 这是任务 1 的用户栈空间。
usr_stk1:

二、相关技术介绍
1)全局描述符

和全局描述符相关的有:
!)全局描述符表寄存器 GDTR
GDTR 寄存器(48位)格式如下:
在这里插入图片描述
指令 LGDT 用于加载GDTR 寄存器的内容。

范例如下:

lgdt [gdt_48]
gdt_48:
        dw 0x7ff        ; (16位表长度)GDT 表长度是 2048 字节,可容纳 256 个描述符项。
        dw 0x7c00+gdt,0 ; (32位线性基地址)GDT 表的线性基地址在 0x7c0 段的偏移 gdt 处。

每一个GDT表项称为一个段描述符(64位),32位模式下,可以通过段选择符(16位)来索引到对应的段描述符。

段描述符(64位)的通用格式如下:
在这里插入图片描述
根据TYPE的值不同,段描述符(64位)可以分为代码段、数据段和系统段描述符。
而代码段、数据段描述符又可细分为十几种不同的描述符。

注意:可以看出,段描述符由基地址+段限长组成,描述的是一个内存段的范围,没有偏移值什么的。

!!)段选择符
进入32位保护模式后,段寄存器(cs,ds,es,gs,fs等,都是16位寄存器)选用的都是段选择符。
段选择符(16位)格式如下:
在这里插入图片描述
段选择符 3 个字段内容:
 请求特权级 RPL( Requested Privilege Level);0~1位
 表指示标志 TI( Table Index); 2位
 索引值( Index)。3~15位

TI=0 表示描述符在 GDT 中;
TI=1 表示描述符在 LDT 中。
索引字段给出了描述符在 GDT 或 LDT 表中的索引项号。
范例如下:

jmp 8,0

8是cs寄存器中的值,转换为二进制00001 0 00b,查找段选择符格式可知:
索引值为00001,即第一个段描述符;
T1=0,即段描述符在GDT中;
特权级为0.

!!!)GDT表(64位)
范例如下:

; 下面是全局描述符表 GDT 的内容。其中包含 3 个段描述符。第 1 个不用,另 2 个是代码和数据段描述符。
gdt: 
        dw 0,0,0,0 ; 段描述符 0,不用。每个描述符项占 8 字节。

        dw 0x07FF ; 段描述符 1。 8Mb - 段限长值=2047 (2048*4096=8MB)。
        dw 0x0000 ; 段基地址=0x00000。
        dw 0x9A00 ; 是代码段,可读/执行。
        dw 0x00C0 ; 段属性颗粒度=4KB, 80386。

        dw 0x07FF ; 段描述符 2。 8Mb - 段限长值=2047 (2048*4096=8MB)。
        dw 0x0000 ; 段基地址=0x00000。
        dw 0x9200 ; 是数据段,可读写。
        dw 0x00C0 ; 段属性颗粒度=4KB, 80386。

对应于上面jmp 8,0的解析,可知,选择的段是段描述符1。根据段描述符1的内容,查看上面的GDT表(64位)格式,可知,对应段基地址0x00000000。因此,程序跳转到线性地址0x00000000处运行。

2)中断描述符

和中断描述符相关的有:
!)中断描述符表寄存器 IDTR
IDTR寄存器(48位)格式如下:
在这里插入图片描述
可知,中断描述符表寄存器和全局描述符表寄存器是一样的结构。

指令 LIDT 用于加载IDTR 寄存器的内容。

范例如下:

lidt [idt_48]

align 4
lidt_48:
        dw 256*8-1              ; 加载 IDTR 寄存器的 6 字节操作数:表长度和基地址。
        dd idt 

align 8
idt:
        times 256 dq 0          ; IDT 空间。共 256 个门描述符,每个 8 字节,占用 2KB。

IDT 表中可以存放三种类型的门描述符:
 中断门( Interrupt gate)描述符
 陷阱门( Trap gate)描述符
 任务门( Task gate)描述符

IDT表项(64位)格式如下:
在这里插入图片描述
可以看出,IDT表项和GDT表项,格式是不一样的。
注意:可以看出,IDT表中的门描述符由 段选择符(16位)+偏移值(32位) 组成。
这个段选择符一般是内核代码段0x00080000。

中断过程调用如下:
,图。

中断向量相当于一个IDT表索引,查找中断描述符表(64位),然后根据中断描述符表(64位)的段选择符(16位)查找GDT表项,得到段基址+段限长,然后根据IDT表项的偏移值(32位),得到具体的线性地址(异常或中断处理过程的地址)。

###设置IDT表项,int IDT段索引就可以跳转到相应的偏移地址处执行程序。

以上GDT表和IDT表描述完毕,如果要切换任务,则还需要TSS任务表

3)任务

和任务相关的有:
!)任务寄存器 TR(16位)
TR 寄存器用于存放当前任务 TSS 段的 16 位段选择符、 32 位基地址、 16 位段长度和描述符属性值。
它引用 GDT 表中的一个 TSS 类型的描述符。指令 LTR 和 STR 分别用于加载和保存 TR 寄存器的段选择符
部分。当使用 LTR 指令把选择符加载进任务寄存器时, TSS 描述符中的段基地址、段限长度以及描述符属
性会被自动地加载到任务寄存器中。当执行任务切换时,处理器会把新任务的 TSS 的段选择符和段描述符
自动地加载进任务寄存器 TR 中。

任务寄存器 TR格式如下:
在这里插入图片描述
范例如下:

TSS0_SEL equ 0x20       ;00100 0 00b    --->GDT4        ; 任务 0 的 TSS 段选择符。

        mov eax,TSS0_SEL ; 把任务 0 的 TSS 段选择符加载到任务寄存器 TR。
        ltr ax

gdt:
        dq 0x0000000000000000 ; GDT 表。第 1 个描述符不用。
        dq 0x00c09a00000007ff ; 第 2 个是内核代码段描述符。其选择符是 0x08。
        dq 0x00c09200000007ff ; 第 3 个是内核数据段描述符。其选择符是 0x10。
        dq 0x00c0920b80000002 ; 第 4 个是显示内存段描述符。其选择符是 0x18。
        dw 0x68, tss0, 0xe900, 0x0 ; 第 5 个是 TSS0 段的描述符。其选择符是 0x20
        dw 0x40, ldt0, 0xe200, 0x0 ; 第 6 个是 LDT0 段的描述符。其选择符是 0x28
        dw 0x68, tss1, 0xe900, 0x0 ; 第 7 个是 TSS1 段的描述符。其选择符是 0x30
        dw 0x40, ldt1, 0xe200, 0x0 ; 第 8 个是 LDT1 段的描述符。其选择符是 0x38
; 下面是任务 0 的 TSS 段的内容。注意其中标号等字段在任务切换时不会改变。
tss0:
        dd 0                    ; back link
        dd krn_stk0, 0x10       ; esp0, ss0
        dd 0, 0, 0, 0, 0        ; esp1, ss1, esp2, ss2, cr3
        dd 0, 0, 0, 0, 0        ; eip, eflags, eax, ecx, edx
        dd 0, 0, 0, 0, 0        ; ebx esp, ebp, esi, edi
        dd 0, 0, 0, 0, 0, 0     ; es, cs, ss, ds, fs, gs
        dd LDT0_SEL, 0x8000000  ; ldt, trace bitmap

根据任务寄存器 TR(16位)的段选择符,查询GDT表,找到 dw 0x68, tss0, 0xe900, 0x0。继续查询GDT表项的格式,得到:
32位基地址:tss0
段限长:0x68
属性:0xe900—>0x1110 1001 0000 0000
P(15):-- 段存在=1
DPL(14-13):=3
S(12):-- 描述符类型(0-系统;1-代码或数据)=0
TYPE(11-8):0。数据段。

有关代码或数据段的类型,暂且不提。

!!)TSS 段描述符格式如下:
在这里插入图片描述
与其他段一样,任务状态段 TSS 也是使用段描述符来定义。TYPE是10B1。

类型字段 TYPE 中的忙标志 B 用于指明任务是否处于忙状态。忙状态的任务是当前正在执行的任务或
等待执行(被挂起)的任务。值为 0b1001 的类型字段表明任务处于非活动状态;而值为 0b1011 的类型字
段表示任务正忙。任务是不可以递归执行的,因此处理器使用忙标志 B 来检测任何企图对被中断执行任务
的调用。
!!!)任务状态段 TSS
用于恢复一个任务执行的处理器状态信息被保存在称为任务状态段 TSS( Task state segment)的段中。
在这里插入图片描述
范例如下:

; 下面是任务 0 的 TSS 段的内容。注意其中标号等字段在任务切换时不会改变。
tss0:
        dd 0                    ; back link
        dd krn_stk0, 0x10       ; esp0, ss0
        dd 0, 0, 0, 0, 0        ; esp1, ss1, esp2, ss2, cr3
        dd 0, 0, 0, 0, 0        ; eip, eflags, eax, ecx, edx
        dd 0, 0, 0, 0, 0        ; ebx esp, ebp, esi, edi
        dd 0, 0, 0, 0, 0, 0     ; es, cs, ss, ds, fs, gs
        dd LDT0_SEL, 0x8000000  ; ldt, trace bitmap
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章