由于在内核中所有的内核段寄存器均为统一的,因此这里无需保存ES,CS,SS,DS,FS,GS;
CR3(Linux中没有使用LDT)已经在前面的switch_mm处理了;
由于Linux没使用TSS-previous task link field,其切换完全采用软件处理切换,故这里无需考虑TSS-previous task link field;
指令EIP会保存在task.thread.eip中,ESP会保存在task.thread.esp中,EBP,ESI,EDI会用显示指令入栈保存;
对于Linux而言,其仅仅使用到了CPU的4级机制中的0和3两级,使用方法如下:
当进程正运行在用户空间时,如果此时来了个中断,CPU将会执行:从TR->GDTR[i ]->TSS中取出当前的SS0:ESP0,从IDTR->IDT[i ]中取出执行代码CS:IP,将当前所有寄存器压到SS0:ESP0堆栈中,包括进程的SS3:ESP3,随后从CS:IP处开始执行代码。当中断代码执行完毕后,内核将会从进程堆栈中,将SS3:ESP3、CS:IP弹出,从而回到用户空间重新开始执行,此时并不需要CPU主动来切换级别了;从这里可知,CPU是需要TSS中的SS0和ESP0来进行高->低级别切换的,因此进程在切换时,必须要将自己的SS0和ESP0保存到TR-> GDTR[i ]->TSS的SS0和ESP0字段中去,其实,在Linux中,对于同一个CPU,所有的进程都使用一个TSS,只是在进程切换时,被切换到的进程将会把自己的ESP0保存到TSS.ESP0中去(在函数__switch_to中),那为什么不把自己的SS0也保存到TSS.SS0中呢,这是因为所有进程的SS0都是统一的,为内核的SS,而内核在初始化的时候,已经将该TSS.SS0设置为自己的SS,因此无需继续设置SS0;
至于EFLAGS为什么没有保存,这点在2.6中已经纠正,即执行了pushf和popf;
ECX为什么没有保存,则涉及到了如下的理由:i386 ABI / function calling sequence
Allregisters on the Intel386 are global and thus visible to both a callingand a called function. Registers %ebp, %ebx, %edi,%esi, and %esp'belong' to the calling function. In other words, a called functionmust preserve these registers' values for its caller. Remainingregisters 'belong' to the called function. If a calling function wantsto preserve such a register value across a function call, it must savethe value in its local stack frame.Some registers have assigned rolesin the standard calling sequence:
%esp: The stack pointerholds the limit of the current stack frame, which is the address of thestack’s bottom-most, valid word. At all times, the stack pointer shouldpoint to a word-aligned area.
......
%ecx and%edx:Scratch registers have no specified role in the standard callingsequence. Functions do not have to preserve their values for the caller.
......
Linux进程管理和X86进程管理的结合
从上面描述中,我们知道X86要求每个进程都必须有自己的TSS,在每次进程切换的时候,通过对应的TR[i ]+GDTRbase找到该进程的TSS,然后保存前一个任务的所有寄存器,同时将找到的TSS中所有的寄存器值恢复到系统对应的寄存器中,从而实现进程切换。
由于Linux实现进程切换的时候,并不采用该机制,但是却避不过X86这个机制,因此Linux内核设置了init_tss全局变量,在start_kernel-> trap_init->cpu_init初始化时,设置了对应的GDT[i ]指向该init_tss以及TR.index=i;此后,在Linux系统运行过程中,就再也不会改变TR以及该GDT[i ],对应X86来讲,就好像永远运行着1个进程;Linux使用自身的切换机制来实现了进程的切换,这些在上面的文章中已经说明,这里不在多说。
另一重要问题Linux必须面对:当从高级别切换到低级别时,会引起CPU运行级别的变化,例如从级别3到级别0,此时CPU需要获取0级别的SS0以及 ESP0(例如前面描述的当进程正运行在用户空间时来了个中断范例)来恢复SS:ESP,其设计方法就是从当前的TSS->SS0:ESP0中获取。为了适应CPU的这种设计,Linux内核在每次switch_to切换进程时,都将被切换来的进程的ESP0保存到init_tss.esp0中;另外由于Linux内核的SS0始终为KERNEL_SS保持不变,故无需每次切换都将其保存到init_tss.ss0中,只需要在Linux内核初始化时将init_tss.ss0设置为KERNEL_SS就可以了;
