8 • 编程概念55
可以使用pthread_create(3C) 为当前进程添加新的控制线程。
int pthread_create(pthread_t *tid, const pthread_attr_t *tattr,
void*(*start_routine)(void *), void *arg);
调用pthread_create() 函数时,使用包含必要的状态行为的attr。
start_routine 是开始执行新线程的函数。start_routine 返回时,线程退
出,且退出状态设置为start_routine 返回的值。如果调用成功完成,则
pthread_create() 返回零。如果返回任何其他值,则表示发生了错误。请
查看OpenGrok 中的/on/usr/src/lib/libc/spec/threads.spec,了解
pthread 函数和声明的完整列表。
通过线程同步可以控制程序流程以及访问并行执行线程的共享数据。有四
个同步对象,分别是互斥锁、读/写锁、条件变量和信号量。
■ 互斥锁允许一次只能有一个线程执行特定代码段或访问特定数据。
■ 读/写锁允许对受保护的共享资源执行并行读取操作和互斥写入操作。
要修改资源,线程必须先获取互斥写锁。只有在释放所有读锁后,才
允许使用互斥写锁。
■ 条件变量用来阻塞线程,直到满足某个特定条件。
■ 计数信号量通常用来协调对资源的访问。使用计数,可以限制访问某
个信号量的线程数量。达到指定的计数时,尝试访问资源的线程将会
阻塞。
同步
同步对象是内存中的变量,可以像访问数据那样对其进行访问。不同进程
中的线程可以通过放在由线程控制的共享内存中的同步对象互相通信。即
使不同进程中的线程通常互不可见,这些线程也可以互相通信。也可以将
同步对象放在文件中。同步对象可能具有比创建它的进程更长的生命周
期。
进程和系统管理
56 操作系统介绍:OpenSolaris 项目简明使用手册• 2006 年12 月
我们可以使用OpenGrok 在源代码树中查找libthread,第二最相关的结
果可在mutex.c 中找到,并附带以下代码注释摘录:
在ld.so.1 和libthread 中,所有线程的实现彼此会相互影响。在非线程环境
中,所有线程接口都被向量化为noop。当从libthread 调用via
_ld_concurrency() 时,这些向量将被重新指定给实际的线程接口。支持两
种模型:
TI_VERSION== 1 在此模型下,libthread 提供rw_rwlock/rw_unlock,我们
通过rw_rwlock/rw_unlock 向量化所有rt_mutex_lock/rt_mutex_unlock 调
用。在lib/libthread 下,这些接口提供_sigon/_sigoff(这与通过
bind_guard/bind_clear 提供信号阻塞的lwp/libthread 不同)。
TI_VERSION== 2 在此模型下,仅使用libthread 的bind_guard/bind_clear
和thr_self 接口。两种libthread 都在bind_guard/bind_clear 接口下阻塞信
号。较低级别的锁定从内部绑定_lwp_ 接口获得。这就消除了从libthread
获取锁定接口时遇到的递归问题。在读取器/写入器锁上使用互斥后,还
将允许使用条件变量,以便控制线程并发性(只有在对象的.init 完成之
后,才允许访问对象)。
使用OpenGrok 对POSIX 进行完全查找后,将显示包含该模块的
POSIX.pod 文件,如以下代码注释摘录中所述:
POSIX 模块允许您访问所有(或几乎所有)标准POSIX 1003.1 标识符。这
些标识符中的许多标识符都被指定了Perl-ish 接口。C 源文件中的宏定义
(C<#defines>),如EINTR 或O_NDELAY,将自动导出到您的名称空间。
对于所有函数,只有在进行显式要求时才导出。人们很可能更喜欢使用完
全限定的函数名。
至此,您已经对同步对象在多线程编程中如何定义有了大致了解,接下来
我们将学习如何使用调度类来管理这些对象。
CPU 调度
进程按调度类运行,对每个类都应用了不同的调度策略,如下所示:
■ 实时(RT)-最高优先级的调度类,为那些要求快速响应并要求用户或应
用程序对调度优先级有绝对控制权的进程提供策略。可以将RT 调度应
用到整个进程,或将其应用到进程中的一个或多个轻量级进程
(lightweight process, LWP)。必须具有proc_priocntl 权限才能使用实时
类。有关详细信息,请参见privileges(5) 手册页。
进程和系统管理
8 • 编程概念57
■ 系统(SYS)-中等优先级的调度类,不能将系统类应用到用户进程。
■ 分时(TS)-最低优先级的调度类是TS,它也是缺省的类。TS 策略在具
有不同CPU 消耗量特性的进程之间公平地分配处理资源。内核的其他
部分可以在很短的时间间隔内独占处理器,而用户所察觉到的响应时
间不会增加。
■ 交互(IA)-IA 策略在具有不同CPU 消耗量特性的进程之间公平地分配
处理资源,同时还为用户交互提供了良好的响应性。
■ 公平共享(FSS)-FSS 策略在项目之间公平地分配处理资源,这种公平
分配通过指定份额以控制对CPU 资源的处理权利来实现,而与这些项
目所拥有的进程数量无关。随着时间的推移将资源的使用情况记下
来,以便减少对其他项目大量使用资源的权利而增加其少量使用资源
的权利。
■ 固定优先级(FX)-FX 策略为具有以下要求的进程提供固定优先级抢占
调度策略:这些进程要求系统不对调度优先级进行动态调整,并要求
用户或应用程序对调度优先级具有控制权。该类是一个影响CPU 分配
策略的良好开端。
为每个轻量级进程(lightweight process, LWP) 维护了一个调度类。线程具有
其基础LWP 的调度类和优先级。进程中的每个LWP 都可以具有内核可见
的唯一调度类和优先级。线程优先级可以控制同步对象的争用情况。
RT 和TS 调度类都调用priocntl(2) 来设置进程或进程中的LWP 的优先级
别。使用OpenGrok 搜索priocntl 的代码库,我们可以在rtsched.c 文件
中找到RT 和TS 调度类中所用的变量,如下所示:
27 #pragma ident "@(#)rtsched.c 1.10 05/06/08 SMI"
28
29 #include "lint.h"
30 #include "thr_uberdata.h"
31 #include
32 #include
33 #include
34 #include
35 #include
36 #include
37
38 /*
39 * The following variables are used for caching information
40 * for priocntl TS and RT scheduling classs.
41 */
进程和系统管理
58 操作系统介绍:OpenSolaris 项目简明使用手册• 2006 年12 月
42 struct pcclass ts_class, rt_class;
43
44 static rtdpent_t *rt_dptbl; /* RT class parameter table */
45 static int rt_rrmin;
46 static int rt_rrmax;
47 static int rt_fifomin;
48 static int rt_fifomax;
49 static int rt_othermin;
50 static int rt_othermax;
...
在终端窗口中键入man priocntl 命令时,将会显示每个调度类的详细信息
并提供属性和用法说明。例如:
% man priocntl
Reformatting page. Please Wait... done
User Commands priocntl(1)
NAME
priocntl - display or set scheduling parameters of specified
process(es)
SYNOPSIS
priocntl -l
priocntl -d [-i idtype] [idlist]
priocntl -s [-c class] [ class-specific options] [-
i idtype] [idlist]
priocntl -e [-c class] [ class-specific options] command
[argument(s)]
DESCRIPTION
The priocntl command displays or sets scheduling parameters
of the specified process(es). It can also be used to display
the current configuration information for the system’s process
scheduler or execute a command with specified scheduling
parameters.
Processes fall into distinct classes with a separate
scheduling policy applied to each class. The process classes
currently supported are the real-time class, time-sharing
class, interactive class, fair-share class, and the fixed
进程和系统管理
8 • 编程概念59
priority class. The characteristics of these classes and the
class-specific options they accept are described below in
the USAGE section under the headings Real-Time Class, Time-
Sharing Class, Inter-Active Class, Fair-Share Class, and
--More--(4%)
内核概述
至此,您已经对进程、线程和调度具有了较高层面的了解,接下来我们将
讨论内核以及内核模块与用户程序有哪些差异。Solaris 内核具有以下功能
:
■ 管理系统资源,包括文件系统、进程和物理设备。
■ 为应用程序提供系统服务,如I/O 管理、虚拟内存和调度。
■ 协调所有用户与系统资源的交互。
■ 指定优先级、为资源请求提供服务以及为硬件中断和异常提供服务。
■ 调度和切换线程、为内存分页以及交换进程。
下面一节讨论内核模块和用户程序之间的若干重要差异。
内核模块和用户程序之间的执行差异
内核模块的以下特性突出说明了内核模块与用户程序在执行方面的重要差
异:
■ 内核模块具有独立的地址空间。模块在内核空间中运行。应用程序在
用户空间中运行。系统软件受到保护,不允许用户程序访问。内核空
间和用户空间有其各自的内存地址空间。
■ 内核模块具有更高的执行权限。在内核空间运行的代码要比在用户空
间运行的代码具有更大的权限。
■ 内核模块不是按顺序执行。用户程序通常按顺序执行并且从头到尾地
执行一个任务。内核模块并不是按顺序执行,它注册自己是为了为将
来的请求提供服务。
■ 内核模块可以被中断。在同一时间,可能有多个进程向内核模块发出
请求。例如,中断处理程序可以在内核模块正在为系统调用提供服务
时,向内核模块发出请求。在对称多处理器(symmetric multiprocessor,
SMP) 系统中,内核模块可以在多个CPU 上并发地执行。
进程和系统管理
60 操作系统介绍:OpenSolaris 项目简明使用手册• 2006 年12 月
■ 内核模块必须是可抢占的。您不能仅仅因为驱动程序代码没有阻塞,
就认为内核模块代码是安全的。设计驱动程序时,要假设模块可能会
被抢占。
■ 内核模块可以共享数据。应用程序的不同线程无需共享数据。与之相
对应的是,使用某个驱动程序的所有线程要共享组成该驱动程序的数
据结构和例程。驱动程序必须能够处理由多个请求导致的争用问题。
请仔细设计驱动程序数据结构,以保持多个线程独立执行。
内核模块和用户程序之间的结构差异
内核模块的以下特性突出说明了内核模块与用户程序在结构方面的重要差
异:
■ 内核模块不定义主程序。内核模块(包括设备驱动程序)没有main()
例程。相反,内核模块是子例程和数据的集合。
■ 内核模块仅链接到内核。内核模块不在用户程序链接的同一个库中进
行链接。内核模块能够调用的函数只能是内核导出的函数。
■ 内核模块使用不同的头文件。与用户程序相比,内核模块需要一组不
同的头文件。每个函数的手册页中列出了所需的头文件。内核模块可
以包含由用户程序共享的头文件,但前提是使用_KERNEL 宏在此类共享
头文件中对用户和内核接口进行了有条件定义。
■ 内核模块应避免使用全局变量。在内核模块中避免使用全局变量甚至
比在用户程序中避免使用全局变量更加重要。请尽可能地将符号声明
为static。如果必须使用全局符号,请给它们加一个在内核内唯一的
前缀。为模块中的私有符号也使用此前缀是一种不错的做法。
■ 内核模块可以针对硬件进行自定义。内核模块可以将进程寄存器专用
于特定的角色。内核代码可以针对特定的处理器进行优化。您还可以
拥有自定义的库,这是OpenSolaris 针对某些较新的x86/x64 和
UltraSPARC 平台而提供的。所以,虽然内核可以将特定的寄存器专用
于特定的角色,但也可以为内核和用户/库编写以其他方式自定义的代
码。
■ 可以根据需要装入和卸载内核模块。组成设备驱动程序的子例程和数
据的集合可以编译成一个单独的可装入对象代码模块。然后,可以将
此可装入模块静态或动态地链接到内核以及从内核解除链接。当系统
启动并运行时,您可以向内核添加功能。您可以在不重新引导系统的
情况下测试新版本的驱动程序。
进程和系统管理
8 • 编程概念61
进程调试
在开发堆栈的所有级别调试进程是编写内核模块的关键部分。
在OpenGrok 中对libthread 进行完全查找后,将在mdb_tdb.c 文件中显示
以下代码注释,这些注释描述了多线程调试和mdb 的工作方式之间的联系
:
为了正确地调试多线程程序,proc 目标必须能够使用libproc 提供的本机
LWP 服务(如果该进程没有与libthread 链接)或使用libthread_db 提供的
服务(如果该进程与libthread 链接),来查询和修改诸如线程的寄存器集
之类的信息。此外,进程可能以单线程进程开始生命周期,然后再
dlopen() libthread,所以我们必须进行准备以便即时切换模式。还有两个
可能的libthread 实现(一个位于/usr/lib 中,一个位于/usr/lib/lwp 中),
所以我们不能基于libthread_db 直接链接mdb;相反,我们必须基于受害
进程打开了哪个libthread.so,来即时dlopen 相应的libthread_db。最后,
对mdb 进行设计,使多个目标能够同时处于活动状态,所以我们甚至可
以使*两个* libthread_db 同时都处于打开状态。如果您正在查看崩溃转储
中的两个多线程用户进程(一个使用/usr/lib/libthread.so,另一个使用
/usr/lib/lwp/libthread.so),就有可能发生这种情况。为了满足这些要求,
我们在该文件中实现一个libthread_db“高速缓存”。proc 目标使用路径
名libthread_db 调用mdb_tdb_load() 以进行装入,如果它尚未打开,我们
就dlopen() 它,查找我们需要引用的符号,然后填充我们要返回到调用程
序的ops 向量。一旦装入对象,只要整个高速缓存没有被显式刷新,我们
就不必烦心卸载它。该机制还有一个不错的属性,那就是只要不需要
libthread_db,我们就不必烦心装入它,所以调试程序可以以更快的速度启
动。
可以使用以下mdb 命令访问多线程程序的LWP:
■ $l 列出代表线程的LWP ID(如果目标是用户进程)。
■ $L 列出目标中每个LWP 的LWP ID(如果目标是用户进程)。
■ pid ::attach 通过使用pid(即进程ID)连接到进程。
■ ::release 释放以前连接的进程或核心转储文件。随后,可以通过
prun(1) 继续运行进程,或者可以通过应用MDB 或其他调试程序来恢
复进程。
■ address ::context 上下文切换到指定的进程。这些用来设置条件断点的
命令通常很有用。
进程和系统管理
62 操作系统介绍:OpenSolaris 项目简明使用手册• 2006 年12 月
■ [ addr ] ::bp [+/-dDestT] [-c cmd] [-n count] sym ... 在指定的位置
设置断点。
■ addr ::delete [id | all] 删除具有给定ID 号的事件说明符。
DTrace 探测器的构造方式与MDB 查询的构造方式类似。我们将继续进行
DTrace 的动手操作实践练习,然后当调试变得更加复杂之后添加MDB。
进程和系统管理
8 • 编程概念63
64
DTrace 入门
目的
本实践的目标是通过使用DTrace 对一个系统调用使用探测器脚本来向您
介绍DTrace。
99
65
其他资源
■ 《Solaris 动态跟踪指南》。SunMicrosystems, Inc.,2005。
■ 《DTrace User Guide》, SunMicrosystems, Inc.,2006。
DTrace 入门
66 操作系统介绍:OpenSolaris 项目简明使用手册• 2006 年12 月
启用简单DTrace 探测器
完成该实践练习之后,您将对DTrace 探测器具有基本的了解。
总结
接下来,我们将通过使用名为BEGIN 的探测器生成一些非常简单的请求
来开始学习DTrace,该探测器在每次启动新的跟踪请求时触发一次。您
可以使用dtrace(1M) 实用程序的-n 选项,通过使用探测器的字符串名称
来启用探测器。