【Linux学习】06 信号

2023-11-07

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录


前言


一、信号的概念

  1. 信号是进程在运行过程中,由自身产生或由进程外部发过来的消息(也称为事件)。
  2. 信号是硬件中断的软件模拟,是一种软中断。每个信号用一个整型常量宏表示,以 SIG 开头,比如 SIGCHLD、SIGINT等,它们在系统头文件<signal.h>中定义,也可以通过在 shell 下键入 kill –l 查看信号列表,或者键入 man 7 signal 查看更详细的说明。
$kill -l
$man 7 signal
  1. 软件层面: 异步事件机制
    自己杀自己:同步,别人杀死自己:异步
    产生信号的手段:
    ①:硬件产生 ctrl+c 异步
    ② :硬件成生/0 同步
    ③ : 软件产生 kill 异步
    ④: 软件产生 abort 同步

二、Linux中信号(signal函数)

1.种类

SIGINT Ctrl+C
SIGQUIT CRTL+
SIGKILL CRTL+TOP
SIGUSR1 自定义行为 一般用来实现进程的有序退出

2. 信号的实现机制

  1. 信号的生成来自内核,对应了 task_structsigpending成员,表示待处理信号的集合(位图),当一个进程处于一个可以接受信号状态的时候(这种状态被称为响应时机),它会从sigpending中取得信号,并执行默认行为、忽略或者是自定义信号处理函数。
  2. 因此信号的实现可以分为两个阶段,信号产生表示内核已知信号发生并修改进程的数据结构;信号递送表示内核执行信号处理流程。
  3. 已经产生但是还没有传递的信号被称为挂起信号(pending signal)或者是未决信号。如果信号一直处于未决状态,那么就称进程阻塞了信号传递
  4. 由进程的某个操作产生的信号称为同步信号(synchronous signals),例如在代码中除 0;由像用户击键这样的进程外部事件产生的信号叫做异步信号(asynchronous signals)。
  5. 同步和异步是编程当中一个非常重要的概念,同步表示事件之间的执行顺序是确定的,这种事件处理方式就更符合人类的认知习惯;异步表示事件之间的执行顺序是随机的,异步模式在使用良好的情况下更符合真实的物理世界,也能实现更高的执行效率。
  6. 目前,很多框架都是采用异步的方式实现底层请求处理,但是框架实现了良好的封装,这样程序员可以比较容易地用同步的方式编写程序代码,间接地使用异常方式提高运行效率。
  7. 用户使用自定义信号处理函数的主要目的就是实现进程的有序退出。如果没有实现进程的有序退出,就产生一些严重的运行事故,比如著名的“东京证券交易所系统宕机事件”。
    tast struct

3. 信号的处理:

1. 默认递送行为

  1. 进程接收信号后以默认的方式处理。
    例如连接到终端的进程,用户按下 ctrl+c ,将导致内核向进程发送一个 SIGINT 的信号,进程如果不对该信号做特殊的处理,系统将采用默认的方式处理该信号( 对应的信号处理函数使用是 signal(SIGINT,SIG_DFL) )。
    默认处理有5种可能:
    ① Term表示终止进程;
    ② Ign表示忽略信号;
    ③ Core表示终止进程并产生core文件;
    ④ Stop表示暂停进程;
    ⑤ Cont表示继续进程。
  2. 实现进程的有序退出。

2. 忽略信号

进程可以通过代码,显示地忽略某个信号的处理。比如如果将SIGSEGV信号进行忽略(使用信号处理函数 signal(SIGSEGV,SIG_IGN) ),这是程序运行如果访问到空指针,就不会报错了。但是某些信号比如SIGKILL是不能被忽略的。

3. 捕捉信号并处理(具体见4):

进程可以事先注册信号处理函数,当接收到信号时,由信号处理函数自动捕捉并且处理信号。

4. 修改信号的递送行为

1. 函数signal注册信号

  1. signal 函数可以用来捕获信号并且指定对应的信号处理行为。
#include <signal.h>

typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
//这个函数是由ISO C定义的,因为要支持多个平台,所以它对信号的支持功能非常有限
  1. typedef void (*sighandler_t)(int);
    typedef 给类型、函数指针 起别名 sighandler_t

  2. sighandler_t signal(int signum, sighandler_t handler);
    signal 的第 1 个参数 signum 表示要捕捉的信号,第 2 个参数是个函数指针,表示捕获信号后执行的函数(这里就是一个回调函数)。

  3. sighandler_t 是信号捕捉函数,是一个回调函数,在 signal 函数中注册,注册后在整个进程运行过程中均有效,并且对不同的信号可以注册同一个回调函数。该函数只有一个整型参数,表示信号值。

  4. signal 如果调用成功,返回以前该信号的处理函数的地址,否则返回SIG_ERR。

  5. signum 信号的类型;
    sighandler_t handler 递送行为。

在这里插入图片描述

#include <func.h>
void sigfunc(int signum){
	printf("signum = %d is coming\n",signum);//signum表示信号的具体数值
}
int main()
{
	signal(SIGINT, sigfunc);//将SIGINT信号的处理行为注册成sigfunc
	printf("proces begin!\n");
	while(1);
	return 0;
}
  1. 启动进程以后,如果向这个进程发送键盘中断,可以看到进程不会终止,反而会调用回调函数打印一些信息。
  2. 所以从这里可以了解到bash进程的实现原理,bash进程会注册SIGINT信号,这样当从键盘输入中断时,bash进程不会终止了。

2. 函数signal注册信号

  1. 需要特别注意的是,回调函数并不是由进程自己执行,而是当进程处于内核态的时候(比如处理系统调用或者是中断),由内核执行的。

  2. 因为信号处理流程是由内核发起的操作,而且内核执行的上下文(上下文表示执行过程中的寄存器状态)和进程上下文是完全独立的,所以当使用到stdout这类用户态缓冲区时,内核态代码执行结束的时候并不会主动去清理缓冲区

  3. 所以,要记得加换行!!!!!

  4. 9号KILL信号不可以被注册。
    信号递送过程再产生一个同类信号
    在递送过程中,临时屏蔽信号

3. 注册多个信号

  1. 使用 signal 函数是可以同时注册多个信号的。
#include <func.h>
void sigfunc(int signum){
	printf("signum = %d is coming\n",signum);//signum表示信号的具体数值
}
int main()
{
	signal(SIGINT, sigfunc);//将SIGINT信号的处理行为注册成sigfunc
	signal(SIGQUIT, SIG_IGN);
	printf("proces begin!\n");
	while(1);
	return 0;
}

4. 信号递送过程再产生一个同类信号

  1. 接受到了另一个相同类型信号,那么当前的信号处理流程是会不会被中断的,CPU会继续原来的信号处理流程,执行完毕以后再响应新来到的信号。

  2. 如果接受到了连续重复的相同类型的信号,后面重复的信号会被忽略,从而该信号处理流程只能至多执行一次
    在这里插入图片描述

  3. 底层原理:
    mask 屏蔽集合:
    ① 信号不在mask中 产生信号马上递送
    ② 信号在mask中,产生信号放入pending
    pending 未决集合:
    每种光信号只占1bit,判断有没有信号处于未决
    当解除屏蔽时,如果pending有信号,就取出递送之。

5. 信号递送过程产生一个不同类信号

  1. 接受到了另一个不同类型信号,那么当前的信号处理流程是会被中断的,CPU会先转移执行新到来的信号处理流程,执行完毕以后再恢复原来信号的处理流程。
    在这里插入图片描述

  2. 内核里面,某个进程的所有的挂起信号都是使用一个位图(被称为信号屏蔽字)进行管理的。

  3. 当进程处于某个信号处理流程的时候,如果再产生一个同类型信号,信号处理流程不会中断,而信号屏蔽字中的对应位会设置为1,表示此时存在一个挂起信号,随后产生的同类型信号将不再被记录。

  4. 之所以设计同类信号无法中断,是考虑到信号处理流程可能会修改静态数据或者堆数据(这种函数被称为不可重入函数),如果中断处理流程,可能会导致进程破坏。

5. signal的其他性质

使用传送终端了低俗系统调用之后,自动重启低俗系统调用。

二、sigacation函数

1. 简介

  1. 在 signal 处理机制下,在一些特殊的场景,它满足这样的行为:
  • 注册一个信号处理函数,并且处理完毕一个信号之后,不需要重新注册,就能够捕捉下一个信号。
  • 如果信号处理函数正在处理信号,并且还没有处理完毕时,又产生了一个同类型的信号,那么会依 次处理信号,并且忽略多余的信号。
  • 如果信号处理函数正在处理信号,并且还没有处理完毕时,又产生了一个不同类型的信号,那么会 中断当前处理流程,跳转新信号的处理流程。
  • 如果程序阻塞在一个系统调用(比如 read )时,产生一个信号,这时会有两种不同类型的行为,
    一种大多数系统调用的行为,例如读写磁盘文件时的等待,信号递送时会让系统调用返回错误再接
    着进入信号处理函数;另一种是先跳转到信号处理函数,等信号处理完毕后,再重新启动系统调
    用,这些系统调用往往是低速系统调用,比如读写管道、终端和网络设备,又比如 wait 和 waitpid 等等。
  1. 显然如果使用 signal 函数,在这些场景下的执行流程是固定的并且无法调整的,而使用函数
    sigaction 就可以自定义这些场景下进程的行为

  2. 三大性质:
    ① 一次注册,永久生效
    ② 传送x信号,临时屏蔽x
    ③ 自动重启低速系统调用

include <signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction
*oldact);
struct sigaction {
	void (*sa_handler)(int);
	void (*sa_sigaction)(int, siginfo_t *, void *);
	sigset_t sa_mask;
	int sa_flags;
	void (*sa_restorer)(void);
};

oldact参数表示之前的回调函数,通常会传入空指针,所以我们主要关心act参数。

  • sa_handler成员或者sa_sigaction成员(一般使用sa_sigaction)是用来描述信号处理的回调函 数。
  • 如果回调函数不是SIG_IGN或者SIG_DFL时,sa_mask成员描述了一个信号集,调用回调函数以
    前,该信号集要加入进程的信号屏蔽字中,回调函数返回的时候再恢复原来的信号屏蔽字,除此以 外,正在处理的信号默认是被阻塞的。(额外临时屏蔽
  • sa_flags参数表示信号处理方式掩码,可以用来设置信号的处理模式。(属性)
  • sa_restorer成员暂时无用

2. sa_flags的取值集合

在这里插入图片描述

//SA_SIGINFO
//SA_RESETHAND
#include <func.h>
void sigfunc(int signum, siginfo_t *p, void *p1){
//printf("%d is coming", signum);
printf("%d is coming\n", signum);//必须添加\n
}
int main()
{
	struct sigaction act;
	memset(&act,0,sizeof(act));
	//act.sa_flags = SA_SIGINFO;
	act.sa_flags = SA_SIGINFO|SA_RESETHAND;
	act.sa_sigaction = sigfunc;
	int ret = sigaction(SIGINT,&act,NULL);
	ERROR_CHECK(ret,-1,"sigaction");
	while(1);
	return 0;
}
  1. 低速系统调用的中断处理流程,首先是使用signal的处理流程,在这种情况系统调用会自动重启
//使用ps命令查看进程状态时,进程阻塞在wait_w
#include <func.h>
void sigfunc(int signum){
	printf("signum = %d is coming\n",signum);
}
int main()
{
	signal(SIGINT,sigfunc);
	char buf[128] = {0};
	read(STDIN_FILENO,buf,sizeof(buf));
	puts(buf);
	return 0;
}
  1. 如果使用 sigaction 的处理流程,那么系统调用会出错并终止
#include <func.h>
void sigfunc(int signum, siginfo_t *p, void *p1){
	printf("%d is coming\n", signum);//必须添加\n
}
int main()
{
	struct sigaction act;
	memset(&act,0,sizeof(act));
	act.sa_flags = SA_SIGINFO;
	act.sa_sigaction = sigfunc;
	int ret = sigaction(SIGINT,&act,NULL);
	ERROR_CHECK(ret,-1,"sigaction");
	char buf[128] = {0};
	ret = read(STDIN_FILENO,buf,sizeof(buf));
	ERROR_CHECK(ret,-1,"read");
	return 0;
}//read: Interrupted system call
  1. 增加SA_RESTART可以自动重启低速系统调用
#include <func.h>
void sigfunc(int signum, siginfo_t *p, void *p1){
printf("%d is coming\n", signum);//必须添加\n
}
int main()
{
struct sigaction act;
memset(&act,0,sizeof(act));
act.sa_flags = SA_SIGINFO|SA_RESTART;
act.sa_sigaction = sigfunc;
int ret = sigaction(SIGINT,&act,NULL);
ERROR_CHECK(ret,-1,"sigaction");
char buf[128] = {0};
ret = read(STDIN_FILENO,buf,sizeof(buf));
ERROR_CHECK(ret,-1,"read");
return 0;
}

3. sa_mask设置阻塞集合

1. 简介

在信号处理流程中,如果递送了新的不同类型信号,在没有指定SA_NODEFER参数的情况下,新信号将
会中断正在执行的信号处理流程。为了避免这种中断行为,可以使用sa_mask参数来增加一些信号的阻
塞操作。

typedef struct
{
unsigned long int __val[(1024 / (8 * sizeof (unsigned long int)))];
} __sigset_t;
typedef __sigset_t sigset_t;
//

sigset_t的本质就是一个位图,共有1024位

在这里插入图片描述

2. sa_mask 临时屏蔽

  1. 使用sa_mask参数来增加一些信号的阻塞操作,临时屏蔽值发生在递送过程中。
#include <func.h>
void sigfunc(int signum, siginfo_t *p, void *p1){
	printf("before %d is coming\n", signum);
	sleep(3);
	printf("after %d is coming\n", signum);
}
int main()
{
	struct sigaction act;
	memset(&act,0,sizeof(act));
	act.sa_flags = SA_SIGINFO;
	act.sa_sigaction = sigfunc;
	sigemptyset(&act.sa_mask);
	sigaddset(&act.sa_mask,SIGQUIT);
	int ret = sigaction(SIGINT,&act,NULL);
	ERROR_CHECK(ret,-1,"sigaction");
	ret = sigaction(SIGQUIT,&act,NULL);
	ERROR_CHECK(ret,-1,"sigaction");
	while(1);
	return 0;
}
  1. 需要特别注意的是阻塞屏蔽忽略信号有着既然不同的含义,阻塞表示信号产生了但是还未递送,内核会维护一个所有未决信号的位图,如果信号已经被阻塞,再次产生信号就会被忽略了。被阻塞的信号将会后续执行,而被忽略的信号就被丢弃了。

3. 系统调用sigpending获取未决信号

未决信号:已经产生未传递的信号

  1. 使用系统调用 sigpending 可以获取当前所有未决信号(已经产生没有递送的信号)的集合。
  2. 通常这个系统调用是在回调函数当中使用的,用于检查当前是否阻塞了某个信号
#include <func.h>
void sigfunc(int signum, siginfo_t *p, void *p1){
	printf("before %d is coming\n", signum);
	sleep(3);
	sigset_t pendingSet;
	sigpending(&pendingSet);
	if(sigismember(&pendingSet,SIGQUIT)){
		printf("SIGQUIT is pending!\n");
	}
	else{
		printf("SIGQUIT is not pending!\n");
	}
	printf("after %d is coming\n", signum);
}
int main()
{
	struct sigaction act;
	memset(&act,0,sizeof(act));
	act.sa_flags = SA_SIGINFO;
	act.sa_sigaction = sigfunc;
	sigemptyset(&act.sa_mask);
	sigaddset(&act.sa_mask,SIGQUIT);
	int ret = sigaction(SIGINT,&act,NULL);
	ERROR_CHECK(ret,-1,"sigaction");
	ret = sigaction(SIGQUIT,&act,NULL);
	ERROR_CHECK(ret,-1,"sigaction");
	while(1);
	return 0;
}

3. 系统调用sigprocmask实现全程阻塞

永久屏蔽

三、其他一些系统调用+

4. kill 发送信号

5. raise给自己发送信号

6. alarm 信号

间隔定时器:

7. pause函数

等待一个信号的产生

8. 时钟

tick 嘀嗒
真实时钟 墙上时间
虚拟时钟 进程在用户态cpu下才统计的时间
实用 用户态+内核态

间隔

9. 时钟处理


总结

提示:这里对文章进行总结:
例如:以上就是今天要讲的内容,本文仅仅简单介绍了pandas的使用,而pandas提供了大量能使我们快速便捷地处理数据的函数和方法。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【Linux学习】06 信号 的相关文章

随机推荐

  • js实现AES加密

    安装第三方加密包 npm i crypto js 加密代码 let str 需要加密的字符串 let keyStr 密钥 let ivStr iv偏移量 const key CryptoJS enc Utf8 parse keyStr 十六
  • WGS84坐标系下大地坐标转换为空间直角坐标

    大地坐标表示方法 BLH 空间直角坐标表示方法 XYZ 进行地图投影的一般操作步骤为先将BLH转换为XYZ 然后将XYZ通过三参数或者7参数的办法转换为xyz 涉及到两个椭球体以及坐标系之间的转换 本文主要讨论BLH转换为XYZ的办法 通过
  • 线性代数的本质(二)——线性变换与矩阵

    文章目录 线性变换与矩阵 线性变换与二阶方阵 常见的线性变换 复合变换与矩阵乘法 矩阵的定义 列空间与基 矩阵的秩 逆变换与逆矩阵 线性变换与矩阵 线性变换与二阶方阵 本节从二维平面出发学习线性代数 通常选用平面坐标系 O x y Oxy
  • Java中jdbc的框架

    使用框架可以简化代码 提高开发效率 所以了解和掌握一些框架也是必须的 下面简单介绍几个jdbc框架 1 jdbcTemplate Spring提供 2 commons dbutils Apache提供 小巧的jdbc轻量级封装的工具包 主要
  • 【YARN】(1)-- 整体架构、RM、NM、AM等基础组件快速理解

    一 Yarn的功能和整体架构 Apache Hadoop YARN Yet Another Resource Negotiator 另一种资源协调者 是一种新的 Hadoop 资源管理器 它是一个通用资源管理系统和调度平台 可为上层应用提供
  • 什么是自动化测试?如何开展自动化测试你需要知道这些点

    目录 前言 什么是自动化测 分层的自动化测试 我为什么要做自动化测试 什么项目适合做自动化测试 选择什么工具进行自动化测试 selenium 用前须知 selenium IDE selenium Grid selenium RC selen
  • 怎样用苹果手机播放html文件夹,无需转格式 如何用iPhone轻松爽看各种片

    iPhone 5问世后 瞬间就成为了大家追随的最热门产品之一 无论是最具创新还是最热门 每一款产品推出后总是会存在遗憾的 iPhone 5同样不例外 在大家眼中它可能有这样或那样的问题 但是在我看来 自带视频播放器仅支持指定苹果标准视频 不
  • uni-app根据经纬度逆解析详细地址

    uni app中的getLocation 方法可以获取到用户当前的地理位置 经纬度 速度 但是返回参数中的address在app中才会显示 小程序中不会显示 所以我们需要进行逆解析其地址 解析出它的地址信息 1 首先要在腾讯位置服务中 控制
  • 第三方登陆--接入谷歌和FaceBook

    一 第三方登陆流程 一 用户点击登录 前端会调用第三方的SDK 获取到对应的数据 一般会有token userId 二 前端拿到这些信息之后 回调自己后端服务端的接口 进行token校验 主要目的是后端得防止他人使用恶意手段 别的平台 或者
  • Ubuntu下安装LLVM/Clang

    关于LLVM和Clang 参考原文 https blog csdn net SiberiaBear article details 103111028 LLVM 起初的作者是 Chris Lattner 博硕期间研究关于编译器优化的东西 其
  • 区块链:盗版者的噩梦?

    传统版权保护是用文本或数据库来进行处理的 用纸张文本处理有诸多不便之处 如记录搜寻 纸质保存 文件遗失等 而使用普通数据库 虽然查询速度加快 但其中的数据是可以被篡改的 因此很难被视为有效的电子证据 数字资产难以确权 同时再加上如今极度便利
  • LLVM passes: MergeFunctions Pass

    目录 What is MergeFunctions Pass 概述 FnTree和Deferred 基本流程 相同函数搜索 函数哈希值比较 函数哈希值的计算 函数哈希值比较的使用 函数结构比较 FunctionNodeCmp 函数比较方法
  • leetcode分类刷题:队列(Queue)(二、优先队列解决TopK简单问题)

    1 优先队列好像一般都叫堆 以大顶堆为例 顶部第一个元素最大 底部最后一个元素最小 自顶向底是递减的 更准确的说是非递增的 对外只能访问顶部第一个元素 对应索引为0 和底部最后一个元素 对应索引为 1 在Python中 heapq默认维护小
  • 关于#include

    经常看人写 include
  • Failed to resolve packages 打开开源项目 VectorFieldExamples 失败

    unity3d打开开源项目问题 最近研究 keijiro大神的开源项目 VectorFieldExamples clone工程后打开总是提示如下错误 Failed to resolve packages Registry configura
  • 感谢CSDN平台记录了我6年的点点滴滴

    感谢CSDN平台记录了我6年的点点滴滴 我的新博客如下 博客园https www cnblogs com ztguang
  • MySQL REPLACE字符串函数简介

    MySQL为您提供了一个有用的字符串函数REPLACE 它允许您用新的字符串替换表的列中的字符串 REPLACE 函数的语法如下 REPLACE str old string new string SQL REPLACE 函数有三个参数 它
  • centos安装Anaconda并使用其安装pytorch

    下载并安装Anaconda wget no check certificate https mirrors tuna tsinghua edu cn anaconda archive Anaconda3 5 1 0 Linux x86 64
  • 操作系统内存管理及虚拟内存技术

    一 内存管理 操作系统的内存管理主要负责内存的分配与回收 malloc 函数 申请内存 free 函数 释放内存 另外地址转换也就是将逻辑地址转换成相应的物理地址等功能也是操作系统内存管理做的事情 1 常见的内存管理机制 1 1 连续分配管
  • 【Linux学习】06 信号

    提示 文章写完后 目录可以自动生成 如何生成可参考右边的帮助文档 文章目录 前言 一 信号的概念 二 Linux中信号 signal函数 1 种类 2 信号的实现机制 3 信号的处理 1 默认递送行为 2 忽略信号 3 捕捉信号并处理 具体