Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1800767
  • 博文数量: 438
  • 博客积分: 9799
  • 博客等级: 中将
  • 技术积分: 6092
  • 用 户 组: 普通用户
  • 注册时间: 2012-03-25 17:25
文章分类

全部博文(438)

文章存档

2019年(1)

2013年(8)

2012年(429)

分类: 系统运维

2012-03-30 19:45:32

在7.10节,我们讨论了setjmp和longjmp函数,它们可以用作非本地分支。longjmp函数通常被一个信号处理器调用来返回到一个程序的主循环,而不是从处理器里返回。


然而调用longjmp有一个问题。当一个信号被捕获时,信号捕获函数被进入,当前信号被自动加到进程的信号掩码。这阻止了信号的后续发生而中断这个信号处理器。如果我们从信号处理器longjmp出去,进程的信号掩码会发生什么呢?


在FreeBSD 5.2.1和Mac OS X 10.3下,setjmp和longjmp存储并恢复信号掩码。然而,Linux 2.4.22和Solaris 9不这样做。FreeBSD和Mac OS X提供了函数_setjmp和_longjmp,它们不存储和恢复信号掩码。


为了允许任一形式的行为,POSIX.1不指定setjmp和longjmp在信号掩码上的效果。相反,POSIX.1定义了两个新的函数sigsetjmp和siglongjmp。在从一个信号处理器分支出去时应该总是使用这两个函数。



  1. #include <setjmp.h>

  2. int sigsetjmp(sigjmp_buf env, int savemask);

  3. 如果直接调用返回0,从siglongjmp调用返回则返回非0值。

  4. void siglongjmp(sigjmp_buf env, int val);


这 些函数和setjmp与longjmp函数的唯一区别是sigsetjmp有一个额外的参数。如果savemask是非0值,那么sigsetjmp也在 env里保存进程当前的信号掩码。当siglongjmp被调用时,如果env参数被一个非0的savemask的sigsetjmp调用保存,那么 siglongjmp恢复保存的信号掩码。


下面的代码演示了当一个信号处理器被自动调用时由系统安装的信号掩码如何被捕获的信号。程序也展示了sigsetjmp和siglongjmp函数的使用。



  1. #include <setjmp.h>
  2. #include <time.h>
  3. #include <signal.h>

  4. void pr_mask(const char *str);

  5. static void sig_usr1(int), sig_alrm(int);
  6. static sigjmp_buf jmpbuf;
  7. static volatile sig_atomic_t canjump;

  8. int
  9. main(void)
  10. {
  11.     if (signal(SIGUSR1, sig_usr1) == SIG_ERR) {
  12.         printf("signal(SIGUSR1) error\n");
  13.         exit(1);
  14.     }
  15.     if (signal(SIGALRM, sig_alrm) == SIG_ERR) {
  16.         printf("signal(SIGALM) error\n");
  17.         exit(1);
  18.     }
  19.     pr_mask("staring main: "); /* section 10.12 */

  20.     if (sigsetjmp(jmpbuf, 1)) {
  21.         pr_mask("ending main: ");
  22.         exit(0);
  23.     }
  24.     canjump = 1; /* now sigsetjmp() is OK */

  25.     for (;;)
  26.         pause();
  27. }

  28. static void
  29. sig_usr1(int signo)
  30. {
  31.     time_t starttime;

  32.     if (canjump == 0)
  33.         return; /* unexpected signal, ignore */

  34.     pr_mask("starting sig_usr1: ");
  35.     alarm(3); /* SIGALRM in 3 seconds */
  36.     starttime = time(NULL);
  37.     for (;;) /* busy wait for 5 seconds */
  38.         if (time(NULL) > starttime + 5)
  39.             break;
  40.     pr_mask("finishing sig_usr1: ");

  41.     canjump = 0;
  42.     siglongjmp(jmpbuf, 1); /* jump back to main, don't return */
  43. }

  44. static void
  45. sig_alrm(int signo)
  46. {
  47.     pr_mask("in sig_alrm: ");
  48. }

这个程序演示了另一个技术,它应该在无论何时siglongjmp在一个信号处理器里被调用时使用。我们把变量canjump变量设置为非0 值,只当我们调用sigsetjmp后。这个变量同样在信号处理器里检查,siglongjmp只当标志canjump为非0值时被调用。这提供了保护, 以免信号处理器在更早或更晚的时间被调用,当sigsetjmp还没初始化跳转缓冲时。(在这个平凡的程序里,我们在siglongjmp后快速地终止, 但是在更大的程序里,信号处理器可能在siglongjmp很久后仍保持安装状态。)提供这种类型的保护通常不被普通C代码里的longjmp需要(与一 个信号处理器相反)。因为一个信号可以在任何时间发生,我们需要在信号处理器里加入保护。

这里,我们使用了数据类型 sig_atomic_t,它由ISO C标准定义为不被中断的写的变量类型。意思是一个这样类型的变量不应该在一个有虚拟内存的系统上跨过页边界,而且可以被单个指定访问。我们总是同时为这些 数据类型包含volatile类型修饰符,因为这个变量被两个不同的信号控制访问:main函数和异步执行的信号处理器。


我们可以把程序分 为三部分。main、sig_usr1和sig_alrm。main函数会先执行,随后是sig_usr1,sig_usr1里的计时器会导致 sig_alrm的执行。在sig_alrm完毕后,返回到sig_usr1,而sig_usr1会返回到main。当执行sig_usr1时,信号掩码 是SIGUSR1。当执行sig_alrm时,信号掩码是SIGUSR1 | SIGALRM。

程序运行结果:
$ ./a.out &
[1] 10787
staring main:
$ kill -USR1 10787
starting sig_usr1: SIGUSR1
$ in sig_alrm: SIGUSR1 SIGALRM
finishing sig_usr1: SIGUSR1
ending main:

[1]+  完成                  ./a.out



输出如我们所料:当一个信号处理器被调用时,被捕获的信号被加入到进程的当前信号掩码。当信号处理器返回时原始掩码被恢复。同样,siglongjmp恢复由sigsetjmp保存的信号掩码。


如 果我们改变上面的代码在Linux上让setjmp和longjmp调用来代替sigsetjmp和siglongjmp调用(或在FreeBSD上用 _setjmp和_longjmp代替),输出的最终行变为:ending main: SIGUSR1。这意味着main函数在setjmp调用后执行时,SIGUSR1信号被阻塞。这很可能不是我们想要的。

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