信号:消息,通知进程系统中发生了某种类型的事件,每种类型的信号对应某种系统事件。
信号是进程间通信的一种异步通信机制。在进程运行过程中,经常会产生一些事件,这些事件的产生和进程的执行往往是异步的,信号提供了一种在软件层面上的异步处理事件的机制。在硬件层面上的异步处理机制是中断,信号是中断的软件模拟。
每个信号用一个整型常量宏表示,以 SIG
开头,在头文件<signal.h>
中定义
# 查看信号列表 kill -l man 7 signal
1、信号的实现机制
发送信号到目的进程是由发送信号和接收信号两个步骤组成。
- 发送信号:内核通过更新目的进程上下文中的某个状态,发送一个信号给目的进程。
- 接收信号:目的进程被内核强迫以某种方式对信号的发送做出反应。
待处理信号(pending signal):一个发出而没有接收的信号,又称为挂起信号或未决信号。
在任何时候,一种类型的信号至多只会有一个待处理信号。也就是说,如果一个进程有一个类型为 k 的待处理信号,那么任何接下来发送到这个进程的类型为 k 的信号都不会排队等待,它们只是被简单丢弃。一个进程可以选择性地阻塞接收某种信号,当一种信号被阻塞,它仍可以被发送,但是产生的待处理信号不会被接收,直到进程取消对这种信号的阻塞。
一个待处理信号最多只能被接收一次。内核为每个进程,在位图 pending
中维护着待处理信号的结合,在位图 blocked
集合(信号屏蔽字)中维护着被阻塞的信号集合。只要发送了一个类型为 k 的信号,内核就会设置 pending
中的第 k 位,只要接收了一个类型为 k 的信号,内核就会清楚 pending
中的第 k 位。
2、发送信号
2.1、发送信号的原因
- 内核检测到系统事件,例除零错误等
- 进程调用 kill 函数
2.2、发送信号的机制
unix 系统提供了大量向进程发送信号的机制,这些机制都是基于进程组的。每个进程拥有自己的 pid
,每个进程属于一个进程组 pgid
。
硬件来源:键盘。硬件触发中断,操作系统切换到内核态执行中断处理程序,中断处理程序发送信号,进程接收信号并处理。
ctrl + c: SIGINT,表示终止该进程 ctrl + z: SIGTSTP,表示挂起该进程
软件来源:/bin/kill 程序,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);
3、接收信号
进程接收到信号,有三种处理方法:
- 默认处理:
signal(SIGINT,SIG_DFL)
- Term:终止当前进程
- Ign:忽略该信号
- Core:终止当前进程,并且产生core dump
- Stop:停止(挂起)一个进程
- Cont:使当前停止的进程,继续运行 - 忽略信号:
signal(SIGSEGV,SIG_IGN)
- 捕捉信号:自定义信号处理函数。用户自定义信号处理函数的目的就是实现进程的有序退出。
自定义信号处理函数设置原则
- 程序尽可能简单。例:处理程序简单设置全局标志并返回,所有与接收信号相关的处理由主程序执行。
- 函数内部只调用信号异步安全函数
安全函数:可重入的(例只访问局部变量),不能被信号处理程序中断。
3.1、处理信号
signal 函数
进程可以通过signal
函数修改和信号相关联的默认行为。注意:SIGKILL
和 SIGSTOP
这两个信号既不能被忽略也不能被捕捉,即进程接收到这两个信号后,只能接受系统的默认处理,即终止进程。
# include <signal.h> // 信号回调函数,信号处理 typedef void (*sighandler_t)(int); /* 功能:捕获信号 返回值:成功返回前次处理程序的指针,失败返回 SIG_ERR 参数: - signum:要捕捉的信号值, - handler:函数处理函数。SIG_DFL 默认处理; SIG_IGN,忽略信号; 其他,执行信号回调函数 */ sighandler_t signal(int signum, sighandler_t handler);
sigaction 函数
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); };
3.2、信号阻塞 | 解除
Linux 提供阻塞信号的机制
- 隐式阻塞机制:内核默认阻塞任何当前处理程序正在处理信号类型的待处理信号。
- 显式阻塞机制: sigprocmask 函数
sigset_t 信号集合
#include <signal.h> 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);
sigpending 函数
获取当前待处理信号的集合。通常在回调函数当中使用的,用于检查当前是否阻塞了某个信号
/* 返回值:成功返回0,失败返回-1 参数 set: 要检测的信号 */ int sigpending(sigset_t *set);
sigprocmask 函数
改变当前阻塞的信号集合(位图 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);
sigsuspend 函数
使用 sigprocmask
函数可以实现信号保护临界区。在临界区执行代码的时候,此时产生的信号将会被阻塞,临界区结束的位置只需要再使用 sigprocmask 即可。pause 系统调用可以唤醒一个阻塞进程,直到被一个信号唤醒。若采用 pause 函数捕捉临界区的信号,信号解除阻塞后,立即执行信号处理函数,无法捕捉信号。
为了捕捉临界区的信号,将解除阻塞和等待信号合并成一个原子操作,就是sigsuspend
函数
int sigsuspend(const sigset_t *mask);
4、定时器
4.1、睡眠函数
sleep 函数
#include <unistd.h> // 休眠 sec 秒 unsigned int sleep(unsigned int seconds); // 休眠 usec 秒 int usleep(useconds_t usec);
pause 函数
阻塞一个进程,直到某个信号被递送时,进程会解除阻塞,然后终止进程或者执行信号处理函数
int pause(void);
alarm 函数
进程调用 alarm 函数向它自己发送 SIGALRM
信号。
/* 返回值:前一次闹钟剩余的秒数,若以前没有设定闹钟,则为 0 参数:seconds: 闹钟的时间间隔 */ unsigned int alarm(unsigned int seconds);
alarm 函数安排内核在 secs
秒后发送一个 SIGALRM
信号给调用进程。在任何情况下,对 alarm 的调用都将取消任何待处理的闹钟,并且返回待处理的闹钟在被发送前还剩下的秒数,若没有代理处理的闹钟,返回 0。
4.2、定时器
setitimer 函数
setitimer 系统调用负责调整间隔定时器。间隔定时器在创建的时候,就会设置一个时间间隔,定时器到达时间间隔时,调用进程会产生一个信号,随后定时器被重置。
定时器的分类
- 真实计时器:程序实际运行的时间(时钟时间),时间到发送
SIGALARM
信号 - 虚拟计时器:程序在用户态模式下的 CPU 时间,时间到发送
SIGVTALARM
信号, - 实用计时器:程序在用户态和内核态所占用的 CPU 时间,时间到发送
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 <func.h> 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; }
5、例:四窗口聊天
在四个终端上分别启动A、A1、B、B1四个进程。
- A 和 B 负责通信,A1和B1负责显示信息。
- A 和 B 使用有名管道进行通信
- A 和 A1 进程通信采用共享内存(信号量同步),B 和 B1 进程通信采用消息队列
- 退出方式:
ctrl + c
和kill
命令,任意进程收到信号后,给所有进程发送 10 号信号,有序退出。有序退出要做的主要有:关闭管道,解除对共享内存的映射,删除共享内存,删除信号量,删除消息队列。
showA
#include <head.h> // 保存各个进程的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 <head.h> 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 <head.h> 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 <head.h> 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; }