34 信号产生

本文详细解释了键盘信号的输入过程,操作系统如何通过中断机制处理和显示,以及信号的产生方式,包括键盘组合键、异常和闹钟信号。还介绍了CoreDump的概念及其在调试中的作用,以及kill和系统调用的相关知识。
摘要由CSDN通过智能技术生成

目录

1.键盘信号
2.显示器回显过程
3.信号产生方式
4.总结

键盘信号

键盘数据是如何输入给内核的,crtl+c是怎么变成信号的?
键盘被按下,肯定是os先知道,os怎么知道键盘上有数据呢?

c让操作系统每隔一段时间去轮询每个硬件有没有数据消耗很大。所以cpu有很多针脚高低电平,每个针脚都对应了硬件的中断号,当硬件有数据时,通过中断单元给cpu发送硬件中断,充电和放电,根据中断号就知道哪个硬件有数据了,然后os将内容拷贝到对应文件的缓冲区,传给用户缓冲区。在内存操作系统部分,有一个中断向量表,里面保存了硬件方法的地址,调用这个方法就可以将键盘的数据读取到内存。拷贝完成后再次给cpu发送中断,继续之前的执行

在这里插入图片描述

os在拿到数据后会先判断,如果是控制类,如crtl+c,会转换为2号信号,不会拷贝到缓冲区

信号是模仿硬件中断的一种软件中断的方式

显示器回显的过程

显示器和键盘是两个文件,有自己对应的缓冲区,键盘输入后将内容拷贝到显示器的缓冲区就是回显。所以当后台运行时,输入命令后,虽然显示器显示是乱的,但仍然可以运行命令,就是因为内容是键盘缓冲区里,并没有在显示器缓冲区,只是回显到了显示器

在这里插入图片描述

信号产生方式

1.键盘组合键

crtl+c默认终止进程,除过这个快捷键还有crtl+\是3号信号
捕捉3号信号,运行验证:

#include <stdio.h>
#include <iostream>
#include <unistd.h>
#include <signal.h>

using namespace std;
void myhandler(int signal)
{
    cout << "get a singal" << signal << endl;
}

int main()
{
    //signal(SIGINT, myhandler);
    signal(3, myhandler);

    while (true)
    {
        //printf("hello world\n");
        sleep(1);
    }
}

在这里插入图片描述
kill3号信号也是同样的结果
在这里插入图片描述

crtl+z是19号信号,捕捉19号信号发现不成功,crtl+z仍然会暂停进程,kill发送19号信号也暂停进程

不是所有的信号都可以被signal捕捉的,比如19

可以循环捕捉31个信号,然后kill从1发送到31,看看哪些信号不能捕获
结果9和19号新号不能捕捉
9和19号新号是杀死和暂停进程,为了防止恶意程序和重要工作运行正常不能被捕捉

SIGQUIT的默认处理动作是终止进程并且Core Dump

Core Dump

什么是Core Dump
当一个进程要异步终止时,可以选择把进程的用户空间内存数据全部保存到磁盘上,文件名通常是core,这叫core dump。进程异步终止通常是因为有bug,如非法访问导致段错误,事后可以用调试器检查core文件以查清错误原因,这叫做post-morterm debug(事后调试)。一个进程允许产生多大的core文件取决于进程的resource limit(这个信息保存在pcb中)。默认是不允许产生core文件的,因为core文件中可能包含用户密码等敏感信息,不安全。在开发调试阶段可以用ulimit命令改变这个限制,允许产生core文件,首先ulimit命令改变shell进程的resource limit允许core文件最大为1024k,$ulimit -c 1024

7章man手册
在这里插入图片描述

core dump 和term

ign是忽略,cont是继续。term和coredump都是终止,但有区别

这个标记之前在子进程status状态中说过,是第8位。生成子进程父进程等待获取一下coredump
在这里插入图片描述

#include <stdio.h>
#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <wait.h>

using namespace std;

void work()
{
    cout << "print log" << endl;
}
void myhandler(int signal)
{
    work();
    //cout << "get a singal" << signal << endl;
    int n = alarm(5);
    cout << "sencond: " << n << endl;
}

int main()
{
    //signal(SIGINT, myhandler);
    //signal(3, myhandler);
    // signal(SIGALRM, myhandler);
    // int n = alarm(50);
    // while (true)
    // {
    //     printf("%d\n", getpid());
    //     sleep(1);
    // }

    pid_t id = fork();
    if (id == 0)
    {
        while (true)
        {
            printf("我是子进程: %d\n", getpid());
            sleep(1);
        }
     
    }

    int status;
    pid_t ret = waitpid(id, &status, 0);
    printf("core dump: %d\n", status >> 7 & 1);
}

在这里插入图片描述在这里插入图片描述

云服务器的core功能被关闭的,ulimit -a可以查看所有,-c查看core的开关
在这里插入图片描述

ulimit -c 1024,设置大小为1024,开启功能,关闭设置为0

运行一个除0错误的代码,并生成一个core文件
在这里插入图片描述
在这里插入图片描述
打开系统的core dump功能,一旦出现异常,os会将进程在内存运行信息,转储到进程的当前目录,形成core.pid文件:核心转储(core dump)。这个可以让运行时错误,保存出现问题的在哪一行,先运行,再core-file,就是事后调试

运行gdb,直接输入core文件就可以定位打印错误信息

core-file [core文件]

在这里插入图片描述

core dump方便事后调试

为什么云服务器关掉这个功能,因为生成的core文件比较大,在一个比较大型的项目中,如果一出现这些错误就生成core文件,可能会导致生成特别多文件,占用很多资源,甚至导致运行被停止

2. kill

kill -sign pid

3. 系统调用

  1. kill命令是调用kill函数实现的。kill函数可以给一个指定的进程发送指定的信号

#include <signal.h>
int kill(pid_t pid, int signo);
int raise(int signo);
这两个函数都是成功返回0,错误返回-1。

在这里插入图片描述

自己实现kill命令

#include <stdio.h>
#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>

using namespace std;

void usage(string proc)
{
    cout << "usage:\n\t" << proc << "signum pid\n\n";
}

void myhandler(int signal)
{
    cout << "get a singal" << signal << endl;
}

//./kill 3 pid
int main(int args, char* argv[])
{
    if (args != 3)
    {
        usage(argv[0]);
        return 1;
    }

    int sign = stoi(argv[2]);
    pid_t pid = stoi(argv[1]);
    int n = kill(sign, pid);
    if (n < 0)
    {
        perror("kill");
        return 2;
    }
}

在这里插入图片描述

raise命令给当前进程发送指定信号

int raise(int signo);
这两个函数都是成功返回0,错误返回-1。

在这里插入图片描述

  1. abort可以给当前进程发送6号信号

#include <stdlib.h>
void abort(void);
就像exit函数一样,abort函数总是会成功的,所以没有返回值。

这个函数内部做了封装,调用后还会结束进程。如果是kill发送这种形式发6号信号,并不会结束进程

无论信号如何产生,一定是os发送的,因为os是进程的管理者

4. 异常

除0错误和野指针错误,这两个分别是8号和11号信号,发生这个错误会报告崩溃并退出。当对这两个信号捕捉,行为会改为一直循环
在这里插入图片描述

os如何知道异常

除0异常
cpu中的内容是进程的上下文数据,当cpu执行到除0错误的时候,会将cpu中的状态寄存器里的溢出标志位改为1,关于cpu的设计在cpu开发手册会有说明。cpu也是硬件,os是硬件的管理者,os发现这个这个标志就知道这个计算错误,然后给进程发送信号
在这里插入图片描述

野指针异常
cpu中继承了MMU(内存管理单元),这个可以进行虚拟地址和物理地址的页表转换,野指针就是地址转换失败的情况,cpu会将结果和失败的地址保存起来。os和上面一样发现这个错误就发送信号

为什么重复输出

当出现这个错误的时候没有结束进程,就会继续执行,当cpu再次加载进程的时候,发生的错误没有修复,又会继续发送信号,重复这个动作

硬件异常一方面没有权限去修复,是用户自己的数据。另一方面因为异常了,这个计算结果已经不值得信任。所以进程最好终止。信号和捕捉并不能解决这个异常,只是告诉用户明白出现的情况,可以统一处理

软件条件信号

异常不只可以由硬件产生,软件也可以。如管道中如果读取方关闭,就会发生异常,发送13号SIGPIPE信号并关闭写端。os检测条件不满足就会出现异常,可以这样产生闹钟

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

这个函数到达设定延迟后会发送SIGALRM信号
返回值是0或者以前设定的闹钟时间还剩下的秒数。打个比方,某人要小睡一觉,设定闹钟为30分钟后响,20分钟后被吵醒了,还想多睡一会,重新设定闹钟为15分钟后响,这时以前设定的闹钟剩下的时间就是10分钟。如果参数为0,表示取消以前设定的闹钟,函数的返回值仍然是以前设定的闹钟时间还剩下的秒数

捕获闹钟信号验证:

#include <stdio.h>
#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>

using namespace std;
void myhandler(int signal)
{
    cout << "get a singal" << signal << endl;
}

int main()
{
    //signal(SIGINT, myhandler);
    //signal(3, myhandler);
    signal(SIGALRM, myhandler);
    int n = alarm(5);
    while (true)
    {
        printf("%d\n", getpid());
        sleep(1);
    }
    

}

在这里插入图片描述
闹钟不是异常,所以响应一次后就会结束,想让闹钟每隔5s响一次,可以重新设置

void myhandler(int signal)
{
    cout << "get a singal" << signal << endl;
    alarm(5);
}

在这里插入图片描述

闹钟可以设置一个定时任务

void work()
{
    cout << "print log" << endl;
}
void myhandler(int signal)
{
    work();
    cout << "get a singal" << signal << endl;
    alarm(5);
}

将闹钟时间设长,运行的时候kill发送信号,打印出返回值
在这里插入图片描述

os中一定存在大量的闹钟,将这些结构用组织管理起来,用系统当前时间和这些时间作对比,就能判断是否超时。超时了就发送对应的信号。这种用堆来管理最好,优先处理超时时间最近的,只要堆顶没超时,堆所有都没超时,就不需要遍历所有数据

总结

上面所说的所有信号产生,最终都要由os来执行,因为os是进程的管理者
信号可以是立即处理,也可以在合适的时候
信号如果不是被立即处理,那么信号是否需要暂时被记录下来,记录在哪里?怎么记录
一个进程在没有收到信号的时候,能否知道,自己应该对合法信号如何处理?
如何理解os向进程发送信号?能否描述一下完整的发送处理过程?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值