15章其实应该是和14章相辅相成的(感觉应该是作者觉得14章内容太多了然后切出来了一点)。任务切换和14章的某些概念是分不开的。
★PART1:任务门与任务切换的方法
1. 任务管理程序
14章的时候我们说过,一个程序他可以有很多个任务,特权级指的是任务的不同部分的特权级,一个任务可以有两个空间,一个全局空间,一个局部空间。在一个任务内,全局空间和局部空间具有不同的特权级别,使用门,可以在任务内将控制从3特权级的局部空间转移到0特权级的全局空间,以使用内核或者操作系统提供的服务。
任务切换时以任务为单位的,是指离开一个任务,转到另一个任务中去执行。任务转移相对复杂得多,执行任务时,系统必须至少有两个任务,而且已经有一个正在执行,为了实现任务在全局空间和局部空间可以很快速地切换,系统使用任务管理器机制来管理系统(任务管理器随内核加载而加载,处于0特权级)。任务管理程序以一个任务而独立存在,以完成一些管理和控制功能(相当于一个中继)。
任务管理程序可以没有自己的LDT(处理器允许一个程序没有LDT),但是他必须要有TSS来进行任务切换。下面就演示一段来设置任务管理器的程序(把内核看成任务管理器)。
经过上面一番设置以后,就可以认为任务管理器已经加载了。
2. 任务切换的方法
Intel处理器为任务切换提供了很多方法,以灵活地在各个任务实施切换。
第一个方法就是借助中断来进行任务切换,这是现代抢占式多任务的基础。在实模式下,内存最低端1KB是中断向量表,保存着256个中断处理过程的段地址和偏移地址。在保护模式下,处理器不再使用中断向量表,而是使用中断描述符表。中段描述符表和GDT,LDT是一样的,用于保存描述符,唯一不同的地方是,他保存的是门描述符,包括中断门,陷阱门和任务门。中断门和陷阱门允许在任务内实施中断处理,转到全局空间去执行一些系统级的管理工作,本质上,也是任务内的控制转移行为。但是,在中断发生的时候,如果该中断号对应的门是任务门,那么必须进行任务切换(中断当前任务的执行,保护当前任务的现场,并转移到另一个任务去执行)。
任务门的样子如上,任务门也是一个系统段,所以S一定是0,TYPE位是0101表明是任务门。任务门的P位来表示这个门是否有效,当P=0时候,不允许通过此门来进行任务切换,DPL是任务门描述符的特权级,但是对因为中断而发起的任务切换不起作用,处理器不按特权级施加任何保护。
当中断发生的时候,处理器用中断号乘以8作为索引访问中段描述符表,当这个门是任务门时,则处理器会取出新任务的TSS选择子,然后用TSS的选择子访问GDT,取出TSS描述符。然后,处理器访问新的TSS,从中恢复在TSS登记的所有东西。然后,TR开始指向新任务的TSS,一旦新任务开始执行,则处理器固件会自动将TSS描述符的B位设置为1(表示在忙)。
当中断发生的时候,可以执行常规的中断处理过程。也可以进行任务切换。这两者都使用iret指令返回。前者是返回到同一个任务的不同代码段,后者是返回到被中断的任务。处理器通过EFLAGS位来区分这两者的区别。
如图,EFLAGS的14位就是NT位(Nested Task Flag),这个位指示的是任务是否嵌套。本质上,因为现在中断的描述符全部都是门,所以都是可以根据NT位来确定如何进行返回的。如果一个任务的切换是因为中断发起的,发生任务切换以后,旧任务的TSS描述符的B位不会改变,处理器将现场的TSS的所有需要填写的项都填写好,然后把旧任务的TSS描述符选择子写入新任务的任务连接域中(0x00)处,同时将新任务EFLAGS寄存器的NT位置变为1,以允许新任务返回旧任务的操作,同时还要把新任务的TSS描述符的B位设置为1(忙)。
回到iret指令上来,当处理器看见iret指令,则会立马检查NT位,如果这个位是0,那么就按一般的中断返回操作进行;如果这个位是1,那么就会返回原先被中断的任务的位置继续进行旧任务,同时把NT位复原,并把切换前的任务的TSS描述符的B位设置为0。然后TR指向原来被切换的任务的TSS,进行任务切换。
事实上,处理器除了可以用中断进行任务切换,还有其他三种切换任务的方式。处理器切换任务的方式有4种,如下:
- 当前程序,任务或者过程执行控制转移到GDT内某个TSS描述符(使用jmp或者call进行切换)。
- 当前程序,任务或者过程执行控制转移到GDT或者当前LDT内某个任务门描述符(任务门可以在任务的LDT上,TSS一定要在GDT上)(使用jmp或者call进行切换)。
- 一个异常或者中断发生时候,中断号指向中断描述表内的任务门。
- 在EFLAGS寄存器的NT位置为的情况下,当前任务执行一个iret指令。
由call指令发起的任务切换类似于因为中断发起的任务切换,也就是说,call指令发起的任务切换是嵌套的,当前任务(旧任务)TSS描述符的B位保持原来的“1”不变。EFLAGS寄存器的NT位也不会发生变化。新任务的TSS描述符的B位置1,EFLAGS的NT位也置1,表示此任务嵌套于其他任务中,同时,TSS任务连接域的内容改为旧任务的TSS描述符选择子。
和call指令不同,使用jmp指令发起的任务切换,不会形成任务的嵌套关系。执行任务切换的时候,当前任务(旧任务)的TSS描述符清零,变为非忙状态,EFLAGS寄存器的NT位不变;新任务的TSS描述符的B位置1,进入忙的状态,EFLAGS寄存器的NT位保持从TSS中加载时的状态度不变。
不管如何,任务是不可以重入的,本质上来说,就是发起任务切换的时候,新任务的TSS描述符的B位不能是1,处理器每次切换任务都会检查TSS描述符的B位。
在进行任务切换的时候,处理器执行以下操作:
- 使用JMP或者CALL指令的操作数(注意任务门的选择子问题,和调用门一样,进行远调用的时候任务门会忽略32位的偏移地址,直接用16位的选择子部分),任务门或者当前任务的TSS任务连接域取得新任务的TSS描述符选择子。
- 检查是否允许从当前任务切换到新任务,数据访问的特权级检查适用于jmp和call指令,当前(旧)任务的CPL和新任务段的RPL必须在数值上小于或者等于目标TSS或者任务门的DPL。异常,中断(除了由int n指令发起的中断)和iret指令引起的任务切换忽略任务门或者TSS描述符的DPL,对于以int n发起的任务切换,要检查DPL。
- 检查新任务的TSS描述符是否被标记为有效(P=1),而且界限要大于103,同时检查任务的B位,除了iret指令发起的切换,其他切换B位要等于0(防止任务重入),iret指令发起的切换B=1。
- 检查当前任务(旧任务)和新任务的TSS,以及所有在任务切换时要用到的段描述符已经安排到系统内存中。
- 如果任务切换是由jmp或者iret发起的,处理器清除当前(旧)任务的B位,如果是由call,异常或者中断发起的,那么B位就维持原来的状态。
- 如果任务切换是由iret发起的,那么处理器建立EFLAGS寄存器的副本并且把NT位清除,如果是其他三种方式发起的切换,那么副本中的NT位保持不变。
- 保存当前任务状态到他的TSS中(TSS所需要的所有副本)。
- 如果任务切换是由call,异常或者中断发起的,则处理器从新任务加载的EFLAGS寄存器的NT位置1;如果是由iret或者jmp发起的,则NT位保持不变。
- 如果任务是由call,jmp,异常或者中断发起的,则处理器将新任务的TSS描述符的B位置1;如果由iret发起,则B位保持不变。
- 用新任务的TSS选择子和TSS描述符加载到任务寄存器TR(注意描述符的基地址,段界限和属性加载到TR的高速缓存器中了)。
- 新任务的TSS状态数据被加载到处理器(TSS里面有的都要加载)。载入期间只要有一个发生故障,架构状态会被破坏(无法撤销)。所谓架构状态就是从一个状态转移到另一个状态是一确定的,不会出现不可预知的状况。
- 段选择子对应的描述符也会被加载,与加载和验证新环境有关的任务错误都会破坏架构状态,如果前面1-10步出现了不可恢复的错误,处理器将不能完成任务切换,并且确保处理器返回到执行发起任务切换的那条指令前的状态,如果在11步出现了不可恢复的错误,架构状态将会被破坏。如果在12步(提交点)以后发生了不可恢复错误,处理器将会完成任务切换并且在开始执行新任务之前产生一个相应的异常。
- 开始执行新任务。任务切换的时候,新任务的特权级别不是从那个被挂起的任务继承来的。新任务的特权级别都是由其段寄存器CS的低2位决定的,而该寄存器的内容取自新任务的TSS。因为每个任务都有自己的独立地址空间和任务状态段TSS,所以任务之间是彼此隔离的。只需要用特权级规则控制对TSS的访问就行,软件不需要在任务切换时进行显式的特权级检查。
★PART2:本章代码
1. 书上原来配套代码
注意书上返回的时候用的是iretd,这个指令功能其实是和iret是一样的,只是他是编译器提供的,和pushfd,popfd一样,只是在16位模式下往iret前面加前缀而已。iret指令的机器码是CF,在16位模式下,iret是没有指令前缀0x66的,ireted有0x66;在32位保护模式下,iret和iretd指令的机器码都是CF。
不过书上的任务返回的时候,为了说清楚call和jmp指令的返回差别,他用一个调用门强行把任务转移回内核中,事实上这样的话就任务切换机制就被破坏了,也就是不能再用TSS再加载到上一个任务的跳转位置再进行指令。(可以跑回调用门的最后,但是这样就没有意义了)。
;代码清单15-1
;文件名:c15_core.asm
;文件说明:保护模式微型核心程序
;创建日期:2011-11-19 21:40 ;以下常量定义部分。内核的大部分内容都应当固定
core_code_seg_sel equ 0x38 ;内核代码段选择子
core_data_seg_sel equ 0x30 ;内核数据段选择子
sys_routine_seg_sel equ 0x28 ;系统公共例程代码段的选择子
video_ram_seg_sel equ 0x20 ;视频显示缓冲区的段选择子
core_stack_seg_sel equ 0x18 ;内核堆栈段选择子
mem_0_4_gb_seg_sel equ 0x08 ;整个0-4GB内存的段的选择子 ;-------------------------------------------------------------------------------
;以下是系统核心的头部,用于加载核心程序
core_length dd core_end ;核心程序总长度#00 sys_routine_seg dd section.sys_routine.start
;系统公用例程段位置#04 core_data_seg dd section.core_data.start
;核心数据段位置#08 core_code_seg dd section.core_code.start
;核心代码段位置#0c core_entry dd start ;核心代码段入口点#10
dw core_code_seg_sel ;===============================================================================
[bits ]
;===============================================================================
SECTION sys_routine vstart= ;系统公共例程代码段
;-------------------------------------------------------------------------------
;字符串显示例程
put_string: ;显示0终止的字符串并移动光标
;输入:DS:EBX=串地址
push ecx
.getc:
mov cl,[ebx]
or cl,cl
jz .exit
call put_char
inc ebx
jmp .getc .exit:
pop ecx
retf ;段间返回 ;-------------------------------------------------------------------------------
put_char: ;在当前光标处显示一个字符,并推进
;光标。仅用于段内调用
;输入:CL=字符ASCII码
pushad ;以下取当前光标位置
mov dx,0x3d4
mov al,0x0e
out dx,al
inc dx ;0x3d5
in al,dx ;高字
mov ah,al dec dx ;0x3d4
mov al,0x0f
out dx,al
inc dx ;0x3d5
in al,dx ;低字
mov bx,ax ;BX=代表光标位置的16位数 cmp cl,0x0d ;回车符?
jnz .put_0a
mov ax,bx
mov bl,
div bl
mul bl
mov bx,ax
jmp .set_cursor .put_0a:
cmp cl,0x0a ;换行符?
jnz .put_other
add bx,
jmp .roll_screen .put_other: ;正常显示字符
push es
mov eax,video_ram_seg_sel ;0xb8000段的选择子
mov es,eax
shl bx,
mov [es:bx],cl
pop es ;以下将光标位置推进一个字符
shr bx,
inc bx .roll_screen:
cmp bx, ;光标超出屏幕?滚屏
jl .set_cursor push ds
push es
mov eax,video_ram_seg_sel
mov ds,eax
mov es,eax
cld
mov esi,0xa0 ;小心!32位模式下movsb/w/d
mov edi,0x00 ;使用的是esi/edi/ecx
mov ecx,
rep movsd
mov bx, ;清除屏幕最底一行
mov ecx, ;32位程序应该使用ECX
.cls:
mov word[es:bx],0x0720
add bx,
loop .cls pop es
pop ds mov bx, .set_cursor:
mov dx,0x3d4
mov al,0x0e
out dx,al
inc dx ;0x3d5
mov al,bh
out dx,al
dec dx ;0x3d4
mov al,0x0f
out dx,al
inc dx ;0x3d5
mov al,bl
out dx,al popad ret ;-------------------------------------------------------------------------------
read_hard_disk_0: ;从硬盘读取一个逻辑扇区
;EAX=逻辑扇区号
;DS:EBX=目标缓冲区地址
;返回:EBX=EBX+512
push eax
push ecx
push edx push eax mov dx,0x1f2
mov al,
out dx,al ;读取的扇区数 inc dx ;0x1f3
pop eax
out dx,al ;LBA地址7~0 inc dx ;0x1f4
mov cl,
shr eax,cl
out dx,al ;LBA地址15~8 inc dx ;0x1f5
shr eax,cl
out dx,al ;LBA地址23~16 inc dx ;0x1f6
shr eax,cl
or al,0xe0 ;第一硬盘 LBA地址27~24
out dx,al inc dx ;0x1f7
mov al,0x20 ;读命令
out dx,al .waits:
in al,dx
and al,0x88
cmp al,0x08
jnz .waits ;不忙,且硬盘已准备好数据传输 mov ecx, ;总共要读取的字数
mov dx,0x1f0
.readw:
in ax,dx
mov [ebx],ax
add ebx,
loop .readw pop edx
pop ecx
pop eax retf ;段间返回 ;-------------------------------------------------------------------------------
;汇编语言程序是极难一次成功,而且调试非常困难。这个例程可以提供帮助
put_hex_dword: ;在当前光标处以十六进制形式显示
;一个双字并推进光标
;输入:EDX=要转换并显示的数字
;输出:无
pushad
push ds mov ax,core_data_seg_sel ;切换到核心数据段
mov ds,ax mov ebx,bin_hex ;指向核心数据段内的转换表
mov ecx,
.xlt:
rol edx,
mov eax,edx
and eax,0x0000000f
xlat push ecx
mov cl,al
call put_char
pop ecx loop .xlt pop ds
popad
retf ;-------------------------------------------------------------------------------
allocate_memory: ;分配内存
;输入:ECX=希望分配的字节数
;输出:ECX=起始线性地址
push ds
push eax
push ebx mov eax,core_data_seg_sel
mov ds,eax mov eax,[ram_alloc]
add eax,ecx ;下一次分配时的起始地址 ;这里应当有检测可用内存数量的指令 mov ecx,[ram_alloc] ;返回分配的起始地址 mov ebx,eax
and ebx,0xfffffffc
add ebx, ;强制对齐
test eax,0x00000003 ;下次分配的起始地址最好是4字节对齐
cmovnz eax,ebx ;如果没有对齐,则强制对齐
mov [ram_alloc],eax ;下次从该地址分配内存
;cmovcc指令可以避免控制转移
pop ebx
pop eax
pop ds retf ;-------------------------------------------------------------------------------
set_up_gdt_descriptor: ;在GDT内安装一个新的描述符
;输入:EDX:EAX=描述符
;输出:CX=描述符的选择子
push eax
push ebx
push edx push ds
push es mov ebx,core_data_seg_sel ;切换到核心数据段
mov ds,ebx sgdt [pgdt] ;以便开始处理GDT mov ebx,mem_0_4_gb_seg_sel
mov es,ebx movzx ebx,word [pgdt] ;GDT界限
inc bx ;GDT总字节数,也是下一个描述符偏移
add ebx,[pgdt+] ;下一个描述符的线性地址 mov [es:ebx],eax
mov [es:ebx+],edx add word [pgdt], ;增加一个描述符的大小 lgdt [pgdt] ;对GDT的更改生效 mov ax,[pgdt] ;得到GDT界限值
xor dx,dx
mov bx,
div bx ;除以8,去掉余数
mov cx,ax
shl cx, ;将索引号移到正确位置 pop es
pop ds pop edx
pop ebx
pop eax retf
;-------------------------------------------------------------------------------
make_seg_descriptor: ;构造存储器和系统的段描述符
;输入:EAX=线性基地址
; EBX=段界限
; ECX=属性。各属性位都在原始
; 位置,无关的位清零
;返回:EDX:EAX=描述符
mov edx,eax
shl eax,
or ax,bx ;描述符前32位(EAX)构造完毕 and edx,0xffff0000 ;清除基地址中无关的位
rol edx,
bswap edx ;装配基址的31~24和23~16 (80486+) xor bx,bx
or edx,ebx ;装配段界限的高4位 or edx,ecx ;装配属性 retf ;-------------------------------------------------------------------------------
make_gate_descriptor: ;构造门的描述符(调用门等)
;输入:EAX=门代码在段内偏移地址
; BX=门代码所在段的选择子
; CX=段类型及属性等(各属
; 性位都在原始位置)
;返回:EDX:EAX=完整的描述符
push ebx
push ecx mov edx,eax
and edx,0xffff0000 ;得到偏移地址高16位
or dx,cx ;组装属性部分到EDX and eax,0x0000ffff ;得到偏移地址低16位
shl ebx,
or eax,ebx ;组装段选择子部分 pop ecx
pop ebx retf ;-------------------------------------------------------------------------------
terminate_current_task: ;终止当前任务
;注意,执行此例程时,当前任务仍在
;运行中。此例程其实也是当前任务的
;一部分
pushfd
mov edx,[esp] ;获得EFLAGS寄存器内容
add esp, ;恢复堆栈指针 mov eax,core_data_seg_sel
mov ds,eax test dx,0100_0000_0000_0000B ;测试NT位
jnz .b1 ;当前任务是嵌套的,到.b1执行iretd
mov ebx,core_msg1 ;当前任务不是嵌套的,直接切换到
call sys_routine_seg_sel:put_string
jmp far [prgman_tss] ;程序管理器任务 .b1:
mov ebx,core_msg0
call sys_routine_seg_sel:put_string
iretd sys_routine_end: ;===============================================================================
SECTION core_data vstart= ;系统核心的数据段
;-------------------------------------------------------------------------------
pgdt dw ;用于设置和修改GDT
dd ram_alloc dd 0x00100000 ;下次分配内存时的起始地址 ;符号地址检索表
salt:
salt_1 db '@PrintString'
times -($-salt_1) db
dd put_string
dw sys_routine_seg_sel salt_2 db '@ReadDiskData'
times -($-salt_2) db
dd read_hard_disk_0
dw sys_routine_seg_sel salt_3 db '@PrintDwordAsHexString'
times -($-salt_3) db
dd put_hex_dword
dw sys_routine_seg_sel salt_4 db '@TerminateProgram'
times -($-salt_4) db
dd terminate_current_task
dw sys_routine_seg_sel salt_item_len equ $-salt_4
salt_items equ ($-salt)/salt_item_len message_1 db ' If you seen this message,that means we '
db 'are now in protect mode,and the system '
db 'core is loaded,and the video display '
db 'routine works perfectly.',0x0d,0x0a, message_2 db ' System wide CALL-GATE mounted.',0x0d,0x0a, bin_hex db '0123456789ABCDEF'
;put_hex_dword子过程用的查找表 core_buf times db ;内核用的缓冲区 cpu_brnd0 db 0x0d,0x0a,' ',
cpu_brand times db
cpu_brnd1 db 0x0d,0x0a,0x0d,0x0a, ;任务控制块链
tcb_chain dd ;程序管理器的任务信息
prgman_tss dd ;程序管理器的TSS基地址
dw ;程序管理器的TSS描述符选择子 prgman_msg1 db 0x0d,0x0a
db '[PROGRAM MANAGER]: Hello! I am Program Manager,'
db 'run at CPL=0.Now,create user task and switch '
db 'to it by the CALL instruction...',0x0d,0x0a, prgman_msg2 db 0x0d,0x0a
db '[PROGRAM MANAGER]: I am glad to regain control.'
db 'Now,create another user task and switch to '
db 'it by the JMP instruction...',0x0d,0x0a, prgman_msg3 db 0x0d,0x0a
db '[PROGRAM MANAGER]: I am gain control again,'
db 'HALT...', core_msg0 db 0x0d,0x0a
db '[SYSTEM CORE]: Uh...This task initiated with '
db 'CALL instruction or an exeception/ interrupt,'
db 'should use IRETD instruction to switch back...'
db 0x0d,0x0a, core_msg1 db 0x0d,0x0a
db '[SYSTEM CORE]: Uh...This task initiated with '
db 'JMP instruction, should switch to Program '
db 'Manager directly by the JMP instruction...'
db 0x0d,0x0a, core_data_end: ;===============================================================================
SECTION core_code vstart=
;-------------------------------------------------------------------------------
fill_descriptor_in_ldt: ;在LDT内安装一个新的描述符
;输入:EDX:EAX=描述符
; EBX=TCB基地址
;输出:CX=描述符的选择子
push eax
push edx
push edi
push ds mov ecx,mem_0_4_gb_seg_sel
mov ds,ecx mov edi,[ebx+0x0c] ;获得LDT基地址 xor ecx,ecx
mov cx,[ebx+0x0a] ;获得LDT界限
inc cx ;LDT的总字节数,即新描述符偏移地址 mov [edi+ecx+0x00],eax
mov [edi+ecx+0x04],edx ;安装描述符 add cx,
dec cx ;得到新的LDT界限值 mov [ebx+0x0a],cx ;更新LDT界限值到TCB mov ax,cx
xor dx,dx
mov cx,
div cx mov cx,ax
shl cx, ;左移3位,并且
or cx,0000_0000_0000_0100B ;使TI位=1,指向LDT,最后使RPL=00 pop ds
pop edi
pop edx
pop eax ret ;-------------------------------------------------------------------------------
load_relocate_program: ;加载并重定位用户程序
;输入: PUSH 逻辑扇区号
; PUSH 任务控制块基地址
;输出:无
pushad push ds
push es mov ebp,esp ;为访问通过堆栈传递的参数做准备 mov ecx,mem_0_4_gb_seg_sel
mov es,ecx mov esi,[ebp+*] ;从堆栈中取得TCB的基地址 ;以下申请创建LDT所需要的内存
mov ecx, ;允许安装20个LDT描述符
call sys_routine_seg_sel:allocate_memory
mov [es:esi+0x0c],ecx ;登记LDT基地址到TCB中
mov word [es:esi+0x0a],0xffff ;登记LDT初始的界限到TCB中 ;以下开始加载用户程序
mov eax,core_data_seg_sel
mov ds,eax ;切换DS到内核数据段 mov eax,[ebp+*] ;从堆栈中取出用户程序起始扇区号
mov ebx,core_buf ;读取程序头部数据
call sys_routine_seg_sel:read_hard_disk_0 ;以下判断整个程序有多大
mov eax,[core_buf] ;程序尺寸
mov ebx,eax
and ebx,0xfffffe00 ;使之512字节对齐(能被512整除的数低
add ebx, ;9位都为0
test eax,0x000001ff ;程序的大小正好是512的倍数吗?
cmovnz eax,ebx ;不是。使用凑整的结果 mov ecx,eax ;实际需要申请的内存数量
call sys_routine_seg_sel:allocate_memory
mov [es:esi+0x06],ecx ;登记程序加载基地址到TCB中 mov ebx,ecx ;ebx -> 申请到的内存首地址
xor edx,edx
mov ecx,
div ecx
mov ecx,eax ;总扇区数 mov eax,mem_0_4_gb_seg_sel ;切换DS到0-4GB的段
mov ds,eax mov eax,[ebp+*] ;起始扇区号
.b1:
call sys_routine_seg_sel:read_hard_disk_0
inc eax
loop .b1 ;循环读,直到读完整个用户程序 mov edi,[es:esi+0x06] ;获得程序加载基地址 ;建立程序头部段描述符
mov eax,edi ;程序头部起始线性地址
mov ebx,[edi+0x04] ;段长度
dec ebx ;段界限
mov ecx,0x0040f200 ;字节粒度的数据段描述符,特权级3
call sys_routine_seg_sel:make_seg_descriptor ;安装头部段描述符到LDT中
mov ebx,esi ;TCB的基地址
call fill_descriptor_in_ldt or cx,0000_0000_0000_0011B ;设置选择子的特权级为3
mov [es:esi+0x44],cx ;登记程序头部段选择子到TCB
mov [edi+0x04],cx ;和头部内 ;建立程序代码段描述符
mov eax,edi
add eax,[edi+0x14] ;代码起始线性地址
mov ebx,[edi+0x18] ;段长度
dec ebx ;段界限
mov ecx,0x0040f800 ;字节粒度的代码段描述符,特权级3
call sys_routine_seg_sel:make_seg_descriptor
mov ebx,esi ;TCB的基地址
call fill_descriptor_in_ldt
or cx,0000_0000_0000_0011B ;设置选择子的特权级为3
mov [edi+0x14],cx ;登记代码段选择子到头部 ;建立程序数据段描述符
mov eax,edi
add eax,[edi+0x1c] ;数据段起始线性地址
mov ebx,[edi+0x20] ;段长度
dec ebx ;段界限
mov ecx,0x0040f200 ;字节粒度的数据段描述符,特权级3
call sys_routine_seg_sel:make_seg_descriptor
mov ebx,esi ;TCB的基地址
call fill_descriptor_in_ldt
or cx,0000_0000_0000_0011B ;设置选择子的特权级为3
mov [edi+0x1c],cx ;登记数据段选择子到头部 ;建立程序堆栈段描述符
mov ecx,[edi+0x0c] ;4KB的倍率
mov ebx,0x000fffff
sub ebx,ecx ;得到段界限
mov eax,
mul ecx
mov ecx,eax ;准备为堆栈分配内存
call sys_routine_seg_sel:allocate_memory
add eax,ecx ;得到堆栈的高端物理地址
mov ecx,0x00c0f600 ;字节粒度的堆栈段描述符,特权级3
call sys_routine_seg_sel:make_seg_descriptor
mov ebx,esi ;TCB的基地址
call fill_descriptor_in_ldt
or cx,0000_0000_0000_0011B ;设置选择子的特权级为3
mov [edi+0x08],cx ;登记堆栈段选择子到头部 ;重定位SALT
mov eax,mem_0_4_gb_seg_sel ;这里和前一章不同,头部段描述符
mov es,eax ;已安装,但还没有生效,故只能通
;过4GB段访问用户程序头部
mov eax,core_data_seg_sel
mov ds,eax cld mov ecx,[es:edi+0x24] ;U-SALT条目数(通过访问4GB段取得)
add edi,0x28 ;U-SALT在4GB段内的偏移
.b2:
push ecx
push edi mov ecx,salt_items
mov esi,salt
.b3:
push edi
push esi
push ecx mov ecx, ;检索表中,每条目的比较次数
repe cmpsd ;每次比较4字节
jnz .b4
mov eax,[esi] ;若匹配,则esi恰好指向其后的地址
mov [es:edi-],eax ;将字符串改写成偏移地址
mov ax,[esi+]
or ax,0000000000000011B ;以用户程序自己的特权级使用调用门
;故RPL=3
mov [es:edi-],ax ;回填调用门选择子
.b4: pop ecx
pop esi
add esi,salt_item_len
pop edi ;从头比较
loop .b3 pop edi
add edi,
pop ecx
loop .b2 mov esi,[ebp+*] ;从堆栈中取得TCB的基地址 ;创建0特权级堆栈
mov ecx,
mov eax,ecx ;为生成堆栈高端地址做准备
mov [es:esi+0x1a],ecx
shr dword [es:esi+0x1a], ;登记0特权级堆栈尺寸到TCB
call sys_routine_seg_sel:allocate_memory
add eax,ecx ;堆栈必须使用高端地址为基地址
mov [es:esi+0x1e],eax ;登记0特权级堆栈基地址到TCB
mov ebx,0xffffe ;段长度(界限)
mov ecx,0x00c09600 ;4KB粒度,读写,特权级0
call sys_routine_seg_sel:make_seg_descriptor
mov ebx,esi ;TCB的基地址
call fill_descriptor_in_ldt
;or cx,0000_0000_0000_0000 ;设置选择子的特权级为0
mov [es:esi+0x22],cx ;登记0特权级堆栈选择子到TCB
mov dword [es:esi+0x24], ;登记0特权级堆栈初始ESP到TCB ;创建1特权级堆栈
mov ecx,
mov eax,ecx ;为生成堆栈高端地址做准备
mov [es:esi+0x28],ecx
shr [es:esi+0x28], ;登记1特权级堆栈尺寸到TCB
call sys_routine_seg_sel:allocate_memory
add eax,ecx ;堆栈必须使用高端地址为基地址
mov [es:esi+0x2c],eax ;登记1特权级堆栈基地址到TCB
mov ebx,0xffffe ;段长度(界限)
mov ecx,0x00c0b600 ;4KB粒度,读写,特权级1
call sys_routine_seg_sel:make_seg_descriptor
mov ebx,esi ;TCB的基地址
call fill_descriptor_in_ldt
or cx,0000_0000_0000_0001 ;设置选择子的特权级为1
mov [es:esi+0x30],cx ;登记1特权级堆栈选择子到TCB
mov dword [es:esi+0x32], ;登记1特权级堆栈初始ESP到TCB ;创建2特权级堆栈
mov ecx,
mov eax,ecx ;为生成堆栈高端地址做准备
mov [es:esi+0x36],ecx
shr [es:esi+0x36], ;登记2特权级堆栈尺寸到TCB
call sys_routine_seg_sel:allocate_memory
add eax,ecx ;堆栈必须使用高端地址为基地址
mov [es:esi+0x3a],ecx ;登记2特权级堆栈基地址到TCB
mov ebx,0xffffe ;段长度(界限)
mov ecx,0x00c0d600 ;4KB粒度,读写,特权级2
call sys_routine_seg_sel:make_seg_descriptor
mov ebx,esi ;TCB的基地址
call fill_descriptor_in_ldt
or cx,0000_0000_0000_0010 ;设置选择子的特权级为2
mov [es:esi+0x3e],cx ;登记2特权级堆栈选择子到TCB
mov dword [es:esi+0x40], ;登记2特权级堆栈初始ESP到TCB ;在GDT中登记LDT描述符
mov eax,[es:esi+0x0c] ;LDT的起始线性地址
movzx ebx,word [es:esi+0x0a] ;LDT段界限
mov ecx,0x00408200 ;LDT描述符,特权级0
call sys_routine_seg_sel:make_seg_descriptor
call sys_routine_seg_sel:set_up_gdt_descriptor
mov [es:esi+0x10],cx ;登记LDT选择子到TCB中 ;创建用户程序的TSS
mov ecx, ;tss的基本尺寸
mov [es:esi+0x12],cx
dec word [es:esi+0x12] ;登记TSS界限值到TCB
call sys_routine_seg_sel:allocate_memory
mov [es:esi+0x14],ecx ;登记TSS基地址到TCB ;登记基本的TSS表格内容
mov word [es:ecx+], ;反向链=0 mov edx,[es:esi+0x24] ;登记0特权级堆栈初始ESP
mov [es:ecx+],edx ;到TSS中 mov dx,[es:esi+0x22] ;登记0特权级堆栈段选择子
mov [es:ecx+],dx ;到TSS中 mov edx,[es:esi+0x32] ;登记1特权级堆栈初始ESP
mov [es:ecx+],edx ;到TSS中 mov dx,[es:esi+0x30] ;登记1特权级堆栈段选择子
mov [es:ecx+],dx ;到TSS中 mov edx,[es:esi+0x40] ;登记2特权级堆栈初始ESP
mov [es:ecx+],edx ;到TSS中 mov dx,[es:esi+0x3e] ;登记2特权级堆栈段选择子
mov [es:ecx+],dx ;到TSS中 mov dx,[es:esi+0x10] ;登记任务的LDT选择子
mov [es:ecx+],dx ;到TSS中 mov dx,[es:esi+0x12] ;登记任务的I/O位图偏移
mov [es:ecx+],dx ;到TSS中 mov word [es:ecx+], ;T=0 mov dword [es:ecx+], ;登记CR3(PDBR) ;访问用户程序头部,获取数据填充TSS
mov ebx,[ebp+*] ;从堆栈中取得TCB的基地址
mov edi,[es:ebx+0x06] ;用户程序加载的基地址 mov edx,[es:edi+0x10] ;登记程序入口点(EIP)
mov [es:ecx+],edx ;到TSS mov dx,[es:edi+0x14] ;登记程序代码段(CS)选择子
mov [es:ecx+],dx ;到TSS中 mov dx,[es:edi+0x08] ;登记程序堆栈段(SS)选择子
mov [es:ecx+],dx ;到TSS中 mov dx,[es:edi+0x04] ;登记程序数据段(DS)选择子
mov word [es:ecx+],dx ;到TSS中。注意,它指向程序头部段 mov word [es:ecx+], ;TSS中的ES=0 mov word [es:ecx+], ;TSS中的FS=0 mov word [es:ecx+], ;TSS中的GS=0 pushfd
pop edx mov dword [es:ecx+],edx ;EFLAGS ;在GDT中登记TSS描述符
mov eax,[es:esi+0x14] ;TSS的起始线性地址
movzx ebx,word [es:esi+0x12] ;段长度(界限)
mov ecx,0x00408900 ;TSS描述符,特权级0
call sys_routine_seg_sel:make_seg_descriptor
call sys_routine_seg_sel:set_up_gdt_descriptor
mov [es:esi+0x18],cx ;登记TSS选择子到TCB pop es ;恢复到调用此过程前的es段
pop ds ;恢复到调用此过程前的ds段 popad ret ;丢弃调用本过程前压入的参数 ;-------------------------------------------------------------------------------
append_to_tcb_link: ;在TCB链上追加任务控制块
;输入:ECX=TCB线性基地址
push eax
push edx
push ds
push es mov eax,core_data_seg_sel ;令DS指向内核数据段
mov ds,eax
mov eax,mem_0_4_gb_seg_sel ;令ES指向0..4GB段
mov es,eax mov dword [es: ecx+0x00], ;当前TCB指针域清零,以指示这是最
;后一个TCB mov eax,[tcb_chain] ;TCB表头指针
or eax,eax ;链表为空?
jz .notcb .searc:
mov edx,eax
mov eax,[es: edx+0x00]
or eax,eax
jnz .searc mov [es: edx+0x00],ecx
jmp .retpc .notcb:
mov [tcb_chain],ecx ;若为空表,直接令表头指针指向TCB .retpc:
pop es
pop ds
pop edx
pop eax ret ;-------------------------------------------------------------------------------
start:
mov ecx,core_data_seg_sel ;令DS指向核心数据段
mov ds,ecx mov ecx,mem_0_4_gb_seg_sel ;令ES指向4GB数据段
mov es,ecx mov ebx,message_1
call sys_routine_seg_sel:put_string ;显示处理器品牌信息
mov eax,0x80000002
cpuid
mov [cpu_brand + 0x00],eax
mov [cpu_brand + 0x04],ebx
mov [cpu_brand + 0x08],ecx
mov [cpu_brand + 0x0c],edx mov eax,0x80000003
cpuid
mov [cpu_brand + 0x10],eax
mov [cpu_brand + 0x14],ebx
mov [cpu_brand + 0x18],ecx
mov [cpu_brand + 0x1c],edx mov eax,0x80000004
cpuid
mov [cpu_brand + 0x20],eax
mov [cpu_brand + 0x24],ebx
mov [cpu_brand + 0x28],ecx
mov [cpu_brand + 0x2c],edx mov ebx,cpu_brnd0 ;显示处理器品牌信息
call sys_routine_seg_sel:put_string
mov ebx,cpu_brand
call sys_routine_seg_sel:put_string
mov ebx,cpu_brnd1
call sys_routine_seg_sel:put_string ;以下开始安装为整个系统服务的调用门。特权级之间的控制转移必须使用门
mov edi,salt ;C-SALT表的起始位置
mov ecx,salt_items ;C-SALT表的条目数量
.b3:
push ecx
mov eax,[edi+] ;该条目入口点的32位偏移地址
mov bx,[edi+] ;该条目入口点的段选择子
mov cx,1_11_0_1100_000_00000B ;特权级3的调用门(3以上的特权级才
;允许访问),0个参数(因为用寄存器
;传递参数,而没有用栈)
call sys_routine_seg_sel:make_gate_descriptor
call sys_routine_seg_sel:set_up_gdt_descriptor
mov [edi+],cx ;将返回的门描述符选择子回填
add edi,salt_item_len ;指向下一个C-SALT条目
pop ecx
loop .b3 ;对门进行测试
mov ebx,message_2
call far [salt_1+] ;通过门显示信息(偏移量将被忽略) ;为程序管理器的TSS分配内存空间
mov ecx, ;为该任务的TSS分配内存
call sys_routine_seg_sel:allocate_memory
mov [prgman_tss+0x00],ecx ;保存程序管理器的TSS基地址 ;在程序管理器的TSS中设置必要的项目
mov word [es:ecx+], ;没有LDT。处理器允许没有LDT的任务。
mov word [es:ecx+], ;没有I/O位图。0特权级事实上不需要。
mov word [es:ecx+], ;反向链=0
mov dword [es:ecx+], ;登记CR3(PDBR)
mov word [es:ecx+], ;T=0
;不需要0、1、2特权级堆栈。0特级不
;会向低特权级转移控制。 ;创建TSS描述符,并安装到GDT中
mov eax,ecx ;TSS的起始线性地址
mov ebx, ;段长度(界限)
mov ecx,0x00408900 ;TSS描述符,特权级0
call sys_routine_seg_sel:make_seg_descriptor
call sys_routine_seg_sel:set_up_gdt_descriptor
mov [prgman_tss+0x04],cx ;保存程序管理器的TSS描述符选择子 ;任务寄存器TR中的内容是任务存在的标志,该内容也决定了当前任务是谁。
;下面的指令为当前正在执行的0特权级任务“程序管理器”后补手续(TSS)。
ltr cx ;现在可认为“程序管理器”任务正执行中
mov ebx,prgman_msg1
call sys_routine_seg_sel:put_string mov ecx,0x46
call sys_routine_seg_sel:allocate_memory
call append_to_tcb_link ;将此TCB添加到TCB链中 push dword ;用户程序位于逻辑50扇区
push ecx ;压入任务控制块起始线性地址 call load_relocate_program call far [es:ecx+0x14] ;执行任务切换。和上一章不同,任务切
;换时要恢复TSS内容,所以在创建任务
;时TSS要填写完整 ;重新加载并切换任务
mov ebx,prgman_msg2
call sys_routine_seg_sel:put_string mov ecx,0x46
call sys_routine_seg_sel:allocate_memory
call append_to_tcb_link ;将此TCB添加到TCB链中 push dword ;用户程序位于逻辑50扇区
push ecx ;压入任务控制块起始线性地址 call load_relocate_program jmp far [es:ecx+0x14] ;执行任务切换 mov ebx,prgman_msg3
call sys_routine_seg_sel:put_string hlt core_code_end: ;-------------------------------------------------------------------------------
SECTION core_trail
;-------------------------------------------------------------------------------
core_end:
;代码清单15-2
;文件名:c15.asm
;文件说明:用户程序
;创建日期:2011-11-15 19:11 ;===============================================================================
SECTION header vstart= program_length dd program_end ;程序总长度#0x00 head_len dd header_end ;程序头部的长度#0x04 stack_seg dd ;用于接收堆栈段选择子#0x08
stack_len dd ;程序建议的堆栈大小#0x0c
;以4KB为单位 prgentry dd start ;程序入口#0x10
code_seg dd section.code.start ;代码段位置#0x14
code_len dd code_end ;代码段长度#0x18 data_seg dd section.data.start ;数据段位置#0x1c
data_len dd data_end ;数据段长度#0x20
;-------------------------------------------------------------------------------
;符号地址检索表
salt_items dd (header_end-salt)/ ;#0x24 salt: ;#0x28
PrintString db '@PrintString'
times -($-PrintString) db TerminateProgram db '@TerminateProgram'
times -($-TerminateProgram) db ReadDiskData db '@ReadDiskData'
times -($-ReadDiskData) db header_end: ;===============================================================================
SECTION data vstart= message_1 db 0x0d,0x0a
db '[USER TASK]: Hi! nice to meet you,'
db 'I am run at CPL=', message_2 db
db '.Now,I must exit...',0x0d,0x0a, data_end: ;===============================================================================
[bits ]
;===============================================================================
SECTION code vstart=
start:
;任务启动时,DS指向头部段,也不需要设置堆栈
mov eax,ds
mov fs,eax mov eax,[data_seg]
mov ds,eax mov ebx,message_1
call far [fs:PrintString] mov ax,cs
and al,0000_0011B
or al,0x30
mov [message_2],al mov ebx,message_2
call far [fs:PrintString] call far [fs:TerminateProgram] ;退出,并将控制权返回到核心 code_end: ;-------------------------------------------------------------------------------
SECTION trail
;-------------------------------------------------------------------------------
program_end:
3. 习题2
这一题要求演示任务嵌套,说实话这题还是有点难度的,因为他要求我们在程序A中加载任务B并且切换的,这就要我们直接调用内核的加载程序的程序。我们把内核调用程序的例程改成调用门就好了,这个不是问题。但是问题是我们怎么切换任务B呢?当然我们可以改一下load_program让他返回TSS的选择子给我们,但是我又想到了程序A最好要有他自己所有的私有数据,所以想了一想我还是往任务A的LDT写了调用程序B的任务门了,程序A要有一个地方可以记录加载程序的LDT选择子。最后我给所有任务都填上了任务管理器的TSS选择子,方便切换。
;===============================内核程序=================================
;定义内核所要用到的选择子
All_4GB_Segment equ 0x0008 ;4GB的全内存区域
Stack_Segement equ 0x0018 ;内核栈区
Print_Segement equ 0x0020 ;显存映射区
Sys_Routine_Segement equ 0x0028 ;公用例程段
Core_Data_Segement equ 0x0030 ;内核数据区
Core_Code_Segement equ 0x0038 ;内核代码段
;----------------------------------------------------------------
User_Program_AddressA equ ;用户程序所在逻辑扇区
Switch_Stack_Size equ ;切换栈段的大小
;=============================内核程序头部===============================
SECTION header vstart=
Program_Length dd Program_end ;内核总长度
Sys_Routine_Seg dd section.Sys_Routine.start ;公用例程段线性地址
Core_Data_Seg dd section.Core_Data.start ;内核数据区线性地址
Core_Code_Seg dd section.Core_Code.start ;内核代码区线性地址
Code_Entry dd start ;注意偏移地址一定是32位的
dw Core_Code_Segement
;----------------------------------------------------------------
[bits ]
;=========================================================================
;============================公用例程区===================================
;=========================================================================
SECTION Sys_Routine align= vstart=
ReadHarddisk: ;push1:28位磁盘号(esi)
;push2:应用程序数据段选择子(ax->ds)
;push3: 偏移地址(ebx)
;push4: 应用程序代码段选择子(dx)
pushad
push ds
push es mov ebp,esp mov esi,[ebp+*]
movzx eax,word[ebp+*]
mov ebx,[ebp+*]
movzx edx,word[ebp+*] arpl ax,dx
mov ds,ax mov dx,0x1f2
mov al,0x01 ;读一个扇区
out dx,al inc edx ;0-7位
mov eax,esi
out dx,al inc edx ;8-15位
mov al,ah
out dx,al inc edx ;16-23位
shr eax,
out dx,al inc edx ;24-28位,主硬盘,LBA模式
mov al,ah
and al,0x0f
or al,0xe0
out dx,al inc edx
mov al,0x20
out dx,al _wait:
in al,dx
and al,0x88
cmp al,0x08
jne _wait mov dx,0x1f0
mov ecx, _read:
in ax,dx
mov [ebx],ax
add ebx,
loop _read pop es
pop ds
popad
retf ;4个数据
;----------------------------------------------------------------
put_string: ;ebx:偏移地址
pushad
push ds
push es _print:
mov cl,[ebx]
cmp cl,
je _exit
call put_char
inc ebx
jmp _print
_exit:
pop es
pop ds
popad
retf ;段间返回
;--------------------------------------------------------------
put_char: ;cl就是要显示的字符
push ebx
push es
push ds mov dx,0x3d4
mov al,0x0e ;高8位
out dx,al
mov dx,0x3d5
in al,dx
mov ah,al ;先把高8位存起来
mov dx,0x3d4
mov al,0x0f ;低8位
out dx,al
mov dx,0x3d5
in al,dx ;现在ax就是当前光标的位置 _judge:
cmp cl,0x0a
je _set_0x0a
cmp cl,0x0d
je _set_0x0d
_print_visible:
mov bx,ax
mov eax,Print_Segement
mov es,eax
shl bx, ;注意这里一定要把ebx变成原来的两倍,实际位置是光标位置的两倍
mov [es:bx],cl ;注意这里是屏幕!
mov byte[es:bx+],0x07
add bx,
shr bx,
jmp _roll_screen
_set_0x0d: ;回车
mov bl,
div bl
mul bl
mov bx,ax
jmp _set_cursor
_set_0x0a: ;换行
mov bx,ax
add bx,
jmp _roll_screen
_roll_screen:
cmp bx,
jl _set_cursor
mov eax,Print_Segement
mov ds,eax
mov es,eax cld
mov edi,0x00
mov esi,0xa0
mov ecx,
rep movsw
_cls:
mov bx,
mov ecx,
_print_blank:
mov word[es:bx],0x0720
add bx,
loop _print_blank
mov bx, ;别总是忘了光标的位置!
_set_cursor: ;改变后的光标位置在bx上
mov dx,0x3d4
mov al,0x0f ;低8位
out dx,al mov al,bl
mov dx,0x3d5
out dx,al mov dx,0x3d4
mov al,0x0e ;高8位
out dx,al mov al,bh
mov dx,0x3d5
out dx,al pop ds
pop es
pop ebx
ret
;----------------------------------------------------------------
allocate_memory: ;简易内存分配策略
;输入ecx:想要分配的总字节数
;输出ecx:分配的线性基地址
push ds
push eax
push ebx
call Cal_User_Mem mov eax,Core_Data_Segement
mov ds,eax
mov eax,[ram_alloc]
mov edx,eax ;edx暂存一下eax
add eax,ecx cmp eax,edx ;发现新分配的现地址比原来的还小,说明已经溢出
jge _alloc
mov ebx,mem_alloc_fail
call Sys_Routine_Segement:put_string
mov ecx, ;分配为0说明已经分配失败
jmp _exit1
_alloc: mov ebx,eax
and ebx,0xfffffffc
add ebx, ;强行向上取整
test eax,0x00000003
cmovnz eax,ebx
mov ecx,[ram_alloc] ;要返回要分配的初始地址
mov [ram_alloc],eax ;下一次分配的线性基地址 _exit1:
pop ebx
pop eax
pop ds retf
;----------------------------------------------------------------
recycled_memory_and_gdt:
mov eax,[ram_recycled]
sub [ram_alloc],eax
mov dword[ram_recycled], ;因为我们还没学到多任务,先这样简单地清零 sgdt [pgdt_base_tmp]
sub word[pgdt_base_tmp], ;应用程序的LDT,TSS
lgdt [pgdt_base_tmp] ;重新加载内核
retf
;----------------------------------------------------------------
Cal_User_Mem: ;输入ecx:应用程序用到的内存(字节)
add [ram_recycled],ecx
ret
;----------------------------------------------------------------
Make_Seg_Descriptor: ;构造段描述符
;输入:
;eax:线性基地址
;ebx:段界限
;ecx:属性
;输出:
;eax:段描述符低32位
;edx:段描述符高32位
mov edx,eax
and edx,0xffff0000
rol edx,
bswap edx
or edx,ecx shl eax,
or ax,bx
and ebx,0x000f0000
or edx,ebx
retf
;----------------------------------------------------------------
Make_Gate_Descriptor: ;构造门描述符
;输入:
;eax:段内偏移地址
;bx: 段的选择子
;cx: 段的属性
;输出:
;eax:门描述符低32位
;edx:门描述符高32位
push ebx
push ecx mov edx,eax
and edx,0xffff0000 ;要高16位
or dx,cx shl ebx,
and eax,0x0000ffff
or eax,ebx pop ecx
pop ebx retf
;----------------------------------------------------------------
Set_New_GDT: ;装载新的全局描述符
;输入:edx:eax描述符
;输出:cx选择子
push ds
push es mov ebx,Core_Data_Segement
mov ds,ebx mov ebx,All_4GB_Segment
mov es,ebx sgdt [pgdt_base_tmp] movzx ebx,word[pgdt_base_tmp]
inc bx ;注意这里要一定是inc bx而不是inc ebx,因为gdt段界限初始化是0xffff的
;要用到回绕特性
add ebx,[pgdt_base_tmp+0x02] ;得到pgdt的线性基地址 mov [es:ebx],eax
mov [es:ebx+0x04],edx ;装载新的gdt符
;装载描述符要装载到实际位置上 add word[pgdt_base_tmp], ;给gdt的段界限加上8(字节) lgdt [pgdt_base_tmp] ;加载gdt到gdtr的位置和实际表的位置无关 mov ax,[pgdt_base_tmp] ;得到段界限
xor dx,dx
mov bx, ;得到gdt大小
div bx
mov cx,ax
shl cx, ;得到选择子,ti=0(全局描述符),rpl=0(申请特权0级) pop es
pop ds
retf
;----------------------------------------------------------------
Set_New_LDT_To_TCB: ;装载新的局部描述符
;输入:edx:eax描述符
; : ebx:TCB线性基地址
;输出:cx选择子 push edi
push eax
push ebx
push edx
push ds mov ecx,All_4GB_Segment
mov ds,ecx mov edi,[ebx+0x0c] ;LDT的线性基地址
movzx ecx,word[ebx+0x0a]
inc cx ;得到实际的LDT的大小(界限还要-1) mov [edi+ecx+0x00],eax
mov [edi+ecx+0x04],edx add cx,
dec cx mov [ebx+0x0a],cx mov ax,cx
xor dx,dx
mov cx,
div cx shl ax,
mov cx,ax
or cx,0x0004 ;LDT,第三位TI位一定是1 pop ds
pop edx
pop ebx
pop eax
pop edi
retf
;----------------------------------------------------------------
PrintDword: ;显示edx内容的一个调试函数
pushad
push ds mov eax,Core_Data_Segement
mov ds,eax mov ebx,bin_hex
mov ecx, _query:
rol edx,
mov eax,edx
and eax,0x0000000f
xlat push ecx
mov cl,al
call put_char
pop ecx loop _query pop ds
popad retf
;---------------------------------------------------------------------
load_program_form_user: ;输入push:用户程序逻辑扇区号
; fs: 指向用户程序头部
pushad
push ds
push es mov ecx,0x46 ;另一个TCB链
call Sys_Routine_Segement:allocate_memory
call Sys_Routine_Segement:append_to_tcb mov ebp,esp mov eax,Core_Data_Segement
mov ds,eax mov eax,All_4GB_Segment
mov es,eax mov edi,[ebp+*] ;获取tcb的线性基地址,别忘了调用相对近调用还要有1个push mov ecx,
call Sys_Routine_Segement:allocate_memory
mov [es:edi+0x0c],ecx
mov word[es:edi+0x0a],0xffff ;初始化LDT界限位0xffff mov esi,[ebp+*] ;esi必须是逻辑扇区号
mov ebx,core_buf ;ebx要在内核数据缓冲区(先读取头部在缓冲区,esi已经是有扇区号了) push esi
push ds
push ebx
push cs
call Sys_Routine_Segement:ReadHarddisk mov eax,[core_buf] ;读取用户程序长度 mov ebx,eax
and ebx,0xfffffe00 ;清空低9位(强制对齐512)
add ebx,
test eax,0x000001ff
cmovnz eax,ebx ;低9位不为0则使用向上取整的结果 mov ecx,eax ;eax是整个程序的向上取整的大小
call Sys_Routine_Segement:allocate_memory
;先分配内存给整个程序,再分配内存给栈区
mov ebx,ecx
mov [es:edi+0x06],ecx ;tcb 0x06:程序加载基地址 xor edx,edx
mov ecx, ;千万不要改掉ebx
div ecx
mov ecx,eax mov eax,All_4GB_Segment
mov ds,eax _loop_read:
push esi
push ds
push ebx
push cs
call Sys_Routine_Segement:ReadHarddisk ;esi还是User_Program_Address
inc esi
add ebx,
loop _loop_read mov esi,edi ;esi: TCB的线性基地址
mov edi,[es:esi+0x06] ;程序加载的线性基地址 ;建立头部描述符
mov eax,edi
mov ebx,[edi+0x04]
dec ebx ;段界限
mov ecx,0x0040f200
call Sys_Routine_Segement:Make_Seg_Descriptor
mov ebx,esi
call Sys_Routine_Segement:Set_New_LDT_To_TCB
or cx,0x0003 ;特权级3
mov [es:esi+0x44],cx ;记得要登记头部的选择子
mov [edi+0x04],cx ;建立代码段描述符
mov eax,edi
add eax,[edi+0x14]
mov ebx,[edi+0x18]
dec ebx
mov ecx,0x0040f800
call Sys_Routine_Segement:Make_Seg_Descriptor
mov ebx,esi
call Sys_Routine_Segement:Set_New_LDT_To_TCB
or cx,0x0003
mov [edi+0x14],cx ;建立数据段描述符
mov eax,edi
add eax,[edi+0x1c]
mov ebx,[edi+0x20]
dec ebx
mov ecx,0x0040f200
call Sys_Routine_Segement:Make_Seg_Descriptor
mov ebx,esi
call Sys_Routine_Segement:Set_New_LDT_To_TCB
or cx,0x0003
mov [edi+0x1c],cx ;建立栈段描述符
mov ecx,[edi+0x0c]
mov ebx,0x000fffff
sub ebx,ecx
mov eax, ;4KB粒度
mul ecx
mov ecx,eax
call Sys_Routine_Segement:allocate_memory
mov eax,ecx ;eax是栈段的线性基地址
mov ecx,0x00c0f600
call Sys_Routine_Segement:Make_Seg_Descriptor
mov ebx,esi
call Sys_Routine_Segement:Set_New_LDT_To_TCB
or cx,0x0003
mov [edi+0x08],cx mov eax,[esi+0x0c] ;LDT线性基地址
mov [edi+0x32],eax
mov ax,[esi+0x0a] ;LDT段界限
mov [edi+0x30],ax ;现在开始重定位API符号表
;---------------------------------------------------------------------
mov eax,All_4GB_Segment ;因为这个时候用户头部在LDT,而LDT还没有被加载,只能通过4GB空间访问
mov es,eax
mov eax,Core_Data_Segement
mov ds,eax cld
mov ecx,[es:edi+0x36] ;得到用户程序符号表的条数
add edi,0x3a ;用户符号表的偏移地址是0x3a _loop_U_SALT:
push edi
push ecx mov ecx,salt_items_sum
mov esi,salt _loop_C_SALT:
push edi
push esi
push ecx mov ecx, ;比较256个字节
repe cmpsd
jne _re_match ;如果成功匹配,那么esi和edi刚好会在数据区之后的 mov eax,[esi] ;偏移地址
mov [es:edi-],eax ;把偏移地址填入用户程序的符号区
mov ax,[esi+0x04] ;段的选择子 or ax,0x0002 ;把RPL改为3,代表(内核)赋予应用程序以特权级3
mov [es:edi-],ax ;把段的选择子填入用户程序的段选择区 _re_match:
pop ecx
pop esi
add esi,salt_length
pop edi
loop _loop_C_SALT pop ecx
pop edi
add edi,
loop _loop_U_SALT
;---------------------------------------------------------------------
;----------------------填入临时中转任务门选择子-----------------------
mov edi,[ebp+*]
mov edi,[es:edi+0x06] ;从TCB线性基地址中获得用户程序加载的基地址 mov ax,[salt_tp]
mov [es:edi+0x28],ax ;填充任务门选择子
;--------------------------------------------------------------------- mov esi,[ebp+*] ;重新获得TCB的线性基地址 mov ecx,Switch_Stack_Size
mov eax,ecx
mov [es:esi+0x1a],ecx
shr dword[es:esi+0x1a], ;相当于除以4096
call Sys_Routine_Segement:allocate_memory
add eax,ecx ;得到最高地址
mov [es:esi+0x1e],eax ;登记线性基地址
mov ebx,0x000fffff
sub ebx,[es:esi+0x1a]
mov ecx,0x00c09600 ;特权级0
call Sys_Routine_Segement:Make_Seg_Descriptor
mov ebx,esi
call Sys_Routine_Segement:Set_New_LDT_To_TCB
or cx,0x0000 ;RPL为0
mov [es:esi+0x22],cx
mov dword[es:esi+0x24], mov ecx,Switch_Stack_Size
mov eax,ecx
mov [es:esi+0x28],ecx
shr dword[es:esi+0x28], ;相当于除以4096
call Sys_Routine_Segement:allocate_memory
add eax,ecx ;得到最高地址
mov [es:esi+0x2c],eax ;登记线性基地址
mov ebx,0x000fffff
sub ebx,[es:esi+0x28]
mov ecx,0x00c0b600 ;特权级1
call Sys_Routine_Segement:Make_Seg_Descriptor
mov ebx,esi
call Sys_Routine_Segement:Set_New_LDT_To_TCB
or cx,0x0001 ;RPL为1
mov [es:esi+0x30],cx
mov dword[es:esi+0x32], mov ecx,Switch_Stack_Size
mov eax,ecx
mov [es:esi+0x36],ecx
shr dword[es:esi+0x36], ;相当于除以4096
call Sys_Routine_Segement:allocate_memory
add eax,ecx ;得到最高地址
mov [es:esi+0x3a],eax ;登记线性基地址
mov ebx,0x000fffff
sub ebx,[es:esi+0x36]
mov ecx,0x00c0d600 ;特权级2
call Sys_Routine_Segement:Make_Seg_Descriptor
mov ebx,esi
call Sys_Routine_Segement:Set_New_LDT_To_TCB
or cx,0x0002 ;RPL为2
mov [es:esi+0x3e],cx
mov dword[es:esi+0x40], ;在GDT中存入LDT信息
mov eax,[es:esi+0x0c]
movzx ebx,word[es:esi+0x0a]
mov ecx,0x00408200 ;LDT描述符,特权级0级
call Sys_Routine_Segement:Make_Seg_Descriptor
call Sys_Routine_Segement:Set_New_GDT
mov [es:esi+0x10],cx ;在TCB放入LDT选择子 ;-------------------在TCB中登记TSS的信息-------------------------------
mov ecx, ;创建一个最小尺寸的TSS
mov [es:esi+0x12],cx
dec word[es:esi+0x12] ;记得-1,要的是段界限
call Sys_Routine_Segement:allocate_memory
mov [es:esi+0x14],ecx ;TSS基地址 ;构建TSS信息表
mov dword[es:ecx+0x00], ;没有前一个任务 mov edx,[es:esi+0x24] ;0栈段
mov [es:ecx+],edx
mov dx,[es:esi+0x22]
mov [es:ecx+],dx mov edx,[es:esi+0x32] ;1栈段
mov [es:ecx+],edx
mov dx,[es:esi+0x30]
mov [es:ecx+],dx mov edx,[es:esi+0x40] ;2栈段
mov [es:ecx+],edx
mov dx,[es:esi+0x3e]
mov [es:ecx+],dx mov edx,[es:esi+0x10] ;LDT选择子
mov [es:ecx+],edx mov dx,[es:esi+0x12] ;I/O偏移
mov [es:ecx+],dx ;是102不是104 mov word[es:ecx+], ;T=0 mov edi,[es:esi+0x06] ;用户程序的线性基地址 mov edx,[es:edi+0x10] ;EIP
mov [es:ecx+],edx mov edx,[es:edi+0x14] ;CS
mov [es:ecx+],dx mov edx,[es:edi+0x08] ;SS
mov [es:ecx+],dx mov edx,[es:edi+0x04] ;DS(是指向用户头部,而不是用户程序的数据区)
mov [es:ecx+],dx mov word[es:ecx+], ;ES
mov word[es:ecx+], ;FS
mov word[es:ecx+], ;GS pushfd
pop edx
mov [es:ecx+],edx ;EFLAGS ;在GDT中存入TSS信息,必须放入GDT,这样才能在当前LDT中加入调用门
mov eax,[es:esi+0x14]
movzx ebx,word[es:esi+0x12]
mov ecx,0x00408900
call Sys_Routine_Segement:Make_Seg_Descriptor
call Sys_Routine_Segement:Set_New_GDT
mov [es:esi+0x18],cx ;在当前用户程序的LDT中加入需要加载的程序的任务门,且把任务门的选择子写入fs指向的用户程序头部的相应位置
mov eax,0x00000
mov bx,[es:esi+0x18]
mov cx,0xe500 call Sys_Routine_Segement:Make_Gate_Descriptor
call Sys_Routine_Segement:Set_Task_Gate_To_UsersLDT pop es
pop ds
popad
retf ;相当于是stdcall,过程清栈
;---------------------------------------------------------------------
append_to_tcb: ;写入新的TCB链
;输入:ecx新的TCB线性基地址
pushad push ds
push es mov eax,All_4GB_Segment
mov es,eax mov eax,Core_Data_Segement
mov ds,eax mov eax,[tcb_chain]
cmp eax,0x00
je _notcb _search_tcb:
mov edx,[tcb_chain+0x00]
mov eax,[es:edx]
cmp eax,0x00
jne _search_tcb mov [es:edx+0x00],ecx
jmp _out_tcb_search _notcb:
mov [tcb_chain],ecx _out_tcb_search:
pop es
pop ds popad
retf
;---------------------------------------------------------------------
Set_Task_Gate_To_UsersLDT: ;输入:eax:任务门描述符的低32位
; edx:任务门描述符的高32位
;输出:无
mov edi,[fs:0x32] ;LDT基地址
movzx ebx,word[fs:0x30] ;段界限 inc bx mov [es:edi+ebx+0x00],eax
mov [es:edi+ebx+0x04],edx add bx,
dec bx ;记得要段界限 mov [fs:0x30],bx ;重新写入 mov ax,bx
xor dx,dx
mov bx,
div bx ;得到LDT选择位置 shl ax,
or ax,0x0004 ;TI=1,特权级是3
mov [fs:0x2e],ax retf
;---------------------------------------------------------------------
;=========================================================================
;===========================内核数据区====================================
;=========================================================================
SECTION Core_Data align= vstart=
;-------------------------------------------------------------------------------
pgdt_base_tmp: dw ;这一章的用户程序都是从GDT中加载的
dd ram_alloc: dd 0x00100000 ;下次分配内存时的起始地址(直接暴力从0x00100000开始分配了)
ram_recycled dd ;这里储存程序实际用的大小
salt:
salt_1: db '@Printf' ;@Printf函数(公用例程)
times -($-salt_1) db
dd put_string
dw Sys_Routine_Segement
dw ;参数个数 salt_2: db '@ReadHarddisk' ;@ReadHarddisk函数(公用例程)
times -($-salt_2) db
dd ReadHarddisk
dw Sys_Routine_Segement
dw ;参数个数 salt_3: db '@PrintDwordAsHexString' ;@PrintDwordAsHexString函数(公用例程)
times -($-salt_3) db
dd PrintDword
dw Sys_Routine_Segement
dw ;参数个数 salt_4: db '@Fopen' ;@Fopen函数(内核例程)
times -($-salt_4) db
dd load_program_form_user
dw Sys_Routine_Segement
dw ;参数个数 salt_length: equ $-salt_4
salt_items_sum equ ($-salt)/salt_length ;得到项目总数 salt_tp: dw ;任务门,专门拿来给程序切换到全局空间的 message_1 db ' If you seen this message,that means we '
db 'are now in protect mode,and the system '
db 'core is loaded,and the video display '
db 'routine works perfectly.',0x0d,0x0a, message_2 db ' Loading user program...', do_status db 'Done.',0x0d,0x0a, message_3 db 0x0d,0x0a,0x0d,0x0a,0x0d,0x0a
db ' User program terminated,control returned.'
db 0x0d,0x0a,0x0d,0x0a,
message_4 db ' We have been backed to kernel.',0x0d,0x0a,
message_5 db ' The GDT and memory have benn recycled.',
message_6 db ' From the system wide gate:',0x0d,0x0a,
message_7 db ' Setting the gate discriptor...',
message_In_Gate db ' Hi!My name is Philip:',0x0d,0x0a, bin_hex db '0123456789ABCDEF'
;put_hex_dword子过程用的查找表
core_buf times db ;内核用的缓冲区(2049个字节(2MB)) esp_pointer dd ;内核用来临时保存自己的栈指针 cpu_brnd0 db 0x0d,0x0a,' ',
cpu_brand times db
cpu_brnd1 db 0x0d,0x0a,0x0d,0x0a,
mem_alloc_fail db 'The Program is too large to load'
core_ss dw
core_sp dd
;程序管理器的任务信息
prgman_tss dd ;程序管理器的TSS基地址
dw ;程序管理器的TSS描述符选择子 prgman_msg1 db 0x0d,0x0a
db '[PROGRAM MANAGER]: Hello! I am Program Manager,'
db 'run at CPL=0.Now,create user task and switch '
db 'to it by the CALL instruction...',0x0d,0x0a, prgman_msg2 db 0x0d,0x0a
db '[PROGRAM MANAGER]: I am glad to regain control.'
db 'Now,create another user task and switch to '
db 'it by the JMP instruction...',0x0d,0x0a, prgman_msg3 db 0x0d,0x0a
db '[PROGRAM MANAGER]: I am gain control again,', core_msg0 db 0x0d,0x0a
db '[SYSTEM CORE]: Uh...This task initiated with '
db 'CALL instruction or an exeception/ interrupt,'
db 'should use IRETD instruction to switch back...'
db 0x0d,0x0a, core_msg1 db 0x0d,0x0a
db '[SYSTEM CORE]: Uh...This task initiated with '
db 'JMP instruction, should switch to Program '
db 'Manager directly by the JMP instruction...'
db 0x0d,0x0a,
core_stop db 'HALT...', tcb_chain dd ;任务控制块链头指针
;=========================================================================
;===========================内核代码区====================================
;=========================================================================
SECTION Core_Code align= vstart=
load_program: ;输入push1:逻辑扇区号
; push2: 线性基地址
pushad
push ds
push es mov ebp,esp ;别忘了把参数传给ebp mov eax,Core_Data_Segement
mov ds,eax ;切换到内核数据段 mov eax,All_4GB_Segment
mov es,eax mov edi,[ebp+*] ;获取tcb的线性基地址,别忘了调用相对近调用还要有1个push mov ecx,
call Sys_Routine_Segement:allocate_memory
mov [es:edi+0x0c],ecx
mov word[es:edi+0x0a],0xffff ;初始化LDT界限位0xffff mov esi,[ebp+*] ;esi必须是逻辑扇区号
mov ebx,core_buf ;ebx要在内核数据缓冲区(先读取头部在缓冲区,esi已经是有扇区号了) push esi
push ds
push ebx
push cs
call Sys_Routine_Segement:ReadHarddisk mov eax,[core_buf] ;读取用户程序长度 mov ebx,eax
and ebx,0xfffffe00 ;清空低9位(强制对齐512)
add ebx,
test eax,0x000001ff
cmovnz eax,ebx ;低9位不为0则使用向上取整的结果 mov ecx,eax ;eax是整个程序的向上取整的大小
call Sys_Routine_Segement:allocate_memory
;先分配内存给整个程序,再分配内存给栈区
mov ebx,ecx
mov [es:edi+0x06],ecx ;tcb 0x06:程序加载基地址 xor edx,edx
mov ecx, ;千万不要改掉ebx
div ecx
mov ecx,eax mov eax,All_4GB_Segment ;切换到4GB段区域(平坦模式)
mov ds,eax _loop_read_u:
push esi
push ds
push ebx
push cs
call Sys_Routine_Segement:ReadHarddisk ;esi还是User_Program_Address
inc esi
add ebx,
loop _loop_read_u mov esi,edi ;esi: TCB的线性基地址
mov edi,[es:esi+0x06] ;程序加载的线性基地址 ;建立头部描述符
mov eax,edi
mov ebx,[edi+0x04]
dec ebx ;段界限
mov ecx,0x0040f200
call Sys_Routine_Segement:Make_Seg_Descriptor
mov ebx,esi
call Sys_Routine_Segement:Set_New_LDT_To_TCB
or cx,0x0003 ;特权级3
mov [es:esi+0x44],cx ;记得要登记头部的选择子
mov [edi+0x04],cx ;建立代码段描述符
mov eax,edi
add eax,[edi+0x14]
mov ebx,[edi+0x18]
dec ebx
mov ecx,0x0040f800
call Sys_Routine_Segement:Make_Seg_Descriptor
mov ebx,esi
call Sys_Routine_Segement:Set_New_LDT_To_TCB
or cx,0x0003
mov [edi+0x14],cx ;建立数据段描述符
mov eax,edi
add eax,[edi+0x1c]
mov ebx,[edi+0x20]
dec ebx
mov ecx,0x0040f200
call Sys_Routine_Segement:Make_Seg_Descriptor
mov ebx,esi
call Sys_Routine_Segement:Set_New_LDT_To_TCB
or cx,0x0003
mov [edi+0x1c],cx ;建立栈段描述符
mov ecx,[edi+0x0c]
mov ebx,0x000fffff
sub ebx,ecx
mov eax, ;4KB粒度
mul ecx
mov ecx,eax
call Sys_Routine_Segement:allocate_memory
mov eax,ecx ;eax是栈段的线性基地址
mov ecx,0x00c0f600
call Sys_Routine_Segement:Make_Seg_Descriptor
mov ebx,esi
call Sys_Routine_Segement:Set_New_LDT_To_TCB
or cx,0x0003
mov [edi+0x08],cx mov eax,[esi+0x0c] ;LDT线性基地址
mov [edi+0x32],eax
mov ax,[esi+0x0a] ;LDT段界限
mov [edi+0x30],ax ;现在开始重定位API符号表
;---------------------------------------------------------------------
mov eax,All_4GB_Segment ;因为这个时候用户头部在LDT,而LDT还没有被加载,只能通过4GB空间访问
mov es,eax
mov eax,Core_Data_Segement
mov ds,eax cld
mov ecx,[es:edi+0x36] ;得到用户程序符号表的条数
add edi,0x3a ;用户符号表的偏移地址是0x3a _loop_U_SALT_u:
push edi
push ecx mov ecx,salt_items_sum
mov esi,salt _loop_C_SALT_u:
push edi
push esi
push ecx mov ecx, ;比较256个字节
repe cmpsd
jne _re_match_u ;如果成功匹配,那么esi和edi刚好会在数据区之后的 mov eax,[esi] ;偏移地址
mov [es:edi-],eax ;把偏移地址填入用户程序的符号区
mov ax,[esi+0x04] ;段的选择子 or ax,0x0002 ;把RPL改为3,代表(内核)赋予应用程序以特权级3
mov [es:edi-],ax ;把段的选择子填入用户程序的段选择区 _re_match_u:
pop ecx
pop esi
add esi,salt_length
pop edi
loop _loop_C_SALT_u pop ecx
pop edi
add edi,
loop _loop_U_SALT_u
;---------------------------------------------------------------------
;----------------------填入临时中转任务门选择子-----------------------
mov edi,[ebp+*]
mov edi,[es:edi+0x06] ;从TCB线性基地址中获得用户程序加载的基地址 mov ax,[salt_tp]
mov [es:edi+0x28],ax ;填充任务门选择子
;---------------------------------------------------------------------
mov esi,[ebp+*] ;重新获得TCB的线性基地址 ;现在设置所有的特权级栈段,并且把特权级栈段放到TCB中(为了等一下设置TSS)
;设置TSS特权0级栈段(暂存在TCB中)
mov ecx,Switch_Stack_Size
mov eax,ecx
mov [es:esi+0x1a],ecx
shr dword[es:esi+0x1a], ;相当于除以4096
call Sys_Routine_Segement:allocate_memory
add eax,ecx ;得到最高地址
mov [es:esi+0x1e],eax ;登记线性基地址
mov ebx,0x000fffff
sub ebx,[es:esi+0x1a]
mov ecx,0x00c09600 ;特权级0
call Sys_Routine_Segement:Make_Seg_Descriptor
mov ebx,esi
call Sys_Routine_Segement:Set_New_LDT_To_TCB
or cx,0x0000 ;RPL为0
mov [es:esi+0x22],cx
mov dword[es:esi+0x24], ;设置TSS特权1级栈段(暂存在TCB中)
mov ecx,Switch_Stack_Size
mov eax,ecx
mov [es:esi+0x28],ecx
shr dword[es:esi+0x28], ;相当于除以4096
call Sys_Routine_Segement:allocate_memory
add eax,ecx ;得到最高地址
mov [es:esi+0x2c],eax ;登记线性基地址
mov ebx,0x000fffff
sub ebx,[es:esi+0x28]
mov ecx,0x00c0b600 ;特权级1
call Sys_Routine_Segement:Make_Seg_Descriptor
mov ebx,esi
call Sys_Routine_Segement:Set_New_LDT_To_TCB
or cx,0x0001 ;RPL为1
mov [es:esi+0x30],cx
mov dword[es:esi+0x32], ;设置TSS特权2级栈段(暂存在TCB中)
mov ecx,Switch_Stack_Size
mov eax,ecx
mov [es:esi+0x36],ecx
shr dword[es:esi+0x36], ;相当于除以4096
call Sys_Routine_Segement:allocate_memory
add eax,ecx ;得到最高地址
mov [es:esi+0x3a],eax ;登记线性基地址
mov ebx,0x000fffff
sub ebx,[es:esi+0x36]
mov ecx,0x00c0d600 ;特权级2
call Sys_Routine_Segement:Make_Seg_Descriptor
mov ebx,esi
call Sys_Routine_Segement:Set_New_LDT_To_TCB
or cx,0x0002 ;RPL为2
mov [es:esi+0x3e],cx
mov dword[es:esi+0x40], ;在GDT中存入LDT信息
mov eax,[es:esi+0x0c]
movzx ebx,word[es:esi+0x0a]
mov ecx,0x00408200 ;LDT描述符,特权级0级
call Sys_Routine_Segement:Make_Seg_Descriptor
call Sys_Routine_Segement:Set_New_GDT
mov [es:esi+0x10],cx ;在TCB放入LDT选择子 ;在TCB中登记TSS的信息
mov ecx, ;创建一个最小尺寸的TSS
mov [es:esi+0x12],cx
dec word[es:esi+0x12] ;记得-1,要的是段界限
call Sys_Routine_Segement:allocate_memory
mov [es:esi+0x14],ecx ;TSS基地址 ;构建TSS信息表
mov dword[es:ecx+0x00], ;没有前一个任务 mov edx,[es:esi+0x24] ;0栈段
mov [es:ecx+],edx
mov dx,[es:esi+0x22]
mov [es:ecx+],dx mov edx,[es:esi+0x32] ;1栈段
mov [es:ecx+],edx
mov dx,[es:esi+0x30]
mov [es:ecx+],dx mov edx,[es:esi+0x40] ;2栈段
mov [es:ecx+],edx
mov dx,[es:esi+0x3e]
mov [es:ecx+],dx mov edx,[es:esi+0x10] ;LDT选择子
mov [es:ecx+],edx mov dx,[es:esi+0x12] ;I/O偏移
mov [es:ecx+],dx ;是102不是104 mov word[es:ecx+], ;T=0 mov edi,[es:esi+0x06] ;用户程序的线性基地址 mov edx,[es:edi+0x10] ;EIP
mov [es:ecx+],edx mov edx,[es:edi+0x14] ;CS
mov [es:ecx+],dx mov edx,[es:edi+0x08] ;SS
mov [es:ecx+],dx mov edx,[es:edi+0x04] ;DS(是指向用户头部,而不是用户程序的数据区)
mov [es:ecx+],dx mov word[es:ecx+], ;ES
mov word[es:ecx+], ;FS
mov word[es:ecx+], ;GS pushfd
pop edx
mov [es:ecx+],edx ;EFLAGS ;在GDT中存入TSS信息
mov eax,[es:esi+0x14]
movzx ebx,word[es:esi+0x12]
mov ecx,0x00408900
call Sys_Routine_Segement:Make_Seg_Descriptor
call Sys_Routine_Segement:Set_New_GDT
mov [es:esi+0x18],cx pop es
pop ds
popad
ret ;相当于是stdcall,过程清栈
;---------------------------------------------------------------------
start:
mov eax,Core_Data_Segement
mov ds,eax mov ebx,message_1
call Sys_Routine_Segement:put_string _@load:
mov ebx,message_7
call Sys_Routine_Segement:put_string
;----------------------------安装门------------------------------------
mov edi,salt
mov ecx,salt_items_sum
_set_gate:
push ecx
mov eax,[edi+]
mov bx,[edi+] ;选择子
mov cx,0xec00 ;门是特权级是3的门,那么任何程序都能调用
or cx,[edi+] ;加上参数个数 call Sys_Routine_Segement:Make_Gate_Descriptor
call Sys_Routine_Segement:Set_New_GDT
mov [edi+],cx ;回填选择子
add edi,salt_length
pop ecx
loop _set_gate
;----------------------------------------------------------------------
mov ebx,do_status
call far [salt_1+]
mov ebx,message_6
call far [salt_1+]
mov ebx,message_In_Gate
call far [salt_1+] ;调用门显示字符信息(忽略偏移地址(前4字节)) mov ebx,message_4
call far [salt_1+]
mov ebx,message_2
call far [salt_1+] mov eax,All_4GB_Segment
mov es,eax mov ecx,
call Sys_Routine_Segement:allocate_memory
mov [prgman_tss],ecx ;保留线性基地址 mov word[es:ecx+], ;TI=0
mov word[es:ecx+], ;任务允许没有自己的LDT
mov dword[es:ecx+], ;设置CR3
mov word[es:ecx+], ;没有前一个任务
mov word[es:ecx+], ;任务管理器不需要I/O映射,要大于等于界限 mov eax,ecx
mov ebx, ;TSS段界限
mov ecx,0x00408900
call Sys_Routine_Segement:Make_Seg_Descriptor
call Sys_Routine_Segement:Set_New_GDT
mov [prgman_tss+0x04],cx ltr cx ;启动任务
;------------------安装用户管理程序的临时返回任务门--------------------
mov eax,0x0000 ;TSS不需要偏移地址
mov bx,[prgman_tss+0x04] ;TSS的选择子
mov cx,0xe500 call Sys_Routine_Segement:Make_Gate_Descriptor
call Sys_Routine_Segement:Set_New_GDT
mov [salt_tp],cx ;填入临时中转任务门选择子,注意不需要加260了
;----------------------------------------------------------------------
mov ebx,prgman_msg1
call Sys_Routine_Segement:put_string ;----------------------用户管理程序----------------------------
mov ecx,0x46 ;TCB链大小
call Sys_Routine_Segement:allocate_memory
call Sys_Routine_Segement:append_to_tcb push User_Program_AddressA
push ecx
call load_program jmp far [es:ecx+0x14] ;打开程序A,跳转方式打开 mov eax,Core_Data_Segement
mov ds,eax mov ebx,prgman_msg3
call Sys_Routine_Segement:put_string
mov ebx,core_stop
call Sys_Routine_Segement:put_string cli
hlt
;----------------------------------------------------------------------
;=========================================================================
SECTION core_trail
;----------------------------------------------------------------
Program_end:
用户程序改动挺大的。
;==============================用户程序A=======================================
SECTION header vstart= program_length dd program_end ;程序总长度#0x00 head_len dd header_end ;程序头部的长度#0x04 stack_seg dd ;用于接收堆栈段选择子#0x08
stack_len dd ;程序建议的堆栈大小#0x0c
;以4KB为单位 prgentry dd start ;程序入口#0x10
code_seg dd section.code.start ;代码段位置#0x14
code_len dd code_end ;代码段长度#0x18 data_seg dd section.data.start ;数据段位置#0x1c
data_len dd data_end ;数据段长度#0x20
;-------------------------------------------------------------------------------
TpBack: dd ;管理程序任务门的偏移地址没用,直接填充就可以了
;#0x24
dw ;管理程序任务门的选择子
;#0x28 LoadPos: dd ;用户程序想加载程序的位置,直接填充就可以了,任务门用不到
;#0x2a
dw ;另一个用户程序任务门的选择子
;#0x2e Users_LDT_Add dw ;用户程序自己的LDT段界限
;#0x30
dd ;用户程序自己的LDT基地址
;#0x32
;-------------------------------------------------------------------------------
;符号地址检索表
salt_items dd (u_salt_end-salt)/ ;#0x36 salt: ;#0x3a
Printf: db '@Printf'
times -($-Printf) db TerminateProgram: db '@TerminateProgram'
times -($-TerminateProgram) db ReadHarddisk: db '@ReadHarddisk'
times -($-ReadHarddisk) db Fopen: db '@Fopen'
times -($-Fopen) db u_salt_end:
header_end:
;===============================================================================
SECTION data align= vstart= message_1 db 0x0d,0x0a
db '[USER TASKA]: Hi! I am task PhilipA',0x0d,0x0a, message_2 db '[USER TASKA]: Now I will load PhilipB',0x0d,0x0a,
data_end:
;===============================================================================
[bits ]
;===============================================================================
SECTION code align= vstart=
start:
User_Program_AddressB equ ;用户程序所在逻辑扇区
mov eax,ds
mov fs,eax mov eax,[data_seg]
mov ds,eax mov ebx,message_1
call far [fs:Printf] mov ebx,message_2
call far [fs:Printf] push User_Program_AddressB
call far [fs:Fopen] jmp far [fs:LoadPos] ;直接加载想要加载的用户程序B jmp far [fs:TpBack] ;回到任务管理程序
code_end: ;===============================================================================
SECTION trail
;-------------------------------------------------------------------------------
program_end:
;==============================用户程序B=======================================
SECTION header vstart= program_length dd program_end ;程序总长度#0x00 head_len dd header_end ;程序头部的长度#0x04 stack_seg dd ;用于接收堆栈段选择子#0x08
stack_len dd ;程序建议的堆栈大小#0x0c
;以4KB为单位 prgentry dd start ;程序入口#0x10
code_seg dd section.code.start ;代码段位置#0x14
code_len dd code_end ;代码段长度#0x18 data_seg dd section.data.start ;数据段位置#0x1c
data_len dd data_end ;数据段长度#0x20
;-------------------------------------------------------------------------------
TpBack: dd ;管理程序任务门的偏移地址没用,直接填充就可以了
;#0x24
dw ;管理程序任务门的选择子
;#0x28 LoadPos: dd ;用户程序想加载程序的位置,直接填充就可以了,任务门用不到
;#0x2a
dw ;另一个用户程序任务门的选择子
;#0x2e Users_LDT_Add dw ;用户程序自己的LDT段界限
;#0x30
dd ;用户程序自己的LDT基地址
;#0x32
;-------------------------------------------------------------------------------
;符号地址检索表
salt_items dd (u_salt_end-salt)/ ;#0x36 salt: ;#0x3a
Printf: db '@Printf'
times -($-Printf) db TerminateProgram: db '@TerminateProgram'
times -($-TerminateProgram) db ReadHarddisk: db '@ReadHarddisk'
times -($-ReadHarddisk) db Fopen: db '@Fopen'
times -($-Fopen) db
u_salt_end:
header_end:
;===============================================================================
SECTION data align= vstart= message_1 db '[USER TASKB]: Hi! I am PhilipB',0x0d,0x0a
db '[USER TASKB]: Now I will back to program manager',0x0d,0x0a,
data_end: ;===============================================================================
[bits ]
;===============================================================================
SECTION code align= vstart=
start:
mov eax,ds
mov fs,eax mov eax,[data_seg]
mov ds,eax mov ebx,message_1
call far [fs:Printf]
jmp far [fs:TpBack] ;直接回任务管理程序
code_end: ;===============================================================================
SECTION trail
;-------------------------------------------------------------------------------
program_end:
4. 习题3
习题三本来是书上没有的,但是我自己加上去了,其实我还是对任务门的机制很感兴趣的,所以我把第一题写了一个新的。
;===============================内核程序=================================
;定义内核所要用到的选择子
All_4GB_Segment equ 0x0008 ;4GB的全内存区域
Stack_Segement equ 0x0018 ;内核栈区
Print_Segement equ 0x0020 ;显存映射区
Sys_Routine_Segement equ 0x0028 ;公用例程段
Core_Data_Segement equ 0x0030 ;内核数据区
Core_Code_Segement equ 0x0038 ;内核代码段
;----------------------------------------------------------------
User_Program_AddressA equ ;用户程序所在逻辑扇区
User_Program_AddressB equ ;用户程序所在逻辑扇区
Switch_Stack_Size equ ;切换栈段的大小
;=============================内核程序头部===============================
SECTION header vstart=
Program_Length dd Program_end ;内核总长度
Sys_Routine_Seg dd section.Sys_Routine.start ;公用例程段线性地址
Core_Data_Seg dd section.Core_Data.start ;内核数据区线性地址
Core_Code_Seg dd section.Core_Code.start ;内核代码区线性地址
Code_Entry dd start ;注意偏移地址一定是32位的
dw Core_Code_Segement
;----------------------------------------------------------------
[bits ]
;=========================================================================
;============================公用例程区===================================
;=========================================================================
SECTION Sys_Routine align= vstart=
ReadHarddisk: ;push1:28位磁盘号(esi)
;push2:应用程序数据段选择子(ax->ds)
;push3: 偏移地址(ebx)
;push4: 应用程序代码段选择子(dx)
pushad
push ds
push es mov ebp,esp mov esi,[ebp+*]
movzx eax,word[ebp+*]
mov ebx,[ebp+*]
movzx edx,word[ebp+*] arpl ax,dx
mov ds,ax mov dx,0x1f2
mov al,0x01 ;读一个扇区
out dx,al inc edx ;0-7位
mov eax,esi
out dx,al inc edx ;8-15位
mov al,ah
out dx,al inc edx ;16-23位
shr eax,
out dx,al inc edx ;24-28位,主硬盘,LBA模式
mov al,ah
and al,0x0f
or al,0xe0
out dx,al inc edx
mov al,0x20
out dx,al _wait:
in al,dx
and al,0x88
cmp al,0x08
jne _wait mov dx,0x1f0
mov ecx, _read:
in ax,dx
mov [ebx],ax
add ebx,
loop _read pop es
pop ds
popad
retf ;4个数据
;----------------------------------------------------------------
put_string: ;ebx:偏移地址
pushad
push ds
push es _print:
mov cl,[ebx]
cmp cl,
je _exit
call put_char
inc ebx
jmp _print
_exit:
pop es
pop ds
popad
retf ;段间返回
;--------------------------------------------------------------
put_char: ;cl就是要显示的字符
push ebx
push es
push ds mov dx,0x3d4
mov al,0x0e ;高8位
out dx,al
mov dx,0x3d5
in al,dx
mov ah,al ;先把高8位存起来
mov dx,0x3d4
mov al,0x0f ;低8位
out dx,al
mov dx,0x3d5
in al,dx ;现在ax就是当前光标的位置 _judge:
cmp cl,0x0a
je _set_0x0a
cmp cl,0x0d
je _set_0x0d
_print_visible:
mov bx,ax
mov eax,Print_Segement
mov es,eax
shl bx, ;注意这里一定要把ebx变成原来的两倍,实际位置是光标位置的两倍
mov [es:bx],cl ;注意这里是屏幕!
mov byte[es:bx+],0x07
add bx,
shr bx,
jmp _roll_screen
_set_0x0d: ;回车
mov bl,
div bl
mul bl
mov bx,ax
jmp _set_cursor
_set_0x0a: ;换行
mov bx,ax
add bx,
jmp _roll_screen
_roll_screen:
cmp bx,
jl _set_cursor
mov eax,Print_Segement
mov ds,eax
mov es,eax cld
mov edi,0x00
mov esi,0xa0
mov ecx,
rep movsw
_cls:
mov bx,
mov ecx,
_print_blank:
mov word[es:bx],0x0720
add bx,
loop _print_blank
mov bx, ;别总是忘了光标的位置!
_set_cursor: ;改变后的光标位置在bx上
mov dx,0x3d4
mov al,0x0f ;低8位
out dx,al mov al,bl
mov dx,0x3d5
out dx,al mov dx,0x3d4
mov al,0x0e ;高8位
out dx,al mov al,bh
mov dx,0x3d5
out dx,al pop ds
pop es
pop ebx
ret
;----------------------------------------------------------------
allocate_memory: ;简易内存分配策略
;输入ecx:想要分配的总字节数
;输出ecx:分配的线性基地址
push ds
push eax
push ebx
call Cal_User_Mem mov eax,Core_Data_Segement
mov ds,eax
mov eax,[ram_alloc]
mov edx,eax ;edx暂存一下eax
add eax,ecx cmp eax,edx ;发现新分配的现地址比原来的还小,说明已经溢出
jge _alloc
mov ebx,mem_alloc_fail
call Sys_Routine_Segement:put_string
mov ecx, ;分配为0说明已经分配失败
jmp _exit1
_alloc: mov ebx,eax
and ebx,0xfffffffc
add ebx, ;强行向上取整
test eax,0x00000003
cmovnz eax,ebx
mov ecx,[ram_alloc] ;要返回要分配的初始地址
mov [ram_alloc],eax ;下一次分配的线性基地址 _exit1:
pop ebx
pop eax
pop ds retf
;----------------------------------------------------------------
recycled_memory_and_gdt:
mov eax,[ram_recycled]
sub [ram_alloc],eax
mov dword[ram_recycled], ;因为我们还没学到多任务,先这样简单地清零 sgdt [pgdt_base_tmp]
sub word[pgdt_base_tmp], ;应用程序的LDT,TSS
lgdt [pgdt_base_tmp] ;重新加载内核
retf
;----------------------------------------------------------------
Cal_User_Mem: ;输入ecx:应用程序用到的内存(字节)
add [ram_recycled],ecx
ret
;----------------------------------------------------------------
Make_Seg_Descriptor: ;构造段描述符
;输入:
;eax:线性基地址
;ebx:段界限
;ecx:属性
;输出:
;eax:段描述符低32位
;edx:段描述符高32位
mov edx,eax
and edx,0xffff0000
rol edx,
bswap edx
or edx,ecx shl eax,
or ax,bx
and ebx,0x000f0000
or edx,ebx
retf
;----------------------------------------------------------------
Make_Gate_Descriptor: ;构造门描述符
;输入:
;eax:段内偏移地址
;bx: 段的选择子
;cx: 段的属性
;输出:
;eax:门描述符低32位
;edx:门描述符高32位
push ebx
push ecx mov edx,eax
and edx,0xffff0000 ;要高16位
or dx,cx shl ebx,
and eax,0x0000ffff
or eax,ebx pop ecx
pop ebx retf
;----------------------------------------------------------------
Set_New_GDT: ;装载新的全局描述符
;输入:edx:eax描述符
;输出:cx选择子
push ds
push es mov ebx,Core_Data_Segement
mov ds,ebx mov ebx,All_4GB_Segment
mov es,ebx sgdt [pgdt_base_tmp] movzx ebx,word[pgdt_base_tmp]
inc bx ;注意这里要一定是inc bx而不是inc ebx,因为gdt段界限初始化是0xffff的
;要用到回绕特性
add ebx,[pgdt_base_tmp+0x02] ;得到pgdt的线性基地址 mov [es:ebx],eax
mov [es:ebx+0x04],edx ;装载新的gdt符
;装载描述符要装载到实际位置上 add word[pgdt_base_tmp], ;给gdt的段界限加上8(字节) lgdt [pgdt_base_tmp] ;加载gdt到gdtr的位置和实际表的位置无关 mov ax,[pgdt_base_tmp] ;得到段界限
xor dx,dx
mov bx, ;得到gdt大小
div bx
mov cx,ax
shl cx, ;得到选择子,ti=0(全局描述符),rpl=0(申请特权0级) pop es
pop ds
retf
;----------------------------------------------------------------
Set_New_LDT_To_TCB: ;装载新的局部描述符
;输入:edx:eax描述符
; : ebx:TCB线性基地址
;输出:cx选择子 push edi
push eax
push ebx
push edx
push ds mov ecx,All_4GB_Segment
mov ds,ecx mov edi,[ebx+0x0c] ;LDT的线性基地址
movzx ecx,word[ebx+0x0a]
inc cx ;得到实际的LDT的大小(界限还要-1) mov [edi+ecx+0x00],eax
mov [edi+ecx+0x04],edx add cx,
dec cx mov [ebx+0x0a],cx mov ax,cx
xor dx,dx
mov cx,
div cx shl ax,
mov cx,ax
or cx,0x0004 ;LDT,第三位TI位一定是1 pop ds
pop edx
pop ebx
pop eax
pop edi
retf
;----------------------------------------------------------------
PrintDword: ;显示edx内容的一个调试函数
pushad
push ds mov eax,Core_Data_Segement
mov ds,eax mov ebx,bin_hex
mov ecx, _query:
rol edx,
mov eax,edx
and eax,0x0000000f
xlat push ecx
mov cl,al
call put_char
pop ecx loop _query pop ds
popad retf
;=========================================================================
;===========================内核数据区====================================
;=========================================================================
SECTION Core_Data align= vstart=
;-------------------------------------------------------------------------------
pgdt_base_tmp: dw ;这一章的用户程序都是从GDT中加载的
dd ram_alloc: dd 0x00100000 ;下次分配内存时的起始地址(直接暴力从0x00100000开始分配了)
ram_recycled dd ;这里储存程序实际用的大小
salt:
salt_1: db '@Printf' ;@Printf函数(公用例程)
times -($-salt_1) db
dd put_string
dw Sys_Routine_Segement
dw ;参数个数 salt_2: db '@ReadHarddisk' ;@ReadHarddisk函数(公用例程)
times -($-salt_2) db
dd ReadHarddisk
dw Sys_Routine_Segement
dw ;参数个数 salt_3: db '@PrintDwordAsHexString' ;@PrintDwordAsHexString函数(公用例程)
times -($-salt_3) db
dd PrintDword
dw Sys_Routine_Segement
dw ;参数个数 salt_length: equ $-salt_3
salt_items_sum equ ($-salt)/salt_length ;得到项目总数 salt_tp: dw ;任务门,专门拿来给程序切换到全局空间的 message_1 db ' If you seen this message,that means we '
db 'are now in protect mode,and the system '
db 'core is loaded,and the video display '
db 'routine works perfectly.',0x0d,0x0a, message_2 db ' Loading user program...', do_status db 'Done.',0x0d,0x0a, message_3 db 0x0d,0x0a,0x0d,0x0a,0x0d,0x0a
db ' User program terminated,control returned.'
db 0x0d,0x0a,0x0d,0x0a,
message_4 db ' We have been backed to kernel.',0x0d,0x0a,
message_5 db ' The GDT and memory have benn recycled.',
message_6 db ' From the system wide gate:',0x0d,0x0a,
message_7 db ' Setting the gate discriptor...',
message_In_Gate db ' Hi!My name is Philip:',0x0d,0x0a, bin_hex db '0123456789ABCDEF'
;put_hex_dword子过程用的查找表
core_buf times db ;内核用的缓冲区(2049个字节(2MB)) esp_pointer dd ;内核用来临时保存自己的栈指针 cpu_brnd0 db 0x0d,0x0a,' ',
cpu_brand times db
cpu_brnd1 db 0x0d,0x0a,0x0d,0x0a,
mem_alloc_fail db 'The Program is too large to load'
core_ss dw
core_sp dd
;程序管理器的任务信息
prgman_tss dd ;程序管理器的TSS基地址
dw ;程序管理器的TSS描述符选择子 prgman_msg1 db 0x0d,0x0a
db '[PROGRAM MANAGER]: Hello! I am Program Manager,'
db 'run at CPL=0.Now,create user task and switch '
db 'to it by the CALL instruction...',0x0d,0x0a, prgman_msg2 db 0x0d,0x0a
db '[PROGRAM MANAGER]: I am glad to regain control.'
db 'Now,create another user task and switch to '
db 'it by the JMP instruction...',0x0d,0x0a, prgman_msg3 db 0x0d,0x0a
db '[PROGRAM MANAGER]: I am gain control again,', core_msg0 db 0x0d,0x0a
db '[SYSTEM CORE]: Uh...This task initiated with '
db 'CALL instruction or an exeception/ interrupt,'
db 'should use IRETD instruction to switch back...'
db 0x0d,0x0a, core_msg1 db 0x0d,0x0a
db '[SYSTEM CORE]: Uh...This task initiated with '
db 'JMP instruction, should switch to Program '
db 'Manager directly by the JMP instruction...'
db 0x0d,0x0a,
core_stop db 'HALT...', tcb_chain dd ;任务控制块链头指针
;=========================================================================
;===========================内核代码区====================================
;=========================================================================
SECTION Core_Code align= vstart=
;---------------------------------------------------------------------
append_to_tcb: ;写入新的TCB链
;输入:ecx新的TCB线性基地址
pushad push ds
push es mov eax,All_4GB_Segment
mov es,eax mov eax,Core_Data_Segement
mov ds,eax mov eax,[tcb_chain]
cmp eax,0x00
je _notcb _search_tcb:
mov edx,[tcb_chain+0x00]
mov eax,[es:edx]
cmp eax,0x00
jne _search_tcb mov [es:edx+0x00],ecx
jmp _out_tcb_search _notcb:
mov [tcb_chain],ecx _out_tcb_search:
pop es
pop ds popad
ret
;---------------------------------------------------------------------
load_program: ;输入push1:逻辑扇区号
; push2: 线性基地址
pushad
push ds
push es mov ebp,esp ;别忘了把参数传给ebp mov eax,Core_Data_Segement
mov ds,eax ;切换到内核数据段 mov eax,All_4GB_Segment
mov es,eax mov edi,[ebp+*] ;获取tcb的线性基地址,别忘了调用相对近调用还要有1个push mov ecx,
call Sys_Routine_Segement:allocate_memory
mov [es:edi+0x0c],ecx
mov word[es:edi+0x0a],0xffff ;初始化LDT界限位0xffff mov esi,[ebp+*] ;esi必须是逻辑扇区号
mov ebx,core_buf ;ebx要在内核数据缓冲区(先读取头部在缓冲区,esi已经是有扇区号了) push esi
push ds
push ebx
push cs
call Sys_Routine_Segement:ReadHarddisk mov eax,[core_buf] ;读取用户程序长度 mov ebx,eax
and ebx,0xfffffe00 ;清空低9位(强制对齐512)
add ebx,
test eax,0x000001ff
cmovnz eax,ebx ;低9位不为0则使用向上取整的结果 mov ecx,eax ;eax是整个程序的向上取整的大小
call Sys_Routine_Segement:allocate_memory
;先分配内存给整个程序,再分配内存给栈区
mov ebx,ecx
mov [es:edi+0x06],ecx ;tcb 0x06:程序加载基地址 xor edx,edx
mov ecx, ;千万不要改掉ebx
div ecx
mov ecx,eax mov eax,All_4GB_Segment ;切换到4GB段区域(平坦模式)
mov ds,eax _loop_read:
push esi
push ds
push ebx
push cs
call Sys_Routine_Segement:ReadHarddisk ;esi还是User_Program_Address
inc esi
add ebx,
loop _loop_read mov esi,edi ;esi: TCB的线性基地址
mov edi,[es:esi+0x06] ;程序加载的线性基地址 ;建立头部描述符
mov eax,edi
mov ebx,[edi+0x04]
dec ebx ;段界限
mov ecx,0x0040f200
call Sys_Routine_Segement:Make_Seg_Descriptor
mov ebx,esi
call Sys_Routine_Segement:Set_New_LDT_To_TCB
or cx,0x0003 ;特权级3
mov [es:esi+0x44],cx ;记得要登记头部的选择子
mov [edi+0x04],cx ;建立代码段描述符
mov eax,edi
add eax,[edi+0x14]
mov ebx,[edi+0x18]
dec ebx
mov ecx,0x0040f800
call Sys_Routine_Segement:Make_Seg_Descriptor
mov ebx,esi
call Sys_Routine_Segement:Set_New_LDT_To_TCB
or cx,0x0003
mov [edi+0x14],cx ;建立数据段描述符
mov eax,edi
add eax,[edi+0x1c]
mov ebx,[edi+0x20]
dec ebx
mov ecx,0x0040f200
call Sys_Routine_Segement:Make_Seg_Descriptor
mov ebx,esi
call Sys_Routine_Segement:Set_New_LDT_To_TCB
or cx,0x0003
mov [edi+0x1c],cx ;建立栈段描述符
mov ecx,[edi+0x0c]
mov ebx,0x000fffff
sub ebx,ecx
mov eax, ;4KB粒度
mul ecx
mov ecx,eax
call Sys_Routine_Segement:allocate_memory
mov eax,ecx ;eax是栈段的线性基地址
mov ecx,0x00c0f600
call Sys_Routine_Segement:Make_Seg_Descriptor
mov ebx,esi
call Sys_Routine_Segement:Set_New_LDT_To_TCB
or cx,0x0003
mov [edi+0x08],cx ;现在开始重定位API符号表
;---------------------------------------------------------------------
mov eax,All_4GB_Segment ;因为这个时候用户头部在LDT,而LDT还没有被加载,只能通过4GB空间访问
mov es,eax
mov eax,Core_Data_Segement
mov ds,eax cld
mov ecx,[es:edi+0x24] ;得到用户程序符号表的条数
add edi,0x28 ;用户符号表的偏移地址是0x28 _loop_U_SALT:
push edi
push ecx mov ecx,salt_items_sum
mov esi,salt _loop_C_SALT:
push edi
push esi
push ecx mov ecx, ;比较256个字节
repe cmpsd
jne _re_match ;如果成功匹配,那么esi和edi刚好会在数据区之后的 mov eax,[esi] ;偏移地址
mov [es:edi-],eax ;把偏移地址填入用户程序的符号区
mov ax,[esi+0x04] ;段的选择子 or ax,0x0002 ;把RPL改为3,代表(内核)赋予应用程序以特权级3
mov [es:edi-],ax ;把段的选择子填入用户程序的段选择区 _re_match:
pop ecx
pop esi
add esi,salt_length
pop edi
loop _loop_C_SALT pop ecx
pop edi
add edi,
loop _loop_U_SALT
;---------------------------------------------------------------------
;----------------------填入临时中转任务门选择子-----------------------
mov edi,[ebp+*]
mov edi,[es:edi+0x06] ;从TCB线性基地址中获得用户程序加载的基地址 mov ax,[salt_tp]
mov [es:edi+0x24+],ax ;填充任务门选择子
;---------------------------------------------------------------------
mov esi,[ebp+*] ;重新获得TCB的线性基地址 ;现在设置所有的特权级栈段,并且把特权级栈段放到TCB中(为了等一下设置TSS)
;设置TSS特权0级栈段(暂存在TCB中)
mov ecx,Switch_Stack_Size
mov eax,ecx
mov [es:esi+0x1a],ecx
shr dword[es:esi+0x1a], ;相当于除以4096
call Sys_Routine_Segement:allocate_memory
add eax,ecx ;得到最高地址
mov [es:esi+0x1e],eax ;登记线性基地址
mov ebx,0x000fffff
sub ebx,[es:esi+0x1a]
mov ecx,0x00c09600 ;特权级0
call Sys_Routine_Segement:Make_Seg_Descriptor
mov ebx,esi
call Sys_Routine_Segement:Set_New_LDT_To_TCB
or cx,0x0000 ;RPL为0
mov [es:esi+0x22],cx
mov dword[es:esi+0x24], ;设置TSS特权1级栈段(暂存在TCB中)
mov ecx,Switch_Stack_Size
mov eax,ecx
mov [es:esi+0x28],ecx
shr dword[es:esi+0x28], ;相当于除以4096
call Sys_Routine_Segement:allocate_memory
add eax,ecx ;得到最高地址
mov [es:esi+0x2c],eax ;登记线性基地址
mov ebx,0x000fffff
sub ebx,[es:esi+0x28]
mov ecx,0x00c0b600 ;特权级1
call Sys_Routine_Segement:Make_Seg_Descriptor
mov ebx,esi
call Sys_Routine_Segement:Set_New_LDT_To_TCB
or cx,0x0001 ;RPL为1
mov [es:esi+0x30],cx
mov dword[es:esi+0x32], ;设置TSS特权2级栈段(暂存在TCB中)
mov ecx,Switch_Stack_Size
mov eax,ecx
mov [es:esi+0x36],ecx
shr dword[es:esi+0x36], ;相当于除以4096
call Sys_Routine_Segement:allocate_memory
add eax,ecx ;得到最高地址
mov [es:esi+0x3a],eax ;登记线性基地址
mov ebx,0x000fffff
sub ebx,[es:esi+0x36]
mov ecx,0x00c0d600 ;特权级2
call Sys_Routine_Segement:Make_Seg_Descriptor
mov ebx,esi
call Sys_Routine_Segement:Set_New_LDT_To_TCB
or cx,0x0002 ;RPL为2
mov [es:esi+0x3e],cx
mov dword[es:esi+0x40], ;在GDT中存入LDT信息
mov eax,[es:esi+0x0c]
movzx ebx,word[es:esi+0x0a]
mov ecx,0x00408200 ;LDT描述符,特权级0级
call Sys_Routine_Segement:Make_Seg_Descriptor
call Sys_Routine_Segement:Set_New_GDT
mov [es:esi+0x10],cx ;在TCB放入LDT选择子 ;在TCB中登记TSS的信息
mov ecx, ;创建一个最小尺寸的TSS
mov [es:esi+0x12],cx
dec word[es:esi+0x12] ;记得-1,要的是段界限
call Sys_Routine_Segement:allocate_memory
mov [es:esi+0x14],ecx ;TSS基地址 ;构建TSS信息表
mov dword[es:ecx+0x00], ;没有前一个任务 mov edx,[es:esi+0x24] ;0栈段
mov [es:ecx+],edx
mov dx,[es:esi+0x22]
mov [es:ecx+],dx mov edx,[es:esi+0x32] ;1栈段
mov [es:ecx+],edx
mov dx,[es:esi+0x30]
mov [es:ecx+],dx mov edx,[es:esi+0x40] ;2栈段
mov [es:ecx+],edx
mov dx,[es:esi+0x3e]
mov [es:ecx+],dx mov edx,[es:esi+0x10] ;LDT选择子
mov [es:ecx+],edx mov dx,[es:esi+0x12] ;I/O偏移
mov [es:ecx+],dx ;是102不是104 mov word[es:ecx+], ;T=0 mov edi,[es:esi+0x06] ;用户程序的线性基地址 mov edx,[es:edi+0x10] ;EIP
mov [es:ecx+],edx mov edx,[es:edi+0x14] ;CS
mov [es:ecx+],dx mov edx,[es:edi+0x08] ;SS
mov [es:ecx+],dx mov edx,[es:edi+0x04] ;DS(是指向用户头部,而不是用户程序的数据区)
mov [es:ecx+],dx mov word[es:ecx+], ;ES
mov word[es:ecx+], ;FS
mov word[es:ecx+], ;GS pushfd
pop edx
mov [es:ecx+],edx ;EFLAGS ;在GDT中存入TSS信息
mov eax,[es:esi+0x14]
movzx ebx,word[es:esi+0x12]
mov ecx,0x00408900
call Sys_Routine_Segement:Make_Seg_Descriptor
call Sys_Routine_Segement:Set_New_GDT
mov [es:esi+0x18],cx pop es
pop ds
popad
ret ;相当于是stdcall,过程清栈
;---------------------------------------------------------------------
start:
mov eax,Core_Data_Segement
mov ds,eax mov ebx,message_1
call Sys_Routine_Segement:put_string mov eax,
cpuid
cmp eax,0x80000004 ;判断是否有0x80000002-0x80000004功能
jl _@load ;显示处理器品牌信息,从80486的后期版本开始引入
mov eax,0x80000002
cpuid
mov [cpu_brand+0x00],eax
mov [cpu_brand+0x04],ebx
mov [cpu_brand+0x08],ecx
mov [cpu_brand+0x0c],edx mov eax,0x80000003
cpuid
mov [cpu_brand+0x10],eax
mov [cpu_brand+0x14],ebx
mov [cpu_brand+0x18],ecx
mov [cpu_brand+0x1c],edx mov eax,0x80000004
cpuid
mov [cpu_brand+0x20],eax
mov [cpu_brand+0x24],ebx
mov [cpu_brand+0x28],ecx
mov [cpu_brand+0x2c],edx mov ebx,cpu_brnd0
call Sys_Routine_Segement:put_string
mov ebx,cpu_brand
call Sys_Routine_Segement:put_string
mov ebx,cpu_brnd1
call Sys_Routine_Segement:put_string _@load:
mov ebx,message_7
call Sys_Routine_Segement:put_string
;----------------------------安装门------------------------------------
mov edi,salt
mov ecx,salt_items_sum
_set_gate:
push ecx
mov eax,[edi+]
mov bx,[edi+] ;选择子
mov cx,0xec00 ;门是特权级是3的门,那么任何程序都能调用
or cx,[edi+] ;加上参数个数 call Sys_Routine_Segement:Make_Gate_Descriptor
call Sys_Routine_Segement:Set_New_GDT
mov [edi+],cx ;回填选择子
add edi,salt_length
pop ecx
loop _set_gate
;----------------------------------------------------------------------
mov ebx,do_status
call far [salt_1+]
mov ebx,message_6
call far [salt_1+]
mov ebx,message_In_Gate
call far [salt_1+] ;调用门显示字符信息(忽略偏移地址(前4字节)) mov ebx,message_4
call far [salt_1+]
mov ebx,message_2
call far [salt_1+] mov eax,All_4GB_Segment
mov es,eax mov ecx,
call Sys_Routine_Segement:allocate_memory
mov [prgman_tss],ecx ;保留线性基地址 mov word[es:ecx+], ;TI=0
mov word[es:ecx+], ;任务管理器不需要I/O映射,要大于等于界限
mov word[es:ecx+], ;任务允许没有自己的LDT
mov dword[es:ecx+], ;设置CR3
mov word[es:ecx+], ;没有前一个任务 mov eax,ecx
mov ebx, ;TSS段界限
mov ecx,0x00408900
call Sys_Routine_Segement:Make_Seg_Descriptor
call Sys_Routine_Segement:Set_New_GDT
mov [prgman_tss+0x04],cx ltr cx ;启动任务
;------------------安装用户管理程序的临时返回任务门--------------------
mov eax,0x0000 ;TSS不需要偏移地址
mov bx,[prgman_tss+0x04] ;TSS的选择子
mov cx,0xe500 call Sys_Routine_Segement:Make_Gate_Descriptor
call Sys_Routine_Segement:Set_New_GDT
mov [salt_tp],cx ;填入临时中转任务门选择子,注意不需要加260了
;----------------------------------------------------------------------
mov ebx,prgman_msg1
call Sys_Routine_Segement:put_string ;----------------------用户管理程序----------------------------
mov ecx,0x46 ;TCB链大小
call Sys_Routine_Segement:allocate_memory
call append_to_tcb push User_Program_AddressA
push ecx
call load_program jmp far [es:ecx+0x14] ;初次打开程序A,一定要用跳转方式打开,不能call,否则不能返回了
;------------------------------------------------
mov ebx,prgman_msg2
call Sys_Routine_Segement:put_string mov ecx,0x46 ;TCB链大小
call Sys_Routine_Segement:allocate_memory
call append_to_tcb push User_Program_AddressB
push ecx
call load_program
jmp far [es:ecx+0x14] ;初次打开程序B,一定要用跳转方式打开,不能call,否则不能返回了
;------------------------------------------------
mov eax,Core_Data_Segement
mov ds,eax
mov eax,All_4GB_Segment
mov es,eax mov ebx,prgman_msg3
call Sys_Routine_Segement:put_string mov ecx,[tcb_chain] ;任务A
call far [es:ecx+0x14]
;------------------------------------------------
mov eax,Core_Data_Segement
mov ds,eax
mov eax,All_4GB_Segment
mov es,eax mov ebx,prgman_msg3
call Sys_Routine_Segement:put_string mov ecx,[tcb_chain]
mov ecx,[es:ecx+0x00] ;任务B
call far [es:ecx+0x14]
;------------------------------------------------
mov eax,Core_Data_Segement
mov ds,eax mov ebx,prgman_msg3
call Sys_Routine_Segement:put_string
mov ebx,core_stop
call Sys_Routine_Segement:put_string cli
hlt
;----------------------------------------------------------------------
;=========================================================================
SECTION core_trail
;----------------------------------------------------------------
Program_end:
;==============================用户程序B=======================================
SECTION header vstart= program_length dd program_end ;程序总长度#0x00 head_len dd header_end ;程序头部的长度#0x04 stack_seg dd ;用于接收堆栈段选择子#0x08
stack_len dd ;程序建议的堆栈大小#0x0c
;以4KB为单位 prgentry dd start ;程序入口#0x10
code_seg dd section.code.start ;代码段位置#0x14
code_len dd code_end ;代码段长度#0x18 data_seg dd section.data.start ;数据段位置#0x1c
data_len dd data_end ;数据段长度#0x20
;-------------------------------------------------------------------------------
;符号地址检索表
salt_items dd (u_salt_end-salt)/ ;#0x24 salt: ;#0x28
Printf: db '@Printf'
times -($-Printf) db TerminateProgram:db '@TerminateProgram'
times -($-TerminateProgram) db ReadHarddisk: db '@ReadHarddisk'
times -($-ReadHarddisk) db u_salt_end:
TpBack: dd ;任务门的偏移地址没用,直接填充就可以了
dw ;任务门的选择子#0x24+776
header_end:
;===============================================================================
SECTION data align= vstart= message_1 db 0x0d,0x0a
db '[USER TASKB]: Hi! I am task B',0x0d,0x0a, message_2 db 0x0d,0x0a
db '[USER TASKB]: Hi! I am task B, I have been backed',0x0d,0x0a,
data_end: ;===============================================================================
[bits ]
;===============================================================================
SECTION code align= vstart=
start:
mov eax,ds
mov fs,eax mov eax,[data_seg]
mov ds,eax mov ebx,message_1
call far [fs:Printf]
jmp far [fs:TpBack] ;任务切换 mov ebx,message_2
call far [fs:Printf]
iretd
code_end: ;===============================================================================
SECTION trail
;-------------------------------------------------------------------------------
program_end:
;==============================用户程序A=======================================
SECTION header vstart= program_length dd program_end ;程序总长度#0x00 head_len dd header_end ;程序头部的长度#0x04 stack_seg dd ;用于接收堆栈段选择子#0x08
stack_len dd ;程序建议的堆栈大小#0x0c
;以4KB为单位 prgentry dd start ;程序入口#0x10
code_seg dd section.code.start ;代码段位置#0x14
code_len dd code_end ;代码段长度#0x18 data_seg dd section.data.start ;数据段位置#0x1c
data_len dd data_end ;数据段长度#0x20
;-------------------------------------------------------------------------------
;符号地址检索表
salt_items dd (u_salt_end-salt)/ ;#0x24 salt: ;#0x28
Printf: db '@Printf'
times -($-Printf) db TerminateProgram:db '@TerminateProgram'
times -($-TerminateProgram) db ReadHarddisk: db '@ReadHarddisk'
times -($-ReadHarddisk) db u_salt_end:
TpBack: dd ;任务门的偏移地址没用,直接填充就可以了
dw ;任务门的选择子#0x24+776
header_end:
;===============================================================================
SECTION data align= vstart= message_1 db 0x0d,0x0a
db '[USER TASKA]: Hi! I am task A',0x0d,0x0a, message_2 db 0x0d,0x0a
db '[USER TASKA]: Hi! I am task A, I have been backed',0x0d,0x0a,
data_end:
;===============================================================================
[bits ]
;===============================================================================
SECTION code align= vstart=
start:
mov eax,ds
mov fs,eax mov eax,[data_seg]
mov ds,eax mov ebx,message_1
call far [fs:Printf]
jmp far [fs:TpBack] ;任务切换 mov ebx,message_2
call far [fs:Printf]
iretd
code_end: ;===============================================================================
SECTION trail
;-------------------------------------------------------------------------------
program_end:
程序上我没有回收内存,我是觉得我自己就是做个简单的也不是很好,索性不做了。