switch_to(n)函数


这篇文章我们来分析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;
}


你可能感兴趣的:(switch_to(n)函数)