Chinaunix首页 | 论坛 | 博客
  • 博客访问: 318580
  • 博文数量: 69
  • 博客积分: 352
  • 博客等级: 入伍新兵
  • 技术积分: 296
  • 用 户 组: 普通用户
  • 注册时间: 2011-06-16 15:41
文章分类
文章存档

2023年(1)

2021年(1)

2020年(2)

2017年(4)

2016年(3)

2015年(1)

2013年(1)

2012年(21)

2011年(35)

分类:

2011-12-19 16:59:51

原文地址:APUE学习笔记_信号_进阶 作者:deepsky24

Outline 

- 1.可重入函数

- 2.可靠的信号

- 3.中断的系统调用

- 4.kill/rise

- 5.alarm/pause

- 6.信号集及其操作

=================================================================================================

1. 可重入函数
举一个例子,什么是不可重入的函数。假设一个进程正在执行malloc函数,此时接收到一个信号,转入信号处理例程中,在此例程中,又再次调用了malloc函数,将发生错误。原因是malloc通常需要操作一些系统全局变量,而插入的例程将在上一次malloc结束之前修改该变量,当例程返回时,malloc继续执行,但是由于全局变量已经被修改,将发生错误。

2. 可靠的信号
pending:信号已经发生,但是尚未被进程处理
delivery:信号发生却已经被处理
block阻塞:一个信号将保持penging状态知道进程对它接触了阻塞或者将其处理的工作改成为忽略。假设在该信号被接触阻塞之前,又发生了多次该信号,结果还是与只产生一次该信号相同

3. 中断的系统调用
有些可能会阻塞的系统调用处于阻塞状态时,进程若收到信号,那么该系统调用将被中断,并且立刻返回。

4. kill / raise
  1. #include <signal.h>
  2. int kill(pid_t pid, int signo);
  3. int raise(int signo);
调用kill / raise将产生某个信号,两个函数的区别在于kill可以发送给任意进程而raise只发送给调用进程。

5. alarm / pause
  1. #include <unistd.h>
  2. unsigned int alarm(unsigned int seconds);
  3. int pause(void);
alarm设置一个定时器,当超过指定时间后产生SIGALRM信号
pause函数使调用进程挂起,直到捕捉到一个信号,注意,pause不是一个安全的函数,在某些情况下会造成信号丢失引起的进程挂起,只能用sigsuspend解决。
  1. #include <unistd.h>
  2. #include <signal.h>
  3. #include <stdio.h>

  4. static void alarm_handler(int signo);

  5. int main()
  6. {
  7.     if(signal(SIGALRM, alarm_handler) == SIG_ERR)
  8.         printf("can't catch SIGALRM'");

  9.     alarm(1);
  10.     while(1)
  11.         pause();

  12.     return 0;
  13. }

  14. static void alarm_handler(int signo)
  15. {
  16.     if(signo == SIGALRM)
  17.         printf("signal alarm captured.\n");
  18. }
输出:
  1. deepsky@Debian:~/apue$ ./a.out
  2. signal alarm captured.
alarm还有一个非常常见的用法,用来对可能阻塞的操作设置时间上限值,其原理是系统调用可被信号中断,如欲深入了解其原因详请参见:
例子:设定read最多只能阻塞5s
  1. #include <signal.h>
  2. #include <stdio.h>
  3. #include <unistd.h>

  4. static void alarm_handler(int signo);

  5. int main()
  6. {
  7.     int n;
  8.     char line[100];

  9.     if(signal(SIGALRM, alarm_handler) == SIG_ERR)
  10.         printf("can't catch SIGALRM\n");

  11.     alarm(5);
  12.     if ((n = read(STDIN_FILENO, line, 100)) < 0)
  13.         printf("read error\n");
  14.     alarm(0); //if read() returns within 5s, we need to clear the previous alarm

  15.     write(STDOUT_FILENO, line, n);
  16.     return 0;
  17. }

  18. static void alarm_handler(int signo)
  19. {
  20.     if(signo == SIGALRM)
  21.         printf("caught SIGALRM.\n");
  22. }

6. 信号集及其操作
a )之前对信号的操作均基于每一个特定的信号,如SIGALRM,现在开始讨论如何对一个信号集合进行操作
  1. #include <signal.h> //sigset_t 为信号集合的结构
  2. int sigempty(sigset_t *set); //清空集合
  3. int sigfillset(sigset_t *set); //将所有信号加入集合
  4. int sigaddset(sigset_t *set, int signo); //向集合添加signo信号
  5. int sigdelset(sigset_t *set, int signo); //删除信号
  6. int sigismember(const sigset_t *set, int signo);//判断signo是否在集合中

b)进程可以将某些信号设定对阻塞,即产生信号后不立即处理,等待空闲或许要是才主动去处理,设定哪些信号需要阻塞使用了函数:
  1. #include <signal.h>
  2. int sigprocmask(int how, const sigset_t *restrict set, sigset_t *restrict oset);
参数说明:
  1. how: 指定以何种方式修改当前信号屏蔽集合,删除/添加/替换
  2. set:若非空指针,则表示how指定的操作有效,否则不会修改当前信号屏蔽集合
  3. oset:若非空指针,返回修改前的信号屏蔽字

c)获取当前被阻塞的信号,即已经发生,但未被处理:
  1. #include <signal.h>
  2. int sigpending(sigset_t *set);

d)sigaction函数
该函数作用与signal类似,只不过加入了信号集合以及信号屏蔽字的概念:
  1. #include <signal.h>
  2. int sigaction(int signo, const struct sigaction *restriict act, struct sigaction *restrict oact);

e)sigsuspend函数
此函数作用是可以安全地解除对某信号的阻塞,下面将通过实例说明。
设想一个场景,我们有一段代码在执行时,不希望被某个信号打断,我可以在这段代码之前将该信号加入屏蔽字中,等代码执行完后,将该信号从屏蔽字中删除:
  1. sigset_t tempset, oldset;
  2. sigemptyset(&tempset);
  3. sigaddset(&tempset, SIGUSR1);

  4. sigprocmask(SIG_BLOCK, &tempset, &oldset);

  5. /* 被保护的代码 */

  6. sigprocmask(SIG_SETMASK, &oldset, NULL);
  7. /* 窗口 */
  8. pause(); //因为在被屏蔽期间,可能发生过SIGUSR1,被解除后,SIGUSR1将被处理

以上代码乍看之下,没什么问题,但其实存在隐患。由于在调用sigprocmask之后,之前被阻塞的信号将立即生效,进程将调用相应的处理函数,也就是说在 /* 窗口 */位置就会发生信号,那么在pause执行以前,信号实际上已经被处理掉了,显然与我们的本意不符,我们本来期待pause可以捕获到该信号。这种现象称之为信号丢失。那么如果防止这种错误呢?答案就是使用sigsuspend。
  1. #include <stdio.h>
  2. #include <signal.h>

  3. int pr_mask(char *s)
  4. {
  5.     sigset_t sigset;

  6.     sigprocmask(0, NULL, &sigset);

  7.     printf("%s: ", s);
  8.     
  9.     if(sigismember(&sigset, SIGINT)) printf("SIGINT ");
  10.     if(sigismember(&sigset, SIGQUIT)) printf("SIGQUIT ");
  11.     if(sigismember(&sigset, SIGUSR1)) printf("SIGUSR1 ");
  12.     if(sigismember(&sigset, SIGALRM)) printf("SIGALRM ");
  13.     /* ..... */
  14.     
  15.     printf("\n");
  16.     return 0;
  17. }

  18. void sig_quit(int signo)
  19. {
  20.     pr_mask("in sig quit");
  21. }

  22. int main()
  23. {
  24.     sigset_t new, old, tempset;

  25.     signal(SIGQUIT, sig_quit);
  26.     
  27.     sigemptyset(&tempset);

  28.     sigemptyset(&new);
  29.     sigaddset(&new, SIGQUIT);
  30.     sigprocmask(SIG_BLOCK, &new, &old);

  31.     pr_mask("in critical section");
  32.     /* critical section */
  33.     
  34.     sigsuspend(&tempset); //解除阻塞,并将现有mask替换为空集
  35.     pr_mask("after return form sigsuspend");
  36.     
  37.     sigprocmask(SIG_UNBLOCK, &new, NULL);
  38.     pr_mask("program exit");
  39.     
  40.     return 0;
  41. }
输出:
  1. in critical section: SIGQUIT 
  2. in sig quit: SIGINT SIGQUIT //此处为向进程发送SIGQUIT后的输出,可见SIGQUIT已不再阻塞,但是为什么打印结果中还包括了SIGQUIT,因为在任何的信号处理程序中,都会将当前正在处理的信号加入到屏蔽字中,防止信号重入
  3. after return form sigsuspend: SIGQUIT //从sigsuspend返回后,它会将屏蔽字恢复到之前的状态,此时其实SIGQUTI还未被解除,只是在sigsuspend我们已经零时将其解除并处理了该信号,所以现在再调能够用sigprocmask就不用担心丢失信号的问题了 
  4. program exit:





阅读(1400) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~