信号的概念
- 信号的机制:A给B发送信号,在B收到信号之前执行自己的代码,收到信号后无论状态如何,首先处理信号然后去执行代码,与硬件终端类似–异步模式,但是信号是在软件层面上实现的,早期常称为软中断。
信号的特质:信号由于是使用软件的方法实现的,所以会有延时性,一般时间比较短,用户感觉不到。
注:每个进程收到的信号,都是由内核负责发送的,也是由内核处理的。 - 与信号相关的事件和状态
产生信号:
- 按键产生,如:Ctrl+C, Ctrl+z
- 系统调用产生,如:kill,raise,abort
- 软件条件产生,如:alarm
- 硬件异常产生:如:断错误
- 命令产生,如:kill命令
在进程中的PCB控制块中,有两个信号集,分别为未决信号集和阻塞信号集,阻塞信号集影响未决信号集。
递达:递达并且执行
未决:阻塞或屏蔽产生
信号的处理方式:
- 执行默认操作
- 忽略(丢弃) 处理的方式为丢弃,在未决信号集中置0
- 捕捉(调自己写的处理函数)
信号的四要素
- 信号的名字
- 信号的编号
- 信号的默认处理动作
- 信号所对应的事件(如:11号信号出现断错误)
使用kill -l或者man文档能查看所有的信号,1-31号信号为常规信号,其他为实时信号。
默认动作:
- Term:终止进程
- ign:忽略信号(执行忽略操作)
- Core:终止进程,生成Core文件(查验进程死亡原因,用于gdb调试)
- Stop:停止(暂停)进程
- Cont:继续运行程序
信号中9号信号(sigkill)和19号信号(sigstop)无法忽略和捕捉,不同平台可能变好不同,所以调用kill的时候写宏名比较好
产生信号的方法
- kill的使用
- 函数原型:kill(pid_t pid, int signal)
- 作用:向pid进程发送signal信号
- 返回值:成功返回0,失败返回-1
- 注意:因为平台不同,信号的编号可能不同,所以第二个参数写宏名字比较好
- raise函数
- 函数原型:raise(int signo)
- 作用:给当前进程发送指定信号,自己给自己发一个信号
- abort函数
- 函数原型:abort()
- 作用:给自己发送一个异常终止信号。
软件条件产生信号
- alarm函数
- 介绍:设置定时器(闹钟),在指定的second后,内核会给当前进程发送14)SIGALRM信号,进程收到该信号后默认动作为终止
- 函数原型:unsigned int alarm(unsigned int seconds)
- 返回值:
- 注意:每个进程有且仅有一个定时器
使用time命令可以看到程序执行时间。
real = sys + user,程序执行时间的瓶颈在与io
- setitimer函数精确定时
- 介绍:设置定时器(闹钟),可以替代alarm,精确到us。
- 函数原型:int setitimer(int which, const struct itimerval *restrict value,struct itimerval *restrict ovalue)
- 返回值:成功返回0,失败返回-1
- 参数:
which:ITIMER_REAL(自然计时), ITIMER_VIRTUAL(用户空间定时),到时发送SIGVTALARM信号,ITIMER_PROF(用户+内核)发送SIGPROF信号,主要了解第一个信号。 - 作用:既可以延时也可以定时,机制为,首先对it_value进行倒计时,然后再把it_val重制为it_interval继续倒计时,假如it_value为0是不会触发信号的,所以要能触发信号,it_value得大于0;如果it_interval为零,只会延时,不会定时(也就是说只会触发一次信号)。
信号集操作函数
信号集设定
1.集合
sigset_t set; //typedef unsigned long sigset_t
集合对应的操作
- 置集合所有元素为0
sigemptyset(sigset_t * set); - 置集合所有元素为1
sigfillset(sigset_t * set); - 添加一个信号到集合
sigaddset(sigset_t * set, int signo); - 从集合中删除一个元素
sigdelset(sigset_t * set, int signo); - 判断某个信号是否在集合中
sigismember(sigset_t * set, int signo);
以上仅仅为如何去设置一个信号集合,但是我们需要通过我们设置的这个集合去影响阻塞信号集进而影响未决信号集,达到屏蔽信号,解除屏蔽的目的。
- sigprocmask函数
- 函数原型:
int sigprocmask(int how, const sigset_t *restrict set,sigset_t *restrict oset); - 函数参数解释:
how:存在三个宏来定义how分别为SIG_BLOCK、SIG_UNBLOCK、SIG_SETMASK,含义分别为,设置阻塞信号,解除阻塞信号,给阻塞信号集赋值。 - 第二个参数为我们自定义的sigset_t去影响阻塞信号集
- 第三个参数同样为我们自己设置,用来接受刚才的信号集。
- sigpending函数
- 函数原型:
int sigpending(sigset_t *set); - 作用:读取未决信号集的内容并存在set中
- 返回值:成功返回0,失败返回-1
信号的操作(打印未决信号集):
//需要配合按键ctrl+c或者ctrl+\来观察变化
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
int main(void) {
sigset_t myset;
sigemptyset(&myset);
sigaddset(&myset, SIGQUIT);
sigaddset(&myset, SIGINT);
if(sigismember(&myset, SIGALRM)) {
puts("SIGALRM is myset member");
}
int ret = sigprocmask(SIG_BLOCK, &myset, NULL);
if(ret == 0) puts("success");
else puts("failed");
sigset_t signal_set;
sigemptyset(&signal_set);
while(1) {
sigpending(&signal_set);
for(int i = 1; i < 32; i++) {
if(sigismember(&signal_set, i) == 1) {
printf("1");
} else {
printf("0");
}
}
puts("");
sleep(1);
}
return 0;
}
信号的捕捉
- 如何注册信号捕捉函数
-
signal函数
函数原型:
void (*signal(int sig, void (*func)(int)))(int);
返回值:
void (*sig_t) (int);
参数:第一个为信号编号或者信号宏,第二参数为函数指针
void (*func)(int)
作用:给对应的信号注册一个函数,当收到对应信号的时候执行对应的注册函数
注意:返回值可以使用
typedef void (*sig_t) (int)
来定义 -
sigaction函数(重点)
函数原型:
sigaction(int sig, const struct sigaction *restrict act, struct sigaction *restrict oact);
返回值:成功返回0,失败返回-1,并设置errno
作用:给对应的信号注册一个函数,并且返回上一个信号的执行函数,通过
struct sigaction
保存 -
struct sigaction介绍:
头文件:#include <signal.h>
struct sigaction { union __sigaction_u __sigaction_u; /* signal handler */ sigset_t sa_mask; /* signal mask to apply */ int sa_flags; /* see signal options below */ }; union __sigaction_u { void (*__sa_handler)(int); void (*__sa_sigaction)(int, siginfo_t *, void *); }; #define sa_handler __sigaction_u.__sa_handler #define sa_sigaction __sigaction_u.__sa_sigaction
我们发现代码(共用体union)中存在有两个函数指针类型的成员,暂时只介绍第一个,为我们所需的注册函数指针,sa_mask只在信号发生时并且在执行对应的函数时进行起作用,作用为屏蔽sa_mask中设置的信号,sa_flags为标识位,一般使用默认值0,表示在执行函数的时候屏蔽忽略本身的信号
下面使用sigaction注册了SIGINT信号,同时在callback运行的时候SIGQUIT由于在sa_mask中设定,所以会被屏蔽。#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <signal.h> void callback(int signo) { printf("%d has catched!\n", signo); while(1); } int main(void) { struct sigaction act; act.sa_handler = callback; act.sa_flags = 0; sigemptyset(&act.sa_mask); sigaddset(&act.sa_mask, SIGQUIT); int ret = sigaction(SIGINT, &act, NULL); if(ret == -1) { perror("ret failed"); exit(1); } while(1); return 0; }
内核信号捕捉注意事项和原理
- 注意事项:
- 使用sigaction注册函数的时候,结构体中的sa_mask仅仅在回调函数执行期间有作用,达到屏蔽对应信号的作用。
- 如果在回调函数执行的过程中,有某个信号多次产生,由于信号的执行由未决信号集决定,只有0或1,未决信号集会把对应的信号位置置1,并没有记录多少次,所以只会执行一次对应的信号。
- 信号捕捉原理
首先由用户区的主控制区来接收到信号中断当前程序,进入内核区,内核区处理完异常,在回用户区之前,内核区先处理可以递送的信号,如果信号为自己注册的函数,那么由内核区回到对应的函数处,然后执行对应的函数,而不是回到主控制流程,回调函数执行完之后,然后使用sigreturn回到内核区表示回调函数执行完,然后由内核区回到主控制流程。