漫话linux:信号的捕捉

#哪个编程工具让你的工作效率翻倍?#

1.sigaction

int sigaction(int signo, const struct sigaction *act, struct sigaction *oact);

参数分析:

        1.signo:指定信号的编号

        2.act输入型参数,根据act修改信号的处理动作

        3.oact输出型参数,通过oact传出该信号原来的处理动作

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

act和oact指向sigaction结构体

测试:对函数指针结构体的存入

 1 #include<iostream>
  2 #include<cstring>
  3 #include<unistd.h>
  4 #include<signal.h>
  5 using namespace std;
  6 void handler(int signo)
  7 {
  8     cout<<"catch a signal,signal number:"<<signo<<endl;
  9 }
 10 int main()
 11 {
 12     struct sigaction act,oact;
 13     memset(&act,0,sizeof(act));//初始化
 14     memset(&oact,0,sizeof(oact));
 15     act.sa_handler=handler;
 16     sigaction(2,&act,&oact);
 17     int sign=20;
 18     while (sign>0)
 19     {
 20         cout<<"i am a process:"<<getpid()<<endl;
 21         sleep(1);
 22         sign--;
 23         /* code */
 24     }                                                                                                               
 25     return 0;
 26 }

memset 常用于初始化变量、清空缓冲区或者设置特定模式的字节序列

问题1:pending位图,什么时候从1->0

执行信号捕捉方法之前,先清0,在调用,来使信号再次产生时,当前信号完成了再执行下一个,禁止不断嵌套式的捕捉

当某个信号的处理函数被调用时,内核自动将当前信号加入进程的信号屏蔽字,提交到block表中

//循环模拟实现
void handler(int signo)
{
    cout << "catch a signal, signal number : " << signo << endl;
    while (true)
    {
        PrintPending();
        sleep(1);
    }
}

问题2:信号被处理的时候,对应的信号也会被添加到block表中,防止信号捕捉被嵌套调用

正在处理某条信号时,会屏蔽该信号(以2举例),那么如何在同时屏蔽其他信号呢

    sigemptyset(&act.sa_mask);
    sigaddset(&act.sa_mask, 1);
    sigaddset(&act.sa_mask, 3);
    sigaddset(&act.sa_mask, 4);
    act.sa_handler = handler; // SIG_IGN SIG_DFL
    sigaction(2, &act, &oact);

如果 在调用信号处理函数时,除了当前信号被自动屏蔽之外,还希望自动屏蔽另外一些信号,则用sa_mask字段说明这些需 要额外屏蔽的信号,当信号处理函数返回时自动恢复原来的信号屏蔽字

2.可重入函数:可以被多个线程同时使用而不会导致崩溃,因此具有以下特性:

        1.不修改静态数据

        2.不调用不可调用的函数

        3.不返回指向静态数据的指针

为什么链表头插是不可重入的函数?举例分析

首先调用进程1,在哨兵位head后插入一个节点1,此时在另一个窗口调用进程2,往head后面插入一个节点2,此时head->next会在两个进程中指向不同的节点,但是head->next只能指向一个节点,所以进程2创建的节点会覆盖进程1创建的节点,导致程序出现问题,出现节点丢失

目前我们接触的函数基本上都是不可重入的函数,接下来我们看一下这类函数的标志位

        1.调用了malloc和free,因为malloc也是调用全局链表管理堆,而STL也不可以,涉及了很多指针的变换和扩容(但凡涉及到指针就要思考多进程的情况)

        2.调用了标准I/O库函数,很多实现都使用了不可重入的方式使用了全局数据结构

3.volatile

int flag = 0;
//volatile int flag=0;
 
void handler(int signo)
{
    cout << "catch a signal: " << signo << endl;
    flag = 1;
}
 
int main()
{
    signal(2, handler);
    // 在优化条件下, flag变量只在读取,可能被直接优化到CPU内的寄存器中
    while(!flag); // flag 0, !falg 真  逻辑运算
 
    cout << "process quit normal" << endl;
    return 0;
}

 

ctrl c使程序捕捉到信号2,再ctrl z结束程序 ,$^后面跟上-O(这是哦)和0,1,2,3表示优化等级

在优化条件下, flag变量只在读取,可能被直接优化到CPU内的寄存器中

while(!flag); // flag 0, !falg 真 逻辑运算

看看豆包的分析(谁言豆族无大帝):

这个程序的主要目的是在一个无限循环中等待一个信号(在这个例子中是信号2,即SIGINT的别名,但通常用于SIGINT的是信号编号1,这里可能是一个示例的简化或错误)。当接收到信号时,信号处理函数handler会被调用,它将全局变量flag设置为1,从而理论上应该使主循环结束,并输出“process quit normal”。

然而,这个程序在优化级别较高(如-O2-O3)的编译器设置下可能会遇到问题。问题的根源在于flag变量的访问。在高度优化的编译模式下,编译器可能会认为flag变量在循环体内没有发生变化(因为它只在信号处理函数中修改,而信号处理函数是异步的),因此可能会将flag的值优化到寄存器中,或者直接认为循环条件始终为真,从而生成一个无限循环的代码

因为优化,导致我们的内存不可见了!

这时添加 volatile 关键字:防止编译器过度优化,保持内存的可见性!

volatile int flag=0;

4.SIGCHLD信号

子进程退出后会向父进程发送SIGCHLD信号(17)号信号

基于信号捕捉,对信号进行回收,将17号信号的捕捉放入header中

 #include<iostream>
  2 #include<unistd.h>
  3 #include<signal.h>
  4 #include <sys/wait.h>
  5 using  namespace std;
  6 void handler(int signo)
  7 {
  8     pid_t rid;
  9    rid = waitpid(-1, nullptr, WNOHANG);//实现非阻塞调用
 10     cout << "I am proccess: " << getpid() << " catch a signo: " << signo << "child process quit: " << rid << endl;
 11 }
 12  
 13 int main()
 14 {
 15     signal(17,handler);
 16     for (int i = 0; i < 10; i++)
 17     {
 18         pid_t id = fork();
 19         if (id == 0)
 20         {
 21             while (true)                                                                                            
 22             {
 23                 cout << "I am child process: " << getpid() << ", ppid: " << getppid() << endl;
 24                 sleep(5);
 25                 break;
 26             }
            cout << "child quit!!!" << endl;                                                                        
 28             exit(0);
 29         }
 30         sleep(1);
 31     }
 32     // father
 33     while (true)
 34     {
 35         cout << "I am father process: " << getpid() << endl;
 36         sleep(1);
 37     }
 38  
 39     return 0;
 40 }
~

子进程进行等待的时候,可以基于信号进行等待

等待的好处:

        1.获取子进程的退出状态,释放僵尸进程 

        2.在一般情况下,父进程一般比子进程后退出

还是要调用 wait 接口,father 必须保证自己是一致在运行的-->把子进程等待写入信号捕捉函数中

多个子进程如何正确的回收:while循环加非阻塞方案

void handler(int signo)
{
    sleep(5);
    pid_t rid;
    while ((rid = waitpid(-1, nullptr, WNOHANG)) > 0)//非阻塞方案
    {
        cout << "I am proccess: " << getpid() << " catch a signo: " << signo << "child process quit: " << rid << endl;
    }
}

随机休眠时长进行测试:

#include <cstdlib> // 用于rand()和srand()
#include <ctime>   // 用于time()
srand(time(nullptr));      // 初始化随机数生成器
if (id == 0)
        {
            int random_sleep_time = rand() % 10 + 1; // 生成1到10之间的随机数
            cout << "Child process " << getpid() << " will sleep for " << random_sleep_time << " seconds." << endl;
            sleep(random_sleep_time);
            cout << "Child process " << getpid() << " is quitting." << endl;
            exit(0);
        }

子进程在终止时会给父进程发SIGCHLD信号,该信号的默认处理动作是忽略( SIG_DFL -> action -> IGN)事实上,由于UNIX 的历史原因,要想不产生僵尸进程还有另外一种办法:父进程调用sigaction将SIGCHLD的处理动作 置为SIG_IGN,这样fork出来的子进程在终止时会自动清理掉 signal(17,SIG_IGN);

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值