Linux信号机制探析--信号的产生

Jupiter· 2024-08-23 11:37:02 阅读 71

🍑个人主页:Jupiter.

🚀 所属专栏:Linux从入门到进阶

欢迎大家点赞收藏评论😊

在这里插入图片描述

在这里插入图片描述

目录

`📚信号``什么是信号?``为什么要有信号?`查看Linux系统中信号

`🎈信号产生``📕kill 命令产生信号``🏠键盘产生 ``🦅系统调用`kill系统调用raise系统调用abort系统调用

`🍑软件条件``🌠硬件异常产生信号`对于键盘产生信号的理解对硬件异常产生信号的理解为什么默认的是core dump(核心转储)功能是关闭的呢


<code>📚信号

什么是信号?

信号是Linux系统提供的让进程或则操作系统给其他进程发送异步信息的一种方式。

信号的产生是随时产生的,进程无法预料,所以信号是异步发送的。(信号的产生是由别的进程产生的,在收到信号之前,我(进程)在做自己的事情,我和发送信号的进程是在并发跑的。所以是异步)

为什么要有信号?

Linux系统中要有信号,主要是为了提供一种机制,以便异步地处理系统事件和进程间通信。

进程间通信:信号是进程间通信(IPC)的一种方式,允许进程之间异步地传递消息。系统事件通知:操作系统可以使用信号来通知进程发生了系统级事件,如非法内存访问(段错误)、软件终止请求(如用户按下Ctrl+C)等。简化编程模型:信号提供了一种机制,允许操作系统和应用程序开发者以一种标准化的方式处理各种异步事件,从而简化了编程模型。资源管理:信号可以用于管理进程占用的资源。例如,当进程占用的资源超出限制时,系统可以发送信号来终止或暂停该进程。用户交互:用户可以通过发送信号来控制进程的行为,例如,使用kill命令发送信号以终止或暂停进程。错误处理:当进程执行非法操作时,系统可以发送信号来通知进程发生了错误,进程可以根据信号的指示进行错误处理。

查看Linux系统中信号

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

每个信号都有一个编号和一个宏定义名称,这些宏定义可以在<code>signal.h中找到,例如其中有定义 #define SIGINT 2编号34以上的是实时信号,本章只讨论编号34以下的信号,不讨论实时信号。这些信号各自在什么条件下产生,默认的处理动作是什么,在signal(7)中都有详细说明: man 7 signal注意:没有0 ,32 ,33号信号

<code>🎈信号产生

📕kill 命令产生信号

如:杀掉指定进程kill -9 进程编号

🏠键盘产生

如:2号信号 :ctrl + c 可以终止进程 3号信号:ctrl + \ 终止进程会将该组合键解释为信号 --> 向目标进程发送该信号 —> 进程收到信号 —> 响应信号。

🦅系统调用

kill系统调用

<code>函数介绍:

功能: 向任意的进程发送任意的信号。

参数:参数一:进程pid ; 参数二:信号编号或则信号名称。

返回值: 0表示成功,-1表示失败。

示例代码:

void handler(int signo)

{

cout << "receive a signal: " << signo << endl;

}

int main()

{

signal(2,handler);

int n = kill(getpid(),2); //给自己发送2号信号

return 0;

}

运行结果预期:使用kill系统调用给自己发送一个2号信号,2号信号被捕捉,当收到二号信号的时候,执行我们写的handler函数,打印信息 :receive a signal:2

运行结果:

在这里插入图片描述

raise系统调用

<code>函数介绍:

功能:谁调用,就向哪一个进程发送任意信号

参数:信号编号/信号名称

返回值:0表示成功,非0表示失败。

代码示例:

void handler(int signo)

{

cout << "receive a signal: " << signo << endl;

}

int main()

{

signal(2,handler);

int n = raise(2);

return 0;

}

运行结果:

在这里插入图片描述

abort系统调用

<code>函数介绍:

功能:向调用该函数的进程发送6号信号:SIGABRT(终止进程)。

示例代码:

int main()

{

int cnt = 1;

while(cnt++)

{

if(cnt == 5)

{

abort();

}

cout<<"i am a process,pid:"<<getpid()<<endl;

}

return 0;

}

运行结果:

在这里插入图片描述

<code>🍑软件条件

SIGPIPE是一种由软件条件产生的信号(管道的读端不读,还关闭了读文件描述符,则写端就会因为写条件不具备而被OS终止,OS给写端进程发送的信号就是SIGPIPE:13号信号)。本节还介绍alarm函数SIGALRM信号

<code>函数介绍:

调用alarm函数可以设定一个闹钟,也就是告诉内核在seconds秒之后给当前进程发SIGALRM信号, 该信号的默认处理动作是终止当前进程。

这个函数的返回值是0或者是以前设定的闹钟时间还余下的秒数。比如:当我们设定一个50秒的闹钟,如果在20秒的时候,提前给该进程发送一个SIGALRM信号使进程提前响应,这时,返回值就为50-20=30。

alarm(0):取消闹钟

示例代码:

int main()

{

alarm(5);

while(true)

{

cout<<"i am a process,pid:"<<getpid()<<endl;

sleep(1);

}

return 0;

}

运行结果:

在这里插入图片描述

对SIGALRM信号进行捕捉,实现2秒循环的闹钟

代码实现:

<code>void handler(int signo){

cout << "receive a signal: " << signo << endl;

alarm(2);

}

int main()

{

signal(SIGALRM,handler);

alarm(2);

while(true)

{

cout<<"i am a process,pid:"<<getpid()<<endl;

sleep(1);

}

return 0;

}

运行结果:

在这里插入图片描述

<code>🌠硬件异常产生信号

硬件异常被硬件以某种方式被硬件检测到并通知内核,然后内核向当前进程发送适当的信号。

例如当前进程执行了除以0的指令,CPU的运算单元会产生异常,内核将这个异常解释 为SIGFPE信号发送给进程。再比如当前进程访问了非法内存地址,MMU会产生异常,内核将这个异常解释为SIGSEGV信号发送给进程。

除0异常示例代码:

int main()

{

int a = 0;

int b = 4/0;

while(true)

{ }

sleep(1);

return 0;

}

运行结果:

在这里插入图片描述

访问空指针示例代码:

<code>int main()

{

int *p = 0;

*p=10;

return 0;

}

运行结果:

在这里插入图片描述

对于键盘产生信号的理解

字符输入(字符设备),组合键输入(输入的命令),对于键盘来说,输入的一律当成字符处理,至于到底是字符还是命令,是需要<code>OS和键盘驱动来联合解释的。

OS怎么知道键盘在输入数据?(简略介绍)

使用了一种硬件中断的技术。在开机的时候,OS已经形成了一张中断向量表(函数指针数组),这张表可以提前注册很多对软硬件操作的方法,比如下面表中2号位置是从键盘中读取数据,CPU底部由很多的针脚与外设相连接,这些针脚都有自己的编号当OS正在执行常规代码得时候,用户按键盘输入数据得时候,会给CPU上的特定针脚触发特定的硬件中端,CPU就会将该针脚编号放到一个寄存器中,这时候硬件到软件的动作就完成了,然后CPU就会要求OS停止当前正在执行的工作,让OS根据中端号,取中端向量表中查找对应的操作方法,然后去执行读取键盘数据的任务。

在这里插入图片描述

通过上面的操作后,已经读取到了数据了,接下来会对<code>读取的数据进行判定。

如果操作系统判定为是一般字符输入,则直接放到键盘缓冲区中,如果判定为控制命令OS则会将控制命令解释成信号,然后将信号发送给进程

对硬件异常产生信号的理解

除0错误问题

首先,计算均在CPU中进行的,对于除0错误的计算过程中,会将标志寄存器中的溢出位的0置为1,将计算错误表现到CPU的寄存器上(硬件上),然后CPU发现硬件出现异常,这时OS会介入,发现问题后,将对应的信号发送给相应的进程。

野指针问题

CPU有一个硬件MMU,专门负责虚拟地址到物理地址的转换,同时又两个寄存器,CR2与CR3,如果虚拟地址转换成功,会将转换好的物理地址放到CR3中,如果转换出错,则会将错误标志信息放到CR2中,当野指针的时候,首先将野指针地址放到一个寄存器中,然后通过MMU进行转换,在转换的过程中,发现页表中没有这个虚拟地址或则该虚拟地址中没有相应的权限,则会转换出错,将错误标记放在CR2中,表明了CR2出异常,硬件出现异常,这时OS会介入,发现问题后,将对应的信号发送给相应的进程。

在这里插入图片描述

一个进程,出现异常大多都是将进程终止,但是man 7 signal 中,终止又两种,一个<code>Term,一个是Core,他们有什么区别呢?以及之前在讲进程的时候,进程等待的status位图core dump标记位和这个Core的关系。

在这里插入图片描述

在这里插入图片描述

<code>Core Dump

首先解释什么是Core Dump。当一个进程异常终止时,可以选择把进程的用户空间内存数据全部保存到磁盘上,文件名通常是core,这叫做Core Dump。进程异常终止通常是因为有Bug,比如非法内存访问导致段错误,事后可以用调试器检查core文件以查清错误原因,这叫做Post-mortem Debug(事后调试)。一个进程允许产生多大的core文件取决于进程的Resource Limit(这个信息保存 在PCB中)。默认不允许产生core文件的,因为core文件中可能包含用户密码等敏感信息,不安全。

查看core是否打开 指令:ulimit -a

在这里插入图片描述

开启Linuxcore dump功能:用<code>ulimit命令改变这个限制,允许产生core文件。 首先用ulimit命令改变Shell进程的Resource Limit,允许core文件最大为1024K: $ ulimit -c 1024

在这里插入图片描述

打开过后,再运行一个除0错误的程序,会再当前工作目录中形成一个core文件(ubuntu系统),文件的名字在不同的平台不同(centos系统中就是以core.进程编号命名的)。

<code>事后调试

gdb 可执行程序 —>core-file core 就会将相关调试信息列出(比如,在哪一行出错,出什么错)。

为什么默认的是core dump(核心转储)功能是关闭的呢

有一些平台的core命名不同,如果每一个进程出现core dump后,都形成不同的core文件,那么很容将服务器的磁盘打满。




声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。