一、6.多任务与调用门

任务:由正在运行的内核和用户程序组成

内核的段描述符应放在GDT,用户程序的段描述符放在自己的局部描述符表LDT中

任务状态段:TSS,用于保存该任务的各种寄存器的状态,从而实现任务切换

image-20230803174623639

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

image-20230803170102915

创建任务控制块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

image-20230803173915373

LDT描述符的创建

image-20230803175546446

		;在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描述符的创建

image-20230803175650118

		;在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指令返回

image-20230803180330283

image-20230803180833048

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

image-20230803181405712

image-20230803212545582

image-20230803212743066

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

image-20230803215431863

特权指令:只有特权级为0(最高)的程序才能执行的指令,如lgdt, lldt, ltr, htl...

当前特权级:CPL,正在执行的代码段的特权级,CS选择子的第0、1位保存当前特权级

描述符特权级:DPL,存放在描述符的13-14位

跨段转移/调用时,原则上当前特权级CPL=目标代码段描述符特权级DPL

用户程序特权级是3,当处理器从内核程序跳转用户程序时,是从高特权级段向敌特权级段跳转,不能简单地用jmpcall

低特权级代码段转到高特权级代码段:

  • 描述符C位为1表示是依从代码段,可以由低特权级代码转到该依从代码段执行,转移前后CPL不变
  • 通过调用门,可以从低特权级代码通过calljmp转到高特权级代码执行image-20230804090000357cll转移之后CPL变为目标代码段的DPL,用jmp转移后CPL不变
mov eax, 0x02 
mov ds, eax ;修改段寄存器时需要进行特权级检查,eax选择的段的DPL是否符合转移要求

mov eax, [ebx] ;访问内存的指令不需要检查,因为设置段寄存器时已经检查过了
add [0x2002], eax

特权级检查的时机:

  1. 执行特权级指令
  2. 修改段寄存器

image-20230804092851892

image-20230804093241847

请求特权级调整指令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                          

image-20230804102049104

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

调用门转移过程

  1. call far [salt_1+256]指令中[salt_1+256]指向数据段中一块(双字代码偏移(被忽略)+单字调用门选择子)内存的起始地址。类似call 0x0030:0x0000c000一样,偏移地址被忽略
  2. 调用门选择子赋值DS,转到GDT里的调用门描述符,获得代码段选择子:代码段偏移量
  3. 代码段选择子:代码段偏移量给处理器的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                  ;和头部内 

通过调用门转移控制时的栈切换过程:

image-20230804111247847

image-20230804113055796

创建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

image-20230804133814246

从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

通过模拟调用门返回进入用户程序执行

image-20230804135148469

		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

image-20230804135204040

;用户程序返回
mov eax, ds
mov fs, eax
...
call far [fs:TerminateProgram]
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值