Linux信号实践(2) --信号分类

来源:互联网 发布:java程序员代码库 编辑:程序博客网 时间:2024/04/26 05:12

信号分类 

不可靠信号

Linux信号机制基本上是从UNIX系统中继承过来的。早期UNIX系统中的信号机制比较简单和原始,后来在实践中暴露出一些问题,它的主要问题是:

   1.进程每次处理信号后,就将对信号的响应设置为默认动作。在某些情况下,将导致对信号的错误处理;因此,用户如果不希望这样的操作,那么就要在信号处理函数结尾再一次调用signal(),重新安装该信号。

   2.因此导致, 早期UNIX下的不可靠信号主要指的是进程可能对信号做出错误的反应以及信号可能丢失。 

Linux支持不可靠信号,但是对不可靠信号机制做了改进:在调用完信号处理函数后,不必重新调用该信号的安装函数(信号安装函数是在可靠机制上的实现)。因此,Linux下的不可靠信号问题主要指的是信号可能丢失


可靠信号

随着时间的发展,实践证明,有必要对信号的原始机制加以改进和扩充。所以,后来出现的各种UNIX版本分别在这方面进行了研究,力图实现"可靠信号"。由于原来定义的信号已有许多应用,不好再做改动,最终只好又新增加了一些信号(SIGRTMIN ~ SIGRTMAX),并在一开始就把它们定义为可靠信号,这些信号支持排队,不会丢失。同时,信号的发送和安装也出现了新版本:信号发送函数sigqueue()及信号安装函数sigaction()。

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

早期UNIX系统只定义了31种信号,而Linux 3.x支持64种信号,编号1-64(SIGRTMIN=34,SIGRTMAX=64),将来可能进一步增加,这需要得到内核的支持。 前31种信号已经有了预定义值,每个信号有了确定的用途及含义,并且每种信号都有各自的缺省动作。如按键盘的CTRL+C时,会产生SIGINT信号,对该信号的默认反应就是进程终止。后32个信号表示实时信号,等同于可靠信号。这保证了发送的多个实时信号都被接收。实时信号是POSIX标准的一部分,可用于应用进程。

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


信号API-信号发送(1)

1.kill

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. int kill(pid_t pid, int signo);   

kill既可以向自身发送信号,也可以向其他进程发送信号 

signo参数组合情况解释

   pid>0 将信号sig发给pid进程

   pid=0 将信号sig发给同组进程

   pid=-1 将信号sig发送给所有进程,调用者进程有权限发送的每一个进程(除了1号进程之外,还有它自身)

   pid<-1 将信号sig发送给进程pid(绝对值)的每一个进程

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. //示例  
  2. void onSignalAction(int signalNumber)  
  3. {  
  4.     switch(signalNumber)  
  5.     {  
  6.     case SIGUSR1:  
  7.         cout << "SIGUSR1 = " << signalNumber << endl;  
  8.         break;  
  9.     default:  
  10.         cout << "Other Signal ..." << endl;  
  11.         break;  
  12.     }  
  13. }  
  14.   
  15. int main()  
  16. {  
  17.     if (signal(SIGUSR1,onSignalAction)== SIG_ERR)  
  18.     {  
  19.         perror("signal error");  
  20.         return -1;  
  21.     }  
  22.   
  23.     pid_t pid = fork();  
  24.     if (pid == -1)  
  25.     {  
  26.         perror("fork error");  
  27.         return -1;  
  28.     }  
  29.     else if (pid == 0)  
  30.     {  
  31.         /**向父进程发送信号 
  32.         pid_t ppid = getppid(); 
  33.         kill(ppid,SIGUSR1); 
  34.         */  
  35.   
  36.         /**向同组所有进程发送信号,子进程也会收到该信号 
  37.         kill(0,SIGUSR1); 
  38.         */  
  39.   
  40.         //向本组所有进程发送信号,作用同上  
  41. //getpgrp()函数获取进程组pid  
  42.         pid_t pgid = getpgrp();  
  43.         killpg(pgid,SIGUSR1);  
  44.         exit(0);  
  45.     }  
  46.   
  47.     int sleepTime = 3;  
  48.     while (sleepTime > 0)  
  49.     {  
  50.         write(STDOUT_FILENO,"Parent start Sleep...\n",  
  51.               sizeof("Parent start Sleep...\n"));  
  52.         sleepTime = sleep(sleepTime);  
  53.         write(STDOUT_FILENO,"Parent return from Sleep...\n",  
  54.               sizeof("Parent return from Sleep...\n"));  
  55.     }  
  56.   
  57.     return 0;  
  58. }  

注意:如果在fork之前安装信号,则子进程可以继承信号

 

Sleep遇上signal,子进程向父进程发送信号,sleep函数的几点说明

   1)sleep函数作用,让进程睡眠。

   2)能被信号打断,然后处理信号函数以后,就不再睡眠了。直接向下执行代码

   3)sleep函数的返回值,是剩余的秒数

Man手册显示:

RETURN VALUE

       Zero if the requested time has elapsed, or the number of  seconds  left to sleep, 

if the call was interrupted by a signal handler.

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. //示例:sleep遇上signal  
  2. void onSignalAction(int signalNumber)  
  3. {  
  4.     switch(signalNumber)  
  5.     {  
  6.     case SIGINT:  
  7.         cout << "SIGINT = " << signalNumber << endl;  
  8.         break;  
  9.     default:  
  10.         cout << "Other Signal ..." << endl;  
  11.         break;  
  12.     }  
  13. }  
  14.   
  15. int main()  
  16. {  
  17.     if (signal(SIGINT,onSignalAction)== SIG_ERR)  
  18.     {  
  19.         perror("signal error");  
  20.         return -1;  
  21.     }  
  22.     cout << "Main Start Sleeping..." << endl;  
  23.     int returnValue = sleep(100); //可中断睡眠  
  24.     cout << "Main End Sleeping... returnValue = " << returnValue << endl;  
  25.   
  26.     return 0;  
  27. }  
[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. //示例:sleep加强  
  2. int main()  
  3. {  
  4. //...同上  
  5. cout << "Main Start Sleeping..." << endl;  
  6. //sleep加强版^^  
  7.     int sleepTime = 20;  
  8.     do  
  9.     {  
  10.         sleepTime = sleep(sleepTime);  
  11. cout << "continue..." << endl;  
  12.     }  
  13.     while (sleepTime > 0);  
  14.     cout << "Main End Sleeping... sleepTime = " << sleepTime << endl;  
  15.   
  16.     return 0;  
  17. }  

2.raise

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. int raise(int sig);  

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


3.killpg

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. int killpg(int pgrp, int sig);  

进程组发送信号。killpg(pgrp, sig)等价于kill(-pgrp, sig);


4.sigqueue

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. int sigqueue(pid_t pid, int sig, const union sigval value);  

给进程发送信号,支持排队,可以附带信息。


信号API-pause

int pause(void);

将进程置为可中断睡眠状态。然后它调用内核函数schedule(),使Linux进程调度器找到另一个进程来运行

pause使调用者进程挂起,直到一个信号被捕获

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. //示例  
  2. int main()  
  3. {  
  4.     if (signal(SIGINT,handler)== SIG_ERR)  
  5.         err_exit("signal error");  
  6.     while(true)  
  7.     {  
  8.         pause();  
  9.         cout << "pause return..." << endl;  
  10.     }  
  11. }  

信号API-信号发送(2)

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. unsigned int alarm(unsigned int seconds);  

alarm函数,设置一个闹钟延迟发送SIGALRM信号(告诉Linux内核n秒中以后,发送SIGALRM信号);

手册描述-DESCRIPTION

       alarm() arranges for a SIGALRM signal to be delivered to the process in seconds seconds.

       If seconds is zero, no new alarm() is scheduled.

       In any event any previously set alarm() is cancelled.

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. //alarm 递归调用  
  2. void onSignalAction(int signalNumber)  
  3. {  
  4.     switch(signalNumber)  
  5.     {  
  6.     case SIGALRM:  
  7.         cout << "SIGALRM = " << signalNumber << endl;  
  8.         alarm(1);   //继续调用onSignalAction  
  9.         break;  
  10.     default:  
  11.         cout << "Other Signal ..." << endl;  
  12.         break;  
  13.     }  
  14. }  
  15.   
  16. int main()  
  17. {  
  18.     if (signal(SIGALRM,onSignalAction)== SIG_ERR)  
  19.     {  
  20.         perror("signal error");  
  21.         return -1;  
  22.     }  
  23.   
  24.     alarm(1);  
  25.   
  26.     while(true)  
  27.     {  
  28.         pause();  
  29.         cout << "pause returned..." << endl;  
  30.     }  
  31.   
  32.     return 0;  
  33. }  

可重入/不可重入函数

  所谓可重入函数是指一个可以被多个任务调用的过程,任务在调用时不必担心数据是否会出错。因为进程在收到信号后,就将跳转到信号处理函数去接着执行。如果信号处理函数中使用了不可重入函数,那么信号处理函数可能会修改原来进程中不应该被修改的数据,这样进程从信号处理函数中返回接着执行时,可能会出现不可预料的后果。不可重入函数在信号处理函数中被视为不安全函数。

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

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. //不可重入函数示例  
  2. struct Teacher  
  3. {  
  4.     int a;  
  5.     int b;  
  6.     int c;  
  7.     int d;  
  8. };  
  9.   
  10. Teacher g_teacher;  
  11. void onSigAlarm(int signo)  
  12. {  
  13.     printf("%d %d", g_teacher.a, g_teacher.b);  
  14.     printf(" %d %d\n", g_teacher.c, g_teacher.d);  
  15.     alarm(1);  
  16. }  
  17.   
  18. int main()  
  19. {  
  20.     if (signal(SIGALRM,onSigAlarm)== SIG_ERR)  
  21.         err_exit("signal error");  
  22.   
  23.     Teacher zero = {0, 0, 0, 0};  
  24.     Teacher ones = {1, 1, 1, 1};  
  25.   
  26.     alarm(1);  
  27.     g_teacher = zero;  
  28.     while(true)  
  29.     {  
  30.         g_teacher = zero;  
  31.         g_teacher = ones;  
  32.     }  
  33. }  

输出结果演示:

 

原因分析:

可以将语句g_teacher = zero分解为:

        g_teacher.a = zero.a;

        g_teacher.b = zero.b;

        g_teacher.c = zero.c;

        g_teacher.d = zero.d;

因此, 在这四条语句执行的中间, 如果此时SIGALRM信号到达(中断到达), 则g_teacher中的一些数据会是新值, 而有些却是以前留下的脏值, 究其原始则是g_teacher = zero不是原子操作, 而信号处理函数onSigAlarm却又访问了全局变量g_teacher. 

如果将两条printf封装成一个函数

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. void unsafe_function()  
  2. {  
  3.     printf("%d %d", g_teacher.a, g_teacher.b);  
  4.     printf(" %d %d\n", g_teacher.c, g_teacher.d);  
  5. }  

然后在onSigAlarm中调用, 则unsafe_function函数就成了不可重入函数(其实printf就是不可重入函数), 因此, 在信号响应函数中, 尽量不要调用不可重入函数;

 

不可重入函数

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

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

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

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

 

附-man 7 signal可以查看那些函数是可重入和不可重入的.

0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 腰椎间盘突出初期症状 腰椎间盘突出症的锻炼 腰椎间盘突出的锻炼 腰椎间盘突出的护理 腰椎间盘突出按摩手法 腰椎间盘突出和脱出 腰椎间盘突出如何保养 腰椎间盘突出的保养 腰椎间盘突出有何症状 腰椎间盘突出医院好 得了腰椎间盘突出怎么办 腰椎间盘突出的锻炼方法 腰椎间盘为什么会突出 腰椎间盘突出要注意哪些 腰椎间盘突出吃中药 腰椎间盘突出的腰带 腰椎间盘突出按摩器 腰椎间盘突出新疗法 腰椎间盘突出费用多少 腰椎间盘突出微创多少钱 腰椎间盘突出哪里医院好 腰椎间盘突出怎样按摩 腰椎间盘突出哪个医院好 腰椎间盘突出适合什么运动 腰椎间盘突出能自愈吗 腰椎间盘突出能复位吗 腰椎间盘突出手法复位 腰椎间盘突出能做瑜伽吗 霍华德腰椎间盘突出 腰椎间盘突出拍片能看出来吗 腰椎间盘突出去那家医院好 腰椎间盘突出能用按摩器吗 腰椎间盘突出专业医院 腰椎间盘突出应该看什么科 腰椎间盘突出要住院吗 腰椎间盘突出的微创疗法 腰椎间盘滑脱是怎么回事 腰椎间盘突出康复训练 腰椎间盘突出有什么好的办法 腰椎间盘突出 哪个医院 腰椎间盘突出中药方