linux社区爱心援助Linux认证系列教程业界动态站务新闻公司招聘建议留言网址大全LPI专题CISCO专题
设为首页
加入收藏
管理团队
JSP  
JAVA  
PERL  
 您的位置:首页 > article > unix > unix提高 >
栏目导栏
资料搜索
热门文章
·20%的SOLARIS知识解决80%的问题
·Solaris启动过程详解 zt
·查看Solaris系统硬件配置的命令
·Unix系列shell程序编写(中)
·STRUTS 源码学习笔记
·SOLARIS技巧篇
·snort源码分析
·Unix系列shell程序编写(下)
·在solaris 10/x86下安装oracle
·solaris 10 硬盘安装
·Solaris安全配置手册
·Apache源代码分析——关于模块
·HP-UX基本指令快速参考
· Tomcat Server源码启动分析
·Solaris8 双网卡配置(路由器用
最新文章
·solaris系统安全配置
·RHEL5.0操作系统下NFS服务的配
·Solaris Linux 9下Vsftpd的配置
·在HP-UX下建立只归属于某个目录
·SolarWinds2002使用说明
·从外部存储划盘并加入vg,为fs扩
·因带库问题导致系统光纤卡报错
·Unix下多线程中条件变量的使用
·UNIX和Linux中信号的个数
·不同的类UNIX操作系统密码破解
·AIX系统下Domino邮件服务器安装
·限制ROOT用户远程登陆UNIX系统
·Unix操作系统入侵追踪反击战
·AIX CDE不能启动的故障一般性解
·CentOS4.4用VSFTPD架设FTP服务
Google
 
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论坛讨论
评论】【加入收藏夹】【 】【打印】【关闭
※ 相关链接
 ·Solaris2.4 多线程编程指南3--使用同步对象编程  (2006-07-11 19:46:25)
 ·Solaris2.4 多线程编程指南2--用多线程编程  (2006-07-11 19:44:46)
 ·Solaris2.4 多线程编程指南1--线程基础  (2006-07-11 19:44:05)
 ·Solaris 9 for x86 在VM下安装全攻略  (2006-07-11 19:41:17)
 ·恢复Linux操作系统的GRUB引导程序  (2006-07-11 10:26:33)
 ·solaris系统备份到HP DAT72磁带上  (2006-07-11 10:23:17)
 ·用openwebmail架设webmail系统[方法/过程].  (2006-07-09 20:21:07)
 · xine安装指南  (2006-07-09 20:14:30)
 ·qmail在Linux,Solaris系统安装详解  (2006-07-09 00:28:09)
 ·Linux操作系统Gaim玩转QQ MSN  (2006-07-08 23:37:01)