第17章 协同式任务切换
在多任务系统中,每个任务都有各自的局部描述符表(LDT)和任务状态段(TSS)。
从任务切换的时机来讲,有两种基本的策略:
- 协同式:从一个任务切换到另一个任务。需要当前任务主动地请求暂时放弃执行权,或者在通过调用门请求操作系统服务时,由操作系统“趁机”将控制转移到另一个任务。这种方式依赖于每个任务的“自律”性,当一个任务失控时,其他任务可能得不到执行的机会。
- 抢占式:在这种方式下,可以安装一个定时器中断,并在中断服务程序中实施任务切换。硬件中断信号总会定时出现,不管处理器当时在做什么,中断都会适时地发生,而任务切换也就能够顺利进行。在这种情况下,每个任务都能获得平等的执行机会。而且,即使一个任务失控,也不会导致其他任务没有机会执行。
本章主要介绍协同式的任务切换。
本章代码清单
本章的代码主要实现的功能:
- 将内核也设计为一个任务;
- 内核任务和用户任务之间通过协同式进行切换;
最后在屏幕上打印如下信息:
任务切换前的设置
所有任务共享一个全局空间,由内核或操作系统提供,包含了系统服务程序和数据;同时每个任务还有自己的局部空间,局部空间是包含一个任务区别于其他任务的私有代码和数据。
虚假调用门返回:一开始,处理器是在任务的全局空间执行的,当前特权级别是0,然后,我们通过一个虚假的调用门返回,使处理器回到任务的局部空间执行,当前特权级别降为3。
书中描述符了通过虚假调用门返回切换到用户任务的缺点,提出了解决方案。
如何优化:采用创建0特权级别的操作系统(内核)任务,然后切换到这个任务。之后在各个任务之间进行切换。
开始优化:核心思路就是要创建0特权级别的操作系统(内核)任务。
1)主引导程序:主引导程序和第15章用的是一样的。
2)内核程序准备:内核程序设置DS为内核数据段,ES设置为4G字节内存段,显示处理器品牌信息,安装调用门。这些和上一章节一致。
3)内核程序创建内核任务:内核本身要作为一个独立的任务而存在,我们称之为内核任务,而且我们要在内核任务和普通的用户任务之间来回切换。内核任务的另一个重要工作是创建其他任务,管理它们,所以称作任务管理器,或者叫程序管理器。
创建内核任务TCB:和创建普通任务一样,内核任务也有自己的任务控制块TCB,所以我们要为内核任务创建一个任务控制块TCB,并将它加入TCB链表。
mov ecx,0x46 ;TCB任务控制块,大小是0x46字节
call sys_routine_seg_sel:allocate_memory ;分配内存
call append_to_tcb_link ;将此TCB添加到TCB链中
mov esi,ecx ;exc保存了TCB的线性地址
内核任务的TCB分配的内存和用户程序分配的内存地址都是从0x00100000开始,感觉内核任务如果可以和用户程序区分开会更好。
创建内核任务TSS:对于处理器来说TCB非必须,TSS是必须要有的。下面程序实现分配TSS内存空间,设置TSS内各项。
;为内核任务的TSS分配内存空间
mov ecx,104 ;为该任务的TSS分配104字节内存
call sys_routine_seg_sel:allocate_memory ;执行分配内存的操作
mov [es:esi+0x14],ecx ;在内核TCB中保存TSS基地址
;在程序管理器的TSS中设置必要的项目
mov word [es:ecx+96],0 ;没有LDT,处理器允许没有LDT的任务
mov word [es:ecx+102],103 ;没有I/O位图。0特权级事实上不需要。
mov word [es:ecx+0],0 ;反向链=0,这里没有实际意义。
mov dword [es:ecx+28],0 ;登记CR3(PDBR),没有分页,暂时不管
mov word [es:ecx+100],0 ;T=0,调试标志位
;不需要0、1、2特权级堆栈。0特级不
;会向低特权级转移控制。
;任务的局部描述符表LDT是可选的,有也行,没有也可以。
; 内核任务是黏附在内核之上的,没有自己独立的段,所以不需要LDT。
安装TSS段描述符:创建TSS段描述符,并生成一个选择子,再将返回的TSS选择子登记在任务控制块TCB中偏移为0x18的地方,再将任务的状态设置为忙。
;创建TSS描述符,并安装到GDT中
mov eax,ecx ;TSS的起始线性地址
mov ebx,103 ;段长度(界限)
mov ecx,0x00008900 ;TSS描述符,特权级0
call sys_routine_seg_sel:make_seg_descriptor ;指向创建描述符的操作
call sys_routine_seg_sel:set_up_gdt_descriptor ;安装GDT中
mov word [es:esi+0x18],cx ;登记TSS选择子到TCB
mov word [es:esi+0x04],0xffff ;任务的状态为“忙”,表示正在执行中。
TSS选择子安装到TR寄存器中:任务寄存器TR中的内容是任务存在的标志,该内容也决定了当前任务是谁。为了表明当前正在任务中执行,所要做的最后一个工作是将当前任务的TSS选择子传送到任务寄存器TR中。
;任务寄存器TR中的内容是任务存在的标志,该内容也决定了当前任务是谁。
;下面的指令为当前正在执行的0特权级任务“程序管理器”后补手续(TSS)。
ltr cx
执行这条指令后,处理器用该选择子访问GDT,找到相对应的TSS描述符,将其B位置“1”,表示该任务正在执行中(或者处于挂起状态)。同时,还要将该描述符传送到寄存器TR的描述符高速缓存器中。
内核任务就名正言顺地成了当前任务,打印一个消息:
;现在可认为“程序管理器”任务正执行中
mov ebx,core_msg1
call sys_routine_seg_sel:put_string
;core_msg1的定义在内核数据段
core_msg1 db 0x0d,0x0a
db '[CORE TASK]: I am running at CPL=0.Now,create '
db 'user task and switch to it.',0x0d,0x0a,0
任务切换的方法
切换的方法有两种:
- 硬件切换:基本没有用了,书中只是大概介绍了一下硬件切换。
- 软件切换:书籍最后一节介绍了软件切换的方法。
硬件切换:有很多种方法:中断、call和jmp指令。
1)中断:第一种任务切换的方法是借助于中断,这也是现代抢占式多任务的基础。
保护模式下,中断向量表不再使用,而是使用中断描述符表。中断描述符表保存中断门、陷阱门和任务门,每个描述符占用8个字节。
1.1)中断门和陷阱门:允许在任务内实施中断处理,转到全局空间去执行一些系统的管理工作,本质上也是任务内的控制转移行为。
1.2)任务门:如果中断号对应的门是任务门,那么就要进行任务切换。
任务门描述符格式:
- TSS选择子:任务门描述符中的主要组成是任务的TSS选择子。任务门用于在中断发生时执行任务切换,而执行任务切换时必须找到新任务的任务状态段(TSS)。所以,任务门应当指向任务的TSS。为了指向任务的TSS,只需要在任务门描述符中给出任务的TSS选择子就可以了。
- P位:任务门描述符中的P位指示该门是否有效,当P位为“0”时,不允许通过此门实施任务切换;
- DPL是任务门描述符的特权级,但是对因中断而发起的任务切换不起作用,处理器不按特权级施加任何保护。
当中断发生时,处理器用中断号乘以8作为索引访问中断描述符表。当它发现这是一个任务门(描述符)时,需要进行任务切换:
- 取出任务门描述符;
- 任务门描述符中取出新任务的TSS选择子;
- 用TSS选择子访问GDT,取出新任务的TSS描述符;
- 在转到新任务执行前,处理器要先把当前任务的状态保存起来;当前任务的TSS是由任务寄存器TR的当前内容指向的,处理器把每个寄存器的“快照”保存到由TR指向的TSS中;
- 处理器访问新任务的TSS,从中恢复各个寄存器的内容,包括通用寄存器、标志寄存器EFLAGS、段寄存器、指令指针寄存器EIP、栈指针寄存器ESP,以及局部描述符表寄存器LDTR等;
- 任务寄存器TR指向新任务的TSS,而处理器旋即开始执行新的任务;
- 一旦新任务开始执行,处理器固件会自动将其TSS描述符的B位置“1”,表示该任务的状态为忙。
1.3)中断返回区分两种类型:当中断发生时,可以执行常规的中断处理过程,也可以进行任务切换。尽管性质不同,但它们都要使用iret指令返回。前者是返回到同一任务内的不同代码段;后者是返回到被中断的那个任务。问题是,处理器如何区分这两种截然不同的返回类型呢?
答案是通过EFLAGS的NT位:
32位处理器的EFLAGS有NT位(位14),意思是嵌套任务标志(Nested Task Flag)。每次中断返回时都会检查这个位。
- 为0:表示是一般的中段过程,按一般的中断返回处理;
- 为1:当前任务中断了别的任务,要返回原先被中断的任务。
任务的嵌套结构图示如下:
2)call和jmp指令:除了因中断引发的任务切换,还可以用远过程调用指令call,或者远跳转指令jmp直接发起任务切换。
在这两种情况下,call和jmp指令的操作数是任务的TSS描述符选择子或任务门。
call 0x0010:0x00000000 ;
;0000_0000_00001_0000,描述符索引号是2
jmp 0x0010:0x00000000
当处理器执行这两条指令时,首先用指令中给出的描述符选择子访问GDT,分析它的描述符类型:
- 如果是一般的代码段描述符,就按普通的段间转移规则执行;
- 如果是调用门,按调用门的规则执行;
- 如果是TSS描述符,或者任务门,则执行任务切换。
一般的代码段描述符、调用门描述符、TSS描述符格式都有,但是处理器是怎么验证的呢?
网上查找了资料,在MIT提供的80386手册上找到一些资料,主是根据S和TYPE进行判断。
常规的代码段和数据段描述符:
TSS描述符格:
任务门、中断门和陷阱门描述符:
下图是LDT描述符:LDT段描述符格式在MIT提供的手册上没有找到,下图是是来自该书籍的说明。
call和jmp指令的区别:
- call:call指令发起的任务切换类似于因中断发起的任务切换,任务执行完成后要切换会上一个任务。
- jmp:jmp指令发起的任务切换,不会形成任务之间的嵌套关系。
任务不可重入:
- 第一种情形,执行任务切换时,新任务不能是当前任务自己。
- 第二种情形,不准切换到嵌套链上的任务,如前面从任务3切换到任务2和任务1上,就会乱套。
用jmp指令发起任务切换的实例
内核任务显示了自己的信息之后,接下来创建一个或多个用户任务。
1)创建第1个用户任务:
1.1)创建任务控制块TCB:先要创建一个任务控制块TCB。
;以下开始创建用户任务
mov ecx,0x46 ;任务控制块0x46字节
call sys_routine_seg_sel:allocate_memory ;分配内存
mov word [es:ecx+0x04],0 ;任务状态:就绪
call append_to_tcb_link ;将此TCB添加到TCB链中
1.2)加载用户程序:加载和重定位用户程序,并将它创建为任务。
push dword 50 ;用户程序位于逻辑50扇区
push ecx ;压入任务控制块起始线性地址
call load_relocate_program
一些不同于上一章的情况:
- 用户程序的头部段中,符号—地址检索表添加了一个新的符号InitTaskSwitch,它用来主动发起任务切换,从当前任务切换到另一个任务。该符号对应到内核新增加的@InitTaskSwitch 符号,对应公共例程段的 initiate_task_switch 。
- 内核称为独立任务,切换到用户程序,会使用到用户程序的TSS,TSS的内容需要设置好。
- 0、1和2特权级的栈段选择子,以及初始的栈指针;
- 标志寄存器EFLAG中的IOPL字段,对任务的执行比较重要
- 指令指针寄存器EIP需要设置为用户任务入口点的段内偏移量;
- 段寄存器GS、FS、DS、SS、ES可以提前设置,或者在用户任务开始执行后再进行初始化也不迟;
- CS必须设置为用户任务入口点的代码段选择子;
- 如果用户任务有自己的LDT,还必须在TSS里填写LDT选择子;
- 如果有I/O许可位映射区,还必须在这里设置映射区的偏移量;
2)创建更多任务:可以创建其他用户任务,可以使用同一个程序来创建很多任务。
;可以创建更多的任务,例如:
;mov ecx,0x46
;call sys_routine_seg_sel:allocate_memory
;mov word [es:ecx+0x04],0 ;任务状态:空闲
;call append_to_tcb_link ;将此TCB添加到TCB链中
;push dword 50 ;用户程序位于逻辑50扇区
;push ecx ;压入任务控制块起始线性地址
;call load_relocate_program
3)切换任务:现在正在执行的任务是内核任务。创建了第一个用户任务后,返回点是标号.do_switch。在这里,内核任务调用例程initiate_task_switch主动发起一个任务切换。
.do_switch:
;主动切换到其它任务,给它们运行的机会
call sys_routine_seg_sel:initiate_task_switch
3.1)initiate_task_switch: 例程initiate_task_switch用来执行任务调度。如果某个任务想把处理器的控制权让给别的任务,自己休息一会儿,它可以调用这个内核例程,这样就可以让别的任务获得执行机会。因为这是主动放弃执行权,任务切换靠的是自觉自律、互相配合,所以叫协同式任务切换。
例程initiate_task_switch位于内核的公共例程段,既不需要传入参数,也不输出任何东西,它只是执行任务调度,其基本的方法是从任务链表中找到下一个状态为空闲的任务,然后切换到这个任务。我们来看一下这个过程是如何实现的。
切换的步骤:这里切换任务的步骤很简单。
- 顺着TCB链表,找到当前正在执行的任务,也就是状态为0xFFFF的任务;
- 继续顺着链表往后寻找,直至找到一个就绪的任务,也就是状态为0的任务;
- 切换任务,旧任务的状态从0xFFFF改为0,将新任务的状态从0改成0xFFFF。
这里有两个特殊情况:
- 如果链表中只有一个任务,那它肯定是状态为忙的任务。此时,无法执行任务切换;
- 另一种情况是,因为我们每次是先找状态为忙的任务(当前任务所对应的链表节点),然后再找就绪任务。如果状态为忙的任务位于链表末端,
那么,我们必须返回链表的头部,从头开始寻找状态为就绪的任务。
分析代码:书中代码已经注释非常详细了,知道原理了,感觉也不难理解,就是跳来跳去的比较麻烦。
initiate_task_switch: ;主动发起任务切换
;输入:无
;输出:无。
pushad
push ds
push es
mov eax,core_data_seg_sel ;es指向内核数据段
mov es,eax
mov eax,mem_0_4_gb_seg_sel ;ds指向4GB数据段
mov ds,eax
mov eax,[es:tcb_chain] ;tcb链表起点
;搜索状态为忙(当前任务)的节点
.b0:
cmp word [eax+0x04],0xffff ;判断是否忙
cmove esi,eax ;找到忙的节点,ESI=节点的线性地址
jz .b1 ;表示任务忙
mov eax,[eax] ;指向下一个TCB节点的线性地址,[eax+0x0]
jmp .b0
;从当前节点继续搜索就绪任务的节点
.b1:
mov ebx,[eax] ;指向下一个TCB节点的线性地址
or ebx,ebx ;判断下一个TCB节点的线性地址是否为0
jz .b2 ;到链表尾部也未发现就绪节点,从头找
cmp word [ebx+0x04],0x0000 ;如果节点存在则判断是否是就绪
cmove edi,ebx ;已找到就绪节点,EDI=节点的线性地址
jz .b3 ;找到就绪节点则跳转到.b3执行
mov eax,ebx ;不是就绪节点,则找下一个
jmp .b1 ;跳转的.b1寻找下一个
.b2:
mov ebx,[es:tcb_chain] ;EBX=链表首节点线性地址
.b20:
cmp word [ebx+0x04],0x0000 ;如果节点存在则判断是否是就绪
cmove edi,ebx ;已找到就绪节点,EDI=节点的线性地址
jz .b3 ;找到就绪节点则跳转到.b3执行
mov ebx,[ebx] ;获取下一个节点的线性地址
or ebx,ebx ;判断线性地址是否为0,到结尾。
jz .return ;链表中已经不存在空闲任务,返回
jmp .b20 ;继续寻找下一个节点;
;就绪任务的节点已经找到,准备切换到该任务
.b3:
not word [esi+0x04] ;将忙状态的节点改为就绪状态的节点
not word [edi+0x04] ;将就绪状态的节点改为忙状态的节点
jmp far [edi+0x14] ;任务切换
;0xFFFF not操作就是 0x0000, 0x0000 not操作就是 0xFFFF
.return:
pop es
pop ds
popad
retf
找到就绪节点后,使用 jmp far 发起切换,这个是硬件任务切换的方式,处理器固件要做很多检查和设置工作。
- 比如检查新任务TSS描述符的P位是否为1(任务状态段TSS是否在内存中),
- TSS描述符中的界限值是否有效;
- B位是否为0(任务不忙)。
4)用户任务执行:切换后,因为用户任务的TSS中CS登记的是用户程序的代码段,登记的EIP是其入口点在代码段内的偏移量,所以第一次切换到用户任务后,就从其入口点开始执行。
4.1)暂存头部段到fs:ds段寄存器后面还有用,需要用fs暂存用户程序头部段。tss中ds存储的是用户任务的头部段,切换的时候ds就为用户任务头部段。
;任务启动时,DS指向头部段,也不需要设置堆栈
mov eax,ds
mov fs,eax ;将头部段放到fs保存起来
4.2)指向自己的数据段:方便后面操作。
mov ax,[data_seg] ;ds指向自己的数据段
mov ds,ax
4.3)计算特权级的字符形式:后台要打印特权级信息,所以要将特权级转换成字符。
mov ax,cs ;段选择器的低2位是当前特权级CPL
and al,0000_0011B ;保留低两位,其他位清0
or al,0x30 ;加上0x30,特权级转成字符
mov [cpl],al ;将特权级字符放到数据段cpl保存起来
4.4)打印特权级信息:
mov ebx,message_1 ;打印消息1,显示自身的特权级
call far [fs:PrintString]
;message_1的格式如下
; cpl在前面被替换成了特权级信息。
message_1 db 0x0d,0x0a
db '[USER TASK]: Hi! nice to meet you,'
db 'I am run at CPL=',
cpl db 0
db '.',0x0d,0x0a,0
4.5)打印我要休息一下的信息:切还任务前,打印一个信息说明一下。
mov ebx,message_2 ;打印我要休息一下的信息
call far [fs:PrintString]
4.6)主动发起任务切换:系统中只有两个任务,一个内核任务,一个用户任务,所以这次切换会切换到内核任务。
call far [fs:InitTaskSwitch] ;主动发起任务切换
5)切换到内核:从当时切换的下一行开始执行。
;就绪任务的节点已经找到,准备切换到该任务
.b3:
not word [esi+0x04] ;将忙状态的节点改为就绪状态的节点
not word [edi+0x04] ;将就绪状态的节点改为忙状态的节点
jmp far [edi+0x14] ;任务切换
;0xFFFF not操作就是 0x0000, 0x0000 not操作就是 0xFFFF
.return:
pop es ;任务切换回来后从这里开始执行。
pop ds
popad
retf
5.1)内核打印一个信息:
.do_switch:
;主动切换到其它任务,给它们运行的机会
call sys_routine_seg_sel:initiate_task_switch
mov ebx,core_msg2 ;切换任务完成后从这里开始执行
call sys_routine_seg_sel:put_string
;core_msg2定义如下:
core_msg2 db 0x0d,0x0a
db '[CORE TASK]: I am working!',0x0d,0x0a,0
5.2)清理终止的任务并回收资源:
;清理已经终止的任务,并回收它们占用的资源
call sys_routine_seg_sel:do_task_clean
;do_task_clean 啥都么有干,直接就是一个retf返回。
5.3)继续切换到准备就绪的任务:搜索TCB链表,看还有没有就绪任务。如果有的话,就切换到那个任务。
mov eax,[tcb_chain]
.find_ready:
cmp word [es:eax+0x04],0x0000 ;还有处于就绪状态的任务?
jz .do_switch ;有,继续执行任务切换
mov eax,[es:eax] ;TCB头部保存了下一个TCB的起始地址
or eax,eax ;还有用户任务吗?
jnz .find_ready ;一直搜索到链表尾部
6)又切换用户程序:系统中有两个任务,当前任务是内核任务,另一个是用户任务,而且用户任务处于就绪状态。所有又会切换到用户任务。
call far [fs:InitTaskSwitch] ;主动发起任务切换
mov ebx,message_3 ;切换回来从从这里开始执行
call far [fs:PrintString]
实际上切换回来后,应该是从 initiate_task_switch 的 jmp far [edi+0x14] 切换指令下一行开始执行,只是后面都是返回的代码,所以就到 call far [fs:InitTaskSwitch] 的下一行代码执行了。
6.1)用户程序打印退出消息:显示我要退出了。
mov ebx,message_3 ;切换回来从从这里开始执行
call far [fs:PrintString]
;message_3定义
message_3 db '[USER TASK]: I am back again.'
db 'Now,I must exit...',0x0d,0x0a,0
6.2)用户程序退出:
call far [fs:TerminateProgram] ;退出,并将控制权返回到核心
TerminateProgram是系统内核提供的任务,例程就是terminate_current_task,实现思路:
- 从TCB链表中找到当前任务,状态设置为0x3333,表示停止。
- 从TCB链表中搜索就绪状态的任务,进行切换。
terminate_current_task: ;终止当前任务
;注意,执行此例程时,当前任务仍在
;运行中。此例程其实也是当前任务的
;一部分
mov eax,core_data_seg_sel ;es指向内核数据段
mov es,eax
mov eax,mem_0_4_gb_seg_sel ;ds指向4GB数据段
mov ds,eax
mov eax,[es:tcb_chain]
;EAX=首节点的线性地址
;搜索状态为忙(当前任务)的节点
.s0:
cmp word [eax+0x04],0xffff ;判断节点是否忙
jz .s1 ;找到忙的节点,EAX=节点的线性地址
mov eax,[eax] ;eax赋值为下一个节点的地址
jmp .s0 ;继续下一个节点的判断
;将状态为忙的节点改成终止状态
.s1:
mov word [eax+0x04],0x3333
;搜索就绪状态的任务
mov ebx,[es:tcb_chain] ;EBX=链表首节点线性地址
.s2:
cmp word [ebx+0x04],0x0000 ;判断节点是否就绪
jz .s3 ;已找到就绪节点,EBX=节点的线性地址
mov ebx,[ebx] ;ebx赋值为下一个节点的地址
jmp .s2 ;继续下一个节点的判断
;就绪任务的节点已经找到,准备切换到该任务
.s3:
not word [ebx+0x04] ;将就绪状态的节点改为忙状态的节点
jmp far [ebx+0x14] ;任务切换
我在想这个地方是不是少了一个retf?
7)又切回内核:因为系统中就内核任务和用户任务,用户任务终止后就返回内核任务。还是从 call far [fs:InitTaskSwitch] 下一行开始执行。
call far [fs:InitTaskSwitch] ;主动发起任务切换
mov ebx,message_3 ;切换回来从从这里开始执行
call far [fs:PrintString]
7.1)再次寻找就绪任务:这次铁定就找不到了。
7.2)停机:如果没有任务,那么就停机。
;已经没有可以切换的任务,停机
mov ebx,core_msg3
call sys_routine_seg_sel:put_string
hlt
处理器在实施任务切换时的操作
处理器有4种方法将控制转移到其他任务:
- jmp 或者 call 到 tss描述符选择子;
- jmp 或者 call到 任务门描述符选择子;
- 异常或者中断,由任务门处理;
- 寄存器EFLAGS的NT位置为1的情况下,执行了一个iret指令。
在任务切换时,处理器执行以下操作:
- 从jmp或call指令的操作数、任务门或者当前任务的TSS任务链接域获取新任务的TSS描述符选择子。
- 检查是否允许从当前任务(旧任务)切换到新任务,主要是特权级的检查。
- 检查新任务的TSS描述符是否已经标记为有效(P=1),并且界限也有效(大于或者等于0x67,即十进制的103)。
- 检查新任务是否可用:
a. 不忙(B=0,对于以call、jmp、异常或者中断发起的任务切换)
b. 忙(B=1,对于以iret发起的任务切换)。 - 检查当前任务(旧任务)和新任务的TSS,以及所有在任务切换时用到的段描述符已经安排到系统内存中。
- jmp或者iret发起,清除当前任务的忙(B)标志;如果是call指令、异常或者中断发起的,忙标志保持原来的置位状态。
- 如果任务切换是由iret指令发起的,处理器建立EFLAGS寄存器的一个临时副本并清除其NT标志;如果是由call指令、jmp指令、异常或者中发起的,副本中的NT标志不变。
- 保存当前(旧)任务的状态到它的TSS中。
- 如果任务切换是由call指令、异常或者中断发起的,处理器把从新任务加载的寄存器EFLAGS的NT标志置位(=1);如果是由iret或者jmp指令发起的,NT标志位的状态对应着从新任务加载的寄存器EFLAGS的NT位。
- 如果任务切换是由call指令、jmp指令、异常或者中断发起的,处理器将新任务TSS描述符中的B位置位;如果是由iret指令发起的,B位保持原先的置位状态不变。
- 用新任务的TSS选择子和TSS描述符加载任务寄存器TR。
- 新任务的TSS状态数据被加载到处理器。
- 与段选择子相对应的描述符在经过验证后也被加载。
- 开始执行新任务。
切换任务时,不同的方式相关的数据变化如下:
程序的编译和运行
- mbr程序还是用第15章的,写入0扇区;
- core内核程序,写入1扇区;
- app用户程序,写入50扇区;
运行结果如下:
完。