Linux 0.12 switch_to切换过程
2014-07-18 12:53
453 查看
Linux 0.12 switch_to切换过程
switch_to源代码:/* * switch_to(n) should switch tasks to task nr n, first * checking that n isn't the current task, in which case it does nothing. * This also clears the TS-flag if the task we switched to has used * tha math co-processor latest. */ #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 )); \ }
汇编知识:
这段汇编代码分为四个部分,以":"号加以分割,一般形式为:
指令部:输出部:输入部:损坏部
在指令部中,数字前面加上前缀%, 如%0, %1表示使用寄存器样板操作数,可以使用的此类操作数的总数取决于具体CPU中通用寄存器的数量。那么这里的数字具体代表那些寄存器呢?这里就要看输出部和输入部了
操作数的编号从输出部的第一个约束(序号为0)开始,顺序下来,每个约束计数一次,表示约束条件的字母很多,主要有:
"m","v","o" ——表示内存单元; "r" ——表示任何寄存器; "q" ——表示eax,ebx,ecs,edx 之一; "i"和"h" ——表示直接操作数; "E"和"F" ——表示浮点数; "g" ——表示“任意”; "a","b","c","d" ——表示使用寄存器eax,ebx,ecx,edx "S"和"D" ——表示要求使用寄存器esi和edi "I" ——表示常数(0至31)
实例如下:
__asm__ __volatile__ ( LOCK "addl %1, %0" : "=m" (v->counter) : "ir" (i), "m" (v->counter));
这段代码的输出部为v->counter,存储在内存单元中,对于的寄存器编号为%0,
输入部:直接操作数i对应于任何寄存器,对应的寄存器编号为%1, v->counter存储在内存单元中,对应的寄存器编号为%2
没有损坏部
这段代码的含义是:将v->counter的值加上i,并把最终的结果写回到v->counter中
那么对应到switch_to的这段代码中,由于没有输出部,所以输入部对应参数如下:
"m" (&__tmp.a) ===> %0, &tmp.a存储在内存中
"m" (*&tmp.b) ===> %1, *&__tmp.b存储在内存中
"d" (_TSS(n)) ===> %2, _TSS(n)存储在edx中
"c" ((long) task
) ===> %3, (long) task
存储在ecx中
下面对switch_to汇编代码进行详细分析
这段代码的主要意思是:首先判断需要切换到的进程是否是当前进程,cmpl %%ecx,_current\n\t" \ "je 1f\n\t",如果是,则什么都不做,直接跳转到标号1处,退出程序; 否则进行进程上下文环境的切换。
"movw %%dx,%1\n\t"将_TSS(n)的值存储在tmp.b中,那么TSS(n)代表什么呢?
#define FIRST_TSS_ENTRY 4 #define _TSS(n) ((((unsigned long) n)<<4)+(FIRST_TSS_ENTRY<<3))
为了理解这段宏,需要知道GDT中各描述符的组织结构和TSS段选择符的结构
从图中可以看出,0 ---> NULL, 1 ---> code segment, 2 ---> data segment, 3 ---> syscall, 4 --->tss0, 5 ---> ldt0, 6 ---> tss1, 7 ---> ldt1, ...
__TSS(n)是为了获得进程n的段选择符,TSS段选择符结构如下:
图中 TI=0表示TSS在GDT中,1表示TSS在LDT中, RPL是请求特权级,控制内核态和用户态对段的访问,描述符索引就是在GDT或是LDT中的索引。如tss0的描述符索引为4.
宏
#define _TSS(n) ((((unsigned long) n)<<4)+(FIRST_TSS_ENTRY<<3))中,
FIRST_TSS_ENTRY<<3即为4<<3,这里的4表示GDT中第一个tss是从下标4开始的,左移3位是因为TSS段选择符的第三位用于其他用途;
(unsigned long) n)<<4中将进程编号n左移4位是为了保证每个进程的TSS段选择子在GDT中的索引均为偶数。TSS(0) = 1000000(二进制),对应下标为4, TSS(1) = 1100000(二进制),对应下标为6, TSS(2) = 1000000(二进制), 对应下标为8,以此类推...
因此,这里将TSS(n)的段选择符装入到 tmp.b中
"xchgl %%ecx,_current\n\t"当前任务变为task
, ecx =
被切换出的任务
ljmp %0\n\t或
ljmp *%0是进行进程环境切换的关键,先分析如下:
加入'',这是gas的语法,表示绝对跳转,如果程序中没有加,编译器会自己加上
*ljmp用法说明:
AS手册中,ljmp指令存在两种形式,即:
直接操作数跳转,此时操作数即为目标逻辑地址(选择子,偏移量), 即形如:ljmp $seg_selector, $offset的方式
使用内存操作数,按照AS手册规定,内存操作数必须使用''做前缀,即形如:ljmp mem48,其中内存位置mem48存放目标逻辑地址:高16bit存放的是set_selector, 低32bit存放的是offset.注意,这条指令的''只是表示间接跳转的意思,与C语言的''完全不同
回到switch_to源码,
ljmp %0用的是ljmp的第二种用法,
ljmp %0这条语句展开后相当于
ljmp *__tmp.a, 也就是跳转到地址&tmp.a中包含的48bit逻辑地址处。按照struct tmp的定义,这也意味着_tmp.a即为逻辑地址的offset部分,tmp.b的低16bit为seg_selector(高16bit无用)部分,在'ljmp %0'之前已经执行了
movw %%dx,%1将TSS(n)的段选择子装入了 tmp.b中。
通过以上说明,我们知道ljmp将跳转到TSS选择子所指向的地方,大致过程是:ljmp判断为TSS类型,于是就告诉硬件要切换任务,硬件首先要将当前的PC,esp, eax等现场信息保存在自己的TSS端描述符中,然后再将目标TSS段描述符中的pc, esp, eax的值拷贝至对应寄存器中,这些工作全部做完以后,内核就实现了进程上下文环境的切换,切换过程参考下图:
cmpl %%ecx,_last_task_used_math原任务使用过协处理器吗
jne 1f没有,直接退出
clts原任务使用过协处理器, 清cr0中的任务切换标志TS
ljmp 详解
下面用伪指令来描述Jump做了什么事情, 又是怎么区分这些事件的1. JMP(SelectorType Selector, int Offset) 2. { 3. SegAttributes Attributes; 4. SelectorType GSelector; 5. int Base, Limit, GOffset; 6. if((Selector & 0FFFCh) == 0) 7. SegmentException($GP, 0); 8. ReadDescriptor(Selector,&Attributes,&Base,&Limit,&GSelector,&GOffset); 9. if(Attributes.DType){ 10. CSDescriptorLoad(Selector, Attributes,Base,Limit, $GP); 11. if(Offset > CS.Limit) 12. SegmentException($GP,0); 13. CS.Selector= Selector; 14. CS.Selector.RPL = CPL; 15. EIP = Offset; 16. } 17. else{ 18. if((Attributes.DPL < CPL) || (Attributes.DPL < Selector.RPL)) 19. SegmentException($GP, Selector); 20. switch(Attributes.Type){ 21. case 1: 22. if(Attributes.P == 0) 23. SegmentException($NP, Selector); 24. TaskSwitch286(Selector, Attributes, Base, Limit, 0); 25. break; 26. case 5: 27. if(Attributes.P == 0) 28. SegmentException($NP, Selector); 29. TaskGate(GSelector, 0); 30. break; 31. case 9: 32. if(Attributes.P == 0) 33. SegmentException($NP, Selector); 34. TaskSwitch(Selector, Attributes, Base, Limit, 0); 35. break; 36. case 4: 37. if(Attributes.P == 0) 38. SegmentException($NP, Selector); 39. JumpGate286(GSelector, GOffset, $GP); 40. break; 41. case 12: 42. if(Attributes.P == 0) 43. SegmentException($NP, Selector); 44. JumpGate386(GSelector, GOffset, $GP); 45. break; 46. Default: 47. SegmentException($GP, Selector); 48. } 49. } 50.}
1.装入的参数为Selector 和 Offset
3.定义变量Attribute, 对应段描述符中的属性字段
4.定义变量Gselector, 如果是类型为门, 则此描述符存放的是选择子Selector和偏移Offset, 再根据Gselector找到对应的段描述符.
5.定义基地址, 界限和Goffset(偏移, 用于系统段或门)
6-7.选择子为空, 产生段异常.
8.根据选择子, 返回段描述符中的属性, 基地址和段界限. 如果为门, 则返回门选择子和偏移.
9.如果描述符为存储段
10.CSDescriptorLoad函数会进行特权级检查,如果出错,则会出现段异常. 再把Attribute,Base, Limit装入到CS 投影寄存器.(经常说的清CPU的prefetch queue)
11-12.检查是否越界, 是则产生段异常
13.单独装入Selector.(MOV指令无法修改CS寄存器,只能靠JUMP, CALL和IRET指令)
14.RPL装入CPL, 可以仔细思考一下为什么这么做?
15.EIP装入Offset. 和上面的CS组合起来, 就是CS:EIP, 这就实现了跳转了.
17.如果描述符类型为系统段或门
18-19.特权级检查, 出错则调用段异常函数
20.根据属性中的类型, 进行不同的处理
21-25.调用TaskSwitch286进行任务切换, 参数0表示无链接
26-30.调用任务门TaskGate() 函数, 0 表示 无链接
31-35.调用任务切换TaskSwitch() 函数
36-40.调用286 调用门JumpGate286()
41-45.调用386调用门JumpGate386()
接下来通过伪代码函数TaskSwitch()做了哪些事情
1. TaskSwitch(SelectorType Selector, SegAttributes Attributes, int Base, int Limit, int Linkage) 2. { 3. SelectorType OldTSS; 4. if(Limit < 103) 5. SegmentException($TS, Selector); 6. AccessTSSState(1); //Write 7. TR.Base = Base; 8. TR.Limit = Limit; 9. TR.Attributes = Attributes; 10. OldTSS = TR.Selector; 11. TR.Selector = Selector; 12. AccessTSSState(0); //read 13. if(Linkage == 1) 14. { 15. AccessLinear(TR.Base, 2, 0, 1, &OldTSS); 16. EFLAGS.NT = 1; 17. SetTSSBusy(Selector, 1); 18. } 19. else if(Linkage == -1) 20. SetTSSBusy(OldTSS, 0); 21. else if(Linkage == 0) 22. { 23. SetTSSBusy(OldTSS, 0); 24. SetTSSBusy(Selector, 1); 25. } 26. CR0.TS = 1; 27. CPL = CS.Selector.RPL; 28. LDTR.Attributes.Present = 0; 29. CS.Attributes.Present = 0; 30. SS.Attributes.Present = 0; 31. CS.Attributes.Present = 0; 32. DS.Attributes.Present = 0; 33. ES.Attributes.Present = 0; 34. FS.Attributes.Present = 0; 35. GS.Attributes.Present = 0; 36. if(LDTR.Selector.TI == 1) 37. SegmentException($TS, LDTR.Selector); 38. if((LDTR.Selector & 0FFFCh) == 0) 39. LDTR.Attributes.P = 0; 40. else 41. { 42. ReadDescriptor(LDTR.Selector, &Attributes, &Base, &Limit, &GSelector, &GOffset); 43. if((Attributes.DType == 1) 44. || (Attributes.Type != 2) 45. || (Attributes.Present == 0)) 46. SegmentException($TS, LDTR.Selector); 47. SetAccessed(LDTR.Selector); 48. LDTR.Attributes = Attributes; 49. LDTR.Base = Base; 50. LDTR.Limit = Limit; 51. } 52. JumpGate(CS.Selector, EIP, $TS); 53. SRegLoad(SS, SS.Selector, $TS); 54. SRegLoad(DS, SS.Selector, $TS); 55. SRegLoad(ES, SS.Selector, $TS); 56. SRegLoad(FS, SS.Selector, $TS); 57. SRegLoad(GS, SS.Selector, $TS); 58.}
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的伪代码:
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; }
Reference:
/article/9682527.html
/article/10903728.html
相关文章推荐
- Linux进程上下文切换过程context_switch详解--Linux进程的管理与调度(二十一)
- Linux进程(之)进程切换函数switch_to()解析
- Linux任务切换代码(switch_to)详解
- linux0.11任务切换switch_to
- Linux进程上下文切换过程context_switch详解--Linux进程的管理与调度(二十一)【转】
- Linux任务切换代码(switch_to)详解(转)
- Linux进程调度和切换过程分析
- The Linux Bootdisk HOWTO 之 3. Bootdisks and the boot process 启动盘与启动过程
- Linux 内核--任务0的运行(切换到用户模式)move_to_user_mode
- Linux 内核--任务0的运行(切换到用户模式)move_to_user_mode
- Linux操作系统学习_用户态与内核态之切换过程
- linux0.11中switch_to理解
- Linux 2.6的switch_to函数的分析
- Linux 内核--任务0的运行(切换到用户模式)move_to_user_mode
- 硬件文境的切换 -- __switch_to()
- Linux进程调度和切换过程分析
- Linux操作系统学习_用户态与内核态之切换过程
- Linux任务切换过程流程分析
- Linux switch_to()深入分析
- How to Install Open vSwitch on Linux