文章目录
  1. 1. 环境
  2. 2. 进程切换
  3. 3. thread_struct
  4. 4. 内核堆栈切换

环境

CPU : x86 32bit
内核版本:2.6.18

进程切换

为了控制进程的执行,内核必须有能力挂起正在CPU上执行的进程,并恢复以前挂起的某个进程的执行,这叫做进程切换,任务切换,上下文切换

schedule()——>context_switch()——>switch_to()——>__switch_to()

schedule是主调度函数,当schedule()需要暂停A进程的执行而继续B进程的执行时,就发生了进程之间的切换。
进程切换主要有两部分:
1、切换全局页表项(这个切换工作由context_switch()完成;
2、切换内核堆栈和硬件上下文(__switch_to()主要完成硬件上下文切换,switch_to主要完成内核堆栈切换)。

thread_struct

1.一个进程的硬件上下文主要保存在thread_struct中
2.其他信息放在内核态堆栈中

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;
         unsigned long    iopl;
    /* max allowed port in the bitmap, in bytes: */
        unsigned long    io_bitmap_max;
};

内核堆栈切换

switch_to利用了prev,next,last三个参数:
prev:指向当前进程
next:指向被调度的进程
last : 指向当前进程

#define switch_to(prev,next,last) do {                    \
    unsigned long esi,edi;                        \
    //下面两步把EFLAGS和EBP入栈保存现场。
    asm volatile("pushfl\n\t"        /* Save flags */    \
         "pushl %%ebp\n\t"                    \
    //把ESP的值保存到prev->thread.esp,保存当前进程上下文
         "movl %%esp,%0\n\t"    /* save ESP */        \ 
    //把next->thread.esp值赋值给ESP,堆栈被切换进入到next进程
         "movl %5,%%esp\n\t"    /* restore ESP */    \
    //“$1f”指向lable 1
    //在prev进程的上下文设置返回地址,返回到下面标号为1处
    //一个进程被正常切换出时,保存的eip总是标号为1的那个位置
    //把label 1的地址赋值给prev->thread.eip
         "movl $1f,%1\n\t"        /* save EIP */        \
    //把next->thread.eip的值入栈
    //从next进程的上下文中取得该进程的返回地址,放入堆栈中
         "pushl %6\n\t"        /* restore EIP */    \
    //调用__switch_to进行硬件上下文切换
         "jmp __switch_to\n"                \
    //当这个进程再次被调度运行时,恢复在堆栈上的返回地址总是这个1。
         "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)