🎁个人主页:我们的五年

🔍系列专栏:Linux课程学习 

🌷追光的人,终会万丈光芒

🎉欢迎大家点赞👍评论📝收藏⭐文章

Linux学习笔记:
 https://blog.csdn.net/djdjiejsn/category_12669243.html

前言:

本篇是对上一篇信号的基础部分再一次讲解,对信号的有一个更深入的理解。相信看完下面的部分,我们能更清楚的认识到信号的本质。

目录

一.理解OS如何得键盘有数据

1.1硬件中断的定义:

1.2理解硬件中断和软件信号:

1.3阐述中断到CPU拿到数据的全过程:

 二.通过系统命令向进程发信号:

2.1系统命令发生信号的方法:

2.系统命令的预想:

 2.3结构分析:

 三.给后台进程发送终止信号的以后,为什么回车才能显示退出信息?

四.使用函数产生信号

🥪kill函数:

1.函数原型:

2.函数解析:

3.函数实践

🥪 raise函数和abort函数:

1.raise函数:

2.abort函数:

五.由软件条件产生信号

5.1alarm函数介绍:

5.2函数使用:

​编辑

5.3理解软件条件产生信号:

5.4OS如何管理信号:

六.硬件条件产生信号:

七.本篇总结:


一.理解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对该进程发送一系列的信号。或者我们可以通过对某个信号进程捕捉,然后给进程发该信号,然后去执行我们的自定义行为。有很多的中断进程的信号,我们还可以去发送其他的信号观察进程的反应情况。

#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命令,直接和系统接轨。

#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函数使用:

#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异常。

#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。


Logo

助力广东及东莞地区开发者,代码托管、在线学习与竞赛、技术交流与分享、资源共享、职业发展,成为松山湖开发者首选的工作与学习平台

更多推荐