【Linux】进程信号(上)

简介: 【Linux】进程信号(上)

> 作者:დ旧言~

> 座右铭:松树千年终是朽,槿花一日自为荣。

> 目标:理解进程信号。

> 毒鸡汤:有些事情,总是不明白,所以我不会坚持。早安!

> 专栏选自:Linux初阶

> 望小伙伴们点赞👍收藏✨加关注哟💕💕



🌟前言

上期我们学习了进程间通信,内容很多,干货满满,当然也是有难度的我们知道进程之间可以相互通信,就像七八十年代,我们远在他乡,思念家人,那时我们只能寄来的信了解家里的情况。我们可以用信来传达,那Linux是采用哪种媒介来传达信息的呢???这里不得不题Linux中的信号量了,那这个信号量到底是个啥呢???


⭐主体

学习【Linux】进程信号咱们按照下面的图解:



🌙 生活信号

首先,从物理和技术的角度来看,生活信号可以包括光信号、声信号、电信号等。例如,手机通讯、互联网通讯、音乐播放器和语音识别技术等都涉及到信号的传输和处理。在手机通讯中,信号通过无线电波进行传输,而互联网通讯则通过数字信号进行信息交换。音乐播放器通过解码信号将音乐以声音的形式播放出来,而语音识别技术则通过处理语音信号将其转换成计算机可识别的数字信号。


其次,从日常生活的角度来看,生活信号可以包括身体健康信号、情绪信号、社交信号、身体语言信号和经济信号等。例如,头痛、发烧、疲劳等可能是身体机能异常或疾病的信号;快乐、悲伤、焦虑等情绪状态则可以帮助我们了解自己的情绪状态和需求;微笑、眼神交流、姿势等非言语方式的沟通信号可以传达我们的情感和意图;手势、肢体动作、面部表情等身体语言信号可以揭示我们的情感和态度;物价上涨、收入增长、股市波动等经济信号则可以反映经济状况和趋势。


此外,交通信号也是生活中常见的信号系统之一,如红绿灯、交通标志等,它们在道路交通中起着引导和安全作用。


总之,生活信号无处不在,它们可以来自不同的方面,包括物理和技术层面以及日常生活层面。这些信号在我们的生活中起着重要的作用,帮助我们进行信息传递、情感交流、健康监测和经济分析等。因此,我们需要正确理解和应对这些信号,以便更好地生活和工作。


🌙 进程信号

进程信号是Linux中用于进程间通信和控制的一种机制。当一个进程需要发送一个信号给另一个进程时,可以调用kill系统调用或向指定进程发送信号。进程信号可以被视为一种软件中断,通知进程发生了某个事件,并打断进程当前的操作去处理这个事件。


信号是给进程发的,比如我们之前使用过的指令:kill -9 pid


进程本身是被程序员编写的属性和逻辑的组合,由程序员编码完成的;当进程收到信号的时候,进程可能正在执行更重要的代码,所以信号不一定被处理;进程本身必须要对于信号的保存能力;进程在处理信号的时候一般有三种动作:默认、自定义、忽略,处理信号也可被称为信号被捕捉。


如果信号是发给进程的,而进程是要保存的,那么应该保存在哪里?task_struct结构里,如何保存?保存是否收到了指定的信号,信号:用比特位的位置代表信号的编号,比特位的内容代表是否收到该信号,0表示没有,1表示有。


🌙 查看信号kill -l与信号解释man 7 signal

用kill -l命令可以察看系统定义的信号列表 :



每个信号都有一个编号和一个宏定义名称,这些宏定义可以在signal.h中找到:


man 7 signal可以查看信号详细信息的命令



🌙 信号的产生




💫 按键产生

2号信号产生:ctrl+c



2号信号作用:-->终止进程


所以当我们ctrl+c的时候该进程直接进入结束状态


3号信号产生:ctrl+\



3号信号作用:-->终止进程


所以当我们ctrl+\的时候该进程直接进入终止进程。



采用kill -3 +pid查询3号信号:



总结:


键盘是硬件,通过组合键按下给OS识别,OS将组合键解释成信号,向目标进程发信号,目标进程在合适的时候处理这个信号,对于2号和3号信号处理动作默认为终止进程。


💫 系统调用

概念:


除了键盘向前台进程发送信号之外,前台进程会影响shell,linux规定跟shell交互的时候只允许有一个前台进程,默认情况下bash也是一个进程。而实际上当我们自己运行进程时,我们的进程就变成前台进程了,而bash自动被切到后台。


使用:

kill:可以向任意进程发送任意信号

NAME
       kill - send signal to a process
 
SYNOPSIS
       #include <sys/types.h>
       #include <signal.h>
       int kill(pid_t pid, int sig);
RETURN VALUE
    On  success  (at  least  one  signal was sent), zero is returned.  On error, -1 is returned, and errno is set appropriately.


总结:

发送信号的能力是OS的,但是有这个能力并不一定有使用这个能力的权力,一般情况下用户决定OS向目标进程发信号。所以OS有这个能力,那么对外提供能力只能通过系统调用的接口的方式来让用户向目标进程发送信号。


1.raise

作用:

给自己发送任意信号

库中讲解:

NAME
       raise - send a signal to the caller
 
SYNOPSIS
       #include <signal.h>
       int raise(int sig);
 
RETURN VALUE
     raise() returns 0 on success, and nonzero for failure.


举个栗子:

#include <iostream>
#include <unistd.h> // 包含 sleep 头文件
#include <signal.h> // 包含 raise 头文件
using namespace std;
 
int main(int argc,char*argv[])
{
    //raise()给自己发送任意信号
    int cnt = 0;
    while(cnt<=10)
    {
        printf("cnt:%d\n",cnt++);
        sleep(1);
        if(cnt>=5)
        {
            raise(3);//kill(getpid(),signo);
        }
    }
}



2.abort

作用:

  • abort——进程给自己发6号信号
  • abort:终止进程的方式,给自己发送指定的信号SIGABRT

库中讲解:

NAME
       abort - cause abnormal process termination
 
#include <stdlib.h>
void abort(void);


举个栗子:

#include <iostream>
#include <unistd.h> // 包含 sleep 头文件
#include <signal.h> // 包含 raise 头文件
#include <stdlib.h> // 包含 abort 头文件
using namespace std;
 
int main(int argc,char*argv[])
{
    int cnt = 0;
    while(cnt<=10)
    {
        printf("cnt:%d,pid:%d\n",cnt++,getpid());
        sleep(1);
        if(cnt>=5)
        {
           abort();//kill(getpid(),SIGABRT)
        }
    }
}



💫 硬件异常产生信号

除零发送8号信号

信号产生,不一定非得用户显示的发送,有些情况下信号会在OS内部自动产生。

举个栗子:

#include <iostream>
#include <unistd.h> // 包含 sleep 头文件
#include <signal.h> // 包含 raise 头文件
#include <stdlib.h> // 包含 abort 头文件
using namespace std;
 
int main(int argc,char*argv[])
{
    //3.产生信号的方式:硬件异常产生信号
    //信号产生,不一定非得用户显示的发送
    while(true)
    {
        cout<<"我在运行中..."<<endl;
        sleep(1);
        int a = 10;
        a/=0;
    }
}


分析:


CPU内有很多寄存器eax,edx等,执行int a=10,a/=0;CPU内除了数据保存,还得保证运算有没有问题,所以还有状态寄存器,状态寄存器衡量这次的运算结果,10/0.相当于10乘以无穷大,结果无穷大,引起状态寄存器溢出标记位由0变成1,CPU发生了运算异常,OS得知CPU发生运算异常,就要识别异常:状态寄存器的标记位置为1,由当前进程导致的,在向目标进程发送信号,最后就终止进程了。


我们可以看到上面的结果:收到信号不一会引起进程的退出


**收到信号不一定会引起进程退出!**进程没有退出,则还有可能还会被调度,CPU内部的寄存器只有一份,但是寄存器中的内容属于当前进程的上下文,一旦出现异常我们没有能力去修正这个问题,所以当进程被切换的时候,就有无数次状态寄存器被保存和恢复的过程,所以每一次恢复的时候就让OS识别到了CPU内部的状态寄存器中的溢出标志位是1。


野指针发送11号信号

野指针引发11信号

举个栗子:

#include <iostream>
#include <unistd.h> // 包含 sleep 头文件
#include <signal.h> // 包含 raise 头文件
#include <stdlib.h> // 包含 abort 头文件
using namespace std;
 
int main(int argc,char*argv[])
{
    while(true)
    {
        cout<<"我在运行中..."<<endl;
        sleep(1);
        int*p = nullptr;
        *p=10;
    }
}



分析:

OS会给当前进程发送11号信号,11号信号代表非法的内存引用(man 7 signal).OS又怎么知道野指针:野指针的时候也会引起虚拟地址到物理内存之间转化时对应的MMU报错,进而OS识别到报错,转换成信号。


82517b76c69544f59722676d879204d8.png


💫 软件条件

管道——13号信号SIGPIPE

比如我们之前所说的管道,如果读端关闭,写端一直在写,写的数据没有读就没有意义了,OS不允许这样子,会终止这个进程,向写进程发送13号信号SIGPIPE。管道跟OS发信号的原因是因为读端关闭软件条件触发的。


定时器——4号信号SIGALRM


定时器软件条件:alarm():设定闹钟,调用alarm函数可以设定一个闹钟,也就是告诉内核在seconds秒之后给当前进程发SIGALRM信号, 该信号的默认处理动作是终止当前进程。


这个函数的返回值是0或者是以前设定的闹钟时间还余下的秒数。打个比方,某人要小睡一觉,设定闹钟为30分钟之后响,20分钟后被人吵醒了,还想多睡一会儿,于是重新设定闹钟为15分钟之后响,“以前设定的闹钟时间还余下的时间”就是10分钟。如果seconds值为0,表示取消以前设定的闹钟,函数的返回值仍然是以前设定的闹钟时间还余下的秒数。


库中讲解:

NAME
       alarm - set an alarm clock for delivery of a signal
#include <unistd.h>
 
unsigned int alarm(unsigned int seconds);


举个栗子:

#include <iostream>
#include <unistd.h> // 包含 sleep 头文件
#include <signal.h> // 包含 raise 头文件
#include <stdlib.h> // 包含 abort 头文件
using namespace std;
 
int main(int argc,char*argv[])
{
    //软件条件
    alarm(1);
    int cnt = 0;
    while(true)
    {
        cout<<"cnt:"<<cnt++<<endl;
    }
}


f8e99dfffdee417c8a31ce782bedaa3b.png


这份代码的意义在于可以统计1S左右,我们的计算机能够将数据累计多少次。实际上这种方法是比较慢的,为什么?打印时是要进行输出的,输出是外设,外设IO较慢。如果没有打印:

#include <iostream>
#include <unistd.h> // 包含 sleep 头文件
#include <signal.h> // 包含 raise 头文件
#include <stdlib.h> // 包含 abort 头文件
using namespace std;
 
int cnt = 0;
void catchSig(int signo)
{
    cout<<"获取到一个信号,信号编号是:"<<cnt<<endl;
}
int main(int argc,char*argv[])
{
    //软件条件
    signal(SIGALRM,catchSig);
    alarm(1);
    while(true)
    {
        cnt++;
    }
}


306b240585f24e99849ff68b6ed0743c.png


总结:

“闹钟”其实就是用软件实现的,任意一个进程都可以通过alarm系统调用在内核中设置闹钟,那么OS内可能会存在很多的闹钟,OS则需要管理闹钟:先描述,再组织,所以OS内部设置闹钟的时候,要为闹钟创建特定的数据结构对象。


内核管理闹钟比如最大堆、最小堆:比如100个闹钟可以把100个闹钟的when建小堆,最小的就在堆顶,只要堆顶的没有超时那其余的自然没有超时,所以只需要检查堆顶即可,就可以管理好闹钟。

【Linux】进程信号(下):https://developer.aliyun.com/article/1565755

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

你好,我是AI助理

可以解答问题、推荐解决方案等

登录插画

登录以查看您的控制台资源

管理云资源
状态一览
快捷访问