Linux进程切换
时间:2006-03-06 来源:ChinaE_OS
为了能保证不同的进程在CPU上运行,内核必须做到挂起正在CPU上运行的进程,唤醒其他进程,并且使其在CPU上正常运行。这个过程叫进程却换,或者上下文切换,任务切换。
1:硬件上下文
尽管每个进程都有自己的地址空间,但是所有的进程都共享CPU寄存器。所以当唤醒进程执行时,必须加载唤醒进程寄存器值到相应的寄存器。
硬件上下文是进程执行上下文的一个子集,Linux中一部分硬件上下文保存在进程描述符,一部分保存在内核栈。
进程切换非常频繁,所以一定要尽量减少保存和加载寄存器的时间。Linux2。6使用软件完成进程却换。进程切换只发生在内核态,进程切换的时候,所有用户态使用的寄存器内容被保存在内核栈,包括ss和esp,他们指明了用户态栈指针。
2:任务状态段(Task state segment(TSS))
80x86有一个特殊的段(任务状态段)保存硬件上下文,尽管Linux2。6不使用硬件上下文切换,但是必须为系统中不同的CPU建立TSS.,每个TSS都一个8位的TSS描述符,该描述符包括一个32位的Base域(指向TSS的开始地址)和一个20位的Limit域,S标示该TSS是否是系统段,0表示是系统段。
Linux创建的TSSD保存在全局描述符表(GDT),它的基地址保存在CPU的gdtr寄存器,tr寄存器保存着TSS的TSSD选择子。
Linux中用tss_struct描述TSS,代码如下:
struct tss_struct {
unsigned short back_link,__blh;
unsigned long esp0;
unsigned short ss0,__ss0h;
unsigned long esp1;
unsigned short ss1,__ss1h; /* ss1 is used to cache MSR_IA32_SYSENTER_CS */
unsigned long esp2;
unsigned short ss2,__ss2h;
unsigned long __cr3;
unsigned long eip;
unsigned long eflags;
unsigned long eax,ecx,edx,ebx;
unsigned long esp;
unsigned long ebp;
unsigned long esi;
unsigned long edi;
unsigned short es, __esh;
unsigned short cs, __csh;
unsigned short ss, __ssh;
unsigned short ds, __dsh;
unsigned short fs, __fsh;
unsigned short gs, __gsh;
unsigned short ldt, __ldth;
unsigned short trace, io_bitmap_base;
/*
* The extra 1 is there because the CPU will access an
* additional byte beyond the end of the IO permission
* bitmap. The extra byte must be all 1 bits, and must
* be within the limit.
*/
unsigned long io_bitmap[IO_BITMAP_LONGS + 1];
/*
* Cache the current maximum and the last task that used the bitmap:
*/
unsigned long io_bitmap_max;
struct thread_struct *io_bitmap_owner;
/*
* pads the TSS to be cacheline-aligned (size is 0x100)
*/
unsigned long __cacheline_filler[35];
/*
* .. and then another 0x100 bytes for emergency kernel stack
*/
unsigned long stack[64];
} __attribute__((packed));
3: thread域
在进程切换的时候,Linux不能将硬件上下文保存在TSS,因为Linux为每个处理器设计一个TSS,而不是为每个进程设计一个TSS。
每个进程描述符包含一个thread域(thread_struct)。进程切换的时候,内核保存硬件上下文在thread域中。
struct thread_struct {
/* cached TLS descriptors. */
struct desc_struct tls_array[GDT_ENTRY_TLS_ENTRIES];
unsigned long esp0;
unsigned long sysenter_cs;
unsigned long eip;
unsigned long esp;
unsigned long fs;
unsigned long gs;
/* Hardware debugging registers */
unsigned long debugreg[8]; /* %%db0-7 debug registers */
/* fault info */
unsigned long cr2, trap_no, error_code;
/* floating point info */
union i387_union i387;
/* virtual 86 mode info */
struct vm86_struct __user * vm86_info;
unsigned long screen_bitmap;
unsigned long v86flags, v86mask, saved_esp0;
unsigned int saved_fs, saved_gs;
/* IO permissions */
unsigned long *io_bitmap_ptr;
/* max allowed port in the bitmap, in bytes: */
unsigned long io_bitmap_max;
};
4:switch_to宏
该宏是与硬件相关的。宏中有三个参数:prev, next以及last,其中前两个是输入参数,后一个是输出参数。Prev指向将被替换进程的进程描述符,next指向将被执行的进程描述符。(1)介绍last的功能:
不是所有的进程却换都是两个参数,有的进程切换涉及到三个参数,如:A进程被切换出去,而B进程被激活,prev指向A进程描述符,next指向B进程描述符。一旦A进程无效,A的控制流就停止了。然后,当内核又切换掉进程C(一般C和B进程不是同一个进程),激活进程A,prev指向进程C,next指向进程A。当A唤醒后,它找到它以前的内核栈,其prev指向A,next指向B。这时,执行在A进程一般的调度器失去了对C的参照,而事实上,该参照对进程的切换非常有用。
Last的用途:保存C进程描述符内容。
(2) 保存步骤:
Ø 在进程切换前,将prev指向的内容保存在eax寄存器,即:prev在A进程的内核栈上分配。
Ø 当进程切换后,当A唤醒后,将eax的内容写回到A进程的内存空间,A进程通过第三个参数last标示。因为进程切换过程中,CPU的寄存器是不变的,内存空间获取C进程的描述符。在schedule中,last标示A进程的prev变量,所以prev的地址被C描述符地址覆盖。
Switch_to的代码如下:
#define switch_to(prev,next,last) do { \
unsigned long esi,edi; \
asm volatile("pushfl\n\t" \
"pushl %%ebp\n\t" \
"movl %%esp,%0\n\t" /* save ESP */ \
"movl %5,%%esp\n\t" /* restore ESP */ \
"movl $1f,%1\n\t" /* save EIP */ \
"pushl %6\n\t" /* restore EIP */ \
"jmp __switch_to\n" \
"1:\t" \
"popl %%ebp\n\t" \
"popfl" \
:"=m" (prev->thread.esp),"=m" (prev->thread.eip), \
"=a" (last),"=S" (esi),"=D" (edi) \
:"m" (next->thread.esp),"m" (next->thread.eip), \
"2" (prev), "d" (next)); \
} while (0)