Linux进程——信号详解(上)

news2024/9/24 13:21:05

文章目录

  • 信号入门
    • 生活角度的信号
    • 技术应用角度的信号
    • 用kill -l命令可以察看系统定义的信号列表
    • 信号处理常见方式概述
  • 产生信号
    • 通过键盘进行信号的产生,```ctrl+c```向前台发送2号信号
    • 通过系统调用
    • 异常
    • 软件条件

信号入门

生活角度的信号

  • 你在网上买了很多件商品,再等待不同商品快递的到来。但即便快递没有到来,你也知道快递来临时,你该怎么处理快递。也就是你能“识别快递”
  • 当快递员到了你楼下,你也收到快递到来的通知,但是你正在打游戏,需5min之后才能去取快递。那么在在这5min之内,你并没有下去去取快递,但是你是知道有快递到来了。也就是取快递的行为并不是一定要立即执行,可以理解成“在合适的时候去取”。
  • 在收到通知,再到你拿到快递期间,是有一个时间窗口的,在这段时间,你并没有拿到快递,但是你知道有一个快递已经来了。本质上是你“记住了有一个快递要去取”
  • 当你时间合适,顺利拿到快递之后,就要开始处理快递了。而处理快递一般方式有三种:1. 执行默认动作(幸福的打开快递,使用商品)2. 执行自定义动作(快递是零食,你要送给你你的女朋友)3. 忽略快递(快递拿上来之后,扔掉床头,继续开一把游戏)
  • 快递到来的整个过程,对你来讲是异步的,你不能准确断定快递员什么时候给你打电话

技术应用角度的信号

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

int main()
{
    while (1)
    {
        printf("I am a process, I am waiting signal!\n");
        sleep(1);
    }
}

在这里插入图片描述

我们知道上述代码是死循环的,我们使用ctrl+c来终止掉这个进程,本质是键盘向CPU发送了一个中断被操作系统获取并解释成信号(ctrl+c被解释成2号信号),最后操作系统将2号信号发送给目标前台进程,当前台进程收到2号信号后就会退出。
按照文章开头所谈的话,进程就是我,操作系统就是快递员,信号就是快递

注意:

  • ctrl+c产生的信号只能发给前台进程。
  • shell可以同时运行一个前台进程和任意多个后台进程,只有前台进程才能接到像 ```ctrl+c``这种控制键产生的信号
  • 前台进程在运行过程中用户随时可能按下 ctrl+c而产生一个信号,也就是说该进程的用户空间代码执行到任何地方都有可能收到 SIGINT 信号而终止,所以信号相对于进程的控制流程来说是异步(Asynchronous)的。
  1. 我们可以使用jobs来查看正在执行的任务
  2. fg number将后台中的命令调至前台继续运行
  3. bg number将一个在后台暂停的命令变成继续执行
  4. &加在一个命令的最后,可以把这个命令放到后台执行;
    (./xxx &)这样就可以将进程放在后台
  5. ctrl+z可以将一个正在前台执行的命令放到后台,并且处于暂停状态,不可执行

用kill -l命令可以察看系统定义的信号列表

kil -l
在这里插入图片描述
其中1 - 31号信号是普通信号,34 - 64号信号是实时信号

我们看到上面这一堆的大写字母 + 数字,不难想到它们是使用了宏


信号是如何记录的?
使用位图,如果该位置为1即该信号被收到

在这里插入图片描述
所以信号产生的本质上就是操作系统直接去修改目标进程的task_struct中的信号位图。

信号处理常见方式概述

  • 执行该信号的默认处理动作。
  • 忽略此信号。
  • 提供一个信号处理函数,内核在处理该信号时切换到用户态执行这个处理函数,这种方式称为捕捉(Catch)一个信号;自定义的——信号的捕捉

我们可以使用man 7 signal来查看信号的默认处理动作。
在这里插入图片描述

产生信号

通过键盘进行信号的产生,ctrl+c向前台发送2号信号

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

int main()
{
	while (1)
	{
		printf("hello signal!\n");
		sleep(1);
	}
	return 0;
}

除此之外,我们还可以使用ctrl+\来终止进程
在这里插入图片描述
那么它们两个有什么区别呢???
在这里插入图片描述

我们发现ctrl+c对应的行为是Term,而ctrl+\对应的行为是Core,二者都代表着终止进程,但是Core会多进行一个动作,那就是核心转储
在这里插入图片描述

何为核心转储???
首先, 在云服务器中,核心转储是默认被关掉的,我们可以通过使用ulimit -a命令查看当前资源限制的设定。
在这里插入图片描述

第一行显示core文件的大小为0,即表示核心转储是被关闭的。

我们可以通过ulimit -c size命令来设置core文件的大小。
在这里插入图片描述

core文件的大小设置完毕后,就将核心转储功能打开了。此时如果我们再使用ctrl+\对进程进行终止,就会发现终止进程后会显示core dumped
在这里插入图片描述

并且在该路径下生成了一个core.pid文件
其中pid,是一串数字,而这一串数字就是发生这一次核心转储的进程的PID。在这里插入图片描述

核心转储有什么用呢???
当我们的代码报错以后,我们总需要找到报错原因;
当我们的程序在运行过程中崩溃了,我们会通过调试来进行逐步查找程序崩溃的原因;
当我们的程序在运行结束了,那么我们可以通过退出码来判断代码出错的原因;
而在某些特殊情况下,我们会使用核心转储,核心转储是操作系统在进程收到某些信号而终止运行时,将该进程地址空间的内容以及有关进程状态的其他信息转而存储到一个磁盘文件当中,这个磁盘文件也叫做核心转储文件,一般命名为core.pid

#include <stdio.h>

int main()
{
    printf("I am Div\n");
    int a = 10;
    a /= 0;
    return 0;
}

在这里插入图片描述

显然,上述代码出现了除零错误


ctrl+其他有什么效果呢???

我们可以通过以下代码,将1~31号信号全部进行捕捉,将收到信号后的默认处理动作改为打印收到信号的编号。

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

void handler(int signal)
{
	printf("get a signal:%d\n", signal);
}
int main()
{
	int signo;
	for (signo = 1; signo <= 31; signo++)
    {
		signal(signo, handler);
	}
	while (1)
    {
		sleep(1);
	}
	return 0;
}

在这里插入图片描述

但我们发送9号进程,它并不会收到,而是执行收到9号信号后的默认处理动作,即被杀死。
所以,对于某些信号是不可以被自定义处理的。比如9号信,因为如果所有信号都能被捕捉的话,那么进程就可以将所有信号全部进行捕捉并将动作设置为忽略,此时该进程将无法被杀死,即便是操作系统也做不到。

通过系统调用

我们可以以kill -信号编号 进程ID的形式进行发送。

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

int main()
{
    while (true)
    {
        printf("I am a running process..., mypid = %d\n", getpid());
        sleep(1);
    }
    
	return 0;
}

在这里插入图片描述

kill函数

int kill(pid_t pid, int sig);

实际上,kill指令的底层就是 kill函数

kill函数用于向进程IDpid的进程发送sig号信号,如果信号发送成功,则返回0,否则返回-1。


raise函数
raise函数可以给当前进程发送指定信号,即自己给自己发送信号

int raise(int sig);

用raise函数每隔一秒向自己发送一个2号信号。

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

void handler(int signo)
{
	printf("get a signal:%d\n", signo);
}
int main()
{
	signal(2, handler);
	while (1)
    {
		sleep(1);
		raise(2);
	}
	return 0;
}

在这里插入图片描述


abort函数
raise函数可以给当前进程发送SIGABRT信号,使得当前进程异常终止

void abort(void);
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>

void handler(int signo)
{
	printf("get a signal:%d\n", signo);
}
int main()
{
	signal(6, handler);
	while (1){
		sleep(1);
		abort();
	}
	return 0;
}

在这里插入图片描述
abort函数的作用是异常终止进程
exit 函数的作用是正常终止进程

异常

当我们程序当中出现类似于除0、野指针、越界之类的错误时,为什么程序会崩溃?本质上是因为进程在运行过程中收到了操作系统发来的信号进而被终止,那操作系统是如何识别到一个进程触发了某种问题的呢?

我们知道,CPU当中有一堆的寄存器,当我们需要对两个数进行算术运算时,我们是先将这两个操作数分别放到两个寄存器当中,然后进行算术运算并把结果写回寄存器当中。此外,CPU当中还有一组寄存器叫做状态寄存器,它可以用来标记当前指令执行结果的各种状态信息,如有无进位、有无溢出等等。而操作系统是软硬件资源的管理者,在程序运行过程中,若操作系统发现CPU内的某个状态标志位被置位,而这次置位就是因为出现了某种除0错误而导致的,那么此时操作系统就会马上识别到当前是哪个进程导致的该错误,并将所识别到的硬件错误包装成信号发送给目标进程,本质就是操作系统去直接找到这个进程的task_struct,并向该进程的位图中写入8信号,写入8号信号后这个进程就会在合适的时候被终止。

那对于下面的野指针问题,或者越界访问的问题时,操作系统又是如何识别到的呢?
在这里插入图片描述

在这里插入图片描述
当我们要访问一个变量时,一定要先经过页表的映射,将虚拟地址转换成物理地址,然后才能进行相应的访问操作。

其中页表属于一种软件映射关系,而实际上在从虚拟地址到物理地址映射的时候还有一个硬件叫做MMU,它负责处理CPU的内存访问请求的计算机硬件,因此映射工作不是由CPU做的,而是MMU做的,但现在MMU已经集成到CPU当中了。
当需要进行虚拟地址到物理地址的映射时,我们先将页表的左侧的虚拟地址导给MMU,然后MMU会计算出对应的物理地址,我们再通过这个物理地址进行相应的访问。
而MMU既然是硬件单元,那么它当然也有相应的状态信息,当我们要访问不属于我们的虚拟地址时,MMU在进行虚拟地址到物理地址的转换时就会出现错误,然后将对应的错误写入到自己的状态信息当中,这时硬件上面的信息也会立马被操作系统识别到,进而将对应进程发送SIGSEGV信号。


C/C++程序会崩溃,是因为程序当中出现的各种错误最终一定会在硬件层面上有所表现,进而会被操作系统识别到,然后操作系统就会发送相应的信号将当前的进程终止。

软件条件

SIGPIPE信号
SIGPIPE信号实际上就是一种由软件条件产生的信号,当进程在使用管道进行通信时,读端进程将读端关闭,而写端进程还在一直向管道写入数据,那么此时写端进程就会收到SIGPIPE信号进而被操作系统终止。

下面代码当中,创建匿名管道进行父子进程之间的通信,其中父进程是读端进程,子进程是写端进程,但是一开始通信父进程就将读端关闭了,那么此时子进程在向管道写入数据时就会收到SIGPIPE信号,进而被终止。

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
	int fd[2] = { 0 };
	if (pipe(fd) < 0)
	{ //使用pipe创建匿名管道
		perror("pipe");
		return 1;
	}
	pid_t id = fork(); //使用fork创建子进程
	if (id == 0)
	{
		//child
		close(fd[0]); //子进程关闭读端
		//子进程向管道写入数据
		const char* msg = "hello father, I am child...";
		int count = 10;
		while (count--)
		{
			write(fd[1], msg, strlen(msg));
			sleep(1);
		}
		close(fd[1]); //子进程写入完毕,关闭文件
		exit(0);
	}
	//father
	close(fd[1]); //父进程关闭写端
	close(fd[0]); //父进程直接关闭读端(导致子进程被操作系统杀掉)
	int status = 0;
	waitpid(id, &status, 0);
	printf("child get signal:%d\n", status & 0x7F); //打印子进程收到的信号
	return 0;
}

在这里插入图片描述


alarm函数

unsigned int alarm(unsigned int seconds);

alarm函数可以设定一个闹钟,也就是告诉操作系统在若干时间后发送SIGALRM信号给当前进程

alarm函数的返回值:

  • 若调用alarm函数前,进程已经设置了闹钟,则返回上一个闹钟时间的剩余时间,并且本次闹钟的设置会覆盖上一次闹钟的设置。
  • 如果调用alarm函数前,进程没有设置闹钟,则返回值为0。

用下面的代码,测试自己的云服务器一秒时间内可以将一个变量累加到多大。

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

int main()
{
	int count = 0;
	alarm(1);
	while (1)
	{
		count++;
		printf("count: %d\n", count);
	}
	return 0;
}

在这里插入图片描述
我们发现加的变量好像有点小,原有有两点:

  1. 由于我们每进行一次累加就进行了一次打印操作,而与外设之间的IO操作所需的时间要比累加操作的时间更长
  2. 我当前使用的是云服务器,因此在累加操作后还需要将累加结果通过网络传输将服务器上的数据发送过来,因此最终显示的结果要比实际一秒内可累加的次数小得多。

为了尽可能避免上述问题,我们可以先让count变量一直执行累加操作,直到一秒后进程收到SIGALRM信号后再打印累加后的数据。

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

int count = 0;
void handler(int signo)
{
	printf("get a signal: %d\n", signo);
	printf("count: %d\n", count);
	exit(1);
}
int main()
{
	signal(SIGALRM, handler);
	alarm(1);
	while (1)
	{
		count++;
	}
	return 0;
}

在这里插入图片描述
count变量在一秒内被累加的次数变成了四亿多,所以,我们得出一个结论与计算机单纯的计算相比较,计算机与外设进行IO时的速度是非常慢的。

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

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

相关文章

洗地机推荐购买要点全攻略:洗地机哪些品牌好用?热门洗地机详细体验点评

清洁家务可谓是家务清洁中最累人的存在&#xff0c;既浪费时间也浪费精力&#xff0c;还费腰。如果是家有萌宠的铲屎官们就更加辛苦了&#xff0c;不仅清洁时会被萌宠们打扰&#xff0c;还要处理漫天飞舞和沾在地面上的毛发。那么有没有一款智能家电可以帮助我们快速高效的完成…

百度SEO快排原理是什么?如何快速排名方法?

前言&#xff1a;我之前说过我不打算写这个快速排序。 首先&#xff0c;我从来没有在自己的网站上操作过所谓的快速排序。 其次&#xff0c;我不能像网上很多人写的那样透露百度快速排序的秘密&#xff08;说实话&#xff0c;你可以透露秘密&#xff09;。 方法是有了&#xff…

MWC 2024丨美格智能CEO杜国彬出席中国联通创新成果发布会并发表主题演讲

2月26日&#xff0c;中国联通在MWC2024 巴塞罗那期间举办了以“算网为基&#xff0c;智领未来”为主题的创新成果发布会&#xff0c;集中展示最新的创新成果与最佳实践。 中国通信标准化协会理事长闻库、GSMA首席财务官Louise Easterbrook、中国联通副总经理梁宝俊、华为ICT销…

xsslabs第五关

看一下源码 <!DOCTYPE html><!--STATUS OK--><html> <head> <meta http-equiv"content-type" content"text/html;charsetutf-8"> <script> window.alert function() { confirm("完成的不错&#xff01…

代码随想录第45天|● 70. 爬楼梯 (进阶) ● 322. 零钱兑换 ● 279.完全平方数

文章目录 ● 70. 爬楼梯 &#xff08;进阶&#xff09;思路&#xff1a;- 排列 先value后weight代码&#xff1a; ● 322. 零钱兑换思路&#xff1a;代码 ● 279.完全平方数思路&#xff1a;代码 ● 70. 爬楼梯 &#xff08;进阶&#xff09; 思路&#xff1a;- 排列 先value后…

Godot自定义控件样式语法解析

前言 本篇原始文章写于2023年8月7日&#xff0c;存储在我的语雀文档中。但是语雀分享有诸多不便&#xff0c;为了让更多Godoter更轻松的搜到和看到&#xff0c;就转过来了。 这个项目我上传了Github&#xff0c;后续会贴上链接。 概述 Godot控件体系存在的问题之一就是样式无…

C++ 快速排序快速选择

目录 1、75. 颜色分类 2、912. 排序数组 3、 215. 数组中的第K个最大元素 4、LCR 159. 库存管理 III 1、75. 颜色分类 思路&#xff1a;利用快速排序思路&#xff0c;使用三指针分块进行优化。 [0,left]——小于key[left1,right-1]——等于key[right,nums.size()]——大于k…

解决Mybatis报Type interface *.*Mapper is not known to the MapperRegis

解决Mybatis报Type interface *.*Mapper is not known to the MapperRegis 问题发现问题解决方法一&#xff1a;检查Mapper文件的namespace路径是否正确方法二&#xff1a;使用其他方法是否正确 问题发现 在学习MyBatis框架的时候&#xff0c;不使用 XML 构建 SqlSessionFacto…

2023年NOC大赛软件创意编程(学而思)赛道图形化小高组初赛试题,包含答案

2023NOC初赛试题-小高-A卷(平行讲义) 一、判断 1、如果想要编程演奏乐曲需要添加下面的拓展模块 2、运行下面的程序,我们看不到角色位置在舞台上发生变化 3、运行下面的程序,我们会在舞台上看到一个正方形。 4、运行下面的程序,结果一定为true。 5、运行下面的程序,…

顶易海关数据怎么做获客?功能详解看这里!

顶易海关数据怎么做获客呢&#xff1f;详解看这里&#xff01; 海关数据系统登录&#xff1a;hg.smtso.com/?iEF6DCB 如果对开发国外优质客户感兴趣的话&#xff0c;关注Felicia外贸说&#xff0c;一键开发客户不是问题。 海关数据主要功能&#xff1a; 报关单详情查询&#…

YOLO v9训练自己数据集

原以为RT-DETR可以真的干翻YOLO家族&#xff0c;结果&#xff0c;&#xff01;&#xff01;&#xff01;&#xff01; 究竟能否让卷积神经网络重获新生&#xff1f; 1.数据准备 代码地址&#xff1a;https://github.com/WongKinYiu/yolov9 不能科学上网的评论区留言 数据集…

【JavaEE】_Spring MVC项目之建立连接

目录 1. Spring MVC程序编写流程 2. 建立连接 2.1 RequestMapping注解介绍 2.2 RequestMapping注解使用 2.2.1 仅修饰方法 2.2.2 修饰类与方法 2.3 关于POST请求与GET请求 2.3.1 GET请求 2.3.2 POST请求 2.3.3 限制请求方法 1. Spring MVC程序编写流程 1. 建立连接&…

【重温设计模式】装饰模式及其Java示例

装饰模式的介绍 在众多设计模式中&#xff0c;有一种叫做装饰模式&#xff0c;它以一种独特的方式赋予了代码更多的灵活性。 装饰模式是一种结构型设计模式&#xff0c;它允许我们在运行时动态地为对象添加新的行为。这就像是我们在装饰一个房间时&#xff0c;可以随意添加或更…

学习网络编程No.11【传输层协议之UDP】

引言&#xff1a; 北京时间&#xff1a;2023/11/20/9:17&#xff0c;昨天成功更文&#xff0c;上周实现了更文两篇&#xff0c;所以这周再接再厉。当然做题任在继续&#xff0c;而目前做题给我的感觉以套路和技巧偏多&#xff0c;还是那句话很多东西不经历你就是不懂&#xff…

C#入门:简单数据类型和强制类型转换

本文由 简悦 SimpRead 转码&#xff0c; 原文地址 mp.weixin.qq.com 本期来讲讲 unity 的脚本语言 —C#&#xff0c;C# 的简单数据类型及范围和强制类型转化的方法。这可是 unity 游戏开发必备技能。 1. 简单数据类型 各个类型的范围&#xff1a; byte -> System.Byte (字节…

roslaunch 报错 numpy与python版本冲突

报错&#xff1a; File "/usr/lib/python3/dist-packages/numpy/core/__init__.py", line 17, in <module> from . import multiarray File "/usr/lib/python3/dist-packages/numpy/core/multiarray.py", line 14, in <module> from . import…

蓝桥杯练习系统(算法训练)ALGO-992 士兵杀敌(二)

资源限制 内存限制&#xff1a;256.0MB C/C时间限制&#xff1a;1.0s Java时间限制&#xff1a;3.0s Python时间限制&#xff1a;5.0s 问题描述 南将军手下有N个士兵&#xff0c;分别编号1到N&#xff0c;这些士兵的杀敌数都是已知的。   小工是南将军手下的军师&…

gif闪图如何在线生成?仅需三步在线制作gif闪图

Gif动态图片是一种通过连续播放的一系列图片来展示的。Gif动图的优势是体积小、传播速度快且不受限制。当我们想要将多张图片变成带有闪动效果的gif动图时应该怎么操作呢&#xff1f;这时候&#xff0c;只需要使用在线闪图制作&#xff08;https://www.gif.cn/&#xff09;网站…

【C++初阶】内存管理

目录 一.C语言中的动态内存管理方式 二.C中的内存管理方式 1.new/delete操作内置类型 2.new和delete操作自定义类型 3.浅识抛异常 &#xff08;内存申请失败&#xff09; 4.new和delete操作自定义类型 三.new和delete的实现原理 1.内置类型 2.自定义类型 一.C语…

全部免费!抖音,牛逼了!

相比于百度文心、清华智谱和讯飞星火这些在国内有一定市场知名度的AI工具&#xff0c;字节跳动多少显得有点低调了。 国内的AI工具用了不少&#xff0c;但要是说哪家最有前景&#xff0c;那最看好的还是字节跳动。 倒不是说字节的云雀大模型比上面这几个更牛逼&#xff0c;而…