信号知识详解

本文详细介绍了Linux系统中的信号机制,包括信号的产生(如kill命令和键盘输入)、核心转储功能、信号的保存与处理(如pending和block位图,以及sigprocmask函数的使用),并通过示例代码展示了如何自定义信号处理和生成core文件。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

1、信号的产生

2、core 核心转储

3、信号的保存

4、信号的处理 


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

常见的信号处理方式:

        1、默认行为

        2、忽略

        3、自定义

1、信号的产生

        1、kill命令

        我们可以使用命令 kill -l 查看信号,也可以使用 kill -signum pid 对指定进程发送信号。

例如:用 kill -9 将指定的死循环进程杀死。

         

        同样,我们也可以用 kill 命令发送其他信号。

        查看信号可以用 man 7 查看 man 手册。

        2、键盘

        先介绍一个系统调用:signal(),它可以改变信号的默认行为,变为自定义。

        参数:signum 为指定进程编号,想改变哪个信号的默认行为,就传哪个编号。

                   handler 为函数指针,如果进程收到了信号 signum,就执行这个函数。

        返回值:该信号的上一个处理方法。

        我们在终止进程时经常使用 ctrl + c ,这其实就是向进程发送了一个2号命令。

        ctrl + \ :这是3号命令。

证明:当我们把 2号命令和 3号命令的默认行为改成打印,使用 ctrl + c 和 ctrl + \ 能打印出来。

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

using namespace std;

void handler2(int sig)
{
    cout << "I am signal2 " << endl;
}

void handler3(int sig)
{
    cout << "I am signal3 " << endl;
}

int main()
{
    // 改变信号2和3的默认行为
    signal(2, handler2);
    signal(3, handler3);
    while(1)
    {
        ;
    }

    return 0;
}

        3、系统调用

        a、kill(),向指定进程发送指定信号

        参数: pid为要指定的进程的id,sig为要发送的信号的编号。

        返回值:成功返回0;失败返回 -1,并设置错误码。

示例:5 秒后给自己发送 2号信号终止自己

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

using namespace std;

int main()
{
    // 5 秒后给自己发送 2号信号终止自己
    int i = 0;
    while(1)
    {
        cout << "mypid is " << getpid() << " i = " << i << endl;
        if(i == 5)
        {
            kill(getpid(), 2);
        }
        ++i;
    }

    return 0;
}

        b、raise() :向调用者发送一个指定信号(谁使用这个函数就向谁发)

         参数:发送信号的编号。

        返回值:成功返回 0;失败返回非0。

示例:5 秒后给自己发送 2号信号终止自己

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

using namespace std;

int main()
{
    // 5 秒后给自己发送 2号信号终止自己
    int i = 0;
    while(1)
    {
        cout << "mypid is " << getpid() << " i = " << i << endl;
        if(i == 5)
        {
            raise(2);
        }
        ++i;
    }

    return 0;
}

        c、abort():通过发送6号信号终止自己 

         使用abort发送6号信号。

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

using namespace std;

void handler6(int sig)
{
    cout << "I am signal6 " << endl;
}

int main()
{
    // 改变信号6的默认行为
    signal(6, handler6);
    
    abort();

    return 0;
}

        d、通过alarm() 设置闹钟,时间到了就发送14号信号 

         参数:想设定的闹钟的秒数。

        返回值:如果上一个闹钟还有剩余时间,返回上一个闹钟的剩余时间,如果上一个闹钟剩余时间为0,返回0。

示例:设置一个 5 秒的闹钟,在死循环中每隔 1 秒打印 1 次信息

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

using namespace std;

int main()
{
    // 设置一个5秒的闹钟
    alarm(5);

    int i = 1;
    while(i)
    {
        cout << "mypid is " << getpid() << ", i = " << i << endl;
        sleep(1);
        ++i;
    }

    return 0;
}

2、core 核心转储

        core功能就是核心转储,将进程在内核中的核心数据,转储到磁盘中,形成core文件。通过core,我们可以定位到进程为什么退出,以及执行到哪行代码退出的。

        打开 linux 的 core 功能:

        ulimit -a  : 查看 core file size 选项,为 0 表示 core 功能关闭,不为 0 表示打开。

         ulimit -c 10240 :打开 core 功能,设置文件大小( -c 后面为文件大小 )

        我们上面讲过的 abort() 报错信号就是 有core功能的。

        如果要生成 core 文件,我们需要先停止 apport 服务:sudo service apport stop

示例代码:

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

using namespace std;

int main()
{
    int sum = 0;
    int n = 10;
    
    abort();

    for(int i = 0; i < n; ++i)
    {
        sum += i;
    }
    return 0;
}

        可以用 gdb 查看错误信息

 

3、信号的保存

        我们把实际处理信号的动作叫信号的递达(deliver)

        从产生到递达之间的状态叫做信号未决(pending)

        在进程控制块 task_struct 中,有三个表:

        block位图:阻塞位图,置为 1 表示阻塞,不处理该信号。

        pending位图:表示是否收到,置为 1 表示收到该信号。

        handler方法:序号为几的下标报存着序号为几的信号的执行方法。

        因此,我们发送信号的本质就是修改 pending 位图,阻塞信号的本质就是修改 block 位图!在信号递达时,现将 pending 位图清 0 ,再递达。

 那么我们如何对信号进行修改呢?

        未决与阻塞可以用相同的数据类型表示:sigset_t 也就是位图,系统内也存在着各种对位图进行操作的方法。

        1、sigemptyset :将所给参数中的 set 集合全置为 0.

        2、sigfillset: 将所给参数中的 set 集合全置为 1.

        3、sigaddset:将集合中的 signum 信号置为1,也就是添加该信号。

        4、sigdelset:将集合中的 signum 信号置为0,也就是删除该信号。

        5、sigismember:判断 signum 是不是 set 中的成员,也就是判断 set 中 signum 标志位是否为1.

sigprocmask可读取或更改进程的信号屏蔽字。

        参数:how可以为SIG_BLOCK表示 set 包含了我们想添加的信号屏蔽字。SIG_UNBLOCK表示 set  包含了我们想解除的信号屏蔽字。SIG_SETMASK表示直接将 set 设置为当前的信号屏蔽字。oldset表示:读取当前的信号屏蔽字。

        返回值:成功为 0 ,失败返回 -1.

其中,9号以及19号信号无法被屏蔽。

示例:屏蔽2号信号

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

using namespace std;

int main()
{
    sigset_t set;
    sigemptyset(&set);
    sigaddset(&set, 2);
    sigprocmask(SIG_BLOCK, &set, nullptr);

    while(true)
    {
        sleep(1);
    }
    return 0;
}

        多次输入ctrl + c(2号信号),没有终止进程 

 

4、信号的处理 

信号是什么时候被处理的呢?

        进程从内核态到用户态的时候,信号会被检查并处理。        

评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值