`
yzd
  • 浏览: 1812270 次
  • 性别: Icon_minigender_2
  • 来自: 北京
文章分类
社区版块
存档分类
最新评论

linuxIPC——信号(下)

 
阅读更多
<p><span style="font-size: medium;"><a name="1"><span class="atitle">一、信号生命周期</span>
</a>
</span>
</p>
<p><span style="font-size: medium;">从信号发送到信号处理函数的执行完毕</span>
</p>
<p><span style="font-size: medium;">对于一个完整的信号生命周期(从信号发送到相应的处理函数执行完毕)来说,可以分为三个重要的阶段,这三个阶段由四个重要事件来刻画:信号诞生;信号在进程中注册完毕;信号在进程中的注销完毕;信号处理函数执行完毕。相邻两个事件的时间间隔构成信号生命周期的一个阶段。</span>
</p>
<p>

     

        <span style="font-size: medium;"><br><img src="http://www.ibm.com/developerworks/cn/linux/l-ipc/part2/3.gif" alt="" width="580" height="33"></span>

       

      <span style="font-size: medium;"><br></span>
</p>
<p><span style="font-size: medium;">下面阐述四个事件的实际意义:</span>
</p>
<ol>
<li>
<span style="font-size: medium;">信号"诞生"。信号的诞生指的是触发信号的事件发生(如检测到硬件异常、定时器超时以及调用信号发送函数kill()或sigqueue()等)。</span>
</li>
<li>
<span style="font-size: medium;">信号在目标进程中"注册";进程的task_struct结构中有关于本进程中未决信号的数据成员:
          </span>
<table style="width: 500px;" border="0" cellspacing="0" cellpadding="0"><tbody><tr>
<td class="code-outline">
<pre class="displaycodeliquid"><span style="font-size: medium;">struct sigpending pending:
struct sigpending{
struct sigqueue *head, **tail;
sigset_t signal;
};
</span>
</pre>
</td>
</tr></tbody></table>
<span style="font-size: medium;"><br>

第三个成员是进程中所有未决信号集,第一、第二个成员分别指向一个sigqueue类型的结构链(称之为"未决信号信息链")的首尾,信息链中的每个sigqueue结构刻画一个特定信号所携带的信息,并指向下一个sigqueue结构:
          </span>
<table style="width: 500px;" border="0" cellspacing="0" cellpadding="0"><tbody><tr>
<td class="code-outline">
<pre class="displaycodeliquid"><span style="font-size: medium;">struct sigqueue{
struct sigqueue *next;
siginfo_t info;
}
</span>
</pre>
</td>
</tr></tbody></table>
<span style="font-size: medium;"><br>

信号在进程中注册指的就是信号值加入到进程的未决信号集中(sigpending结构的第二个成员sigset_t signal),并且信号所携带的信息被保留到未决信号信息链的某个sigqueue结构中。
只要信号在进程的未决信号集中,表明进程已经知道这些信号的存在,但还没来得及处理,或者该信号被进程阻塞。
          </span>
<p>

            <span style="font-size: medium;"><strong>注:</strong>
</span>

            <span style="font-size: medium;"><br>
当一个实时信号发送给一个进程时,不管该信号是否已经在进程中注册,都会被再注册一次,因此,信号不会丢失,因此,实时信号又叫做"可靠信号"。这意味着
同一个实时信号可以在同一个进程的未决信号信息链中占有多个sigqueue结构(进程每收到一个实时信号,都会为它分配一个结构来登记该信号信息,并把
该结构添加在未决信号链尾,即所有诞生的实时信号都会在目标进程中注册);
            <br>
当一个非实时信号发送给一个进程时,如果该信号已经在进程中注册,则该信号将被丢弃,造成信号丢失。因此,非实时信号又叫做"不可靠信号"。这意味着同一
个非实时信号在进程的未决信号信息链中,至多占有一个sigqueue结构(一个非实时信号诞生后,(1)、如果发现相同的信号已经在目标结构中注册,则
不再注册,对于进程来说,相当于不知道本次信号发生,信号丢失;(2)、如果进程的未决信号中没有相同信号,则在进程中注册自己)。
          </span>
</p>
</li>
<li>
<span style="font-size: medium;">信号在进程中的注销。在目标进程执行过程中,会检测是否有信号等待处理(每次从系统空间返回到用户空间时都做这样的检查)。如果
存在未决信号等待处理且该信号没有被进程阻塞,则在运行相应的信号处理函数前,进程会把信号在未决信号链中占有的结构卸掉。是否将信号从进程未决信号集中
删除对于实时与非实时信号是不同的。对于非实时信号来说,由于在未决信号信息链中最多只占用一个sigqueue结构,因此该结构被释放后,应该把信号在
进程未决信号集中删除(信号注销完毕);而对于实时信号来说,可能在未决信号信息链中占用多个sigqueue结构,因此应该针对占用sigqueue结
构的数目区别对待:如果只占用一个sigqueue结构(进程只收到该信号一次),则应该把信号在进程的未决信号集中删除(信号注销完毕)。否则,不应该
在进程的未决信号集中删除该信号(信号注销完毕)。
          <br>
进程在执行信号相应处理函数之前,首先要把信号在进程中注销。
        </span>
</li>
<li>
<span style="font-size: medium;">信号生命终止。进程注销信号后,立即执行相应的信号处理函数,执行完毕后,信号的本次发送对进程的影响彻底结束。
          </span>
<p>

            <span style="font-size: medium;"><strong>注:</strong>
</span>

            <span style="font-size: medium;"><br>
1)信号注册与否,与发送信号的函数(如kill()或sigqueue()等)以及信号安装函数(signal()及
sigaction())无关,只与信号值有关(信号值小于SIGRTMIN的信号最多只注册一次,信号值在SIGRTMIN及SIGRTMAX之间的信
号,只要被进程接收到就被注册)。
            <br>
2)在信号被注销到相应的信号处理函数执行完毕这段时间内,如果进程又收到同一信号多次,则对实时信号来说,每一次都会在进程中注册;而对于非实时信号来说,无论收到多少次信号,都会视为只收到一个信号,只在进程中注册一次。
          </span>
</p>
</li>
</ol>
<div class="ibm-alternate-rule">
<hr>
</div>
<p><span style="font-size: medium;"><a name="2"><span class="atitle">二、信号编程注意事项</span>
</a>
</span>
</p>
<ol>
<li>
<span style="font-size: medium;">防止不该丢失的信号丢失。如果对八中所提到的信号生命周期理解深刻的话,很容易知道信号会不会丢失,以及在哪里丢失。</span>
</li>
<li>

          <span style="font-size: medium;"><strong>程序的可移植性</strong>
</span>

          <span style="font-size: medium;"><br>
考虑到程序的可移植性,应该尽量采用POSIX信号函数,POSIX信号函数主要分为两类:
        </span>
<ul>
<li>
<span style="font-size: medium;">POSIX 1003.1信号函数:
Kill()、sigaction()、sigaddset()、sigdelset()、sigemptyset()、sigfillset()、sigismember()、sigpending()、sigprocmask()、sigsuspend()。
</span>
</li>
<li>
<span style="font-size: medium;">POSIX 1003.1b信号函数。POSIX 1003.1b在信号的实时性方面对POSIX 1003.1做了扩展,包括以下三个函数:
sigqueue()、sigtimedwait()、sigwaitinfo()。
其中,sigqueue主要针对信号发送,而sigtimedwait及sigwaitinfo()主要用于取代sigsuspend()函数,后面有相应实例。
            </span>
<table style="width: 100%;" border="0" cellspacing="0" cellpadding="0"><tbody><tr>
<td class="code-outline">
<pre class="displaycodeliquid"><span style="font-size: medium;">#include &lt;signal.h&gt;
int sigwaitinfo(sigset_t *set, siginfo_t *info).
</span>
</pre>
</td>
</tr></tbody></table>
<span style="font-size: medium;"><br>

该函数与sigsuspend()类似,阻塞一个进程直到特定信号发生,但信号到来时不执行信号处理函数,而是返回信号值。因此为了避免执行相应的信号处理函数,必须在调用该函数前,使进程屏蔽掉set指向的信号,因此调用该函数的典型代码是:
            </span>
<table style="width: 100%;" border="0" cellspacing="0" cellpadding="0"><tbody><tr>
<td class="code-outline">
<pre class="displaycodeliquid"><span style="font-size: medium;">sigset_t newmask;
int rcvd_sig;
siginfo_t info;
sigemptyset(&amp;newmask);
sigaddset(&amp;newmask, SIGRTMIN);
sigprocmask(SIG_BLOCK, &amp;newmask, NULL);
rcvd_sig = sigwaitinfo(&amp;newmask, &amp;info)
if (rcvd_sig == -1) {
..
}
</span>
</pre>
</td>
</tr></tbody></table>
<span style="font-size: medium;"><br>

调用成功返回信号值,否则返回-1。sigtimedwait()功能相似,只不过增加了一个进程等待的时间。
          </span>
</li>
</ul>
</li>
<li>

          <span style="font-size: medium;"><strong>程序的稳定性。</strong>
</span>

          <span style="font-size: medium;"><br>
为了增强程序的稳定性,在信号处理函数中应使用可重入函数。
          </span>
<p><span style="font-size: medium;">信号处理程序中应当使用可再入(可重入)函数(注:所谓可重入函数是指一个可以被多个任务调用的过程,任务在调用时不必担心数
据是否会出错)。因为进程在收到信号后,就将跳转到信号处理函数去接着执行。如果信号处理函数中使用了不可重入函数,那么信号处理函数可能会修改原来进程
中不应该被修改的数据,这样进程从信号处理函数中返回接着执行时,可能会出现不可预料的后果。不可再入函数在信号处理函数中被视为不安全函数。</span>
</p>
<p><span style="font-size: medium;">满足下列条件的函数多数是不可再入的:(1)使用静态的数据结构,如
getlogin(),gmtime(),getgrgid(),getgrnam(),getpwuid()以及getpwnam()等等;(2)函数
实现时,调用了malloc()或者free()函数;(3)实现时使用了标准I/O函数的。The Open Group视下列函数为可再入的:</span>
</p>
<p><span style="font-size: medium;">_exit()、access()、alarm()、cfgetispeed()、cfgetospeed()、
cfsetispeed()、cfsetospeed()、chdir()、chmod()、chown()
、close()、creat()、dup()、dup2()、execle()、execve()、fcntl()、fork()、
fpathconf()、fstat()、fsync()、getegid()、
geteuid()、getgid()、getgroups()、getpgrp()、getpid()、getppid()、getuid()、
kill()、link()、lseek()、mkdir()、mkfifo()、
open()、pathconf()、pause()、pipe()、raise()、read()、rename()、rmdir()、
setgid()、setpgid()、setsid()、setuid()、
sigaction()、sigaddset()、sigdelset()、sigemptyset()、sigfillset()、
sigismember()、signal()、sigpending()、sigprocmask()、sigsuspend()、sleep()、
stat()、sysconf()、tcdrain()、tcflow()、tcflush()、tcgetattr()、tcgetpgrp()、
tcsendbreak()、tcsetattr()、tcsetpgrp()、time()、times()、
umask()、uname()、unlink()、utime()、wait()、waitpid()、write()。
</span>
</p>
<p><span style="font-size: medium;">即使信号处理函数使用的都是"安全函数",同样要注意进入处理函数时,首先要保存errno的值,结束时,再恢复原值。因为,
信号处理过程中,errno值随时可能被改变。另外,longjmp()以及siglongjmp()没有被列为可再入函数,因为不能保证紧接着两个函数
的其它调用是安全的。</span>
</p>
</li>
</ol>
<div class="ibm-alternate-rule">
<hr>
</div>
<p><span style="font-size: medium;"><a name="3"><span class="atitle">三、深入浅出:信号应用实例</span>
</a>
</span>
</p>
<p><span style="font-size: medium;">linux下的信号应用并没有想象的那么恐怖,程序员所要做的最多只有三件事情:</span>
</p>
<ol>
<li>
<span style="font-size: medium;">安装信号(推荐使用sigaction());</span>
</li>
<li>
<span style="font-size: medium;">实现三参数信号处理函数,handler(int signal,struct siginfo *info, void *);</span>
</li>
<li>
<span style="font-size: medium;">发送信号,推荐使用sigqueue()。</span>
</li>
</ol>
<p><span style="font-size: medium;">实际上,对有些信号来说,只要安装信号就足够了(信号处理方式采用缺省或忽略)。其他可能要做的无非是与信号集相关的几种操作。</span>
</p>
<p>

        <span style="font-size: medium;"><strong>实例一:信号发送及处理</strong>
</span>

        <span style="font-size: medium;"><br>
实现一个信号接收程序sigreceive(其中信号安装由sigaction())。
      </span>
</p>
<table style="width: 100%;" border="0" cellspacing="0" cellpadding="0"><tbody><tr>
<td class="code-outline">
<pre class="displaycode"><span style="font-size: medium;">#include &lt;signal.h&gt;
#include &lt;sys/types.h&gt;
#include &lt;unistd.h&gt;
void new_op(int,siginfo_t*,void*);
int main(int argc,char**argv)
{
struct sigaction act;
int sig;
sig=atoi(argv[1]);

sigemptyset(&amp;act.sa_mask);
act.sa_flags=SA_SIGINFO;
act.sa_sigaction=new_op;

if(sigaction(sig,&amp;act,NULL) &lt; 0)
{
printf("install sigal error/n");
}

while(1)
{
sleep(2);
printf("wait for the signal/n");
}
}
void new_op(int signum,siginfo_t *info,void *myact)
{
printf("receive signal %d", signum);
sleep(5);
}
</span>
</pre>
</td>
</tr></tbody></table>
<p><span style="font-size: medium;"><br></span>
</p>
<p><span style="font-size: medium;">说明,命令行参数为信号值,后台运行sigreceive signo &amp;,可获得该进程的ID,假设为pid,然后再另一终端上运行kill -s signo pid验证信号的发送接收及处理。同时,可验证信号的排队问题。
        <br><strong>注:</strong>
</span>

        <span style="font-size: medium;">可以用sigqueue实现一个命令行信号发送程序sigqueuesend,见 附录。
      </span>
</p>
<p>

        <span style="font-size: medium;"><strong>实例二:信号传递附加信息</strong>
</span>

        <span style="font-size: medium;"><br>
主要包括两个实例:
      </span>
</p>
<ol>
<li>
<span style="font-size: medium;">向进程本身发送信号,并传递指针参数;
          </span>
<table style="width: 100%;" border="0" cellspacing="0" cellpadding="0"><tbody><tr>
<td class="code-outline">
<pre class="displaycodeliquid"><span style="font-size: medium;">#include &lt;signal.h&gt;
#include &lt;sys/types.h&gt;
#include &lt;unistd.h&gt;
void new_op(int,siginfo_t*,void*);
int main(int argc,char**argv)
{
struct sigaction act;
union sigval mysigval;
int i;
int sig;
pid_t pid;
char data[10];
memset(data,0,sizeof(data));
for(i=0;i &lt; 5;i++)
data[i]='2';
mysigval.sival_ptr=data;

sig=atoi(argv[1]);
pid=getpid();

sigemptyset(&amp;act.sa_mask);
act.sa_sigaction=new_op;//三参数信号处理函数
act.sa_flags=SA_SIGINFO;//信息传递开关
if(sigaction(sig,&amp;act,NULL) &lt; 0)
{
printf("install sigal error/n");
}
while(1)
{
sleep(2);
printf("wait for the signal/n");
sigqueue(pid,sig,mysigval);//向本进程发送信号,并传递附加信息
}
}
void new_op(int signum,siginfo_t *info,void *myact)//三参数信号处理函数的实现
{
int i;
for(i=0;i&lt;10;i++)
{
printf("%c/n ",(*( (char*)((*info).si_ptr)+i)));
}
printf("handle signal %d over;",signum);
}
</span>
</pre>
</td>
</tr></tbody></table>
<span style="font-size: medium;"><br></span>
<p><span style="font-size: medium;">这个例子中,信号实现了附加信息的传递,信号究竟如何对这些信息进行处理则取决于具体的应用。</span>
</p>
</li>
<li>
<span style="font-size: medium;">2、 不同进程间传递整型参数:把1中的信号发送和接收放在两个程序中,并且在发送过程中传递整型参数。
          <br>
信号接收程序:
          </span>
<table style="width: 100%;" border="0" cellspacing="0" cellpadding="0"><tbody><tr>
<td class="code-outline">
<pre class="displaycodeliquid"><span style="font-size: medium;">#include &lt;signal.h&gt;
#include &lt;sys/types.h&gt;
#include &lt;unistd.h&gt;
void new_op(int,siginfo_t*,void*);
int main(int argc,char**argv)
{
struct sigaction act;
int sig;
pid_t pid;

pid=getpid();
sig=atoi(argv[1]);

sigemptyset(&amp;act.sa_mask);
act.sa_sigaction=new_op;
act.sa_flags=SA_SIGINFO;
if(sigaction(sig,&amp;act,NULL)&lt;0)
{
printf("install sigal error/n");
}
while(1)
{
sleep(2);
printf("wait for the signal/n");
}
}
void new_op(int signum,siginfo_t *info,void *myact)
{
printf("the int value is %d /n",info-&gt;si_int);
}
</span>
</pre>
</td>
</tr></tbody></table>
<span style="font-size: medium;"><br></span>
<p><span style="font-size: medium;">信号发送程序:命令行第二个参数为信号值,第三个参数为接收进程ID。</span>
</p>
<table style="width: 100%;" border="0" cellspacing="0" cellpadding="0"><tbody><tr>
<td class="code-outline">
<pre class="displaycodeliquid"><span style="font-size: medium;">#include &lt;signal.h&gt;
#include &lt;sys/time.h&gt;
#include &lt;unistd.h&gt;
#include &lt;sys/types.h&gt;
main(int argc,char**argv)
{
pid_t pid;
int signum;
union sigval mysigval;
signum=atoi(argv[1]);
pid=(pid_t)atoi(argv[2]);
mysigval.sival_int=8;//不代表具体含义,只用于说明问题
if(sigqueue(pid,signum,mysigval)==-1)
printf("send error/n");
sleep(2);
}
</span>
</pre>
</td>
</tr></tbody></table>
<span style="font-size: medium;"><br></span>
<p>

            <span style="font-size: medium;"><strong>注:</strong>
实例2的两个例子侧重点在于用信号来传递信息,目前关于在linux下通过信号传递信息
的实例非常少,倒是Unix下有一些,但传递的基本上都是关于传递一个整数,传递指针的我还没看到。我一直没有实现不同进程间的指针传递(实际上更有意
义),也许在实现方法上存在问题吧,请实现者email我。
          </span>
</p>
</li>
</ol>
<p>

        <span style="font-size: medium;"><strong>实例三:信号阻塞及信号集操作</strong>
</span>

      </p>
<table style="width: 100%;" border="0" cellspacing="0" cellpadding="0"><tbody><tr>
<td class="code-outline">
<pre class="displaycode"><span style="font-size: medium;">#include "signal.h"
#include "unistd.h"
static void my_op(int);
main()
{
sigset_t new_mask,old_mask,pending_mask;
struct sigaction act;
sigemptyset(&amp;act.sa_mask);
act.sa_flags=SA_SIGINFO;
act.sa_sigaction=(void*)my_op;
if(sigaction(SIGRTMIN+10,&amp;act,NULL))
printf("install signal SIGRTMIN+10 error/n");
sigemptyset(&amp;new_mask);
sigaddset(&amp;new_mask,SIGRTMIN+10);
if(sigprocmask(SIG_BLOCK, &amp;new_mask,&amp;old_mask))
printf("block signal SIGRTMIN+10 error/n");
sleep(10);
printf("now begin to get pending mask and unblock SIGRTMIN+10/n");
if(sigpending(&amp;pending_mask)&lt;0)
printf("get pending mask error/n");
if(sigismember(&amp;pending_mask,SIGRTMIN+10))
printf("signal SIGRTMIN+10 is pending/n");
if(sigprocmask(SIG_SETMASK,&amp;old_mask,NULL)&lt;0)
printf("unblock signal error/n");
printf("signal unblocked/n");
sleep(10);
}
static void my_op(int signum)
{
printf("receive signal %d /n",signum);
}
</span>
</pre>
</td>
</tr></tbody></table>
<p><span style="font-size: medium;"><br></span>
</p>
<p><span style="font-size: medium;">编译该程序,并以后台方式运行。在另一终端向该进程发送信号(运行kill -s 42 pid,SIGRTMIN+10为42),查看结果可以看出几个关键函数的运行机制,信号集相关操作比较简单。</span>
</p>
<p>

        <span style="font-size: medium;"><strong>注:</strong>
在上面几个实例中,使用了printf()函数,只是作为诊断工具,pringf()函数是不可重入的,不应在信号处理函数中使用。
      </span>
</p>
分享到:
评论

相关推荐

    Linux学习笔记Linux学习资料Linux教程

    【linux学习笔记--18】POSIX IPC——信号量.doc 【linux学习笔记--19】POSIX IPC——共享内存.doc 【linux学习笔记-10】Linux进程相关系统调用(三).doc 【linux学习笔记-11】守护进程daemon.doc 【linux学习笔记-...

    非常宝贵的LINUX学习笔记

    【linux学习笔记-1】使用GDB调试简单的用户程序 【linux学习笔记-2】父子进程共享文件描述符 【linux学习笔记-3】文件操作...【linux学习笔记--18】POSIX IPC——信号量 【linux学习笔记--19】POSIX IPC——共享内存

    linux源代码分析——消息管理

    在操作系统中,有些进程存在着相互制约的关系,这些制约关系来源于并行进程的相互合作和资源共享。...在linux 中支持UNIX SYSTEM V 的三种通信机制: 消息队列, 信号量和共享内存. 现就消息队列这种机制进行分析.

    linux系统进程间通信——共享内存(System V版本)

    之前用过Prosix版本的共享内存和信号量,一直没有实践System V版本的,主要是因为其信号量集的概念操作有些复杂,今天试着写一个SV版本的共享内存进程间通信,使用信号量同步。程序提供了几个简单的用于操作SV版本...

    Unix环境高级编程(APUE)——linux入门必学

    书中除了介绍UNIX文件和目录、标准I/O库、系统数据文件和信息、进程环境、进程控制、进程关系、信号、线程、线程控制、守护进程、各种I/O、进程间通信、网络IPC、伪终端等方面的内容,还在此基础上介绍了多个应用...

    Linux高性能服务器编程

    《Linux高性能服务器编程》共17章,分为3个部分:第一部分对Linux服务器编程的核心基础——TCP/IP协议进行了深入的解读和阐述,包括TCP/IP协议族、TCP/IP协议,以及一个经典的TCP/IP通信案例;第二部分对高性能...

    Linux程序设计 第4版.haozip01

    14.1.3 linux的信号量机制 490 14.1.4 使用信号量 492 14.2 共享内存 496 14.2.1 shmget函数 497 14.2.2 shmat函数 497 14.2.3 shmdt 498 14.2.4 shmctl 498 14.3 消息队列 502 14.3.1 msgget函数 502 ...

    Linux程序设计 第4版.haozip02

    14.1.3 linux的信号量机制 490 14.1.4 使用信号量 492 14.2 共享内存 496 14.2.1 shmget函数 497 14.2.2 shmat函数 497 14.2.3 shmdt 498 14.2.4 shmctl 498 14.3 消息队列 502 14.3.1 msgget函数 502 ...

    Android驱动开发权威指南

    7.5.2 Linux下的DMA编程 第8章Linux设备驱动中的中断 8.1 Linux中断及中断处理架构 8.2 Linux中断编程 8.2.1申请和释放中断 8.2.2使能与屏蔽中断 8.2.3底半部机制 8.2.4中断共享 8.3 Linux定时器 8.4 Linux延时处理 ...

    Linux内核工作原理 word版本 强烈推荐

    信号与管道是 其中的两种,Linux同时还支持系统V IPC机制。这些进程间通讯机制在IPC一章中描叙。 外部设备互连(PCI)标准已经成为PC上低价位高数传率的总线标准。PCI一章将描叙Linux核心是如何初始化并使用PCI总线...

    Linux网络编程

    4.8.3 信号量的实例——semtool,交互式的信号量使用工具.......103 4.9 共享内存(Shared Memory) ............109 4.9.1 有关的数据结构.....109 4.9.2 有关的函数......... 110 4.9.3 共享内存应用举例——shmtool...

    linux 网络编程源代码

    1.5 Linux 的发展.................................................................................................. 11 1.5.1 Linux 的发展历史 ..............................................................

    Linux网络编程.pdf socket tcp udp

    1.5 Linux 的发展.................................................................................................. 11 1.5.1 Linux 的发展历史 ............................................................

Global site tag (gtag.js) - Google Analytics