🎁个人主页:我们的五年****
🔍系列专栏:Linux课程学习****
🌷追光的人,终会万丈光芒
🎉欢迎大家点赞👍评论📝收藏⭐文章
Linux学习笔记:
https://blog.csdn.net/djdjiejsn/category_12669243.html
前言:
本篇是对上一篇信号的基础部分再一次讲解,对信号的有一个更深入的理解。相信看完下面的部分,我们能更清楚的认识到信号的本质。
目录
三.给后台进程发送终止信号的以后,为什么回车才能显示退出信息?
[🥪 raise函数和abort函数:](#🥪 raise函数和abort函数:)
一.理解OS如何得键盘有数据
|----|
| 信号 |
| 中断 |
1.1硬件中断的定义:
硬件中断是由与系统相连的外设(如磁盘、网卡、键盘、时钟等)自动产生的异步信号。这些外设通过总线将中断信号发送给中断控制器,再由中断控制器转发给CPU。每个设备或设备集都有自己的中断请求(IRQ)号,基于这个IRQ号,CPU可以将相应的请求分发到对应的硬件驱动上。
每一个硬件都有自己的中断请求号(IRQ),所以以后的中断就能区分是哪个硬件发来的,就可以让CPU去哪个硬件读取数据。
中断确实是通过电路给CPU发信号的。
1.2理解硬件中断和软件信号:
1.信号其实是在模拟硬件中断的行为,只是信号是软件层面的,中断是硬件层面的。
2.中断是通过电路进行发生的,是发给CPU的,而信号是发给进程的。
3.两者有相似性,但是层级不同。
1.3阐述中断到CPU拿到数据的全过程:
当我们键盘按下以后,键盘通过电路(高电压)向CPU的针脚发送中断信息,CPU执行操作系统保存当前进程的代码和数据,然后操作系统停下来去读取外设的内存。
二.通过系统命令向进程发信号:
2.1系统命令发生信号的方法:
通过kill -(信号编号)(进程pid):对进程发送信号
通过上面的方法就能给指定的进程发送指定的信号,下面的代码我们可以给指定的信号。
2.系统命令的预想:
我们可以让一个进程死循环,然后启动另外一个shell对该进程发送一系列的信号。或者我们可以通过对某个信号进程捕捉,然后给进程发该信号,然后去执行我们的自定义行为。有很多的中断进程的信号,我们还可以去发送其他的信号观察进程的反应情况。
cpp
#include <iostream>
#include <unistd.h>
#include <signal.h>
void handle(int signo)
{
std::cout << "signo:" << signo << "系统命令" << std::endl;
}
int main()
{
//自定义SIGINT-2号信号的行为,让二号信号不再执行终止进程的操作
::signal(SIGINT, handle);
while (true)
{
std::cout << "pid:" << getpid() << std::endl;
sleep(1);
}
return 0;
}
2.3结构分析:
因为是前台进程,我们只能通过另外一个shell对死循环的进程发送2号信号,3号信号。发送2号信号,打印2号信号的数字2,还有系统命令。
三.给后台进程发送终止信号的以后,为什么回车才能显示退出信息?
shell的设计理念是不希望用户的输入信息和出错的信息交错在一起。
所以为什么要按一次回车才能显示退出的信息是因为:
是因为进程在退出之前就来到了shell命令行提示符,等待用户输入,此时shell不希望和输入交错,所以按回车以后才能打印退出的信息。
在Shell中,确实存在后台进程的退出信号和用户的输入可能交错在一起的情况,但这种情况并不是Shell所期望的,也不是设计上的必然结果。这种交错通常是由于多个进程同时向同一个输出流(如标准输出或标准错误)写入数据,并且输出缓冲机制的作用导致的。
四.使用函数产生信号
🥪kill函数:
1.函数原型:
头文件:
#include <sys/types.h>
#include <signal.h>函数原型:
int kill(pid_t pid, int sig);
作用:
kill - send signal to a process
2.函数解析:
函数的作用是给一个指定的进程发送指定的信号,pid是指定进程的pid,sig是信号的编号。
3.函数实践
下面是通过kill给自己发信号,然后到达执行自定义行为的过程。通过kill函数,
我们可以获取命令行的信息,实现自己的kill命令,直接和系统接轨。
cpp
#include <iostream>
#include <unistd.h>
#include <signal.h>
void handle(int signo)
{
std::cout << "signo:" << signo << "系统命令" << std::endl;
}
int main()
{
// 自定义SIGINT-2号信号的行为,让二号信号不再执行终止进程的操作
::signal(SIGINT, handle);
sleep(2);
::kill(getpid(), 2);
while (true)
{
std::cout << "pid:" << getpid() << std::endl;
sleep(1);
}
return 0;
}
🥪 raise函数和abort函数:
1.raise函数:
raise函数只能给当前的进程发送一个指定的信号,进程的固定的,信号的种类可以选择。
使用场景:
当一个进程需要主动触发某个信号时,可以使用raise函数。例如,在信号处理函数中捕获到一个信号后,通过raise函数手动发送另一个信号作为后续操作。
头文件:
#include <signal.h>
函数原型:
int raise(int sig);
2.abort函数:
所在库:
#include <stdlib.h>
函数原型:
void abort(void);
abort没有返回任何值,总是运行失败。
使用场景:用于调试触发断点
abort函数主要用于处理系统错误,在调试中触发断点以便检查程序状态,以及确保程序在出现异常时不会继续执行无用操作。
五.由软件条件产生信号
本点的主人公是:alarm函数
5.1alarm函数介绍:
作用:
alarm - set an alarm clock for delivery of a signal所在库:
#include <unistd.h>函数原型:
unsigned int alarm(unsigned int seconds);
alarm的作用是给进程设置一个闹钟,到时间到了的时候,就会给进程发送一个SIGALRM信号。
如果参数为0,就是取消之前设置的闹钟,只能设置一个。当之前设置的闹钟还没结束,又去设置一个信号的闹钟,那么返回值就是之前闹钟剩余的秒数。
5.2函数使用:
cpp
#include <iostream>
#include <unistd.h>
#include <signal.h>
void handle(int signo)
{
std::cout << "signo:" << signo << "闹钟响啦" << std::endl;
}
int main()
{
// 自定义SIGLRM信号的行为,让信号不再执行终止进程的操作
::signal(SIGALRM, handle);
::alarm(3);
while (true)
{
std::cout << "pid:" << getpid() << std::endl;
sleep(1);
}
return 0;
}
5.3理解软件条件产生信号:
alarm函数,是在操作系统内核设置了一个定时器,在时间到了的时候就会给进程发送SIGALRM信号,在操作系统内核设置,操作系统也是一个软件,所以是属于软件层面的。
这些条 件包括但不限于定时器超时(如alarm函数设定的时间到达)、**软件异常(如向已关闭的管道写数据产⽣的SIGPIPE信号)**等。
5.4OS如何管理信号:
学了那么就的面向对象,在管理一个东西的时候,当然是先描述,再组织啦。
下面OS对于闹钟的描述,其中包括了剩余时间entry,还有执行方法function。
六.硬件条件产生信号:
当发生硬件异常的时候,硬件会通过异常等信息告诉操作系统,然后让操作系统结束该进程。当然,我们也可以捕捉这个信号,让它不执行结束进程的操作。
1.当代码中有除以0,或者其他的错误,CPU会产生异常,操作系统OS对于这个异常的处理方法是:对进程发送SIGFPE信号。
2.⽐如当前进程访问了⾮法内存地址, MMU会产⽣异常,内核将这个异常解释为SIGSEGV信号发送
给进程。
除0导致的CPU异常。
cpp
#include <iostream>
#include <unistd.h>
#include <signal.h>
void handle(int signo)
{
std::cout << "signo:" << signo << ",CPU异常" << std::endl;
sleep(1);
}
int main()
{
// 自定义SIGFPE号信号的行为,让SIGFPE信号不再执行终止进程的操作
::signal(SIGFPE, handle);
int n = 1 / 0;
return 0;
}
在这里,为什么会循环打印这个了?
因为在除0以后,进程PCB中,一直保存这该进程出现了异常,不能继续往下执行,所以CPU每次从信号处理完以后,准备进入主控制流的时候,还会进行检查,结果除0异常的信息还没被处理,那么OS又会给进程发送SIGFPE信号。
七.本篇总结:
🍒1.上面信号产生的方法,都要经过OS这一步,因为信号是发给进程的,OS是进程的管理者。
🍒2.信号是否立刻执行?不会立刻执行,只有当block解除以后,才能处理信号,如果不解除block,那么永远不会处理信号。
🍒3.在进程还没收到信号的时候,在handler表中就已经有对应信号的执行方法。
🍒4.如果信号是被立刻执行,那么它会把pending表对应的位图置为1。