【Linux】信号的产生

目录

信号的简单理解

信号的产生

kill命令

键盘产生

系统调用

软件条件

异常


从这篇博客开始,我们就要学习一下Linux系统中的信号机制,包括什么是信号,为什么要有信号,关于信号的一些操作等等。

信号的简单理解

首先,我们要了解什么是信号

信号是Linux系统提供的,让用户或进程给其他进程发送异步信息的一种方式

这里我们就不得不解释一下什么是异步,我们之前说过管道有同步机制,就是一个进程会等另一个进程;所以,异步就是各自独立运行,相互之间不会影响。换句话说,信号的产生,是由别人产生的,我收到之前,一直在忙自己的事情,我们是并发在跑的。

其实Linux系统中的信号和我们现实中的信号还是有很多相似之处的,我们在日常生活中会接收到各种各样的信号,进程也是一样,所以就可以把我们比做成进程,我们现实生活中收到的信号就是进程收到的信号。

信号有如下特点:

1.在没有发生的时候,我们已经知道发送的时候,应该怎么处理的

2.信号我们能够认识,肯定是之前有人在我们的大脑中设置了识别特定信号的方式

3.信号到来的时候,我们正在处理更重要的事情,可以不立即处理,可以在合适的时候处理,所以我们必须保存到来的信号

5.信号的产生是随时的,我们无法准确预料,所以,信号是异步发送的

以上特点不仅是人看待信号的方式,同样,也是进程看待信号的方式

那么,为什么要有信号呢?

我们可能有时会想要求进程停止或者其他要求,所以,系统要求进程要有随时响应外部信号的能力,随后作出反应。

那么接下来,我们就来简单认识一下信号

通过kill -l命令,我们就可以看到系统中的所有信号

我们可以发现,没有0号,32号,33号。其中,我们用的到的就是1到31号,因为34到64号是实时信号,意思是只要收到实时信号就要立刻相应并处理完,这在实时操作系统中是常用的,我们平常使用的公平调度的Linux操作系统是用不到的。并且,信号名字其实就是一个宏,所以数字和名字都可以表示信号

进程遇到信号应该如何处理呢?

1.应有默认动作,比如人红灯停,绿灯行

2.可以自定义处理信号的方式,这个自定义的过程就叫做信号的捕捉

3.可以忽略这个信号,这当然也算是处理了信号了

我们可以简单的使用一下信号的捕捉,要用到系统调用man signal

第一个参数就是要自定义的信号的编号(传数字和名字是一样的),第二个参数类型是函数指针(这个函数要求参数是一个int,没有返回值),就是我们想让收到这个信号时触发什么动作就可以写到函数中。我们可以简单来用一下

这样,当我们给这个进程用kill命令发送2号信号后,他就会打印内容。其实method的参数就是signal的第一个参数,它底层实际上是通过回调的方式来做出动作的

当然我们也可以把signal的第二个参数给成SIG_IGN,这样就会忽略这个信号

这个SIG_IGN其实就是把1强转成了函数指针,其实就是什么都不做

信号的产生

信号的产生有5种方式:kill命令,键盘产生,系统调用,软件条件,异常

我们也可以认为只有一种方式,就是OS给进程发送信号,因为以上五种方式本质上都是OS给进程发的信号

kill命令

我们只需要kill -signum pid就可以给指定进程发送指定信号,非常简单

kill是一个命令,运行起来就是一个进程,这个进程肯定是调用的系统调用,所以说本质上就是OS给进程发的命令。

键盘产生

我们之前就知道,一般程序死循环了,我们就可以ctrl+c终止进程,本质上ctrl+c实际上就是给进程发送的2号信号(SIGINT)。除此之外,还有ctrl+\也是进程退出,发送的是3号信号(SIGQUIT)ctrl+z是暂停程序,发送的是19号信号(SIGSTOP)

键盘产生信号为什么和OS有关呢?这我们就不得不谈OS是如何把键盘中的输入读取到OS中的缓冲区了

简单来说是通过硬件中断的技术,就是我们一敲键盘,那么CPU中某个连接键盘的引脚就会收到高电平,这时CPU会把这个引脚的编号放到特定的寄存器中交给OS,OS通过查中断向量表就可以查到是读取键盘的方法,这样OS就会去读取键盘了。读取到的会有字符信息和控制信息(ctrl+c),OS会把控制信息解释为信号,再向进程发送。

我们前面说,进程可能有更重要的事情需要处理。所以,进程要把收到的信号暂时保存起来,其实就是通过位图的形式保存到了进程的PCB中。利用一个整数就可以保存31个信号的信息,比特位的位置表示信号的编号(第0个比特位不表示信号),比特位的0或1 就表示是否收到信号。

所以向进程发送信号的本质就是向位图中写入

系统调用

man 2 kill

通过这个系统调用,就可以在程序中给某个特定的进程发送特定的信号

通过这个系统调用,我们其实可以写一个简单的mykill命令,来模拟实现一下系统命令kill,代码如下:

#include<iostream>
#include<unistd.h>
#include<signal.h>
#include<sys/types.h>
#include<cstring>
#include<cerrno>
using namespace std;
int main(int argc,char* argv[])
{
    if(argc!=3)
    {
        cout<<"Usage: "<<argv[0]<<" -signalnum pid"<<endl;
        return 1;
    }
    int signalnum=stoi(argv[1]+1);
    int pid=stoi(argv[2]);
    int n=kill(pid,signalnum);
    if(n<0)
    {
        cout<<"errno: "<<errno<<" error string: "<<strerror(errno)<<endl;
    }
    return 0;
}

man raise

这就是给自己发送某个信号,它底层也是封装了kill

man abort

这个是通过给自己发送6号信号(SIGABRT),终止自己,就是调用自己的进程,它底层也是封装了kill

我们学了这么多信号,我们怎么知道每个信号都是什么作用呢?我们可以通过这个命令查一下

man 7 signal

这就存着各种信号的默认动作和解释

软件条件

软件条件比如我们之前的管道,如果没人读了并且读端关闭,那么写端就会被发13号(SIGPIPE)信号,这就是因为管道这个软件的条件不满足了

还有一个信号是14号(SIGALRM)信号,这个就是当我们通过alarm定的时钟到时间了,那么OS就会给当前进程发送14号信号,那就会停止程序。下面我们来介绍一下alarm这个系统调用

man alarm

参数就是我们想让多少秒后给我们发信号,返回值是上一次没响的闹钟的剩余时间,因为在一个程序中只允许有一个正在倒计时的时钟,当我们设置了第二个后,第一个时钟就会失效,并且第二个时钟的返回值就是第一个时钟的剩余时间,我们也可以用下面的代码来验证一下

n就是三秒时钟的剩余时间2,当定完5秒的时钟后,3秒的时钟就会失效,并且打印完n的五秒后程序结束,我上面的描述既是上面理论的运行结果,也是实际的运行结果。

我们想连续定闹钟的话可以在SIGALRM信号的自定义捕捉里面再设置闹钟,比如我想顶一个5秒的闹钟,闹钟响后3秒后再响,响后三秒后再响.......,我们就可以这样实现

我们也可以调用alarm(0)取消闹钟

那么,这里的时钟时间到了为什么也是操作系统给发的信号呢?系统中的时钟肯定不止一个,这样,OS就需要把这些时钟管理起来,可以建立一个小根堆,这样我们只需要看最近要到达的时间跟目前的时间对比就可以了,如果到了时间OS就要去发送信号。

异常

异常比如进程有除零错误会收到8号(SIGFPE)信号(Floating Point Exception)访问野指针会收到11号(SIGSEGV)信号(Segmentation Violation)

如果有除零错误的话,在CPU中有个用来存是否溢出的寄存器,这样就表示了程序溢出了,就表示出错了,之后CPU会告诉OS,OS就会处理,就是向进程发送信号。

如果有访问野指针的问题,那么MMU(负责虚拟地址到物理地址的转化)拿到CR3中的页表地址和有问题的指针后进行处理,结果发现无法映射,就会将错误的虚拟地址存到CR2这个寄存器中,仍然是OS拿到错误信息就会向进程发送信号。

下面,有这样一个代码,有着奇怪的现象,我们来解释一下

void method(int sig)
{
    cout<<"I receive a signal: "<<sig<<endl;
}
int main()
{
    signal(SIGFPE,method);
    int a=0;
    a/=0;
    while(1);
    return 0;
}

这个代码的现象是一直打印

可是我也没有循环打印,原因就是:遇到除零错误后OS向进程发送SIGFPE信号,但是它的执行动作是仅仅打印,并没有终止程序,我们知道,进程调度是基于时间片进行轮转的寄存器中会保存有关进程调度的上下文,包括溢出错误,这样,每一次这个进程被放到CPU上,OS都会看到错误并且发信号,所以,就会一直打印。

  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值