Linux信号机制
1.信号本质
信号是进程间通信机制中唯一的异步通信机制,在软件层次上是对中断机制的一种模拟。即信号类似软中断。
信号和软中断的区别:
[1]中断有优先级,而信号没有优先级。
[2]信号处理程序是在用户态下运行的,而中断处理程序是在核心态下运行。
[3]中断响应是及时的,而信号响应通常都有较大的时间延迟。
2.信号的种类
信号分为可靠信号和不可靠信号。
可靠信号:可靠信号即通过排队的方式使信号不会丢失,信号值位于[SIGRTMIN,SIGRTMAX]之间的信号都是可靠信号。
不可靠信号:信号值小于SIGRTMIN的信号都是不可靠信号,即不会进行排队。
[root@node01 ~]# kill -l
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP
6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1
11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR
31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3
38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8
43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7
58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2
63) SIGRTMAX-1 64) SIGRTMAX
3.信号处理
信号的处理方式有三种:
[1]忽略信号,即对信号不做任何处理。
[2]捕捉信号。定义信号处理函数,当信号发生时,执行相应的处理函数。
[3]执行缺省操作,Linux对每种信号都规定了默认操作。
信号的安装函数:signal() 和 sigaction()。
[1]sighandler_t signal(int signum, sighandler_t handler);
[2]int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
sigaction()优于signal()主要体现在支持信号带有参数。
SIGKILL及SIGSTOP不能被忽略或者捕捉,虽然可以正常编译运行,但是并不会生效。
4.信号发送
发送信号的主要函数有:kill()、raise()、 sigqueue()、alarm()、setitimer()以及abort()。
int sigqueue(pid_t pid, int sig, const union sigval val);
该函数发送的信号附带参数val,这个参数会传递到信号的回调函数参数中,这时候的信号回调函数要设置成三个参数的,即:
act.sa_handler = signal_handler; //一个参数的信号回调函数
act.sa_sigaction = signal_handler1; //三个参数的信号回调函数
如果这时候两种回调函数都设置了,则只有三个参数的函数会生效。
int raise(int signo) ;
raise向进程本身发送信号,参数为即将发送的信号值。调用成功返回 0;否则,返回 -1。
unsigned int alarm(unsigned int seconds) ;
专门为SIGALRM信号而设,在指定的时间seconds秒后,将向进程本身发送SIGALRM信号,又称为闹钟时间。进程调用alarm后,任何以前的alarm()调用都将无效。如果参数seconds为零,那么进程内将不再包含任何闹钟时间。
int setitimer(int which, const struct itimerval *value, struct itimerval *ovalue));
setitimer()比alarm功能强大,支持3种类型的定时器,从参数就可以看到比alarm的时间更精细。
void abort(void);
向进程发送SIGABORT信号,默认情况下进程会异常退出,当然可定义自己的信号处理函数。
5.信号集及信号集操作函数
信号集用来描述信号的集合,linux所支持的所有信号可以全部或部分的出现在信号集中,主要与信号阻塞相关函数配合使用。下面是为信号集操作定义的相关函数:
int sigemptyset(sigset_t *set);
初始化(清空)信号集set。
int sigfillset(sigset_t *set);
把所有信号添加到信号集set中。
int sigaddset(sigset_t *set, int signum);
在set指向的信号集中加入signum信号。
int sigdelset(sigset_t *set, int signum);
在set指向的信号集中删除signum信号;
int sigismember(const sigset_t *set, int signum);
判定信号signum是否在set指向的信号集中。
6.信号阻塞和信号未决
每个进程都有一个用来描述哪些信号递送到进程时将被阻塞的信号集,该信号集中的所有信号在递送到进程后都将被阻塞。下面是与信号阻塞相关的几个函数:
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset));
该函数能够根据参数how来实现对信号集的操作,操作主要有三种:
SIG_BLOCK:在进程当前阻塞信号集中添加set指向信号集中的信号。
SIG_UNBLOCK:如果进程阻塞信号集中包含set指向信号集中的信号,则解除对该信号的阻塞。
SIG_SETMASK:重置进程阻塞信号集为set指向的信号集。
int sigpending(sigset_t *set));
获取当前已经产生却被阻塞的信号集合,也叫未决信号集。
int sigsuspend(const sigset_t *mask));
用于在接收到某个信号之前, 临时用mask替换进程的信号掩码, 该函数会阻塞,直到收到信号执行信号函数后才会返回,返回后将恢复调用之前的阻塞信号掩码。
7.sigaction的使用
sigaction函数的参数act:
struct sigaction {
void (*sa_handler)(int); //信号处理函数
void (*sa_sigaction)(int, siginfo_t *, void *); //信号处理函数,三个参数的回调函数
sigset_t sa_mask; //信号回调函数执行期间的信号屏蔽集
int sa_flags; //标记
void (*sa_restorer)(void);
};
sa_mask表示信号回调函数执行期间的信号屏蔽集。
sa_flags常设置为SA_RESTART:信号会导致系统调用(例如read()函数、sleep()函数)执行期间被打断,导致系统调用立刻返回失败(并且error被设置为EINTR),设置此标记后则系统调用不会立刻返回,而是在信号回调处理完成后继续系统调用流程。
下面的代码中,在信号屏蔽集中加入了信号SIGINT,这表示在信号回调函数执行期间会阻塞信号SIGINT,知道回调函数执行完才会继续处理这个SIGINT信号。
static void signal_handler(int sig)
{
printf("Processing SIGUSR1...\n");
sleep(5);
printf("sleep over\n");
}
int main(int argc, char** argv)
{
struct sigaction act, oldact;
act.sa_handler = signal_handler;
sigemptyset(&act.sa_mask);
sigaddset(&act.sa_mask, SIGINT);
act.sa_flags = SA_RESTART;
if(sigaction(SIGUSR1, &act, &oldact) < 0){
perror("sigaction error");
return 1;
}
while(1)
sleep(10);
return 0;
}
8.问题
问题1:使用signal函数时,并且使用编译选项-std=c99时,调用rt_sigaction()函数时会带上标记:SA_NODEFER|SA_RESETHAND,导致信号处理函数只能进入一次,后面再次触发信号会无效。
问题2:下面的程序运行时,如果连续执行“kill -SIGUSR1 2068”多次时(例如5次),回调函数只执行了2次。这是因为正执行回调函数时,第2次到来的信号被阻塞了。由于SIGUSR1是不可靠信号,所以后面到来的信号不会排队导致被丢弃。
static void sig_fun(int sig) { printf("sig fun in\n"); sleep(5); printf("sig fun out\n"); } int main() { signal(SIGUSR1, sig_fun); //signal(SIGRTMIN, sig_fun); while(1) sleep(1); return 0; }
问题3:把上面例子中的SIGUSR1改成SIGRTMIN后,连续执行5次“kill -SIGRTMIN 2068”,发现回调函数执行了5次,因为SIGRTMIN是可靠信号,后面来的信号会排队执行。
问题4:使用signal()函数时,并且使用编译选项-std=c99,这会导致信号回调函数执行后被重置为默认处理,并且在回调函数执行期间不阻塞当前信号。所以最好用sigaction来代替signal注册信号。