Linux进程信号

进程信号:是一种中断机制,或者说是一种事件通知机制,这里指的是软件中断。通过信号通知进程发生了某个事件,打断进程当前的操作,去处理事件。一个信号对应一个事件,并且信号必须能够被识别。
注意:信号量不是信号,这两个不是同一个东西。

linux下信号种类:使用kill -l命令进行查看,一共62种
1~31号:非可靠信号
34~64号:可靠信号
信号的生命周期:产生,注册,注销,处理

  1. 产生
    硬件产生:ctrl+c(解释出的软件信号为2号信号SIGINT中断信号)、ctrl+\(转换为3号信号SIGQUIT退出信号)、ctrl+z(19号信号SIGSTOP停止信号)

    软件产生:kill命令发送一个信号给进程(例如:kill -sigid pid—如果不指定信号值sigid,默认发送15号信号SIGTERM终止信号)
    kill杀死进程的原理是给进程发送了一个终止信号,进程处理信号的方式就是退出进程。

    命令操作:
    int kill(pid_pid, int sig)—给指定进程发送指定信号(头文件#include<signal.h>)

    unsigned int alarm(unsigned int seconds)—设置定时器,经过了seconds 秒之后会给进程发送一个SIGALRM信号,从而打断当前操作(头文件#include<unistd.h>)

    int raise(int sig)—给调用进程发送指定信号(头文件#include<signal.h>)

    void abort(void)—给进程发送一个SIGABRT信号,使异常进程终止(头文件#include<stdlib.h>)

  2. 注册:让进程能够知道自己收到了哪个信号(通过修改pcb中的未决信号集合位图,并且添加信号信息节点)
    未决信号集合:没有被处理的信号集合,是一个位图,用于标记有哪些信号待处理
    sigqueue:双向链表,用于添加信号信息节点。相同的节点有多少个,就表示有多个相同信号待处理

    非可靠信号:若信号没有注册则注册,已经注册则什么都不做
    可靠信号:不管信号有没有注册,都会注册一次

  3. 注销:删除信号痕迹(节点+位图)
    非可靠信号:删除信息节点后直接重置位图
    可靠信号:删除一个信息节点后,确定没有相同节点了才会重置位图

  4. 处理:就是打断进程当前操作,然后执行信号的处理函数,执行完毕后回到原来的主控流程继续运行

    处理方式:
    默认处理方式:执行默认的处理函数
    忽略处理方式:信号依然会注册只是处理方式变为空操作
    自定义处理方式:自己定义信号处理函数,修改信号的处理函数指针

    sighandler_t signal(int signum, sighandler_t handler)
    signum:要修改的信号
    handler:传入新的处理方式—SIG_DFL/SIG_IGN/自定义函数
    typedef void(*sighandler_t)(int)—int参数为传入的信号值
    返回值:成功则返回原来的处理方式;失败则返回SIG_ERR

使用signal函数自定义SIGINT信号的处理方式:

#include<stdio.h>                            
#include<unistd.h>                           
#include<stdlib.h>                           
#include<signal.h>                           
                                             
void sigcb(int no)   //自定义函数处理方式
{
  printf("signal number:%d\n", no);   //打印当前所自定义信号的值                                                    
}                                      
                                       
int main()                             
{                                      
  signal(SIGINT, sigcb);   //通过sigcb函数自定义SIGINT信号的处理方式
  while(1)                                               
  {                                                      
    printf("----------\n");                              
    sleep(1);                                            
  }
  return 0;                                                      
}  

运行结果:
可以看到此时按ctrl+c是打印出了当前的信号值
在这里插入图片描述

程序运行:当程序运行的都是我们自己写的代码和访问的都是自己的变量则程序运行在用户态。若程序运行要访问内核空间或者说要完成内核中的功能就需要切换到内核态运行。
程序运行从用户态切换到内核态的方式:
系统调用接口(read)、中断、异常(int a = 10 / 0)

阻塞:信号依然可以注册,只是暂时阻止信号被处理
在pcb中还有一个信号集合—阻塞集合,哪个信号在这个集合中被标记,则表示这个信号要阻塞,收到了这个信号就暂时不去处理。
int sigprocmask(int how, const sigset_t* set, sigset_t* oldset)
how:操作类型—要对阻塞集合进行的操作
SIG_BLOCK:将set集合中的信号添加到阻塞集合—block |= set
SIG_UNBLOCK:从阻塞集合中移除set中的信号—block &= ~set
SIG_SETMASK:将set集合中的信号设置为阻塞集合—block = set
oldset:用于保存修改前阻塞集合中的数据,以便于能够还原

修改指定信号的处理方式为自定义,能够感受到收到了某个信号(signal)
阻塞所有信号(sigprocmask)
让程序运行停下来,向进程发送信号(非可靠,可靠)(getchar)
让程序继续向下运行,解除阻塞,查看信号的处理结果
int sigemptyset(sigset_t* set)—清空set集合
int sigfillset(sigset_t* set)—填充所有信号到set集合中
int sigaddset(sigset_t* set, int signum)—添加指定信号到set集合中
int sigdelset(sigset_t* set, int signum)—从set集合中删除指定信号
int sigismember(const sigset_t* set, int signum)—判断信号是否在集合中

使用sigprocmask函数阻塞2号信号和40号信号:

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

void sigcb(int no)   //自定义的信号处理函数
{
  printf("recv signal:%d\n", no);   //打印当前所调用信号的信号值
}

int main()
{
  signal(SIGINT, sigcb);   //自定义SIGINT信号处理函数为sigcb
  signal(40, sigcb);   //自定义40号信号处理函数为sigcb
  sigset_t set;   //定义set信号集
  sigemptyset(&set);   //清空set集合
  sigfillset(&set);   //填充所有信号到set集合中
  sigprocmask(SIG_BLOCK, &set, NULL);   //将set集合中的信号添加到阻塞集合
  printf("信号已经被阻塞,回车继续\n");
  getchar();   //
  sigprocmask(SIG_UNBLOCK, &set, NULL);   //从阻塞信号中移除set中的信号
  while(1)sleep(1);
  return 0;
}            

运行结果:
此时按下ctrl+c五次,程序并没有被中断
在这里插入图片描述
输入ps -ef | grep sigprocmask查询当前进程的pid并使用kill -40命令五次,发现也无法杀死进程
在这里插入图片描述
按下回车键之后发现可靠信号40号信号处理了5次,而非可靠信号2号信号只处理了1次。原因是在第一次按ctrl+c时2号信号被注册了,之后再次按ctrl+c注册时发现该非可靠信号已被注册了,但是由于信号被阻塞了并没有进行处理,所以该非可靠信号便不再被注册了,造成了事件丢失,因此只调用了一回信号处理函数。而可靠信号不管信号之前有没有注册过,每次发送信号都会被注册,向链表里面添加一个信号信息节点,所以发送5次信号就处理了5次,信号事件不会丢失。

在这里插入图片描述

在所有信号中有两个信号比较特殊:SIGKILL-9 / SIGSTOP-19
这两个信号不能被阻塞,不能被修改处理方式,不能被忽略

测试结果:
使用kill -9命令
在这里插入图片描述
在这里插入图片描述
使用kill -19命令
在这里插入图片描述
在这里插入图片描述

在哪些情况下,进程无法被杀死:
(1)僵尸进程;(2)信号被阻塞、自定义或者忽略;(3)进程是停止状态(后两种情况可以使用9号信号SIGKILL强制杀死)

信号的基本应用:
(1)SIGCHLD:子进程退出之后给父进程发送的信号(SIGCHLD信号的默认处理方式,就是什么都不做)

通过自定义SIGCHLD信号的处理方式,在信号回调中调用waitpid避免出现僵尸进程。但是由于SIGCHLD信号是一个非可靠信号,只注册了一次,因此有可能会丢失事件。通过在回调函数中使用while(waitpid(-1, NULL, WNOHANG) > 0)实现在一次回调中回收所有退出的子进程。这样父进程也可以不用阻塞等待子进程退出,从而继续完成自己的工作,直到有子进程退出发送信号调用自定义信号处理函数完成对子进程的回收。

还可以通过signal(SIGCHLD, SIG_IGN)显式的忽略处理SIGCHLD信号,即告诉操作系统我们不关心子进程的退出状态,如果子进程退出,直接释放子进程的资源。

(2)SIGPIPE:管道所有读端被关闭后继续写入触发异常对应的信号
SIGPIPE默认处理方式是退出进程,若不想退出进程,则需要自定义处理,从而可以显示出错误原因。

关键字:volatile—修饰一个变量,保持变量的内存可见性
修饰变量后,cpu每次访问变量数据都需要重新从内存中加载数据,防止编译器过度优化(gcc -O2选项可以实现代码优化)

函数的可重入与不可重入
函数的重入:一个函数在多个执行流程中,重复进入一个函数进行执行
可重入函数:一个函数重入之后,不会出现问题
不可重入函数:一旦函数重入之后,就有可能造成数据二义或者逻辑混乱
基准:在一个函数中是否对一个全局数据进行了不受保护的非原子操作
原子操作:原子性的操作,操作不会被打断,一次完成

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值