对linux 0.11版本中switch_to()的理解

switch_to的代码在linux-0.11\include\linux中的sched.h。它是一个宏定义,代码如下:

#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])); \
}

首先讲一下末尾的

::"m" (*&__tmp.a),"m" (*&__tmp.b), \
"d"(_TSS(n)),"c" ((long) task[n])); \

这句话吧,假如看过嵌入式汇编的应该知道这个内容,不过我还是多写点也帮助自己记忆。第一个冒号表示输出寄存器为空,第二个冒号表示输入寄存器,其中*&__tmp.a和*&__tmp.b存在任意寄存器中,m就代表任意寄存器,_TSS(n)即该任务在GDT中的选择符,对于这个的理解需要明白GDT的构成,我截了linux内核完全注释中的一张图,如下
对linux 0.11版本中switch_to()的理解_第1张图片

再来看看_TSS(n)这个宏定义

#define _TSS(n) ((((unsigned long) n)<<4)+(FIRST_TSS_ENTRY<<3))
#define FIRST_TSS_ENTRY 4

      每个描述符占8个字节,第一个状态段是第四个,所以<<3,得到第一个任务描述符在GDT中的位置,而每个任务使用一个tss和ldt,占16字节,所以<<4,两者相加得到任务n的tss在GDT中的位置。另外ecx指向要切换过去的新任务。
     现在开始理解代码,首先声明了一个_tmp的结构,这个结构里面包括两个long型,32位机里面long占32位,声明这个结构主要与ljmp这个长跳指令有关,这个指令有两个参数,一个参数是段选择符,另一个是偏移地址,所以这个_tmp就是保存这两个参数。再比较任务n是不是当前任务,如果不是则跳转到标号1,否则交互ecx和current的内容,交换后的结果为ecx指向当前进程,current指向要切换过去的新进程,在执行长跳,%0代表输出输入寄存器列表中使用的第一个寄存器,即"m"(*&__tmp.a),这个寄存器保存了*&__tmp.a,而_tmp.a存放的是32位偏移,_tmp.b存放的是新任务的tss段选择符,长跳到段选择符会造成任务切换,这个是x86的硬件原理。

TSS指向的地址里面的内容见下图

对linux 0.11版本中switch_to()的理解_第2张图片

        而整个硬件切换的过程如下:

对linux 0.11版本中switch_to()的理解_第3张图片

执行上述过程的操作时机主要包括:

1.      当前任务队GDT中的TSS描述符执行JMP或CALL指令;

2.      当前任务队GDT或LDT中的任务门描述符执行JMP或CALL指令;

3.      中断或异常向量执行IDT表中的任务门描述符;

4.      当EFLAG中的NT标志置位时当前任务执行IRET指令

         switch_to中利用第一条进行切换。切换后后面的代码就不会执行了,只有等到重新切换回来的时候才会继续执行,它会判断原任务是否使用过协处理器,没有则退出,有的话要清掉cr0的的任务切换标志位TS。




你可能感兴趣的:(linux,0.11版本内核学习笔记)