29、协同式任务切换

01、任务和任务切换概述

多任务的系统中,每个任务都有自己的任务状态段TSS和局部描述符表LDT,当前任务是由任务寄存器TR指示,指向当前任务的任务状态段TSS、局部描述符表LDTR也指向当前任务的局部描述符表LDT
在这里插入图片描述
多任务系统是指可以同时执行两个或以上任务的系统,即使一个任务没有执行完也能执行下一个任务,任务切换时TRLDTR也跟着切换到新的任务中。

任务切换方式:
1、协同式任务切换:需要当前任务主动请求暂时放弃执行权、或者在通过调用门请求操作系统服务时由操作系统乘机转移到另一个任务中。此种方式依赖于任务的自律性,当一个任务失控时其他任务可能得不到控制的机会。
2、抢占式任务切换:这种方式可以安装一个定时器中断,在中断信号产生的时候实施任务切换。硬件中断会定时发生,不管处理器在做什么,中断到来时任务一定会切换成功。这种情况下任务都会得到平等的执行机会,当一个任务失控时,也不会导致其他任务没有机会执行。

多任务系统:
在这里插入图片描述
任务的组成是灵活的,不一定由不同的特权级组成、也不一定是内核和用户程序组成。本章将创建3个任务:

  • 1、任务1单纯由内核组成,0特权级。内核除了是一个单独的任务,同时也是其他任务的全局部分。
  • 2、任务2由内核与任务2的私有部分组成,其是3特权级。
  • 3、任务3由内核与任务3的私有部分组成,其是3特权级。
    在这里插入图片描述

在本章中,当处理器加电复位之后,进入保护模式之后就直接创建和执行内核的0特权级任务—>之后切换到任务2的私有部分—>之后切换到内核—>之后切换到任务3的私有部分。

每个任务都有自己的状态,特别是当一个任务正在执行时,所有段寄存器、通用寄存器都和当前任务息息相关。段寄存器指向当前任务自己的段、通用寄存器保存着当前任务的数据和临时结果、标志寄存器保存着当前任务产生的各种标志。

当前任务要切换出去时,必须将当前任务的所有状态保护起来以便将来恢复,这叫做保护现场;被切换到的那个任务也必须恢复到原先它被打断时的状态,叫做恢复现场。

为了保护现场和恢复现场,使用每个任务的TSS来保存数据:CR3和分页有关。
在这里插入图片描述
保存当前任务的现场:
在这里插入图片描述
恢复目标任务的现场:
在这里插入图片描述
恢复之后被切换到的任务就变为当前任务。

02、内核任务的创建和I/O特权级IOPL

本章程序:
引导程序:c13_mbr0.asm,加载执行内核。
内核程序:c15_core0.asm,加了新的内容。
用户程序:c15_app0.asm,加了新的内容。

I/O许可位图,之前讲过特权指令,即只有0特权级才能执行的指令。但是有一些低特权级的程序也需要使用这些特权指令。如下:
在这里插入图片描述
为了控制哪些任务能够访问硬件端口,需要用到标志寄存器EFLAGS
在这里插入图片描述
当前特权级CPL若高于IOPLI/O Privilege Level),数值上CPL <= IOPL,则表示所有I/O访问都是允许的。

03、I/O特权级的修改和POPF指令

标志寄存器中的标志位是随着程序运行而实时改变的,如ZF、CF;有些则需要特定的指令进行改变,比如DF。但是IOPL不会自动随程序修改,也没有特定指令来修改。那么IOPL的修改需要如下操作:

  • 1、先将标志寄存器压栈;
    在这里插入图片描述
    在这里插入图片描述
  • 2、然后对栈中IOPL内容进行修改;
    在这里插入图片描述
    IOPL修改为01
  • 3、最后将栈中修改后的内容弹出到标志寄存器。
    在这里插入图片描述
    在这里插入图片描述

04、任务的用户态和内核态

每个任务都有TSS,其中保存了EFLAGS,有IOPL字段。
在这里插入图片描述
多任务系统特点:可以在内核任务和用户任务之间来回切换、也可以在两个用户任务之间来回切换。
在这里插入图片描述
每当一个任务被切换回后台时,它与之相关的状态都会保存在它的TSS中,当它恢复是,会从它的TSS中将各种状态恢复到处理器中。显然每个任务都受自己的IOPL所限制。

每个任务都可以对自己的标志和状态进行修改,比如标志寄存器中的内容,需要使用如下指令进行压栈、修改、再出栈返回到标志寄存器中。
在这里插入图片描述
其中pushf、pushfd在任何特权级下均可执行,但是popf、popfd执行时标志寄存器中的有些标志位是否会受到影响(如IOPL字段)、是否能够被修改,需要取决于当前特权级CPL

  • 1、CPL为0,那么执行popf、popfd指令时,标志寄存器的IOPL字段会被修改;
  • 2、CPL为1,那么执行popf、popfd指令时,标志寄存器的IOPL字段类似于只读,不会受到影响;

即低特权级的指令无法使用popf、popfd指令修改IOPL字段。popf、popfd并不是特权指令,特权指令是只能在0特权级下执行,popf、popfd指令在低特特权级下也可以执行,只不过在低特权级下执行时一些标志位不受影响。
在这里插入图片描述
内核任务只能在内核态执行,用户任务可以在内核态和用户态中执行。

05、I/O许可位串和TSS的I/O许可位映射区

在这里插入图片描述
当前CPL 高于等于 IOPL(即数值上CPL <= IOPL),则所有操作I/O操作都是允许的;
当前CPL 低于 IOPL(即数值上CPL > IOPL),也并不意味这所有I/O操作都不被允许,而是需要进一步指定哪些允许,那些不允许。在输入输出许可位串(I/O许可位串)中指定。
在这里插入图片描述
TSS中基本长度是104字节,当然也可以包括I/O许可位映射区。
在这里插入图片描述
TSS描述符及其布局:其中段界限是包括I/O许可位映射区的。
在这里插入图片描述
其中I/O许可位偏移M若大于TSS描述符的段界限,则表示没有I/O许可位。在这种情况下,如果当前特权级CPL低于当前IOPL,那么就必须检查I/O许可位串,但是没有I/O许可位串就意味着不允许访问硬件端口,执行任何硬件I/O指令都会引发处理器异常中断。
在这里插入图片描述
处理器检查I/O许可位方法如下:
1、先根据端口号计算它在I/O许可位映射区的那个字节中,
2、然后读取该字节,并测试那个byte位,如out 0x09, al指令:端口0x09位于第二个字节,而且位于第二个字节的位1。处理器读取并测试这个byte位是0还是1来决定是否允许执行这个out指令。

I/O端口是按照字节编址的,即每个端口只能用来读取一个字节的数据,那些多字节的端口其实是合并了几个端口组成一个多字节端口的:
在这里插入图片描述
由于I/O端口是按照字节编址的原因,当处理器执行一个字或者双字的I/O指令时,会检查许可位串中的2个或者4个连续的byte,而且要求它们必须都是0,否则引发异常中断。

麻烦在于这些连续的byte可能是跨字节的,即一些byte位于前一个字节,另一些byte位于后一个字节,如下:
在这里插入图片描述
处理器每次都会从I/O许可映射区读取2个连续的字节,而不是1个字节。

这种操作方式也导致了另一个问题,即要检查的byte在最后一个字节中,那么这个2字节的读操作将会越界。为了防止这种情况,处理器要求I/O许可映射区在最后必须附加一个额外的字节,其值为0xFF。如下:
在这里插入图片描述
I/O许可映射区本身只有11个字节,除去最后一个全为1的字节,只剩10个字节,那么只能映射80个端口,访问更高地址的端口(即高于79号端口)将引发异常中断。
在这里插入图片描述

06、任务切换的方法及内核任务的确立

内核本身要当作一个独立的任务,内核正在执行,现在要为其补一个合法的手续;创建内核任务的TSS,接着要在TSS中填充一些内容,在任务切换之前提前准备好。

设置内核任务的TSS

。。。
	;为内核任务的TSS分配内存空间
	mov ecx,104                        ;为该任务的TSS分配内存
	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特级不
	                                   ;会向低特权级转移控制。
。。。
  • 1、内核任务不需要LDT,所以在内核TSS偏移0x96的地方填写数字0即可;
  • 2、内核任务也不需要I/O许可位映射区,内核是0特权级,始终可以进行所有I/O操作。这里偏移填写103为内核TSS的界限值,因为I/O许可位映射区TSS中的偏移 >= TSS的界限值,即表示保存在I/O许可位映射区
  • 3、反向链相关。硬件任务切换在64位处理器上不再支持,除非以兼容模式允许32位。
    在这里插入图片描述
    在这里插入图片描述
    CALL指令发起任务切换时,任务之间会形成一个任务链,可以通过任务链反向切换到原来的任务中。
    在这里插入图片描述
    所以在TSS中偏移为0的位置,置0即可:
    在这里插入图片描述
  • 4、和分页相关的位清0
    在这里插入图片描述
  • 5、设置T位为0,因为内核特权级为0,不会向低特权级实施控制转移。
    在这里插入图片描述

创建内核任务的TSS描述符,安装到GDT中:

。。。
	;创建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
	mov word [es:esi+0x18],cx          ;登记TSS选择子到TCB
	mov word [es:esi+0x04],0xffff      ;任务的状态为“忙”
	
	;任务寄存器TR中的内容是任务存在的标志,该内容也决定了当前任务是谁。
	;下面的指令为当前正在执行的0特权级任务“程序管理器”后补手续(TSS)。
	ltr cx
	
	;现在可认为“程序管理器”任务正执行中
	mov ebx,core_msg1
	call sys_routine_seg_sel:put_string
。。。

07、用户任务的创建和初始化

TCB中偏移为0x04位置:
在这里插入图片描述
任务状态为0表示就绪、为0xFFFF表示忙状态、为0x3333表示任务已经终止

为用户任务创建TCB

。。。
	;以下开始创建用户任务
	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     
。。。

在例程load_relocate_program中:
1、创建LDT
2、加载用户程序;
3、创建用户程序每个段的描述符,并将其安装到LDT中;
4、重定位用户和程序的符号地址检索表SALT,本章在SLAT中新增一个条目InitTaskSwitch用于任务切换;
5、创建0、1、2特权级的栈段描述符及其选择子,为通过调用门转移控制而准备的;
6、在GDT中等级LDT描述符;
7、创建用户任务的TSS
8、在用户任务的TSS中登记相关的信息,阅读时参考TSS结构(TSS.pdf);

  • 8.1、填写任务的反向链
  • 8.2、填写0、1、2特权级的栈段选择子和栈指针
  • 8.3、登记LDT选择子
  • 8.4、登记I/O许可位映射区偏移
  • 8.5、登记T标志
  • 8.6、等登记CR3(和分页有关)
  • 8.7、登记其他信息
    1、以前内核不是独立的任务,而是用户任务的私有部分。所以用户程序加载之后模拟调用门返回,从任务的全局部分返回任务的私有部分。本章中内核为一个独立的任务,是正在执行的任务,所以当我们创建了用户任务之后,将使用任务切换的方式从内核任务切换到用户任务。
    2、切换到用户任务时,一定会从用户任务的TSS中恢复现场,即使是用户任务的第一次执行,为了确保用户任务的第一次切换成功,需要在用户任务的TSS中设置哪些内容呢?
    3、首先是0、1、2特权级的栈段选择子和栈指针;接着是通用寄存器的内容,一般都是运行时自动设置,也有一些需要单独设置(如EFLAGS中的IOPL字段、EIP要设置为用户任务入口点的偏移量)。
    4、段寄存器的内容,可以提前设置也可以在程序中用指令初始化,CS必须在这里设置为用户程序入口点的代码段选择子。
    5、若用户任务有LDT,则需要设置LDT的段选择子。
    6、若用户任务有I/O许可位映射区,则需要设置映射区的偏移。
    在这里插入图片描述

9、创建用户任务的TSS描述符,并将其安装在GDT中,安装之后在CX中返回TSS的选择子,将其登记在用户任务控制块TCB中。
10、从例程load_relocate_program返回。

创建一个用户任务之后还可以创建其他任务:

。。。
	;可以创建更多的任务,例如:
	;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
。。。         

之后就是任务管理的循环,用来发起内核任务到其他任务的切换、回收已经终止任务的资源、也可以选择创建新的任务。

。。。
.do_switch:
	;主动切换到其它任务,给它们运行的机会
	call sys_routine_seg_sel:initiate_task_switch
	
	mov ebx,core_msg2
	call sys_routine_seg_sel:put_string
	
	;这里可以添加创建新的任务的功能,比如:
	;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
	
	;清理已经终止的任务,并回收它们占用的资源
	call sys_routine_seg_sel:do_task_clean
	
	mov eax,[tcb_chain]
.find_ready:
	cmp word [es:eax+0x04],0x0000      ;还有处于就绪状态的任务?
	jz .do_switch                      ;有,继续执行任务切换
	mov eax,[es:eax]
	or eax,eax                         ;还有用户任务吗?
	jnz .find_ready                    ;一直搜索到链表尾部
	
	;已经没有可以切换的任务,停机
	mov ebx,core_msg3
	call sys_routine_seg_sel:put_string
	hlt
。。。

08、简单的任务调度和切换策略

接上一节。

所有任务都是平等的参与任务切换,切换到用户任务时做的是自己的私事,切换到内核任务时做的是管理整个系统。

使用例程initiate_task_switch进行任务切换,从任务链表中找到下一个就绪状态的任务,进行切换。tcb_chain中记录每个任务的TCB,其中有下一个任务的地址和这个任务的状态(忙或正在执行0就绪FFFF已终止3333)。
在这里插入图片描述
任务调度策略:顺着链表找到当前正在执行的任务,再找到一个就绪的任务,切换到这个就绪的任务。切换之后将任务状态进行改变(忙改为就绪、就绪改为忙)。
在这里插入图片描述
特殊情况1:如上图,系统中只有一个任务,不执行任务切换。
在这里插入图片描述
特殊情况2:如上图,每次都是从tcb_chain链表头部开始搜索,先找到忙任务再从后面找到就绪任务。若忙任务处理链表尾部,那么也是从链表头部重新搜索就绪任务。这样任务之间就是公平的进行轮转。

09、遍历TCB链表寻找忙任务和就绪任务

本节是分析例程initiate_task_switch,具体看代码和视频。

10、通过JMP FAR执行任务切换的过程

上一节中找到了状态忙的任务状态为就绪的任务
在这里插入图片描述
在64位处理器上不再提供硬件任务切换,操作系统也不适用硬件任务切换。
在这里插入图片描述
使用jmp far [edi+0x14]指令进行任务切换时:

  • EDI保存就绪任务的TCB线性地址,TCB偏移0x14的地方,存放TSS的基地址和16位TSS选择子;
  • 处理器执行这条指令时使用DS描述符高速缓存器中的基地址 + 段内偏移(EDI + 0x14),取出6个字节,假定它们是段选择子和段内偏移;当处理器发现6和字节中后2字节是段选择子则前面的4字节地址会被忽略。
  • 用这个选择子到GDT中寻找对应的描述符,处理器发现这是TSS描述符,就知道需要发起任务切换。
  • 保存旧任务的状态
    在这里插入图片描述
    当前CS指向内核公共例程段,EIP指向下一条指令,则保存状态时,CSEIP保存的是上述值。
  • 设置新任务的状态
    在这里插入图片描述
    第一次是从内核任务切换到用户任务,在创建用户任务时,已经在用户任务的TSS中登记了各种信息,包括3个特权级的栈段选择子SS和栈指针ESP,接着登记LDT选择子,之后是入口点信息,包括CSEIP。一旦从用户任务的TSS中恢复那些信息,处理器就会转入用户任务执行。

11、内核任务与用户任务轮流执行的过程

看视频、代码即可,主要讲解了从内核任务切换到用户任务的执行流程。

12、任务的终止和清理

看视频、代码即可,主要讲解了从用户任务切换到内核任务的执行流程,其中会对任务进行清理。

Virtual Box虚拟机:
在这里插入图片描述
Bochs虚拟机:
在这里插入图片描述

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

「已注销」

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值