【终章】从实模式到保护模式笔记13中断与抢占式多任务

【终章】从实模式到保护模式笔记13中断与抢占式多任务

0x00 代码

    本章代码的mbr、内核主体部分和两个用户程序均使用平坦模型,为了节省时间,会省略内核主体部分没有变动的例程。

    mbr代码:

;常量部分
core_base_address equ 0x00040000;内核加载地址
core_start_sector



 equ 0x00000001;内核所在扇区号

;========================================================
SECTION mbr vstart=0x00007c00;编译器会把标号+0x7c00
mov ax,cs
mov ss,ax
mov sp,0x7c00


;计算GDT所在的逻辑段地址
mov eax,[cs:pgdt+0x02];GDT的32位物理地址,实模式下使用32位寄存器
xor edx,edx
mov ebx,16
div ebx
mov ds,eax
mov ebx,edx;ds:bx即段地址:偏移地址 指向GDT物理地址 ebx小于16


;GDT 0#不可用
;1# 代码段 段基地址0x00000000 界限 0xfffff 粒度4KB DPL 0
mov dword [ebx+0x08],0x0000ffff
mov dword [ebx+0x0c],0x00cf9800

;2# 数据段 段基地址0x00000000 界限 0xfffff 粒度4KB DPL 0
mov dword [ebx+0x10],0x0000ffff
mov dowrd [ebx+0x14],0x00cf9200

mov word [cs:pgdt],23;段界限
lgdt [cs:pgdt];写入GDTR

;打开a20 第21根地址线
in al,0x92
or al,0000_0010B
out 0x92,al

cli;保护模式下的idt尚未配置,关闭intr引脚的中断

mov eax,cr0
or eax,1;pe位置1
mov cr0,eax

;dword 指定使用32位模式 段间跳转,刷新cs段寄存器
jmp dword 0x08:flush
[bits 32]
flush:
mov eax,0x10;加载数据段
mov ds,eax
mov ss,eax
mov esp,0x7c00;向下扩展
mov es,eax
mov fs,eax
mov gs,eax

mov edi,core_base_address
mov eax,core_start_sector
mov ebx,edi;ds:ebx eax
call read_hard_disk_0;ebx+=512

;以下读取整个程序
mov eax,[edi];内核大小
xor edx,edx
mov ecx,512
div ecx

or edx,edx
jnz @1;余数不是0
dec eax;余数是0,刚好整除完,之前读了一个扇区 减一

@1:
or eax,eax
jz pge; eax=0 长度<=512 不用再读了

;否则读取剩余
mov ecx,eax
mov eax,core_start_sector
inc eax
@2:
call read_hard_disk_0
inc eax
loop @2;ebx自动变化

;开启分页机制
pge:
mov ebx,0x00020000;内核PDT page director table
;最后一项指向自己,二进制低12位是属性 rw置1可读可写 p置1 存在内存
mov dword [ebx+1023*4],0x00020003
mov edx,0x00021003;页表
mov [ebx+0*4],edx ;第一项 对应页表
mov [ebx+0x200*4]  ;对应0x80000000 高10位对应的项
mov ebx,0x00021000
xor eax,eax
xor esi,esi
.b1:
mov edx,eax
or edx,0x00000003
mov [ebx+esi*4],edx
add eax,0x1000
inc esi
cmp esi,256
jl .b1 ;0-255项 低端1MB内存

;至此0x00020000 PDT 0x00021000页表 
;完成了 0x00000000-0x000fffff->0x00000000-0x000fffff
;和 0x80000000-0x800fffff->0x00000000-0x000fffff
;的映射

mov eax,0x00020000 ;同样低12位是属性=》PCD PWT=0
mov cr3,eax

;将gdt处改为0x80000000开始的映射
sgdt [pgdt]
add dword [pgdt+2],0x80000000
lgdt [pgdt]

mov eax,cor0
or eax,0x80000000
mov cr0,eax;正式开启分页机制

;esp使用0x80000000线性地址映射
add esp,0x80000000
;eip也使用0x80000000线性地址映射,并跳转
jmp [0x80040004]
;----------------------------------
read_hard_disk_0:
;.......
;.......省略不写
ret
;-------------------------------
pgdt dw 0  ;界限
dd 0x00008000 ;GDT物理地址
;------------------------------
times 510-($-$$) db 0
db 0x55,0xaa

    内核主体代码:

;常量部分
core_code_seg_sel equ 0x08
core_data_seg_sel equ 0x10
idt_linear_address equ 0x8001f000
;-------------------------------------
;定义宏 宏是编译预处理 文本替换
%macro alloc_core_linear 0;内核中分配虚拟/线性空间
    mov ebx,[core_tcb+0x06]
    add dword [core_tcb+0x06],0x1000;一次分配4KB
    call core_code_seg_sel:alloc_core_linear
%endmacro    

%macro alloc_user_linear 0;esi指向tcb基地址 用户任务中分配虚拟/线性空间
    mov ebx,[esi+0x06]
    add dword [esi+0x06],0x1000;一次分配4KB
    call core_code_seg_sel:alloc_core_linear
%endmacro   

[bits 32]
;==============================
SECTION core vstart=0x80040000
;指定线性偏移 这样标号就是正确的 但是访问物理地址是仍然
;要注意用高2GB的映射
;注意 
;用SECTION 将程序分段,不代表一定要在GDT中分段来访问
;可以仍然用0-4GB的data code 段描述符访问

put_string:
;....
;....省略
retf;仍然用的是段间返回 因为调用门要使用

put_char:
;......
;......省略
ret;段间

read_hard_disk_0:
;.....
;.....省略
retf

put_hex_dword:
;......
;......省略
retf

set_up_gdt_descriptor:
;....
;....省略
retf

make_seg_descriptor:
;....
;....省略
retf

make_gate_descriptor:
;...
;...省略
retf

allocate_a_4k_page:;分配一个物理页 返回参数 eax
push ebx
push ecx
push edx

xor eax,eax
.b1:
bts [page_bit_map],eax
jnc .b2;如果是空闲 cf置0
inc eax
cmp eax,page_map_len*8
jl .b1

;至此 所有页全部找完 还没找到空闲,停机
mov ebx,message_3
call core_code_seg_sel:put_string
hlt

.b2:
shl eax,12;找到的页的物理地址
pop edx
pop ecx
pop ebx
ret

alloc_inst_a_page:;ebx 待分配的线性地址
push eax
push ebx
push esi
;检查pdt对应项是否存在(页表是否存在)
mov esi,ebx
and esi,0xffc00000;提取高10位
shr esi,20 ;右移22位 左移两位*4 作为页内偏移
or esi,0xfffff000

test dword [esi],0x00000001 ;检查P位是否为1 是否存在
jnz .b1 ;存在

;不存在 创建页表
call allocate_a_4k_page
or eax,0x00000007
mov [esi],eax;登记在pdt对应项

.b1:
;访问该页表对应项
mov esi,ebx
shr esi,10 
and esi,0x003ff000;拿到中间10位
or esi,0xffc00000;得到对应页表的线性地址

;得到该线性地址(传入的ebx)在页表内的对应条目
and ebx,0x003ff000
shr ebx,10;右移12位 左移2位*4 作为页内偏移
or esi,ebx ;至此 esi即是ebx对应的页表项的线性地址

call allocate_a_4k_page;分配一个物理页
or eax,0x00000007
mov [esi],eax

pop esi
pop ebx
pop eax
retf

create_copy_cur_pdir:;创建一个新的PDT,并复制当前pdt给新pdt
;输出 eax 新pdt的物理地址
push esi
push edi
push ebx
push ecx

call allocate_a_4k_page ;分配一个页用作pdt
mov ebx,eax
or ebx,0x00000007
;当前pdt倒数第二项指向新pdt 不然无法用线性地址访问 新pdt
;更无法复制了
mov [0xfffffff8],ebx

invlpg [0xfffffff8];刷新0xfffffff8对应的pdt 页表缓存

mov esi,0xfffff000;当前pdt线性地址
mov edi,0xffffe000;新pdt线性地址
mov ecx,1024
cld
repe movsd
pop ecx
pop ebx
pop edi
pop esi
retf

general_interrupt_handle:;通用中断处理
push eax
mov al,0x20
out 0xa0,al;向8059a 主片发送中断结束
out 0x20,al;向8059a 从片发送中断结束
pop eax
iretd ;iretd是 nasm 把iret变成恒32位模式处理的指令

general_exception_handle:
mov ebx,excep_msg
call core_code_seg_sel:put_string
hlt;直接停机

rtm_interrupt_handle:
pushad
mov al,0x20
out 0xa0,al
out 0x20,al

mov al,0x0c
out 0x70,al
in al,0x71

mov eax,tcb_chain ;eax头指针
.b0:
mov ebx,[eax]; ebx是指向的tcb的线性基地址 
or ebx,ebx
jz .irtn;如果当前指向是空的,说明链表是空的或者全部空闲
cmp word [ebx+0x04],0xffff
je .b1;如果指向的tcb是忙的
mov eax,ebx;否则的话 沿着链条继续往下
jmp .b0

;将当前为忙的任务移到链尾  ;ebx指向当前任务tcb

;将ebx所在的tcb从链表中断开
;原本是 eax->ebx->ecx
;现在是 eax->ecx  ebx->ecx
.b1:
mov ecx,[ebx];ecx是链表上下一个tcb线性地址
mov [eax],ecx;eax是链表上上一个tcb线性地址


;一直移动 直到eax对应的下一个是空,也就是eax是链表尾部
.b2:
mov edx,[eax]
or edx,edx
jz .b3
mov eax,edx
jmp .b2


;将ebx放在eax的下一级,也就是ebx(忙的任务)成为尾部了
.b3:
mov [eax],ebx
mov dword [ebx],0x00000000;ebx改成空闲

;从链表找第一个空闲任务
mov eax,tcb_chain

.b4:
mov eax,[eax]
or eax,eax
jz .irtn ;eax是尾部,也就是链表扫描完了还是没有空闲的
cmp word [eax+0x04],0x0000;任务是否空闲
jnz .b4

;找到空闲的tcb -》eax了
not word [eax+0x04];设置空闲任务为忙
not word [ebx+0x04];设置忙任务空闲
jmp far [eax+0x14];任务转换

.irtn:
popad
iretd

terminate_current_task:
;终止当前任务 任务调用该例程,仍处于任务的全局空间
mov eax,tcb_chain
.b0:
mov ebx,[eax]
cmp word [ebx+0x04],0xffff
je .b1
mov eax,ebx
jmp .b0

;至此 ebx即使当前任务tcb线性地址

.b1:
mov word [ebx+0x04],0x3333;修改当前任务为退出
.b2:
hlt
jmp .b2;当前任务停机,等待任务管理任务运行时回收这个任务

;----------------------------------------
;数据
pgdt             dw  0             ;用于设置和修改GDT 
                          dd  0

         pidt             dw  0
                          dd  0
                          
         ;任务控制块链
         tcb_chain        dd  0 

         core_tcb   times  32  db 0         ;内核(程序管理器)的TCB

         page_bit_map     db  0xff,0xff,0xff,0xff,0xff,0xff,0x55,0x55
                          db  0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff
                          db  0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff
                          db  0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff
                          db  0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55
                          db  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
                          db  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
                          db  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
         page_map_len     equ $-page_bit_map
                          
         ;符号地址检索表
         salt:
         salt_1           db  '@PrintString'
                     times 256-($-salt_1) db 0
                          dd  put_string
                          dw  flat_4gb_code_seg_sel

         salt_2           db  '@ReadDiskData'
                     times 256-($-salt_2) db 0
                          dd  read_hard_disk_0
                          dw  flat_4gb_code_seg_sel

         salt_3           db  '@PrintDwordAsHexString'
                     times 256-($-salt_3) db 0
                          dd  put_hex_dword
                          dw  flat_4gb_code_seg_sel

         salt_4           db  '@TerminateProgram'
                     times 256-($-salt_4) db 0
                          dd  terminate_current_task
                          dw  flat_4gb_code_seg_sel

         salt_item_len   equ $-salt_4
         salt_items      equ ($-salt)/salt_item_len

         excep_msg        db  '********Exception encounted********',0

         message_0        db  '  Working in system core with protection '
                          db  'and paging are all enabled.System core is mapped '
                          db  'to address 0x80000000.',0x0d,0x0a,0

         message_1        db  '  System wide CALL-GATE mounted.',0x0d,0x0a,0
         
         message_3        db  '********No more pages********',0
         
         core_msg0        db  '  System core task running!',0x0d,0x0a,0
         
         bin_hex          db '0123456789ABCDEF'
                                            ;put_hex_dword子过程用的查找表 

         core_buf   times 512 db 0          ;内核用的缓冲区
;--------------------------
fill_descriptor_in_ldt:
;....
;....省略
ret     
load_relocate_program:
;加载并重定位用户程序
;push 逻辑扇区 push 任务tcb基地址
pushad
mov ebp,esp 

;清除pdt的前半部分 给用户任务
mov ebx,0xfffff000
xor esi,esi
.b1:
mov dword [ebx+esi*4],0x00000000
inc esi
cmp esi,512
jl .b1  

;刷新全部pdt 页表缓存
mov eax,cr3
mov cr3,eax

;以下分配内存加载用户程序
mov eax,[ebp+40];扇区号
mov ebx,core_buf
call core_code_seg_sel:read_hard_disk_0

;判断程序大小
mov eax,[core_buf]
mov ebx,eax
and ebx,0xfffff000
add ebx,0x1000
test eax,0x00000fff
cmovnz eax,ebx;如果eax不对齐 用对齐后的ebx

mov ecx,eax
shr ecx,12;ecx/4096即页数

mov eax,[ebp+40];扇区
mov esi,[ebp+36];tcb基地址
.b2:
alloc_user_linear;分配内存
push ecx
mov ecx,8;内循环次数
.b3:
call core_code_seg_sel:read_hard_disk_0
inc eax
loop .b3

pop ecx
loop .b2

;在内核地址空间里创建任务的tss
alloc_core_linear;内核空间分配内存
mov [esi+0x14],ebx;在tcb中填写任务的tss地址
mov word [esi+0x12],103;tss界限

;在任务的局部空间创建ldt
alloc_user_linear
mov [esi+0x0c],ebx;tcb里填写ldt线性地址

;建立代码段描述符
;0x00000000-0xffffffff
;dpl 3 代码段
mov eax,0x00000000
mov ebx,0x000fffff
mov ecx,0x00c0f800 
call core_code_seg_sel:make_seg_descriptor
mov ebx,esi
call fill_descriptor_in_ldt
or cx,0000_0000_0000_0011B ;设置rpl

mov ebx,[esi+0x14];获取tss基地址
mov [ebx+76],cx;填写tss的cs域名

;建立数据段
mov eax,0x00000000
mov ebx,0x000fffff                 
mov ecx,0x00c0f200                 ;4KB粒度的数据段描述符,特权级3
call flat_4gb_code_seg_sel:make_seg_descriptor
mov ebx,esi                        ;TCB的基地址
call fill_descriptor_in_ldt
or cx,0000_0000_0000_0011B         ;设置选择子的特权级为3

mov ebx,[esi+0x14]                 ;从TCB中获取TSS的线性地址
mov [ebx+84],cx                    ;填写TSS的DS域 
mov [ebx+72],cx                    ;填写TSS的ES域
mov [ebx+88],cx                    ;填写TSS的FS域
mov [ebx+92],cx                    ;填写TSS的GS域

;将数据段作为栈段
alloc_user_linear
mov ebx,[esi+0x14]
mov [ebx+80],cx;填写tss中的ss
mov edx,[esi+0x06];tcb中记载的新的可分配地址,是esp上界
mov [ebx+56],edx;填写esp初始值

;额外的0特权级堆栈
alloc_user_linear
mov eax,0x00000000
mov ebx,0x000fffff
mov ecx,0x00c09200
call core_code_seg_sel:make_seg_descriptor
mov ebx,esi
call fill_descriptor_in_ldt
or cx,0000_0000_0000_0000B;设置rpl0

mov ebx,[esi+0x14]
mov [ebx+8],cx;填写tss中的ss0
mov edx,[esi+0x06]
mov [ebx+4],edx;填写esp0的初始值

;额外的1特权级堆栈
alloc_user_linear
mov eax,0x00000000
mov ebx,0x000fffff
mov ecx,0x00c0b200
call core_code_seg_sel:make_seg_descriptor
mov ebx,esi
call fill_descriptor_in_ldt
or cx,0000_0000_0000_0001B;设置rpl0

mov ebx,[esi+0x14]
mov [ebx+16],cx;填写tss中的ss0
mov edx,[esi+0x06]
mov [ebx+12],edx;填写esp0的初始值

;额外的2特权级堆栈
alloc_user_linear
mov eax,0x00000000
mov ebx,0x000fffff
mov ecx,0x00c0d200
call core_code_seg_sel:make_seg_descriptor
mov ebx,esi
call fill_descriptor_in_ldt
or cx,0000_0000_0000_0010B;设置rpl0

mov ebx,[esi+0x14]
mov [ebx+24],cx;填写tss中的ss0
mov edx,[esi+0x06]
mov [ebx+20],edx;填写esp0的初始值

cld
;重定位salt
mov ecx,[0x0c];u_salt条目数
mov edi,[0x08];u_salt的汇编地址,由于任务加载到了0x000,实际就是线性地址

.b4:
push ecx
push edi

mov ecx,salt_items;内循环次数
mov esi,salt; ds:esi 和 es:edi的比较

.b5:
push edi
push esi
push ecx

mov ecx,64
repe cmpsd
jnz .b6;不相等
mov eax,[esi];相等时 esi刚好指向 c_salt对应项的偏移地址
mov [edi-256],eax;edi-256才回到u_salt该项的开头
mov ax,[esi+4]
or ax,0000_0000_0000_0011B;调用门选择子,rpl设置为3
mov [edi-252],ax;回填调用门选择子

.b6:
pop ecx
pop esi
add esi,salt_item_len
pop edi                            ;从头比较 
loop .b5

pop edi
add edi,256
pop ecx
loop .b4

;在GDT中登记LDT描述符
mov esi,[ebp+36]                   ;从堆栈中取得TCB的基地址
mov eax,[esi+0x0c]                 ;LDT的起始线性地址
movzx ebx,word [esi+0x0a]          ;LDT段界限
mov ecx,0x00408200                 ;LDT描述符,特权级0
call flat_4gb_code_seg_sel:make_seg_descriptor
call flat_4gb_code_seg_sel:set_up_gdt_descriptor
mov [esi+0x10],cx                  ;登记LDT选择子到TCB中

mov ebx,[esi+0x14]                 ;从TCB中获取TSS的线性地址
mov [ebx+96],cx                    ;填写TSS的LDT域 

mov word [ebx+0],0                 ;反向链=0

mov dx,[esi+0x12]                  ;段长度(界限)
mov [ebx+102],dx                   ;填写TSS的I/O位图偏移域 

mov word [ebx+100],0               ;T=0

mov eax,[0x04]                     ;从任务的4GB地址空间获取入口点 
mov [ebx+32],eax                   ;填写TSS的EIP域 

pushfd
pop edx
mov [ebx+36],edx                   ;填写TSS的EFLAGS域 

;在GDT中登记TSS描述符
mov eax,[esi+0x14]                 ;从TCB中获取TSS的起始线性地址
movzx ebx,word [esi+0x12]          ;段长度(界限)
mov ecx,0x00408900                 ;TSS描述符,特权级0
call flat_4gb_code_seg_sel:make_seg_descriptor
call flat_4gb_code_seg_sel:set_up_gdt_descriptor
mov [esi+0x18],cx                  ;登记TSS选择子到TCB

;创建用户任务的页目录
;注意!页的分配和使用是由页位图决定的,可以不占用线性地址空间 
call flat_4gb_code_seg_sel:create_copy_cur_pdir
mov ebx,[esi+0x14]                 ;从TCB中获取TSS的线性地址
mov dword [ebx+28],eax             ;填写TSS的CR3(PDBR)域
        
popad

ret 8                              ;丢弃调用本过程前压入的参数 

append_to_tcb_link:
;...
;...省略
ret

start:
;创建中断描述符表idt
;idt不用分配内存了,因为定义在低1MB以内 在pdt中已经映射好了
mov eax,general_exception_handle
mov bx,core_code_seg_sel
mov cx,0x8e00;32位中断门,0特权级
call core_code_seg_sel:make_gate_descriptor
mov ebx,idt_linear_address
xor esi,esi

.idt0:
mov [ebx+esi*8],eax
mov [ebx+esi*8+4],edx
inc esi
cmp esi,19
jle .idt0;安装前20个异常中断处理程序

mov eax,general_interrupt_handle
mov bx,core_code_seg_sel
mov cx,0x8e00
call core_code_seg_sel:make_gate_descriptor
mov ebx,idt_linear_address

.idt1:
mov [ebx+esi*8],eax
mov [ebx+esi*8+4],edx
inc esi
cmp esi,255
jle .idt1;安装普通硬件中断

;覆盖0x70时钟中断
mov eax,rtm_interrupt_handle
mov bx,core_code_seg_sel
mov cx,0x8e00
call core_code_seg_sel:make_gate_descriptor

mov ebx,idt_linear_address
mov [ebx+0x70*8],eax
mov [ebx+0x70*8+4],edx

;开放中断
mov word [pidt],256*8-1
mov dword [pidt+2],idt_linear_address
lidt [pidt]

;设置8259A中断控制器,下面这些端口读写不用记住
mov al,0x11
out 0x20,al                        ;ICW1:边沿触发/级联方式
mov al,0x20
out 0x21,al                        ;ICW2:起始中断向量
mov al,0x04
out 0x21,al                        ;ICW3:从片级联到IR2
mov al,0x01
out 0x21,al                        ;ICW4:非总线缓冲,全嵌套,正常EOI

mov al,0x11
out 0xa0,al                        ;ICW1:边沿触发/级联方式
mov al,0x70
out 0xa1,al                        ;ICW2:起始中断向量
mov al,0x04
out 0xa1,al                        ;ICW3:从片级联到IR2
mov al,0x01
out 0xa1,al                        ;ICW4:非总线缓冲,全嵌套,正常EOI

;设置和时钟中断相关的硬件 
mov al,0x0b                        ;RTC寄存器B
or al,0x80                         ;阻断NMI
out 0x70,al
mov al,0x12                        ;设置寄存器B,禁止周期性中断,开放更
out 0x71,al                        ;新结束后中断,BCD码,24小时制

in al,0xa1                         ;读8259从片的IMR寄存器
and al,0xfe                        ;清除bit 0(此位连接RTC)
out 0xa1,al                        ;写回此寄存器

mov al,0x0c
out 0x70,al
in al,0x71                         ;读RTC寄存器C,复位未决的中断状态

sti                                ;开放硬件中断

mov ebx,message_0
call core_code_seg_sel:put_string

;显示处理器信息 

;省略了
;以下开始安装为整个系统服务的调用门。特权级之间的控制转移必须使用门
mov edi,salt                       ;C-SALT表的起始位置 
mov ecx,salt_items                 ;C-SALT表的条目数量 
.b4:
push ecx   
mov eax,[edi+256]                  ;该条目入口点的32位偏移地址 
mov bx,[edi+260]                   ;该条目入口点的段选择子 
mov cx,1_11_0_1100_000_00000B      ;特权级3的调用门(3以上的特权级才
                                ;允许访问),0个参数(因为用寄存器
                                ;传递参数,而没有用栈) 
call flat_4gb_code_seg_sel:make_gate_descriptor
call flat_4gb_code_seg_sel:set_up_gdt_descriptor
mov [edi+260],cx                   ;将返回的门描述符选择子回填
add edi,salt_item_len              ;指向下一个C-SALT条目 
pop ecx
loop .b4

;对门进行测试 
mov ebx,message_1
call far [salt_1+256]              ;通过门显示信息(偏移量将被忽略) 

;初始化创建程序管理器任务的任务控制块TCB
mov word [core_tcb+0x04],0xffff    ;任务状态:忙碌
mov dword [core_tcb+0x06],0x80100000    
                                ;内核虚拟空间的分配从这里开始。
mov word [core_tcb+0x0a],0xffff    ;登记LDT初始的界限到TCB中(未使用)
mov ecx,core_tcb
call append_to_tcb_link            ;将此TCB添加到TCB链中

;为程序管理器的TSS分配内存空间
alloc_core_linear                  ;宏:在内核的虚拟地址空间分配内存

;在程序管理器的TSS中设置必要的项目 
mov word [ebx+0],0                 ;反向链=0
mov eax,cr3
mov dword [ebx+28],eax             ;登记CR3(PDBR)
mov word [ebx+96],0                ;没有LDT。处理器允许没有LDT的任务。
mov word [ebx+100],0               ;T=0
mov word [ebx+102],103             ;没有I/O位图。0特权级事实上不需要。

;创建程序管理器的TSS描述符,并安装到GDT中 
mov eax,ebx                        ;TSS的起始线性地址
mov ebx,103                        ;段长度(界限)
mov ecx,0x00408900                 ;TSS描述符,特权级0
call flat_4gb_code_seg_sel:make_seg_descriptor
call flat_4gb_code_seg_sel:set_up_gdt_descriptor
mov [core_tcb+0x18],cx             ;登记内核任务的TSS选择子到其TCB

;任务寄存器TR中的内容是任务存在的标志,该内容也决定了当前任务是谁。
;下面的指令为当前正在执行的0特权级任务“程序管理器”后补手续(TSS)。
ltr cx

;现在可认为“程序管理器”任务正执行中

;创建用户任务的任务控制块 
alloc_core_linear                  ;宏:在内核的虚拟地址空间分配内存

mov word [ebx+0x04],0              ;任务状态:空闲 
mov dword [ebx+0x06],0             ;用户任务局部空间的分配从0开始。
mov word [ebx+0x0a],0xffff         ;登记LDT初始的界限到TCB中

push dword 50                      ;用户程序位于逻辑50扇区
push ebx                           ;压入任务控制块起始线性地址 
call load_relocate_program
mov ecx,ebx         
call append_to_tcb_link            ;将此TCB添加到TCB链中

;创建用户任务的任务控制块
alloc_core_linear                  ;宏:在内核的虚拟地址空间分配内存

mov word [ebx+0x04],0              ;任务状态:空闲
mov dword [ebx+0x06],0             ;用户任务局部空间的分配从0开始。
mov word [ebx+0x0a],0xffff         ;登记LDT初始的界限到TCB中

push dword 100                     ;用户程序位于逻辑100扇区
push ebx                           ;压入任务控制块起始线性地址
call load_relocate_program
mov ecx,ebx
call append_to_tcb_link            ;将此TCB添加到TCB链中

.core:
mov ebx,core_msg0
call flat_4gb_code_seg_sel:put_string

;这里可以编写回收已终止任务内存的代码

jmp .core

core_code_end:
;----------------------
SECTION core_trail
core_end:;另起一段,不然code段声明了vstart=0 会让
;core_end不能正确表达内核全长

0x01 代码讲解

     本章内容比较简单,相较于前几章,没有太多知识内容,无非是保护模式下的idt,在idt里安装陷阱门中断门或者任务门,用中断号 * 8去idt里拿描述符,idtr和gdtr一样 都是 低16位界限+高32位基地址,另外idt里0#可用和ldt一样。

    切换任务通过rtc定时中断实现,在中断里对tcb链表进行处理,实现切换,用的是jmp far tss 选择子,而terminate_current_task不再是jmp far /iret(d) 而是将当前任务hlt 并在tcb里记录,等待切换到pgrman(program manage)后由其负责处理tcb链将其从tcb链移除

    值得一提的是,本章所有代码都用了平坦模型,实现的主要方法是在mbr就开启分页,并同时将0x80000000和0x00000000开始的1MB线性地址空间映射到物理地址的低1MB空间,然后刷新esp eip,gdt里的描述符 由于是平坦模型 都是0-4gb不用刷新。

    这样一来 既可以看做将内核主体加载到了0x80040000的线性地址空间,所以内核主体的SECTION 设置了vstart=0x80040000,这样标号就会对应正确的线性地址,而对于put_string访问显存使用绝对物理地址的,也要使用高2gb的线性地址映射过去。

0x02 尾声

    关于中断,我的掌握并不是很扎实,但是碍于时间,很遗憾,本章的笔记远不如前几章那么透彻丰富。《x86汇编:从实模式到保护模式》的笔记就更新到这里了,从明天开始,将进入CSAPP(Computer System: A Programmer’s Perspective)的学习。

路漫漫其修远兮,吾将上下而求索!