这篇文章我们来分析switch_to()函数, 分析的代码取自linux kernel 0.11 sched.h文件, 我们只为说明原理, 所以尽量简单. 其中代码注释参考了赵炯老师<Linux内核完全注释>一书, 言归正传,下面来看switch_to()函数.
switch_to(n)将切换当前任务到任务n.首先检测任务n是不是当前任务,是则直接退出.
输入: %0 – 指向_tmp 1% - 指向_tmp.b处, 用于存放新TSS的选择符
dx – 新任务n的TSS段选择符 ecx – 新任务n的任务结构指针task[n].
_tmp用于jump指令的操作数, 其中a值是32位偏移地址, 而b的低2字节是新TSS段的选择符. 细心的读者看到a并没有被赋值,可以思考一下这是为什么
#define switch_to(n) {\ struct {long a,b;} __tmp; \ __asm__("cmpl %%ecx,_current\n\t" \ "je 1f\n\t" \ "movw %%dx,%1\n\t" \ "xchgl %%ecx,_current\n\t" \ "ljmp %0\n\t" \ "cmpl %%ecx,_last_task_used_math\n\t" \ "jne 1f\n\t" \ "clts\n" \ "1:" \ ::"m" (*&__tmp.a),"m" (*&__tmp.b), \ "d" (_TSS(n)),"c" ((long) task[n])); \ }
3. 检查任务n是当前任务吗?
4. 是则什么都不做,退出
5. 将新任务TSS的16位选择子存入_tmp.b中.
6. 当前任务变为task[n]; ecx = 被切换出的任务.
7. 长跳转到*&_tmp, 造成任务切换. 我们可以看之前的一篇文章<JMP指令>, 其中代码34行, 指明了任务切换时要调用的函数TaskSwitch(), 下面我们将分析这个函数具体要做什么.
8. 原任务使用过协处理器吗
9. 没有,则跳转退出.
10. 原任务使用过协处理器, 清cr0中的任务切换标志TS
下面介绍<JMP指令>一文中伪代码函数TaskSwitch()做了哪些事情
TaskSwitch(SelectorType Selector, SegAttributes Attributes, int Base, int Limit, int Linkage) { SelectorType OldTSS; if(Limit < 103) SegmentException($TS, Selector); AccessTSSState(1); //Write TR.Base = Base; TR.Limit = Limit; TR.Attributes = Attributes; OldTSS = TR.Selector; TR.Selector = Selector; AccessTSSState(0); //read if(Linkage == 1) { AccessLinear(TR.Base, 2, 0, 1, &OldTSS); EFLAGS.NT = 1; SetTSSBusy(Selector, 1); } else if(Linkage == -1) SetTSSBusy(OldTSS, 0); else if(Linkage == 0) { SetTSSBusy(OldTSS, 0); SetTSSBusy(Selector, 1); } CR0.TS = 1; CPL = CS.Selector.RPL; LDTR.Attributes.Present = 0; CS.Attributes.Present = 0; SS.Attributes.Present = 0; CS.Attributes.Present = 0; DS.Attributes.Present = 0; ES.Attributes.Present = 0; FS.Attributes.Present = 0; GS.Attributes.Present = 0; if(LDTR.Selector.TI == 1) SegmentException($TS, LDTR.Selector); if((LDTR.Selector & 0FFFCh) == 0) LDTR.Attributes.P = 0; else { ReadDescriptor(LDTR.Selector, &Attributes, &Base, &Limit, &GSelector, &GOffset); if((Attributes.DType == 1) || (Attributes.Type != 2) || (Attributes.Present == 0)) SegmentException($TS, LDTR.Selector); SetAccessed(LDTR.Selector); LDTR.Attributes = Attributes; LDTR.Base = Base; LDTR.Limit = Limit; } JumpGate(CS.Selector, EIP, $TS); SRegLoad(SS, SS.Selector, $TS); SRegLoad(DS, SS.Selector, $TS); SRegLoad(ES, SS.Selector, $TS); SRegLoad(FS, SS.Selector, $TS); SRegLoad(GS, SS.Selector, $TS); }4. 任务状态段至少有104个字节
6. 当前机器的状态(EIP, EFLAGS,EAX,ECX, EDX, ESP, EBP…….)保存到老任务的TSS中
7-11 TR及投影寄存器指向新任务的TSS, 原来任务的selector保存到OldTSS中
12. 把新任务TSS中的内容装入到硬件寄存器(EIP,EFLAGS, EAX,ECX, EDX, ESP, EBP…….)中.
13-25 根据链家字段的值做不同的处理
26-27 设置任务切换位及当前特权级
28-35 各描述符投影寄存器存在位预置为0
36-37 LDTR的选择子必须指定全局描述符表,否则产生异常
38-39 局部描述符表可以为空, 如为空, 将描述符表存在位设置为0
40. 局部描述符表非空
41-51 读出描述符的属性, 基地址和段界限. 检查出错,产生段异常. 之后装入局部描述符表投影寄存器.
52. 装入CS投影寄存器
53-57 装入SS, DS, ES, FS, GS投影寄存器
下面给出JumpGate的伪代码, 经过<JMP一文>,相信读者可以自行分析.
JumpGate(SelectorType Selector, int Offset, int GP) { SegAttributes Attributes; SelectorType GSelector; int Base, Limit, GOffset; if((Selector&0FFFCh) == 0) SegmentException(GP, 0); ReadDescriptor(Selector,&Attributes,&Base,&Limit,&GSelector,&GOffset); if(Attributes.DType == 0) SegmentException(GP, Selector); Selector.RPL = 0; CSDescriptorLoad(Selector,Attributes,Base,Limit,GP); if(Offset > CS.Limit) SegmentException(GP, 0); CS.Selector = Selector; CS.Selector.RPL = CPL; EIP = Offset; }