1. 程式人生 > >uclibc中LinuxThread模型與nptl線程庫一點不同

uclibc中LinuxThread模型與nptl線程庫一點不同

() flags lib 主線程 sum manage his 存在 str

一、uclibc中posix thread實現
在早期Linux內核對象線程支持不是那麽貼心的時候,用戶態的posix線程實現也很蹩腳,通俗的說,就是上梁不正下梁歪。對於Unix下重要的posix線程庫,libc的實現是通過所謂的LinuxThread模型來實現的,這個是試圖在內核不支持線程的基礎上模擬一個多線程,結果就是一個四不像,勉強可以用,但是很別扭。好消息是在2.6內核完善了對於線程的支持之後,用戶態也鹹與維新了,posix線程庫的實現也更加妖嬈了,這個實現就是現在大家常說的NPTL(Native Posix Thread Library),這個庫用起來是比較順手的,而且應該也在較早的版本中提供了,例如我剛一開始接觸的就是NTPL線程庫,沒有機會接觸這個古董的LinuxThread線程庫實現。這就有點遺憾,就好像《圍城》裏三閭大學的督學,張口閉口一個“兄弟我在英國的時候”,那還是很有震撼力的。在計算機中,你要是說,兄弟我當年玩DOS的時候時候,或者兄弟我玩LinuxThread的時候……,那也是很能讓人肅然起敬的。

不過皇天不負有心人,前一段時間這個古董的LinuxThread就讓我逮了個正著,抓住了青春的尾巴,和它來了個親密接觸。事情還要從我們編譯busbox說起,我們用的是uclibc實現,這個玩意據說編譯出來的可執行文件比較小,而且偏偏版本有比較老,具體有多老呢?用的是uclibc0.9.30版本,這個版本還沒有加入NPTL線程庫實現,所以使用的是LinuxThread線程庫,這也沒有什麽,偏偏它最後就出問題了,所以輕輕的圍觀了一下。
二、從pthread_attr_init開始
這個是Posix中一個標準的線程屬性初始化函數,但是具體該如何實現,可能是POSIX沒有說,沒有說的地方就是大家發揮主觀能動性的時候,就好像某些影視作品中的馬賽克,也就是不透明的意思,計算機中稱為implementation defined。這個地方uclibc的實現是無微不至的,做了很多功課,對整個結構中成員逐個初始化,兢兢業業。
uClibc-0.9.33\libpthread\linuxthreads\attr.c
int __pthread_attr_init(pthread_attr_t *attr)
{
size_t ps = __getpagesize ();

attr->__detachstate = PTHREAD_CREATE_JOINABLE;
attr->__schedpolicy = SCHED_OTHER;
attr->__schedparam.sched_priority = 0;
attr->__inheritsched = PTHREAD_EXPLICIT_SCHED
;
相對來說,glibc的實現就相對粗獷一些,其代碼為
glibc-2.7\nptl\pthread_attr_init.c
int
__pthread_attr_init_2_1 (attr)
pthread_attr_t *attr;
{
struct pthread_attr *iattr;

/* Many elements are initialized to zero so let us do it all at
once. This also takes care of clearing the bytes which are not
internally used. */
memset (attr, ‘\0‘, __SIZEOF_PTHREAD_ATTR_T);
可以看到,glibc中的實現用一個詞來概括,就是“簡單粗暴”,不管三七二十一,直接來個清零,這一點將會對新派生的進程和父進程的繼承性產生決定性影響。總體來說,就是當使用pthread_attr_init出的屬性傳遞給pthread_create創建線程時,uclibc創建的線程是雷打不動的,就是普通進程,優先級為正常;而glibc創建的線程則會集成pthread_create調用者線程的優先級。這樣一看,比較簡單,好像是竹筒倒豆子——直來直去的,但是往往現象沒有這裏說明的這麽顯而易見。例如你肚子上長個瘤,跑了各大醫院,問了各大老中醫都沒有機會而絕望時,有人告訴你,可能是褲子系的太緊,被帶扣給格的一樣,那種恍然大悟。至於是什麽現象,三言兩語說不清,所以此處省略一萬字。
三、manager線程
這個是LinuxThread一個重要特點,這個通過ps看系統線程的時候,你會發現如果通過pthread_create創建一個或者多個線程之後,LinuxThread會買N送一,會額外創建一個manager線程,這樣看到的系統中的線程數和調用的pthread_create次數多兩個,多的兩個一個是主線程,另一個是manager線程。這個線程的創建時在pthread_create執行的時候才創建的,如果不執行就不創建,執行第一次時創建。
uClibc-0.9.33\libpthread\linuxthreads\pthread.c
int __pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void * (*start_routine)(void *), void *arg)
{
pthread_descr self = thread_self();
struct pthread_request request;
int retval;
if (__builtin_expect (__pthread_manager_request, 0) < 0) {這個地方會判斷manager線程是否已經創建,如果沒有創建,則創建之。
if (__pthread_initialize_manager() < 0) return EAGAIN;
}
四、線程以進程形式展現
如果是使用uclibc中pthread_create創建的線程,通過ps可以看到,這些線程都是以進程的形式存在的,這一點乍一看,很詭異,當然也有很多人看都沒看一眼,所以也沒覺得詭異。這個實現就是早期內核對線程支持不夠完善導致的一個問題,這個屬性就是CLONE_THREAD屬性,uclibc中執行clone系統調用的時候沒有傳遞這個選項,為什麽沒有傳遞,可能就是因為當時內核還不支持這個屬性。uclibc中創建代碼為
uClibc-0.9.33\libpthread\linuxthreads\manager.c
pid = __clone(pthread_start_thread_event, stack_addr,
CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND | CLONE_SYSVSEM |
__pthread_sig_cancel, new_thread);
Glibc的創建為
#define CLONE_SIGNAL (CLONE_SIGHAND | CLONE_THREAD)
int clone_flags = (CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGNAL
| CLONE_SETTLS | CLONE_PARENT_SETTID
| CLONE_CHILD_CLEARTID | CLONE_SYSVSEM
#if __ASSUME_NO_CLONE_DETACHED == 0
| CLONE_DETACHED
#endif
| 0);

五、manager線程優先級動態調整
它作為管理者,優先級自然要比它創建的所有的線程的優先級要高,這樣manager執行的時候就不會被其它線程搶占,這一點作為一個manager還是很給力的。具體高多少呢?其實也沒必要高很多,只要高一個就可以了,這一點manager還是比較謙虛的,沒有說直接提到最高的99優先級。
uClibc-0.9.33\libpthread\linuxthreads\manager.c
/* Adjust priority of thread manager so that it always run at a priority
higher than all threads */

void __pthread_manager_adjust_prio(int thread_prio)
{
struct sched_param param;

if (thread_prio <= manager_thread->p_priority) return;
param.sched_priority =
thread_prio < __sched_get_priority_max(SCHED_FIFO)
? thread_prio + 1 : thread_prio;
__sched_setscheduler(manager_thread->p_pid, SCHED_FIFO, &param);
manager_thread->p_priority = thread_prio;
}

uclibc中LinuxThread模型與nptl線程庫一點不同