第17章 协同式任务切换

news2024/9/27 10:47:01

第17章 协同式任务切换

在多任务系统中,每个任务都有各自的局部描述符表(LDT)和任务状态段(TSS)。

从任务切换的时机来讲,有两种基本的策略:

  • 协同式:从一个任务切换到另一个任务。需要当前任务主动地请求暂时放弃执行权,或者在通过调用门请求操作系统服务时,由操作系统“趁机”将控制转移到另一个任务。这种方式依赖于每个任务的“自律”性,当一个任务失控时,其他任务可能得不到执行的机会。
  • 抢占式:在这种方式下,可以安装一个定时器中断,并在中断服务程序中实施任务切换。硬件中断信号总会定时出现,不管处理器当时在做什么,中断都会适时地发生,而任务切换也就能够顺利进行。在这种情况下,每个任务都能获得平等的执行机会。而且,即使一个任务失控,也不会导致其他任务没有机会执行。

本章主要介绍协同式的任务切换。

本章代码清单

本章的代码主要实现的功能:

  • 将内核也设计为一个任务;
  • 内核任务和用户任务之间通过协同式进行切换;

最后在屏幕上打印如下信息:

image

任务切换前的设置

所有任务共享一个全局空间,由内核或操作系统提供,包含了系统服务程序和数据;同时每个任务还有自己的局部空间,局部空间是包含一个任务区别于其他任务的私有代码和数据。

image

虚假调用门返回:一开始,处理器是在任务的全局空间执行的,当前特权级别是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)任务门:如果中断号对应的门是任务门,那么就要进行任务切换。

任务门描述符格式:

image

  • 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位:

image

32位处理器的EFLAGS有NT位(位14),意思是嵌套任务标志(Nested Task Flag)。每次中断返回时都会检查这个位。

  • 为0:表示是一般的中段过程,按一般的中断返回处理;
  • 为1:当前任务中断了别的任务,要返回原先被中断的任务。

任务的嵌套结构图示如下:

image

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进行判断。

常规的代码段和数据段描述符:

image

TSS描述符格:

image

任务门、中断门和陷阱门描述符:

image

下图是LDT描述符:LDT段描述符格式在MIT提供的手册上没有找到,下图是是来自该书籍的说明。

image

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指令。

在任务切换时,处理器执行以下操作:

  1. 从jmp或call指令的操作数、任务门或者当前任务的TSS任务链接域获取新任务的TSS描述符选择子。
  2. 检查是否允许从当前任务(旧任务)切换到新任务,主要是特权级的检查。
  3. 检查新任务的TSS描述符是否已经标记为有效(P=1),并且界限也有效(大于或者等于0x67,即十进制的103)。
  4. 检查新任务是否可用:
    a. 不忙(B=0,对于以call、jmp、异常或者中断发起的任务切换)
    b. 忙(B=1,对于以iret发起的任务切换)。
  5. 检查当前任务(旧任务)和新任务的TSS,以及所有在任务切换时用到的段描述符已经安排到系统内存中。
  6. jmp或者iret发起,清除当前任务的忙(B)标志;如果是call指令、异常或者中断发起的,忙标志保持原来的置位状态。
  7. 如果任务切换是由iret指令发起的,处理器建立EFLAGS寄存器的一个临时副本并清除其NT标志;如果是由call指令、jmp指令、异常或者中发起的,副本中的NT标志不变。
  8. 保存当前(旧)任务的状态到它的TSS中。
  9. 如果任务切换是由call指令、异常或者中断发起的,处理器把从新任务加载的寄存器EFLAGS的NT标志置位(=1);如果是由iret或者jmp指令发起的,NT标志位的状态对应着从新任务加载的寄存器EFLAGS的NT位。
  10. 如果任务切换是由call指令、jmp指令、异常或者中断发起的,处理器将新任务TSS描述符中的B位置位;如果是由iret指令发起的,B位保持原先的置位状态不变。
  11. 用新任务的TSS选择子和TSS描述符加载任务寄存器TR。
  12. 新任务的TSS状态数据被加载到处理器。
  13. 与段选择子相对应的描述符在经过验证后也被加载。
  14. 开始执行新任务。

切换任务时,不同的方式相关的数据变化如下:

image

程序的编译和运行

  • mbr程序还是用第15章的,写入0扇区;
  • core内核程序,写入1扇区;
  • app用户程序,写入50扇区;

运行结果如下:

image

完。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2169731.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

好看的首页展示

代码 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</title><style>/* RESET…

气膜游泳馆:舒适恒温,寒冷季节中的理想游泳场所—轻空间

随着天气逐渐转凉&#xff0c;许多人在秋冬季节减少了户外活动&#xff0c;尤其是游泳。然而&#xff0c;气膜游泳馆为您提供了一种全新的选择&#xff0c;让您即使在寒冷的季节&#xff0c;也能享受畅游的乐趣。凭借其独特的恒温设计和舒适的环境&#xff0c;气膜游泳馆成为了…

计算机毕业设计宠物领养网站我的发布领养领养用户信息/springboot/javaWEB/J2EE/MYSQL数据库/vue前后分离小程序

目录 1.课题背景 2.课题意义 ‌ 3.技术介绍 4.技术性需求 4.1后端服务‌&#xff1a; 4.2 前端展示‌ 5.数据库设计‌&#xff1a; 6.系统性能‌&#xff1a; 7.安全性‌&#xff1a; 8. 功能介绍&#xff1a; 9. 部分代码 1.课题背景 近年来&#xff0c;随着宠物饲养数量…

TDEngine在煤矿综采管控平台中的应用

一、行业背景 智能综采管控平台&#xff0c;是将煤矿综采工作面传感器数据采集&#xff0c;通过可视化界面展示。实现综采工作面的透明化展示&#xff0c;并基于历史的传感器数据进行机器学习的训练&#xff0c;了解工作面周期来压&#xff0c;设备故障检测等数据应用。因此针…

AIGC引领数智未来:企业架构演进的深度解析与实践路径,The Open Group 2024生态系统架构·可持续发展年度大会专题报道

随着人工智能技术的迅猛发展&#xff0c;特别是以ChatGPT、Sora等为代表的AIGC&#xff08;人工智能生成内容&#xff09;技术的爆发&#xff0c;我们正处于通用人工智能&#xff08;AGI&#xff09;时代的前夜。AIGC技术在多个领域展现出近乎甚至超越人类的能力&#xff0c;已…

【LeetCode】动态规划—最小路径和(附完整Python/C++代码)

动态规划—64. 最小路径和 前言题目描述基本思路1. 问题定义:2. 理解问题和递推关系:3. 解决方法:3.1. 初始化:3.2. 边界条件:3.3. 填充 dp 数组:3.4. 返回结果: 4. 进一步优化:5. 小总结: 代码实现Python3代码实现Python 代码解释C代码实现C 代码解释 总结: 前言 给定一个包含…

liunx系统虚拟机

https://mirrors.aliyun.com/centos/7.9.2009/isos/x86_64/ 下载地址 DVD版本 安装vm软件12通过vm软件来创建一个虚拟机空间通过vm软件在创建好的虚拟机空间上&#xff0c;安装我们的centos操作系统使用centos你得需要将鼠标点击进入界面中&#xff0c;但是鼠标会消失&#xf…

杨辉三角-C语言

1.问题&#xff1a; 输出杨辉三角。 2.解答&#xff1a; 对有特点的数&#xff08;每行开头和结束的数都是1&#xff09;进行赋值&#xff0c;给中间的数进行赋值&#xff0c;把上面赋值后的二维数组&#xff0c;遍历输出。 3.代码&#xff1a; #include<stdio.h>//头…

基于SSM商铺租赁系统JAVA|VUE|SSM计算机毕业设计源代码+数据库+LW文档+开题报告+答辩稿+部署教+代码讲解

源代码数据库LW文档&#xff08;1万字以上&#xff09;开题报告答辩稿 部署教程代码讲解代码时间修改教程 一、开发工具、运行环境、开发技术 开发工具 1、操作系统&#xff1a;Window操作系统 2、开发工具&#xff1a;IntelliJ IDEA或者Eclipse 3、数据库存储&#xff1a…

使用session来实现验证码发送功能

一、什么是session&#xff1f; Session由服务器创建&#xff0c;并为每一次会话分配一个Session对象。同一个浏览器发起的多次请求&#xff0c;同属于一次会话&#xff08;Session&#xff09;。首次使用到Session时&#xff0c;服务器会自动创建Session&#xff0c;并创建Co…

大觅网之自动化部署(Automated Deployment of Da Mi Network)

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 本人主要分享计算机核心技…

前端大模型入门:Transformer.js 和 Xenova-引领浏览器端的机器学习变革

除了调用别人的api接口使用transformer技术&#xff0c;你是否想过将大模型在浏览器中运行呢&#xff1f;尤其是WebGPU的出现&#xff0c;性能比WebGL高不少&#xff0c;很多小任务真的不再需要在一个中心运行了。 不少同学买课学python了&#xff0c;但我还是在坚持用js尝试&a…

【Linux实践】实验九:Shell流程控制语句

【Linux实践】实验九&#xff1a;Shell流程控制语句 实验目的实验内容实验步骤及结果1. 变量的定义和使用2. 条件3. 运算4. if 语句5. case 语句6. for 语句7. while 语句8. until 语句9. 遍历复制10. 计算平方 实验目的 1、掌握条件判断语句&#xff0c;如if语句、case语句。…

tomcat的安装,管理与配置

目录 Tomcat 服务部署 1.关闭防火墙&#xff0c;将安装 Tomcat 所需软件包上传到虚拟机 2.安装JDK 3.设置JDK环境变量 4.安装启动Tomcat 5.启动tomcat 6.优化tomcat启动速度 Tomcat 服务管理 systemd 管理控制 supervisor 管理控制 Tomcat 虚拟主机配置 1.创建 sun…

EE trade:黄金 999 和黄金 9999 的区别

黄金&#xff0c; 作为一种珍贵的金属&#xff0c; 一直是人们投资和收藏的对象。 在购买黄金时&#xff0c; 您可能会遇到两种纯度的黄金 —— 黄金 999 和黄金 9999。 这两种黄金有什么区别? 消费者应该如何选择呢? 一、 黄金 999 和黄金 9999 的区别 含金量&#xff1a;…

OCR 行驶证识别 离线识别

目录 正页识别 副页识别 全部识别 OCR 行驶证识别 离线识别 正页识别 副页识别 全部识别

C语言实现归并排序(Merge Sort)

目录 一、递归实现归并排序 1. 归并排序的基本步骤 2.动图演示 3.基本思路 4.代码 二、非递归实现 1.部分代码 2.代码分析 修正后代码&#xff1a; 归并过程打印 性能分析 复杂度分析 归并排序是一种高效的排序算法&#xff0c;采用分治法&#xff08;Divide and Con…

中电金信:“源启”金融级数字底座

01方案简介 金融级数字底座是中电金信依托中国电子自主安全计算产业链&#xff0c;采用新一代技术架构&#xff0c;为金融及重点行业打造的数字化新型基础设施。 “源启”面向金融等重点行业场景&#xff0c;依照系统工程方法论&#xff0c;进行全栈技术产品的验证、适配和调…

word2vector训练数据集整理(代码实现)

import math import os import random import torch import dltools from matplotlib import pyplot as plt #读取数据集 def read_ptb():"""将PTB数据集加载到文本行的列表中"""with open(./ptb/ptb.train.txt) as f:raw_text f.read()return…

【深度学习基础模型】双向循环神经网络(Bidirectional Recurrent Neural Networks, BiRNN)详细理解并附实现代码。

【深度学习基础模型】双向循环神经网络&#xff08;Bidirectional Recurrent Neural Networks, BiRNN&#xff09; 【深度学习基础模型】双向循环神经网络&#xff08;Bidirectional Recurrent Neural Networks, BiRNN&#xff09;详细理解并附实现代码。 文章目录 【深度学习…