linux多线(进)程编程——(10)信号

前言

之前所有的传音术都需要双方为消息处理留出时间,有没有一种可以随时发送,不用考虑对方状态的手段呢?

信号

信号也是进程间通信方式的一种,他的名字和信号量有2/3的相似度,但是实际上它们两个的关系就好比狸花猫和大熊猫,都是猫但是毫不相干。信号量和信号也是如此。

如何通俗易懂的理解信号量呢?

对于有过嵌入式裸机(例如51单片机,STM32单片机)开发经验的读者,我说一个名词大家就可能会知道了。这个名词就是:中断

这里简单说一下中断:就是当一个外部事件发生,例如单片机的GPIO引脚发生电平变化,硬件检测到这种变化后就会切断GPU当前执行成程序,保护现场后进入到一个特定的代码段去执行自定的函数。我们一般把这段代码叫中断服务程序。

在终端使用信号

相较于我们之前几节都是使用C语言代码,这节课我们通过终端shell来讲解,因为信号一般就是用在shell脚本中的。(C语言代码一会也会讲,大家别急)

linux中信号一般来源于多进程的程序,其中一个进程向另一个进程发送信号。除此之外,信号还来源于我们的外界输入,例如我们在键盘上按下指定的按键组合。我将为大家展示两种用法。

(1)外界输入的信号

我们首先给大家介绍一个shell命令:top,这个命令用于监测各个进程的资源调用情况,类似于Windows下的任务管理器。在终端键入top命令后,系统就会开启一个进程用于检测系统资源。

如下图所示,我输入top指令后,开启了top进程

现在我们要停止top进程 ,我们在键盘上输入ctrl + C,此时这个进程就会被直接中断。这个过程相信大家都很熟悉了。实际上,在我们键盘按下ctrl + C的时候,top进程会收到一个SIGINT的信号,表示中断当前进程的执行。这就是由我们外界输入触发的信号。

(2)来自其他进程的信号

我们再次运行top指令,不同的是这次我们在后台运行,并通过ps命令检测目前运行的进程:

bash 复制代码
hyl@hylPC:~/Desktop/Proj/LinuxMultiProcessProj$ top&
[1] 6745
hyl@hylPC:~/Desktop/Proj/LinuxMultiProcessProj$ ps

[1]+  Stopped                 top
    PID TTY          TIME CMD
   2973 pts/0    00:00:00 bash
   6745 pts/0    00:00:00 top
   6766 pts/0    00:00:00 ps

可以看到top进程的PID是6745,我们现在要关闭这个进程,我们需要输入:

bash 复制代码
hyl@hylPC:~/Desktop/Proj/LinuxMultiProcessProj$ kill -9 6745
[1]+  Killed                  top
hyl@hylPC:~/Desktop/Proj/LinuxMultiProcessProj$ ps
    PID TTY          TIME CMD
   2973 pts/0    00:00:00 bash
   6886 pts/0    00:00:00 ps

这里我们通过kill指令强制杀掉进程。容易引起歧义的是:实际上杀掉进程的不是kill指令,而是-9所代表的信号。这里kill也不是杀死的意思,而是一个shell指令,用于向指定PID的进程发送信号。这里我发送的信号是编号为-9SIGKILL信号。刚刚我们通过按下ctrl + C发送的SIGINT信号则对应编号-2

之所以说是来自其他进程的信号,是因为我们输入这些指令后,shell主进程会识别指令并向对应PID的进程发送信号,所以信号不是来源于我们的输入,实际上还是来自于软件。

信号在程序中的应用

首先观察下面的代码,并运行:

c 复制代码
#include <stdio.h>
#include <unistd.h>

int main() {
    while(1) {
        sleep(1);
        printf("hello world\n");
    }
    return 0;
}

编译后执行结果为:

bash 复制代码
hyl@hylPC:~/Desktop/test$ gcc -o main main.c
hyl@hylPC:~/Desktop/test$ ./main
hello world
hello world
hello world
^C
hyl@hylPC:~/Desktop/test$ 

这里我们已经知道了按下ctrl + C后我们的main进程会收到-2SIGINT信号,从而终止程序。我们说过,信号和中断很像,那我我们能不能自己定义一个信号处理函数来执行一些我们想要的操作呢?

答案是:可以的。

我们包含signal.h文件,编写代码如下:

c 复制代码
#include <stdio.h>
#include <unistd.h>
#include <signal.h>

void MySignalProc(int sigid) {
    printf("\nCtrl + C is pressed, the para sigid = %d\n", sigid);
}

int main() {
    signal(SIGINT, MySignalProc);

    while(1) {
        sleep(1);
        printf("hello world\n");
    }
    return 0;
}

执行程序:

bash 复制代码
hyl@hylPC:~/Desktop/test$ gcc -o main main.c
hyl@hylPC:~/Desktop/test$ ./main
hello world
hello world
hello world
^C
Ctrl + C is pressed, the para sigid = 2
hello world
^C
Ctrl + C is pressed, the para sigid = 2
hello world
^C
Ctrl + C is pressed, the para sigid = 2
hello world
hello world
hello world
^Z
[1]+  Stopped                 ./main
hyl@hylPC:~/Desktop/test$ 

可以看到,结果我们上述出操作后,再次按下ctrl + C,系统会打印对应的字符串,但是进程就无法退出了,之后我们使用了ctrl + Z来暂停进程才得以脱困(之后要使用kill -9 [PID]进行清理)。

关sigid参数,大家应该有一个猜想,是的,sigid就是信号的标号,SIGINT对应的是-2,因此sigid的值也是2。

这里的signal函数就是一个信号处理函数的注册接口,我们来看一下他的定义:

c 复制代码
extern __sighandler_t signal (int __sig, __sighandler_t __handler)

__sig:想处理的信号的标号
__handler:信号处理句柄,这是一个函数指针,返回值为void,参数列表为int

c 复制代码
typedef void (*__sighandler_t) (int);

当我们不满足系统对信号提供的默认操作时,我们就可以使用signal接口注册我们想要的信号处理函数。

接下来我将给大家讲解一个实际的例子来为大家展示信号的作用:

在之前的文字中,我对于共享资源包括共享内存,消息队列,信号量总是只创建不回收,因为我们总是以ctrl + C的方式退出进程。之后使用ipcrm -a手动清理。如果我们在信号处理函数中加入一些逻辑回收资源,那么我们的程序将更加健壮。

对于下面的代码:

c 复制代码
#include <stdio.h>
#include <unistd.h>
#include <sys/sem.h>

int main() {
    int semid = semget(1, 1, 0666 | IPC_CREAT);
    while(1);
    return 0;
}

编译后运行,使用ctrl + C退出,发现信号量仍然存在

bash 复制代码
hyl@hylPC:~/Desktop/test$ gcc -o main main.c
hyl@hylPC:~/Desktop/test$ ./main
^C
hyl@hylPC:~/Desktop/test$ ipcs

------ Message Queues --------
key        msqid      owner      perms      used-bytes   messages    

------ Shared Memory Segments --------
key        shmid      owner      perms      bytes      nattch     status      

------ Semaphore Arrays --------
key        semid      owner      perms      nsems     
0x00000001 8          hyl        666        1     

我们注册SIGINT信号处理函数,并在信号处理函数中回收信号量:

c 复制代码
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <sys/sem.h>
#include <stdlib.h>

int semid;

void func(int sigid) {
    semctl(semid, 0, IPC_RMID);
    printf("\nsem has been deleted\n");
    exit(0);
}

int main() {
    semid = semget(1, 1, 0666 | IPC_CREAT);
    signal(SIGINT, func);
    while(1);
    return 0;
}

程序执行后,使用ctrl + C停止,并参看信号量状态,发现信号量已经被回收。

bash 复制代码
hyl@hylPC:~/Desktop/test$ gcc -o main main.c
hyl@hylPC:~/Desktop/test$ ./main
^C
sem has been deleted
hyl@hylPC:~/Desktop/test$ ipcs

------ Message Queues --------
key        msqid      owner      perms      used-bytes   messages    

------ Shared Memory Segments --------
key        shmid      owner      perms      bytes      nattch     status      

------ Semaphore Arrays --------
key        semid      owner      perms      nsems     

hyl@hylPC:~/Desktop/test$ ^C

小结

这篇文章我们以单片机中断为切入点,介绍了信号的概念,并把我们自然的一些操作涉及到的信号的知识进行了梳理,希望对大家有所帮助。

学了这节课,大家可以去把之前写的一些代码修改一下,回收资源,让代码有更好的性能。

相关推荐
I_Scholar30 分钟前
mysql-5.7.24-linux-glibc2.12-x86_64.tar.gz的下载安装和使用
linux·mysql
ziqibit33 分钟前
Linux安全清理删除目录bash脚本
linux·bash
Cuit小唐43 分钟前
C++ 单例模式详解
开发语言·c++·单例模式
imhikaru1 小时前
Linux Shell 重定向与管道符号(>, >>, |)的实现机制
linux·服务器·网络
张三和李四的家1 小时前
ubuntu的libc 库被我 sudo apt-get --reinstall install libc6搞没了
linux·ubuntu
whoarethenext1 小时前
linux的时间轮
linux·运维·linq·时间轮
黑不溜秋的1 小时前
驱动开发系列54 - Linux Graphics QXL显卡驱动代码分析(一)设备初始化
linux·服务器·qemu·qxl·虚拟显卡
独行soc2 小时前
2025年渗透测试面试题总结-拷打题库35(题目+回答)
linux·运维·服务器·python·网络安全·面试·职场和发展
爱吃巧克力的程序媛2 小时前
c++ 二级指针 vs 指针引用
c++
mahuifa2 小时前
(36)VTK C++开发示例 ---纹理贴图四边形
c++·vtk·cmake·贴图·3d开发