相分食有賰, 相搶食無份 |
- int pthread_create(pthread_t *restrict thread, const pthread_attr_t *restrict attr, void *(*start_routine)(void*), void *restrict arg);
- void pthread_exit(void *value_ptr);
- pthread_t pthread_self(void);
這篇文章說明 pthread_create(), pthread_exit() 我自己實作的過程, pthread_self() 感覺好像不難, 但也沒那麼單純。
[pthread_t pthread_self(void)]
pthread_self 把 pthread_t 當成 thread id 回傳, 這就造成我只能把 pthread_t 定義成一個數字, 雖然也可以把 pthread_t 定義成 struct, 但在這邊就不好處理了。
原本我把 pthread_t 定義為 struct, 只好改為
typedef unsigned long long pthread_t;
struct ThreadData { my_x32_jmp_buf jmp_buf_; }; typedef std::pairThreadPair;
另外準備 ThreadPair 當做管理 thread 的資料結構。
pthread_t pthread_self(void) { return thread_vec[current_index].first; }
pthread_self() 就這麼簡單。當目前的 thread 執行時, 傳回目前的 ThreadPair 資料結構。所以 current_index 指向正確的資料結構是很重要的, 計算錯誤就指到別的 thread 了。
[void pthread_exit(void *value_ptr)]
pthread_create 相對簡單一些, pthread_exit 就有點難倒我。
在「user mode pthread 實作 - simple_thread」我是用 while(1) 測試一個 thread, 但如果這個 thread 正常結束了, 應該要怎麼辦呢? 應該要正常離開吧。
但什麼才是正常離開呢?
schedule 不會再去把這個 thread 選出來執行, 否則這個 thread 永遠都會被執行, 但是由於 function 已經結束, 再選出來執行的時候, 只會亂跑, cpu 跳到不正確的地方 (因為已經沒有正常的程式碼了), 最後會造成整個 process segmentation fault。
另外一個問題是, 如果這個 thread 沒有呼叫 pthread_exit(), 我們希望可以作到在 thread 結束時, 會去呼叫 pthread_exit(), 覺得是不可能的任務嗎?
在這個 func3 thread 結束之後, 要讓他呼叫 pthread_exit(), 而 pthread_exit 會做一些事情, 將 func3 這個 thread 移除, 之後就不會再選 func3 來執行。
這樣應該會覺得已經很難了, 不過作業還不只這樣, 作業希望可以把 func3_ret 的值傳給 pthread_exit(void *value_ptr), 這樣在 pthread_exit(void *value_ptr) 印出 value_ptr 時, 會得到 33, list 1 的結果。
有點不可思議是嗎?
結果又是我自己把他想得太難了, 在看完「CS170: Project 3 - Thread Synchronization (20% of project score)」Implementation 之後, 原來有個這麼簡單的方式, 我恍然大悟。
先說明我自己那麼胡亂的想法。
怎麼在 func3 之後呼叫 pthread_exit(), 操作 stack, 在 stack 的 return address 把 pthread_exit 位址存入之後即可, 不算太難, 但如果要把 &func3_ret 傳給 pthread_exit 的參數呢? 有點難, 我先 push 一個 function push_arg, 再次 push pthread_exit, 讓 pthread_exit 可以取得 push_arg 的參數也就是 &func3_ret, 看起來很厲害, 聽不懂沒關係, 因為完全是多此一舉。
漂亮的解法是, 只要用一個 wrapper function 包住這個 thread function 即可, 文字說明可能不清楚, show me the code。
simple_thread.cpp L215 用了一個 wrap_routine 包住要執行的 thread function start_routine, 最後再呼叫 pthread_exit() 即可。
而 jmp_buf 的 eip 則是把 wrap_routine 填入即可。
而 wrap_routine 的 2 個參數: start_routine, arg, 就從 stack push 進去, simple_thread.cpp L251 ~ 257 的部份。
如此一來, 就算你的 thread funcion 沒呼叫 pthread_exit, wrap_routine 也會幫你呼叫, 也可以取得 start_routine 的 return value, 實在妙以。
這樣就順利解決 pthread_exit 取得 thread function return value 的問題, 再來由於這個 thread 已經結束, 那 pthread_exit 還要做什麼呢? 我是讓 pthread_exit 去挑下一個 thread function 來執行, 並在相關的資料結構標記目前的 thread 已經結束, 之後就不會挑這個 thread 來執行。
但是這邊有個討厭的難題, 由於存取到 global variable, 這個 global variable 在 signal handler 也會使用, 所以要注意到同步的問題, 我目前沒有處理。Todo
做這些是為了之後的 pthread_join 做準備。
[int pthread_create(pthread_t *restrict thread, const pthread_attr_t *restrict attr, void *(*start_routine)(void*), void *restrict arg)]
這裡有一個我之前忽略的問題, 沒有讓 main thread 繼續執行下去, 得補上這段。另外, 如果 main thread 結束, 所有的 thread 也要跟著結束。
list 2. L304 ~ 312, 就是在保存 main thread jmp_buf, list 2. L247 ~ 252 把 main thread 的資料結構加入 thread_vec, tid 固定在 1, 所以呼叫一次 pthread_create, 會產生 2 個 thread 資料結構, 一個是該 function, 另外一個是 main thread。
所以 signal handler 要保證在之後才能發動, 要不然 main thread 的 jmp_buf 沒設定, 就無法回到 main thread 了。我沒有處理此狀況, 不難, 先 block 該 signal 即可, setjmp 之後在 unblock。
其他就是在設定 jmp_buf 的 eip, esp, 和之前一樣。
source code:
https://github.com/descent/simple_thread/tree/master/cpp
thread 的上限:
cat /proc/sys/kernel/threads-max 我的系統是 39319
沒有留言:
張貼留言
使用 google 的 reCAPTCHA 驗證碼, 總算可以輕鬆留言了。
我實在受不了 spam 了, 又不想讓大家的眼睛花掉, 只好放棄匿名留言。這是沒辦法中的辦法了。留言的朋友需要有 google 帳號。