国产一级a片免费看高清,亚洲熟女中文字幕在线视频,黄三级高清在线播放,免费黄色视频在线看

打開(kāi)APP
userphoto
未登錄

開(kāi)通VIP,暢享免費(fèi)電子書(shū)等14項(xiàng)超值服

開(kāi)通VIP
Linux下編程為什么多用進(jìn)程少用線程-編程討論之二--Linux軟件下載源碼編程文章資料...
一、Linux內(nèi)核對(duì)多進(jìn)程和多線程的支持方式
Linux的線程實(shí)現(xiàn)是在核外進(jìn)行的,核內(nèi)提供的是創(chuàng)建進(jìn)程的接口do_fork()。內(nèi)核提供了兩個(gè)系統(tǒng)調(diào)用__clone()和fork(),最終都用不同的參數(shù)調(diào)用do_fork()核內(nèi)API。 do_fork() 提供了很多參數(shù),包括CLONE_VM(共享內(nèi)存空間)、CLONE_FS(共享文件系統(tǒng)信息)、CLONE_FILES(共享文件描述符表)、CLONE_SIGHAND(共享信號(hào)句柄表)和CLONE_PID(共享進(jìn)程ID,僅對(duì)核內(nèi)進(jìn)程,即0號(hào)進(jìn)程有效)。
當(dāng)使用fork系統(tǒng)調(diào)用產(chǎn)生多進(jìn)程時(shí),內(nèi)核調(diào)用do_fork()不使用任何共享屬性,進(jìn)程擁有獨(dú)立的運(yùn)行環(huán)境。
當(dāng)使用pthread_create()來(lái)創(chuàng)建線程時(shí),則最終設(shè)置了所有這些屬性來(lái)調(diào)用__clone(),而這些參數(shù)又全部傳給核內(nèi)的do_fork(),從而創(chuàng)建的"進(jìn)程"擁有共享的運(yùn)行環(huán)境,只有棧是獨(dú)立的,由 __clone()傳入。
即:Linux下不管是多線程編程還是多進(jìn)程編程,最終都是用do_fork實(shí)現(xiàn)的多進(jìn)程編程,只是進(jìn)程創(chuàng)建時(shí)的參數(shù)不同,從而導(dǎo)致有不同的共享環(huán)境。
Linux線程在核內(nèi)是以輕量級(jí)進(jìn)程的形式存在的,擁有獨(dú)立的進(jìn)程表項(xiàng),而所有的創(chuàng)建、同步、刪除等操作都在核外pthread庫(kù)中進(jìn)行。pthread 庫(kù)使用一個(gè)管理線程(__pthread_manager() ,每個(gè)進(jìn)程獨(dú)立且唯一)來(lái)管理線程的創(chuàng)建和終止,為線程分配線程ID,發(fā)送線程相關(guān)的信號(hào),而主線程pthread_create()) 的調(diào)用者則通過(guò)管道將請(qǐng)求信息傳給管理線程。
/************關(guān)于本文檔********************************************
*filename: Linux內(nèi)核中線程的實(shí)現(xiàn)-Linux下編程為什么多用進(jìn)程少用線程之二
*purpose: 探討Linux下多進(jìn)程和多線程的關(guān)系
*wrote by: zhoulifa(zhoulifa@163.com) 周立發(fā)(http://zhoulifa.bokee.com)
Linux愛(ài)好者 Linux知識(shí)傳播者 SOHO族 開(kāi)發(fā)者 最擅長(zhǎng)C語(yǔ)言編程
*date time:2006-07-12 21:20:00
*Note: 任何人可以任意復(fù)制代碼并運(yùn)用這些文檔,當(dāng)然包括你的商業(yè)用途
* 但請(qǐng)遵循GPL
*Hope:希望越來(lái)越多的人貢獻(xiàn)自己的力量,為科學(xué)技術(shù)發(fā)展出力
*********************************************************************/
二、Linux下輕量級(jí)進(jìn)程
很多朋友都說(shuō)使用多線程的好處是資源占用少,其隱含之意就是說(shuō)進(jìn)程占用資源比線程多,對(duì)吧?
但實(shí)際上Linux下多進(jìn)程是否就真的點(diǎn)用很多資源呢?暫且不說(shuō)進(jìn)程是否比線程占用資源多,就進(jìn)程占用資源的多少情況而言,Linux確實(shí)是做得相當(dāng)節(jié)省的。產(chǎn)生一個(gè)多進(jìn)程時(shí)肯定是要產(chǎn)生的一點(diǎn)內(nèi)存是要復(fù)制進(jìn)程表項(xiàng),即一個(gè)task_struct結(jié)構(gòu),但這個(gè)結(jié)構(gòu)本身做得相當(dāng)小巧。其它對(duì)于一個(gè)進(jìn)程來(lái)說(shuō)必須有的數(shù)據(jù)段、代碼段、堆棧段是不是全盤(pán)復(fù)制呢?對(duì)于多進(jìn)程來(lái)說(shuō),代碼段是肯定不用復(fù)制的,因?yàn)楦高M(jìn)程和各子進(jìn)程的代碼段是相同的,數(shù)據(jù)段和堆棧段呢?也不一定,因?yàn)樵贚inux里廣泛使用的一個(gè)技術(shù)叫copy-on-write,即寫(xiě)時(shí)拷貝。copy-on-write意味著什么呢?意味著資源節(jié)省,假設(shè)有一個(gè)變量x在父進(jìn)程里存在,當(dāng)這個(gè)父進(jìn)程創(chuàng)建一個(gè)子進(jìn)程或多個(gè)子進(jìn)程時(shí)這個(gè)變量x是否復(fù)制到了子進(jìn)程的內(nèi)存空間呢?不會(huì)的,子進(jìn)程和父進(jìn)程使用同一個(gè)內(nèi)存空間的變量,但當(dāng)子進(jìn)程或父進(jìn)程要改變變量x的值時(shí)就會(huì)復(fù)制該變量,從而導(dǎo)致父子進(jìn)程里的變量值不同。
下面這段測(cè)試代碼顯示了父子進(jìn)程變量不能共享:
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#define PARENTSLEEP 5
#define INTERVALSLEEP 3
int main(int argc, char ** argv)
{
int x = 0;
pid_t chldpid;
x = 5;
chldpid = fork();
if(chldpid < 0){
perror("fork");
return 0;
}
else if(chldpid > 0){
printf("here is the parent process\n");
printf("variable x(in parent) is:%d\n", x);
x = 8;
sleep(PARENTSLEEP);
printf("again variable x(in parent) is:%d\n", x);
sleep(INTERVALSLEEP);
printf("last variable x(in parent) is:%d\n", x);
waitpid(0, NULL, 0);
}
else{
printf("\there is the child process\n");
printf("\tvariable x(in child) is:%d\n", x);
sleep(INTERVALSLEEP);
printf("\tagain variable x(in child) is:%d\n", x);
sleep(INTERVALSLEEP);
x = 10;
sleep(INTERVALSLEEP);
printf("\tlast variable x(in child) is:%d\n", x);
}
return 0;
}
其運(yùn)行結(jié)果如下:
/*
here is the child process
variable x(in child) is:5
here is the parent process
variable x(in parent) is:5
again variable x(in child) is:5
again variable x(in parent) is:8
last variable x(in parent) is:8
last variable x(in child) is:10
*/
從上述代碼可以看到,父子進(jìn)程變量是互不影響的。但遺憾的是由于父子進(jìn)程地址空間是完全隔開(kāi)的,變量的地址可以是完全相同的,所以我沒(méi)辦法用代碼驗(yàn)證變量確實(shí)是不同的變量,有誰(shuí)能想到一段驗(yàn)證的代碼呢?
三、很好的文章
下面看一篇文章吧,因?yàn)槲矣X(jué)得我暫時(shí)不能寫(xiě)出更好于此的言辭,文章出處是 http://ww-128.ibm.com/developerworks/cn/linux/kernel/l-thread/
/**************引用開(kāi)始**************/
Linux 線程實(shí)現(xiàn)機(jī)制分析
級(jí)別: 初級(jí)
楊沙洲國(guó)防科技大學(xué)計(jì)算機(jī)學(xué)院
2003 年 5 月 19 日
自從多線程編程的概念出現(xiàn)在 Linux 中以來(lái),Linux 多線應(yīng)用的發(fā)展總是與兩個(gè)問(wèn)題脫不開(kāi)干系:兼容性、效率。本文從線程模型入手,通過(guò)分析目前 Linux 平臺(tái)上最流行的 LinuxThreads 線程庫(kù)的實(shí)現(xiàn)及其不足,描述了 Linux 社區(qū)是如何看待和解決兼容性和效率這兩個(gè)問(wèn)題的。
按照教科書(shū)上的定義,進(jìn)程是資源管理的最小單位,線程是程序執(zhí)行的最小單位。在操作系統(tǒng)設(shè)計(jì)上,從進(jìn)程演化出線程,最主要的目的就是更好的支持SMP以及減?。ㄟM(jìn)程/線程)上下文切換開(kāi)銷(xiāo)。
無(wú)論按照怎樣的分法,一個(gè)進(jìn)程至少需要一個(gè)線程作為它的指令執(zhí)行體,進(jìn)程管理著資源(比如cpu、內(nèi)存、文件等等),而將線程分配到某個(gè)cpu上執(zhí)行。一個(gè)進(jìn)程當(dāng)然可以擁有多個(gè)線程,此時(shí),如果進(jìn)程運(yùn)行在SMP機(jī)器上,它就可以同時(shí)使用多個(gè)cpu來(lái)執(zhí)行各個(gè)線程,達(dá)到最大程度的并行,以提高效率;同時(shí),即使是在單cpu的機(jī)器上,采用多線程模型來(lái)設(shè)計(jì)程序,正如當(dāng)年采用多進(jìn)程模型代替單進(jìn)程模型一樣,使設(shè)計(jì)更簡(jiǎn)潔、功能更完備,程序的執(zhí)行效率也更高,例如采用多個(gè)線程響應(yīng)多個(gè)輸入,而此時(shí)多線程模型所實(shí)現(xiàn)的功能實(shí)際上也可以用多進(jìn)程模型來(lái)實(shí)現(xiàn),而與后者相比,線程的上下文切換開(kāi)銷(xiāo)就比進(jìn)程要小多了,從語(yǔ)義上來(lái)說(shuō),同時(shí)響應(yīng)多個(gè)輸入這樣的功能,實(shí)際上就是共享了除cpu以外的所有資源的。
針對(duì)線程模型的兩大意義,分別開(kāi)發(fā)出了核心級(jí)線程和用戶級(jí)線程兩種線程模型,分類(lèi)的標(biāo)準(zhǔn)主要是線程的調(diào)度者在核內(nèi)還是在核外。前者更利于并發(fā)使用多處理器的資源,而后者則更多考慮的是上下文切換開(kāi)銷(xiāo)。在目前的商用系統(tǒng)中,通常都將兩者結(jié)合起來(lái)使用,既提供核心線程以滿足smp系統(tǒng)的需要,也支持用線程庫(kù)的方式在用戶態(tài)實(shí)現(xiàn)另一套線程機(jī)制,此時(shí)一個(gè)核心線程同時(shí)成為多個(gè)用戶態(tài)線程的調(diào)度者。正如很多技術(shù)一樣,"混合"通常都能帶來(lái)更高的效率,但同時(shí)也帶來(lái)更大的實(shí)現(xiàn)難度,出于"簡(jiǎn)單"的設(shè)計(jì)思路, Linux從一開(kāi)始就沒(méi)有實(shí)現(xiàn)混合模型的計(jì)劃,但它在實(shí)現(xiàn)上采用了另一種思路的"混合"。
在線程機(jī)制的具體實(shí)現(xiàn)上,可以在操作系統(tǒng)內(nèi)核上實(shí)現(xiàn)線程,也可以在核外實(shí)現(xiàn),后者顯然要求核內(nèi)至少實(shí)現(xiàn)了進(jìn)程,而前者則一般要求在核內(nèi)同時(shí)也支持進(jìn)程。核心級(jí)線程模型顯然要求前者的支持,而用戶級(jí)線程模型則不一定基于后者實(shí)現(xiàn)。這種差異,正如前所述,是兩種分類(lèi)方式的標(biāo)準(zhǔn)不同帶來(lái)的。
當(dāng)核內(nèi)既支持進(jìn)程也支持線程時(shí),就可以實(shí)現(xiàn)線程-進(jìn)程的"多對(duì)多"模型,即一個(gè)進(jìn)程的某個(gè)線程由核內(nèi)調(diào)度,而同時(shí)它也可以作為用戶級(jí)線程池的調(diào)度者,選擇合適的用戶級(jí)線程在其空間中運(yùn)行。這就是前面提到的"混合"線程模型,既可滿足多處理機(jī)系統(tǒng)的需要,也可以最大限度的減小調(diào)度開(kāi)銷(xiāo)。絕大多數(shù)商業(yè)操作系統(tǒng)(如Digital Unix、Solaris、Irix)都采用的這種能夠完全實(shí)現(xiàn)POSIX1003.1c標(biāo)準(zhǔn)的線程模型。在核外實(shí)現(xiàn)的線程又可以分為"一對(duì)一"、"多對(duì)一"兩種模型,前者用一個(gè)核心進(jìn)程(也許是輕量進(jìn)程)對(duì)應(yīng)一個(gè)線程,將線程調(diào)度等同于進(jìn)程調(diào)度,交給核心完成,而后者則完全在核外實(shí)現(xiàn)多線程,調(diào)度也在用戶態(tài)完成。后者就是前面提到的單純的用戶級(jí)線程模型的實(shí)現(xiàn)方式,顯然,這種核外的線程調(diào)度器實(shí)際上只需要完成線程運(yùn)行棧的切換,調(diào)度開(kāi)銷(xiāo)非常小,但同時(shí)因?yàn)楹诵男盘?hào)(無(wú)論是同步的還是異步的)都是以進(jìn)程為單位的,因而無(wú)法定位到線程,所以這種實(shí)現(xiàn)方式不能用于多處理器系統(tǒng),而這個(gè)需求正變得越來(lái)越大,因此,在現(xiàn)實(shí)中,純用戶級(jí)線程的實(shí)現(xiàn),除算法研究目的以外,幾乎已經(jīng)消失了。
Linux內(nèi)核只提供了輕量進(jìn)程的支持,限制了更高效的線程模型的實(shí)現(xiàn),但Linux著重優(yōu)化了進(jìn)程的調(diào)度開(kāi)銷(xiāo),一定程度上也彌補(bǔ)了這一缺陷。目前最流行的線程機(jī)制LinuxThreads所采用的就是線程-進(jìn)程"一對(duì)一"模型,調(diào)度交給核心,而在用戶級(jí)實(shí)現(xiàn)一個(gè)包括信號(hào)處理在內(nèi)的線程管理機(jī)制。Linux-LinuxThreads的運(yùn)行機(jī)制正是本文的描述重點(diǎn)。
最初的進(jìn)程定義都包含程序、資源及其執(zhí)行三部分,其中程序通常指代碼,資源在操作系統(tǒng)層面上通常包括內(nèi)存資源、IO資源、信號(hào)處理等部分,而程序的執(zhí)行通常理解為執(zhí)行上下文,包括對(duì)cpu的占用,后來(lái)發(fā)展為線程。在線程概念出現(xiàn)以前,為了減小進(jìn)程切換的開(kāi)銷(xiāo),操作系統(tǒng)設(shè)計(jì)者逐漸修正進(jìn)程的概念,逐漸允許將進(jìn)程所占有的資源從其主體剝離出來(lái),允許某些進(jìn)程共享一部分資源,例如文件、信號(hào),數(shù)據(jù)內(nèi)存,甚至代碼,這就發(fā)展出輕量進(jìn)程的概念。Linux內(nèi)核在 2.0.x版本就已經(jīng)實(shí)現(xiàn)了輕量進(jìn)程,應(yīng)用程序可以通過(guò)一個(gè)統(tǒng)一的clone()系統(tǒng)調(diào)用接口,用不同的參數(shù)指定創(chuàng)建輕量進(jìn)程還是普通進(jìn)程。在內(nèi)核中, clone()調(diào)用經(jīng)過(guò)參數(shù)傳遞和解釋后會(huì)調(diào)用do_fork(),這個(gè)核內(nèi)函數(shù)同時(shí)也是fork()、vfork()系統(tǒng)調(diào)用的最終實(shí)現(xiàn):
int do_fork(unsigned long clone_flags, unsigned long stack_start,
struct pt_regs *regs, unsigned long stack_size)其中的clone_flags取自以下宏的"或"值:
#define CSIGNAL 0x000000ff /* signal mask to be sent at exit */
#define CLONE_VM 0x00000100 /* set if VM shared between processes */
#define CLONE_FS 0x00000200 /* set if fs info shared between processes */
#define CLONE_FILES 0x00000400 /* set if open files shared between processes */
#define CLONE_SIGHAND 0x00000800 /* set if signal handlers and blocked signals shared */
#define CLONE_PID 0x00001000 /* set if pid shared */
#define CLONE_PTRACE 0x00002000 /* set if we want to let tracing continue on the child too */
#define CLONE_VFORK 0x00004000 /* set if the parent wants the child to wake it up on mm_release */
#define CLONE_PARENT 0x00008000 /* set if we want to have the same parent as the cloner */
#define CLONE_THREAD 0x00010000 /* Same thread group? */
#define CLONE_NEWNS 0x00020000 /* New namespace group? */
#define CLONE_SIGNAL (CLONE_SIGHAND | CLONE_THREAD)
在do_fork()中,不同的clone_flags將導(dǎo)致不同的行為,對(duì)于LinuxThreads,它使用(CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND)參數(shù)來(lái)調(diào)用clone()創(chuàng)建"線程",表示共享內(nèi)存、共享文件系統(tǒng)訪問(wèn)計(jì)數(shù)、共享文件描述符表,以及共享信號(hào)處理方式。本節(jié)就針對(duì)這幾個(gè)參數(shù),看看Linux內(nèi)核是如何實(shí)現(xiàn)這些資源的共享的。
1.CLONE_VM
do_fork ()需要調(diào)用copy_mm()來(lái)設(shè)置task_struct中的mm和active_mm項(xiàng),這兩個(gè)mm_struct數(shù)據(jù)與進(jìn)程所關(guān)聯(lián)的內(nèi)存空間相對(duì)應(yīng)。如果do_fork()時(shí)指定了CLONE_VM開(kāi)關(guān),copy_mm()將把新的task_struct中的mm和active_mm設(shè)置成與 current的相同,同時(shí)提高該mm_struct的使用者數(shù)目(mm_struct::mm_users)。也就是說(shuō),輕量級(jí)進(jìn)程與父進(jìn)程共享內(nèi)存地址空間,由下圖示意可以看出mm_struct在進(jìn)程中的地位:
2.CLONE_FS
task_struct中利用fs (struct fs_struct *)記錄了進(jìn)程所在文件系統(tǒng)的根目錄和當(dāng)前目錄信息,do_fork()時(shí)調(diào)用copy_fs()復(fù)制了這個(gè)結(jié)構(gòu);而對(duì)于輕量級(jí)進(jìn)程則僅增加fs- >count計(jì)數(shù),與父進(jìn)程共享相同的fs_struct。也就是說(shuō),輕量級(jí)進(jìn)程沒(méi)有獨(dú)立的文件系統(tǒng)相關(guān)的信息,進(jìn)程中任何一個(gè)線程改變當(dāng)前目錄、根目錄等信息都將直接影響到其他線程。
3.CLONE_FILES
一個(gè)進(jìn)程可能打開(kāi)了一些文件,在進(jìn)程結(jié)構(gòu)task_struct中利用files(struct files_struct *)來(lái)保存進(jìn)程打開(kāi)的文件結(jié)構(gòu)(struct file)信息,do_fork()中調(diào)用了copy_files()來(lái)處理這個(gè)進(jìn)程屬性;輕量級(jí)進(jìn)程與父進(jìn)程是共享該結(jié)構(gòu)的,copy_files() 時(shí)僅增加fils->count計(jì)數(shù)。這一共享使得任何線程都能訪問(wèn)進(jìn)程所維護(hù)的打開(kāi)文件,對(duì)它們的操作會(huì)直接反映到進(jìn)程中的其他線程。
4.CLONE_SIGHAND
每一個(gè)Linux進(jìn)程都可以自行定義對(duì)信號(hào)的處理方式,在task_struct中的sig(struct signal_struct)中使用一個(gè)struct k_sigaction結(jié)構(gòu)的數(shù)組來(lái)保存這個(gè)配置信息,do_fork()中的copy_sighand()負(fù)責(zé)復(fù)制該信息;輕量級(jí)進(jìn)程不進(jìn)行復(fù)制,而僅僅增加signal_struct::count計(jì)數(shù),與父進(jìn)程共享該結(jié)構(gòu)。也就是說(shuō),子進(jìn)程與父進(jìn)程的信號(hào)處理方式完全相同,而且可以相互更改。
do_fork()中所做的工作很多,在此不詳細(xì)描述。對(duì)于SMP系統(tǒng),所有的進(jìn)程fork出來(lái)后,都被分配到與父進(jìn)程相同的cpu上,一直到該進(jìn)程被調(diào)度時(shí)才會(huì)進(jìn)行cpu選擇。
盡管Linux支持輕量級(jí)進(jìn)程,但并不能說(shuō)它就支持核心級(jí)線程,因?yàn)長(zhǎng)inux的"線程"和"進(jìn)程"實(shí)際上處于一個(gè)調(diào)度層次,共享一個(gè)進(jìn)程標(biāo)識(shí)符空間,這種限制使得不可能在Linux上實(shí)現(xiàn)完全意義上的POSIX線程機(jī)制,因此眾多的Linux線程庫(kù)實(shí)現(xiàn)嘗試都只能盡可能實(shí)現(xiàn)POSIX的絕大部分語(yǔ)義,并在功能上盡可能逼近。
LinuxThreads 是目前Linux平臺(tái)上使用最為廣泛的線程庫(kù),由Xavier Leroy (Xavier.Leroy@inria.fr)負(fù)責(zé)開(kāi)發(fā)完成,并已綁定在GLIBC中發(fā)行。它所實(shí)現(xiàn)的就是基于核心輕量級(jí)進(jìn)程的"一對(duì)一"線程模型,一個(gè)線程實(shí)體對(duì)應(yīng)一個(gè)核心輕量級(jí)進(jìn)程,而線程之間的管理在核外函數(shù)庫(kù)中實(shí)現(xiàn)。
LinuxThreads 定義了一個(gè)struct _pthread_descr_struct數(shù)據(jù)結(jié)構(gòu)來(lái)描述線程,并使用全局?jǐn)?shù)組變量__pthread_handles來(lái)描述和引用進(jìn)程所轄線程。在 __pthread_handles中的前兩項(xiàng),LinuxThreads定義了兩個(gè)全局的系統(tǒng)線程:__pthread_initial_thread 和__pthread_manager_thread,并用__pthread_main_thread表征 __pthread_manager_thread的父線程(初始為_(kāi)_pthread_initial_thread)。
struct _pthread_descr_struct是一個(gè)雙環(huán)鏈表結(jié)構(gòu),__pthread_manager_thread所在的鏈表僅包括它一個(gè)元素,實(shí)際上,__pthread_manager_thread是一個(gè)特殊線程,LinuxThreads僅使用了其中的errno、p_pid、 p_priority等三個(gè)域。而__pthread_main_thread所在的鏈則將進(jìn)程中所有用戶線程串在了一起。經(jīng)過(guò)一系列 pthread_create()之后形成的__pthread_handles數(shù)組將如下圖所示:
新創(chuàng)建的線程將首先在__pthread_handles數(shù)組中占據(jù)一項(xiàng),然后通過(guò)數(shù)據(jù)結(jié)構(gòu)中的鏈指針連入以__pthread_main_thread為首指針的鏈表中。這個(gè)鏈表的使用在介紹線程的創(chuàng)建和釋放的時(shí)候?qū)⑻岬健?div style="height:15px;">
LinuxThreads 遵循POSIX1003.1c標(biāo)準(zhǔn),其中對(duì)線程庫(kù)的實(shí)現(xiàn)進(jìn)行了一些范圍限制,比如進(jìn)程最大線程數(shù),線程私有數(shù)據(jù)區(qū)大小等等。在LinuxThreads的實(shí)現(xiàn)中,基本遵循這些限制,但也進(jìn)行了一定的改動(dòng),改動(dòng)的趨勢(shì)是放松或者說(shuō)擴(kuò)大這些限制,使編程更加方便。這些限定宏主要集中在 sysdeps/unix/sysv/linux/bits/local_lim.h(不同平臺(tái)使用的文件位置不同)中,包括如下幾個(gè):
每進(jìn)程的私有數(shù)據(jù)key數(shù),POSIX定義_POSIX_THREAD_KEYS_MAX為128,LinuxThreads使用 PTHREAD_KEYS_MAX,1024;私有數(shù)據(jù)釋放時(shí)允許執(zhí)行的操作數(shù),LinuxThreads與POSIX一致,定義 PTHREAD_DESTRUCTOR_ITERATIONS為4;每進(jìn)程的線程數(shù),POSIX定義為64,LinuxThreads增大到1024 (PTHREAD_THREADS_MAX);線程運(yùn)行棧最小空間大小,POSIX未指定,LinuxThreads使用 PTHREAD_STACK_MIN,16384(字節(jié))。
" 一對(duì)一"模型的好處之一是線程的調(diào)度由核心完成了,而其他諸如線程取消、線程間的同步等工作,都是在核外線程庫(kù)中完成的。在LinuxThreads中,專門(mén)為每一個(gè)進(jìn)程構(gòu)造了一個(gè)管理線程,負(fù)責(zé)處理線程相關(guān)的管理工作。當(dāng)進(jìn)程第一次調(diào)用pthread_create()創(chuàng)建一個(gè)線程的時(shí)候就會(huì)創(chuàng)建(__clone())并啟動(dòng)管理線程。
在一個(gè)進(jìn)程空間內(nèi),管理線程與其他線程之間通過(guò)一對(duì)"管理管道(manager_pipe[2])"來(lái)通訊,該管道在創(chuàng)建管理線程之前創(chuàng)建,在成功啟動(dòng)了管理線程之后,管理管道的讀端和寫(xiě)端分別賦給兩個(gè)全局變量 __pthread_manager_reader和__pthread_manager_request,之后,每個(gè)用戶線程都通過(guò) __pthread_manager_request向管理線程發(fā)請(qǐng)求,但管理線程本身并沒(méi)有直接使用 __pthread_manager_reader,管道的讀端(manager_pipe[0])是作為_(kāi)_clone()的參數(shù)之一傳給管理線程的,管理線程的工作主要就是監(jiān)聽(tīng)管道讀端,并對(duì)從中取出的請(qǐng)求作出反應(yīng)。
創(chuàng)建管理線程的流程如下所示:
(全局變量pthread_manager_request初值為-1)
初始化結(jié)束后,在__pthread_manager_thread中記錄了輕量級(jí)進(jìn)程號(hào)以及核外分配和管理的線程id, 2*PTHREAD_THREADS_MAX+1這個(gè)數(shù)值不會(huì)與任何常規(guī)用戶線程id沖突。管理線程作為pthread_create()的調(diào)用者線程的子線程運(yùn)行,而pthread_create()所創(chuàng)建的那個(gè)用戶線程則是由管理線程來(lái)調(diào)用clone()創(chuàng)建,因此實(shí)際上是管理線程的子線程。(此處子線程的概念應(yīng)該當(dāng)作子進(jìn)程來(lái)理解。)
__pthread_manager()就是管理線程的主循環(huán)所在,在進(jìn)行一系列初始化工作后,進(jìn)入while(1)循環(huán)。在循環(huán)中,線程以2秒為timeout查詢(__poll())管理管道的讀端。在處理請(qǐng)求前,檢查其父線程(也就是創(chuàng)建manager的主線程)是否已退出,如果已退出就退出整個(gè)進(jìn)程。如果有退出的子線程需要清理,則調(diào)用pthread_reap_children ()清理。
然后才是讀取管道中的請(qǐng)求,根據(jù)請(qǐng)求類(lèi)型執(zhí)行相應(yīng)操作(switch-case)。具體的請(qǐng)求處理,源碼中比較清楚,這里就不贅述了。
在LinuxThreads中,管理線程的棧和用戶線程的棧是分離的,管理線程在進(jìn)程堆中通過(guò)malloc()分配一個(gè)THREAD_MANAGER_STACK_SIZE字節(jié)的區(qū)域作為自己的運(yùn)行棧。
用戶線程的棧分配辦法隨著體系結(jié)構(gòu)的不同而不同,主要根據(jù)兩個(gè)宏定義來(lái)區(qū)分,一個(gè)是NEED_SEPARATE_REGISTER_STACK,這個(gè)屬性僅在IA64平臺(tái)上使用;另一個(gè)是FLOATING_STACK宏,在i386等少數(shù)平臺(tái)上使用,此時(shí)用戶線程棧由系統(tǒng)決定具體位置并提供保護(hù)。與此同時(shí),用戶還可以通過(guò)線程屬性結(jié)構(gòu)來(lái)指定使用用戶自定義的棧。因篇幅所限,這里只能分析i386平臺(tái)所使用的兩種棧組織方式:FLOATING_STACK方式和用戶自定義方式。
在FLOATING_STACK方式下,LinuxThreads利用mmap()從內(nèi)核空間中分配 8MB空間(i386系統(tǒng)缺省的最大??臻g大小,如果有運(yùn)行限制(rlimit),則按照運(yùn)行限制設(shè)置),使用mprotect()設(shè)置其中第一頁(yè)為非訪問(wèn)區(qū)。該8M空間的功能分配如下圖:
低地址被保護(hù)的頁(yè)面用來(lái)監(jiān)測(cè)棧溢出。
對(duì)于用戶指定的棧,在按照指針對(duì)界后,設(shè)置線程棧頂,并計(jì)算出棧底,不做保護(hù),正確性由用戶自己保證。
不論哪種組織方式,線程描述結(jié)構(gòu)總是位于棧頂緊鄰堆棧的位置。
每個(gè)LinuxThreads線程都同時(shí)具有線程id和進(jìn)程id,其中進(jìn)程id就是內(nèi)核所維護(hù)的進(jìn)程號(hào),而線程id則由LinuxThreads分配和維護(hù)。
__pthread_initial_thread 的線程id為PTHREAD_THREADS_MAX,__pthread_manager_thread的是 2*PTHREAD_THREADS_MAX+1,第一個(gè)用戶線程的線程id為PTHREAD_THREADS_MAX+2,此后第n個(gè)用戶線程的線程 id遵循以下公式:
tid=n*PTHREAD_THREADS_MAX+n+1
這種分配方式保證了進(jìn)程中所有的線程(包括已經(jīng)退出)都不會(huì)有相同的線程id,而線程id的類(lèi)型pthread_t定義為無(wú)符號(hào)長(zhǎng)整型(unsigned long int),也保證了有理由的運(yùn)行時(shí)間內(nèi)線程id不會(huì)重復(fù)。
從線程id查找線程數(shù)據(jù)結(jié)構(gòu)是在pthread_handle()函數(shù)中完成的,實(shí)際上只是將線程號(hào)按PTHREAD_THREADS_MAX取模,得到的就是該線程在__pthread_handles中的索引。
在pthread_create ()向管理線程發(fā)送REQ_CREATE請(qǐng)求之后,管理線程即調(diào)用pthread_handle_create()創(chuàng)建新線程。分配棧、設(shè)置thread 屬性后,以pthread_start_thread()為函數(shù)入口調(diào)用__clone()創(chuàng)建并啟動(dòng)新線程。pthread_start_thread ()讀取自身的進(jìn)程id號(hào)存入線程描述結(jié)構(gòu)中,并根據(jù)其中記錄的調(diào)度方法配置調(diào)度。一切準(zhǔn)備就緒后,再調(diào)用真正的線程執(zhí)行函數(shù),并在此函數(shù)返回后調(diào)用 pthread_exit()清理現(xiàn)場(chǎng)。
由于Linux內(nèi)核的限制以及實(shí)現(xiàn)難度等等原因,LinuxThreads并不是完全POSIX兼容的,在它的發(fā)行README中有說(shuō)明。
1)進(jìn)程id問(wèn)題
這個(gè)不足是最關(guān)鍵的不足,引起的原因牽涉到LinuxThreads的"一對(duì)一"模型。
Linux 內(nèi)核并不支持真正意義上的線程,LinuxThreads是用與普通進(jìn)程具有同樣內(nèi)核調(diào)度視圖的輕量級(jí)進(jìn)程來(lái)實(shí)現(xiàn)線程支持的。這些輕量級(jí)進(jìn)程擁有獨(dú)立的進(jìn)程id,在程調(diào)度、信號(hào)處理、IO等方面享有與普通進(jìn)程一樣的能力。在源碼閱讀者看來(lái),就是Linux內(nèi)核的clone()沒(méi)有實(shí)現(xiàn)對(duì) CLONE_PID參數(shù)的支持。
在內(nèi)核do_fork()中對(duì)CLONE_PID的處理是這樣的:
if (clone_flags & CLONE_PID) {
if (current->pid)
goto fork_out;
}
這段代碼表明,目前的Linux內(nèi)核僅在pid為0的時(shí)候認(rèn)可CLONE_PID參數(shù),實(shí)際上,僅在SMP初始化,手工創(chuàng)建進(jìn)程的時(shí)候才會(huì)使用CLONE_PID參數(shù)。
按照POSIX定義,同一進(jìn)程的所有線程應(yīng)該共享一個(gè)進(jìn)程id和父進(jìn)程id,這在目前的"一對(duì)一"模型下是無(wú)法實(shí)現(xiàn)的。
2)信號(hào)處理問(wèn)題
由于異步信號(hào)是內(nèi)核以進(jìn)程為單位分發(fā)的,而LinuxThreads的每個(gè)線程對(duì)內(nèi)核來(lái)說(shuō)都是一個(gè)進(jìn)程,且沒(méi)有實(shí)現(xiàn)"線程組",因此,某些語(yǔ)義不符合POSIX標(biāo)準(zhǔn),比如沒(méi)有實(shí)現(xiàn)向進(jìn)程中所有線程發(fā)送信號(hào),README對(duì)此作了說(shuō)明。
如果核心不提供實(shí)時(shí)信號(hào),LinuxThreads將使用SIGUSR1和SIGUSR2作為內(nèi)部使用的restart和cancel信號(hào),這樣應(yīng)用程序就不能使用這兩個(gè)原本為用戶保留的信號(hào)了。在Linux kernel 2.1.60以后的版本都支持?jǐn)U展的實(shí)時(shí)信號(hào)(從_SIGRTMIN到_SIGRTMAX),因此不存在這個(gè)問(wèn)題。
某些信號(hào)的缺省動(dòng)作難以在現(xiàn)行體系上實(shí)現(xiàn),比如SIGSTOP和SIGCONT,LinuxThreads只能將一個(gè)線程掛起,而無(wú)法掛起整個(gè)進(jìn)程。
3)線程總數(shù)問(wèn)題
LinuxThreads將每個(gè)進(jìn)程的線程最大數(shù)目定義為1024,但實(shí)際上這個(gè)數(shù)值還受到整個(gè)系統(tǒng)的總進(jìn)程數(shù)限制,這又是由于線程其實(shí)是核心進(jìn)程。
在kernel 2.4.x中,采用一套全新的總進(jìn)程數(shù)計(jì)算方法,使得總進(jìn)程數(shù)基本上僅受限于物理內(nèi)存的大小,計(jì)算公式在kernel/fork.c的fork_init()函數(shù)中:
max_threads = mempages / (THREAD_SIZE/PAGE_SIZE) / 8
在i386上,THREAD_SIZE=2*PAGE_SIZE,PAGE_SIZE=2^12(4KB),mempages=物理內(nèi)存大小 /PAGE_SIZE,對(duì)于256M的內(nèi)存的機(jī)器,mempages=256*2^20/2^12=256*2^8,此時(shí)最大線程數(shù)為4096。
但為了保證每個(gè)用戶(除了root)的進(jìn)程總數(shù)不至于占用一半以上物理內(nèi)存,fork_init()中繼續(xù)指定:
init_task.rlim[RLIMIT_NPROC].rlim_cur = max_threads/2;
init_task.rlim[RLIMIT_NPROC].rlim_max = max_threads/2;
這些進(jìn)程數(shù)目的檢查都在do_fork()中進(jìn)行,因此,對(duì)于LinuxThreads來(lái)說(shuō),線程總數(shù)同時(shí)受這三個(gè)因素的限制。
4)管理線程問(wèn)題
管理線程容易成為瓶頸,這是這種結(jié)構(gòu)的通??;同時(shí),管理線程又負(fù)責(zé)用戶線程的清理工作,因此,盡管管理線程已經(jīng)屏蔽了大部分的信號(hào),但一旦管理線程死亡,用戶線程就不得不手工清理了,而且用戶線程并不知道管理線程的狀態(tài),之后的線程創(chuàng)建等請(qǐng)求將無(wú)人處理。
5)同步問(wèn)題
LinuxThreads中的線程同步很大程度上是建立在信號(hào)基礎(chǔ)上的,這種通過(guò)內(nèi)核復(fù)雜的信號(hào)處理機(jī)制的同步方式,效率一直是個(gè)問(wèn)題。
6)其他POSIX兼容性問(wèn)題
Linux中很多系統(tǒng)調(diào)用,按照語(yǔ)義都是與進(jìn)程相關(guān)的,比如nice、setuid、setrlimit等,在目前的LinuxThreads中,這些調(diào)用都僅僅影響調(diào)用者線程。
7)實(shí)時(shí)性問(wèn)題
線程的引入有一定的實(shí)時(shí)性考慮,但LinuxThreads暫時(shí)不支持,比如調(diào)度選項(xiàng),目前還沒(méi)有實(shí)現(xiàn)。不僅LinuxThreads如此,標(biāo)準(zhǔn)的Linux在實(shí)時(shí)性上考慮都很少。
LinuxThreads 的問(wèn)題,特別是兼容性上的問(wèn)題,嚴(yán)重阻礙了Linux上的跨平臺(tái)應(yīng)用(如Apache)采用多線程設(shè)計(jì),從而使得Linux上的線程應(yīng)用一直保持在比較低的水平。在Linux社區(qū)中,已經(jīng)有很多人在為改進(jìn)線程性能而努力,其中既包括用戶級(jí)線程庫(kù),也包括核心級(jí)和用戶級(jí)配合改進(jìn)的線程庫(kù)。目前最為人看好的有兩個(gè)項(xiàng)目,一個(gè)是RedHat公司牽頭研發(fā)的NPTL(Native Posix Thread Library),另一個(gè)則是IBM投資開(kāi)發(fā)的NGPT(Next Generation Posix Threading),二者都是圍繞完全兼容POSIX 1003.1c,同時(shí)在核內(nèi)和核外做工作以而實(shí)現(xiàn)多對(duì)多線程模型。這兩種模型都在一定程度上彌補(bǔ)了LinuxThreads的缺點(diǎn),且都是重起爐灶全新設(shè)計(jì)的。
1.NPTL
NPTL的設(shè)計(jì)目標(biāo)歸納可歸納為以下幾點(diǎn):
POSIX兼容性
SMP結(jié)構(gòu)的利用
低啟動(dòng)開(kāi)銷(xiāo)
低鏈接開(kāi)銷(xiāo)(即不使用線程的程序不應(yīng)當(dāng)受線程庫(kù)的影響)
與LinuxThreads應(yīng)用的二進(jìn)制兼容性
軟硬件的可擴(kuò)展能力
多體系結(jié)構(gòu)支持
NUMA支持
與C++集成
在技術(shù)實(shí)現(xiàn)上,NPTL仍然采用1:1的線程模型,并配合glibc和最新的Linux Kernel2.5.x開(kāi)發(fā)版在信號(hào)處理、線程同步、存儲(chǔ)管理等多方面進(jìn)行了優(yōu)化。和LinuxThreads不同,NPTL沒(méi)有使用管理線程,核心線程的管理直接放在核內(nèi)進(jìn)行,這也帶了性能的優(yōu)化。
主要是因?yàn)楹诵牡膯?wèn)題,NPTL仍然不是100%POSIX兼容的,但就性能而言相對(duì)LinuxThreads已經(jīng)有很大程度上的改進(jìn)了。
2.NGPT
IBM的開(kāi)放源碼項(xiàng)目NGPT在2003年1月10日推出了穩(wěn)定的2.2.0版,但相關(guān)的文檔工作還差很多。就目前所知,NGPT是基于GNU Pth(GNU Portable Threads)項(xiàng)目而實(shí)現(xiàn)的M:N模型,而GNU Pth是一個(gè)經(jīng)典的用戶級(jí)線程庫(kù)實(shí)現(xiàn)。
按照2003年3月NGPT官方網(wǎng)站上的通知,NGPT考慮到NPTL日益廣泛地為人所接受,為避免不同的線程庫(kù)版本引起的混亂,今后將不再進(jìn)行進(jìn)一步開(kāi)發(fā),而今進(jìn)行支持性的維護(hù)工作。也就是說(shuō),NGPT已經(jīng)放棄與NPTL競(jìng)爭(zhēng)下一代Linux POSIX線程庫(kù)標(biāo)準(zhǔn)。
3.其他高效線程機(jī)制
此處不能不提到Scheduler Activations。這個(gè)1991年在ACM上發(fā)表的多線程內(nèi)核結(jié)構(gòu)影響了很多多線程內(nèi)核的設(shè)計(jì),其中包括Mach3.0、NetBSD和商業(yè)版本 Digital Unix(現(xiàn)在叫Compaq True64 Unix)。它的實(shí)質(zhì)是在使用用戶級(jí)線程調(diào)度的同時(shí),盡可能地減少用戶級(jí)對(duì)核心的系統(tǒng)調(diào)用請(qǐng)求,而后者往往是運(yùn)行開(kāi)銷(xiāo)的重要來(lái)源。采用這種結(jié)構(gòu)的線程機(jī)制,實(shí)際上是結(jié)合了用戶級(jí)線程的靈活高效和核心級(jí)線程的實(shí)用性,因此,包括Linux、FreeBSD在內(nèi)的多個(gè)開(kāi)放源碼操作系統(tǒng)設(shè)計(jì)社區(qū)都在進(jìn)行相關(guān)研究,力圖在本系統(tǒng)中實(shí)現(xiàn)Scheduler Activations。
本站僅提供存儲(chǔ)服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊舉報(bào)。
打開(kāi)APP,閱讀全文并永久保存 查看更多類(lèi)似文章
猜你喜歡
類(lèi)似文章
linux 線程 進(jìn)程經(jīng)典文章
zz關(guān)于linux線程
linux線程淺析
Posix線程編程指南
Posix線程編程指南(5)
內(nèi)核線程、輕量級(jí)進(jìn)程、用戶線程和LinuxThreads庫(kù)
更多類(lèi)似文章 >>
生活服務(wù)
分享 收藏 導(dǎo)長(zhǎng)圖 關(guān)注 下載文章
綁定賬號(hào)成功
后續(xù)可登錄賬號(hào)暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服