《深入理解Linux内核3rd》学习笔记——进程切换(下):switch_to宏、__switch_to函数

switch_to宏

  switch_to宏代码如下,其中,prev是即将要被换出CPU的进程的描述符,next是即将得到CPU的进程的描述符。

switch_to宏
 1 #define switch_to(prev,next,last) do {                    \
 2     unsigned long esi,edi;                        \
 3     asm volatile("pushfl\n\t"                    \
 4              "pushl %%ebp\n\t"                    \
 5              "movl %%esp,%0\n\t"    /* save ESP */        \
 6              "movl %5,%%esp\n\t"    /* restore ESP */    \
 7              "movl $1f,%1\n\t"        /* save EIP */        \
 8              "pushl %6\n\t"        /* restore EIP */    \
 9              "jmp __switch_to\n"                \
10              "1:\t"                        \
11              "popl %%ebp\n\t"                    \
12              "popfl"                        \
13              :"=m" (prev->thread.esp),"=m" (prev->thread.eip),    \
14               "=a" (last),"=S" (esi),"=D" (edi)            \
15              :"m" (next->thread.esp),"m" (next->thread.eip),    \
16               "2" (prev), "d" (next));                \
17 while (0)

 

  该宏的工作步骤大致如下:

  1. prev的值送入eax,next的值送入edx(这里我从代码中没有看出来,原著上如是写,可能是从调用switch_to宏的switch_context或schedule函数中处理的)。
  2. 保护prev进程的eflags和ebp寄存器内容,这些内容保存在prev进程的内核堆栈中。
  3. 将prev的esp寄存器中的数据保存在prev->thread.esp中,即将prev进程的内核堆栈保存起来。
  4. 将next->thread.esp中的数据存入esp寄存器中,这是加载next进程的内核堆栈。
  5. 将数值1保存到prev->thread.eip中,该数值1其实就是代码中"1:\t"这行中的1。为了恢复prev进程执行时用。
  6. 将next->thread.eip压入next进程的内核堆栈中。这个值往往是数值1。
  7. 跳转到__switch_to函数处执行。
  8. 执行到这里,prev进程重新获得CPU,恢复prev进程的ebp和eflags内容。
  9. 将eax的内容存入last参数(这里我也没看出来,原著上如是写,只是在__switch_to函数中返回prev,该值是放在eax中的)。

 

__switch_to函数

  __switch_to函数采用FASTCALL调用模式,利用eax和edx传入两个参数的值。由于__switch_to中用了很多其他函数,这里首先介绍相关函数和宏,然后再讨论__switch_to函数。

 

  smp_process_id宏展开如下。该宏得到当前代码运行在哪个CPU上,返回CPU编号。current_thread_info函数返回当前运行着的进程的thread_info结构地址,该函数中让esp的值与上(THREAD_SIZE - 1)的逆,实际上,THREAD_SIZE - 1 = 8192 - 1 = 8191 = 0x1FFF,取反就是0xE000,就是让esp低13位清零,这样就通过内核堆栈得到thread_info结构地址。然后从该结构中得到cpu编号。

smp_process_id宏及其展开
/* smp_processor_id宏 */
#define smp_processor_id() __smp_processor_id()

/* __smp_processor_id宏 */
#define __smp_processor_id() (current_thread_info()->cpu)

/* current_thread_info函数 */
static inline struct thread_info *current_thread_info(void)
{
    
struct thread_info *ti;
    __asm__(
"andl %%esp,%0; ":"=r" (ti) : "0" (~(THREAD_SIZE - 1)));
    
return ti;
}

 

 

  per_cup宏展开如下。__switch_to函数中传入init_tss参数和CPU编号cpu给per_cpu宏,然后得到该CPU上的TSS指针。

per_cup宏及其展开
/* per_cpu宏 */
#define per_cpu(var, cpu) (*RELOC_HIDE(&per_cpu__##var, __per_cpu_offset[cpu]))

/* RELOC_HIDE宏 */
# define RELOC_HIDE(ptr, off)            \
  ({ unsigned 
long __ptr;                \
     __ptr 
= (unsigned long) (ptr);        \
    (
typeof(ptr)) (__ptr + (off)); })

/* __switch_to函数中使用per_cup的语句 */
struct tss_struct *tss = &per_cpu(init_tss, cpu);

 

  load_esp0函数定义如下。这里从thread_struct结构中加载esp0到tss中,即即将执行进程的esp0。当SEP打开时,还用wrmsr写入新的CS段选择子——即sysenter指令执行后的代码段。

load_esp0函数
/* load_esp0 函数定义*/
static inline void load_esp0(struct tss_struct *tss, struct thread_struct *thread)
{
    tss
->esp0 = thread->esp0;
    
/* This can only happen when SEP is enabled, no need to test "SEP"arately */
    
if (unlikely(tss->ss1 != thread->sysenter_cs)) {
        tss
->ss1 = thread->sysenter_cs;
        wrmsr(MSR_IA32_SYSENTER_CS, thread
->sysenter_cs, 0);
    }
}

/* wrmsr宏 */
#define wrmsr(msr,val1,val2) \
    __asm__ __volatile__(
"wrmsr" \
              : 
/* no outputs */ \
              : 
"c" (msr), "a" (val1), "d" (val2))

 

  Load_TLS函数定义如下。该函数中使用宏C(i)和per_cpu,先通过per_cpu得到CPU的GDT所在内存的地址,然后将3个thread_struct结构中的tls_array加载到GDT的TLS段中。其中GDT_ENTRY_TLS_MIN=6,这正是3个TLS在GDT中的索引,因此Load_TLS就加载了GDT中3个线程局部段(TLS)。

static inline void load_TLS(struct thread_struct *t, unsigned int cpu)
{
#define C(i) per_cpu(cpu_gdt_table, cpu)[GDT_ENTRY_TLS_MIN + i] = t->tls_array[i]
    C(
0); C(1); C(2);
#undef C
}

 

  现在来看__switch_to函数,其定义和注释如下。

__switch_to
struct task_struct fastcall * __switch_to(struct task_struct *prev_p, struct task_struct *next_p)
{
    
struct thread_struct *prev = &prev_p->thread,
                 
*next = &next_p->thread;
    
int cpu = smp_processor_id(); /* 得到当前代码运行的CPU编号 */
    
struct tss_struct *tss = &per_cpu(init_tss, cpu);  /* 得到当前CPU的TSS */

    
/* never put a printk in __switch_to... printk() calls wake_up*() indirectly */

    __unlazy_fpu(prev_p); 
/* 加载FPU、MMX、XMM的寄存器组 */

    
/*
     * Reload esp0, LDT and the page table pointer:
     
*/
    load_esp0(tss, next); 
/* 加载next的esp0到tss的esp0中 */

    
/*
     * Load the per-thread Thread-Local Storage descriptor.
     
*/
    load_TLS(next, cpu);  
/* 加载next的TLS到CPU的GDT的TLS中 */

    
/*
     * Save away %fs and %gs. No need to save %es and %ds, as
     * those are always kernel segments while inside the kernel.
     
*/
    asm 
volatile("movl %%fs,%0":"=m" (*(int *)&prev->fs)); /* 保存prev的fs寄存器 */
    asm 
volatile("movl %%gs,%0":"=m" (*(int *)&prev->gs)); /* 保存prev的gs寄存器 */

    
/*
     * Restore %fs and %gs if needed.
     
*/
    
if (unlikely(prev->fs | prev->gs | next->fs | next->gs)) {
        loadsegment(fs, next
->fs); /* 加载fs寄存器 */
        loadsegment(gs, next
->gs); /* 加载gs寄存器 */
    }

    
/*
     * Now maybe reload the debug registers
     
*/
    
if (unlikely(next->debugreg[7])) {
        loaddebug(next, 
0); /* 从next的thread_struct结构中加载调试信息到dr0-dr7寄存器 */
        loaddebug(next, 
1);
        loaddebug(next, 
2);
        loaddebug(next, 
3);
        
/* no 4 and 5 */
        loaddebug(next, 
6);
        loaddebug(next, 
7);
    }

    
if (unlikely(prev->io_bitmap_ptr || next->io_bitmap_ptr))
        handle_io_bitmap(next, tss); 
/* 从next得到IO允许位,并跟新tss->io_bitmap_base字段 */

    
return prev_p;
}

 

 

posted on 2010-06-02 20:37  小虎无忧  阅读(6175)  评论(2编辑  收藏  举报

导航