1 相关函数介绍
1.1 函数kill和raise
kill将信号发送给进程。raise函数则允许进程向自身发送信号。
#include <signal.h>
// 成功,返回0;失败,返回-1
int kill(pid_t pid, int signo);
int raise(int signo);
调用raise(signo)等价于调用kill(getpid(), signo)
kill函数的pid参数分为以下4种情况:
(1)pid>0
将信号发送给进程ID为pid的进程
(2)pid=0
将信号发送给与发送进程属于同一进程组的所有进程(发送进程具有权限向这些进程发送信号)
(3)pid<0
将信号给其进程组ID等于pid绝对值,且发送进程具有权限向其发送信号的所有进程
(4)pid==-1
将信号发送给发送进程有权限向它们发送信号的所有进程
进程将信号发送给其他进程需要权限。超级用户可以将信号发送给任一进程。对于非超级用户,规则是发送者的实际用户ID或有效用户ID必须等于接收者的实际用户ID或有效用户ID。
1.2 函数alarm和pause
alarm函数可以设置一个定时器,在将来的某个时刻该定时器会超时。当定时器超时时,产生SIGALARM信号。如果忽略或不捕捉该信号,则默认动作是终止调用alarm函数的进程。
#include <unistd.h>
//返回值:0或之前设置的闹钟时间的余留值
unsigned int alarm(unsigned int seconds);
每个进程只能有一个闹钟时间。如果在调用alarm时,之前已为该进程注册的闹钟时间还没有超时,则该闹钟时间的余留值作为本次alarm函数调用的返回值。以前注册的闹钟时间则被新值代替。如果有之前注册的尚未超过的闹钟时间,且本次调用的seconds值为0,那么其余留值仍作为alarm函数返回,然后取消以前的闹钟时间。
pause函数使调用进程挂起直到捕捉到一个信号。
#include <unistd.h>
//返回值:-1,errno设置为EINTR
int pause(void);
只有执行了一个信号处理程序并从其返回时,pause才返回。这时,pause返回-1,errno设置为EINTR。
2 使用alarm函数实现sleep
使用alarm和pause,进程可以使自己休眠一段指定的时间。
#include <signal.h>
#include <unistd.h>
#include <stdio.h>
static void sig_alrm(int signo)
{
/* nothing to do, just return to wake up the pause */
printf("sig_alrm !!! \n");
}
unsigned int sleep1(unsigned int seconds)
{
if (signal(SIGALRM, sig_alrm) == SIG_ERR)
return(seconds);
printf("alarm start !!!\n");
alarm(seconds); /* start the timer */
pause(); /* next caught signal wakes us up */
printf("alarm done !!!\n");
return(alarm(0)); /* turn off timer, return unslept time */
}
int main(void) {
unsigned int unslept;
unslept = sleep1(5);
printf("unslept:%d \n", unslept);
return 0;
}
测试结果如下:
xxx$ ./sleep1
alarm start !!!
sig_alrm !!!
alarm done !!!
unslept:0
实际上,sleep1函数有竞争关系:alarm在调用pause之前超时。如果发生这种情况,则在调用pause后将不会再捕捉到信号,调用者将永远被挂起。
可以使用setjmp和longjmp来避免如上的竞争条件:
#include <setjmp.h>
#include <signal.h>
#include <unistd.h>
#include <stdio.h>
static jmp_buf env_alrm;
static void sig_alrm(int signo)
{
printf("sig_alrm!!! \n");
longjmp(env_alrm, 1);
}
unsigned int sleep2(unsigned int seconds)
{
if (signal(SIGALRM, sig_alrm) == SIG_ERR)
return(seconds);
if (setjmp(env_alrm) == 0) {
printf("alarm start!!! \n");
alarm(seconds); /* start the timer */
pause(); /* next caught signal wakes us up */
printf("after pause 1 !!! \n");
}
// longjmp to here!
printf("after pause 2 !!! alarm done!!! \n");
return(alarm(0)); /* turn off timer, return unslept time */
}
int main(void) {
unsigned int unslept;
unslept = sleep2(5);
printf("unslept:%d \n", unslept);
return 0;
}
测试结果如下:
xxx$ ./sleep2
alarm start!!!
sig_alrm!!!
after pause 2 !!! alarm done!!!
unslept:0
sleep2函数也有问题,涉及到与其他信号的交互。例如如下伪代码:
unsigned int sleep2(unsigned int);
static void sig_int(int);
int main(void)
{
unsigned int unslept;
if (signal(SIGINT, sig_int) == SIG_ERR)
err_sys("signal(SIGINT) error");
unslept = sleep2(5);
printf("sleep2 returned: %u\n", unslept);
exit(0);
}
static void sig_int(int signo)
{
//耗时操作>5s
}
如上程序若SIGINT的信号处理程序先触发,且是耗时操作>5s。则等sleep2(5) 5s超时后,sig_int程序会被突然打断,引起异常。
3 使用alarm函数实现对阻塞操作设置超时
alarm函数还常用于对可能阻塞的操作设置时间上限值。
如下程序,从标准输入读一行,然后将其写到标准输出上。
#include <setjmp.h>
#include <signal.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#define MAXLINE 4096
static void sig_alrm(int);
static jmp_buf env_alrm;
int main(void)
{
int n;
char line[MAXLINE];
if (signal(SIGALRM, sig_alrm) == SIG_ERR)
printf("signal(SIGALRM) error");
if (setjmp(env_alrm) != 0) {
// longjmp here!!!
printf("read timeout \n");
goto out1;
}
alarm(10);
if ((n = read(STDIN_FILENO, line, MAXLINE)) < 0)
printf("read error \n");
alarm(0);
write(STDOUT_FILENO, line, n);
exit(0);
out1:
printf("out1 exit(1) >>> \n");
exit(1);
}
static void sig_alrm(int signo)
{
longjmp(env_alrm, 1);
}
测试结果如下:
xxx$ ./read2
read timeout
out1 exit(1) >>>
xxx$ ./read2
safasdf
safasdf