用户级线程切换

实现线程库的一个核心问题是实现线程的切换。线程切换主要做了两件事:一是旧线程执行环境的保存,二是新线程执行环境的恢复。执行环境主要指寄存器,栈的切换也是通过寄存器的切换来完成的。
非抢占式用户级线程可以使用两类接口来实现: longjmp和setjmp; swapcontext,makecontext,setcontext. 在此不再赘述。
抢占式用户级线程切换
线程切换的时机。 切换分两种,一种是主动切换,一种是被动切换。主动切换是非抢占式的;被动切换发生在时间片用完之后,一个线程的时间片用完后就要强行切换到另外的线程。那么被动切换很自然的要发生在时钟中断里。在linux里面我们使用alarm信号。既然如此,赶快动手吧。
void  timeHandler ( int  signo)
{  
    thread_list 
*   old;     if (signo  !=  SIGALRM)  return  ;   
    signal(SIGALRM,timeHandler);   
    //! 如果有结束了的线程,释放该线程。
    ...   

    old  
=   sys.current;
    
if (old  ==   0 ){
        
if (sys.threads){
            sys.current  
=   sys.threads;
            longjmp(sys.current 
->  thread.buf,   1  );
        }
    }
else {
        sys.current  
=   GetNext(sys.current)
        
if (sys.current){
            
if   ( ! setjmp(old  ->  thread.buf, 1 )){
                longjmp(sys.current 
->  thread.buf,   1  );
            }
        }
    }
}
我们会很悲观的发现,alarm信号只发生了一此。问题出在哪儿呢?原来linux的信号是不可重入的。进入信号处理函数之前,该信号被屏蔽,退出该信号处理函数之后该信号重新开放。而在信号处理函数中发生longjmp后,程序就跳转到其他线程,该信号处理函数不能退出。为了能再次进入信号处理函数,我们在切换之前就要重新开放该信号。我们可以使用siglongjmp/sigsetjmp代替longjmp/setjmp.
虽然该方法能够实现抢占功能,但总让人觉得不舒服。没有退出的信号处理函数让人如骨鲠在喉。有没有更优美的方法呢?
先来看一下信号处理的流程:以alarm信号为例。
1。 alarm信号到达,执行权由用户空间进入内核空间,栈自动切换到内核栈(内核栈指针存放在TSS结构的ESP0中)。同时用户空间的执行环境存放到内核栈中。
2。 内核进行一些信号相关工作。 最后发现我们注册了alarm信号的处理函数,然后建立执行信号handler的栈环境(通常在用户栈栈顶),将已经保存在内核栈中的用户执行环境复制到信号处理函数栈。 然后通过reti从内核返回到用户空间,恢复用户空间执行环境,执行信号处理函数。
3。 执行完信号处理函数后,再次进入内核,注意此次进入内核后,内核栈会再次自动保存用户空间执行环境,但这不是我们需要的。所以内核会将信号处理函数栈中的用户执行环境复制回内核栈。
4。  再次从内核空间切换到用户空间,用户执行环境自动恢复到alarm信号到达时的执行状态。
分析后我们会发现,当我们执行信号处理函数时,栈顶是保存了用户执行环境的,通过更换这个执行环境,就可以达到用户态线程切换的目的。
#include <sys/ucontext.h>
# if __WORDSIZE == 64
#define OFFSET_TO_SIGCONTEXT 7 
# else
#define OFFSET_TO_SIGCONTEXT 3 
# endif
void
 timeHandler ( int  signo)
{
    unsigned 
int  i, j;
    sigcontext
*  psig_context;         
     if (signo  !=  SIGALRM)  return  ;
    signal(SIGALRM,timeHandler);
    __asm( 
" lea (%% "  bp_register  " , %1), %0 " : " =r " (psig_context):  " r " (OFFSET_TO_SIGCONTEXT *   sizeof (ptr_size * )));
    schedule(psig_context);

}
用户执行环境用sigcontext结构体来描述, 在64位系统中,该结构体位于 RBP + 7*8 位置处; 在32位系统中位于EBP+3*4位置处。  __asm(  " lea (%% "  bp_register  " , %1), %0 " : " =r " (psig_context):  " r " (OFFSET_TO_SIGCONTEXT *  sizeof (ptr_size * ))); 用于获得该结构体指针,然后就可以传到 调度函数用户线程切换了。
查看源代码
Vimium has been updated to 1.30. x

你可能感兴趣的:(用户级线程切换)