信号是进程间通信中唯一的异步通信机制。在进程运行过程中,经常会产生一些事件,这些事件的产生和进程的执行往往是异步的,信号提供了一种在软件层面上的异步处理事件的机制。在硬件层面上的异步处理机制是中断,信号是中断的软件模拟。
每个信号用一个整型常量宏表示,以 SIG
开头,在头文件
中定义
# 查看信号列表
kill -l
man 7 signal
发送信号到目的进程是由发送信号和接收信号两个步骤组成。
待处理信号(pending signal):一个发出而没有接收的信号,又称为挂起信号或未决信号。
在任何时候,一种类型的信号至多只会有一个待处理信号。也就是说,如果一个进程有一个类型为 k 的待处理信号,那么任何接下来发送到这个进程的类型为 k 的信号都不会排队等待,它们只是被简单丢弃。一个进程可以选择性地阻塞接收某种信号,当一种信号被阻塞,它仍可以被发送,但是产生的待处理信号不会被接收,直到进程取消对这种信号的阻塞。
一个待处理信号最多只能被接收一次。内核为每个进程,在位图 pending
中维护着待处理信号的结合,在位图 blocked
集合(信号屏蔽字)中维护着被阻塞的信号集合。只要发送了一个类型为 k 的信号,内核就会设置 pending
中的第 k 位,只要接收了一个类型为 k 的信号,内核就会清楚 pending
中的第 k 位。
unix 系统提供了大量向进程发送信号的机制,这些机制都是基于进程组的。每个进程拥有自己的 pid
,每个进程属于一个进程组 pgid
。
硬件来源:键盘。硬件触发中断,操作系统切换到内核态执行中断处理程序,中断处理程序发送信号,进程接收信号并处理。
ctrl + c: SIGINT,表示终止该进程
ctrl + z: SIGTSTP,表示挂起该进程
软件来源:/bin/kill 程序,kill 函数,定时器函数。
进程通过 kill 函数发送信号给其他进程(包括自己)。
/*
返回值:成功返回 0,失败返回 -1
参数
- pid: pid > 0,发送 sig 信号给进程 pid
pid = 0, 发送 sig 信号给调用进程所在进程组中的每个进程(包括调用进程自己)
pid < 0,发送 sig 信号给进程组 |pid| 中的每个进程
- sig: 信号类型
*/
int kill(pid_t pid, int sig);
进程接收到信号,有三种处理方法:
默认处理:signal(SIGINT,SIG_DFL)
- Term:终止当前进程
- Ign:忽略该信号
- Core:终止当前进程,并且产生core dump
- Stop:停止(挂起)一个进程
- Cont:使当前停止的进程,继续运行
忽略信号:signal(SIGSEGV,SIG_IGN)
捕捉信号:自定义信号处理函数。用户自定义信号处理函数的目的就是实现进程的有序退出。
自定义信号处理函数设置原则
安全函数:可重入的(例只访问局部变量),不能被信号处理程序中断。
进程可以通过signal
函数修改和信号相关联的默认行为。注意:SIGKILL
和 SIGSTOP
这两个信号既不能被忽略也不能被捕捉,即进程接收到这两个信号后,只能接受系统的默认处理,即终止进程。
# include
// 信号回调函数,信号处理
typedef void (*sighandler_t)(int);
/*
功能:捕获信号
返回值:成功返回前次处理程序的指针,失败返回 SIG_ERR
参数:
- signum:要捕捉的信号值,
- handler:函数处理函数。SIG_DFL 默认处理; SIG_IGN,忽略信号; 其他,执行信号回调函数
*/
sighandler_t signal(int signum, sighandler_t handler);
signal
函数处理机制在多信号处理的场景下
不同的系统有不同的信号处理语义,但 signal
函数的处理过程是固定的,无法调整。因此,Posix 标准定义了sigaction
函数,允许用户自定义这些场景下进程的行为。
/*
返回值:成功返回0,失败返回-1。
参数:
- signum:要捕捉的信号值
- act:自定义行为
- oldact:保存原来信号的回调函数,通常传入空指针
*/
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact)
其中 sigaction
结构体
struct sigaction {
// 联合体,新旧处理函数只有一个可以生效
union {
// 旧类型的信号处理函数
__sighandler_t _sa_handler;
// 新类型的信号处理函数,siginfo_t 保存信号的相关信息
void (*_sa_sigaction)(int, struct siginfo *, void *);
} _u;
// 阻塞信号的结合
sigset_t sa_mask;
// 信号处理的方式,旧类型填 0。其他例如:
// SA_SIGINFO,选择回调函数 sa_sigaction;
unsigned long sa_flags;
// 保留不用
void (*sa_restorer)(void);
};
Linux 提供阻塞信号的机制
#include
typedef struct {
unsigned long int __val[(1024/(8*sizeof(unsigned long int)))];
} __sigset_t;
// sigset_t的本质就是一个位图,共有1024位
typedef __sigset_t sigset_t;
// 初始化信号集,清除所有信号
int sigemptyset(sigset_t *set);
// 初始化信号集,包括所有信号
int sigfillset(sigset_t *set);
// 增加信号
int sigaddset(sigset_t *set, int signum);
// 删除信号
int sigdelset(sigset_t *set, int signum);
// 检查信号是否处于信号集之中
int sigismember(const sigset_t *set, int signum);
获取当前待处理信号的集合。通常在回调函数当中使用的,用于检查当前是否阻塞了某个信号
/*
返回值:成功返回0,失败返回-1
参数 set: 要检测的信号
*/
int sigpending(sigset_t *set);
改变当前阻塞的信号集合(位图 blocked
)
/*
返回值:成功返回0, 失败返回-1
参数
- how:
SIG_BLOCK: 把集合 set 中的信号加入到阻塞集合 blocked 当中 (blocked |= set)
SIG_UNBLOCK: 把集合 set 从阻塞集合 blocked 中删除 (blocked = blocked & ~set)
SIG_SETMASK: 把集合 set 替换阻塞集合 blocked (blocked = set)
- set:阻塞集合
- 参数3:原有的集合
*/
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
使用 sigprocmask
函数可以实现信号保护临界区。在临界区执行代码的时候,此时产生的信号将会被阻塞,临界区结束的位置只需要再使用 sigprocmask 即可。pause 系统调用可以唤醒一个阻塞进程,直到被一个信号唤醒。若采用 pause 函数捕捉临界区的信号,信号解除阻塞后,立即执行信号处理函数,无法捕捉信号。
为了捕捉临界区的信号,将解除阻塞和等待信号合并成一个原子操作,就是sigsuspend
函数
int sigsuspend(const sigset_t *mask);
#include
// 休眠 sec 秒
unsigned int sleep(unsigned int seconds);
// 休眠 usec 秒
int usleep(useconds_t usec);
阻塞一个进程,直到某个信号被递送时,进程会解除阻塞,然后终止进程或者执行信号处理函数
int pause(void);
进程调用 alarm 函数向它自己发送 SIGALRM
信号。
/*
返回值:前一次闹钟剩余的秒数,若以前没有设定闹钟,则为 0
参数:seconds: 闹钟的时间间隔
*/
unsigned int alarm(unsigned int seconds);
alarm 函数安排内核在 secs
秒后发送一个 SIGALRM
信号给调用进程。在任何情况下,对 alarm 的调用都将取消任何待处理的闹钟,并且返回待处理的闹钟在被发送前还剩下的秒数,若没有代理处理的闹钟,返回 0。
setitimer 系统调用负责调整间隔定时器。间隔定时器在创建的时候,就会设置一个时间间隔,定时器到达时间间隔时,调用进程会产生一个信号,随后定时器被重置。
定时器的分类
SIGALARM
信号SIGVTALARM
信号,SIGPROF
信号使用 fork 的时候子进程不会继承父进程的定时器,使用 exec 时候,定时器不会销毁。
/*
返回值:成功返回0,失败返回-1.
参数:
- which:设置定时器的种类:真实计时器 SIGALARM、虚拟计时器 SIGVTALARM、实用计时器 SIGPROF
- new_value:定时器的初始时间
- old_value: 定时器的间隔时间
*/
int getitimer(int which, struct itimerval *curr_value)
int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value)
struct itimerval {
struct timeval it_interval; // 时间间隔
struct timeval it_value; // 初始时间
};
struct timeval {
time_t tv_sec; // 秒
suseconds_t tv_usec; // 微秒
};
例:使用真实计时器,实用计时器,统计程序执行时间,在实用计时器及虚拟计时器设定计时后,先睡眠,再让程序处于while(1)
#include
void sigFunc(int sigNum) {
time_t now;
time(&now);
printf("now time = %s\n", ctime(&now));
}
int main(int argc,char*argv[]) {
//真实计时器,信号SIGALRM
signal(SIGALRM, sigFunc);
struct itimerval Time;
memset(&Time, 0, sizeof(Time));
Time.it_value.tv_sec = 1; // 1s后启动
Time.it_interval.tv_sec = 2; // 间隔2s
time_t now;
time(&now);
printf("now time = %s", ctime(&now));
// ITIMER_REAL
setitimer(ITIMER_REAL, &Time, NULL);
// ITIMER_PROF
// setitimer(ITIMER_PROF, &Time, NULL);
printf("before sleep\n");
sleep(3);
printf("after sleep\n");
while(1);
return 0;
}
在四个终端上分别启动A、A1、B、B1四个进程。
ctrl + c
和 kill
命令,任意进程收到信号后,给所有进程发送 10 号信号,有序退出。有序退出要做的主要有:关闭管道,解除对共享内存的映射,删除共享内存,删除信号量,删除消息队列。showA
#include
// 保存各个进程的pid
typedef struct pidnums{
int pidWriteA;
int pidWriteB;
int pidShowA;
int pidShowB;
} pidNums_t, *pPidNums_t;
typedef struct package{
int flag;
char buf[64];
}Package_t, *pPackage_t;
pPidNums_t pidsets;
pPackage_t p;
// 2 信号处理函数
void sigFunc2(int signum,siginfo_t *p,void *p1){
printf("%d is coming\n",signum);
kill(pidsets->pidWriteA, 10);
kill(pidsets->pidWriteB, 10);
kill(pidsets->pidShowB, 10);
kill(pidsets->pidShowA, 10);
}
// 10 信号处理函数:
void sigFunc10(int signum, siginfo_t *p, void *p1){
printf("%d is coming\n", signum);
shmdt(pidsets);
shmdt(p);
exit(0);
}
int main(int argc, char *argv[]) {
// 2 号信号
struct sigaction act2;
bzero(&act2, sizeof(act2));
act2.sa_flags = SA_SIGINFO;
act2.sa_sigaction = sigFunc2;
int ret = sigaction(SIGINT, &act2, NULL);
ERROR_CHECK(ret, -1, "sigaction 2");
// 10 号信号
struct sigaction act10;
bzero(&act10, sizeof(act10));
act10.sa_flags = SA_SIGINFO;
act10.sa_sigaction = sigFunc10;
ret = sigaction(10, &act10, NULL);
ERROR_CHECK(ret, -1, "sigaction 10");
// 创建共享内存
int shmid = shmget(1000, 1024, IPC_CREAT|0600);
ERROR_CHECK(shmid,-1,"shmget");
// 映射地址空间
p = (pPackage_t)shmat(shmid,NULL,0);
ERROR_CHECK(p, (pPackage_t)-1, "shmat");
int semArrId = semget(1000, 1, IPC_CREAT|0600);
ERROR_CHECK(semArrId, -1, "semget");
ret = semctl(semArrId, 0, SETVAL, 1);
ERROR_CHECK(ret, -1, "semctl");
struct sembuf sopp, sopv;
sopp.sem_num = 0;
sopp.sem_op = -1;
sopp.sem_flg = SEM_UNDO;
sopv.sem_num = 0;
sopv.sem_op = 1;
sopv.sem_flg = SEM_UNDO;
int pid = getpid();
int shmidPid = shmget(2000, 4096, IPC_CREAT|0600);
ERROR_CHECK(shmidPid,- 1, "pid shmget");
pidsets = (pPidNums_t)shmat(shmidPid, NULL, 0);
ERROR_CHECK(pidsets, (pPidNums_t) - 1, "pid shmat");
pidsets->pidShowA = pid;
while(1){
semop(semArrId, &sopp, 1);
if(p->flag == 1){
puts(p->buf);
printf("\n");
memset(p->buf, 0, sizeof(p->buf));
p->flag = 0;
}
if(p->flag == 2){
printf("%*s%s\n", 10, "", p->buf);
memset(p->buf, 0, sizeof(p->buf));
p->flag = 0;
}
semop(semArrId, &sopv, 1);
}
return 0;
}
write A
#include
typedef struct package{
int flag;
char buf[64];
}Package_t, *pPackage_t;
typedef struct pidnums{
int pidWriteA;
int pidWriteB;
int pidShowA;
int pidShowB;
} pidNums_t, *pPidNums_t;
pPidNums_t pidsets;
int fdr = 0,fdw = 0;
int shmid = 0;
int semArrId = 0;
int shmidPid = 0;
pPackage_t p;
void sigFunc2(int signum,siginfo_t *p,void *p1){
printf("%d is coming\n",signum);
kill(pidsets->pidWriteB, 10);
kill(pidsets->pidShowA, 10);
kill(pidsets->pidShowB, 10);
kill(pidsets->pidWriteA, 10);
}
void sigFunc10(int signum,siginfo_t *p,void *p1){
printf("%d is coming\n", signum);
semctl(semArrId, 0, IPC_RMID);
shmdt(p);
shmdt(pidsets);
shmctl(shmid, IPC_RMID, NULL);
shmctl(shmidPid, IPC_RMID, NULL);
close(fdr);
close(fdw);
exit(0);
}
int main(int argc,char *argv[]) {
ARGS_CHECK(argc, 3);
struct sigaction act2;
bzero(&act2, sizeof(act2));
act2.sa_flags = SA_SIGINFO;
act2.sa_sigaction = sigFunc2;
int ret = sigaction(SIGINT, &act2, NULL);
ERROR_CHECK(ret, -1, "sigaction 2");
struct sigaction act10;
bzero(&act10, sizeof(act10));
act10.sa_flags = SA_SIGINFO;
act10.sa_sigaction = sigFunc10;
ret=sigaction(10, &act10, NULL);
ERROR_CHECK(ret, -1, "sigaction 10");
//A和B的读写管道
fdr=open(argv[1], O_RDONLY);
ERROR_CHECK(fdr, -1, "open1");
fdw=open(argv[2], O_WRONLY);
ERROR_CHECK(fdw, -1, "open2");
printf("i am chat1, fdr=%d fdw=%d\n",fdr,fdw);
char buf[128] = {0};
fd_set rdset;
// A和showA之间的共享内存
shmid=shmget(1000, 1024, IPC_CREAT|0600);
ERROR_CHECK(shmid, -1, "shmget");
p=(pPackage_t)shmat(shmid, NULL, 0);
ERROR_CHECK(p, (pPackage_t) - 1, "shmat");
p->flag = 0;
// A和showA之间的互斥锁
semArrId = semget(1000, 1, IPC_CREAT|0600);
ERROR_CHECK(semArrId, -1, "semget");
ret=semctl(semArrId, 0, SETVAL, 1);
ERROR_CHECK(ret, -1, "semctl");
struct sembuf sopp, sopv;
sopp.sem_num = 0;
sopp.sem_op = -1;
sopp.sem_flg = SEM_UNDO;
sopv.sem_num = 0;
sopv.sem_op = 1;
sopv.sem_flg = SEM_UNDO;
// pid存储的共享内存
int pid = getpid();
shmidPid = shmget(2000, 4096, IPC_CREAT|0600);
ERROR_CHECK(shmidPid, -1, "pid shmget");
pidsets=(pPidNums_t)shmat(shmidPid, NULL, 0);
ERROR_CHECK(pidsets, (pPidNums_t) - 1, "pid shmat");
pidsets->pidWriteA = pid;
while(1){
FD_ZERO(&rdset);
FD_SET(STDIN_FILENO, &rdset);
FD_SET(fdr, &rdset);
ret = select(fdw + 1, &rdset, NULL, NULL, NULL);
ERROR_CHECK(ret, -1, "select");
printf("ret = %d\n", ret);
if(FD_ISSET(STDIN_FILENO,&rdset)){
memset(buf, 0, sizeof(buf));
ret=read(STDIN_FILENO, buf, sizeof(buf));
// if(0==ret){
// printf("主动断开\n");
// break;
// }
if(strcmp(buf,"\n")!=0){
write(fdw,buf,strlen(buf)-1);
ret = semop(semArrId,&sopp,1);
ERROR_CHECK(ret, -1, "semop1");
p->flag=2;
strcpy(p->buf,buf);
ret = semop(semArrId,&sopv,1);
ERROR_CHECK(ret, -1, "semop2");
}
}
if(FD_ISSET(fdr, &rdset)){
memset(buf, 0, sizeof(buf));
ret=read(fdr, buf, sizeof(buf));
ERROR_CHECK(ret, -1, "read");
printf("ret = %d\n", ret);
// if(0==ret){
// printf("连接已断开\n");
// break;
// }
ret = semop(semArrId, &sopp, 1);
ERROR_CHECK(ret, -1, "semop1");
p->flag = 1;
strcpy(p->buf, buf);
ret = semop(semArrId, &sopv, 1);
ERROR_CHECK(ret, -1, "semop2");
puts(buf);
}
}
return 0;
}
showB
#include
typedef struct pidnums{
int pidWriteA;
int pidWriteB;
int pidShowA;
int pidShowB;
} pidNums_t,*pPidNums_t;
typedef struct mymsgbuf{
long mtype;
char mtext[64];
}MSG_t;
pPidNums_t pidsets;
void sigFunc2(int signum, siginfo_t *p, void *p1){
printf("%d is coming\n", signum);
kill(pidsets->pidWriteA, 10);
kill(pidsets->pidWriteB, 10);
kill(pidsets->pidShowA, 10);
kill(pidsets->pidShowB, 10);
}
void sigFunc10(int signum, siginfo_t *p, void *p1){
printf("%d is coming\n", signum);
shmdt(pidsets);
exit(0);
}
int main(int argc,char *argv[]) {
struct sigaction act2;
bzero(&act2,sizeof(act2));
act2.sa_flags =S A_SIGINFO;
act2.sa_sigaction = sigFunc2;
int ret =s igaction(SIGINT,&act2,NULL);
ERROR_CHECK(ret, -1, "sigaction 2");
struct sigaction act10;
bzero(&act10, sizeof(act10));
act10.sa_flags = SA_SIGINFO;
act10.sa_sigaction = sigFunc10;
ret=sigaction(10, &act10, NULL);
ERROR_CHECK(ret, -1, "sigaction");
int msgid=msgget(1000, IPC_CREAT|0600);
ERROR_CHECK(msgid, -1, "msgget");
struct mymsgbuf msgInfo;
int pid = getpid();
int shmidPid = shmget(2000, 4096, IPC_CREAT|0600);
ERROR_CHECK(shmidPid, -1, "pid shmget");
pidsets=(pPidNums_t)shmat(shmidPid, NULL, 0);
ERROR_CHECK(pidsets, (pPidNums_t) - 1, "pid shmat");
pidsets->pidShowB = pid;
while(1){
bzero(&msgInfo, sizeof(msgInfo));
ret = msgrcv(msgid, &msgInfo, sizeof(msgInfo), 0, 0);
ERROR_CHECK(ret, -1, "msgrcv");
if(msgInfo.mtype == 1){
printf("%s\n\n", msgInfo.mtext);
}
if(msgInfo.mtype == 2){
printf("%*s%s\n", 10, "", msgInfo.mtext);
}
}
return 0;
}
wirteB
#include
typedef struct pidnums{
int pidWriteA;
int pidWriteB;
int pidShowA;
int pidShowB;
}pidNums_t, *pPidNums_t;
pPidNums_t pidsets;
int fdr,fdw;
int msgid;
typedef struct mymsgbuf{
long mtype;
char mtext[64];
} Msg_t,*pMsg_t;
void sigFunc2(int signum, siginfo_t *p, void *p1){
printf("%d is coming\n", signum);
kill(pidsets->pidWriteA, 10);
kill(pidsets->pidShowA, 10);
kill(pidsets->pidShowB, 10);
kill(pidsets->pidWriteB, 10);
}
void sigFunc10(int signum, siginfo_t *p, void *p1){
printf("%d is coming\n", signum);
close(fdr);
close(fdw);
shmdt(pidsets);
msgctl(msgid, IPC_RMID,NULL);
exit(0);
}
int main(int argc,char *argv[]) {
ARGS_CHECK(argc, 3);
struct sigaction act2;
bzero(&act2, sizeof(act2));
act2.sa_flags = SA_SIGINFO;
act2.sa_sigaction =s igFunc2;
int ret=sigaction(SIGINT, &act2, NULL);
ERROR_CHECK(ret, -1, "sigaction 2");
struct sigaction act10;
bzero(&act10, sizeof(act10));
act10.sa_flags =S A_SIGINFO;
act10.sa_sigactio n= sigFunc10;
ret=sigaction(10, &act10, NULL);
ERROR_CHECK(ret, -1, "sigaction");
fdw = open(argv[1], O_WRONLY);
fdr = open(argv[2], O_RDONLY);
printf("i am chat2 , fdr=%d fdw=%d\n", fdr, fdw);
// B 和 showB 的消息队列
char buf[128] = {0};
fd_set rdset;
msgid = msgget(1000, IPC_CREAT|0600);
ERROR_CHECK(ret, -1, "msgget");
struct mymsgbuf msgInfo;
// pid 共享内存
int pid = getpid();
int shmidPid = shmget(2000, 4096, IPC_CREAT|0600);
ERROR_CHECK(shmidPid, -1, "pid shmget");
pidsets = (pPidNums_t)shmat(shmidPid, NULL, 0);
ERROR_CHECK(pidsets, (pPidNums_t) - 1, "pid shmat");
pidsets->pidWriteB = pid;
while(1){
FD_ZERO(&rdset);
FD_SET(STDIN_FILENO, &rdset);
FD_SET(fdr, &rdset);
ret=select(fdr + 1, &rdset, NULL, NULL, NULL);
ERROR_CHECK(ret, -1, "select");
if(FD_ISSET(STDIN_FILENO, &rdset)){
memset(buf, 0, sizeof(buf));
ret = read(STDIN_FILENO, buf, sizeof(buf));
// if(0==ret){
// printf("主动断开\n");
// break;
// }
if(strcmp(buf, "\n") != 0){
write(fdw,buf,strlen(buf) - 1);
msgInfo.mtype = 2;
strcpy(msgInfo.mtext, buf);
ret = msgsnd(msgid, &msgInfo, strlen(msgInfo.mtext), 0);
ERROR_CHECK(ret,-1,"msgsnd");
}
}
if(FD_ISSET(fdr, &rdset)){
memset(buf, 0, sizeof(buf));
ret=read(fdr, buf, sizeof(buf));
ERROR_CHECK(ret, -1, "read");
// if(0==ret){
// printf("连接已断开\n");
// break;
// }
if(strlen(buf) != 0){
puts(buf);
msgInfo.mtype = 1;
strcpy(msgInfo.mtext, buf);
ret = msgsnd(msgid, &msgInfo, strlen(msgInfo.mtext), 0);
ERROR_CHECK(ret, -1, "msgsnd");
}
}
}
close(fdr);
close(fdw);
return 0;
}