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的身份状态。

相关文章
|
8月前
|
安全 Linux
【Linux】阻塞信号|信号原理
本教程从信号的基本概念入手,逐步讲解了阻塞信号的实现方法及其应用场景。通过对这些技术的掌握,您可以更好地控制进程在处理信号时的行为,确保应用程序在复杂的多任务环境中正常运行。
288 84
|
7月前
|
并行计算 Linux
Linux内核中的线程和进程实现详解
了解进程和线程如何工作,可以帮助我们更好地编写程序,充分利用多核CPU,实现并行计算,提高系统的响应速度和计算效能。记住,适当平衡进程和线程的使用,既要拥有独立空间的'兄弟',也需要在'家庭'中分享和并行的成员。对于这个世界,现在,你应该有一个全新的认识。
279 67
|
6月前
|
Web App开发 Linux 程序员
获取和理解Linux进程以及其PID的基础知识。
总的来说,理解Linux进程及其PID需要我们明白,进程就如同汽车,负责执行任务,而PID则是独特的车牌号,为我们提供了管理的便利。知道这个,我们就可以更好地理解和操作Linux系统,甚至通过对进程的有效管理,让系统运行得更加顺畅。
188 16
|
6月前
|
Unix Linux
对于Linux的进程概念以及进程状态的理解和解析
现在,我们已经了解了Linux进程的基础知识和进程状态的理解了。这就像我们理解了城市中行人的行走和行为模式!希望这个形象的例子能帮助我们更好地理解这个重要的概念,并在实际应用中发挥作用。
135 20
|
5月前
|
监控 Shell Linux
Linux进程控制(详细讲解)
进程等待是系统通过调用特定的接口(如waitwaitpid)来实现的。来进行对子进程状态检测与回收的功能。
119 0
|
5月前
|
存储 负载均衡 算法
Linux2.6内核进程调度队列
本篇文章是Linux进程系列中的最后一篇文章,本来是想放在上一篇文章的结尾的,但是想了想还是单独写一篇文章吧,虽然说这部分内容是比较难的,所有一般来说是简单的提及带过的,但是为了让大家对进程有更深的理解与认识,还是看了一些别人的文章,然后学习了学习,然后对此做了总结,尽可能详细的介绍明白。最后推荐一篇文章Linux的进程优先级 NI 和 PR - 简书。
169 0
|
5月前
|
存储 Linux Shell
Linux进程概念-详细版(二)
在Linux进程概念-详细版(一)中我们解释了什么是进程,以及进程的各种状态,已经对进程有了一定的认识,那么这篇文章将会继续补全上篇文章剩余没有说到的,进程优先级,环境变量,程序地址空间,进程地址空间,以及调度队列。
118 0
|
5月前
|
Linux 调度 C语言
Linux进程概念-详细版(一)
子进程与父进程代码共享,其子进程直接用父进程的代码,其自己本身无代码,所以子进程无法改动代码,平时所说的修改是修改的数据。为什么要创建子进程:为了让其父子进程执行不同的代码块。子进程的数据相对于父进程是会进行写时拷贝(COW)。
139 0
|
8月前
|
存储 Linux 调度
【Linux】进程概念和进程状态
本文详细介绍了Linux系统中进程的核心概念与管理机制。从进程的定义出发,阐述了其作为操作系统资源管理的基本单位的重要性,并深入解析了task_struct结构体的内容及其在进程管理中的作用。同时,文章讲解了进程的基本操作(如获取PID、查看进程信息等)、父进程与子进程的关系(重点分析fork函数)、以及进程的三种主要状态(运行、阻塞、挂起)。此外,还探讨了Linux特有的进程状态表示和孤儿进程的处理方式。通过学习这些内容,读者可以更好地理解Linux进程的运行原理并优化系统性能。
310 4
|
8月前
|
Linux 数据库 Perl
【YashanDB 知识库】如何避免 yasdb 进程被 Linux OOM Killer 杀掉
本文来自YashanDB官网,探讨Linux系统中OOM Killer对数据库服务器的影响及解决方法。当内存接近耗尽时,OOM Killer会杀死占用最多内存的进程,这可能导致数据库主进程被误杀。为避免此问题,可采取两种方法:一是在OS层面关闭OOM Killer,通过修改`/etc/sysctl.conf`文件并重启生效;二是豁免数据库进程,由数据库实例用户借助`sudo`权限调整`oom_score_adj`值。这些措施有助于保护数据库进程免受系统内存管理机制的影响。