进程信号(Linux)上

简介: 进程信号(Linux)

信号入门


信号分为四个阶段:


  1. 预备
  2. 产生
  3. 保存
  4. 处理


身边的信号


用一个简单的栗子来解释信号的四个阶段:

当我们过马路遇到红绿灯时,首先我们是能够识别红绿灯的(色盲除外),识别包含两个重要的因素:认识,并且能够产生对应的行为;可是我们为什么认识红绿灯呢?一定是有人所教育的,可能是在学校里面所学习的,亦或被家人所教育的,这其实就是信号的预备阶段;变绿灯时,我们不一定要立刻就过马路,如果此时我们有更重要的事情要处理,我们就会选择等待下一次,变灯其实就是产生信号,选择等待下一次就是信号的处理,在信号产生和处理之间还存在着信号的保存,也就是信号需要被记住;信号的处理方式也可以分为三种:默认动作,就是红灯停绿灯行;自定义动作,例如变绿灯之后等上几秒钟再过马路;忽略,不过马路


进程信号


将上面的概念迁移至进程中

首先,我们有个共识:信号是由操作系统发给进程的;进程认识信号,并且还会做出对应的动作;并不是信号一产生,进程会立刻处理信号,所以进程本身必须有保存信号的能力;进程处理信号的方式也是三种:默认,自定义,忽略,处理信号也称信号被捕捉


信号集,只学习[1,31]普通信号

92e9e86b5b970c9be860feb3f9469f49_92fe7a9bb7df458badc758b94712f4df.png


这里再理解一个概念:既然进程能够保存信号,那么应该保存在哪里呢???

其实不难想象,信号是保存在PCB中的,结构体中存在一个属性是用来保存信号的

178fe7f94574b35ed95b69dd8ae748cd_52937470bb344ab1995701f8b29dd368.png

比特位的位置,代表信号编号;比特位的内容,代表是否收到信号,0表示没有,1表示有;所以发送信号的本质就是修改 PCB信号位图,由于 PCB是内核维护的数据结构对象,操作系统又是进程的管理者,所以无论发送什么信号,本质都是操作系统向进程发送的信号,操作系统必须提供发送信号和处理信号相关的系统调用


产生信号


终端按键产生信号


举个栗子,观察进程中的信号


int main()
{
    while(true)
    {
        cout<<"我是一个进程"<<endl;
        sleep(2);
    }
}

0206c065fa96feb12fbba109f1d7ed8d_657b78c62eed4ec196a56b7026ae1e0e.png


热键ctrl+c能够将进程终止,本质是操作系统将其解释为2号信号SIGINT


06b2b370a758c6c128261a0975bf43f9_4bec7722b792495cb1815b2e05cc93d9.png


介绍一个函数

typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);

signum:信号编号

handler:函数指针,将信号自定义处理

修改上面的栗子


void handler(int signo)
{
    cout<<"进程捕捉到一个信号,信号编号:"<<signo<<endl;
}
int main()
{
    signal(2,handler);
    while(true)
    {
        cout<<"我是一个进程"<<getpid()<<endl;
        sleep(2);
    }
}

image.png

虽然设置了 signal函数,但是进程还是正常打印了四次,因为没有捕捉到信号, handler函数并没有被调用;当输入热键时,信号被捕捉,由于处理方式是自定义的,所以进程没有直接退出


调用系统函数向目标进程发信号


kill


int kill(pid_t pid, int sig);

pid:正在运行的进程的pid

sig:将信号sig发送给目标进程;可以是任意信号

实例如下:

mysignal.cpp


void Usage(const string&proc)
{
    cout<<"\nUsage "<<proc<<" pid signo\n"<<endl;
}
int main(int argc,char*argv[])
{
    if(argc!=3)
    {
        Usage(argv[0]);
        exit(1);
    }
    pid_t pid=atoi(argv[1]);
    int signo=atoi(argv[2]);
    int n=kill(pid,signo);
    if(n!=0)
    {
        perror("kill");
    }
    return 0;
}


7f829e28b2e90cad5716b0b547a5dc05_8777bbfa961e4efc97b05ec931c0267f.png


b06eefa38d5f571c93aac7f8619cc1cf_c166703e21c34f6d83e71f82cd55d500.png


19号信号可以将进程停下来


raise


int raise(int sig);

将信号sig直接发送给进程(自己)


int main()
{
    int cnt=0;
    while(cnt<=10)
    {
        printf("cnt:%d,pid:%d\n",cnt++,getpid());
        sleep(1);
        if(cnt>=5)
        {
            raise(9);
        }
    }
    return 0;
}


857902a7548fd4d5965f206ba33c876a_3bfc8b6dcb26494496ab8e7861a495ec.png


e5e0f257f57bdcaa7a80496bd7ee88bb_e78d899f92284e2d9949ce26f5274386.png

9号信号直接将进程杀死


abort


void abort(void);

给进程(自己)发送指定信号,终止进程


int main()
{
    int cnt=0;
    while(cnt<=10)
    {
        printf("cnt:%d,pid:%d\n",cnt++,getpid());
        sleep(1);
        if(cnt>=5)
        {
            abort();
        }
    }
    return 0;
}


47ddab8fd9b8012f328fdb6b8303f8de_1b59fbf8cb2b4ab8a361d8a694f88485.png

feb9503040080e0211b480ce2b133204_d6a2930acc0b4035b21a3bb8f8a77f2f.png

发送的指定信号其实是6号信号


硬件异常产生信号


信号的产生不一定是用户显示地发送


观察下列代码:


int main()
{
    while(true)
    {
        cout<<"我正在运行中..."<<endl;
        sleep(1);
        int a=10;
        a/=0;
    }
}

c151d2faff1941efb8cded8f928988e2_21284a6d343c471787aaf0e3b1349863.png


程序运行的结果是溢出,为什么进行a/=0会终止进程,进程终止是收到操作系统发送的信号,具体是怎么做的呢?


7fc75ee0fee743dbdba95ddb81b74314_a798532c596349eaacdbcd05dec1e3a1.png


在CPU中进行计算,将计算结果存放到寄存器中,由于a/=0计算的结果溢出,此时状态寄存器将溢出标记位记为1,由于操作系统是管理软硬件资源的,CPU运算异常会被其得知,然后将8号信号发送给进程,让进程终止


验证如下:


void catchSIG(int signo)
{
    cout<<"获取一个信号,信号编号是: "<<signo<<endl;
}
int main()
{
    signal(SIGFPE,catchSIG);
    while(true)
    {
        cout<<"我正在运行中..."<<endl;
        sleep(1);
        int a=10;
        a/=0;
    }
}


09eb4716618cabfeaea3416199e2a45b_0eb1414b0bac4e0ab9073ce8bfdb839b.png


信号被捕获了,可是进程没有直接退出;收到信号,不一定会让进程退出,既然进程没有退出,就还有可能再次被获取


CPU内部的寄存器只有一份,寄存器中的内存保存的是进程的上下文;当进程被切换时,状态寄存器就会被保存和恢复,每一次的恢复,操作系统都会识别到CPU内部的状态寄存器的溢出标志位是1,便会向进程发送8号信号


由软件条件产生信号


在上一章的管道中,如果关闭管道的读端,进程会收到13信号,结束进程,这个信号是在软件条件下产生的;这里介绍另一个在软件条件下产生的信号


unsigned int alarm(unsigned int seconds);


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


代码实现:


int main()
{
    int cnt=0;
    alarm(1);
    while(true)
    {
        cout<<"cnt: "<<cnt++<<endl;
    }
}

59f78b7cd20419cc8261cdb516dd9ddd_6c207716bda542e287de55d113bc449e.png


为什么说alarm函数就是软件条件下的产生的信息呢???


闹钟其实就是由软件实现的,任何一个进程,都可以通过alarm系统调用在内核中设置闹钟,操作系统内可能会存在着很多的闹钟,操作系统为了管理这些闹钟,需要先描述再组织


先描述

97ecb3ce3602d8c3e7af28765617cc3c_01ed328d99e74cd4af75c3efe38947f1.png


再组织

2f7572f083243acc063d4ba0bf18cb30_62420d12ae6b4f9b94d489ed7fe93224.png



操作系统会周期性地检查这些闹钟;如果超时,操作系统会发送信号SIGALARM给进程,也就是调用alarm.p,进而终止进程


最后还有一点,进程退出时的核心转储

观察代码


int main()
{
    int a[10];
    a[1000]=10;
    return 0;
}


563ca1b2294ffc11f82db2723d9c5206_dc24baedfb0b4988989ecf67a2312128.png

353716e3010bb0e027cef76219c72508_105fb1d47eca4514a72c0a780d2e659d.png


程序发生了段错误,并且进程是 core退出,在上面还有信号是 term退出,这种退出称为正常退出; core退出时,程序还会做一些其他操作


云服务器上 core退出时,看不到明显现象,默认关闭了 core file

b6c3d370c111fe64c1f337c4337940f6_dbac950a43f54d1c878e128fa08d5f5e.png


自己可以打开云服务器中的 core file


69b03edd5a4cd344d5204ba42d262d7e_16d89cfb87264a5582a0b2cdaad2ef66.png


再次运行程序


6f3d2435ae3263537b0481c8d600e165_f2ab73d2bd934dc0ad9ca0642b43f91e.png


段错误后面出现了一个新的内容core dumped也就是核心存储:当进程出现异常时,操作系统会将进程在对应时刻有效的数据转储到磁盘中,运行之后还多出来一个文件core.2545,后面的编号就是引起问题进程的pid


核心转储存在的意义是为了调试,更方便用户检查进程崩溃的原因

实操一下:

16ed0cb8213d6c4d1fee40db061582f3_c1892682d0574d5cbda2e0f8f6754315.png


总结


以上所有的信号产生都是由操作系统来执行,因为操作系统是管理软硬件资源的

信号的处理并不是立刻,而是在适合的时候

信号没有被立刻处理,信号会被暂时存放在PCB中

进程在收到信号之前,就已经知道该如何处理

操作系统向进程发送信号,其实就是修改PCB中的位图


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

热门文章

最新文章