【终章】从实模式到保护模式笔记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)的学习。
路漫漫其修远兮,吾将上下而求索!