在学习信号之前,我们先要知道,信号与信号量不是同一个东西。信号量主要是实现进程之间的同步与互斥,主要用于进程间通信。
这就是一个信号,现实生活中信号也特别常见。
信号的作用
我们先想一下在现实生活中信号主要是通知一件事情的,其实在操作系统中,信号也扮演着这样的角色。
在操作系统中,通知一个进程发生某一个事件,打断当前进程的操作,去处理这个事件。软件中断
查看信号
进程在相应信号之前必须先知道该信号是什么意思。
kill -l
上面共62中信号,其中没有32,33信号。
1-31号信号:信号有其对应的事件,非可靠信号。
34-64号信号:没有对应的事件,可靠信号。
可靠信号与不可靠信号最主要的区别就是可靠信号不会丢失事件,不可靠信号会丢失事件。
信号的生命周期
信号的产生
-
硬件:ctrl+c
-
软件:
-
命令:kill -信号 pid
kill命令杀死一个进程,主要是因为其想进程发送15号终止信号。 -
函数:
int kill(pid_t pid,int sig),向指定的进程发送指定的信号。
int raise(int sig)给调用进程自身发送指定的信号。
abort(void)向调用进程发送SIGABRT信号,使进程异常退出。
unsigned int alarm(unsigned int seconds)second秒后调用进程发送SIGALRM信号,告诉进程时间到退出。
-
信号在进程中的注册
让进程知道自己收到了一个信号,进程就是一个pcb,Linux下在pcb结构体中定义了一个信号的集合set(位图),若给进程发送一个信号,则将这个信号对应的位置的二进制位置1。
位图只能标记进程是否收到这个信号,无法确定信号收到多少次,所以在内核中其实还有一个链表。
sigset_t set;
申请一个set集合
sigemptyset(&set);
清空一个set集合,因为set集合为栈上申请的空间,所以会存在数据
int sigfillset(sigset_t *set);
将所有信号添加到set中
int sigaddset(sigset_t *set,int signum);
将信号值为signum添加到set中
-
非可靠信号的注册:在注册信号时,判断当前信号是否已经注册过(位图是否为1),若没有注册,则添加结点,修改位图,反之,如果已经注册过则什么也不干。这就导致在链表中只有一个结点,后到来的信号就被丢弃。
-
可靠信号的注册:在注册时,每次针对新到来的信号都会创一个新的结点添加到链表中,并且二进制置1,链表中有多个相同的结点。
信号在进程中的注销
- 非可靠信号:删除结点,位图置0,因为非可靠信号最多只有一个结点。
- 可靠信号:删除结点,若还有相同结点,位图不变,若没有相同结点则位图置0,表示没有该信号。
信号的处理
进程在收到信号之后,针对这个信号的事件找到它对应的处理函数。
- 默认处理:系统已经默认好的处理方式。
- 忽略处理:处理动作什么都不做。
- 自定义处理:用户自定义信号处理函数,使用这个信号处理函数替换原本信号处理函数。
信号处理方式的修改
sighandler_t signal(int signum,sighandle_t handler);
修改一个信号的处理动作的回调函数。
signum:信号值。
handler:函数指针。 sighandler_t 函数指针类型 —> typedef void(*sighandler_t)(int signo),signo:当信号到来时,系统向回调函数传入的信号值。
自定义信号的捕捉流程
信号的处理:当前进程从用户态切换到内核态之前进行处理。
程序从用户态切换到内核态的方法:中断,异常,系统调用。
信号的阻塞
在进程中标记,哪个信号注册之后,暂时不去处理,知道信号接触阻塞。
阻塞一个信号被抵达,进程pcb中有一个信号block集合,用户可以在这个集合中标记哪些信号被阻塞。
本质: 将信号添加到block集合中,则表示这个信号被阻塞,当信号传递过来时,暂时不处理。
int sigprocmask(int how,const sigset_t *set,sigset_t *oldset
how:即将要对pcb中block集合所作的操作,有三个选项:
- SIG_BLOCK:将第二个参数set集合中的信号,添加到block集合中。
- SIG_UNBLOCK:将第二个参数set集合中的信号,从block集合中移除,解除阻塞
- SIG_SETMASK:使用set集合替换block集合 block=set
oldset:每当block集合发生改变时,都会将原本的block拷贝一份放到oldset集合中,方便后期还原block
注意:
1:信号阻塞时依旧能够进行信号注册,但是暂时不处理。
2:可靠信号注册多次,因此会阻塞处理多次,不可靠信号只能被注册一次,只阻塞处理一次。
3:9号信号与19号信号不能被阻塞。
#include<unistd.h>
#include<signal.h>
#include<stdlib.h>
void sigcb(int signo)
{
printf("收到一个信号%d\n",signo);
}
int main()
{
//修改信号的处理方式
signal(SIGINT,sigcb);
signal(SIGRTMIN+5,sigcb);
sigset_t set;//定义一个局部变量,有可能有数据留存,因此需要对其进行初始化
sigemptyset(&set); //清空信号集合
//int sigfillset(sigset_t *set);将所有信号添加到set中
//int sigaddset(sigset_t *set,int signum);将signum添加到set中
sigfillset(&set);
sigprocmask(SIG_BLOCK,&set,NULL);//对set集合中的信号进行阻塞
printf("回车继续\n");
getchar();//等待标准输入换行,流程继续
sigprocmask(SIG_UNBLOCK,&set,NULL);
return 0;
}