Linux 信号(Signal)

1. 什么是信号

信号(Signal)是 Linux/Unix 中进程间通信的一种机制。

本质:

  • 软件层面的“中断”
  • 用于通知进程发生了某种事件

例如:

信号含义
SIGINTCtrl + C
SIGSEGV段错误
SIGFPE除0异常
SIGALRM闹钟超时
SIGCHLD子进程退出

2. 信号的生命周期

一个信号从产生到处理:

产生 -> 保存(pending) -> 阻塞(block)判断 -> 递达(deliver) -> 处理

Linux 内核维护:

  • pending 表(是否收到)
  • block 表(是否阻塞)

3. 信号产生方式

Linux 中常见五种信号产生方式:

方式示例
终端按键Ctrl + C
硬件异常除0、段错误
软件条件alarm
显式发送kill
系统事件子进程退出

4. kill 发送信号


4.1 kill 系统调用

函数:

int kill(pid_t pid, int sig);

作用:

向指定进程发送信号。


4.2 示例:实现 mykill

源代码

//实现kill命令

void Usage(string proc)
{
    cout << "Usage:\n\t " << proc << " <pid>\n" << endl;
}

//mykill signum pid
int main(int argc, char* argv[])
{
    if(argc != 3)
    {
        Usage(argv[0]);
        exit(1);
    }

    int signum = stoi(argv[1]);
    pid_t pid = stoi(argv[2]);

    int n = kill(pid, signum);

    if(n == -1)
    {
        perror("kill");
        cout << "kill process failed" << endl;
        exit(2);
    }

    cout << "kill process success" << endl;

    return 0;
}

4.3 使用方式

./mykill 2 12345

表示:

向 pid 为 12345 的进程发送:

SIGINT(2)

5. signal 信号捕获


5.1 signal 函数

函数原型:

void (*signal(int signum, void (*handler)(int)))(int);

作用:

为指定信号注册处理函数。


5.2 示例

源代码

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

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

    int cnt = 10;

    while(cnt--)
    {
        cout << "i am a process" << endl;
        sleep(1);
    }

    return 0;
}

5.3 运行效果

按:

Ctrl + C

会触发:

SIGINT

然后执行:

myhandler()

6. alarm 闹钟信号


6.1 alarm 函数

unsigned int alarm(unsigned int seconds);

作用:

指定 seconds 秒后发送:

SIGALRM

6.2 示例

源代码

void work()
{
    cout << "work..." << endl;
}

void MyHandler(int signum)
{
    work();

    int n = alarm(5);

    cout << "剩余时间: " << n << endl;
}

int main()
{
    signal(SIGALRM, MyHandler);

    int n = alarm(50);

    while(1)
    {
        cout << "running..." << endl;
        sleep(1);
    }

    return 0;
}

6.3 特点

alarm 只触发一次

需要在处理函数中:

alarm(5);

重新设置。


6.4 返回值

alarm()

返回:

上一次闹钟剩余时间。


7. 硬件异常信号


7.1 除0异常

源代码

signal(SIGFPE, myhandler);

int a = 10;
a /= 0;

会产生:

SIGFPE

7.2 段错误

例如:

int* p = nullptr;
*p = 100;

产生:

SIGSEGV

8. 子进程退出信号 SIGCHLD


8.1 子进程退出

当子进程退出时:

内核向父进程发送:

SIGCHLD

8.2 waitpid 获取退出信息

源代码

pid_t rid = waitpid(id, &status, 0);

if(rid == id)
{
    cout << "child quit info, rid: "
         << rid
         << ", exit code: "
         << ((status>>8)&0xFF)
         << ", exit signal: "
         << (status&0x7F)
         << ", core dump: "
         << ((status>>7)&1)
         << endl;
}

8.3 status 解析

内容提取方式
退出码(status >> 8) & 0xFF
终止信号status & 0x7F
core dump(status >> 7) & 1

9. 信号阻塞与 pending


9.1 sigprocmask

函数:

int sigprocmask(int how,
                const sigset_t *set,
                sigset_t *oldset);

how 参数

参数含义
SIG_BLOCK添加阻塞
SIG_UNBLOCK解除阻塞
SIG_SETMASK替换阻塞集

9.2 sigpending

函数:

int sigpending(sigset_t *set);

作用:

获取 pending 信号集。


9.3 pending 打印函数

源代码

void PrintPending(sigset_t &pending)
{
    for(int signo = 1; signo < 32; signo++)
    {
        if(sigismember(&pending, signo))
        {
            cout << "1";
        }
        else
        {
            cout << "0";
        }
    }

    cout << endl;
}

9.4 屏蔽 SIGINT 示例

源代码

signal(2, handler);

sigset_t bset, oldset;

sigemptyset(&bset);
sigemptyset(&oldset);

sigaddset(&bset, 2);

sigprocmask(SIG_SETMASK, &bset, &oldset);

int cnt = 0;

sigset_t pending;

while(1)
{
    sigpending(&pending);

    PrintPending(pending);

    sleep(1);

    cnt++;

    if(cnt == 20)
    {
        cout<<"unblock signal 2"<<endl;

        sigprocmask(SIG_SETMASK,
                    &oldset,
                    nullptr);
    }
}

9.5 现象

按:

Ctrl + C

后:

  • 信号不会立即处理
  • pending 对应位变成 1

解除阻塞后:

handler()

立即执行。


10. 哪些信号不能阻塞

以下信号不能被阻塞:

信号含义
SIGKILL(9)强制杀死
SIGSTOP(19)强制暂停

原因:

内核必须保证:

进程一定能被终止

11. sigaction

相比 signal:

sigaction

更可靠、更强大。


11.1 struct sigaction

源代码

 struct sigaction 结构体
 struct sigaction 
{
     void     (*sa_handler)(int);      // 信号处理函数指针(类似 signal)
     void     (*sa_sigaction)(int, siginfo_t *, void *); // 扩展处理函数
     sigset_t sa_mask;                 // 在处理该信号时额外要阻塞的信号集
     int      sa_flags;                // 控制信号处理行为的标志位
     void     (*sa_restorer)(void);    // 已废弃,不再使用
};

11.2 注册信号处理

源代码

struct sigaction act, oact;

memset(&act, 0, sizeof(act));
memset(&oact, 0, sizeof(oact));

sigemptyset(&act.sa_mask);

sigaddset(&act.sa_mask, 4);
sigaddset(&act.sa_mask, 3);

act.sa_handler = handler;

sigaction(2, &act, &oact);

11.3 sa_mask 作用

表示:

在处理当前信号期间:

额外阻塞哪些信号。

例如:

sigaddset(&act.sa_mask, 3);

则处理 SIGINT 时:

SIGQUIT

也会被阻塞。


12. pending 位什么时候清零?

问题:

pending 位什么时候从1变0?

答案:

在调用处理函数之前

即:

先清 pending
再执行 handler

13. 信号处理期间自动阻塞

Linux 规定:

当某信号正在处理时:

该信号会自动加入 block 表。

防止:

信号处理函数嵌套调用

14. volatile 与信号


volatile 关键字用于确保 flag 变量在每次访问时都从内存中读取, 而不是从寄存器中读取,这在多线程环境中特别有用, 因为多个线程可能会同时访问 flag 变量, 而优化器可能会假设 flag 变量的值不会改变

14.1 示例

源代码

volatile int flag = 0;

void handler_2(int signum)
{
    cout<< "catch a signal, signal number: "
        << signum << endl;

    flag = 1;
}

void test_2()
{
    signal(2, handler_2);

    while(!flag)
    {
        cout << "process quit normal" << endl;
        sleep(2);
    }
}

14.2 为什么需要 volatile

避免:

编译器优化

导致:

while(!flag)

死循环。


15. SIGCHLD 异步回收子进程


15.1 信号方式回收

源代码

void handler_3(int signum)
{
    pid_t rid;

    while((rid = waitpid(-1,
                         nullptr,
                         WNOHANG)) > 0)
    {
        cout<< "child process quit: "
            << rid
            << endl;
    }
}

15.2 WNOHANG

表示:

非阻塞回收

避免:

handler 阻塞

15.3 自动回收子进程

Linux 支持:父进程调用sigaction,将SIGCHLD信号处理为SIG_IGN, 则父进程不会收到SIGCHLD信号, 子进程结束时, 会自动回收子进程的资源

signal(SIGCHLD, SIG_IGN);

效果:

子进程退出自动回收

无需:

wait()

15.4 示例

源代码

void test_3()
{
    signal(17, SIG_IGN);

    for(int i = 0; i < 10; i++)
    {
        pid_t id = fork();

        if(id == 0)
        {
            while(1)
            {
                cout<< "I am a child process: "
                    << getpid()
                    << endl;

                sleep(5);

                break;
            }

            cout << "child process quit !!!"
                 << endl;

            exit(0);
        }

        sleep(rand() % 5 + 1);
    }

    while(1)
    {
        cout<< "I am a parent process: "
            << getpid()
            << endl;

        sleep(1);
    }
}

16. Linux 信号核心知识总结


16.1 信号处理流程

产生
 ↓
pending
 ↓
block 判断
 ↓
递达
 ↓
handler

16.2 signal 与 sigaction

函数特点
signal简单
sigaction推荐使用

16.3 pending 与 block

名称含义
pending已收到未处理
block是否阻塞

16.4 常考问题

Q1:SIGKILL 能捕获吗?

不能。


Q2:SIGSTOP 能屏蔽吗?

不能。


Q3:信号会排队吗?

普通信号:

不会

实时信号:


Q4:handler 中能调用 printf 吗?

严格来说:

不安全

因为:

printf 不是异步信号安全函数

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

hehelm

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

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

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

打赏作者

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

抵扣说明:

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

余额充值