传统UNIX中,进程(Intel所谓的task)是调度的最小单位

线程是在进程的基础上进一步的抽象,一个进程分为:线程集合和资源集合

进程中所有线程共享进程的资源,线程有私有对象:比如程序计数器、堆栈和寄存器上下文

复杂的软件往往需要有多个进程,fork+exev是很常用的技巧

网络服务的复杂性增长,fork开销成为瓶颈。产生vfork和copy-on-write技术,减小fork的开销

pthreads也是为了解决fork开销问题,同时能够支持SMP,进程内的多个线程可以分布在各个处理器上并行运行


2.6以前的调度实体都是进程,内核并没有真正支持线程

通过系统调用clone()实现,创建了一份调用进程的拷贝,跟fork()不同的是,这份进程拷贝完全共享了调用进程的地址空间

LinuxThread就是通过这个系统调用提供线程在内核级支持的(许多以前的线程实现都完全是在用户态,内核根本不知道线程的存在)

这种方法有相当多的地方没有遵循POSIX标准,特别是在信号处理,调度,进程间通信原语等方面

为改进,LinuxThread须得到内核支持,并重写线程库

两个竞争项目:IBM的NGTP(Next Generation POSIX Threads),Redhat的NPTL

IBM放弃了NGTP,Redhat发布最初的NPTL(redhat linux 9)。现NPTL成GNU C库的一部分

NPTL跟LinuxThread办法相同,内核里线程仍被当作是一个进程,且仍用clone()系统调用(在NPTL库里调用)

但是,NPTL需要内核级的特殊支持来实现,比如需要挂起然后再唤醒线程的线程同步原语futex

NPTL是1*1的线程库,就是当pthread_create()调用创建一个线程后,内核里就相应创建一个调度实体,在linux里就是一个新进程,这个方法最大可能的简化了线程的实现

除NPTL的1*1模型外还有一个m*n模型,通常这种模型的用户线程数会比内核的调度实体多

在这种实现里,线程库本身必须去处理可能存在的调度,这样在线程库内部的上下文切换通常都会相当的快,因为它避免了系统调用转到内核态

然而这种模型增加了线程实现的复杂性,并可能出现诸如优先级反转的问题,此外,用户态的调度如何跟内核态的调度进行协调也是很难让人满意

线程分三种类型

内核线程

创建/撤消由内核内部需求决定,内核线程不需和用户进程联系起来

共享内核全局数据,具有自己的内核堆栈

能够单独被调度且用标准的内核同步机制,可被单独分配到一个处理器上运行

调度不需要经过态的转换并进行地址空间的重新映射,内核线程间上下文切换比在进程间做上下文切换快得多

轻量级进程

内核支持的用户线程,在一个单独的进程中提供多线程控制

这些轻量级进程被单独的调度,可以在多个处理器上运行,每一个轻量级进程都被绑定在一个内核线程上

被独立调度并且共享地址空间和进程中的其它资源,但是每个LWP都应该有自己的程序计数器、寄存器集合、核心栈和用户栈

用户线程

通过线程库实现,线程库提供同步和调度的方法

可在没有内核参与下创建、释放和管理

进程可使用大量线程而不消耗内核资源,省去大量的系统开销

用户线程的上下文可以在没有内核干预的情况下保存和恢复

每个用户线程都可以有自己的用户堆栈,一块用来保存用户级寄存器上下文以及如信号屏蔽等状态信息的内存区

库通过保存当前线程的堆栈和寄存器内容载入新调度线程的那些内容来实现用户线程之间的调度和上下文切换

内核仍然负责进程的切换,因为只有内核具有修改内存管理寄存器的权力

用户线程不是真正的调度实体,内核对它们一无所知,而只是调度用户线程下的进程或者轻量级进程,这些进程再通过线程库函数来调度它们的线程

当一个进程被抢占时,它的所有用户线程都被抢占,当一个用户线程被阻塞时,它会阻塞下面的轻量级进程,如果进程只有一个轻量级进程,则它的所有用户线程都会被阻塞


pthreads的3种实现方式

2.6 前linux对pthreads没有提供内核级支持,pthreads实现只能采用n:1的方式,也称为库实现

一 多对一

也就是库实现

这没有OS的支持,线程对OS不可见,OS也不负责对其调度

所有的此类工作由线程库来完成

既然调度不由OS进行,SMP机器优势完全用不上。一个进程只能运行在一个CPU上,不管它包含多少线程

二 1:1模式

1:1 模式中,每个线程对应存在一个内核线程。OS知道每个线程的生老病死,对它们进行调度

适合CPU密集型的机器

进程(线程)在运行中会由于等待某种资源而阻塞,可能是I/O资源,也可能是CPU

多CPU机器上是个很不错的选择,能够充分发挥SMP的优势

缺点

OS为每个线程建立一个内核线程,导致内核级的内存空间(IA32机器的4G虚存空间的最高1G)的大开销

第二个缺点在于,在传统意义上,在mutex互斥锁和条件变量上的操作要求进入内核态,这是因为OS负责调度,它要为线程的转态转换负全责

NPTL库避免了这个缺点,它的互斥锁与条件变量的操作在用户态完成

三 M:N模式

兼有上面2种模式的优点

要求一个分层的模型。如,某进程有4个线程,每2个线程对应一个内核线程。这样,OS知道2个实体的存在,负责它们的调度。而在一个实体内有2个线程,这2个线程间的调度就是OS不加干涉、也不知道的了

简单的说

M:N模型的OS级调度上,跟1:1模型相似

线程间调度上,跟n:1模型相似

优点是,既可以在进程内利用SMP的优势,又可以节省系统空间内存的消耗,而且环境切换大多在用户态完成

缺点是显然的:复杂

Linux上的posix线程库最初实现(kernel 2.x)是n:1模型,就是没有OS支持的库实现。这也是狭义上的“LinuxPthreads”

Linux Kernel Mailing List FAQ上一位Hacker曾大大推崇这种方式,拿来与Solaries的1:1模型相比较并证明这种模型是优秀的。至少拿现在的情况来说他的观点是不正确的

IBM的NGPT用M:N模式

现 Linux POSIX线程库事实上的标准实现是 NPTL 1:1

Linus Torvalds被说服,Ingo Molnar加入重要特性:新的clone系统调用,TLS系统调用,posix线程间信号,exit_group等等

有了OS的支持,Ingo Molnar同Ulrich Drepper(glibc的LinuxThreads库的维护者,NPTL的设计者与维护者,现工作于RedHat公司)和其他一些Hackers开始NPTL的完善工作


Linux线程通过进程来实现

kernel为进程创建提供clone()系统调用,参数包括如 CLONE_VM, CLONE_FILES, CLONE_SIGHAND 等

通过clone()的参数,新创建的进程,也称为LWP(Lightweight process)与父进程共享内存空间,文件句柄,信号处理等,从而达到创建线程相同的目的


版本

2.6前

kernel没真正的thread支持,thread library都是在clone()基础上的一些基于user space的封装

通常信号处理、进程调度(每个进程需要一个额外的调度线程)及多线程之间同步共享资源等方面存在一定问题

2.6

线程库 NPTL(Native POSIX Thread Library)

POSIX thread(pthread)是编程规范,遵循此保持良好跨平台特性

尽管是基于进程的实现,但NPTL创建线程的效率非常高

NPTL的内核创建10万个线程只需要2秒,而没有NPTL支持的内核则需要长达15分钟

在Linux中,每一个线程都有一个task_struct。线程和进程可以使用同一调度其调度

内核角度上来将LWP和Process没有区别,有的仅仅是资源的共享

如果独享资源则是HWP,共享资源则是LWP

而在真正内核实现的PTL的实现是在kernel增加了futex(fast userspace mutex)支持用于处理线程之间的sleep与wake

futex是一种高效的对共享资源互斥访问的算法。kernel在里面起仲裁作用,但通常都由进程自行完成

NPTL是一个1×1的线程模型,即一个线程对于一个操作系统的调度进程,优点是非常简单

而其他一些操作系统比如Solaris则是MxN的,M对应创建的线程数,N对应操作系统可以运行的实体

LinuxThreads 管理线程(manager thread)

  • 能响应终止信号并杀死整个进程
  • 以堆栈形式使用的内存回收必须在线程完成之后进行。因此,线程无法自行完成这个过程
  • 终止线程必须进行等待,这样它们才不会进入僵尸状态
  • 线程本地数据的回收需要对所有线程进行遍历;这必须由管理线程来进行
  • 如果主线程需要调用 pthread_exit(),那么这个线程就无法结束。主线程要进入睡眠状态,而管理线程的工作就是在所有线程都被杀死之后来唤醒这个主线程
  • 为维护线程本地数据和内存,LinuxThreads 使用了进程地址空间的高位内存(就在堆栈地址之下)

ref

http://www.cnblogs.com/leaven/archive/2010/05/23/1741941.html

The Native POSIX Thread Library for Linux。Ulrich Drepper的亲笔大作,http://people.redhat.com/drepper/nptl-design.pdf

POSIX多线程程序设计,David Butenhof,中国电力 Linux内核源代码情景分析

GLIBC中NPTL线程实现代码阅读

http://blog.csdn.net/hnwyllmm/article/details/45749063