Linux信号编程实践(二) 信号发送函数和可重入函数

    在早期的UNIX中信号是不可靠的,不可靠在这里指的是:信号可能丢失,一个信号发生了,但进程却可能一直不知道这一点。

现在Linux 在SIGRTMIN实时信号之前的都叫不可靠信号,这里的不可靠主要是不支持信号队列,就是当多个信号发生在进程中的时候(收到信号的速度超过进程处理的速度的时候),这些没来的及处理的信号就会被丢掉,仅仅留下一个信号。

可靠信号是多个信号发送到进程的时候(收到信号的速度超过进程处理信号的速度的时候),这些没来的及处理的信号就会排入进程的队列。等进程有机会来处理的时候,依次再处理,信号不丢失。

    同时,信号的发送和安装也出现了新版本:信号发送函数sigqueue()及信号安装函数sigaction()。

sigaction和signal函数都是调用内核服务do_signal函数;[内核服务函数,应用程序无法调用该函数

非实时信号都不支持排队,都是不可靠信号;实时信号都支持排队,都是可靠信号。

关于信号发送的一些API:

1.kill

[cpp]  view plain  copy
  1. int kill(pid_t pid, int signo);   

参数: 
pid:可能选择有以下四种

1. pid大于零时,pid是信号欲送往的进程的标识。
2. pid等于零时,信号将送往所有与调用kill()的那个进程属同一个使用组的进程。
3. pid等于-1时,信号将送往所有调用进程有权给其发送信号的进程,除了进程1(init)。
4. pid小于-1时,信号将送往以-pid为组标识的进程。

sig:准备发送的信号代码,假如其值为零则没有任何信号送出,但是系统会执行错误检查,通常会利用sig值为零来检验某个进程是否仍在执行。

   返回值说明: 成功执行时,返回0。失败返回-1,errno被设为以下的某个值 EINVAL:指定的信号码无效(参数 sig 不合法) EPERM;权限不够无法传送信号给指定进程 ESRCH:参数 pid 所指定的进程或进程组不存在。

[cpp]  view plain  copy
  1. void handler(int sig)  
  2. {  
  3.         printf("recvv a sig =%d\n",sig);  
  4. }  
  5. int main()  
  6. {  
  7.         if(signal(SIGUSR1,handler)==SIG_ERR)  
  8.         ERR_EXIT("signal error!");  
  9.         pid_t pid=fork();  
  10.         if(pid==-1)  
  11.                 ERR_EXIT("fork error!");  
  12.         else if(pid==0)  
  13.         {  
  14.                                 //等于 killpg(getpgrp(),SIGUSR1);  
  15.                 pid=getpgrp();  
  16.                 kill(-pid,SIGUSR1);  
  17.                 /* 
  18.                    kill(getppid(),SIGUSR1); 
  19.                  */  
  20.                                 //以上  
  21.                 exit(EXIT_SUCCESS);  
  22.         }  
  23.         int n=5;  
  24.         do{  
  25.                 n=sleep(n);  
  26.         }while(n>0);//  
  27.         return 0;  
  28. }  

注意点:

(1)sleep函数会被信号打断,进行完信号的处理函数后不再睡眠,而是继续执行sleep函数以后的操作。如果我们就是想要程序睡眠一段时间呢?通过man手册发现,sleep函数的返回值是 还剩余的秒数,所以可以采用循环的形式,即:

[cpp]  view plain  copy
  1. while(n=sleep(n));  
(2)如果发出信号的目标是进程组,那么子进程fork的时候会继承信号,从而会发生两次信号处理。

2.raise

   raise()给自己发送信号,等价于raise(getpid(),sig)

3.killpg

   killpg()给进程组发送信号,killpg(pgrp,sig)等于kill(pgrp,sig)

4.sigqueue

[cpp]  view plain  copy
  1. int sigqueue(pid_t pid, int sig, const union sigval value);   
给进程发送信号,支持排队,可以附带信息。
5.alarm

  alarm函数,设置一个闹钟延迟发送SIGALRM信号(告诉Linux内核n秒中以后,发送SIGALRM信号);
但是alarm函数一次只能发送一个信号,所以必须递归调用才可以实现间歇性发送信号的功能。

[cpp]  view plain  copy
  1. void handler(int sig)  
  2. {  
  3.         printf("recvv a sig =%d\n",sig);  
  4.         alarm(1); //间接递归,持续发送信号  
  5. }  
  6. int main()  
  7. {  
  8.         if(signal(SIGALRM,handler)==SIG_ERR)  
  9.         ERR_EXIT("signal error!");  
  10.         alarm(1);  
  11.         while(1)  
  12.                 pause();  
  13.         return 0;  
  14. }  

可重入/不可重入函数

主要用于多任务环境中,一个可重入的函数简单来说就是可以被中断的函数,也就是说,可以在这个函数执行的任何时刻中断它,转入OS调度下去执行另外一段代码,而返回控制时不会出现什么错误;而不可重入的函数由于使用了一些系统资源,比如全局变量区,中断向量表等,所以它如果被中断的话,可能会出现问题,这类函数是不能运行在多任务环境下的。

也可以这样理解,重入即表示重复进入,首先它意味着这个函数可以被中断,其次意味着它除了使用自己栈上的变量以外不依赖于任何环境(包括static),这样的函数就是purecode(纯代码)可重入,可以允许有该函数的多个副本在运行,由于它们使用的是分离的栈,所以不会互相干扰。如果确实需要访问全局变量(包括static),一定要注意实施互斥手段。可重入函数在并行运行环境中非常重要,但是一般要为访问全局变量付出一些性能代价。

编写可重入函数时,若使用全局变量,则应通过关中断、信号量(即P、V操作)等手段对其加以保护。

 说明:若对所使用的全局变量不加以保护,则此函数就不具有可重入性,即当多个进程调用此函数时,很有可能使有关全局变量变为不可知状态。

  为了增强程序的稳定性,在信号处理函数中应使用可重入函数。 

 下面给出大家一个不可重入函数在信号处理程序中可能发生的错误:

[cpp]  view plain  copy
  1. ypedef struct  
  2. {  
  3.         int a;  
  4.         int b;  
  5.         int c;  
  6.         int d;  
  7. }TEST;  
  8. TEST g_data;  
  9. void handler(int sig)  
  10. {  
  11.        // printf("recvv a sig =%d\n",sig);  
  12.         printf("%d %d %d %d\n",g_data.a,g_data.b,g_data.c,g_data.d);  
  13.         alarm(1);  
  14. }  
  15. int main()  
  16. {  
  17.         TEST zeros={0,0,0,0};  
  18.         TEST ones={1,1,1,1};  
  19.         if(signal(SIGALRM,handler)==SIG_ERR)  
  20.         ERR_EXIT("signal error!");  
  21.    
  22.         g_data=zeros;  
  23.         alarm(1);  
  24.         while(1)  
  25.         {  
  26.                 g_data=zeros;  
  27.                 g_data=ones;  
  28.         }  
  29.    
  30.         return 0;  
  31. }  

    如图,会出现类似0011的交叉结果,原因是g_data=ones内部对a,b,c,d分别赋值时,有可能被信号中断,造成一部分旧值还没来的及替换,发生错误。这说明进行的操作不是原子操作,是不可重入函数。不要在信号处理程序中使用。

我们可以使用man 7 signal查看常用的可重入函数:




不可重入函数

满足下列条件的函数多数是不可重入的:

  (1)使用静态数据结构,如getlogin(),gmtime(),getgrgid(),getgrnam(),getpwuid()以及getpwnam()等等;

  (2)函数实现时,调用了malloc()或者free()函数;

  (3)实现时使用了标准I/O函数

 关于可重入函数和不可重入函数的相关详细实例,请参考  http://www.cnblogs.com/luvi/archive/2008/05/09/1190493.html

猜你喜欢

转载自blog.csdn.net/zjy900507/article/details/80340621