[linux]详解linux进程信号(代码片段)

哦哦呵呵 哦哦呵呵     2022-12-07     677

关键词:

1. 信号概念

  信号就是程序要执行某件事情之前发送的信号,通知进程要做什么事情,是一个软件中断,信号是进程之间事件异步通知的一种方式。
  下面用一个具体的例子解释信号。在日常生活中有非常多种的信号,比如说在过马路时,红灯不通行,绿灯通行。这就是一个发送给行人与车辆的信号。
  在linux种,写了一个程序,但是这个程序因为某种原因,没有办法正常结束了,可以通过 ctrl+c 强制的结束掉这个进程。在这个过程中,使用ctrl+c产生了一个硬件的中断,给进程发送了一个结束的信号,进程接收到这个信号并且立即处理这个信号,结束进程。

2. 信号的种类

在linux中,共有62种信号可以通过 kill -l命令进行查看.
在这里插入图片描述
1. 非可靠信号(非实时信号)
在信号分类中,1-31号信号被称为非可靠信号,即受到上述信号后,进程不会立即处理该信号,信号有可能会丢失

2. 可靠信号(实时信号)
34-64号信号被称为可靠信号,信号绝对不会丢失

3. 信号的处理流程

在这里插入图片描述

  • 在信号处理信号时,当处理完某些功能,进入内核态后,处理完异常之后,一定会调用do_signal函数,在函数中会检测当前有没有要处理的信号,如果有则判断是否执行默认处理,还是自定义的处理方式,处理完成后才会返回用户态,如果没有则直接返回用户态。
  • 执行流从内核态切换到用户态之前一定会调用do_signal函数处理信号
  • 从用户态切换到内核态时,是调用了系统调用函数,或者进程异常

4. 信号的产生方式

4.1 软件产生

1. 命令产生
kill命令: 可以通过kill -num的方式向进程中发送指定的信号.
在这里插入图片描述
在这里插入图片描述

2. 函数产生

int kill(pid_t pid, int sig);
// 参数: pid要发送信号的进程pid
//      sig发送信号的序号
/
int raise(int sig);
// 作用:向自身发送信号,内部封装了kill函数
/
unsigned int alarm(unsigned int seconds);
// 作用:参数传递一个秒数,在事件到达之后发送一个SIGALRM信号

代码测试

#include <signal.h>    
#include <stdio.h>    
#include <unistd.h>    
    
int main()    
    
    //kill(getpid(), 2);                                                                     
    raise(2);              
                           
    // 如果没有打印这一句 说明上述语句没有发送成功    
    printf("hello");       
                           
    return 0;              

在这里插入图片描述

4.2 硬件产生

ctrl+c: 发送的为2号信号即SIGINT
ctrl+z: 发送的为3号信号即SIGQUIT
ctrl+|: 发送的为20号信号即SIGSTP
在这里插入图片描述

5. 信号的注册

  信号的注册是通过改变signal位图中的比特位进行信号的注册。

在这里插入图片描述

linux中信号的种类共有62个,但用于存储信号的变量只有2个long的长度,共128个字节,如果一个变量存储一个是远远不够的,所以采用 位图 的方式进行存储信号,数组中对应的每一位存储一个信号,有信号则置为1,无则保持为0。并且数组的每一位没有全部使用,含有预留位置。

并且signalpending结构体中含有一个sigqueue队列,信号接收到进行注册时,需要在sigqueue队列中添加结点

  • 非可靠信号的注册
    第一次: 将信号对应的比特位置为1,在sigqueue队列中添加sigqueue结点
    第二次: 只会将信号对应的比特位从1改为1,不会添加sigqueue结点
  • 可靠信号的注册
    第一次: 将信号对应的比特位置为1,并且在sigqueue队列中添加sigqueue结点
    第二次: 将信号对应的比特位从1改为1,并且还会在sigqueue队列中添加sigqueue结点

6. 信号的注销

1. 非可靠信号的注销
  将信号对应在signal位图中的比特位置为0,并且将sigqueue结点从sigqueue队列当中进行出队操作。

2. 可靠信号的注销
  1.将可靠信号对应的sigqueue节点从sigqueue队列中进行出队操作。
  2.判断sigqueue队列当中是否还有当前可靠信号的sigqueue节点。如果没有,则将signal位图当中可靠信号对应的比特位置为0。如果有,则不会将signal位图当中的可靠信号对应的比特位置为0。

7. 信号的处理方式

7.1 信号的处理方式及基本概念

默认处理方式:SIGDFL,在操作系统内核有对该信号的默认处理动作。
忽略处理方式:SIGCHILD,子进程在退出时会给父进程发送一个SIGCHILD信号,但是父进程不关心这个信号
自定义处理方式:更改信号的处理方式。

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

7.2 信号自定义处理方式

1. 接口函数

函数一

sighandler_t signal(int signum, sighandler_t handler);
// 作用:更改信号的处理方式
// 参数: signum 待自定义处理函数的信号
//      handler: 函数指针,接收一个函数地址,该函数的返回值是void
//               参数为int,当进程受到某个信号时,就会触发调用该函数,
//               并将该信号的值,传递给回调函数
#include <stdio.h>                                                                           
#include <unistd.h>    
#include <signal.h>    
    
void sigcb(int signo)    
    
    printf("signo: %d\\n", signo);    
    
                      
int main()                                                  
          
	// 当收到一下三种信号时,会调用回调函数输出进程号                                                         
    signal(SIGINT, sigcb);    
    signal(SIGTSTP, sigcb);                          
    signal(SIGQUIT, sigcb);             
                             
    while(1)                                
        
        sleep(1);                                                      
                                                                    
                                                              
    return 0;    
 

在这里插入图片描述

函数二

int sigaction(int signum, const struct sigaction *act,
              struct sigaction *oldact);
// 参数: signum 待自定义处理信号
//       act 想要将信号更改为什么处理动作,是一个结构体变量:
// 			参数1: void(*sa_handler)(int); 默认保存信号处理函数的函数指针
//		 	参数2: void(*sa_sigaction)(int, siginfo_t*,void*)
//			参数3: int sa_flags; 与上一个参数连用,一般不使用
//			参数4: sigset_t sa_mask 当正在处理信号时,会将注册的信号先放入该变量中过度,
//				当处理完该信号后,会将刚才注册的信号放入signal位图中
//			参数5: void(*sa_restorer)(void); 预留信息
//		oldact:信号之前的处理动作

#include <stdio.h>                                                                                                                                                       
#include <signal.h>    
#include <unistd.h>    
    
void sigcb(int signo)    
    
    printf("signo:%d\\n", signo);    
    
    
int main()    
    
    struct sigaction act;    
    act.sa_handler = sigcb;    
    act.sa_flags = 0;    
    sigemptyset(&act.sa_mask); // 将mask中的比特位全部置为0 
    
    // 拦截SIGINT信号
    sigaction(SIGINT, &act, NULL);    
    
    while (1)    
        
        sleep(1);    
        
    
    return 0;    
 

在这里插入图片描述

8. 信号的阻塞

  当准备处理信号时,会判断当前信号是否为阻塞,如果该信号为阻塞,则暂时不会处理该信号。

8.1 block位图

  该位图的结构与signal位图的类型相同,处理信号之前,会先查看block位图当中对应信号为的位图值是否为1,如果为1,则暂时不会去处理该信号。

8.2 接口函数设置阻塞状态

int sigprocmask(int how, const sigset_t* set, sigset_t* oldset);
// 作用:设置信号为阻塞状态
// 参数:
//	  how: 告诉sigprocmask函数以什么方式进行工作
//		  SIG_BLOCK: 设置某个信号为阻塞状态 block(new) = block(old) | set
//		  SIG_UNBLOCK: 解除信号为阻塞状态 block(new) = block(old) (~set)
//		  SIG_SETMASK: 替换原来的block位图 block(new) = set
//    set: 要修改新的位图
//    oldset: 在没有更改之前老的block位图

代码验证可靠信号与非可靠信号的阻塞

#include <signal.h>    
#include <stdio.h>    
#include <unistd.h>    
    
void signalcallback(int signo)    
    
    printf("signo: %d\\n", signo);    
    
    
int main()    

    // 更改2 40号信号的处理方式
    // 2: 非可靠信号
    // 40: 可靠信号
    signal(2, signalcallback);                                                                                                                                           
    signal(40, signalcallback);
                     
    // 阻塞所有信号       
    sigset_t set;                                  
    sigset_t oldblock;                         
    sigfillset(&set); // 将set中所有比特位置为1   
                                              
    sigprocmask(SIG_SETMASK, &set, &oldblock);
              
  	// 程序运行后,不输入内容会发生阻塞  
    getchar();                                    
                                              
    sigprocmask(SIG_SETMASK, &oldblock, NULL);
             
    return 0;

启动两个窗口,一个窗口进行进程阻塞时的等待,另外一个窗口发送信号。
在这里插入图片描述

在这里插入图片描述
现象总结

  • 信号的阻塞并不会干扰信号的注册
  • 可靠信号受到几次处理几次,非可靠信号收到多次只处理一次,非可靠信号可能会丢失
  • 先处理可靠信号,最后处理非可靠信号,按照队列出队的顺序进行处理
  • 4、9、19号信号不能被阻塞,保证程序退出的条件

9. volatile关键字

先看一段代码

#include <stdio.h>                                                                           
#include <unistd.h>
#include <signal.h>

int g_v = 1;

void signalcallback(int signo)

    g_v = 0;
    printf("signo: %d\\n", signo);


int main()

    signal(2, signalcallback);

    while(g_v)                                                                              
                                                                                           
                                                                                            
                                                                                           
                                                                                            
    return 0;                                                                               
 

在这段代码中,我们阻塞了2号信号,在回调函数内部,修改了g_v这个全局变量的值,main函数中的while循环依赖与这个值的运行去运行,当发送二号信号之后,在回调函数内部,修改g_v之后,程序应该会停止运行

在这里插入图片描述

上图中可以看出,虽然进入了回调函数,并且修改了全局变量的值,但并没有跳出循环。
因为该程序的优化等级较高,效率较快,所以程序会从寄存器中将值取出,我们更改的值存在内存中,内存中的值发生了更改但是寄存器中的值没有发生更改,所以程序没有结束。
如果在全局变量前加上volatile关键字,就会禁止程序从寄存器中取值,保持内存的可见性。

#include <stdio.h>    
#include <unistd.h>    
#include <signal.h>    
    
// 进程程序在寄存器中取值,而是从内存中取值,保持内存可见性                                  
volatile int g_v = 1;                       
                                            
void signalcallback(int signo)              
                                           
    g_v = 0;                                
    printf("signo: %d\\n", signo);           
                                           
                                            
int main()                                  
                                           
    signal(2, signalcallback);              
                                                                                            
    while(g_v)                                                                              
                                                                                           
                                                                                            
                                                                                           
                                                                                            
    return 0;                                                                               
 

在这里插入图片描述

万字详解linux系列进程信号(代码片段)

文章目录一、信号简介1.查看信号2.信号的本质3.信号的记录和发送4.从键盘输入的信号5.signal自定义信号6.处理信号的一般方式二、信号产生1.通过终端按键(键盘)产生信号CoreDump(核心转储)2.程序异常事后调试3.调用函数&... 查看详情

深入详解linux进程间通信之共享内存(sharedmemory)+信号量同步(代码片段)

在Linux下的多个进程间的通信机制叫做IPC(Inter-ProcessCommunication),它是多个进程之间相互沟通的一种方法。专栏前面的文章中演示过使用sharedmemory进行IPC的方法。本文将更进一步探讨Linux进程间通过共享内容进行通信的方法,包... 查看详情

深入详解linux进程间通信之共享内存(sharedmemory)+信号量同步(代码片段)

在Linux下的多个进程间的通信机制叫做IPC(Inter-ProcessCommunication),它是多个进程之间相互沟通的一种方法。专栏前面的文章中演示过使用sharedmemory进行IPC的方法。本文将更进一步探讨Linux进程间通过共享内容进行通信的方法,包... 查看详情

linux进程信号万字详解(上)(代码片段)

🎇Linux:博客主页:一起去看日落吗分享博主的在Linux中学习到的知识和遇到的问题博主的能力有限,出现错误希望大家不吝赐教分享给大家一句我很喜欢的话:看似不起波澜的日复一日,一定会在某一天... 查看详情

linux僵尸进程详解(代码片段)

文章目录僵尸进程概念僵尸进程问题及危害实现一个僵尸进程僵尸进程处理僵尸进程的预防(1)通过信号机制(2)Fork两次僵尸进程概念在unix/linux中,正常情况下,子进程是通过父进程创建的。子进程的... 查看详情

linux僵尸进程详解(代码片段)

文章目录僵尸进程概念僵尸进程问题及危害实现一个僵尸进程僵尸进程处理僵尸进程的预防(1)通过信号机制(2)Fork两次僵尸进程概念在unix/linux中,正常情况下,子进程是通过父进程创建的。子进程的... 查看详情

linux中ps命令详解(代码片段)

文地址:http://blog.csdn.net/x_i_y_u_e/article/details/38708481 linux上进程有5种状态:  1.运行(正在运行或在运行队列中等待)2.中断(休眠中,受阻,在等待某个条件的形成或接受到信号)3.不可中断(收到信号不唤醒和不可运行,进程必须等待... 查看详情

linux八linux进程信号详解(代码片段)

目录一、认识信号1.1生活中的信号1.2将1.1的概念迁移到进程1.3 信号概念1.4查看系统定义信号列表1.5 man7signal1.6解释1.2的代码样例1.7信号处理常见方式概览二、产生信号2.1signal函数2.2 通过终端按键产生信号2.3 调用系统函数向进... 查看详情

linux进程间通信--消息队列相关函数(ftok)详解(代码片段)

ftok消息队列、信号灯、共享内存常用在Linux服务端编程的进程间通信环境中。而此三类编程函数在实际项目中都是用SystemVIPC函数实现的。SystemVIPC函数名称和说明如下表15-1所示。表15-1SystemVIPC函数 消息队列信号灯共享内存区头... 查看详情

深入详解linux进程间通信之共享内存(sharedmemory)+信号量同步(代码片段)

在Linux下的多个进程间的通信机制叫做IPC(Inter-ProcessCommunication),它是多个进程之间相互沟通的一种方法。专栏前面的文章中演示过使用sharedmemory进行IPC的方法。本文将更进一步探讨Linux进程间通过共享内容进行通信的方法,包... 查看详情

linux进程退出详解(do_exit)--linux进程的管理与调度(十四)(代码片段)

Linux进程的退出linux下进程退出的方式正常退出从main函数返回return调用exit调用_exit异常退出调用abort由信号终止_exit,exit和_Exit的区别和联系_exit是linux系统调用,关闭所有文件描述符,然后退出进程。exit是c语言的库函数,他最终... 查看详情

linux系列signal函数详解(代码片段)

Date:2023.1.18文章目录1、介绍2、如何安装多个处理函数3、信号列表转载自:http://imhuchao.com/2300.htmlsignal作用是为信号注册一个处理器。这里的“信号”是软中断信号,这种信号来源主要有三种:程序错误:比如除0&#... 查看详情

linux进程信号量allinone(代码片段)

Linux进程信号量AllInOneprocesssignalLinux进程信号量AllInOne进程信号量processsignaldemos( 查看详情

linux进程信号(代码片段)

让一路花香满径,让紫日安暖自若。linux进程信号什么是信号从生活中角度进程间信号信号概念信号列表信号的产生通过键盘产生信号CoreDump(为了定位错误)通过系统调用函数向进程发信号由软件条件来产生信号硬件异常产生... 查看详情

[os-linux]详解linux的进程间通信2------systemv共享内存(sharedmemory)(代码片段)

本文详解了通过共享内存进行进程间通信的方法,并对消息队列,信号量做了简单介绍。另一种进程间通信--管道,见前文:[OS-Linux]详解Linux的进程间通信1------管道_RMA515T的博客-CSDN博客管道通信本质是基于文件&#x... 查看详情

[os-linux]详解linux的进程间通信2------systemv共享内存(sharedmemory)(代码片段)

本文详解了通过共享内存进行进程间通信的方法,并对消息队列,信号量做了简单介绍。另一种进程间通信--管道,见前文:[OS-Linux]详解Linux的进程间通信1------管道_RMA515T的博客-CSDN博客管道通信本质是基于文件&#x... 查看详情

[os-linux]详解linux的进程间通信2------systemv共享内存(sharedmemory)(代码片段)

本文详解了通过共享内存进行进程间通信的方法,并对消息队列,信号量做了简单介绍。另一种进程间通信--管道,见前文:[OS-Linux]详解Linux的进程间通信1------管道_RMA515T的博客-CSDN博客管道通信本质是基于文件&#x... 查看详情

进程信号(linux)(代码片段)

Linux进程信号信号入门1、生活角度的信号2、技术应用角度的信号3、注意4、信号概念5、用kill-l命令可以察看系统定义的信号列表6、信号是如何发送的以及如何记录?7、信号处理常见方式概览产生信号1、通过终端按键产生信... 查看详情