12-NPTL
33.5.2 NPTL
设计NPTL是为了弥补LinuxThreads的大部分的缺陷。特别是如下部分。
- NPTL更接近SUSv3 Pthreads标准。
- 使用NPTL的有大量线程的应用程序的性能要远优于LinuxThreads。
NPTL允许应用程序创建大量的线程。NPTL实现的测试程序可以创建10万个线程。对于LinuxThreads,实际线程数量的限制大约是一两千个。(应当承认,很少有程序需要创建这个多的线程。)
NPTL实现的开发从2002年开始,大约在第2年完成。同时,Linux内核为适应NPTL也做了各种调整。这些变动出现在Linux 2.6内核,并对NPTL的如下方面提供支持。
- 改进线程组的实现(28.2.1节)。
- 增加futex作为一种同步机制(futex作为一种通用机制,并不只是为NPTL而设计)。
- 增加新的系统调用(get_thread_area()和set_thread_area())以便支持线程本地存储。
- 支持线程化的核心转储和对多线程程序的调试功能。
- 修改并支持与Pthreads模型一样的信号处理。
- 增加新的系统调用exit_group(),可以终止进程中的所有线程(从glibc2.3开始,库函数exit()是exit_group()的包装函数,而函数pthread_exit()调用真正的内核系统调用_exit(),仅终止调用的线程)。
- 重写内核调度程序以便能够有效地调度和处理大量(上千个)KSE的情况。
- 提升内核进程终止的执行效率。
- 扩展系统调用clone()(28.2节)。
NPTL实现最基本的部分如下:
- 线程使用函数clone()创建并指定如下标志。
NPTL比LinuxThreads可以共享更多的信息。标志CLONE_THREAD意思是新线程与创建者线程属于同一个线程组,并且共享同样的进程号以及父进程号。CLONE_SYSVSEM表示新线程与创建者共享System V信号量还原值。
使用ps(1)列出一个运行在 NPTL 下的多线程程序时,只会输出一条记录。为了看到进程中的线程信息,可以使用ps-L选项。
- 实现的内部使用前两个实时信号。应用程序不能使用这些信号。
其中一个信号用来实现线程的取消功能。另一个信号用于确保进程中的所有线程拥有同样的用户号和用户组号。而在内核模式,线程有不同的用户和组凭证。所以 NPTL实现对每一个改变用户号和用户组号的系统调用(setuid()、setresuid()等以及类似的组操作函数)的包装函数都做了修改,以确保进程中的所有线程都做了相应的改变。
- 与LinuxThreads不同,NPTL并不需要管理线程。
NPTL标准一致性
这些改变意味着NPTL比LinuxThreads更接近SUSv3标准。在作者撰写本书的时候,遗留以下不一致的地方。
- 线程之间不共享nice值。
在早期的2.6.x内核中,还有一些额外的不一致的地方。
- 内核版本 2.6.16 之前,备选信号栈是针对每个线程的,但是新的线程从调用pthread_create()函数的线程那里错误地继承了备选信号栈设置(通过sigaltstack()产生),导致出现两个线程共享同一备选信号栈的问题。
- 内核2.6.16之前,只有一个线程组的组长(即主线程)可以通过调用函数setsid()启动一个新的会话。
- 内核2.6.16之前,只有一个线程组的组长可以使用函数setpgid()让宿主进程成为进程组主进程。
- 早于2.6.12的内核版本,在同一进程的线程之间无法共享使用setitimer()创建的间隔定时器。
- 早于2.6.10的内核版本,同一进程中的所有线程并不共享资源限制的设置。
- 早于2.6.9的内核版本,函数times()返回的CPU时间以及函数getrusage()返回的资源使用信息都是针对每个线程的。
NPTL设计与LinuxThreads ABI兼容。那些与提供LinuxThreads的GNU C库链接的程序换用NPTL时无需再重新编译。不过当程序运行在NPTL环境时某些行为可能会有些不同,主要是因为NPTL更接近于SUSv3 Pthreads标准。