【Linux】信号产生

目录

1 信号产生的原理

1.1 信号是如何记录存储的?

1.2 信号发送

1.3 信号捕捉

2 信号产生 方式

2.1 通过终端按键产生信号

2.2 通过系统调用向进程发送信号

kill函数 -- 接受别人发送的信号

raise函数

abort函数

2.3 由软件条件产生信号

2.4 硬件异常产生信号

 多种信号分析

status

 3 信号产生总结


1 信号产生的原理

1.1 信号是如何记录存储的?

分析

一个操作系统中,存在着多个进程,每个进程有着多种信号;所以操作系统要管理这些的信号--先描述,在组织!

描述:普通型号1~31一共有31种,对于一个进程来说,预先已经知道对于这31种信号的处理方法,所以OS只需要告诉进程某个信号是否存在--只有两种状态--位图结构!

组织:在学习进程时候,我们应该了解了每个进程都对应一个task_struct(PCB),在这个task_struct中,记录着进程的各种信息,各种信息中同样也包括信号的记录。信号在task_struct中是以位图的方式记录的,task_struct有变量signal,可以把它的类型理解成无符号整数。比特位的位置为信号编号,比特位的内容为是否收到信号,假如收到6号信号就会把第六个比特位置。

1.2 信号发送

  • 所谓的发送信号,本质其实写入信号,直接改特定进程的信号位图中的特定的比特位,0->1

  • task_struct是内核数据结构,只能由OS来修改 -- 无论什么样产生信号的方法,最终都是让OS来完成最后的发送过程!

1.3 信号捕捉

 signal函数

原型:signal(int sig, void (*func)(int))     

功能:用来自定义信号处理方式

参数:

  • sig:信号编号
  • func为函数指针,传入的函数为信号的处理方式

2 信号产生 方式

2.1 通过终端按键产生信号

通过终端按键也就是通过键盘产生信号,比如我们常用的ctrl+c。ctrl+\。

注意:

  • Ctrl-C 产生的信号只能发给前台进程。Shell可以同时运行一个前台进程和任意多个后台进程,只有前台进程才能接到像 Ctrl-C 这种控制键产生的信号。
  • 一个命令后面加个&可以放到后台运行,bash默认是前台进程,这样bash(Shell)不必等待进程结束就可以接受新的命令,启动新的进程。
  • 前台进程在运行的任何时候都可能收到ctrl+c产生的信号发生终止,所以说信号对于进程的控制流程来说是异步。
     

问题:我们平时在输入的时候,计算机怎么知道我从键盘输入了数据呢?

答:键盘是通过硬件中断的方式,通知系统我们的按键已经按下了。

按键信号分析

CPU硬件有着多个针脚,键盘的按键按下的时候,通过转化对应的针脚会产生中断号,OS此时就会感知到,向中断向量表中发送信号,从该表中,OS就可以分析出按下的对应的是哪一个按键,并将按键解析成对应的信号(整形),这时候OS找到对应的前台的进程并发送相应的信号。

2.2 通过系统调用向进程发送信号

首先在后台执行死循环程序,然后用kill命令给它发SIGSEGV信号。

  • 4568是test进程的id。之所以要再次回车才显示 Segmentation fault ,是因为在4568进程终止掉 之前已经回到了Shell提示符等待用户输入下一条命令,Shell不希望Segmentation fault信息和用 户的输入交错在一起,所以等用户输入命令之后才显示。
  • 指定发送某种信号的kill命令可以有多种写法,上面的命令还可以写成 kill -SIGSEGV 4568 或 kill -114568 , 11是信号SIGSEGV的编号。以往遇 到的段错误都是由非法内存访问产生的,而这个程序本身没错,给它发SIGSEGV也能产生段错误。

kill命令是调用kill函数实现的。kill函数可以给一个指定的进程发送指定的信号。raise函数可以给当前进程发送指定的信号(自己给自己发信号)。

kill函数 -- 接受别人发送的信号

 signo为信号编号。成功返回0,失败返回-1。

【模拟实现kill命令】

#include <iostream>

using namespace std;

#include <cstring>

#include <sys/types.h>
#include <signal.h>

void Usage(char *str)
{
    printf("%s 信号编码 进程pid\n", str);
}

int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        Usage(argv[0]);
    }
    else
    {
        int pid = atoi(argv[2]);
        int sig = atoi(argv[1] + 1);
        cout << "pid:" << pid << "; sig:" << sig << endl;
        int ret = kill(pid, sig);
        if (ret != 0)
        {
            perror("kill:");
        }
    }
    return 0;
}

raise函数

raise函数可以给当前进程发送指定的信号(自己给自己发信号)。

int raise(int signo);

成功返回0,失败返回-1。

abort函数

void abort(void);

功能:向自己发送6号信号SIGABRT

注意:SIGABRT可以被捕捉,但是捕捉之后依然会让进程终止,这就是SIGABRT的特点
就像exit函数一样,abort函数总是会成功的,所以没有返回值。

2.3 由软件条件产生信号

SIGPIPE是一种由软件条件产生的信号,在“管道”中已经介绍过了。(当读端关闭的时候,OS会向写端发送SIGPIPE信号从而终止写端的进程)

本节主要介绍alarm函数 和SIGALRM信号。

alarm函数

#include <unistd.h>
unsigned int alarm(unsigned int seconds);
调用alarm函数可以设定一个闹钟,也就是告诉内核在seconds秒之后给当前进程发SIGALRM信号, 该信号的默认处理动作是终止当前进程。
 

alarm的返回值是0或者是剩余秒数。如果闹钟被提前唤醒,返回值为剩余秒数,否则是0。

【使用】写一个计数程序,看看cou一秒钟可以计多少次数。

int count=0;
void myhandler(int signo)
{
    cout<<"count:"<<count<<endl;
    cout << "get a signo:" << signo << endl;
    exit(0);
}

int main()
{

    signal(SIGALRM, myhandler);
    alarm(1);
    while (true)
    {
        count++;
    }
}

这个程序的作用是1秒钟之内不停地数数,1秒钟到了就被SIGALRM信号终止。

2.4 硬件异常产生信号

在代码中有野指针,除0等操作时程序会出现异常,这时候会产生信号,然后程序崩溃。程序崩溃的本质就是收到了信号。

下面对这个过程进行具体解释:以除0操作为例,我们知道计算都是在cpu中的,cpu中有一个状态寄存器,当进行除0操作后,状态寄存器会异常。os是软硬件的管理者,当检测到cpu状态异常后,会定位到相应的进程,更改进程的信号位图,进程通过位图识别到信号发生崩溃。信号宏名字:SIGFPE

再比如当前进程访问了非法内存地址,负责虚拟地址与物理内存的一个硬件MMU会产生异常,之后和上述过程一样。信号:SIGSEGV

空指针和野指针问题分析:

 多种信号分析

问题:普通信号有有31种,而默认行为只有终止进程,停止进程,继续进程等较少的功能,而为什么操作系统会提供这么多信号机制呢?

答:因为我们并不关心进程的终止,而是要知道进程为什么会终止,多种信号,可以提供给我们更加丰富的信息,从而让我们方便的解决问题!

问题:终止进程的信号有两种action:Term&Core,这有什么用?

再验证之前我们先来学习一个概念:Core Dump

在程序正常结束后,我们可以通过错误码判断程序是否运行正确。但代码在运行过程中出错,我们也要有办法判断,其中一个方法就是调试,除此之外,linux为我们提供了核心转储功能。当一个进程要异常终止时,可以选择把进程的核心内存数据全部保存到磁盘上,文件名通常是core,这叫做Core Dump

核心转储功能一般在云服务器,线上生产时是关闭的。

ulimit      -a     查看系统资源

可以发现core文件大小为0。想要使用此功能,我们首先要改一下core文件大小。

可以发现在发送信号后进行了(core dumped),并且多出了一个core.6229文件。我们使用gdb调试myproc,再查看core文件,可以得到进程异常终止信息。这种方法也适用于代码中内存越界,除0等错误,可以快速定位到第几行。

 

问题:核心转储功能调试的时候这么好用,为什么云服务器默认会关闭它?

答:写代码一共有三种环境:开发环境、测试环境、生产环境;云服务器是生产环境。在生产环境里面,为了维护软件的稳定,当某个进程出了问题,会被其他进程立马重启,从而保证目前的软件能跑!而核心转储功能会在每次程序异常退出的时候生产一个core.pid二进制文件,而且该文件通常都比较大,当一个程序出了问题且在没人维护的时候被重启多次,就会产生多个core文件,没人管,就会占满磁盘空间,导致整个服务崩溃。所以核心转储功能只适合用来事后处理,当需要的时候在开启这个功能!

status

 core dump表示是否有核心转储功能!

获取进程退出的所有信息:

    int status=0;
    waitpid(id,&status,0);
    cout<<"exit code: "<<((status>>8)&0xFF)<<endl;
    cout<<"eixt signal: "<<(status&0x7F)<<endl;
    cout<<"exit dump flag: "<<((status>>7)&0x1)<<endl;

 3 信号产生总结

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

杰深入学习计算机

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值