linux进程信号

发布时间:2023年12月19日

信号

Linux信号是一种更高层的软件形式的异常,它允许进程和内核中断其他进程。

一个信号就是一条小消息,它由内核发出,通知进程系统中发生了一个某种类型的事件。Linux系统支持30种不同类型的信号,信号类型用小整数ID来标识,每种信号类型都对应某种系统事件。

其中,信号SIGKILL(序号为9)、SIGSTOP(序号为19)不可忽略、阻塞、捕获。

信号在内核中的表示如下图所示。pending位向量中维护着待处理信号的集合;blocked位向量中维护着被阻塞地信号集合。

信号的发送

通过终端按键来产生信号

? ? ? ? 比如上面的ctrl + c 就给进程发送了2号信号SIGINT。而ctrl + \可以给进程发送3号信号SIGQUIT。

? ? ? ? 通过按键组合的方式来给进程发送信号。

通过系统函数来向进程发送信号

kill

kill 命令是 Linux 中最常用的发送信号的命令

除了使用 kill 命令,程序中也可以通过 kill 函数来发送信号。

#include<iostream>
#include<unistd.h>
#include<signal.h>
#include<cstdio>
#include<cstring>
#include<string>
 
static void Usage(const std::string &proc){
    std::cout<<"Usage:"<<proc<<"pid signo\n"<<std::endl;
}
 
int main(int argc,char *argv[]){
    if(argc!=3){
        Usage(argv[0]);
        exit(1);
    }
    pid_t pid=atoi(argv[1]);
    int signo=atoi(argv[2]);
    int n=kill(pid,signo);
    if(n!=0)
        perror("kill");
 
    while(true){
        std::cout<<"hello"<<std::endl;
        sleep(1);
    }
}
  • ?raise函数

  • 作用:给当前进程发送信号

代码演示

#include<cstdio>
#include<iostream>
#include<cstring>
#include<signal.h>
#include<unistd.h>

void Usage(const char* proc){
    std::cout<<"Usage:"<<proc<<" pid signo"<<std::endl;
}

int main(int argc,char *argv[]){
    int cnt=0;
    while(cnt<=10){
        printf("cnt:%d\n",cnt++);
        sleep(1);
        if(cnt>=5)
            raise(3);
    }
    return 0;
}

  • ?abort函数,作用:使当前进程收到6号信号,后异常终止。

硬件异常产生信号

?????硬件异常产生信号就是硬件发现进程的某种异常,而硬件是被操作系统管理。硬件会将异常通知给系统,系统就会向当前进程发送适当的信号。

除0错误理解

野指针的情况

???原因:由于p是野指针,p指针变量里保存的是随机值,进程执行到野指针这一行。进程在页表中找映射的物理内存时,硬件mmu会发现该虚拟地址是一个?野指针,会产生异常,由于操作系统管理硬件,硬件会将异常发送给系统。系统会发送适当的信号给当前进程

?由软件条件产生信号

? ? ? ? 在匿名管道中,当读进程关闭时,写进程会收到系统发来的13号信号,终止写进程。系统发给写进程的13号信号就是软件条件生成的信号。

这里也有一个函数alarm,相当于设置一个闹钟,告诉内核多少秒后,发送一个SIGALRM信号给当前进程。

这里有一个现象:

同样将count++,1秒后发送SIGALARM信号给进程,同样时间:上面count才加到21095,下面加到了491153364,差了1000倍。

这是因为上面的代码要不断往屏幕打印,屏幕是外设,在不断进行I/O,时间消耗多。

核心转储

core要核心存储 term直接终止

核心转储(Core Dump)是操作系统在程序发生严重错误(如访问违规或段错误)时生成的一种文件。它记录了程序崩溃时刻的内存映像和程序执行状态,包括程序计数器和寄存器的内容、堆栈信息、正在使用的内存段等。核心转储通常用于随后的调试和分析,以确定程序崩溃的原因。

核心转储的特点:

  1. 详细信息

    • 核心转储包含了足够的信息,以便开发者或系统管理员能够查看程序崩溃时的完整状态,这对于诊断软件错误非常有帮助。
  2. 调试用途

    • 通过使用调试器(如 GDB)来分析核心转储文件,可以查看崩溃时的堆栈跟踪、局部变量值和其他有用信息。
  3. 生成时机

    • 通常在程序由于未捕获的异常、段错误、违反内存访问规则等原因崩溃时产生。
  4. 文件大小

    • 核心转储文件可能很大,因为它们包含了进程的完整内存映像。

阻塞和解除阻塞信号

实际执行的信号的处理动作称为信号递达
信号从产生到递达之间的状态称为信号未决
进程可以选择阻塞某个信号(Block)
被阻塞的信号产生时将保持在未决状态,知道进程解除对此信号的阻塞,才执行递达动作
阻塞和忽略是不同的,只要信号被阻塞就不会被递达,而忽略是在递达之后可选的一种处理动作
忽略:本质是处理信号的一种方式
阻塞:本质是不让信号递达,直到解除阻塞


sigpromask

如果oset是非空指针,则读取进程的当前信号屏蔽字通过oset参数传出。如果set是非空指针,更改进程的信号屏蔽字,参数how指示如何更改。如果oset和set都是非空指针,则将原来的信号屏蔽字备份到oset里,然后根据set和how参数更改信号屏蔽字。假设当前信号屏蔽字为mask,下图为how参数的可选值

sigpending 函数用于检查当前进程的信号挂起集(pending signal set)。这个挂起集包含了那些已经发送给进程但尚未被处理的信号,通常是因为这些信号当前被阻塞了。

sigemptyset初始化set所指向的信号集,使其中所有信号对应的bit清0,表示该信号集不包含任何有效信号。
sigfillset初始化set所指向的信号集,使其中所有信号对应的bit置位,表示该信号集的有效信号包括系统支持的所有信号。


注意:在使用sigset_t类型的变量之前,一定要调用sigemptyset或signfillset做初始化,使信号集处于确定的状态。初始化sigset_t信号变量之后就可以在调用sigaddset和sigdelset在信号集中添加或删除某种有效信号
这四个函数成功返回0,错误返回-1,

sigismember是布尔函数,用于判断一个信号集的有效信号中是否包含某种信号,包含返回1,不包含返回0,出错返回-1.


sigaddset

#include<cstdio>
#include<iostream>
#include<cstring>
#include<signal.h>
#include<unistd.h>

const int max_signum=31;

static void show_pending(const sigset_t &pending){
    for(int signo=1;signo<=max_signum;signo++){
        if(sigismember(&pending,signo)){
            std::cout<<"1";
        }
        else{
            std::cout<<"0";
        }
    }
    std::cout<<"\n";
}

void Usage(const char* proc){
    std::cout<<"Usage:"<<proc<<" pid signo"<<std::endl;
}

int main(int argc,char *argv[]){
    sigset_t block,oblock,pending;
    //1.初始化
    sigemptyset(&block);
    sigemptyset(&oblock);
    sigemptyset(&pending);
    //2.添加屏蔽信号
    sigaddset(&block,SIGINT);
    //3.开始屏蔽设置进内核
    sigprocmask(SIG_SETMASK,&block,&oblock);
    //遍历打印pending信号集
    while(1){
        //获取
        sigpending(&pending);
        //打印
        show_pending(pending);
    }

 
    return 0;
}

sigaction

代码演示

#include<cstdio>
#include<iostream>
#include<cstring>
#include<signal.h>
#include<unistd.h>

void handler(int signo){
    std::cout<<"get a signo"<<signo<<std::endl;
}

int main(int argc,char *argv[]){
    struct sigaction act,oact;
    act.sa_flags=0;
    act.sa_handler=handler;
    sigemptyset(&act.sa_mask);
    sigaction(SIGINT,&act,&oact);
    while(true){
        sleep(1);
    }
    return 0;
}

当某个信号的处理函数被调用时,内核自动将当前信号加入信号屏蔽字,当信号处理函数返回时自动恢复原来的信号屏蔽字,这样就保证了在处理某个信号时,如果这种信号再次产生,那么它会被阻塞到当前处理结束为止。如果在调用信号处理函数时,除了当前信号被自动屏蔽之外,还希望自动屏蔽另一些信号,则用sa_mask字段说明这些需要额外的屏蔽信号,当信号处理函数返回时自动恢复原来的信号屏蔽字。sa_flags字段包含一些选项,一般设置为0;sa_sigation是实时信号的处理函数。

可重入函数

一个进程可能有多个执行流。比如说信号。当自定义捕捉信号函数,当信号没来时,信号不会执行处理信号函数,只会执行自己的代码。当信号来了,会去执行处理信号的函数。

? ? ? ? 可重入函数就是,当一个执行流进入一个函数,当中又进入了这个函数,不会出现错误的函数。

? ? ? ? 不可重入函数就是,当一个执行流进入一个函数,当中又进入了这个函数,会出现错误的函数



volatile

作用:保持内存可见性,告知编译器,被该关键字修饰的变量,不允许被优化,对改变量的任何操作,都必须在真实的内存中进行操作。(解决因编译器优化导致不正确执行)

SIGCHLD

父进程可以阻塞等待子进程结束,也可以非阻塞的查询是否有子进程结束等待清理(轮询)。采用第一种方式,父进程阻塞就不能处理自己的工作了;采用第二种方式,父进程在子进程处理自己的工作的同时还要记得时不时轮询,程序实现复杂。
其实,子进程在终止时会给父进程发SIGCHLDA信号,该信号的默认处理动作是忽略,父进程可以自定义SIGCHLD信号的处理函数,这样父进程只需专心处理自己的工作,不必关系子进程,而子进程终止时会通知父进程,父进程在信号处理函数中调用wait清理子进程即可
不产生僵尸进程的另一种方法:父进程调用sigaction将SIGCHLD的处理动作设置为SIG_IGN,这样fork出来的子进程在终止时会自动清理,不会产生僵尸进程也不会通知父进程。系统,偶然的忽略动作和用户用sigaction函数自定义的忽略通常是没有区别的

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <signal.h>

void sigchld_handler(int sig) {
    int status;
    pid_t pid;

    // waitpid() 必须用 WNOHANG 做为参数,确保不会阻塞
    while ((pid = waitpid((pid_t)(-1), &status, WNOHANG)) > 0) {
        printf("Child with PID %ld exited.\n", (long)pid);
    }
}

int main() {
    signal(SIGCHLD, sigchld_handler);

    pid_t pid = fork();
    if (pid == 0) { // 子进程
        // 子进程做一些工作,然后退出
        printf("Child process working...\n");
        sleep(2);
        printf("Child process done.\n");
        exit(0);
    } else if (pid > 0) { // 父进程
        // 父进程继续做其他工作
        for (int i = 0; i < 5; ++i) {
            printf("Parent working...\n");
            sleep(1);
        }
    } else {
        // fork 失败
        perror("fork");
        exit(1);
    }

    return 0;
}


?

文章来源:https://blog.csdn.net/b1169909203/article/details/135058588
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。