【Linux】进程信号“疑问?坤叫算信号吗?“(上)

简介: 【Linux】进程信号“疑问?坤叫算信号吗?“(上)

前言



信号在我们生活中很常见,下面我们举一举生活中信号的例子:


你在网上买了很多件商品,再等待不同商品快递的到来。但即便快递没有到来,你也知道快递来临时,你该怎么处理快递。也就是你能“ 识别快递 ”

当快递员到了你楼下,你也收到快递到来的通知,但是你正在打游戏,需 5min 之后才能去取快递。那么在在这5min 之内,你并没有下去去取快递,但是你是知道有快递到来了。也就是取快递的行为并不是一定要立即执行,可以理解成“ 在合适的时候去取 ” 。


在收到通知,再到你拿到快递期间,是有一个时间窗口的,在这段时间,你并没有拿到快递,但是你知道有一个快递已经来了。本质上是你“ 记住了有一个快递要去取 ”

当你时间合适,顺利拿到快递之后,就要开始处理快递了。而处理快递一般方式有三种: 1. 执行默认动作(幸福的打开快递,使用商品)2. 执行自定义动作(快递是零食,你要送给你的朋友) 3. 忽略快递(快递拿上来之后,扔掉床头,继续睡觉)

快递到来的整个过程,对你来讲是异步的,你不能准确断定快递员什么时候给你打电话

在讲进程信号之前我们先引入四个重要的概念:


1.互斥,任何一个时刻,都只允许一个执行流在进行共享资源的访问(这样的操作可以通过加锁来实现)

2.我们把任何一个时刻,都只允许一个执行流在进行访问的共享资源,叫做临界资源。

3.临界资源是要通过代码访问的,凡是访问临界资源的代码,叫做临界区。

4.原子性 :只有两种确定状态的属性  (就比如1和0,能存在中间值0.5)


一、认识信号量



感性的认识:


信号量也被称为信号灯,本质上就是一个描述资源数量计数器,下面我们举个生活中的例子来理解信号量:


在生活中我们会去电影院看电影,但是在看电影之前我们必须先买票,而买票的本质功能有两个,第一个是对座位资源的预订机制,第二个是确保不会因为多放出去特定的座位资源而导致座位冲突。而信号量其实对应的就是买票,因为任何一个执行流,想访问临界资源中的任何一个子资源的时候是不能直接访问的,必须得先申请信号量资源(也就是买票),而我们前面说过信号量的本质就是个计数器,所以我们在申请信号量资源的时候只需要让这个计数器加加或减减即可(如果申请成功,那么计数器需要--,因为我们的信号量资源少了一个。如果申请成功后不想用了,那么就让计数器++,代表有人将我们的信号量资源归还了)。也就是说只要我们申请信号量成功,我就一定能在未来拿到一个子资源。同样的例子,如果我们的电影院只有一个座位仅供专属VIP座,那么这个情况就叫做互斥,因为在这期间只有一个VIP能使用这个座位,没有其他的人来抢座位。刚刚我们说了信号量本质是个计数器,既然是计数器就必须让所有的进程都看到,否则无法保证自己的操作是原子的。可以理解为:让不同的进程看到同一份资源(这个资源就是信号量)。


下面我们来认识一下信号量的接口:


首先第一个接口是获取信号量semget:

6b3ab19c7ef94540b2d270d475ed6f4a.png


如果看了我们上一篇共享内存的文章的话,一定可以认识semget这个接口的参数,因为和获取共享内存接口shmget一模一样。第二个参数nsems的含义是代表信号量的个数,也就是说我们一次可以申请多个信号量。要查看我们的信号量的命令是ipcs -s:


961492a8ad42485189ae455bbb806166.png


同样和共享内存一样,删除某个信号量的指令是ipcrm -s +semid。下面我们看看删除信号量的系统调用接口,不出意外的话就是semctl这个函数了:


163e52333e3e4b72a91bd0fe6039aab2.png


这个函数与共享内存的删除接口不一样的地方是多了一个可变参数列表,第二个参数semnum是代表对哪一个信号量做操作(因为刚刚我们说过了可以同时申请多个信号量)。


semop这个函数可以完成对信号量的计数器-1+1操作:


efc8ba3bc94c482e88c41f35f19c9ab3.png


这个函数的第二个参数结构体就是完成我们对信号量的-1+1操作的,下面我们看看这个结构体:


923f6c5d2d3d4ffebba15df6639cfb18.png


比如说我们要对一个信号量做减操作,那么就可以在num这个下标填0(num是一个数组),sem_op填-1(因为要减减),flag默认即可。


对于信号量的接口我们差不多已经看完了,下面我们来理解一下IPC:


2b6abc3ae163436ba749c2e6098c75e5.png


我们可以发现不管是共享内存还是信号量,系统用来描述他们的结构体都是XXXid_ds:


那么操作系统是分开管理这些IPC资源的还是一起管理的呢?


a6d722bf0fc640f2baed1f301d2aa91b.png


我们以左边三个结构体为例,在操作系统中有一个这个结构体类型的指针数组,这个数组按下标依次存放右边三个不同的ipc结构体的地址,对于这个指针数组来讲,要保存其他类型的ipc结构体只需要将这个结构体类型强转为系统用于管理的这个结构体指针类型,这样就完成了将内核中的所有ipc资源统一以数组的方式进行管理。以上就是操作系统管理这个IPC资源的原理,上面的操作不知道有没有看出是什么原理呢,其实这就是多态!


二、信号的产生



红绿灯,闹钟,下课铃都是信号,而这些信号被看懂前是需要我们被培养过,比如说有人告诉我们红灯停,所以我们知道红灯要停下,我们可以把进程比作自己,信号就是一个数字,进程在没有收到信号的时候其实进程早就知道该如何处理信号了(因为这是程序员教的,程序员写代码让进程认识信号),而由于信号可能会随时产生,所以在信号产生前,进程可能在做优先级更高的事情,这个时候进程是可以不用立马处理这个信号的,但是要在后续合适的时间处理刚刚没有处理的信号,由于这样的原因所以我们必须将信号保存起来,这样即使当时没有处理信号也能在后续的时间处理这个信号。总结:进程收到信号的时候,如果没有立马处理这个信号,需要进程具有记录信号的能力。


首先我们要知道查看信号的命令  kill -l:


981aa97971e8468789b66312d06bda99.png


在这些信号中,只有1-31是我们要学的,因为1-31叫做基本信号,34-64叫做实时信号,而我们现在的操作系统都是分时的,所以我们只学习基本信号。因为信号的产生对于进程来说是异步的,那么进程该如何记录对应产生的信号呢?答案是先描述再组织。怎么描述呢?简单的说0 1就能描述一个信号,用位图来管理这个信号。如下图:


8cb418d7402840fc82ee100a24ec92a7.png


下面我们用代码来对信号进行简单的测试:


54c0c57a806e4472b91f87d4bae1498d.pnge1a85da0370b4cba862b5489a385cf3f.png


#include <iostream>
#include <unistd.h>
int main()
{
    while (true)
    {
        std::cout<<"我是一个进程,我正在运行 ...,pid:"<<getpid()<<std::endl;
        sleep(1);
    }
    return 0;
}

下面我们将程序运行起来试一试信号:

b2ed8714ad49492d943e753bb25a2421.png712863148c1b43c1a14c4037a95b2466.png


首先我们看到的现象是我们成功用9号信号杀死了一个进程,这就是通过指令的方式发信号。


当然对于前台进程而言,我们可以从键盘上输入ctrl +c 终止前台进程:


7441f836826845ef9bcb8ecb9891d979.png


而如何将一个进程变为后台进程我们也说过了,就是在后面加上&符号:


985e67eb0692434e97fec0d9e58ce4a4.png


后台进程是无法被ctrl+c这样的命令杀死的,所以最后我们用kill-9杀死了这个进程。其实ctrl+c也是操作系统像进程发信号,只不过我们看不到,下面我们通过signal函数的方式查看操作系统给进程发的信号:


b742e5d83213421bac0f03b101512ebf.png


signal这个函数的第一个参数为信号编号,第二个参数为如果操作系统像这个进程发了一个信号,这个函数会将这个信号拿走用于自定义的功能,而不是再像以前一样听取操作系统的指令。下面我们演示一下:


#include <iostream>
#include <unistd.h>
#include <signal.h>
void handler(int sig)
{
    std::cout<<"get a signal: "<<sig<<std::endl;
}
int main()
{
    signal(2,handler);
    while (true)
    {
        std::cout<<"我是一个进程,我正在运行 ...,pid:"<<getpid()<<std::endl;
        sleep(1);
    }
    return 0;
}


这段代码的意思是当操作系统向我们发送2号信号的时候(ctrl + c 就发送的2号信号)我们不在执行原来的终止程序,而是去打印出来signal信号:


eeb546c7317f46a8aa61609d0c07e5cc.png6a6a2100360249bf8f0a4d9ed62764f1.png


也就是说我们通过signal函数成功捕获了操作系统向我们发送的2号信号(这个2号信号就是我们按下ctrl+c的时候操作系统转化为信号发送给进程的)当然如果上面的图片还是没看懂那么我们也可以这样:


5249610fad29491387b9cfaa613abee2.pnge1100cded977489f986729c6e3766fc4.png


下面这两张图就清楚的证明了我们发送的ctrl+c信号就是2号信号,因为我们发送2号信号不会中断程序,ctrl+c也不会中断程序。下面我们要说一下,在我们用回调函数的时候,就像我们上面的代码,在调用signal函数的时候是不会调用handler函数的,这里只是更改了2号信号的处理动作,并没有调用handler方法。比如下面这样:


a1e784f4f69744df96a27ed762f65d25.png


在我们调用show方法的时候是不会调用print的函数的,下面我们将代码运行起来:


7b950eeaf8ae4dfdb98b9404407d5c41.png81487d5070694aacb7081be81fc703d1.png


我们可以看到只打印了hello show,那么如何在调用show的时候还调用print函数呢?其实很简单,在show中调用函数指针即可:


7353b685792f4df5bd04aed4d792ed41.png

c03ca9517ebb4a6392d448b3bc1a81dd.png


这也就证明了我们调用signal函数的时候是不会调用handler函数的。


下面我们将所有信号都自定义捕捉,这样是不是这个进程就无敌了没有指令可以杀掉这个进程了呢?


int main()
{
    //signal(2,handler);
    for (int i = 1;i<=31;i++)
    {
        signal(i,handler);
    }
    while (true)
    {
        std::cout<<"我是一个进程,我正在运行 ...,pid:"<<getpid()<<std::endl;
        sleep(1);
    }
    return 0;
}


12654fea68934e2b91b4ae66b01e0dbb.png9ada386c1a0840049c40eb794560c7ad.png


我们可以看到其他信号确实都被捕捉了,但是kill -9还是会杀掉进程,因为操作系统不允许有进程不被杀死。


下面我们讲一下信号的产生原理:


我们平时在输入的时候,计算机怎么知道我从键盘输入了数据呢?键盘是通过硬件中断的方式通知系统我们的键盘已经被按下了。:


f1c157ce4c3c4f0c98ee8f04bf1b8e92.png


上图中的圆圈代表CPU,边上的毛代表CPU的针脚,而键盘会通过中断控制器找到对应与CPU的针脚:


3ecd13eb79ad4250b1bac56e418df0d2.png


当我们从键盘输入指令后cpu的寄存器会存储键盘的中断号,然后CPU通过中断号去中断向量表中查找与之中断号相对应的函数方法,这样就完成了我们从键盘输入ctrl + c然后转化为2号信号并且杀死进程的操作。


下面我们将上面所讲的知识先小小的总结一下:


1. 用户输入命令,在Shell下启动一个前台进程。

.

用户按下Ctrl-C ,这个键盘输入产生一个硬件中断,被OS获取,解释成信号,发送给目标前台进程

.

前台进程因为收到信号,进而引起进程退出。

2. Ctrl-C 产生的信号只能发给前台进程。一个命令后面加个&可以放到后台运行,这样Shell不必等待进程结束就可以接受新的命令,启动新的进程。

3. Shell可以同时运行一个前台进程和任意多个后台进程,只有前台进程才能接到像 Ctrl-C 这种控制键产生的信号。

4. 前台进程在运行过程中用户随时可能按下 Ctrl-C 而产生一个信号,也就是说该进程的用户空间代码执行到任何地方都有可能收到 SIGINT 信号而终止,所以信号相对于进程的控制流程来说是异步

(Asynchronous)的。

当然除了上面我们用数字当信号,也可以用宏来使用:


3d53e9fa385c45d1802f15c393797b63.png242414a86a2840018e5c5f6861deff86.png413d4afcf5944bef97ce7393a150bd48.png


856b70ae3fda47f2894bff02ba58c6db.png

目录
打赏
0
0
0
0
0
分享
相关文章
【Linux进程概念】—— 操作系统中的“生命体”,计算机里的“多线程”
在计算机系统的底层架构中,操作系统肩负着资源管理与任务调度的重任。当我们启动各类应用程序时,其背后复杂的运作机制便悄然展开。程序,作为静态的指令集合,如何在系统中实现动态执行?本文带你一探究竟!
【Linux进程概念】—— 操作系统中的“生命体”,计算机里的“多线程”
从零开始掌握进程间通信:管道、信号、消息队列、共享内存大揭秘
本文详细介绍了进程间通信(IPC)的六种主要方式:管道、信号、消息队列、共享内存、信号量和套接字。每种方式都有其特点和适用场景,如管道适用于父子进程间的通信,消息队列能传递结构化数据,共享内存提供高速数据交换,信号量用于同步控制,套接字支持跨网络通信。通过对比和分析,帮助读者理解并选择合适的IPC机制,以提高系统性能和可靠性。
255 14
【Linux】进程IO|系统调用|open|write|文件描述符fd|封装|理解一切皆文件
本文详细介绍了Linux中的进程IO与系统调用,包括 `open`、`write`、`read`和 `close`函数及其用法,解释了文件描述符(fd)的概念,并深入探讨了Linux中的“一切皆文件”思想。这种设计极大地简化了系统编程,使得处理不同类型的IO设备变得更加一致和简单。通过本文的学习,您应该能够更好地理解和应用Linux中的进程IO操作,提高系统编程的效率和能力。
78 34
|
13天前
|
Linux:守护进程(进程组、会话和守护进程)
守护进程在 Linux 系统中扮演着重要角色,通过后台执行关键任务和服务,确保系统的稳定运行。理解进程组和会话的概念,是正确创建和管理守护进程的基础。使用现代的 `systemd` 或传统的 `init.d` 方法,可以有效地管理守护进程,提升系统的可靠性和可维护性。希望本文能帮助读者深入理解并掌握 Linux 守护进程的相关知识。
28 7
|
12天前
|
Linux 进程前台后台切换与作业控制
进程前台/后台切换及作业控制简介: 在 Shell 中,启动的程序默认为前台进程,会占用终端直到执行完毕。例如,执行 `./shella.sh` 时,终端会被占用。为避免不便,可将命令放到后台运行,如 `./shella.sh &`,此时终端命令行立即返回,可继续输入其他命令。 常用作业控制命令: - `fg %1`:将后台作业切换到前台。 - `Ctrl + Z`:暂停前台作业并放到后台。 - `bg %1`:让暂停的后台作业继续执行。 - `kill %1`:终止后台作业。 优先级调整:
32 5
Linux 进程管理基础
Linux 进程是操作系统中运行程序的实例,彼此隔离以确保安全性和稳定性。常用命令查看和管理进程:`ps` 显示当前终端会话相关进程;`ps aux` 和 `ps -ef` 显示所有进程信息;`ps -u username` 查看特定用户进程;`ps -e | grep &lt;进程名&gt;` 查找特定进程;`ps -p &lt;PID&gt;` 查看指定 PID 的进程详情。终止进程可用 `kill &lt;PID&gt;` 或 `pkill &lt;进程名&gt;`,强制终止加 `-9` 选项。
20 3
|
1月前
|
Linux编程: 在业务线程中注册和处理Linux信号
本文详细介绍了如何在Linux中通过在业务线程中注册和处理信号。我们讨论了信号的基本概念,并通过完整的代码示例展示了在业务线程中注册和处理信号的方法。通过正确地使用信号处理机制,可以提高程序的健壮性和响应能力。希望本文能帮助您更好地理解和应用Linux信号处理,提高开发效率和代码质量。
49 17
|
1月前
|
Linux编程: 在业务线程中注册和处理Linux信号
通过本文,您可以了解如何在业务线程中注册和处理Linux信号。正确处理信号可以提高程序的健壮性和稳定性。希望这些内容能帮助您更好地理解和应用Linux信号处理机制。
60 26
c++ linux通过实现独立进程之间的通信和传递字符串 demo
的进程间通信机制,适用于父子进程之间的数据传输。希望本文能帮助您更好地理解和应用Linux管道,提升开发效率。 在实际开发中,除了管道,还可以根据具体需求选择消息队列、共享内存、套接字等其他进程间通信方
68 16
Linux:进程间通信(共享内存详细讲解以及小项目使用和相关指令、消息队列、信号量)
通过上述讲解和代码示例,您可以理解和实现Linux系统中的进程间通信机制,包括共享内存、消息队列和信号量。这些机制在实际开发中非常重要,能够提高系统的并发处理能力和数据通信效率。希望本文能为您的学习和开发提供实用的指导和帮助。
186 20
AI助理

你好,我是AI助理

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