为了控制进程执行,内核必须有能力挂起正在cpu上运行的进程,并恢复以前挂起的某个进程的执行(进程切换、任务切换、上下文切换)。
每个进程可以拥有自己的地址空间,但所有进程必须共享cpu寄存器。在恢复一个进程的执行之前,内核必须确保每个寄存器装入了挂起进程时的数值。
进程恢复执行前必须装入寄存器的一组数据成为硬件上下文(hardware context)。硬件上下文是进程可执行上下文的一个子集,因为可执行上下文包含进程执行时所需要的所有信息。进程硬件上下文的一部分存放在TSS段,而剩余部分存放在内核态堆栈中。
进程切换只发生在内核态。在执行进程切换之前,用户态进程使用的所有寄存器内容都已保存在内核态堆栈上,这也包括ss和esp这对寄存器的内容(存储用户态堆栈指针的地址)。
80x86体系结构包含特殊段类型:任务段类型(task state segment,TSS)存放硬件上下文。linux并不使用硬件上下文切换,但是强制它为系统中每个不同的cpu创建一个TSS.
当80x86的一个cpu从用户态切换到内核态时,就从TSS中获取内核态堆栈的地址。
当用户态进程试图通过in或者out指令访问一个i/o端口时,cpu需要访问存放在TSS中的i/o许可权位图(permission bitmap)以检查该进程是否访问端口的权力。
当进程在用户态下执行in或者out指令时,控制单元执行下列操作:
1.它检查eflags寄存器中的2位iopl字段。如果该字段为3,控制单元就执行i/o指令。
2.访问tr寄存器以确定当前的tss和相应的i/o许可权位图。
3.检查i/o指令中指定的i/o端口在i/o许可权位图中对应的位。如果该位清零,这条i/o指令就执行,否则控制单元产生一个general protection异常。
下面的结构描述TSS格式。init_tss数组为系统上每个不同的cpu存放一个TSS.在每次进程切换时,内核都更新TSS的某些字段以便相应的cpu控制单元可以安全地检索到它所需要的信息。tSS反映了cpu上的当前进程的特权级,但不必为没有在运行的进程保留TSS.每个TSS有自己的8字节的任务状态段描述符(task state segment descriptor,TSSD).这个描述符包括指向TSS起始地址的32位base字段,20位limit字段。tssd的s标志位被清0,以表示相应的TSS是系统段的事实。
type字段置为11或者9以表示这个段实际上是一个TSS.在intel原始设计中,系统中的每个进程都应当指向自己的TSS;type字段的第二个有效位叫做busy位;如果进程正由cpu执行,则该位置1,否则置0.linux设计中,每个cpu只有一个TSS,因此busy位总是置为1.
linux创建的TSSD存放在全局描述符表(GDT)中,GDT的基地址存放在每个cpu的gdtr寄存器中。每个cpu的tr寄存器包含相应tss的tssd选择符,也包含了两个隐藏的非编程字段:tssd的base字段和limit字段。这样,处理器就能直接对tss寻址而不用从GDT中检索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));
进程每次切换时,被替换进程的硬件上下文必须保存在别处。不能如intel原始设计那样保存在TSS中,linux是为每个处理器而不是为了每个进程使用TSS.
每个进程描述符包含一个类型为thread_struct的thread字段,只要进程被切换出去,内核就把其硬件上下文保存在这个结构中。此结构包含的字段设计大部分cpu寄存器,但是不包括eax,ebx这些通用寄存器,它们的数值保留在内核堆栈中。
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;
};
#define INIT_THREAD { /
.vm86_info = NULL, /
.sysenter_cs = __KERNEL_CS, /
.io_bitmap_ptr = NULL, /
}
/*
* Note that the .io_bitmap member must be extra-big. This is 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.
*/
#define INIT_TSS { /
.esp0 = sizeof(init_stack) + (long)&init_stack, /
.ss0 = __KERNEL_DS, /
.ss1 = __KERNEL_CS, /
.ldt = GDT_ENTRY_LDT, /
.io_bitmap_base = INVALID_IO_BITMAP_OFFSET, /
.io_bitmap = { [ 0 ... IO_BITMAP_LONGS] = ~0 }, /
}
linux 2.6源代码情景分析笔记之进程7
最新推荐文章于 2020-03-20 17:35:57 发布