调试分析 Linux 0.00 多任务切换
当执行完 system_interrupt
函数,执行 153
行 iret
时,记录栈的变化情况。
任务0在刚进入system_interrupt
函数时(调用中断int 0x80
处理程序),栈空间为任务0的内核栈,即krn_stk0
。(CS = 0X8, SS = 0x10, ESP = 0xe4c
)
查看栈空间,里面存放着5个双字,即任务0执行的状态信息(EIP、SS、EFLAGS、ESP、SS
)。具体信息是任务0的运行位置:0x10e0 movl $0xfff,%ecx
,是int 0x80
的下一条指令。还有任务0的栈空间地址,即任务0的用户栈(init_stack
)。
在执行iret
指令前,该函数压栈保存程序用到的寄存器,最后弹栈。所以,在iret
执行前,栈空间即如上。
在执行iret
指令后,回到任务0调用中断处的下一条指令,栈空间又切换为了任务0的用户栈(CS = 0Xf, EIP = 0x10eb, SS = 0x17, ESP = 0xbd8
)。这是因为将之前栈中信息弹出到对应的寄存器中。
所以,当任务0执行system_interrupt
函数时,执行完iret
指令后,栈变化的情况是:任务0的用户栈保持不变,任务0的内核栈仍然为空。
任务1同理。
当进入和退出 system_interrupt
时,都发生了模式切换,请总结模式切换时,特权级是如何改变的?栈切换吗?如何进行切换的?
特权级改变
仍然以任务0为例子,任务0 CS = 0xf
,是任务0 LDT
的代码段描述符的选择子,特权级为 3。当进入system_interrupt
后,CS = 0x8
,是GDT
内核代码段的选择子,特权级为 0。
特权级的改变是通过该陷阱门实现的。该陷阱门的DPL
为3
,允许特权级为3的程序执行,执行后,该陷阱门中的段选择符为 0x8
,是内核代码段选择符(中断程序属于内核)。所以,在经过特权级检查成功通过后,特权级就由 3变为了 0。
返回时,允许返回到低特权级程序。特权级也就从 0变为了 3。
栈切换
栈也会切换。在进入system_interrupt
时,特权级会变为 0,栈空间指针(SS ESP
)就会变为从TSS
里取出特权级 0对应的栈指针。
退出时,因为在进入时被调用者栈中保存了原栈空间指针,则直接弹栈,将保存的SS ESP值
加载到SS ESP 寄存器
中,从而切换回调用者的堆栈。
当时钟中断发生,进入到 timer_interrupt
程序,请详细记录从任务 0
切换到任务 1
的过程。
在程序中,有一块 4字的空间存放着一个数值(可以认为是一个全局变量),为current
。在timer_interrupt
程序中,若current
值为 0,则之前执行的是任务 0,要切换到任务 1;若current
值为1,则之前执行的是任务 1,要切换到任务 0。
通过判断current
值,进行不同的跳转(长跳转),从而实现任务 0与 1之间的切换。
当第一次时钟中断发生,任务 0在loop
指令处。栈空间切换为任务 0特权级0 的栈空间(即任务 0的内核栈空间)。在该栈空间中,保存着任务 0运行的状态信息SS ESP EFLAGS CS EIP
。
timer_interrupt
程序顺序向下运行,在判断当前current
值不为1时,将current
更新为1,然后长跳转到任务 1。
跳转的具体过程是,长跳转提供了一个远指针。该指针中的段选择符指向了TSS1
。任务1的TSS
保存着它状态信息 ,包括跳转后的栈指针、EFLAGS
、代码指针。这些信息,在第一次切换到任务 1时,分别是任务1的用户栈,任务1的第一条指令地址。
如此,便从任务0切换到了任务1。
又过了 10ms
,从任务1切换回到任务 0
,整个流程是怎样的? TSS
是如何变化的?各个寄存器的值是如何变化的?
在第一次从任务0切换到任务1后,查看任务0的TSS
,发现任务 0的CS:EIP
为0X08:0x150
,即为内核代码段,是system_interrupt
中指令jmp 2f
,是切换到任务1的远跳转的后一条指令。
当运行任务1时,与任务0同理,在loop
指令处,触发时钟中断,进入到 timer_interrupt
程序。
因为之前,current
值为设置为 1,那么这次就会长跳转到任务 0。在执行该指令ljmp $TSS0_SEL,$0
前,寄存器值:
执行后发现,待执行的指令为<0x0150> jmp .+17(0x0163)
,这正是之前任务 0的TSS
保存的指令执行地址。此时的寄存器值:
可以看到,通用寄存器值、段寄存器值、标志寄存器值、指令指针EIP
值,均对应之前查看的任务0 TSS
。
此时,再查看任务1的TSS
。
正是保存着执行前任务1的寄存器值,其中CS:EIP
指向了下一条指令<0x0163> pop eax
。
请详细总结任务切换的过程。
对于该例子,是当前任务对GDT
的TSS段描述符
执行JMP
指令。则任务切换的过程是:
-
从
JMP
指令操作数中,取得新任务的TSS段选择子
。 -
特权级检查。当前任务的
CPL
和新任务段选择符的RPL
必须小于等于TSS段描述符
的DPL
。 -
检查新任务的
TSS段描述符
是标注为存在的(P = 1),并且TSS
段长度有效。 -
将当前任务忙标志B复位。
-
把当前任务的状态保存到当前任务的
TSS
中。 -
设置新任务的忙标志B。
-
使用新任务的
TSS
的段选择符和描述符加载任务寄存器TR
(包括隐藏部分)。设置CR0
寄存器的TS
标志。 -
加载新任务的
TSS
状态,包括LDTR
寄存器,PDBR
(CR3
)寄存器、EFLAGS
寄存器、EIP
寄存器以及通用寄存器和段选择符。 -
开始执行新任务。
注意:
任务切换时,新任务的特权级和原任务的特权级没有任何关系。新任务在CS
寄存器的CPL
字段指定的特权级上运行,因为各个任务通过它们独立的地址空间和TSS
段相互隔绝,并且特权级规则已经控制对TSS
的访问,所以在任务切换时软件不需要再进行特权级检查。
本文来自博客园,作者:江水为竭,转载请注明原文链接:https://www.cnblogs.com/Az1r/p/17955532