Posted on 2012-01-15 07:01
djx_zh 閱讀(5570)
評(píng)論(0) 編輯 收藏 引用
實(shí)現(xiàn)線程庫(kù)的一個(gè)核心問(wèn)題是實(shí)現(xiàn)線程的切換。線程切換主要做了兩件事:一是舊線程執(zhí)行環(huán)境的保存,二是新線程執(zhí)行環(huán)境的恢復(fù)。執(zhí)行環(huán)境主要指寄存器,棧的切換也是通過(guò)寄存器的切換來(lái)完成的。
非搶占式用戶(hù)級(jí)線程可以使用兩類(lèi)接口來(lái)實(shí)現(xiàn): longjmp和setjmp; swapcontext,makecontext,setcontext. 在此不再贅述。
搶占式用戶(hù)級(jí)線程切換線程切換的時(shí)機(jī)。 切換分兩種,一種是主動(dòng)切換,一種是被動(dòng)切換。主動(dòng)切換是非搶占式的;被動(dòng)切換發(fā)生在時(shí)間片用完之后,一個(gè)線程的時(shí)間片用完后就要強(qiáng)行切換到另外的線程。那么被動(dòng)切換很自然的要發(fā)生在時(shí)鐘中斷里。在linux里面我們使用alarm信號(hào)。既然如此,趕快動(dòng)手吧。
void timeHandler (int signo)
{
thread_list * old; if(signo != SIGALRM) return ;
signal(SIGALRM,timeHandler);
//! 如果有結(jié)束了的線程,釋放該線程。
...
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 );
}
}
}
} 我們會(huì)很悲觀的發(fā)現(xiàn),alarm信號(hào)只發(fā)生了一此。問(wèn)題出在哪兒呢?原來(lái)linux的信號(hào)是不可重入的。進(jìn)入信號(hào)處理函數(shù)之前,該信號(hào)被屏蔽,退出該信號(hào)處理函數(shù)之后該信號(hào)重新開(kāi)放。而在信號(hào)處理函數(shù)中發(fā)生longjmp后,程序就跳轉(zhuǎn)到其他線程,該信號(hào)處理函數(shù)不能退出。為了能再次進(jìn)入信號(hào)處理函數(shù),我們?cè)谇袚Q之前就要重新開(kāi)放該信號(hào)。我們可以使用siglongjmp/sigsetjmp代替longjmp/setjmp.
雖然該方法能夠?qū)崿F(xiàn)搶占功能,但總讓人覺(jué)得不舒服。沒(méi)有退出的信號(hào)處理函數(shù)讓人如骨鯁在喉。有沒(méi)有更優(yōu)美的方法呢?
先來(lái)看一下信號(hào)處理的流程:以alarm信號(hào)為例。
1。 alarm信號(hào)到達(dá),執(zhí)行權(quán)由用戶(hù)空間進(jìn)入內(nèi)核空間,棧自動(dòng)切換到內(nèi)核棧(內(nèi)核棧指針存放在TSS結(jié)構(gòu)的ESP0中)。同時(shí)用戶(hù)空間的執(zhí)行環(huán)境存放到內(nèi)核棧中。
2。 內(nèi)核進(jìn)行一些信號(hào)相關(guān)工作。 最后發(fā)現(xiàn)我們注冊(cè)了alarm信號(hào)的處理函數(shù),然后建立執(zhí)行信號(hào)handler的棧環(huán)境(通常在用戶(hù)棧棧頂),將已經(jīng)保存在內(nèi)核棧中的用戶(hù)執(zhí)行環(huán)境復(fù)制到信號(hào)處理函數(shù)棧。 然后通過(guò)reti從內(nèi)核返回到用戶(hù)空間,恢復(fù)用戶(hù)空間執(zhí)行環(huán)境,執(zhí)行信號(hào)處理函數(shù)。
3。 執(zhí)行完信號(hào)處理函數(shù)后,再次進(jìn)入內(nèi)核,注意此次進(jìn)入內(nèi)核后,內(nèi)核棧會(huì)再次自動(dòng)保存用戶(hù)空間執(zhí)行環(huán)境,但這不是我們需要的。所以?xún)?nèi)核會(huì)將信號(hào)處理函數(shù)棧中的用戶(hù)執(zhí)行環(huán)境復(fù)制回內(nèi)核棧。
4。 再次從內(nèi)核空間切換到用戶(hù)空間,用戶(hù)執(zhí)行環(huán)境自動(dòng)恢復(fù)到alarm信號(hào)到達(dá)時(shí)的執(zhí)行狀態(tài)。
分析后我們會(huì)發(fā)現(xiàn),當(dāng)我們執(zhí)行信號(hào)處理函數(shù)時(shí),棧頂是保存了用戶(hù)執(zhí)行環(huán)境的,通過(guò)更換這個(gè)執(zhí)行環(huán)境,就可以達(dá)到用戶(hù)態(tài)線程切換的目的。
#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);
}用戶(hù)執(zhí)行環(huán)境用sigcontext結(jié)構(gòu)體來(lái)描述, 在64位系統(tǒng)中,該結(jié)構(gòu)體位于 RBP + 7*8 位置處; 在32位系統(tǒng)中位于EBP+3*4位置處。
__asm( "lea (%%" bp_register ", %1), %0":"=r"(psig_context): "r"(OFFSET_TO_SIGCONTEXT* sizeof(ptr_size*))); 用于獲得該結(jié)構(gòu)體指針,然后就可以傳到 調(diào)度函數(shù)用戶(hù)線程切換了。
Vimium has been updated to
1.30.
x