1 概述
1.1 线程的定义
传统的UNIX进程概念在开发有分布式系统中的许多应用时已经显得力不从心(有时
连简单的窗口响应问题都很难做好)。这些问题的最好解决之道就是线程,线程推
广了进程的概念使一个进程可以包含多个活动(或者说执行序列等等)。如今,由
于线程概念的普及,在UNIX系统中已经普遍实现了线程机制,开发并发应用的程序
员现在也可以广泛接触到线程的函数库了。
使用线程的优点在于:
A 改进程序的实时响应能力
B 更有效的使用多处理器
C 改进程序结构(多个线程共享同一地址空间,多爽;要是进程就只能通过那些乱
七八糟的IPC来通讯)
D 减少对系统资源的使用(线程的切换要比进程快几个数量级,尤其是用户态线程)
[注意]:有的人将线程和轻量级进程(LWP)视为等同,但其实在不同的系统/实现
中有不同的解释,LWP更恰当的解释可能为一个虚拟CPU或内核的线程。它
可以在帮助用户态线程实现一些特殊的功能。但具体我也不清楚。
1.2 线程的实现
目前线程用两种方法实现:
(1)用户态线程:
由于内核并没有对多线程进程的支持,因此,内核中只有单线程进程的概念,
而多线程进程是通过一个和应用程序连接的函数库实现的。由于内核没有轻量
级进程(线程)的概念,因此它不能独立的对之进行调度,而是由一个线程运
行库来组织线程的调度,其主要工作在于在各个线程的栈之间调度。如果一个
进程中的某一个线程调用了一个阻塞的系统调用,该进程就会被阻塞,当然该
进程中的其他所有线程也同时被阻塞,因此UNIX使用了异步I/O机制。
这种机制主要的缺点在于在一个进程中的多个线程的调度中无法发挥多处理器
的优势(如上述的阻塞情况)。
其优点包括:
A (相对于进程操作而言)某些线程操作的系统消耗大大减少。比如,对属于
同一个进程的线程之间进行调度切换时不需要调用系统调用,因此将减少额
外的消耗,往往一个进程可以启动上千个线程也没有什么问题。
B 用户态线程的实现方式可以被定制或修改以适应特殊应用的要求。这对于多
媒体实时过程等尤其有用。另外,用户态线程可以比核心态线程实现方法的
默认情况支持更多的线程。
(2)核心态线程
这种线程的实现方法允许不同进程中的线程按照同一相对优先调度方法进行调
度。这有利于发挥多处理器的并发优势。
目前线程主要的实现方法是用户态线程。有几个研究项目已经实现了一些核心
态线程的形式。其中比较著名的是MACH分布式操作系统。通过允许用户代码对
内核线程调度的参与,该系统将用户态和核心态两种线程实现方法的优点结合
了起来。通过提供这样一个两级调度机制,内核在保留了对处理器时间分配的
控制的同时,也使一个进程可以充分利用多处理器的优势。
1.3 线程库
POSIX线程库和Solaris线程库是目前使用最广泛的线程库。这两种实现方法都是可
内操作的(INTER-OPERABLE),它们的功能相似,并可以用在同样的应用中。但是
只有采用POSIX标准的线程才能确保可以完全的移植到其他符合POSIX标准的环境中
。
两者相似处:
这两个库--libpthread和libthread--中的大部分函数都是相互对应的。 往往具有
相似后缀名的的POSIX和SOLARIS函数就具有相似的功能、参数个数以及参数的作用
。所有POSIX线程库的函数都以pthread为前缀,而所有SOLARIS线程库函数都以thr
为前缀。
两者不同处:
POSIX
A 可移植性更好;
B 对每一个线程进行配置;
C 实现了线程取消;
D 强迫调度算法;
E 允许clean-up handlers for fork(2)调用;
Solaris
A 线程可以被挂起和继续;
B 优化的互斥和读写锁定;
C 可以提高并发性;
D 实现了守护线程,其进程不等待其死亡
1.4 线程标准
现在有三种不同的线程库的定义,其中每一种都想成为标准:WIN32,OS/2和POSIX
。其中前两种都是专用的,只能用在它们各自的平台上(WIN32线程仅能运行于
WINNT和WIN9X平台上,OS/2线程运行于OS/2平台上)。POSIX规范(IEEE1003.1c
aka Pthreads)则是适用于各种平台,而且已经或正在在所有主要的UNIX系统(包
括LINUX)上实现,也包括VMS平台。
POSIX线程:
POSIX标准规定了所有线程库必须符合的标准,包括API和其相应的动作。它是POSIX
扩展的一部分,因此它并不是XPG4标准所必需的,但它是X/OPEN UNIX98所要求的,
而目前所有主要的UNIX生产商都遵从此标准。就在本文写作时,几乎所有UNIX生产商
都发布了相应的线程库。
WIN32线程:
无论NT还是OS/2的实现都存在着和POSIX标准的一些基本性的不同之处--以至于无论
从两者中任何一者移植到POSIX上都被证明存在相当难度。微软至今没有任何采用
POSIX标准的计划。目前有一些自由共享的WIN32平台上的POSIX库,在OS/2平台上也
有可供选择的POSIX库。
DCE线程:
在POSIX完成它的标准指定工作之前,它发布了一些仅供参考的草案。其中的草案4
就被用作DCE线程库的基本内容。该草案和最后发布的标准基本相似,但缺少一些重
要的不同。可能是因为现在已经没有人在为DCE写代码了。
SOLARIS线程:
也被称为UI线程,它是太阳微系统公司在POSIX委员会完成标准指定工作之前开发
SOLARIS2所采用的线程库。尽管预计如今大部分程序员都将采用POSIX标准,但
SOLARIS线程在一个相当长的时间内还将存在于SOLARIS2系统上。SOLARIS线程和
POSIX线程两者的决大部分实质上是相同的。
1.5 LINUX线程的思想及特点
在INRIA(位于法国巴黎)的Xavier Leroy,以及Pavel Krauz,Richard Henderson
和另外一些人,已经开发出了LINUX下的一种线程库,该线程库实现了一个叫“一对
一 ”模型(One-to-One), 它可以利用多处理器。该库基于LINUX的一个新的系统
调用--clone()(也就是说它只能用在LINUX上了)。该调用实现于LINUX2.0或以
上版本,并可以运行于Intel,Alpha SPARC,m68k以及MIPS等处理器的机器上。其
一个缺点在于它对信号的非标准处理上。
LinuxThreads采用称为1-1模型:每个线程实际上在核心是一个个单独的进程,核心
的调度程序负责线程的调度,就象调度普通进程。线程是用系统调用clone()创建的
,clone()系统调用是fork()的推广形式,它允许新进程共享父进程的存储空间、文
件描述符和信号处理程序。
“一对一”模型的优点在于:
A 最小限度消耗的CPU级多处理技术(每个CPU一个线程);
B 最小限度消耗的I/O操作;
C 一种简单和强壮的实现(核心调度程序为我们做了大部分艰难的工作)。
该模型主要的缺点在于在互斥和条件操作时的环境切换的系统消耗更大,而该切换
是通过内核来进行的。但是由于LINUX内核对环境切换的处理相当有效,因此在一定
程度上弥补了这个缺点。
除了“一对一”模型之外还有两种基本模型。
“多对一”模型基于用户级的调度,线程切换完全由用户程序完成;从核心角度看,
只有一个进程正在运行。这种模型不是我们所关心的,因为它无法利用多处理器的
优点,而且要用不合理的方法处理I/O操作阻塞。目前LINUX上存在有数个用户态的
线程库,但我发现它们在功能、性能以及强壮性上都存在缺陷。
“多对多”模型结合了核心态和用户态的调度:数个核心态线程并发的执行,每个
都作为一个用户态线程的调度者对用户态线程进行调度。大多数商业版本的UNIX(
SOLARIS,DIGITAL UNIX和IRIX)都采用此模型实现POSIX线程标准。该模型结合了
“多对一”和“一对一”模型的优点,而且由于它能够避免另外两种模型缺陷,尤
其是当内核在处理环境切换时效率较低时,比如DIGITAL UNIX,因而相当具有吸引
力。但不幸的是,这种模型的实现相当复杂,而且需要LINUX内核所不能提供的内核
功能。Linus Torvalds和其它Linux内核开发者一直以来都在全面简单化的原则下推
动“一对一”模型,并且他们大大的提高了Linux内核线程切换的效率。Linux线程遵
从了他们一直以来的原则。
2 Linux核心对线程的支持
Linux核心对线程的支持主要是通过其系统调用,下文将进行系统的介绍。
2.1 系统调用clone()
以下是系统调用clone的代码:
asmlinkage int sys_clone(struct pt_regs regs)
{
unsigned long clone_flags;
unsigned long newsp;
clone_flags = regs.ebx;
newsp = regs.ecx;
if (!newsp)
newsp = regs.esp;
return do_fork(clone_flags, newsp, ®s);
}
与系统调用clone功能相似的系统调用有fork,但fork事实上只是clone的功能的一部
分,clone与fork的主要区别在于传递了几个参数,而当中最重要的参数就是
conle_flags,下表是系统定义的几个clone_flags标志:
标志 Value 含义
CLONE_VM 0x00000100 置起此标志在进程间共享地址空间
CLONE_FS 0x00000200 置起此标志在进程间共享文件