《深入理解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;
}

 

 

你可能感兴趣的:(switch)