当前位置: 首页 > article >正文

Linux操作系统5-进程信号2(信号的4种产生方式,signal系统调用)

上篇文章:Linux操作系统5-进程信号1(信号基础)-CSDN博客

本篇Gitee仓库:myLerningCode/l25 · 橘子真甜/Linux操作系统与网络编程学习 - 码云 - 开源中国 (gitee.com)

本篇重点:信号的4种产生

目录

一. signal系统调用

二. 产生信号的4种方式

2.1 终端按键产生信号

 2.2 系统调用/命令产生信号

a kill调用向其他进程发送信号

b raise向自己发送信号

2.3 硬件异常产生信号

a 除 0 异常

b 空指针解引用 

2.4 软件产生信号

a pipe 读端退出,写端立马退出

b alarm定时器产生信号 


一. signal系统调用

        signal系统调用可以帮助我们自定义信号的行为

//所需头文件
#include <signal.h>

//函数原型    当进程收到signum这个信号之后,执行handler中的代码
typedef void(* sighandler_t)(int)    //函数指针
sighandler_t signal(int signum, sighandler_t handler);

//参数说明
signum    需要自定义行为的信号编号
handler   自定义行为的函数


//当某一个进程使用了signal系统调用之后,捕捉signum编号的信号就会执行下面的自定义行为
void handler(int signum)
{
    //由程序员自定义
}

举例代码:

我们自定义了2号信号的行为(ctrl c 发送的信号就是2号信号

#include <iostream>

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

void handler(int signum)
{
    while(true)
    {
        printf("进程[%d]收到信号[%d]\n",getpid(),signum);
        sleep(1);
    }
}

int main()
{
    signal(2, handler);

    while (true)
    {
        std::cout << "进程pid:" << getpid() << std::endl;
        sleep(1);
    }
    return 0;
}

        我们定义一个死循环的进程,并且自定义2号信号的行为。如果该进程收到2号信号那么他就会执行handler 中的死循环代码

测试结果如下:

我们输入ctrl c 来测试一下。

可以看到,输入ctrl c之后该进程收到2号信号。并且再次ctrl c 之后仍执行自定义行为的代码

注意:在我们调用signal之后并不会执行handler中的方法,而是在收到2号信号后再调用

二. 产生信号的4种方式

2.1 终端按键产生信号

常见的比如 ctrl c 向当前的前台进程发送2号信号,ctrl \ 向当前前台进程发送3号信号。

代码测试:

我们自定义2号和3号信号的行为来测试 ctrl c 和 ctrl \:

#include <iostream>

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

void handler(int signum)
{
    while(true)
    {
        printf("进程[%d]收到信号[%d]\n",getpid(),signum);
        sleep(1);
    }
}

int main()
{
    //同时自定义2号信号和3号信号的行为
    signal(2, handler);
    signal(3, handler);

    while (true)
    {
        std::cout << "进程pid:" << getpid() << std::endl;
        sleep(1);
    }
    return 0;
}

 2.2 系统调用/命令产生信号

        命令产生信号我们经常使用,就是 kill 信号 pid 即可向对应的进程发送对应的信号

a kill调用向其他进程发送信号

        kill不仅仅在命令中可以发送信号,也能在代码中使用

//头文件
#include <sys/types.h>
#include <signal.h>

//函数原型
int kill(pid_t pid, int signum);

//参数
向 pid 这个进程编号的进程发送 signum 这个编号的信号

//返回值
成功返回0,失败返回-1,并且设置错误码

测试代码:

mykill.cpp

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

void Usage(const std::string &proc)
{
    std::cout << "Usage\n"
              << proc << "pid signum\n ";
}

int main(int argc, char *argv[])
{
    if (argc != 3)
        Usage(argv[0]);

    pid_t pid = atoi(argv[1]);
    int signo = atoi(argv[2]);

    int n = kill(pid, signo);
    if(n < 0)
    {
        std::cout << "kill error"<<std::endl;
    }
    return 0;
}

该代码通过命令行参数获取键盘输入的信息,解析后执行kill

test.cpp 

#include <iostream>

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

void handler(int signum)
{
    while (true)
    {
        printf("进程[%d]收到信号[%d]\n", getpid(), signum);
        sleep(1);
    }
}

int main()
{
    // 同时自定义2号信号和9号信号的行为
    signal(2, handler);
    signal(3, handler);

    while (true)
    {
        std::cout << "进程pid:" << getpid() << std::endl;
        sleep(1);
    }
    return 0;
}

可以看到,我们可以通过kill系统调用向其他进程发送信号

b raise向自己发送信号

#include <signal>

int rasie(int sig);

//给自己发送sig这个信号

测试代码:

通过raise向自己发送3号信号 

#include <iostream>

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

void handler(int signum)
{
    while (true)
    {
        printf("进程[%d]收到信号[%d]\n", getpid(), signum);
        sleep(1);
    }
}

int main()
{
    // 同时自定义2号信号和9号信号的行为
    signal(3, handler);

    int cnt = 0;
    while (true)
    {
        std::cout << "进程pid:" << getpid() << "[" << cnt++ << "]" << std::endl;
        if (cnt == 5)
            raise(3);
        sleep(1);
    }
    return 0;
}

测试结果:

可以看到,第5次的时候,收到3号信号执行自定义行为。

2.3 硬件异常产生信号

        信号不一定由用户发出,也有可能由OS发出。比如我们的 /0操作,越界访问操作。

a 除 0 异常

#include <iostream>
#include <string>

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

int main()
{
    // 3.硬件异常产生信号
    // 信号产生,不一定由用户显示发送。有可能由操作系统自动产生
    while (true)
    {
        std::cout << "我正在运行..." << std::endl;
        sleep(1);
        int a = 10;
        a /= 0;
    }
    return 0;
}

运行结果如下:

可以看到进程收到了 Floating point exception。这个其实就是8号信号

可以自定义8号信号的行为来证明:

#include <iostream>
#include <string>

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

void catchSig(int signo)
{
    std::cout << "获取一个信号编号,编号是:" << signo << std::endl;
}

int main()
{
    // 3.硬件异常产生信号
    // 信号产生,不一定由用户显示发送。有可能由操作系统自动产生
    signal(SIGFPE, catchSig);
    while (true)
    {
        std::cout << "我正在运行..." << std::endl;
        sleep(1);
        int a = 10;
        a /= 0; // 为什么除0 会终止进程? 当前进程会收到来自OS的信号
    }
    return 0;
}

运行结果如下:

可以看到,该进程收到了8号信号。

可是为什么一直打印这条信息呢?我们没有写死循环

分析如下:

1 OS怎么知道该进程  \0 了?

        因为在cpu中有一个状态寄存器,这个寄存器中有一个状态标志位。如果我们有 \0 运算,就会导致结果溢出,此时这个寄存器就会将标志位由 0 设置为 1。说明该进程发生了运算异常。

        当OS发现某一个进程的状态标志位是1,就会向其发送8号信号终止它!

2 为什么会一直打印信息?

        一个进程不会一直占用CPU。当发送进程调度的时候,这个进程可能会被调走。此时进程会将自己的上下文信息保存到PCB中。当进程切换切换回来的时候,这个进程的状态标志位还是1,OS仍会向其发送8号信号,继续打印这条信息!

b 空指针解引用 

#include <iostream>
#include <string>

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

void catchSig(int signo)
{
    std::cout << "获取一个信号编号,编号是:" << signo << std::endl;
}

int main()
{ 
    signal(SIGFPE, catchSig);
    while (true)
    {
        std::cout << "我正在执行代码" << std::endl;
        sleep(1);
        int *p = nullptr;
        *p = 1; //野指针
    }
    return 0;
}

运行结果如下:

 可以看到,显示段错误。收到11号信号(非法访问内存)

原因分析:

        我们的指针都是在虚拟内存上的,虚拟内存通过页表和MMU的映射到物理内存上(MMU是集成在CPU上的)。当我们发送非法访问的时候,MMU就会发送硬件异常,OS向进程发送11号信号进行终止。

        不断打印的原因如上面。

2.4 软件产生信号

a pipe 读端退出,写端立马退出

        在这篇文章中,我们看到。管道的读端退出,写端会收到13号信号退出

Linux操作系统4-进程间通信1(通信与管道实现通信)-CSDN博客

这就是一种软件异常产生的信号

b alarm定时器产生信号 

//头文件
#include <unistd.h>

//函数原型
unsigned int alarm(unsigned int seconds);

//使用alarm可以设定闹钟,在输入的参数 seconds 秒之后
//OS会向当前进程发送 SIGALRM 信号,该信号的默认行为是终止该进程

测试代码: 

#include <iostream>
#include <string>

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

void catchSig(int signo)
{
    std::cout << "获取一个信号编号,编号是:" << signo << std::endl;
    exit(1);
}

int main()
{
    alarm(10);
    int count = 0;
    while (1)
    {
        std::cout << "hello world! " << count++ << std::endl;
        sleep(1);
    }
    return 0;
}

运行结果: 

可以看到10秒后,进程收到14号信号退出 

        通过alarm定义闹钟我们可以写出很多有用的代码。

        任意一个进程都能通过alarm向OS中设置闹钟,OS会周期性检测这些闹钟,当闹钟到了之后OS就会向设置闹钟的进程发送信号。

        这种超时的行为,全部是由软件构成的。所以称为 软件条件产生信号


http://www.kler.cn/a/566212.html

相关文章:

  • 【Linux网络编程】五种IO模型 多路转接(select)
  • Windows文件资源管理器左侧导航窗格没有WSL的Linux图标的解决方法
  • Python 课堂点名桌面小程序
  • C++11:工厂方法模式
  • Java中的ArrayDeque
  • AF3 DataPipeline类process_multiseq_fasta 方法解读
  • 代理服务器与内网穿透/打洞
  • MVCC,MySQL中常见的锁
  • 数据库基础二(数据库安装配置)
  • 汽车开放系统架构(AUTOSAR)中运行时环境(RTE)生成过程剖析
  • AI智能体与大语言模型:重塑SaaS系统的未来航向
  • 在 IntelliJ IDEA 中启动多个注册到 Nacos 的服务
  • 基因型—环境两向表数据分析——品种生态区划分
  • TCP长连接与短连接
  • PyCharm怎么集成DeepSeek
  • Full GC 排查
  • Windows 图形显示驱动开发-WDDM 3.2-自动显示切换(十二)
  • Python 创建一个能够筛选文件的PDF合并工具
  • 74.时间显示的两种方法 WPF例子 C#例子
  • HTTP服务