目录

️推荐

一、信号的产生

二、产生信号的系统调用

2.1 kill——给指定的进程发送指定的信号

2.2 模拟实现指令 kill

2.3 raise——给调用的进程发送指定的信号

2.4 abort——给调用者发送 6 号信号

三、验证哪些信号不可以被捕捉

四、为什么除0和解引用空指针会给进程发信号呢?

五、alarm——设置闹钟


️推荐

前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站【Linux修行路】动静态库详解点击跳转到网站

一、信号的产生

  • 键盘组合键ctrl+c 给进程发送2号信号;ctrl+\ 给进程发送3号信号;ctrl+z 给进程发送19号信号(该信号无法被 signal 信号捕捉)。

  • 指令kill -signo pid

  • 系统调用killraiseabort

  • (硬件)异常:例如常见的除0(Floating point exception),当程序中出现除0异常,操作系统就会给对应的进程发送 8 号信号,来终止进程(这是 8 号信号的默认动作);对空指针解引用(Segmentation fault),当程序中出现对空指针解引用的时候,操作系统会给对应的进程发送 11 号信号。

  • 软件条件:管道通信中,写端正常,读端关闭,操作系统会给写端进程发送 13 号信号,终止掉正在进行向管道中写入的进程。alarm 闹钟 。

无论信号是如何产生的,最终一定是由操作系统发送给进程的,因为操作系统是进程的管理者。

二、产生信号的系统调用

2.1 kill——给指定的进程发送指定的信号

image-20240308092323511

2.2 模拟实现指令 kill

#include <iostream>
#include <sys/types.h>
#include <signal.h>
#include <stdlib.h>
#include <string>

using namespace std;

void Manual(char *directives)
{
    cout << "\n\t" << directives << " signum pid\n\n";
}

int main(int argc, char *argv[])
{
    if(argc != 3)
    {
        Manual(argv[0]);
        exit(1);
    }

    int signum = stoi(argv[1]);

    int pid = std::stoi(string(argv[2]));

    cout << signum << ' ' << pid <<endl;

    pid_t ret = kill(pid, signum);
    if(ret == -1)
    {
        perror("kill");
        exit(1);
    }
    return 0;
}

2.3 raise——给调用的进程发送指定的信号

image-20240308102100461

raise 就相当于:kill(getpid(), signum)

2.4 abort——给调用者发送 6 号信号

image-20240308102625369

abort 函数内部,不仅会执行自定义捕捉(前提是捕捉了 6 号信号),在执行完自定义捕捉之后,还要去执行 6 号信号默认的终止动作。通过 kill 指令去给进程发送 6 号信号,进程只会执行捕捉动作(前提是对 6 号进行了捕捉)或者只会执行默认动作(前提是没有对 6 号信号做捕捉)。

三、验证哪些信号不可以被捕捉

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

using namespace std;

void myhandler(int signo)
{
    cout << "process get a signal: " << signo << endl;
    // exit(1);
}

int main()
{
    // signal(2, myhandler);
    signal(19, myhandler);

    for(int i = 1; i <= 31; i++)
    {
        signal(i, myhandler);
    }

    while(true)
    {
        cout << "Hello Linux" << ' ' << getpid() << endl;
        sleep(1);
    }
    return 0;
}

先通过循环将31个信号都捕捉,然后在命令行通过 kill 给进程发送信号,看是否被成功捕捉。经过验证 9、19 号信号都无法被捕捉。其中 9 号信号是杀进程,19 号信号是暂停进程。

四、为什么除0和解引用空指针会给进程发信号呢?

以除0为例,首先,程序中所有的指令是需要被 CPU 来执行的,计算任务也不例外。所以 CPU 里面有特定的寄存器来存储操作数,还有一个状态寄存器,里面都是比特位级别的标记位,其中有一个标记位就是用来表示当前运算是否发生溢出,当 CPU 在执行除 0 的时候,会发生溢出,此时 CPU 就会去修改溢出标记位,这些寄存器中的内容,都属于当前进程的上下文数据,当进程切换的时候,这些数据同时也会被其它进程的上下文数据替换,当该进程再一次被替换进 CPU 执行的时候,这些上下文数据又会恢复出来。**所以任何异常只会影响该进程本身,并不会波及到操作系统。**CPU 是也是硬件资源,操作系统作为硬件的管理者,它是必须要关心硬件的健康,所以当 CPU 中的溢出标志位被设置成溢出的时候,操作系统一定是能知道的,(本质上,发生溢出会给操作系统发送中断),操作系统知道后就会向该进程发送对应的信号。

空指针解引用、越界访问等本质都是因为虚拟到物理转化失败,转化失败还导致硬件报错,最终被操作系统识别到。

程序对于异常信号,默认动作是让程序立即终止。但是我们可以在程序中对异常信号进行捕捉,如果捕捉方法里没有做特殊处理,比如说让程序退出,那这导致的后果就是程序不会立即终止,而是一直被调用运行,硬件错误就一直存在,操作系统就会一直给进程发送异常信号

总结:程序中出现的所有异常,最终一定会转化成硬件错误,操作系统能够识别这种硬件错误,最终给进程发送对应的信号。

image-20240308133955080

信号捕捉并不是为了让我们来解决问题的,而是当收到这个信号后,程序可能要被立即终止,信号捕捉给了我们应对程序即将被终止的机会,我们可以在捕捉函数里做一些数据保存,打印日志等工作。

五、alarm——设置闹钟

image-20240308135828669

  • seconds:闹钟将在 seconds 秒时候响起(给进程发送 14 号信号),如果 seconds == 0 ,则之前设置的闹钟会被取消,并将剩下的时间返回。

  • 返回值:返回之前闹钟的剩余秒数,如果之前未设闹钟,或者上一次设置的闹钟已经响过了,那么返回的就是 0 。

小Tips:如果在上一次闹钟还没响的时候,再一次调用 alarm 函数设置闹钟,那么这一次调用的返回值就是上一次闹钟的剩余时间,并且闹钟的响应时间会被更新成这一次的,上一次那个还没响的闹钟就会被作废。

设置闹钟

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

using namespace std;

int main()
{
    int n = alarm(5);
    while(true)
    {
        cout << "process is running..." << endl;
        sleep(1);
    }
    return 0;
}

image-20240308141730584

捕捉闹钟信号

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

using namespace std;

void handler(int signum)
{
    cout << "get a signal: " << signum << endl;
}

int main()
{
    signal(14, handler);
    int n = alarm(5);
    while(true)
    {
        cout << "process is running..." << endl;
        sleep(1);
    }
    return 0;
}

image-20240308142040976

alarm 返回值验证

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

using namespace std;

void handler(int signum)
{
    cout << "get a signal: " << signum << endl;
    int n = alarm(5); // 闹钟每五秒响一次
    cout << n << endl;
}

int main()
{
    signal(14, handler);
    int n = alarm(50);
    while(true)
    {
        cout << "process " << getpid() << " is running..." << endl;
        sleep(1);
    }
    return 0;
}

第一次设置的闹钟是 50 秒,在前 50 秒内,通过命令行向进程发送 14 号信号,此时程序就会去执行 handler 方法,在该方法中又调用了一个 alarm,这就是前一个闹钟还没响,就又设置了一个闹钟。

在这里插入图片描述

结语:

        今天的分享到这里就结束啦!如果觉得文章还不错的话,可以三连支持一下,您的支持就是我前进的动力!

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部