前言
之前所有的传音术都需要双方为消息处理留出时间,有没有一种可以随时发送,不用考虑对方状态的手段呢?
信号
信号也是进程间通信方式的一种,他的名字和信号量有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
命令检测目前运行的进程:
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,我们现在要关闭这个进程,我们需要输入:
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的进程发送信号。这里我发送的信号是编号为-9
的SIGKILL
信号。刚刚我们通过按下ctrl + C
发送的SIGINT
信号则对应编号-2
。
之所以说是来自其他进程的信号,是因为我们输入这些指令后,shell主进程会识别指令并向对应PID的进程发送信号,所以信号不是来源于我们的输入,实际上还是来自于软件。
信号在程序中的应用
首先观察下面的代码,并运行:
#include <stdio.h>
#include <unistd.h>
int main() {
while(1) {
sleep(1);
printf("hello world\n");
}
return 0;
}
编译后执行结果为:
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进程会收到-2
的SIGINT
信号,从而终止程序。我们说过,信号和中断很像,那我我们能不能自己定义一个信号处理函数来执行一些我们想要的操作呢?
答案是:可以的。
我们包含signal.h
文件,编写代码如下:
#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;
}
执行程序:
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
函数就是一个信号处理函数的注册接口,我们来看一下他的定义:
extern __sighandler_t signal (int __sig, __sighandler_t __handler)
__sig
:想处理的信号的标号
__handler
:信号处理句柄,这是一个函数指针,返回值为void
,参数列表为int
:
typedef void (*__sighandler_t) (int);
当我们不满足系统对信号提供的默认操作时,我们就可以使用signal
接口注册我们想要的信号处理函数。
接下来我将给大家讲解一个实际的例子来为大家展示信号的作用:
在之前的文字中,我对于共享资源包括共享内存,消息队列,信号量总是只创建不回收,因为我们总是以ctrl + C
的方式退出进程。之后使用ipcrm -a
手动清理。如果我们在信号处理函数中加入一些逻辑回收资源,那么我们的程序将更加健壮。
对于下面的代码:
#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
退出,发现信号量仍然存在
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
信号处理函数,并在信号处理函数中回收信号量:
#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
停止,并参看信号量状态,发现信号量已经被回收。
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
小结
这篇文章我们以单片机中断为切入点,介绍了信号的概念,并把我们自然的一些操作涉及到的信号的知识进行了梳理,希望对大家有所帮助。
学了这节课,大家可以去把之前写的一些代码修改一下,回收资源,让代码有更好的性能。