純使用者空間的搶占式多執行緒庫其實是很麻煩的一件事,在設計之前首先必須明白搶占式多執行緒的意義,其本質就是古老的unix多道程式設計,策略可以是分時的,也可以是其它任何的排程策略,不管什麼策略,機制要素都是底層的os核心和機器硬體提供的,對於x86上的linux來說,這些要素包括:分頁機制--提供程序間相同虛擬位址不衝突的棧,執行緒間不同虛擬位址不衝突的棧;時鐘中斷以及任意中斷機制--可以在不通知使用者程序的情況下中斷之,然後進行排程抉擇,該機制是排程策略的前提;fork機制--啟動新執行緒。只有完全模擬出以上等機制,多執行緒才是搶占式的。網上有篇文章用setjmp和longjmp實現了乙個協作多執行緒,由於何時排程必須由執行緒自己決定,因此那不能算是搶占式的。由於x86上的linux核心的分層設計並沒有提供下層對上層的呼叫,因此實現乙個純使用者空間的搶占式多線**的很麻煩。
純使用者空間搶占式多執行緒的外部環境有兩個要點:要點之一是抽象一台機器,該抽象的機器必須可以在程序外部將程序中斷,有一種辦法是向程序發訊號;要點之二是必須能夠得到程序當前的環境,比如所有暫存器,並且能儲存這個環境。內部環境也有兩個要點:其一是每個執行緒必須有乙個屬於自己的棧,由於這是純使用者空間的執行緒,因此最好自己用諸如malloc的方式動態分配;其二是每個執行緒必須可以自己啟動。下面是multithread的部分**:
//jmp_buf env[2];
//int idx[2];
void interrput_func (int sig)
}void thread_func1 ()
}void thread_func2()
}省略建立堆疊的**,thread_func2和thread_func1必須在不同的堆疊上方可無錯誤地執行。
在乙個父程序中fork-exec上述的程式multithread,然後用ptrace介面跟蹤之,在傳送sigusr訊號給multithread並被父程序得知後,父程序交替使用ptrace_getregs/ptrace_setregs儲存並設定上述程式的暫存器環境,如此就可以交替執行thread_func1和thread_func2了。
上述**中注釋呼叫setjmp的語句,本來用setjmp/longjmp+signal可以很好的模擬作業系統的多執行緒,可是jmp_buf儲存的context在呼叫函式返回後就會失效,而signal函式是在當前棧或者另分配的棧(使用sigaltstack)上執行的,無論哪種情況,最後都要呼叫sigreturn,因此在訊號處理函式中的setjmp是無效的,setjmp只針對當前棧幀有效,這裡的要點是,要想實現搶占式多執行緒,棧的切換是必然的,棧的切換不能影響暫存器環境的儲存,因此必須使用ptrace等機制顯式的設定程序的暫存器上下文,我們之所以還是使用了訊號機制,那是因為訊號機制可以中斷程序並且通知ptrace程序,從而給ptrace程序修改multithread程序暫存器上下文從而模擬多執行緒的機會。另外,執行緒的啟動也是乙個要點,在乙個執行緒的情況下,你幾乎不可能在當前的棧幀中啟動使用另乙個棧的另乙個執行緒,所有的基於馮諾依曼體系的機器本身都是單執行緒的,所謂的x86機器的多執行緒只是在程序這個層面的下層保留了一系列的上下文環境,然後不斷切換它們從而模擬了多個執行緒,正如os核心執行緒的建立及啟動需要底層系統呼叫一樣,使用者空間的多執行緒建立及啟動需要訊號機制(使用訊號僅僅是乙個例子,也可以用別的),同樣的理由,在馮氏機器上實現使用者空間多執行緒必須借助別的執行緒,比如ptrace的幫助。
如果setjmp可以得到被中斷前的上下文,並且longjmp可以設定被中斷後的上下文,並且不影響全域性變數的話,正如kernel的context_switch一樣,那麼multithread的interrput_func就會成為:
static int flag = 0; //flag標識執行緒是訊號處理進入的還是longjmp進入的。
void interrput_func (int sig)
//否則訊號返回,這個執行緒不是訊號處理進入的,而是longjmp進入的。}
純使用者空間搶占式多執行緒的設計
純使用者空間的搶占式多執行緒庫其實是很麻煩的一件事,在設計之前首先必須明白搶占式多執行緒的意義,其本質就是古老的unix多道程式設計,策略可以是分時的,也可以是其它任何的排程策略,不管什麼策略,機制要素都是底層的os核心和機器硬體提供的,對於x86上的linux來說,這些要素包括 分頁機制 提供程序...
Linux C實現純使用者態搶占式多執行緒
include include include include include include 僅僅是測試demo,分配4096位元組的stack足夠了。define stack size 4096 為什麼是72?因為我們在訊號處理中增加了乙個區域性變數,這樣pretcode的偏移就是32位元組了。...
Lua非搶占式多執行緒
當乙個協同正在執行時,不能在外部終止他。只能通過顯示的呼叫 yield 掛起他的執行。對於某些應用來說這個不存在問題,但有些應用對此是不能忍受的。不存在搶占式呼叫的程式是容易編寫的。不需要考慮同步帶來的 bugs,因為程式中的所有執行緒間的同步都是顯示的。你僅僅需要在協同 超出臨界區時呼叫 yiel...