541 硬件支持

   Intel i386 体系结构包括了一个特殊的段类型,叫任务状态段(TSS,如图5.4所示。每个任务包含有它自己最小长度为104字节的TSS段,在/include/ i386/processor.h 中定义为tss_struct结构:

 

struct tss_struct {

        unsigned short  back_link,__blh;

        unsigned long   esp0;

        unsigned short  ss0,__ss0h;*0级堆栈指针,即Linux中的内核级 *

        unsigned long   esp1;     

        unsigned short  ss1,__ss1h; * 1级堆栈指针,未用*

        unsigned long   esp2;

        unsigned short  ss2,__ss2h; * 2级堆栈指针,未用*

        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, bitmap;

        unsigned long   io_bitmap[IO_BITMAP_SIZE+1];

         /*

        * pads the TSS to be cacheline-aligned (size is 0x100)

         */

        unsigned long __cacheline_filler[5];

};

   每个TSS有它自己 8字节的任务段描述符(Task State Segment Descriptor ,简称TSSD)。这个描述符包括指向TSS起始地址的32位基地址域,20位界限域,界限域值不能小于十进制104(由TSS段的最小长度决定)。TSS描述符存放在GDT中,它是GDT中的一个表项。

   


   

            5.4 Intel i386任务状态段及TR寄存器

后面将会看到,Linux在进程切换时,只用到TSS中少量的信息,因此Linux内核定义了另外一个数据结构,这就是thread_struct 结构:

 

struct thread_struct {

        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      * vm86_info;

         unsigned long           screen_bitmap;

        unsigned long           v86flags, v86mask, v86mode, saved_esp0;

/* IO permissions */

         int             ioperm;

        unsigned long   io_bitmap[IO_BITMAP_SIZE+1];

};

 

    用这个数据结构来保存cr2寄存器、浮点寄存器、调试寄存器及指定给Intel 80x86处理器的其他各种各样的信息。需要位图是因为ioperm(  ) iopl(  )系统调用可以允许用户态的进程直接访问特殊的I/O端口。尤其是,如果把eflag寄存器中的IOPL 域设置3,就允许用户态的进程访问对应的I/O访问权位图位为0的任何一个I/O端口。

     那么,进程到底是怎样进行切换的?

从第三章我们知道,在中断描述符表(IDT)中,除中断门、陷阱门和调用门外,还有一种“任务们”。任务门中包含有TSS段的选择符。当CPU因中断而穿过一个任务门时,就会将任务门中的段选择符自动装入TR寄存器,使TR指向新的TSS,并完成任务切换。CPU还可以通过JMPCALL指令实现任务切换,当跳转或调用的目标段(代码段)实际上指向GDT表中的一个TSS描述符项时,就会引起一次任务切换。

Intel的这种设计确实很周到,也为任务切换提供了一个非常简洁的机制。但是,由于i386的系统结构基本上是CISC的,通过JMP指令或CALL(或中断)完成任务的过程实际上是“复杂指令”的执行过程,其执行过程长达300多个CPU周期(一个POP指令占12CPU周期),因此,Linux内核并不完全使用i386CPU提供的任务切换机制。

由于i386CPU要求软件设置TRTSSLinux内核只不过“走过场”地设置TRTSS,以满足CPU的要求。但是,内核并不使用任务门,也不使用JMPCALL指令实施任务切换。内核只是在初始化阶段设置TR,使之指向一个TSS,从此以后再不改变TR的内容了。也就是说,每个CPU(如果有多个CPU)在初始化以后的全部运行过程中永远使用那个初始的TSS。同时,内核也不完全依靠TSS保存每个进程切换时的寄存器副本,而是将这些寄存器副本保存在各个进程自己的内核中(参见上一章task_struct结构的存放)。

这样以来,TSS中的绝大部分内容就失去了原来的意义。那么,当进行任务切换时,怎样自动更换堆栈?我们知道,新任务的内核栈指针(SS0ESP0)应当取自当前任务的TSS,可是,Linux中并不是每个任务就有一个TSS,而是每个CPU只有一个TSSIntel原来的意图是让TR的内容(即TSS)随着任务的切换而走马灯似地换,而在Linux内核中却成了只更换TSS中的SS0ESP0,而不更换TSS本身,也就是根本不更换TR的内容。这是因为,改变TSSSS0ESP0所化的开销比通过装入TR以更换一个TSS要小得多。因此,在Linux内核中,TSS并不是属于某个进程的资源,而是全局性的公共资源。在多处理机的情况下,尽管内核中确实有多个TSS,但是每个CPU仍旧只有一个TSS