string

string
posts - 27, comments - 177, trackbacks - 0, articles - 0
  C++博客 :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理

用户级线程切换

Posted on 2012-01-15 07:01 djx_zh 阅读(5489) 评论(0)  编辑 收藏 引用
实现线程库的一个核心问题是实现线程的切换。线程切换主要做了两件事:一是旧线程执行环境的保存,二是新线程执行环境的恢复。执行环境主要指寄存器,栈的切换也是通过寄存器的切换来完成的。
非抢占式用户级线程可以使用两类接口来实现: 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*))); 用于获得该结构体指针,然后就可以传到 调度函数用户线程切换了。

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理