标签:内核
为了控制进程的执行,内核必须有能力挂起在CPU上运行的进程,并恢复以前挂起的某个进程的执行。这种行为被称为进程切换,任务切换或上下文切换。
尽管每个进程可以拥有属于自己的地址空间,但所有进程必须共享CPU寄存器。因此,在恢复一个进程执行前,内核必须确保每个寄存器装入了挂起进程时的值。
进程恢复执行前必须装入寄存器的一组称为硬件上下文(hardware context)。硬件上下文是进程可执行上下文的一个子集,因为可执行上下文包含进程执行时需要的所有信息。在linux中,进程硬件上下文的一部分放在TSS段,而剩余的部分存放在内核态信息堆栈中。
进程切换只发生在内核态。在执行进程切换之前,用户态进程使用的所有寄存器内容都已保存在内核态堆栈上。
80x86体系结构包括了一个特殊的段类型,叫任务状态段(Task State Segment,TSS)来存放硬件上下文。尽管Linux并不使用硬件上下文切换,但是强制它为系统中每个不同的CPU创建一个TSS。主要有两个理由:
1、当80x86的一个CPU从用户态切换到内核态时,它就从TSS中获取内核态堆栈的地址。
2、当用户态进程试图通过in或者out指令访问一个I/O端口是,CPU需要访问存放在TSS中的I/O许可权位图(permission bitmap)以检查进程是否有访问端口的权利。
thread字段
在每次进程切换时,被替换进程的硬件上下文必须保存在别处。不能像Intel原始设计那样把它保存在TSS中,因为Linux为每个处理器而不是为每个进程使用TSS。
因此,每个进程描述符包含一个类型为thread_struct的thread字段,只要进程切换出去,内核就把其他硬件上下文保存在这个结构中。
struct thread_struct { /* cached TLS descriptors. */ struct desc_struct tls_array[GDT_ENTRY_TLS_ENTRIES]; unsigned long esp0; 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; };
进程切换可能只发生在精心定义的点:schedule()函数。
从本质上说,每个进程切换由两步组成:
1、切换页全局目录以安装一个新的地址空间;
2、切换内核态堆栈和硬件上下文,因为硬件上下文提供了内核执行新进程所需要的所有信息,包含CPU寄存器。
IA-32架构处理器在程序应用中提供16个基本程序指令寄存器,可以被分为以下几组:
? General-purpose registers. These eight registers are available for storing operands and pointers.
? EAX — Accumulator for operands and results data
? EBX — Pointer to data in the DS segment
? ECX — Counter for string and loop operations
? EDX — I/O pointer
? ESI — Pointer to data in the segment pointed to by the DS register; source pointer for string operations
? EDI — Pointer to data (or destination) in the segment pointed to by the ES register; destination pointer for
string operations
? ESP — Stack pointer (in the SS segment)
? EBP — Pointer to data on the stack (in the SS segment)
? Segment registers. These registers hold up to six segment selectors.
? EFLAGS (program status and control) register. The EFLAGS register report on the status of the program
being executed and allows limited (application-program level) control of the processor.
? EIP (instruction pointer) register. The EIP register contains a 32-bit pointer to the next instruction to be
executed.
linux-2.6.11.1/include/asm-i386/system.h
#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)PUSHF/PUSHFD Push EFLAGS onto stack
这个程序做了以下工作:
1、在eax和edx寄存器中分别保存prev和next的值
2、把eflags和esp寄存器的内容保存在prev内核栈中。
3、把esp的内容保存到prev->thread.esp中以使该字段指向prev内核栈的栈顶
4、把next->thread.esp装入esp。此时内核开始在next的内核栈上操作,因此这条指令实际上完成了从prev到next的切换。由于进程描述符的地址和 内核栈的地址紧挨着,所以改变内核栈意味着改变当前进程。
5、把标记为1的地址存入prev->thread.eip。当被替换进程重新恢复执行时,进程执行被标记为1的那条指令。
6、宏把next->thread.eip的值压入next的内核栈。
7、跳到__switch_to()函数
8、这里被替换进程再次获得CPU;它执行一些指令恢复eflags和ebp寄存器的内容指令
9、拷贝eax寄存器的内容到第三个参数last标识的内存区域
eax寄存器指向刚被替换的进程的描述符
标签:内核
原文地址:http://blog.csdn.net/wangpeihuixyz/article/details/25474311