波奇学Linux: 信号捕捉

本文详细探讨了C++中的信号处理机制,包括如何修改信号对应的handler函数,信号pending位图的工作原理,可重入函数与volatile关键字的应用,以及基于信号的进程回收策略。通过实例分析了信号处理流程和可能遇到的问题,如内存泄漏和僵尸进程的管理。
摘要由CSDN通过智能技术生成

sigaction:修改信号对应的handler方法

act输入型参数,oldact输出型参数

void (*sa_handler) (int) //修改的自定义函数

sigset_t sa_mask // 

void handler(int signo)
{
    cout<<"catch a signal, signal number: "<<signo<<endl;
}
int main()
{
    // 创建结构体变量
    struct sigaction act,oact;
    //清空
    memset(&act,0,sizeof(act));
    memset(&oact,0,sizeof(oact));
    //设置自定义方法
    act.sa_handler=handler;
    sigaction(2,&act,&oact);
    while(true)
    {
        cout<<" I am a process: "<<getpid()<<endl;
        sleep(1);
    }
    return 0;

}

信号递达时,pending位图什么时候变成0的,是在调用函数前还是调用函数后

void PrintPending()
{
    sigset_t set;
    //获得pending位图
    sigpending(&set);
    for(int signo=1;signo<=31;signo++)
    {
        if(sigismember(&set,signo)) cout<<"1";
        else cout<<" 0 ";
    }
    cout<<"\n";
}
void handler(int signo)
{
    PrintPending();
    cout<<"catch a signal, signal number: "<<signo<<endl;
}

在执行信号捕捉方法之前先清零再调用

当某个信号的处理函数被调用时,内核自动将当前信号加入进程的信号屏蔽字,当信号处理函数返回时,才恢复原来的信号屏蔽。通俗点就是调用处理函数时,block表的值为1,屏蔽相同信号的递达。

void handler(int signo)
{
    cout<<"function start";
    //一直在调用自定义函数
    while(true)
    {//PrintPending();
    cout<<"catch a signal, signal number: "<<signo<<endl;
    }
}

没有function start只打印了一次

sigset_t 信号屏蔽:调用自定义函数时同时屏蔽其他信号

//初始化
sigemptyset(&act.sa_mask);
//屏蔽信号1,2,3,4
sigemptyset(&act.sa_mask,1);
sigemptyset(&act.sa_mask,2);
sigemptyset(&act.sa_mask,3);
sigemptyset(&act.sa_mask,4);

可重入函数的问题

main执行流进入某个函数时,接收到信号调用同一个函数,此时函数被进入了两次。

当insert的函数时执行到head=p时,又调用了sighandler函数p->next=head代码。

当执行完新调用的代码后才会执行head=p。

可能导致内存泄漏问题

如果一个函数被重复进入出错了,或者可能出错,不可重入函数,否则叫可重入函数。main函数和signal hander函数也有自己的栈帧。

volatile 关键字:保证从内存而不是从寄存器中获取变量

int flag=0;
void handler(int signo)
{
    cout<<"catch a signal: "<<signo<<endl;
    flag=1;
}
int main()
{
    signal(2,handler);
    while(!flag);
    cout<<"process quit normal"<<endl;
    return 0;
}

尽管调用了handler函数,但是程序依然死循环

main和handler是不同的栈帧

cpu扫描main栈帧发现flag没有被修改,直接放在寄存器中,且从寄存器中读取,不再每次从内存中读取。

读取变量到寄存器,进行逻辑检测

而hander执行流只能修改内存中的flag值,而cpu不会从内存中读取。

volatile int flag=0 // 防止编译器过渡优化,保存内存可见性!

基于信号捕捉来进行进程回收

子进程在退出的时候,会主动向父进程发送信号17

子进程在进行等待的时候,我们可以采用基于信号的方式等待 

进程等待的好处:

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

虽然不知道父子谁运行,但是父进程一定是最后退出的

void handler(int signo)
{
    pid_t rid=waitpid(-1,nullptr,0); //接收17信号,回收进程
    cout<<"I am process: "<<getpid()<<"catch a signo: "<<signo<<endl;
}
int main()
{
    signal(17,handler);
    pid_t id=fork();
    if(id==0)
    {
        while(true)
        {
            cout<<"I am child process: "<<getpid()<<" , ppid: "<<getppid()<<endl;
            sleep(1);
            break;
        }
        exit(0);
    }

    while(true)
    {
        cout<<" I am father process: "<<getpid()<<endl;
        sleep(1);
    }
}

当有多个子进程利用信号回收

void handler(int signo)
{
    sleep(5);
    pid_t rid;
    // rid的值为0 说明进程全部回收,rid的值大于0为回收进程的pid
    // 轮询回收,防止被某个进程回收卡住
    while((rid==waitpid(-1,nullptr,WNOHANG))>0){
    cout<<"I am process: "<<getpid()<<"catch a signo: "<<signo<<endl;
    }
}

不产生僵尸进程,将sigaction将SIGCHLD的处理动作设置为SIG_IGN或者将signal(17,SIG_IGN)

显性忽略信号17,子进程将不产生僵尸进程

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值