|
 |
栏目导栏 |
|
| |
|
|
|
|
 |
资料搜索 |
|
| |
|
|
|
|
 |
热门文章 |
|
| |
|
|
|
|
 |
最新文章 |
|
| |
|
|
|
| |
| |
|
|
|
| |
| Solaris2.4 多线程编程指南4--操作系统编程 |
[ 作者: Linux联盟收集 加入时间:2006-07-11 19:47:09 来自:Linux联盟收集
] | |
|
4.操作系统编程 5hELinux联盟 5hELinux联盟 本章讨论多线程编程如何和操作系统交互,操作系统作出什么改变来支持多线 5hELinux联盟 程。 5hELinux联盟 进程--为多线程而做的改动 5hELinux联盟 警告(alarm), 计数器(interval timer), 配置(profiling) 5hELinux联盟 全局跳转--setjmp(3C) 和longjmp(3C) 5hELinux联盟 资源限制 5hELinux联盟 LWP和调度类型 5hELinux联盟 扩展传统信号 5hELinux联盟 I/O 问题 5hELinux联盟 5hELinux联盟 4.1进程--为多线程而做的改变 5hELinux联盟 5hELinux联盟 4.1.1复制父线程 5hELinux联盟 fork(2) 5hELinux联盟 用fork(2)和fork1(2)函数,你可以选择复制所有的父线程到子线程,或者子 5hELinux联盟 线程只有一个父线程???。 5hELinux联盟 Fork()函数在子进程中复制地址空间和所有的线程(和LWP)。这很有用,例如, 5hELinux联盟 如果子进程永远不调用exec(2)但是用父进程地址空间的拷贝。 5hELinux联盟 为了说明,考虑一个父进程中的线程--不是调用fork()的那个--给一个互斥锁 5hELinux联盟 加了锁。这个互斥锁被拷贝到子进程当中,但给互斥锁解锁的线程没有被拷贝。所 5hELinux联盟 以子进程中的任何试图给互斥锁加锁的线程永久等待。为了避免这种情况,用fork() 5hELinux联盟 复制进程中所有的线程。 5hELinux联盟 注意,如果一个线程调用fork(),阻塞在一个可中断的系统调用的线程将返回 5hELinux联盟 EINTR。 5hELinux联盟 Fork1(2) 5hELinux联盟 Fork1(2) 函数在子线程中复制完全的地址空间,但是仅仅复制调用fork1()的 5hELinux联盟 线程。这在子进程在fork()之后立即调用exec()时有用。在这种情况下,子进程不 5hELinux联盟 需要复制调用fork(2)函数的那个线程以外的线程。 5hELinux联盟 在调用fork1()和exec()之间不要调用任何库函数--库函数也许会使用一个由 5hELinux联盟 多个线程操作的锁。 5hELinux联盟 5hELinux联盟 *Fork(2)和fork1(2)的注意事项 5hELinux联盟 对于fork()和fork1(),在调用之后使用全局声明时要小心。 5hELinux联盟 例如,如果一个线程顺序地读一个文件而另外一个线程成功地调用了fork(), 5hELinux联盟 每一个进程都有了一个读文件的线程。因为文件指针被两个线程共享,父进程得到 5hELinux联盟 一些数据,而子进程得到另外一些。 5hELinux联盟 对于fork()和fork1(),不要创建由父进程和子进程共同使用的锁。这仅发生在 5hELinux联盟 给锁分配的内存是共享的情况下(用mmap(2)的MAP_SHARED声明过)。 5hELinux联盟 5hELinux联盟 Vfork(2) 5hELinux联盟 Vfork(2)类似于fork1(),只有调用线程被拷贝到子进程当中去。 5hELinux联盟 注意,子进程中的线程在调用exec(2)之前不要改变内存。要记住vfork()将父 5hELinux联盟 进程的地址空间交给子进程。父进程在子进程调用exec()或退出后重新获得地址空 5hELinux联盟 间。子进程不改变父进程的状态是非常重要的。 5hELinux联盟 例如在vfork()和exec()之间创建一个新线程是危险的。 5hELinux联盟 5hELinux联盟 4.1.2执行文件和终止进程 5hELinux联盟 5hELinux联盟 exec(2)和exit(2) 5hELinux联盟 exec(2)和exit(2)调用和单线程的进程没有什么区别,只是它们破坏所有线程 5hELinux联盟 的地址空间。两个调用在执行资源(以及活动线程)被破坏前阻塞。 5hELinux联盟 如果exec()重建一个进程,它创建一个LWP。进程从这个初始线程开始执行程序。 5hELinux联盟 象平时一样,如果初始线程返回,它调用exit()来破坏整个进程。 5hELinux联盟 如果所有线程退出,进程用0值退出。 5hELinux联盟 5hELinux联盟 4.2 Alarms(闹钟???), Interval Timers(定时器), and Profiling(配置) 5hELinux联盟 5hELinux联盟 每个LWP有一个唯一的实时的定时器和一个绑定在LWP上的线程的闹钟。定时器 5hELinux联盟 和闹钟在到时间时向线程发送信号。 5hELinux联盟 每个LWP有一个虚拟时间或一个配置定时器,绑定在该LWP上的线程可以使用它 5hELinux联盟 们。如果虚拟定时器到时间,它向拥有定时器的LWP发送信号SIGVTALRM或SIGPROF, 5hELinux联盟 发送哪一个视情况而定。 5hELinux联盟 你可以用profil(2)给每一个LWP进行预配置,给每个LWP私有的缓冲区或者一个 5hELinux联盟 LWP共享的缓冲区。配置数据按LWP用户时间的每一个时钟单位更新。在创建LWP时配 5hELinux联盟 置状态被继承。 5hELinux联盟 5hELinux联盟 4.3非本地跳转--setjmp(3C)和longjmp(3C) 5hELinux联盟 5hELinux联盟 setjmp()和longjmp()的使用范围限制在一个线程里,在大多数情况下是合适的。 5hELinux联盟 然而,只有setjmp()和longjmp()在同一个线程里,线程才能对一个信号执行longjmp()。 5hELinux联盟 5hELinux联盟 4.4资源限制 5hELinux联盟 5hELinux联盟 资源限制在整个进程内,每个线程都可以给进程增加资源。如果一个线程超过 5hELinux联盟 了软资源限制,它将发出相应的信号。进程内可用的资源总量可以由getrusage(3B) 5hELinux联盟 获得。 5hELinux联盟 5hELinux联盟 4.5 LWP和调度类型 5hELinux联盟 5hELinux联盟 Solaris 内核有3种进程调度类型。最高优先级的是实时(realtime RT)。其次 5hELinux联盟 是系统(system)。系统调度类型不能在用户进程中使用。最低优先级的是分时 5hELinux联盟 (timeshare TS),它也是缺省类型。 5hELinux联盟 调度类型在LWP内维护。如果一个进程被创建,初始LWP继承父进程的调度类型和 5hELinux联盟 优先级。如果有跟多的LWP被创建来运行非绑定线程,它们也继承这些调度类型和优先 5hELinux联盟 级。进程中的所有非绑定线程有相同的调度类型和优先级。 5hELinux联盟 每个调度类型按照调度类型的配置优先级,把LWP的优先级映射到一个全体的分配 5hELinux联盟 优先级。??? 5hELinux联盟 绑定线程拥有和它们绑定的LWP相同的调度类型和优先级。进程中的每个绑定线程 5hELinux联盟 有一个内核可以看到的调度类型和优先级。系统按照LWP来调度绑定线程。 5hELinux联盟 调度类型用priocntl(2)来设置。前两个参数的指定决定了是只有调用的LWP还是 5hELinux联盟 一个或多个进程所有的LWP都被影响。第三个参数是一个指令,它可以是以下值之一。 5hELinux联盟 · PC_GETCID--获得指定类型的类型号和类型属性 5hELinux联盟 · PC_GETCLINFO--获得指定类型的名称和类型属性 5hELinux联盟 · PC_GETPARMS--获得类型标识和进程中,LWP,或者一组进程的因类型而异 5hELinux联盟 的调度参数 5hELinux联盟 · PC_SETPARMS--设置类型标识和进程中,LWP,或者一组进程的因类型而异 5hELinux联盟 的调度参数 5hELinux联盟 用priocntl()仅限于绑定线程。为了设置非绑定线程的优先级,使用thr_setprio(3T)。 5hELinux联盟 5hELinux联盟 4.5.1分时调度 5hELinux联盟 5hELinux联盟 分时调度将执行资源公平地分配给各进程。内核的其他部分可以在短时间内独占 5hELinux联盟 处理器,而不会使用户感到响应时间延长。 5hELinux联盟 Priocntl(2)调用设置一个或多个线程的nice(2)级别。Priocntl()影响进程中所有 5hELinux联盟 的分时类型的LWP的nice级别。普通拥护的nice()级别从0到20,而超级用户的进程从 5hELinux联盟 -20到20。值越小,级别越高。 5hELinux联盟 分时LWP的分配优先级根据它的LWP的CPU使用率和它的nice()级别来确定。Nice() 5hELinux联盟 级别指定了在进程内供分时调度器参考的相对优先级。LWP的nice()值越大,所得的执 5hELinux联盟 行资源越少,但不会为0。一个执行的多的LWP会被赋予比执行的少的LWP更小的优先级。 5hELinux联盟 5hELinux联盟 4.5.2实时调度 5hELinux联盟 5hELinux联盟 实时类型可以被整个进程或进程内部的一个或多个线程来使用。这需要超级用户 5hELinux联盟 权限。与分时类型的nice(2)级别不同,标识为实时的LWP可以被独立或联合地分配优先 5hELinux联盟 级。一个priocntl(2)调用影响进程中所有实时的LWP的属性。 5hELinux联盟 调度器总是分配最高优先级的实时LWP。如果一个高优先级的LWP可运行,它将打断 5hELinux联盟 低优先级的LWP。一个有先行权(preempt)的LWP被放置在该等级队列的头上。一个实 5hELinux联盟 时(RT)的LWP保持控制处理器,直到被其他线程中断时挂起,或者实时优先级改变。 5hELinux联盟 RT类型的LWP对TS类型的进程有绝对的优先权。 5hELinux联盟 一个新的LWP继承父线程或LWP的调度类型。一个RT类型的LWP继承其父亲的时间片, 5hELinux联盟 不管它是有限还是无限的。一个有有限时间片的LWP持续运行直到结束,阻塞(例如等 5hELinux联盟 待一个I/O事件),被更高优先级的实时进程中断,或者时间片用完。一个拥有无限时 5hELinux联盟 间片的进程则不存在第四种情况(即时间片用完)。 5hELinux联盟 5hELinux联盟 4.5.3 LWP调度和线程绑定 5hELinux联盟 · 线程库自动调节缓冲池中LWP的数量来运行非绑定线程。其目标是: 5hELinux联盟 避免程序因为缺少没有阻塞的LWP而阻塞。 5hELinux联盟 例如,如果可运行的非绑定线程比LWP多而所有的活动线程在内核中处于无限等待 5hELinux联盟 的阻塞状态,进程不能继续,知道一个等待的线程返回。 5hELinux联盟 · 有效利用LWP 5hELinux联盟 例如,如果线程库给每个线程创建一个LWP,许多LWP通常处于闲置状态,而操作 5hELinux联盟 系统反而被没用的LWP耗尽资源。 5hELinux联盟 要记住,LWP是按时间片运行的,而不是线程。这意味着如果只有一个LWP,则进程 5hELinux联盟 内部没有时间片--现成运行直到阻塞(通过线程间同步),被中断,或者运行结束。 5hELinux联盟 可以用thr_setprio(3T)来为线程分配优先级:只有在没有高优先级的非绑定线程 5hELinux联盟 可用时,LWP才会被分配给低优先级的线程。当然,绑定线程不会参与这种竞争,因为 5hELinux联盟 它们有自己的LWP。 5hELinux联盟 把线程绑定到LWP上可以精确地控制调度。???但这种控制在很多非绑定线程竞 5hELinux联盟 争一个LWP是不可能的。 5hELinux联盟 实时线程可以对外部事件有更快的反应。考虑一个用于鼠标控制的线程,它必须对 5hELinux联盟 鼠标事件及时作出反应。通过绑定一个线程到LWP上,保证了在需要时会有LWP可用。通 5hELinux联盟 过将LWP设定为实时调度类型,可以保证LWP对LWP事件作出快速响应。 5hELinux联盟 5hELinux联盟 4.5.4信号等待(SIGWAITING)--给等待线程创建LWP 5hELinux联盟 线程库通常保证在缓冲池内有足够的LWP保证程序运行。如果进程中所有的LWP处 5hELinux联盟 于无限等待的阻塞状态(例如在读中断或网络时阻塞),操作系统给进程发送一个新的 5hELinux联盟 信号,SIGWAITING。这个信号由线程库来控制。如果进程中有一个等待运行的线程,一 5hELinux联盟 个新的LWP被创建并被赋予适当的线程使之执行。 5hELinux联盟 SIGWAITING机制在一个或多个线程处于计算绑定并且有新线程可以执行的情况下。 5hELinux联盟 一个计算绑定线程可以阻止在缺少LWP的情况下有多个可运行的线程启动运行。这可以 5hELinux联盟 通过调用thr_setconcurrency(3T)或者在调用thr_create(3T)时使用THR_NEW_LWP标志。 5hELinux联盟 5hELinux联盟 4.5.5确定LWP的已空闲时间 5hELinux联盟 5hELinux联盟 如果活动线程的数量减少,LWP池中的一些LWP将不再被需要。如果LWP的数量比活动 5hELinux联盟 的线程多,线程库破坏那些没有用的LWP。线程库确定LWP的空闲的时间--如果线程在 5hELinux联盟 足够长的时间内没有被使用(现在的设置是5分钟),它们将被删除。 5hELinux联盟 5hELinux联盟 4.6扩展传统的信号 5hELinux联盟 5hELinux联盟 为了适应多线程,UNIX的信号模型以一种相当自然的方式被扩展。信号的分布是用 5hELinux联盟 传统机制建立在进程内部的(signal(2),sigaction(2), 等等)。 5hELinux联盟 如果一个信号控制器被标志为SIG_DFL或者SIG_IGN,在收到信号后所采取的行动 5hELinux联盟 (exit, core dump, stop, continue, or ignore)在整个接收进程中有效,将影响到 5hELinux联盟 进程中的所有线程。关于信号的基本信息请参见signal(5)。 5hELinux联盟 每个线程有它自己的信号掩模。如果线程使用的内存或其他状态也在被信号控制 5hELinux联盟 器使用,线程会关于一些信号阻塞。进程中所有的线程共享由sigaction(2)和其变量 5hELinux联盟 建立的信号控制器,???象通常那样。 5hELinux联盟 进程中的一个线程不能给另一个进程中的线程发送信号。一个由 5hELinux联盟 kill(2)和sigsend(2)送出的信号是在进程内部有效的,将被进程中的任何一个接收态 5hELinux联盟 的线程接收并处理。 5hELinux联盟 非绑定线程不能使用交互的信号栈。一个绑定线程可以使用交互信号栈,因为其 5hELinux联盟 状态是和执行资源连接的。一个交互信号栈必须通过sigaction(2) ,以及 5hELinux联盟 sigaltstack(2)来声明并使能。 5hELinux联盟 一个应用程序给每个进程一个信号控制器,在它的基础上,每个线程都有线程信 5hELinux联盟 号控制器。一种办法是给在一张表中给每个线程控制器建立一个索引,由进程信号控制 5hELinux联盟 器来通过这张表实现线程控制器。这里没有零线程。 5hELinux联盟 信号被分为两类:陷阱(traps)和意外(exceptions,同步信号)和中断 5hELinux联盟 (interrupts,异步信号)。 5hELinux联盟 在传统的UNIX中,如果一个信号处于挂起状态(即等待接收),发生的其他同样的 5hELinux联盟 信号将没有效果--挂起信号由一位来表示,而不是一个计数器。 5hELinux联盟 就象在单线程的进程里那样,如果一个线程在关于系统调用阻塞时收到一个信号, 5hELinux联盟 线程将提前返回,或者带有一个EINTR错误代码,或者带有比请求少的字节数(如果阻 5hELinux联盟 塞在I/O状态)。 5hELinux联盟 对于多线程编程有特殊意义的是作用在cond_wait(3T)上的信号的效果。这个调用 5hELinux联盟 通常在其他线程调用cond_signal(3T)和cond_broadcast(3T),但是,如果等待线程收 5hELinux联盟 到一个UNIX信号,将返回一个EINTR错误代码。更多的信息参见""对于条件变量的等待中断""。 5hELinux联盟 5hELinux联盟 4.6.1同步信号 5hELinux联盟 5hELinux联盟 陷阱(例如SIGILL, SIGFPE, SIGSEGV)发生在线程自身的操作之后,例如除零 5hELinux联盟 错误或者显式地发信号给自身。一个陷阱仅仅被导致它的线程类控制。进程中的几个 5hELinux联盟 线程可以同时产生和控制同类陷阱。 5hELinux联盟 扩展信号到独立线程的主张对于同步信号来说是容易的--信号被导致问题的线程 5hELinux联盟 来处理。然而,如果一个线程没有处理这个问题,例如通过sigaction(2)建立一个信号 5hELinux联盟 控制器,整个进程将终止。 5hELinux联盟 因为一个同步信号通常意味着整个进程的严重错误,而不只是一个线程,终止进程 5hELinux联盟 通常是一个明智的做法。 5hELinux联盟 5hELinux联盟 4.6.2异步信号 5hELinux联盟 5hELinux联盟 中断(例如SIGINT和SIGIO)是与任何线程异步的,它来自于进程外部的一些操作。 5hELinux联盟 它们也许是显式地送到其他线程的信号,或者是例如Control-c的外部操作,处理异步 5hELinux联盟 信号不处理同步信号要复杂的多。 5hELinux联盟 一个中断被任何线程来处理,如果线程的信号掩模允许的话。如果有多个线程可以 5hELinux联盟 接收中断,只有一个被选中。 5hELinux联盟 如果并发的多个同样的信号被送到一个进程,每一个将被不同的线程处理,如果 5hELinux联盟 线程的信号掩模允许的话。如果所有的线程都屏蔽该信号,则这些信号挂起,直到有信 5hELinux联盟 号解除屏蔽来处理它们。 5hELinux联盟 5hELinux联盟 4.6.3连续语义(Continuation Semantics) 5hELinux联盟 5hELinux联盟 连续语义(Continuation Semantics)是处理信号的传统方法。其思想是当一个 5hELinux联盟 信号控制器返回,控制恢复到中断前的状态。这非常适用于单线程进程的异步信号,如 5hELinux联盟 同在示例4-1中的那样。在某些程序设计语言里(例如PL/1),这也被用于意外 5hELinux联盟 (exception)处理机制。 5hELinux联盟 5hELinux联盟 Code Example 4-1 连续语义 5hELinux联盟 Unsigned int nestcocunt; 5hELinux联盟 Unsigned int A(int i, int j) { 5hELinux联盟 Nestcount++; 5hELinux联盟 If(i==0) 5hELinux联盟 Return (j+1); 5hELinux联盟 Else if (j==0) 5hELinux联盟 Return (A(I-1,1)); 5hELinux联盟 Else 5hELinux联盟 Return (A(I-1,A(I, j-1))); 5hELinux联盟 } 5hELinux联盟 void sig(int i){ 5hELinux联盟 printf(""nestcount=%d "",nestcount); 5hELinux联盟 } 5hELinux联盟 main(){ 5hELinux联盟 sigset(SIGINT, sig); 5hELinux联盟 A(4,4); 5hELinux联盟 } 5hELinux联盟 5hELinux联盟 4.6.4对于信号的新操作 5hELinux联盟 5hELinux联盟 对于多线程编程的几个新的信号操作被加入操作系统。 5hELinux联盟 Thr_sigsetmask(3T) 5hELinux联盟 Thr_sigsetmask(3T)针对线程而sigprocmask(2)针对进程--它设置(线程)的 5hELinux联盟 信号掩模。如果一个新线程被创建,它的初始信号掩模从父线程那里继承。 5hELinux联盟 在多线程编程中避免使用sigprocmask(),因为它设置LWP的信号掩模,被这个 5hELinux联盟 操作影响的线程可以在一段时间后改变。??? 5hELinux联盟 不象sigprocmask(),thr_sigsetmask()是一种代价相对低廉的调用,因为它不 5hELinux联盟 产生系统调用。 5hELinux联盟 Thr_kill(3T) 5hELinux联盟 Thr_kill是kill(2)的线程版本--它发送信号给特定的线程。 5hELinux联盟 当然,这与发送信号给进程不同。如果一个信号被发送给进程,信号可以被进 5hELinux联盟 程中的任何线程所控制。一个由thr_kill()发出的信号只能被指定的线程处理。 5hELinux联盟 注意,你只能用thr_kill()给当前进程里的线程发信号。这是因为线程标识符 5hELinux联盟 是本地的--不可能给其他进程内的线程命名。 5hELinux联盟 Sigwait(2) 5hELinux联盟 Sigwait(2)导致调用线程阻塞直到收到set参数指定的所有信号。线程在等待时, 5hELinux联盟 被set标识的信号应当被解除屏蔽,但最初的信号掩模在调用返回时将恢复。 5hELinux联盟 用sigwait()来从异步信号当中把线程分开。你可以创建一个线程来监听异步信 5hELinux联盟 号,而其它线程被创建来关于指定的异步信号阻塞。 5hELinux联盟 如果信号被发送,sigwait()清除挂起的信号,返回一个数。许多线程可以同时 5hELinux联盟 调用sigwait(),但每个信号被收到后只有相关的一个线程返回。 5hELinux联盟 通过sigwait()你可以同时处理异步信号--一个线程通过简单的sigwait()调用 5hELinux联盟 来处理信号,在信号一旦被受到就返回。如果保证所有的线程(包括调用sigwait() 5hELinux联盟 的线程)屏蔽这样的信号,你可以保证这样的信号被你指定的线程安全地处理。 5hELinux联盟 通常,用sigwait()创建一个或多个线程来等待信号。因为sigwait()可以接收 5hELinux联盟 被屏蔽的信号,应当保证其它线程对这样的信号不感兴趣,以免信号被偶然地发送给 5hELinux联盟 这样的线程。如果信号到达,一个线程从sigwait()返回,处理该信号,等待其它的 5hELinux联盟 信号。处理信号的线程不限于使用异步安全函数,可以和其它线程以通常的方式同 5hELinux联盟 步(异步安全函数类型被定义为""安全等级的MT界面MT Interface Safety Levels)。 5hELinux联盟 --------------------------------------- 5hELinux联盟 注意-sigwait()不能用于同步信号 5hELinux联盟 --------------------------------------- 5hELinux联盟 sigtimedwait(2) 5hELinux联盟 sigtimedwait(2)类似于sigwait(2),不过如果在指定时间内没有收到信号, 5hELinux联盟 它出错并返回。 5hELinux联盟 5hELinux联盟 4.6.5面向线程的信号(thread-directed signals) 5hELinux联盟 5hELinux联盟 UNIX信号机制扩展了一个叫做""线程引导信号""的概念。它们就象普通的异步信 5hELinux联盟 号一样,只不过他们被送到指定线程,而不是进程。 5hELinux联盟 在单独的线程内等待信号比安装一个信号控制器安全和容易。 5hELinux联盟 处理异步信号的更好的办法是同时处理它们。通过调用sigwait(2),一个线程 5hELinux联盟 可以等待一个信号发生。 5hELinux联盟 Code Example 4-2 异步信号和sigwait(2) 5hELinux联盟 Main(){ 5hELinux联盟 Sigset_t set; 5hELinux联盟 Void runA(void); 5hELinux联盟 5hELinux联盟 Sigemptyset(&set); 5hELinux联盟 Sigaddset(&set, SIGINT); 5hELinux联盟 Thr_sigsetmask(SIG_BLOCK, &set, NULL); 5hELinux联盟 Thr_create(NULL, 0, runA, NULL, THR_DETACHED, NULL); 5hELinux联盟 While(1){ 5hELinux联盟 Sigwait(&set); 5hELinux联盟 Printf(""nestcount=%d "",nestcount); 5hELinux联盟 } 5hELinux联盟 } 5hELinux联盟 void runA(){ 5hELinux联盟 A(4,4); 5hELinux联盟 Exit(0); 5hELinux联盟 } 5hELinux联盟 这个例子改变了示例4-1:主函数屏蔽了SIGINT信号,创建了一个子线程来调 5hELinux联盟 用前例中的函数A,然后用sigwait来处理SIGINT信号。 5hELinux联盟 注意信号在计算线程中被屏蔽,因为计算线程继承了主线程的信号掩模。除非 5hELinux联盟 用sigwait()阻塞,主线程不会接收SIGINT。 5hELinux联盟 而且,注意在使用sigwait()中,系统调用不会被中断。 5hELinux联盟 5hELinux联盟 4.6.6完成语义(Completion Semantics) 5hELinux联盟 5hELinux联盟 处理信号的另外一种办法是用完成语义。完成语义使用在信号表明有极严重的 5hELinux联盟 错误发生,以至于当前的代码块没有理由继续运行下去。该代码将被停止执行,取 5hELinux联盟 而代之的是信号控制器。换句话说,信号控制器完成代码块。 5hELinux联盟 在示例4-3中,有问题的块是if语句的then部分。调用setjmp(3C)在jbuf中保 5hELinux联盟 存寄存器当前的状态并返回零--这样执行了块。 5hELinux联盟 5hELinux联盟 Code Example 4-3 完成语义 5hELinux联盟 Sigjmp_buf jbuf; 5hELinux联盟 Void mult_divide(void) { 5hELinux联盟 Int a,b,c,d; 5hELinux联盟 Void problem(); 5hELinux联盟 Sigset(SIGFPE, problem); 5hELinux联盟 While(1) { 5hELinux联盟 If (sigsetjmp(&jbuf) ==0) { 5hELinux联盟 Printf(""three numbers, please: ""); 5hELinux联盟 Scanf(""%d %d %d"", &a,&b,&c); 5hELinux联盟 D=a*b/c; 5hELinux联盟 Printf(""%d*%d/%d=%d "",a,b,c,d); 5hELinux联盟 } 5hELinux联盟 } 5hELinux联盟 } 5hELinux联盟 void problem(int sig){ 5hELinux联盟 printf(""couldn''t deal with them,try again ""); 5hELinux联盟 siglongjmp(&jbuf,1); 5hELinux联盟 } 5hELinux联盟 如果SIGFPE(一个浮点意外)发生,信号控制器被唤醒。 5hELinux联盟 信号控制器调用siglongjmp(3C),这个函数保存寄存器状态到jbuf,导致程序 5hELinux联盟 从sigsetjmp()再次返回(保存的寄存器包含程序计数器和堆栈指针)。 5hELinux联盟 然而,这一次,sigsetjmp(3C)返回siglongjmp()的第二个参数,是1。注意块 5hELinux联盟 被跳过,在while循环的下一次重复才会执行。 5hELinux联盟 注意,你可以在多线程编程中用sigsetjmp(3C)和siglongjmp(3C),但是要小心, 5hELinux联盟 线程永远不会用另一个线程的sigsetjmp()的结果来做siglongjmp()。而且, 5hELinux联盟 sigsetjmp()和siglongjmp()保存和恢复信号掩模,但sigjmp(3C)和longjmp(3C) 5hELinux联盟 不会这样做。如果你使用信号控制器时最好使用sigsetjmp()和siglongjmp()。 5hELinux联盟 完成语义经常用来处理意外。具体的,Ada语言使用这种模型。 5hELinux联盟 -------------------------------------- 5hELinux联盟 注意-sigwait(2)永远不应用来同步信号。 5hELinux联盟 -------------------------------------- 5hELinux联盟 5hELinux联盟 4.6.7信号控制器和异步安全 5hELinux联盟 5hELinux联盟 有一个类似与线程安全的概念:异步安全。异步安全操作被保证不会和被中断 5hELinux联盟 的操作相混。 5hELinux联盟 如果信号控制器与正被中断的操作冲突,就会有异步安全的问题。例如,假设 5hELinux联盟 有一个程序正在printf调用的当中,一个信号发生,它的控制器也要调用printf(): 5hELinux联盟 两个printf()的输出会交织在一起。为了避免这种结果,如果是printf被中断,控 5hELinux联盟 制器就不应当调用printf。 5hELinux联盟 这个问题使用同步原语无法解决,因为试图的同步操作会立即导致死锁。 5hELinux联盟 例如,假设printf()用互斥锁来保护它自己。现在假设一个线程正在调用 5hELinux联盟 printf(),第一个printf就得在互斥锁上等待,但是线程突然被信号中断了。如果 5hELinux联盟 控制器(被在printf的里面中断的线程调用)也调用printf(),在互斥锁上阻塞的 5hELinux联盟 线程回再次尝试得到printf的使用权,这就导致了死锁。 5hELinux联盟 为了避免控制器和操作之间的干涉,或者保证这种情况永远不会发生(例如在 5hELinux联盟 可能出问题的时刻封掉所有信号),或者在信号控制器中仅仅使用异步安全操作。 5hELinux联盟 因为在用户级操作设置线程的掩模相对代价较小,你可以方便地设计代码使得 5hELinux联盟 它符合异步安全的范畴。 5hELinux联盟 5hELinux联盟 4.6.8关于条件变量的中断等待 5hELinux联盟 5hELinux联盟 如果在线程等待条件变量的时候获得一个信号,过去的做法是(假设进程没有 5hELinux联盟 终止)被中断的调用返回EINTR。 5hELinux联盟 理想的新条件是当cond_wait(3T)和cond_timedwait(3T)返回,将重新获得互斥 5hELinux联盟 锁。 5hELinux联盟 Solaris多线程是这样做的:如果一个线程在cond_wait或cond_timedwait()函 5hELinux联盟 数上阻塞,而且获得一个没有被屏蔽信号,(信号)控制器将被启用,cond_wait() 5hELinux联盟 或cond_timedwait()返回EINTR,并且互斥锁加锁。??? 5hELinux联盟 这意味着互斥锁将被信号控制器获得,因为控制器必须清理环境。 5hELinux联盟 请看示例4-4 5hELinux联盟 Code Example 4-4 条件变量和等待中断 5hELinux联盟 Int sig_catcher() { 5hELinux联盟 Sigset_t set; 5hELinux联盟 Void hdlr(); 5hELinux联盟 5hELinux联盟 Mutex_lock(&mut); 5hELinux联盟 5hELinux联盟 Sigemptyset(&set); 5hELinux联盟 Sigaddset(&set,SIGING); 5hELinux联盟 Thr_sigsetmask(SIG_UNBLOCK,&set,0); 5hELinux联盟 5hELinux联盟 If(cond_wait(&cond,&mut) == EINTR){ 5hELinux联盟 /* signal occurred and lock is held */ 5hELinux联盟 cleanup(); 5hELinux联盟 mutex_unlock(&mut); 5hELinux联盟 return(0); 5hELinux联盟 } 5hELinux联盟 normal_processing(); 5hELinux联盟 mutex_unlock(&mut); 5hELinux联盟 return(1); 5hELinux联盟 } 5hELinux联盟 void hdlr() { 5hELinux联盟 /* lock is held in the handler */ 5hELinux联盟 ……… 5hELinux联盟 } 5hELinux联盟 假设SIGINT信号在sig_catcher()的入口处被阻塞,而且hdlr()已被建立(通 5hELinux联盟 过sigaction()调用)成为SIGINT的控制器。 5hELinux联盟 如果线程阻塞在cond_wait()的时候,一个没有被屏蔽的信号被送给线程,线 5hELinux联盟 程首先获得互斥锁,然后调用hdlr(),然后从cond_wait()返回EINTR。 5hELinux联盟 注意,在sigaction()中指定SA_RESTART标志是没有效果的--cond_wait(3T) 5hELinux联盟 不是系统调用,不会被自动重新启动。如果线程在cond_wait()阻塞时,调用总是 5hELinux联盟 返回EINTR。 5hELinux联盟 5hELinux联盟 4.7 I/O事项 5hELinux联盟 5hELinux联盟 多线程的一个优势是它的I/O性能。传统的UNIX API在这一领域没有给程序员 5hELinux联盟 足够的辅助--你或者使用文件系统的辅助,或者跳过整个文件系统。 5hELinux联盟 这部分将介绍怎样在多线程利用I/O并发和多缓冲区来获得更多的灵活性。这 5hELinux联盟 个部分也探讨了同步I/O(多线程)和异步I/O(可以是也可以不是多线程)的异同。 5hELinux联盟 5hELinux联盟 4.7.1 I/O作为远程过程调用 5hELinux联盟 5hELinux联盟 在传统的UNIX模型里,I/O表现为同步的,就象你在通过一个远程过程调用 5hELinux联盟 (RPC)来操纵外设。一旦调用返回,I/O完成(或至少看上去已完成--例如一个写 5hELinux联盟 请求,也许仅仅是在操作系统内做数据移动)。 5hELinux联盟 这个模型的优势在于容易理解,因为程序员对过程调用是很熟悉的。 5hELinux联盟 一个代替的办法(在传统的UNIX里没有的)是异步模式,I/O请求仅仅启动一 5hELinux联盟 个操作。程序要自己来发现操作是否完成。 5hELinux联盟 这个办法不象同步方法那样简单,但它的优势在于允许并发的I/O处理和传统 5hELinux联盟 的单线程进程处理。 5hELinux联盟 5hELinux联盟 4.7.2驯服的异步(Tamed Asynchrony) 5hELinux联盟 5hELinux联盟 你可以通过在多线程编程里使用同步I/O来获得异步I/O的大多数好处。在异 5hELinux联盟 步I/O中,你发出一个请求,过一会儿再去检查请求是否已经完成,你可以用分离 5hELinux联盟 的线程来同步操作I/O。然后由主线程(也许是thr_join(3T))检查操作是否完成。 5hELinux联盟 5hELinux联盟 4.7.3异步I/O 5hELinux联盟 5hELinux联盟 在大多数情况下没有必要使用异步I/O,因为它的效果可以通过线程来实现, 5hELinux联盟 每个线程使用同步I/O。然而,在少数情况下,线程不能完全实现实现异步I/O的功 5hELinux联盟 能。 5hELinux联盟 最直接的例子是用流的方法写磁带。这种技术在有持续的数据流写向磁带,磁 5hELinux联盟 带驱动器高速运转时防止磁带驱动器停止。 5hELinux联盟 为了作到这点,在磁带驱动程序响应一个标志上一个写操作已经完成的中断时, 5hELinux联盟 内核里的磁带驱动器必须发出一个写请求队列。 5hELinux联盟 线程不能保证异步写被排队,因为线程本身执行的顺序就是不确定的。例如试 5hELinux联盟 图给磁带的写操作排队是不可能的。 5hELinux联盟 5hELinux联盟 *异步I/O操作 5hELinux联盟 #include 5hELinux联盟 int aioread(int filedes, char *bufp, int bufs, off_t offset, 5hELinux联盟 int whence, aio_result_t *resultp); 5hELinux联盟 int aiowrite(int filedes, const char *bufp, int bufs, 5hELinux联盟 off_t offset, int whence, aio_result_t *resultp); 5hELinux联盟 aio_result_t *aiowait(const struct timeval *timeout); 5hELinux联盟 int aiocancel(aio_result_t *resultp); 5hELinux联盟 aioread(3)和aiowrite(3)在形式上与pread(2)和pwrite(2),不同的是最后一 5hELinux联盟 个参数。调用aioread()和aiowrite()导致初始化(或排队)一个I/O操作。 5hELinux联盟 调用不会阻塞,调用的状态将返回到由resultp指向的结构。其类型为 5hELinux联盟 aio_result_t,包含有: 5hELinux联盟 int aio_return; 5hELinux联盟 int aio_errno; 5hELinux联盟 如果一个调用立即失败,错误码被返回到aio_errno。否则,这个域包含 5hELinux联盟 AIO_INPROGRESS,意味着操作被成功排队。 5hELinux联盟 你可以通过调用aiowait(3)来等待一个特定的异步I/O操作结束。它返回一个 5hELinux联盟 指向aio_result_t数据结构的指针,该结构由最初的aioread(3)或者aiowrite(3) 5hELinux联盟 提供。如果这些函数被调用,Aio_result包含类似与read(2)和write(2)相似返回 5hELinux联盟 值,aio_errno包含错误代码,如果有的话。 5hELinux联盟 Aiowait()使用一个timeout参数,该参数指定了调用者可以等多久。通常情况 5hELinux联盟 下,一个NULL指针表示调用者希望等待的时间不确定,如果指针指向的数据结构包 5hELinux联盟 含零值,表明调用者不希望等待。 5hELinux联盟 你可以启动一个异步I/O操作,做一些工作,然后调用aiowait()来等待结束的 5hELinux联盟 请求。或者你可以在操作结束后,用SIGIO来异步地通知。 5hELinux联盟 最后,一个挂起的异步I/O操作可以通过调用aiocancel()来取消。这个过程在 5hELinux联盟 调用时使用存放结果的地址做参数。这个结果区域标识了要取消哪一个操作。 5hELinux联盟 5hELinux联盟 4.7.4共享的I/O和新的I/O系统调用 5hELinux联盟 5hELinux联盟 如果多个线程同时使用同一个文件描述符来进行I/O操作,你会发现传统的 5hELinux联盟 UNIX I/O接口不安全。在非串行的I/O(即并发)发生时会有问题。它使用 5hELinux联盟 lseek(2)系统调用来为后续的read(2)和write(2)函数设置文件偏移量。如果两个或 5hELinux联盟 更多的线程使用lseek(2)来移动同一个文件描述符,就会发生冲突。 5hELinux联盟 为了避免冲突,使用新的pread(2)和pwrite(2)系统调用。 5hELinux联盟 #include 5hELinux联盟 #include 5hELinux联盟 ssize_t pread(int fildes,void *buf,size_t nbyte,off_t offset); 5hELinux联盟 ssize_t pwrite(int filedes,void *buf,size_t nbyte,off_t offset); 5hELinux联盟 这些调用效果类似于read(2)和write(2),不同之处在于多了一个参数,文件 5hELinux联盟 偏移量。用这个参数,你可以用不着用lseek(2)指定偏移量,多线程可以对同一个 5hELinux联盟 文件描述符进行安全的操作。 5hELinux联盟 5hELinux联盟 4.7.5 Getc(3S)和putc(3S)的替代函数 5hELinux联盟 5hELinux联盟 一个问题会发生在标准I/O的情况下。程序员可以很快地习惯于getc(3S)和 5hELinux联盟 putc(3S)这样的函数--它们是用宏来实现的。因为如此,他们可以在程序的循环内 5hELinux联盟 部使用,用不着考虑效率。 5hELinux联盟 然而,如果改用线程安全的版本后,代价会突然变的昂贵--它们需要(至少) 5hELinux联盟 两个内部子程序调用,来给一个互斥锁加锁和解锁。为了解决这个问题,提供了这 5hELinux联盟 些函数的替代版本--getc_unlocked(3S)和putc_unlocked(3S)。 5hELinux联盟 这些函数不给互斥锁加锁,因此速度象非线程安全版本的getc(3S)和putc(3S) 5hELinux联盟 一样快。然而如果按照线程安全的方法来使用的话,必须用flockfile(3S)和 5hELinux联盟 funlockfile(3S)显式地给互斥锁加锁和解锁来保护标准的I/O流。这两个调用放在 5hELinux联盟 循环外面,而getc_unlocked()或者putc_unlocked()放在循环内部。
Linux联盟收集整理 ,转贴请标明原始链接,如有任何疑问欢迎来本站Linux论坛讨论 |
|
|
|
|
|