信号的发送与捕捉 -- kill、raise、alarm、pause

2014-08-28 22:52:21   最后更新: 2014-08-29 13:03:36   访问数量:1025




int kill(pid_t pid,int signo); int raise(int signo);

 

定义于 signal.h 中

成功调用返回0,出错返回-1

kill 函数将信号发送给进程或进程组,raise 函数则向自身

调用 raise(signo) 等价于调用 kill(getpid(), signo)

 

  • kill 函数的 pid 参数有以下四种情况:

  1. pid > 0
  2. 将该信号发送给进程ID为 pid 的进程

  3. pid == 0
  4. 将该信号发送给与发送进程属于同一进程组的所有进程(发送进程需要具有向这些进程发送信号的权限)

  5. pid < 0
  6. 将该信号发送给进程组 ID 为 pid 的绝对值的所有进程

  7. pid == -1
  8. 将该信号发送给系统上的所有进程(发送进程需要具有向这些进程发送信号的权限)

  • 一般来说,内核进程以及init(pid 1)被称为系统进程集,是不包括在这里所说的“所有进程”中的

 

除超级用户外,发送进程只能将信号发送给实际用户ID或有效用户ID相同的接受者(SIGCOUNT 信号可以发送给属于同一会话的任何其他进程)

POSIX 将编号为 0 的信号定义为空信号,如果 signo 为0,则只进行正常的错误检查,而不发送信号,这种用法常常被用来检查指定的进程是否存在

如果向一个不存在的进程发送信号,则 kill 函数返回1,errno 被置为 ESRCH,但是必须了解的是,当一个进程结束以后,unix很可能将该PID用于新的进程,并且由于这种测试并不是原子操作,很可能在kill返回结果时原进程已经终止,所以这种测试并没有多大的意义

 

alarm

使用 alarm 函数可以设置一个计时器,在将来某个指定的时间该计时器会超时,产生 SIGALRM 信号,其默认动作是终止该进程

unsigned int alarm(unsigned int seconds);

 

定义于 unistd.h 中

返回 0 或以前设置的闹钟时间的余留秒数

  • 需要注意的是,经过 seconds 秒之后,内核会产生 SIGALRM 信号,但是由于进程调度的延迟,所以并不能保证进程处理信号的时间
  • 每个进程只能有一个闹钟时钟,如果在调用 alarm 时以前已经为该进程设置过闹钟时钟且尚未超时,则将该闹钟的余留值作为本次 alarm 函数调用的返回值,并将以前登记的闹钟时钟用新值代替

 

如果我们想捕捉 SIGALRM 信号,则必须在调用 alarm 之前设置该信号的处理程序,因为如果我们先调用 alarm 函数之后,可能在设置处理程序之前已经捕捉到到 SIGALRM 信号,则进程将根据默认动作终止

 

pause

pause 函数使调用进程挂起直至捕捉到一个信号

int pause(void);

 

定义于 unistd.h 中

返回 -1,并将 errno 设置为 EINTR

 

实例 -- sleep

通过使用 alarm 和 pause,可以使进程进入休眠一段时间

#include "../apue.2e/include/apue.h" #include <time.h> void sig_alarm (int signo); int sleep1 (unsigned int seconds); int main () { unsigned int t = time(0); printf("start: %d\n", t); int ret = sleep1(10); printf("ret: %d\n", ret); t = time(0); printf("end: %d\n", t); return 0; } int sleep1 (unsigned int seconds) { if (signal(SIGALRM, sig_alarm) == SIG_ERR) return -1; alarm(seconds); pause(); return 0; } void sig_alarm (int signo) { return; }

 

执行结果为:

 

 

上面的程序存在下面的几个问题:

  1. 如果在倒计时时间内,pause 先捕获了一个信号,那么就会在要求时间之前返回,那么,初步的解决方案是必须在函数返回时返回时钟剩余时间,以通知调用者,并且有必要清除此次倒计时
  2. 如果在调用 sleep1 之前,进程已经设置了闹钟,且尚未超时,则他会被 sleep1 中的 alarm 函数擦除,应在 alarm 函数调用前,首先检查第一次调用 alarm 的返回值,如其小于本次设置的值,则应该先等待上次超时,否则应在函数返回前再次设置闹钟,使其在上次闹钟的设定时间再次发生超时
  3. 该函数中修改了对 SIGALRM 的配置,作为供其他函数调用的函数,应首先保存该函数的原配置,并在返回前调用恢复原配置
  4. 在调用 alarm 和 pause 之间,有一个竞争条件,如果系统非常繁忙,可能在调用 pause 前 alarm 已经超时,并调用了信号处理程序,而到调用 pause 时,信号早已被处理,所以可能导致进程永远被挂起,有两种方法可以避免这个问题,一个是使用 setjmp,一个是使用 sigprocmask 与 sigsuspend,后一种方法我们将稍候介绍

 

 实例(改进版本)

#include "../apue.2e/include/apue.h" #include <time.h> #include <setjmp.h> void sig_alarm (int signo); int sleep2 (unsigned int seconds); void (*pre_sig) (int signo); jmp_buf jmpbuffer; int main () { unsigned int t = time(0); printf("start: %d\n", t); int ret = sleep2(10); printf("ret: %d\n", ret); t = time(0); printf("end: %d\n", t); return 0; } int sleep2 (unsigned int seconds) { int last_seconds = alarm(0); if (last_seconds != 0) return last_seconds > seconds ? sleep2(seconds), sleep2(last_seconds-seconds) : sleep2(last_seconds), sleep2(seconds-seconds); if ((pre_sig = signal(SIGALRM, sig_alarm)) == SIG_ERR) return -1; if (setjmp(jmpbuffer) == 0) { alarm(seconds); pause(); } if (signal(SIGALRM, pre_sig) == SIG_ERR) return -1; return alarm(0); } void sig_alarm (int signo) { longjmp(jmpbuffer, 1); }

 

上例避免了竞争条件,即使 pause 从未执行,发生 SIGALRM 时,sleep2 也会正常返回

 

但是 sleep2 依然有一个难以察觉的问题,就是在涉及与其他信号的交互时,如果 SIGALRM 中断了某个其他信号处理程序,则调用longjmp会提早终止该信号处理程序,如下面的程序所示:

#include "../apue.2e/include/apue.h" #include <time.h> #include <setjmp.h> static void sig_alarm (int signo); static void sig_int (int signo); int sleep2 (unsigned int seconds); jmp_buf jmpbuffer; int main () { if (signal(SIGINT, sig_int) == SIG_ERR) err_sys("signal SIGINT error"); unsigned int t = time(0); printf("start: %d\n", t); unsigned int ret = sleep2(10); printf("ret: %d\n", ret); t = time(0); printf("end: %d\n", t); return 0; } int sleep2 (unsigned int seconds) { int last_seconds = alarm(0); if (last_seconds != 0) return last_seconds > seconds ? sleep2(seconds), sleep2(last_seconds-seconds) : sleep2(last_seconds), sleep2(seconds-seconds); void (*pre_sig) (int signo); if ((pre_sig = signal(SIGALRM, sig_alarm)) == SIG_ERR) return -1; if (setjmp(jmpbuffer) == 0) { alarm(seconds); pause(); } if (signal(SIGALRM, pre_sig) == SIG_ERR) return -1; return alarm(0); } void sig_alarm (int signo) { longjmp(jmpbuffer, 1); } static void sig_int (int signo) { int i, j; volatile int k; printf("\nsig_int stating\n"); for (i=0; i<300000; ++i) for (j=0; j<4000; ++j) k += i*j; printf("\nsig_int finished\n"); }

 

执行结果为:

 

因为 SIGINT 处理程序中的 for 循环的执行时间没有超过 10 秒,而 pause 首先接到了 SIGINT 信号,所以提前退出了

 

如果改成调用 sleep2(2) 呢?

则会得到下面的运行效果:

 

因为 SIGINT 处理程序中的 for 循环时间超过了 2 秒,所以程序首先接到 SIGALRM 而提前 longjmp 导致程序结束

 

下面的程序解决了第一个问题:

#include "../apue.2e/include/apue.h" #include <time.h> #include <setjmp.h> static void sig_alarm (int signo); static void sig_int (int signo); int sleep2 (unsigned int seconds); jmp_buf jmpbuffer; int main () { if (signal(SIGINT, sig_int) == SIG_ERR) err_sys("signal SIGINT error"); unsigned int t = time(0); printf("start: %d\n", t); unsigned int ret = sleep2(10); printf("ret: %d\n", ret); t = time(0); printf("end: %d\n", t); return 0; } int sleep2 (unsigned int seconds) { int last_seconds = alarm(0); if (last_seconds == 0 && seconds == 0) return 0; if (last_seconds != 0) return last_seconds > seconds ? sleep2(seconds), sleep2(last_seconds-seconds) : sleep2(last_seconds), sleep2(seconds-seconds); void (*pre_sig) (int signo); if ((pre_sig = signal(SIGALRM, sig_alarm)) == SIG_ERR) return -1; if (setjmp(jmpbuffer) == 0) { alarm(seconds); pause(); } if (signal(SIGALRM, pre_sig) == SIG_ERR) return -1; return sleep2(alarm(0)); } void sig_alarm (int signo) { longjmp(jmpbuffer, 1); } static void sig_int (int signo) { int i, j; volatile int k; printf("\nsig_int stating\n"); for (i=0; i<300000; ++i) for (j=0; j<4000; ++j) k += i*j; printf("\nsig_int finished\n"); }

 

 

至于第二个问题我们后面进行讨论

 

实例 -- read_time_limit

我们可以根据上述的原理为read等函数操作设计超时功能:

#include "../apue.2e/include/apue.h" #include <time.h> #include <setjmp.h> int read_limit (int fd_in, void *buf, int nbyte, int time_limit); void sig_alarm (int signo); jmp_buf jmpbuffer; int main () { char c[100]; int ret = read_limit(STDIN_FILENO, c, 100, 5); if (ret == -1) write(STDOUT_FILENO, "function called error\n", sizeof("function called error\n")); else if (ret == -2) write(STDOUT_FILENO, "read timeout\n", sizeof("read timeout\n")); else { write(STDOUT_FILENO, "str: ", 5); write(STDOUT_FILENO, c, ret); } return 0; } int read_limit (int fd_in, void *buf, int nbyte, int time_limit) { if (buf == NULL) return -1; if (time_limit == 0) return read(fd_in, buf, nbyte); void (*pre_sig) (int signo); if ((pre_sig = signal(SIGALRM, sig_alarm)) == SIG_ERR) return -1; if (setjmp(jmpbuffer) != 0) return -2; alarm(time_limit); return read(fd_in, buf, nbyte); if (signal(SIGALRM, pre_sig) == SIG_ERR) return -1; } void sig_alarm (int signo) { longjmp(jmpbuffer, 1); }

 

 






读书笔记      linux      unix      龙潭书斋      apue      unix环境高级编程      signal      信号      kill      raise      alarm      pause      sigalrm      sigint     


京ICP备15018585号