Linux进程间通信之三:信号

信号是进程间唯一异步通信机制,是一种软中断。
头文件:signal.h
Linux一共定义了32个非实时信号。编号0-31。常见的如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#define SIGNAL       0
#define SIGHUP 1
#define SIGINT 2
#define SIGQUIT 3

#define SIGTRAP 5
#define SIGABRT 6

#define SIGBUS 7
#define SIGFPE 8 //算术异常
#define SIGKILL 9 //杀死信号,不能被屏蔽
#define SIGUSR1 10 //用户信号1
#define SIGSEGV 11 //段错误
#define SIGUSR2 12 //用户信号2
#define SIGPIPE 13 //写没有读进程的管道
#define SIGALRM 14 //闹钟超时
#define SIGTERM 15

#define SIGCHLD 17 //子进程改变作业状态
#define SIGCONT 18
#define SIGSTOP 19 //停止作业。它不能被用户屏蔽
#define SIGTSTP 20
......

信号0特别说明:
“signal 0″ is kind of like a moral equivalent of “ping”.
signal 0 is just used to check process is exists or not.
Using “kill -0 NNN” in a shell script is a good way to tell if PID “NNN” is alive or not.

一、信号基本工作原理

  1. task_struct含有与信号相关的signal_struct, sighand_struct, sigpending等关键结构体。
  2. 发送给进程的信号,如果是私有信号(只发给进程的信号),则放到sigpending指向的链表;
  3. 发送给进程的信号,如果是共享信号 (进程的所有线程共享),则放到signal指向的链表;
  4. 如果安装了信号(即注册了信号处理函数),则信号处理函数放入相应的sighand指向的数组,数组有64项,下标对应的是信号编号。
  5. 当进程每次从内核态返回用户态,就会调用do_signal检查链表是否有信号要处理,如果有,则根据信号编号为下标,去sighand数组索引找到相应的信号处理函数,删除链表的该信号,并到用户空间执行该函数。否则执行默认处理。

二、信号集结构体sigset_t

1
2
3
typedef struct{
unsigned long sig[2];
}sigset_t;

所以sigset_t一共含有64bit。为Linux可声明的最大信号数。

其中,sig[0]的32bit从1-31bit就对应常用的编号为1-31的信号。0表示信号存在,1表示没有该信号。

不要直接操作这些bit位。定义变量sigset_t myset,有专用接口如下:

  1. sigemptyset(&myset);清空myset,也就是使得数组元素的bit都为0;
  2. sigfillset(&myset);填满信号集myset,也就是使数组元素bit都为1;
  3. sigaddset(&myset,SIGUSR1)把myset数组里与信号SIGUSR1编号对应的bit位置为1,相当于添加了SIGUSR1到这个信号集。
  4. sigdelset(&myset,SIGUSR2)从信号集myset删除SIGUSR2信号。
  5. sigismember(&myset,signo) 返回1 表示signo在信号集,0表示不在.

三、发送信号

int kill(pid_t pid, int signo);
给指定进程发信号

raise(int signo);
给进程自己发信号

unsigned int alarm(unsigned int seconds);
发送定时信号。

四、安装信号

用signal安装信号

signal(signo,handler);
表示对编号为signo的信号,处理函数为handler。例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdio.h>
#include <unistd.h>
#include <signal.h>

void sig_handler(int signo)
{
printf("signo=%d\n",signo);
}
int main()
{
if(signal(SIGUSR1,sig_handler)==SIG_ERR)
{
printf("can't catch SIGUSR1\n");
}
pause();
return 0;
}

其中handler可以不自定义,取SIG_ERRSIG_DFL(用默认处理方法) 、SIG_IGN(
忽略该信号)。

用sigaction安装信号

int sigaction(int signo,struct sigaction *act,struct sigaction *oldact);
结构体sigaction包括信号处理函数,屏蔽信号集,信号标志等。如下:

1
2
3
4
5
6
struct sigaction{
void *sa_sigaction(int,struct siginfo *,void *);
sigset_t sa_mask;
unsigned long sa_flags;
....
}

signo表示要安装的信号,act表示即将安装使用的信号处理信息(处理函数等),oldact存储之前安装的信号处理信息。
示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>
#include <unistd.h>
#include <signal.h>

void sig_handler(int signo,siginfo_t *info,void *p)
{
printf("signo=%d\n",signo);
printf("siginfo->signo:%d\n",info->si_signo);
printf("sender pid=%d\n",info->si_pid);
}
int main()
{
struct sigaction act,oldact;
sigemptyset(&act.sa_mask);
act.sa_flags=SA_SIGINFO;
act.sa_sigaction=sig_handler;
sigaction(SIGUSR1,&act,&oldact);
pause();
return 0;
}

运行结果(在另一个终端手动向该进程发送SIGUSR1信号):

1
2
3
4
tao@taohi-xubuntu:~/linux_c$ ./sigaction_sa_sigaction
signo=10
siginfo->signo:10
sender pid=15656

sig_handler第二个参数为siginfo_t *info。它用于返回一个结构体指针info给
sig_handler,包含了许多与该信号产生原因相关的信息,供信号处理函数sig_handler
使用。大致内容如下:

1
2
3
4
5
6
7
8
9
10
11
typedef struct siginfo{
int si_signo; //信号编号
int si_errno; //错误信息
int si_code; //信号产生原因代码
pid_t si_pid; //发送者的进程ID
uid_t si_uid; //发送者实际用户ID
void *si_addr; //
int si_status; //
long si_band; //
......
}siginfo_t;

五、信号的屏蔽

信号屏蔽:如果某信号被屏蔽,即使信号传递到进程,进程并不响应处理,而是让它处于屏蔽状态。只有当解除屏蔽后,进程才捕获并响应该信号。SIGKILLSIGSTOP信号不能被屏蔽。

信号忽略:进程捕获并处理该信号,但是处理方式就是直接忽略,什么也不做。
int sigprogmask(int how,sigset_t *set,sigset_t *oldset);
说明:set 和oldset都是信号集合。函数中how的取值:

  1. SIG_BLOCK:将set信号集,添加到当前进程屏蔽信号集;
  2. SIG_UNBLOCK:将set信号集合,从当前进程屏蔽信号集删除;
  3. SIG_SETMASK:设置set为当前进程屏蔽信号集。

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <stdio.h>
#include <signal.h>
#include <stdlib.h>

void sighandler(int signo)
{
printf("caught signo:%d\n",signo);
}
int main()
{
sigset_t newmask,oldmask,pendmask;
//安装SIGUSR1的处理函数
signal(SIGUSR1,sighandler);
sigemptyset(&newmask);
sigaddset(&newmask,SIGUSR1);
//屏蔽SIGUSR1
sigprocmask(SIG_BLOCK,&newmask,&oldmask);
printf("SIGUSR1 blocked for 20s.\n");
sleep(20);
//进程可以收到SIGUSR1,但是被屏蔽了,处于pending状态,
//sigpending可以查看处于pending的所有信号。
sigpending(&pendmask);
if(sigismember(&pendmask,SIGUSR1))
printf("SIGUSR1 is pending...\n");
//取消屏蔽SIGUSR1
printf("SIGUSR1 unblocked.\n");
sigprocmask(SIG_SETMASK,&oldmask,NULL);
return 0;
}

运行结果(在另一个终端不断发送SIGUSR1):

1
2
3
4
5
tao@taohi-xubuntu:~/linux_c$ ./sigprocmask
SIGUSR1 blocked for 20s.
SIGUSR1 is pending...
SIGUSR1 unblocked.
caught signo:10

六、等待信号

  1. pause()
    挂起当前进程,等待除了当前被屏蔽信号外的任意信号。此时才会唤醒进程。
  2. int sigsuspend(sigset_t *set)
    将进程屏蔽信号集指定为set,等待除了set外的任意信号到达;到达后,处理到达的信号。然后sigsuspend返回,恢复之前的进程屏蔽信号集。