自学内容网 自学内容网

Linux——信号的补充

Linux——信号的创建、保存和处理-CSDN博客

文章目录


目录

文章目录

前言

一、可重写函数

二、volatile函数

三、 SIGCHLD信号

1、概念

2、信号处理函数

3、使用 sigaction 函数:

总结


前言

Linux——信号的创建、保存和处理-CSDN博客


一、可重写函数

这张图就是我们单链表的头插;

我们知道单链表在头插的时候,是先new一个p(图中的node1和node2)节点,然后然让node节点的next指向头节点,然后头节点指向node;

那么如果我们在头插入节点时,我们刚让节点p的next指向head然后就接收到了信号,然后信号信号中再插入一个新的节点2,那么我们跑完handler函数以后就是图3的效果,然后在返回就是图4的效果;

这种情况就是node2节点丢失,没有指针指向它,就会出现内存泄露;这种情况就是不可重写;

main函数调用insert函数向一个链表head中插入节点node1,插入操作分为两步,刚做完第一步的 时候,因 为硬件中断使进程切换到内核,再次回用户态之前检查到有信号待处理,于是切换 到sighandler函 数,sighandler也调用insert函数向同一个链表head中插入节点node2,插入操作的 两步都做完之后从 sighandler返回内核态,再次回到用户态就从main函数调用的insert函数中继续 往下执行,先前做第一步 之后被打断,现在继续做完第二步。结果是,main函数和sighandler先后 向链表中插入两个节点,而最后只 有一个节点真正插入链表中了。

像上例这样,insert函数被不同的控制流程调用,有可能在第一次调用还没返回时就再次进入该函数,这称 为重入,insert函数访问一个全局链表,有可能因为重入而造成错乱,像这样的函数称为 不可重入函数,反之, 如果一个函数只访问自己的局部变量或参数,则称为可重入(Reentrant) 函数。想一下,为什么两个不同的 控制流程调用同一个函数,访问它的同一个局部变量或参数就不会造成错乱?

 如果一个函数符合以下条件之一则是不可重入的:

调用了malloc或free,因为malloc也是用全局链表来管理堆的。

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

二、volatile函数

  1. 在 C 语言中,volatile是一个类型修饰符。它的主要作用是告诉编译器,被volatile修饰的变量是易变的,其值可能会在程序的控制流之外被改变。例如,一个变量可能被硬件(如中断服务例程)或者其他并发运行的线程修改。
  2. 编译器在进行优化时,通常会对代码进行一些假设。如果没有volatile关键字,编译器可能会认为一个变量在某个代码段中不会改变,从而对涉及该变量的代码进行优化。但是当变量的值可能被外部因素改变时,这种优化可能会导致错误的结果。

也就是说,被volatile函数修饰的变量不会被代码块外部的改变而改变;

代码

#include<iostream>
#include<signal.h>
#include<unistd.h>
using namespace std;
int x=0;
void handler(int signo)
{
    x+=2;
}
int main()
{
    signal(2,handler);
    while(!x)
    {
        cout<<getpid()<<"  x的值没有改变"<<endl;
        sleep(2);
    }
    cout<<"x的值被修改为"<<x<<endl;
    return 0;
}

 代码

#include <stdio.h>
#include <signal.h>
 
int flag = 0;
 
void handler(int sig)
{
 printf("chage flag 0 to 1\n");
 flag = 1;
}
 
int main()
{
 signal(2, handler);
 while(!flag);
 printf("process quit normal\n");
 return 0;
}

我们将这个代码分别用gcc $@ $^和gcc $@ $^ -o3编译

 我们会发现,没有加选项的发送型号会将flag的值改变,然后退出,而加了-o3选项的不会改变flag,从而死循环;

为了改变这个情况我们只需将flag用volatile修饰;

三、 SIGCHLD信号

1、概念

当一个子进程终止时,内核会向其父进程发送 SIGCHLD 信号,以便父进程可以采取相应的操作,例如回收子进程的资源(如释放内存、关闭文件描述符等)。

2、信号处理函数

  1. 为了处理 SIGCHLD 信号,父进程可以设置一个信号处理函数。在 C 语言中,使用 signal 或 sigaction 函数来设置信号处理函数。以下是使用 signal 函数的示例:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <signal.h>

// SIGCHLD 信号处理函数
void sigchld_handler(int signum) {
    if (signum == SIGCHLD) {
        // 等待子进程的状态信息
        int status;
        pid_t pid;
        // 等待任意一个子进程,并获取其 pid 和状态信息
        pid = waitpid(-1, &status, WNOHANG);
        if (pid > 0) {
            // 子进程正常终止
            if (WIFEXITED(status)) {
                printf("Child %d terminated normally with exit status %d\n", pid, WEXITSTATUS(status));
            }
            // 子进程异常终止
            else if (WIFSIGNALED(status)) {
                printf("Child %d terminated by signal %d\n", pid, WTERMSIG(status));
            }
        }
    }
}

int main() {
    // 注册 SIGCHLD 信号处理函数
    signal(SIGCHLD, sigchld_handler);

    // 创建子进程
    pid_t pid = fork();
    if (pid == 0) {
        // 子进程
        printf("Child process: %d\n", getpid());
        // 子进程执行一些操作
        sleep(2);
        exit(0);
    } else if (pid > 0) {
        // 父进程
        printf("Parent process: %d\n", getpid());
        // 父进程继续执行其他操作
        while (1) {
            sleep(1);
        }
    } else {
        perror("fork");
        exit(1);
    }

    return 0;
}

在这个示例中:

  • signal(SIGCHLD, sigchld_handler); 注册了 SIGCHLD 的信号处理函数 sigchld_handler
  • 在 sigchld_handler 函数中,使用 waitpid(-1, &status, WNOHANG) 来检查子进程的状态。
    • waitpid 函数用于等待子进程状态的改变。
    • -1 表示等待任意一个子进程。
    • &status 存储子进程的状态信息。
    • WNOHANG 表示如果没有子进程状态改变,不阻塞父进程,立即返回。

3、使用 sigaction 函数

  • 更推荐使用 sigaction 函数来设置信号处理函数,因为它提供了更多的功能和更好的可移植性。以下是使用 sigaction 的示例:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <signal.h>

// SIGCHLD 信号处理函数
void sigchld_handler(int signum) {
    if (signum == SIGCHLD) {
        int status;
        pid_t pid;
        // 等待任意一个子进程,并获取其 pid 和状态信息
        pid = waitpid(-1, &status, WNOHANG);
        if (pid > 0) {
            if (WIFEXITED(status)) {
                printf("Child %d terminated normally with exit status %d\n", pid, WEXITSTATUS(status));
            } else if (WIFSIGNALED(status)) {
                printf("Child %d terminated by signal %d\n", pid, WTERMSIG(status));
            }
        }
    }
}

int main() {
    struct sigaction sa;
    sa.sa_handler = sigchld_handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = SA_RESTART;
    // 注册 SIGCHLD 信号处理函数
    if (sigaction(SIGCHLD, &sa, NULL) == -1) {
        perror("sigaction");
        exit(1);
    }

    // 创建子进程
    pid_t pid = fork();
    if (pid == 0) {
        // 子进程
        printf("Child process: %d\n", getpid());
        sleep(2);
        exit(0);
    } else if (pid > 0) {
        // 父进程
        printf("Parent process: %d\n", getpid());
        while (1) {
            sleep(1);
        }
    } else {
        perror("fork");
        exit(1);
    }

    return 0;
}

在这个示例中:

  • struct sigaction sa; 定义了一个 sigaction 结构。
  • sa.sa_handler = sigchld_handler; 设置信号处理函数。
  • sigemptyset(&sa.sa_mask); 清空信号屏蔽字。
  • sa.sa_flags = SA_RESTART; 设置信号处理的标志,这里 SA_RESTART 表示某些系统调用在信号处理函数返回后可以自动重新启动。

 


总结

  • 信号是 Linux 系统中一种强大的进程间通信和进程控制机制。
  • 通过 kill 命令或 kill 函数发送信号,通过 signal 或 sigaction 函数处理信号。
  • 信号处理函数需要考虑可重入性和信号屏蔽字,避免使用不可重入函数。
  • 信号可以是同步或异步的,根据不同的来源和情况进行处理。


原文地址:https://blog.csdn.net/2301_80687320/article/details/145193288

免责声明:本站文章内容转载自网络资源,如侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!