【Linux】信号

Linux 信号

  • 1.信号介绍
  • 2.core dump
  • 3.发送信号
    • 3.1.kill
    • 3.2.send
    • 3.3.abort
  • 4.信号产生
    • 4.1.软件条件产生信号
      • 4.1.1.SIGPIPE
      • 4.1.2.SIGALRM
    • 4.2.硬件异常产生信号
  • 5.信号处理
  • 6.可重入函数 & volatile
  • 7.SIGCHLD

1.信号介绍

信号本质是一种通知机制。
而进程要处理信号,必须具备“识别”信号的能力(能看到+能处理)。
一般而言,信号的产生相对于进程而言是异步的。信号随机产生,进程可能不能立即处理,所以进程要能够临时地记录下信号,以便后续合适的时候进行处理。

如何理解信号被进程保存?
进程要能知道是什么信号,以及是否产生
进程具有保存信号的相关数据结构(位图),在进程PCB内部保存了信号的位图字段。

如何理解信号发送的本质?
操作系统向目标进程写信号,即操作系统直接修改进程PCB中的指定的位图结构,完成“发送”信号的过程。

对于信号处理的常见的有三种方式:

  • 默认(SIG_DFL)
  • 忽略(SIG_IGN)
  • 自定义动作(捕捉信号)

在这里插入图片描述

void handler(int signum)
{cout << "["<< getpid() << "] handler正在处理signum: " << signum << endl;
}void test1()
{signal(SIGINT, handler); // 特定信号的处理动作,一般只有一个// signal函数,仅仅是修改进程对特定信号的后续处理动作,不是直接调用对应的处理动作while(true){cout << "["<< getpid() << "] test正在运行中..." << endl;sleep(1);}
}

在这里插入图片描述
在这里插入图片描述
如何理解键盘组合键形成信号?
键盘的工作方式是通过中断的方式进行的。
操作解释组合键信息,查找进程列表找到前台运行的进程,然后写入对应的信号到进程内部的位图结构中。

Linux的所有信号可以通过kill -l指令查看。
在这里插入图片描述
如图所示,[1, 31]是普通信号,[34, 64]是实时信号。man 7 signal可以查看信号的具体信息。

2.core dump

核心转储:
一般而言,云服务器(生产环境)的核心转储功能是被关闭的。
在这里插入图片描述
core dump标记位:
core dump标志,用于标志是否发生核心转储。是在进程发生某种异常的时候,由操作系统将当前进程在内存中的核心数据转储到磁盘中(表现是生成了一个core.pid的文件),可以便于调试使用。

void test2()
{while(true){cout << "["<< getpid() << "] test正在运行中..." << endl;sleep(1);int a = 1 / 0;cout << "hello world" << endl;}
}

在这里插入图片描述
使用core文件可以在gdb调试环境下快速定位出程序出错的地方。(SIGFPE-8号信号)
在这里插入图片描述
还有很多信号的行为有core dump标记位的设置。
在这里插入图片描述

void test3()
{pid_t pid = fork();if(pid == 0){cout << "I am child" << endl;int a = 1 / 0;exit(1);}int status = 0;waitpid(pid, &status, 0);cout << "father pid: " << getpid() << " | " << "child pid: " << pid << \" | " << "child exit signal: " << (status & 0x7f) << " | " << "child core dump: " << ((status >> 7) & 1) << endl;
}

在这里插入图片描述

3.发送信号

如何理解发送信号的系统调用接口?
用户调用系统接口,操作获取参数,向目标进程写对应信号,即修改目标进程的信号标记位,进程后续对信号做出处理动作。

3.1.kill

在这里插入图片描述

// 简单模拟kill接口来实现kill指令static void Use(const string& proc)
{cout << "Usage:\r\n\t" << proc << " signum pid" << endl;
}// ./mykill signum pid
void test4(int argc, char* argv[])
{if(argc != 3){Use(argv[0]);exit(1);}int signum = atoi(argv[1]);pid_t pid = atoi(argv[2]);kill(pid, signum);
}

在这里插入图片描述

3.2.send

在这里插入图片描述

void test5()
{cout << "I am running..." << endl;sleep(1);raise(9);
}

在这里插入图片描述

3.3.abort

在这里插入图片描述
在这里插入图片描述

void test6()
{cout << "I am running..." << endl;sleep(1);abort(); // 通常用来终止进程
}

在这里插入图片描述

4.信号产生

4.1.软件条件产生信号

对于管道文件,当读端不仅不读,还关闭了,那么写端再写也就没有意义了。操作系统会自动中断对应的写端进程(通过发送信号SIGPIPE的方式)。

4.1.1.SIGPIPE

在这里插入图片描述

/*
* SIGPIPE 信号验证
* 1.创建匿名管道
* 2.父进程读,子进程写
* 3.父子进程可以通信一段时间(非必要)
* 4.父进程关闭读端 && waitpid(),子进程一直写
* 5.子进程会退出,父进程拿到子进程退出的status,提取退出信号
*/void Test1()
{// 创建管道int pipefd[2] = {0};int ret = pipe(pipefd);if(ret != 0){perror("pipe");exit(1);}// 创建子进程pid_t pid = fork();if(pid > 0){// 构建单向通行的信道,父进程读,子进程写// 父进程 -- 读// 关闭写端close(pipefd[1]);// 不断读取信息char receive[128] = {0};while(true){ssize_t size = read(pipefd[0], receive, 127);if(size > 0){cout << "father receive: " << receive << endl;if(strcmp(receive, "hello world5") == 0){// 关闭读端close(pipefd[0]);break;}}else if(size == 0) {break;}else{perror("read");exit(4);}}// 等待子进程int status = 0;pid_t wpid = waitpid(pid, &status, 0);if(wpid == -1){perror("waitpid");exit(3);}cout << "father exit code: " << (status & 0x7f) << " | " << "father core dump: " << ((status >> 7) & 1) << endl;}else if(pid == 0){// 子进程 -- 写// 关闭读端close(pipefd[0]);int count = 0;while(true){// 不断写变化的信息string msg = "hello world" + to_string(count++);write(pipefd[1], msg.c_str(), msg.size());sleep(1);}}else {perror("fork");exit(2);}
}

在这里插入图片描述
管道是软件,因为管道是文件在内存级的实现。像这种读端关闭,写端还在写,就属于软件条件不满足的情况,此时操作系统就会向写端进程发送SIGPIPE信号。

4.1.2.SIGALRM

在这里插入图片描述
在这里插入图片描述

// 定时器功能
uint64_t count = 0;
vector<function<void()>> v;void showCount()
{cout << "final count: " << count << endl;
}void logUser()
{if(fork() == 0){execl("/usr/bin/who", "who", nullptr);exit(1);}wait(nullptr);
}void handler(int signum)
{(void)signum;for(auto& f : v){f();}alarm(1);
}void test7()
{alarm(1); // 时间一到就会进行时钟中断,向进程发送时钟信号signal(SIGALRM, handler);v.emplace_back(showCount);v.emplace_back(logUser);while(++count);
}

在这里插入图片描述
如何理解软件条件给进程发送信号?
操作系统先识别到某种软件条件触发或者不满足,然后构建信号,发送给指定进程。

4.2.硬件异常产生信号

如何理解除0操作?
计算机中做计算的是CPU这个硬件,CPU内部是有寄存器的,包括状态寄存器(位图结构),有对应的状态标记位。操作系统会自动进行计算完毕之后的检测(状态寄存器的检测),如果识别到有问题,就去找当前在运行进程的pid,向其发送信号,进程后续会做出处理。

如何理解野指针或越界问题?
无论是野指针还是越界访问,都是必须通过地址,找到目标位置。
语言层面的地址,都是虚拟地址,要将其转化成物理地址(页表+硬件MMU[Memory Management Unit])。
野指针,越界都属于非法地址,MMU在转化的时候会报错。(不只是CPU中有寄存器,几乎所有外设和常见的硬件,都可能存在寄存器,所以MMU中也是有寄存器的)

void handler(int signum)
{cout << "signum: " << signum << endl;sleep(1);
}void test8()
{signal(SIGFPE, handler);int a = 1 / 0;while(true) sleep(1);
}void test9()
{signal(SIGSEGV, handler); // 11) SIGSEGV - Invalid memory referenceint* p = nullptr;*p = 1;while(true) sleep(1);
}

上面程序为什么死循环?
因为寄存器中的异常一直没有得到解决。

所有的信号,都有它的来源,但最终都是会被操作系统识别,解释,并发送的。

5.信号处理

一个信号被处理,是怎样的一个过程呢?
在这里插入图片描述
handler是一个函数指针数组,数组的下标和信号的编号相对应。
block位图,结构和pending位图一样,block位图的含义是对应信号是否被屏蔽阻塞。
操作系统不允许用户直接进行位图的操作,但提供了操作位图的方法。
在这里插入图片描述

  • sigemptyset & sigfillset用于初始化set信号集。
  • sigaddset & sigdelset用于设置特定信号signum的比特位信息。
  • sigismember用于判断特定信号signum是否在set信号集中。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
如果把所有的信号都进行自定义捕捉,进程会怎样?

static void handler(int signum)
{cout << "[" << getpid() << "] " << "signum: " << signum << endl;
}void test10()
{// 9 & 19 信号仍有效for(int signum = 1; signum <= 31; ++signum){signal(signum, handler);}cout << "pid: " << getpid() << endl;while(true) sleep(1);
}
# sendSignal.sh
# chomd +x 添加执行权限
i=1
pid=$(pidof test)
while [ $i -le 31 ]
doif [ $i -eq 9 -o $i -eq 19 ]thenlet ++icontinuefikill -$i $pidecho "kill -$i $pid"let ++isleep 1
done

在这里插入图片描述
如果把所有的信号都进行block,不断获取并打印当前进程的pending信号集,进程会怎样?

static void handler(int signum)
{cout << "[" << getpid() << "] " << "signum: " << signum << endl;
}static void showPending(const sigset_t& pending)
{for(int signum = 1; signum <= 31; ++signum){if(sigismember(&pending, signum)){cout << "1";}else{cout << "0";}}cout << endl;
}void test11()
{// 0.捕捉2号信号,以便测试signal(2, handler);// 1.定义信号集对象(位图)sigset_t bset, obset;sigset_t pending;// 2.初始化sigemptyset(&bset);sigemptyset(&obset);sigemptyset(&pending);// 3.添加要进行屏蔽的信号sigaddset(&bset, 2 /*SIGINT*/);// 4.设置bset到内核中对应的进程PCB内[默认情况下进程不会对任何信号进行block]int ret = sigprocmask(SIG_BLOCK, &bset, &obset);if(ret == -1){perror("sigprocmask");exit(1);}cout << "[" << getpid() << "] " << "block signum 2 success" << endl;// 5.重复打印当前进程的pending信号集int count = 0;while(true){// 5.1.获取当前进程的pending信号集sigpending(&pending);// 5.2.显示pending信号集中没有被递达的信号showPending(pending);sleep(1);++count;if(count == 10){int ret = sigprocmask(SIG_SETMASK, &obset, nullptr);if(ret == -1){perror("sigprocmask");exit(2);}cout << "[" << getpid() << "] " << "recover signum 2 success" << endl;}}
}

在这里插入图片描述

static void blockSignal(int signum)
{sigset_t bset;sigemptyset(&bset);sigaddset(&bset, signum);int ret = sigprocmask(SIG_BLOCK, &bset, nullptr);if(ret == -1){perror("sigprocmask");exit(1);}
}void test12()
{// 9 & 19 信号仍有效for(int signum = 1; signum <= 31; ++signum){blockSignal(signum);}sigset_t pending;while(true){sigpending(&pending);showPending(pending);sleep(1);}
}

在这里插入图片描述
SIGKILL/SIGSTOP信号 无法被自定义,无法被阻塞,无法被忽略。
在这里插入图片描述
在这里插入图片描述
这里主要使用sigaction中的第一个和第三个参数。

void showPending(sigset_t* pending)
{for(int signum = 1; signum <= 31; ++signum){if(sigismember(pending, signum)) cout << "1";else cout << "0";}cout << endl;
}void handler(int signum)
{cout << "[" << getpid() << "] " << "signum: " << signum << endl;sigset_t pending;while(true){sigpending(&pending);showPending(&pending);sleep(1);}
}void test13()
{// 内核数据类型,在用户栈空间定义的struct sigaction act, oact;act.sa_flags = 0;sigemptyset(&act.sa_mask);act.sa_handler = handler;sigaddset(&act.sa_mask, 3);sigaddset(&act.sa_mask, 4);sigaddset(&act.sa_mask, 5);sigaddset(&act.sa_mask, 6);sigaddset(&act.sa_mask, 7);// 设置进当前进程的PCB中sigaction(2, &act, &oact);cout << "default action: " << (int)oact.sa_handler << endl;while(true) sleep(1);
}

在这里插入图片描述
在这里插入图片描述
处理信号的时候,执行自定义的动作,如果在处理信号期间,又来了同样的信号,操作系统又该如何处理?操作系统的处理方式可以让我们意识到block信号集存在的道理。

信号产生之后,可能无法立即被处理,而是在合适的时候处理。
至于合适的时候,是从内核态返回用户态的时候,进行信号的检测和处理。
在这里插入图片描述
内核级页表,可以被所有进程看到。
用户态是一个受管控的状态(权限有限)。内核态是一个操作系统执行自己代码的状态,具备很高的优先级。
用户态可以通过系统调用接口,异常等进入内核态,这也不过是从用户地址空间跃迁到内核地址空间之后,执行操作系统自己的代码。所以,内核也是在所有进程地址空间的上下文中跑的。
所有的程序指令都要经由CPU来执行,CPU又是如何来区分内核态和用户态的呢?
CPU中的CR3寄存器用于表示当前CPU的执行权限是用户级还是内核级。

信号处理的整个流程?
在这里插入图片描述
信号处理的整体可以分为两个层级,四次切换。
如果操作系统是执行SGI_DFLSGI_IGN的处理,内核级就可以完成,但是如果处理自定义捕捉handler的动作,就需要到用户级去处理(只有自定义处理方式的信号会在用户态进行处理)。当然内核级的权限很高,有能力将自定义捕捉动作在内核级就处理掉,但是操作系统并不会这样做。因为操作系统是不相信用户的,不相信用户所写的代码的。

6.可重入函数 & volatile

  1. 可重入函数:
    信号捕捉,并没有创建新的进程或者线程。
    函数是否可重入的关键在于函数内部是否对全局数据进行了不受保护的非原子操作,其中原子操作指的是一次完成,中间不会被打断的操作,表示操作过程是安全的。
    如果用户级程序在访问某一全局资源时,正好陷入内核,然后从内核态返回到用户态时进行信号检测,进行handler方法处理,此时如果同样访问这一资源,就可能出现冲突。能导致这类问题发生的函数便是不可重入函数。
    可重入函数 和 不可重入函数 是函数的一种特征,并没有对错之分。

  2. volatile:
    让CPU保持内存的可见性。

void test14()
{volatile const int i = 0;int* pi = (int*)&i;*pi = 1;cout << "i: " << i << endl;cout << "*pi: " << *pi << endl;
}

在这里插入图片描述

7.SIGCHLD

在这里插入图片描述

void handler(int signum)
{cout << "[" << getpid() << "] " << "signum: " << signum << endl;
}// 证明子进程退出,会向父进程发送信号
void test15()
{signal(SIGCHLD, handler);if(fork() == 0){cout << "child pid: " << getpid() << endl;sleep(3);exit(0);}while(true) sleep(1);
}

在这里插入图片描述

// 不等待子进程,并且让子进程退出后,自动释放
void test16()
{signal(SIGCHLD, SIG_IGN); // 手动设置对子进程进行忽略if(0 == fork()){cout << "child: " << getpid() << endl;sleep(5);exit(0);}while(true){cout << "father: " << getpid() << endl;sleep(1);}
}

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/172949.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

windows通过regsvr32注册dll文件失败

1、注册dll文件失败 最近在研究中文输入法&#xff0c;下载SampleIME源码后编译得到SampleIME.dll&#xff0c;最后只需要将输入法安装&#xff08;即注册&#xff09;就可以使用了。 但是通过命令&#xff1a; regsvr32 C:\Windows\System32\SampleIME.dll 注册时却提示错…

【3 栈和队列】链队的操作。

结构体&#xff1a; typedef struct QNode{int data;struct QNode *next; }QNode;typedef struct{QNode *front,*rear; }*LinkQueue; 初始化、判空&#xff1a; void InitQueue(LinkQueue &Q){ //初始化 Q.frontQ.rear(QNode*)malloc(sizeof(QNode)); //建立头结点…

6.2 Windows驱动开发:内核枚举SSSDT表基址

在Windows内核中&#xff0c;SSSDT&#xff08;System Service Shadow Descriptor Table&#xff09;是SSDT&#xff08;System Service Descriptor Table&#xff09;的一种变种&#xff0c;其主要用途是提供Windows系统对系统服务调用的阴影拷贝。SSSDT表存储了系统调用的函数…

光线追踪-Peter Shirley的RayTracing In One Weekend系列教程(book1-book3)代码分章节整理

自己码完了一遍了&#xff0c;把代码分章节整理了一下&#xff0c;可以按章节独立编译&#xff0c;运行, 也可以直接下载编译好的release版本直接运行。 项目地址&#xff1a; Github: https://github.com/disini/RayTracingInOneWeekendChaptByChapt ​ ​ ​ ​

transformers pipeline出现ConnectionResetError的解决方案

大家好,我是爱编程的喵喵。双985硕士毕业,现担任全栈工程师一职,热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。现为CSDN博客专家、人工智能领域优质创作者。喜欢通过博客创作的方式对所学的…

如果客户端同时有ipv4和ipv6,浏览器是如何选择用哪种ip

在互联网协议&#xff08;IP&#xff09;的发展历程中&#xff0c;IPv4和IPv6是两种主要的版本。对于一个客户端来说&#xff0c;同时拥有IPv4和IPv6的能力是常见的情况。那么&#xff0c;当一个客户端同时具有IPv4和IPv6的能力时&#xff0c;浏览器是如何选择使用哪种IP进行通…

内容运营常用的ChatGPT通用提示词模板

内容定位&#xff1a;请帮助我明确我们的内容定位&#xff0c;包括目标受众、内容类型、主题、风格等方面的内容&#xff0c;以便我能够更好地制定内容策略和规划。 内容策划&#xff1a;请帮助我制定一个详细的内容策划方案&#xff0c;包括内容主题、风格、形式、发布时间等…

软件测试高职教师组比赛复盘

1、考试分为四个模块&#xff1a; 任务1&#xff1a;自动化测试30分&#xff0c;4道题 任务2&#xff1a;单元测试20分&#xff0c;4道题 任务3&#xff1a;接口测试20分&#xff0c;2道题 任务4&#xff1a;性能测试30分&#xff0c;LoadRunner和Jmeter两个。 2、考试时间…

linux复习笔记05(小滴课堂)

hell脚本与crontab定时器的运用 查看状态&#xff1a; 关闭服务&#xff1a; 开启服务&#xff1a; 重启服务&#xff1a; crontab定时器的使用&#xff1a; 我们可以看到没有任何任务。 编辑&#xff1a; 我们可以看到这个任务了。 删除所有任务&#xff1a; 这代表着每分钟…

5.一维数组——输入一行字符,统计其中各个大写字母出现的次数。

文章目录 前言一、题目描述 二、题目分析 三、解题 程序运行代码 四、举一反三一、题目描述 二、题目分析 三、解题 程序运行代码 前言 本系列为一维数组编程题&#xff0c;点滴成长&#xff0c;一起逆袭。 一、题目描述 输入一行字符&#xff0c;统计其中各个大写字母出现的…

Android控件全解手册 - 自定义ViewGroup卫星导航菜单

Unity3D特效百例案例项目实战源码Android-Unity实战问题汇总游戏脚本-辅助自动化Android控件全解手册再战Android系列

Redis序列化操作

目录 1.protostuff 的 Maven 依赖 2.定义实体类 3.序列化工具类 ProtostuffSerializer 提供了序列化和反序列化方法 4.测试 利用 Jedis 提供的字节数组参数方法&#xff0c;如&#xff1a; public String set(String key, String value) public String set(byte[] key…

LeetCode 1457. 二叉树中的伪回文路径

原题链接&#xff1a;力扣&#xff08;LeetCode&#xff09;官网 - 全球极客挚爱的技术成长平台 耗时&#xff1a;28min48s C代码 dfs、二叉树前序遍历、哈希表记录 #include<bits/stdc.h> using namespace std;struct TreeNode {int val;TreeNode *left;TreeNode *rig…

HarmonyOS(六)——@Builder装饰器

前言 前面章节介绍了如何创建一个自定义组件以及认识页面和自定义组件生命周期。明白了自定义组件内部UI结构固定&#xff0c;仅与使用方进行数据传递。同时明白了自定义组件对应生命周期的机制管理。ArkUI还提供了一种更轻量的UI元素复用机制Builder&#xff0c;Builder所装饰…

CentOS7部署FTP服务器

首先准备一台centos7虚拟机&#xff0c;作为服务器IP地址必须是固定的。 vim /etc/sysconfig/network-scripts/ifcfg-ens33配置内容如下&#xff1a; TYPE"Ethernet" PROXY_METHOD"none" BROWSER_ONLY"no" BOOTPROTO"static" DEFROU…

【Vue3+Vite】解决build后空白页的问题

目录 Hash 模式 HTML5 模式&#xff08;历史模式&#xff09; 配置Nginx 配置Spring Boot Hash 模式 build后空白页的问题可能是使用的是历史模式&#xff0c;因为Vue是一个单页的客户端应用&#xff0c;如果没有适当的服务器配置&#xff0c;访问会得到一个 404 错误…

【研究中2】sql server权限用户设置

--更新时间2023.11.26 21&#xff1a;30 负责人&#xff1a;jerrysuse DBAliCMSIF EXISTS (select * from sysobjects where namehkcms_admin)--判断是否存在此表DROP TABLE hkcms_adminCREATE TABLE hkcms_admin (id int identity(1, 1),--id int primary key identity…

静态路由配置过程

静态路由 静态路由简介 路由器在转发数据时&#xff0c;要先在路由表&#xff08;Routing Table&#xff09;中在找相应的路由&#xff0c;才能知道数据包应该从哪个端口转发出去。路由器建立路由表基本上有以下三种途径。 &#xff08;1&#xff09;直连路由&#xff1a;路由…

HDMI接口信号流向及原理图分析

1、HDMI的来源及发展 如今显示器上最常用的接口无非HDMI&#xff08;High Definition Multimedia Interface&#xff09;与DP&#xff08;DisplayPort&#xff09;两种&#xff0c;VGA与DVI已经很少使用&#xff0c;原因在于VGA传输的是模拟信号&#xff0c;在发送端需要将数字…

C++前缀和算法:统计美丽子字符串

题目 给你一个字符串 s 和一个正整数 k 。 用 vowels 和 consonants 分别表示字符串中元音字母和辅音字母的数量。 如果某个字符串满足以下条件&#xff0c;则称其为 美丽字符串 &#xff1a; vowels consonants&#xff0c;即元音字母和辅音字母的数量相等。 (vowels * cons…