任务:由正在运行的内核和用户程序组成
内核的段描述符应放在GDT,用户程序的段描述符放在自己的局部描述符表LDT中
任务状态段:TSS,用于保存该任务的各种寄存器的状态,从而实现任务切换

任务控制块:TCB,本书为了方便而设立的内存结构,用于存储一个任务的相关信息,TCB间用链式连接,形成链表

创建任务控制块TCB
tcb_chain dd 0 ; 第一个TCB的地址,头指针
mov ecx,0x46
call sys_routine_seg_sel:allocate_memory
call append_to_tcb_link ;将任务控制块追加到TCB链表
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],0 ;当前TCB指针域清零,以指示这是最后一个TCB
mov eax,[tcb_chain] ;访问内核数据段,获取TCB表头指针
or eax,eax ;链表为空?
jz .notcb
.searc:
mov edx,eax ;指向的TCB的偏移地址
mov eax,[es: edx+0x00] ;取指向的TCB的第一个数据(0x00),即他的下一个TCB的起始地址
or eax,eax ;检查是否为0,为0则说明指向的TCB为最后一个
jnz .searc
mov [es: edx+0x00],ecx ;改成新TCB的基地址
jmp .retpc
.notcb:
mov [tcb_chain],ecx ;若为空链表,直接令表头指针指向TCB
.retpc:
pop es
pop ds
pop edx
pop eax
ret
创建任务
push dword 50 ;用户程序位于逻辑50扇区,立即数入栈,根据默认操作尺寸按符号位扩充到适当位数
push ecx ;压入任务控制块起始线性地址
call load_relocate_program
load_relocate_program: ;加载并重定位用户程序
;输入: PUSH 逻辑扇区号
; PUSH 任务控制块基地址
pushad
push ds ;段寄存器压栈,根据默认操作尺寸进行符号位扩充到适当位数
push es
mov ebp,esp ;为访问通过堆栈传递的参数做准备:用ebp做偏移地址时内存寻址使用ss做段寄存器
mov ecx,mem_0_4_gb_seg_sel
mov es,ecx
mov esi,[ebp+11*4] ;从堆栈中取得TCB的基地址
创建LDT
;以下申请创建LDT所需要的内存
mov ecx,160 ;允许安装20个(20*8=160)LDT描述符
call sys_routine_seg_sel:allocate_memory
mov [es:esi+0x0c],ecx ;登记LDT基地址到TCB中
mov word [es:esi+0x0a],0xffff ;登记LDT初始的界限到TCB中 (0-1=0xffff保留16位)
;以下开始加载用户程序
...
;以下判断整个程序有多大
...
mov ecx,eax ;实际需要申请的内存数量
call sys_routine_seg_sel:allocate_memory
mov [es:esi+0x06],ecx ;登记程序加载基地址到TCB中
;计算用户程序所占的总扇区数
...
mov eax,mem_0_4_gb_seg_sel ;切换DS到0-4GB的段
mov ds,eax
mov eax,[ebp+12*4] ;从堆栈中取出起始扇区号
.b1:
call sys_routine_seg_sel:read_hard_disk_0
inc eax
loop .b1 ;循环读,直到读完整个用户程序
mov edi,[es:esi+0x06] ;获得程序加载基地址
;创建头部描述符
...
;安装头部段描述符到LDT中
mov ebx,esi ;TCB的基地址
call fill_descriptor_in_ldt
mov [es:esi+0x44],cx ;登记程序头部段选择子到TCB
mov [edi+0x04],cx ;和头部内
;创建其他描述符
;安装到LDT中
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] ;在TCB中获得LDT基地址
xor ecx,ecx
mov cx,[ebx+0x0a] ;获得LDT界限
inc cx ;LDT的总字节数,即新描述符偏移地址
mov [edi+ecx+0x00],eax ;安装描述符低32位,ds段,LDT基地址+新描述符偏移地址+0
mov [edi+ecx+0x04],edx
add cx,8 ;LDT总字节数加上8字节
dec cx ;得到新的LDT界限值
mov [ebx+0x0a],cx ;更新LDT界限值到TCB
mov ax,cx
xor dx,dx
mov cx,8 ;界限值除8获得描述符编号
div cx
;生成选择子
mov cx,ax
shl cx,3 ;左移3位,并且
or cx,0000_0000_0000_0100B ;使TI位=1,指向LDT,最后使RPL=00
pop ds
pop edi
pop edx
pop eax
ret

LDT描述符的创建

;在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的创建
;创建用户程序的TSS
mov ecx,104 ;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描述符的创建

;在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
带立即数的RET指令返回


处理器通过TR寄存器和LDTR寄存器找到当前任务的TSS和LDT



;ecx存有TCB的线性地址
ltr [ecx+0x18] ;从TCB中加载任务状态段TSS到TR选择器
lldt [ecx+0x10] ;加载LDT

特权指令:只有特权级为0(最高)的程序才能执行的指令,如lgdt, lldt, ltr, htl...
当前特权级:CPL,正在执行的代码段的特权级,CS选择子的第0、1位保存当前特权级
描述符特权级:DPL,存放在描述符的13-14位
跨段转移/调用时,原则上当前特权级CPL=目标代码段描述符特权级DPL
用户程序特权级是3,当处理器从内核程序跳转用户程序时,是从高特权级段向敌特权级段跳转,不能简单地用jmp或call
低特权级代码段转到高特权级代码段:
- 描述符C位为1表示是依从代码段,可以由低特权级代码转到该依从代码段执行,转移前后CPL不变
- 通过调用门,可以从低特权级代码通过
call和jmp转到高特权级代码执行
用cll转移之后CPL变为目标代码段的DPL,用jmp转移后CPL不变
mov eax, 0x02
mov ds, eax ;修改段寄存器时需要进行特权级检查,eax选择的段的DPL是否符合转移要求
mov eax, [ebx] ;访问内存的指令不需要检查,因为设置段寄存器时已经检查过了
add [0x2002], eax
特权级检查的时机:
- 执行特权级指令
- 修改段寄存器


请求特权级调整指令ARPL:arpl r/m16, r16左操作数的RPL数值小于右操作数的RPL,则修改为右操作数的RPL,若大于或等于则不变
用户程序在调用内核过程时,为了能返回用户程序,处理器会把用户程序的CS压栈,内核过程可以取出CS选择子,将其上的RPL填入用户程序请求的数据段选择子
mov ax, [esp+0X10] ;取出进入调用门之前的CS
arpl [esp+0X18], ax ;调整RPL
为内核接口例程创建调用门
salt:
salt_1 db '@PrintString'
times 256-($-salt_1) db 0
dd put_string
dw sys_routine_seg_sel
salt_2 db '@ReadDiskData'
times 256-($-salt_2) db 0
dd read_hard_disk_0
dw sys_routine_seg_sel
salt_3 db '@PrintDwordAsHexString'
times 256-($-salt_3) db 0
dd put_hex_dword
dw sys_routine_seg_sel
salt_4 db '@TerminateProgram'
times 256-($-salt_4) db 0
dd return_point
dw core_code_seg_sel
salt_item_len equ $-salt_4
salt_items equ ($-salt)/salt_item_len
;以下开始安装为整个系统服务的调用门。特权级之间的控制转移必须使用门
mov edi,salt ;C-SALT表的起始位置
mov ecx,salt_items ;C-SALT表的条目数量
.b3:
push ecx
mov eax,[edi+256] ;该条目入口点的32位偏移地址
mov bx,[edi+260] ;该条目入口点的段选择子
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 ;调用门选择子的DPL默认为0
mov [edi+260],cx ;将返回的门描述符选择子回填
add edi,salt_item_len ;指向下一个C-SALT条目
pop ecx
loop .b3
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,16
or eax,ebx ;组装段选择子部分
pop ecx
pop ebx
retf

;对门进行测试
mov ebx,message_2
call far [salt_1+256] ;通过门显示信息(偏移量将被忽略)
调用门转移过程
call far [salt_1+256]指令中[salt_1+256]指向数据段中一块(双字代码偏移(被忽略)+单字调用门选择子)内存的起始地址。类似call 0x0030:0x0000c000一样,偏移地址被忽略- 调用门选择子赋值DS,转到GDT里的调用门描述符,获得代码段选择子:代码段偏移量
- 代码段选择子:代码段偏移量给处理器的CS:EIP,从GDT中找到公共例程段描述符,赋给CS描述符高速缓存器
在加载并重定位用户程序的过程中建立用户程序各段的描述符并安装到LDT中,并设置选择子的特权级:
;建立程序头部段描述符
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 ;和头部内
通过调用门转移控制时的栈切换过程:


创建0~2特权级栈段
mov esi,[ebp+11*4] ;从堆栈中取得TCB的基地址
;创建0特权级堆栈
mov ecx,4096
mov eax,ecx ;为生成堆栈高端地址做准备
mov [es:esi+0x1a],ecx
shr dword [es:esi+0x1a],12 ;登记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 ;登记0特权级堆栈初始ESP到TCB
;创建1特权级堆栈
mov ecx,4096
mov eax,ecx ;为生成堆栈高端地址做准备
mov [es:esi+0x28],ecx
shr [es:esi+0x28],12 ;登记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],0 ;登记1特权级堆栈初始ESP到TCB
;创建2特权级堆栈
mov ecx,4096
mov eax,ecx ;为生成堆栈高端地址做准备
mov [es:esi+0x36],ecx
shr [es:esi+0x36],12 ;登记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],0 ;登记2特权级堆栈初始ESP到TCB

从TCB中取出3个特权级栈段的栈段基地址和栈段选择子,填写到TSS中
;登记基本的TSS表格内容
mov word [es:ecx+0],0 ;反向链=0
mov edx,[es:esi+0x24] ;登记0特权级堆栈初始ESP
mov [es:ecx+4],edx ;到TSS中
mov dx,[es:esi+0x22] ;登记0特权级堆栈段选择子
mov [es:ecx+8],dx ;到TSS中
mov edx,[es:esi+0x32] ;登记1特权级堆栈初始ESP
mov [es:ecx+12],edx ;到TSS中
mov dx,[es:esi+0x30] ;登记1特权级堆栈段选择子
mov [es:ecx+16],dx ;到TSS中
mov edx,[es:esi+0x40] ;登记2特权级堆栈初始ESP
mov [es:ecx+20],edx ;到TSS中
mov dx,[es:esi+0x3e] ;登记2特权级堆栈段选择子
mov [es:ecx+24],dx ;到TSS中
mov dx,[es:esi+0x10] ;登记任务的LDT选择子
mov [es:ecx+96],dx ;到TSS中
mov dx,[es:esi+0x12] ;登记任务的I/O位图偏移
mov [es:ecx+102],dx ;到TSS中
mov word [es:ecx+100],0 ;T=0
通过模拟调用门返回进入用户程序执行

ltr [ecx+0x18] ;加载任务状态段
lldt [ecx+0x10] ;加载LDT
;进入任务,但此时在任务的公共部分(内核)
mov eax,[ecx+0x44]
mov ds,eax ;切换到用户程序头部段
;以下假装是从调用门返回。摹仿处理器压入返回参数
push dword [0x08] ;调用前的堆栈段选择子
push dword 0 ;调用前的esp
push dword [0x14] ;调用前的代码段选择子
push dword [0x10] ;调用前的eip
retf

;用户程序返回
mov eax, ds
mov fs, eax
...
call far [fs:TerminateProgram]
1398

被折叠的 条评论
为什么被折叠?



