一、综述
一直以来, linux内核并没有线程的概念。
后来为了引入多线程,Linux2.0~2.4实现的是俗称LinuxThreads的多线程方式,到了2.6,基本上都是NPTL的方式了。
二、LinuxThreads
一个LWP对应一个线程。这个模型最大的好处是线程调度由内核完成了,而其他线程操作(同步、取消)等都是核外的线程库函数完成的。在用户看来,
对此, POSIX标准提出了如下要求:
1,
2,
3,
4,
5,
在LinuxThreads中,专门为每一个进程构造了一个管理线程,负责处理线程相关的管理工作。当进程第一次调用pthread_create()创建一个线程的时候就会创建并启动管理线程。然后管理线程再来创建用户请求的线程。也就是说,用户在调用pthread_create后,先是创建了管理线程,再由管理线程创建了用户的线程。
linuxthreads利用前面提到的轻量级进程来实现线程,
线程的创建与销毁都是通过管理线程来完成的,
这种通过LWP的方式来模拟线程的实现看起来还是比较巧妙的,但也存在一些比较严重的问题:
1)线程ID和进程ID的问题
按照POSIX的定义,同一进程的所有的线程应该共享同一个进程和父进程ID,而Linux的这种LWP方式显然不能满足这一点。
2)信号处理问题
异步信号是以进程为单位分发的,而Linux的线程本质上每个都是一个进程,且没有进程组的概念,所以某些缺省信号难以做到对所有线程有效,例如SIGSTOP和SIGCONT,就无法将整个进程挂起,而只能将某个线程挂起。
3)线程总数问题
LinuxThreads将每个进程的线程最大数目定义为1024,但实际上这个数值还受到整个系统的总进程数限制,这又是由于线程其实是核心进程。
4)管理线程问题
管理线程容易成为瓶颈,这是这种结构的通病;同时,管理线程又负责用户线程的清理工作,因此,尽管管理线程已经屏蔽了大部分的信号,但一旦管理线程死亡,用户线程就不得不手工清理了,而且用户线程并不知道管理线程的状态,之后的线程创建等请求将无人处理。
5)同步问题
LinuxThreads中的线程同步很大程度上是建立在信号基础上的,这种通过内核复杂的信号处理机制的同步方式,效率一直是个问题。
6)其他POSIX兼容性问题
Linux中很多系统调用,按照语义都是与进程相关的,比如nice、setuid、setrlimit等,在目前的LinuxThreads中,这些调用都仅仅影响调用者线程。
7)实时性问题
线程的引入有一定的实时性考虑,但LinuxThreads暂时不支持,比如调度选项,目前还没有实现。不仅LinuxThreads如此,标准的Linux在实时性上考虑都很少。
三、NPTL
到了linux 2.6, glibc中有了一种新的pthread线程库--NPTL(Native POSIX Threading Library)。
本质上来说,NPTL还是一个LWP的实现机制,但相对原有LinuxThreads来说,做了很多的改进。下面我们看一下NPTL如何解决原有LinuxThreads实现机制的缺陷
NPTL实现了前面提到的POSIX的全部5点要求.。但是,
在linux 2.6中,
如果这个task是一个"主线程",
在clone系统调用中,
有了tgid,
而getpid(获取进程ID)系统调用返回的也是tast_struct中的tgid,
为了应付"发送给进程的信号"和"发送给线程的信号", task_struct里面维护了两套signal_pending,
通过kill发送的信号被放在线程组共享的signal_pending中,
当线程停止/继续,
上面提到的两种线程库使用的都是内核级线程(每个线程都对应内核中的一个调度实体),
而NGPT则打算实现M:N模型(M个线程对应N个内核级线程),
线程库需要在一个内核提供的执行实体上抽象出若干个执行实体,
大体上,
用户级线程的切换显然要比内核级线程的切换快一些,
而用户级线程则不能享受多处理器,
不过, M:N的线程模型毕竟提供了这样一种手段,
五、windows中的进程和线程
windows中严格意义上来说算是内核级的线程模型。在windows中进程和线程有明确的界限,进程就是进程,只是分配资源的单位,有自己独立的数据结构。而线程就是线程,作为CPU调度的基本单位,也有自己的数据结构。这点和LInux有着本质的区别。
windows中的进程涉及到两个结构EPROCESS和KPROCESS。EPROCESS是执行体层的对象而KPROCESS是内核层的对象。相对应的ETHREAD是执行体对象,而KTHREAD是内核层对象。
熟悉这些结构的都应该清楚,内核层对象EPROCESS和ETHREAD都是相应执行体对象的首个内嵌结构,即实际上进程和线程的内核层对象和执行体层对象在内核中的地址是相同的。
内核部分实现的基本是和进程或者线程本身相关的属性,而执行体层更多的注重于管理。