Linux——进程信号(中)

简介: Linux——进程信号(中)

第四种,软件也可以产生信号:

比如说之前的管道,读端关闭,写端也会关闭,然后导致这个软件触发条件,发生信号。

在Linux下有一个叫定时器的软件,可以设定一个闹钟,如果时间到了,会给当前进程发送编号为14的信号。(闹钟只会响一次)

参数是按照秒为单位设置一个信号。

#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <cstdlib>
using namespace std;
int main(int argc, char *argv[])
{
    int count = 0;
    alarm(1);
    while(true)
    {
      count++;
        cout << count << endl;
    }
    return 0;
}

这段代码的功能是统计1S左右能让我们的计算机数据累加多少次。

其实正常来说CPU不会这么慢,可以改进一下代码:

#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <cstdlib>
using namespace std;
int count = 0;
void catchSig(int sig)
{
    cout << count <<endl;
    exit(1);
}
int main()
{
    signal(14, catchSig);
    alarm(1);
    while(true)
    {
        count++;
    }
    return 0;
}

那么为什么差距这么大呢?

因为打印是一种外设输出,访问外设的时候是很慢的,需要大量的时间,第一段代码一直在通过外设进行打印,所以很慢,第二段之后结束的时候才会通过外设打印。

如果是服务器还要经过网络IO,会更慢。

”闹钟“其实就是用软件实现的:

任何一个进程都可以通过alarm系统调用在内核中设计闹钟,OS内可能会存在很多的闹钟,OS也一定要管理这些闹钟,先描述再组织。

用struct alarm类型的对象去描述各个进程的闹钟数据:

struct alarm
{
  uint64_t when;//未来的超时时间
  int type;//闹钟类型,一次性的还是周期性的
  tasl_struct *p;//和哪个进程相关
  struct alarm *next;
}

然后OS用特定堆的数据结构方式管理,struct alarm *head

OS会周期性的检测这些闹钟,如果发现超时了OS就会给对应的进程发SIGALARM信号。

上面所说的所有信号的产生,都是由OS来执行,但是信号不一定立即处理,那么是什么时候被处理的呢?

进程退出时——核心转储

先来看一段代码:

#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <cstdlib>
using namespace std;
int main()
{
    while(true)
    {
        int arr[10];
        arr[100] = 106;//这里数组是越界的
    }
    return 0;
}

这里并没有显示越界的报错。

改成一千也没报错,但是i改成一万就报错了

这里是什么情况呢?因为开辟的栈区是合法的,只有到了为开辟的栈区才会进行报错。

像这种,Term这种是正常退出,而Core是退出之后还要做其他工作。

在云服务器上,默认如果进程是core退出的暂时看不到现象,想看到需要打开一个选项:

第一个core file size是0,这是云服务器默认的。

这里设置一下。

然后再次运行上面的段错误的代码:

并且还多出来了一个文件。

第一个后面多出来的core dumped就是核心转储操作,多出来的文件就是核心转储的内容。

多出来的文件.后缀是引起core问题进程的pid。

核心转储:当进程出现异常是hi后,我们将进程的对应时刻,在内存中的有效数据转储到磁盘中。(二进制临时文件)

作用就是为了更方便调试:

这里直接就帮助我们找到了问题。(这里叫做事后调试)

core-file core.xxx

信号的保存

有一个问题,如果所有信号都被捕捉了,那么这个信号是不是就无法停下来了呢?

#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <cstdlib>
using namespace std;
void catchSig(int signo)
{
    cout << "信号拦截:" << signo << endl;
}
int main()
{
    for(int signo = 1; signo <= 31; signo++)
    {
        signal(signo,catchSig);
    }
    while(true)
    {
        cout << "运行中" << getpid() << endl;
        sleep(1);
    }
    return 0;
}

最后用了kill -9才将这个进程杀掉。

OS中9号信号是无法进行捕捉的。

信号其它相关概念

实际执行信号处理的动作称为信号递达。

信号从生产到递达之间的状态称为信号未决(Pending)。

进程可以选择阻塞(Block)某个信号。

被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞才执行递达的动作。

注意,阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略是在递达之后可选的一种处理动作。

并且,PCB中还有一个信号的函数指针数组,里面都是处理信号的方法。

我们使用的信号捕捉也只是将该数组中对应信号的方法给替换了,也就是替换了函数地址。

也就是说,如果要给信号产生,不妨碍他可以先被阻塞。

信号如何实现捕捉的

之前说信号只会在合适的时候才会被处理,不然就一直被保存在pending位图中。

从内核态返回用户态的时候,进行信号的处理。

我们平时是用户态,但是难免会去通过OS访问系统自身的资源和硬件资源,这个时候就要去进行系统调用才能完成:

也就是说,系统调用还要进行身份切换,会比调用用户层本身的方法慢。

所以避免频繁的使用系统调用。

并且,CPU中由寄存器会存储以下相关数据。

那么,一个进程怎么跑到OS中执行方法呢?

因为进程的独立性,所以每个进程都有一个用户级页表。

在开机的时候,操作系统要加载到内存中,因为操作系统只有一份,在内存中也只有一份,相对应的内核级页表也只有一份就够了。

CPU中也会有一个寄存器储存内核级页表,每个进程都会通过内核空间访问内核页表,然后去找到物理内存中的操作系统的代码和数据。

也就是说,进程要访问OS的接口,其实只需要在自己的地址空间上进行跳转就可以了。

如果想访问内核级数据,CPU的CR3要变成0才有权限。

那么是怎么进行切换的呢?是系统调用接口的起始位置会帮助我们进行切换。

也就会说前半段代码可能是用户态跑的,但是这里突然就变成内核态跑。

在Linux中,有一个叫Int 80 —— 陷入内核。

这个是汇编指令,这个就是修改当前进程在寄存器中CR3的身份状态。

相关文章
|
2月前
|
网络协议 Linux
Linux查看端口监听情况,以及Linux查看某个端口对应的进程号和程序
Linux查看端口监听情况,以及Linux查看某个端口对应的进程号和程序
145 2
|
2月前
|
Linux Python
linux上根据运行程序的进程号,查看程序所在的绝对路径。linux查看进程启动的时间
linux上根据运行程序的进程号,查看程序所在的绝对路径。linux查看进程启动的时间
47 2
|
8天前
|
Linux Shell
6-9|linux查询现在运行的进程
6-9|linux查询现在运行的进程
|
1月前
|
Linux C语言
C语言 多进程编程(四)定时器信号和子进程退出信号
本文详细介绍了Linux系统中的定时器信号及其相关函数。首先,文章解释了`SIGALRM`信号的作用及应用场景,包括计时器、超时重试和定时任务等。接着介绍了`alarm()`函数,展示了如何设置定时器以及其局限性。随后探讨了`setitimer()`函数,比较了它与`alarm()`的不同之处,包括定时器类型、精度和支持的定时器数量等方面。最后,文章讲解了子进程退出时如何利用`SIGCHLD`信号,提供了示例代码展示如何处理子进程退出信号,避免僵尸进程问题。
|
1月前
|
NoSQL
gdb中获取进程收到的最近一个信号的信息
gdb中获取进程收到的最近一个信号的信息
|
2月前
|
消息中间件 Linux
Linux进程间通信
Linux进程间通信
36 1
|
2月前
|
Linux 调度
Linux0.11 信号(十二)(下)
Linux0.11 信号(十二)
20 1
|
21天前
|
存储 监控 安全
探究Linux操作系统的进程管理机制及其优化策略
本文旨在深入探讨Linux操作系统中的进程管理机制,包括进程调度、内存管理以及I/O管理等核心内容。通过对这些关键组件的分析,我们将揭示它们如何共同工作以提供稳定、高效的计算环境,并讨论可能的优化策略。
23 0
|
1月前
|
Unix Linux
linux中在进程之间传递文件描述符的实现方式
linux中在进程之间传递文件描述符的实现方式
|
2月前
|
开发者 API Windows
从怀旧到革新:看WinForms如何在保持向后兼容性的前提下,借助.NET新平台的力量实现自我进化与应用现代化,让经典桌面应用焕发第二春——我们的WinForms应用转型之路深度剖析
【8月更文挑战第31天】在Windows桌面应用开发中,Windows Forms(WinForms)依然是许多开发者的首选。尽管.NET Framework已演进至.NET 5 及更高版本,WinForms 仍作为核心组件保留,支持现有代码库的同时引入新特性。开发者可将项目迁移至.NET Core,享受性能提升和跨平台能力。迁移时需注意API变更,确保应用平稳过渡。通过自定义样式或第三方控件库,还可增强视觉效果。结合.NET新功能,WinForms 应用不仅能延续既有投资,还能焕发新生。 示例代码展示了如何在.NET Core中创建包含按钮和标签的基本窗口,实现简单的用户交互。
53 0
下一篇
无影云桌面