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

全部博文(438)

文章存档

2019年(1)

2013年(8)

2012年(429)

分类: 系统运维

2012-03-30 19:17:47

alarm函数允许我们设置一个计时器,它将在一个指定的时间到期。当这个计时器到期时,SIGALRM信号被产生。如果我们忽略或不捕获这个信号,它的默认行为是终止这个进程。



  1. #include <unistd.h>

  2. unsigned int alarm(unsigned int seconds);

  3. 返回0或上次设置的警报到现在的时间。


seconds值是将来这个信号应该产生时的时钟秒数。小心当时间发生时,信号被内核产生,但是在进程得到处理信号的控制前可以有额外的时间,因为处理器的调度延迟。


早期UNIX系统实现警告信号也可能早最多1秒被发送。POSIX.1不允许这个。


每个进程只有一个这样的警报。如果当我们调用alarm时一个之前为进程注册的闹钟还没有过期,那个闹钟还剩的秒数被返回。前一个注册的闹钟被新的值替代。


如 果前一个注册的闹钟没有过期而seconds的值为0,那么前一个闹钟被取消。然后如果进程想要终止,它可以在终止前执行任何所需的清理。如果我们要捕获 SIGALRM,那要小心在调用alarm之前安装它的信号处理器。如果在安装信号处理器前,我们先调用alarm并被发送一个SIGALRM,我们的进 程会终止。


pause函数暂停调用进程,直到一个信号被捕获。



  1. #include <unistd.h>

  2. int pause(void);

  3. 返回-1,errno设置为EINTR。


pause返回的唯一时机是一个信号处理器被执行并返回。在这种情况下,pause返回-1,errno设置为EINTR。


通过使用alarm和pause,我们可以把进程催眠一个指定的时间。下面的sleep1函数做了这件事(但是它有问题,稍后我们会看到):



  1. #include <signal.h>
  2. #include <unistd.h>

  3. static void
  4. sig_alrm(int signo)
  5. {
  6.     /* nothing to do, just return to wake up the pause */
  7. }

  8. unsigned int
  9. sleep1(unsigned int nsecs)
  10. {
  11.     if (signal(SIGALRM, sig_alrm) == SIG_ERR)
  12.         return(nsecs);
  13.     alarm(nsecs); /* start the timer */
  14.     pause(); /* next caught signal wakes us up */
  15.     return(alarm(0)); /* turn off timer, return unslept time */
  16. }

这个函数看起来像sleep函数(10.19节),但是这个简单的实现有三个问题:

1、如果调用者已经设置了一个闹钟,那个闹钟被第 一个alarm调用清除。我们可以通过查看第一个alarm的返回值来修正它。如果之前设置的闹钟剩余的秒数比这个参数小,那么我们只等到之前的闹钟到 期。如果前面设置的闹钟在我们之后,在返回前我们要重调这个闹钟以使它在将来所定的时间发生。

2、我们已经为SIGALRM修改了布署。如果我们正在为其它人写一个调用的函数,那么我们应该在被调用时保存这个布署,在完成时恢复它。我们可以通过保存signal的返回值并在return之前重置它来修正。

3、在第一次alarm调用和pause调用之间有竞争条件。在一个繁忙的系统,有可能在我们调用pause之前闹钟已经到期而信号处理器被调用。如果它发生了,调用者会永远被puase调用挂起(假定没有其它信号被捕获。)

sleep的早期实现看起来像我们的程序,但是修正了描述的第一个和第二个问题。有两种方法可以修正问题3。第一个使用setjmp,我们在下面的例子里展示。另一个是使用sigprocmask和sigsuspend,在10.19节讨论。


下面的代码使用setjmp和longjmp(10.7节)实现sleep,避免前一个例子描述的问题3。一个称为sleep2的另一个简单版本。(为了减少代码,我们不处理问题1和问题2。):



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

  4. static jmp_buf env_alrm;

  5. static void
  6. sig_alrm(int signo)
  7. {
  8.     longjmp(env_alrm, 1);
  9. }

  10. unsigned int
  11. sleep2(unsigned int nsecs)
  12. {
  13.     if (signal(SIGALRM, sig_alrm) == SIG_ERR)
  14.         return(nsecs);
  15.     if (setjmp(env_alrm) == 0) {
  16.         alarm(nsecs); /* start the timer */
  17.         pause(); /* next caught signal wakes us up */
  18.     }
  19.     return(alarm(0)); /* turn off timer, return unslept time */
  20. }



sleep2函数避免了前面的竞争条件。即使pause没有执行,sleep2函数当SIGALRM发生时也会返回。

然而,涉及其它信号交互的sleep2有另一个诡异的问题。如果SIGALRM中断了其它一些信号处理器,当我们调用longjmp时,我们会中止其它的信号处理器。下面的代码展示了这个场景:



  1. static void sig_int(int);

  2. int
  3. main(void)
  4. {
  5.     unsigned int unslept;

  6.     if (signal(SIGINT, sig_int) == SIG_ERR) {
  7.         printf("signal(SIGINT) error\n");
  8.         exit(1);
  9.     }
  10.     unslept = sleep2(5);
  11.     printf("sleep2 returned: %u\n", unslept);
  12.     exit(0);
  13. }

  14. static void
  15. sig_int(int signo)
  16. {
  17.     int i, j;
  18.     volatile int k;

  19.     /*
  20.      * Tune these loops to run for more than 5 secondes
  21.      * on whatever system this test program is run.
  22.      */
  23.     printf("\nsig_int starting\n");
  24.     for (i = 0; i < 300000; i++)
  25.         for (j = 0; j < 4000; j++)
  26.             k += i * j;
  27.     printf("sig_int finished\n");
  28. }

SIGINT处理器里的循环在作者使用的系统上会超过5秒。我们简单地想它比sleep2的参数执行的长些。整型k被声明为volatile来避免一个优化编译器舍弃这个循环。运行结果如下:
$ ./a.out
^C (中断字符)
sig_int starting
sleep2 returned: 0


我们可以看到sleep2函数里的longjmp中止了其它信号处理器,sig_int,尽管它还没有完成。如果你把SVR2的sleep函数和其它信号处理器混在一起时,这将是你碰到的。

sleep1和sleep2这两个例子的目的,是展示天真地处理信号时的问题。后面几节将展示解决所有这些问题的方法,所以我们可以可靠地处理信号,而不影响其它代码。


alarm 函数的一个普遍用法,除了实现sleep,还有为一个可以阻塞的操作加上时间上限。例如,如果我们在一个可以阻塞的设备上有一个读操作(一个“慢”设 备,10.5节),我们可能想这个读操作在一段时间后到时。下面的代码做了这件事,从标准输入里读取一行并写到标准输出上:



  1. #include <signal.h>
  2. #include <stdio.h>
  3. #include <unistd.h>

  4. #define MAXLINE 4096
  5. static void sig_alrm(int);

  6. int
  7. main(void)
  8. {
  9.     int n;
  10.     char line[MAXLINE];

  11.     if (signal(SIGALRM, sig_alrm) == SIG_ERR) {
  12.         printf("signal(SIGALRM) error\n");
  13.         exit(1);
  14.     }

  15.     alarm(10);
  16.     if ((n = read(STDIN_FILENO, line, MAXLINE)) < 0) {
  17.         printf("read error\n");
  18.         exit(1);
  19.     }
  20.     alarm(0);

  21.     write(STDOUT_FILENO, line, n);
  22.     exit(0);
  23. }

  24. static void
  25. sig_alrm(int signo)
  26. {
  27.     /* nothing to do, just return to interrupt the read */
  28. }


这段代码在UNIX应用上很普遍,但这个程序有两个问题。


1、程序有和sleep1一样的瑕疵:第一次调用alarm和 read的调用之间有竞争条件。如果内核在这两个函数之间阻塞了比闹钟期限更长的时间,read可能会永远阻塞。多数这种类型的操作使用一个长的闹钟期, 比如一分钟或更多,使它不太可能发生。尽管如此,它是一个竞争条件。


2、如果系统调用自动重启,当SIGALRM信号处理器返回时read不被中断。这种情况下,计时不做任何事。

这里,我们明确地想要一个系统调用被中断。POSIX.1没有给我们一个可移植的方法来做这件事,但是SUS的XSI扩展可以。我们将在10.4节讨论更多。


让我们重新用longjmp实现前一个例子,这种方法,我们不用担心一个慢的系统调用是否被中断:



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

  4. #define MAXLINE 4096

  5. static void sig_alrm(int);
  6. static jmp_buf env_alrm;

  7. int
  8. main(void)
  9. {
  10.     int n;
  11.     char line[MAXLINE];

  12.     if (signal(SIGALRM, sig_alrm) == SIG_ERR) {
  13.         printf("signal(SIGALRM) error\n");
  14.         exit(1);
  15.     }
  16.     if (setjmp(env_alrm) != 0) {
  17.         printf("read timeout\n");
  18.         exit(1);
  19.     }

  20.     alarm(10);
  21.     if ((n = read(STDIN_FILENO, line, MAXLINE)) < 0) {
  22.         printf("read error\n");
  23.         exit(1);
  24.     }
  25.     alarm(0);

  26.     write(STDOUT_FILENO, line, n);
  27.     exit(0);
  28. }

  29. static void
  30. sig_alrm(int signo)
  31. {
  32.     longjmp(env_alrm, 1);
  33. }


这个版本如期望的一样,不管系统是否重启了中断的系统调用。但是,注意我们仍然有和其它信号处理器交互的问题,和早先的代码一样。
阅读(997) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~