IPC——信号

Posted kelamoyujuzhen

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了IPC——信号相关的知识,希望对你有一定的参考价值。

什么是信号

信号是一种通知进程某件事情发生了的一种通信机制,通过向进程发送某个信号,可以告诉进程发生了什么事情,进程收到这个信号后,就知道某事情发生了,进程可以做出相应的响应(处理)。与IPC中其他进程通信方式不同的是,信号属于不精确通信,信号只能告诉进程大概发生了什么事情,但是不能准确的告诉进程详细的细节信息。Linux下边定义了很多的信号,所有的信号都是一个整数编号,不过为了好辨识,Linux系统给这些整数编号都定义了对应的宏名,宏名都是以SIG开头,比如SIGABRT。

谁会向进程发信号

技术分享图片

总结起来,会有三个“人”会向进程发送信号,分别是“另一进程”、“OS内核”、“硬件”。

另一个进程发送信号

eg:在命令行终端窗口通过kill命令向某个进程发送一个信号将其终止。

kill PID

内核发送信号

发生了某个事件,Linux内核可能会发送该事件对应的信号给某个进程

eg:管道通信中,当所有读文件描述符被关闭,进程会被内核发送一个SIGPIPE信号,提示读管道出错了。

底层硬件发送信号

底层硬件发生了某个事件,会向进程发送对应的某个信号。

eg:按下ctrl+c按键终止进程时,内核收到ctrl+c按键后,会向正在运行的进程发送SIGINT信号,将其异常终止。

 注意:不管进程是被哪一个信号给终止了,只要是被信号终止的,都是异常终止。

一般来说,大多数发送信号的原因,都是因为内核、硬件发生了某些事件时,才会向某个进程发送该事件专用的信号,告诉该进程这个事件发生了。对于我们自己写的进程来说,其实更多是接收信号,而不是发送信号。

 

收到信号后进程如何应对

忽略

装疯卖傻的做法,进程就当信号从来没有发生过。

捕获

进程会调用相应的处理函数,进行相应的处理。

默认

如果不忽略也不捕获的话,此时进程会使用系统设置的默认处理方式来处理信号。

 

Linux系统有哪些信号

查看Linux系统下有哪些信号

技术分享图片
[[email protected] ~]# kill -l
 1) SIGHUP     2) SIGINT     3) SIGQUIT     4) SIGILL     5) SIGTRAP
 6) SIGABRT     7) SIGBUS     8) SIGFPE     9) SIGKILL    10) SIGUSR1
11) SIGSEGV    12) SIGUSR2    13) SIGPIPE    14) SIGALRM    15) SIGTERM
16) SIGSTKFLT    17) SIGCHLD    18) SIGCONT    19) SIGSTOP    20) SIGTSTP
21) SIGTTIN    22) SIGTTOU    23) SIGURG    24) SIGXCPU    25) SIGXFSZ
26) SIGVTALRM    27) SIGPROF    28) SIGWINCH    29) SIGIO    30) SIGPWR
31) SIGSYS    34) SIGRTMIN    35) SIGRTMIN+1    36) SIGRTMIN+2    37) SIGRTMIN+3
38) SIGRTMIN+4    39) SIGRTMIN+5    40) SIGRTMIN+6    41) SIGRTMIN+7    42) SIGRTMIN+8
43) SIGRTMIN+9    44) SIGRTMIN+10    45) SIGRTMIN+11    46) SIGRTMIN+12    47) SIGRTMIN+13
48) SIGRTMIN+14    49) SIGRTMIN+15    50) SIGRTMAX-14    51) SIGRTMAX-13    52) SIGRTMAX-12
53) SIGRTMAX-11    54) SIGRTMAX-10    55) SIGRTMAX-9    56) SIGRTMAX-8    57) SIGRTMAX-7
58) SIGRTMAX-6    59) SIGRTMAX-5    60) SIGRTMAX-4    61) SIGRTMAX-3    62) SIGRTMAX-2
63) SIGRTMAX-1    64) SIGRTMAX    
View Code

总共62个信号,也就是说每个进程可以接收的信号种类有62种,1~64为信号的编号,SIG***为信号的宏名。每个信号代表着某种事件,一般情况下,当进程收到某个信号时,就表示该信号所代表的事件发生了。

1~34:也不是所有的信号都要掌握,我们只需要关心其中常用的信号。

35~64:这些信号是Linux后期增设的信号,这些个信号不需要关心,所以不用了解。

常用信号

技术分享图片

 

常见由用户发出终止进程的信号

ctrl+c发送SIGINT

ctrl+发送SIGQUIT

kill pid发送SIGTERM

只有当进程有占用命令行终端时,才能crtl+c、ctrl+来终止。当无法使用ctrl+c、ctrl+来终止进程时,往往就使用kill命令来终止进程。

 

kill 和 pkill

kill这个命令名字很吓人。其实kill只是发送信号,至于进程会不会被终止,这就看信号的处理方式,处理方式如果是终止,那么就会终止进程。如果把kill起名为send估计更好理解些,因为kill所起到的作用只是发送信号。

发送信号的完整格式:kill  —信号编号  PID

信号编号写数字和宏名都可以。如果不写明信号编号的话:kill PID,默认发送的是15(SIGTERM)信号,等价于kill -SIGTERM PID或者kill -15 PID。只有发送15这个信号时才能省略信号编号,发送其它信号时必须写明信号编号。

 

pkill用法与kill差不多,只不过kill是按照PID来识别进程的,pkill是按照名字来识别进程的。

发送信号的完整格式:pkill -信号编号 名字

同样,如果不写明信号编号的话,默认发送的是15(SIGTERM)这个信号。



core文件

用于保存程序(进程)在当前结束的这一刻,进程在内存中的代码和数据,core文件可以用于分析进程在结束时的状况,不过由于进程代码和数据都是二进制的,所以把core文件直接打开后我们是看不懂的,一般需要特殊软件翻译后才能看懂。并不是所有的信号在终止进程时都会产生core文件,只有某个些信号在终止进程时才会产生core文件,不过一般情况下并不会创建这个文件,因为系统默认将产生core的设置给关闭了,只有打开后这个设置后才会保存core文件。所以当你看到提示core dumped,这就表示这个信号终止进程时,会产生core文件,只不过由于关闭了设置,因此core文件被丢弃了,dumped就是丢弃的意思。

[[email protected] ~]# ./a.out 
^Quit (core dumped)

 

Linux信号处理API

signal函数

函数原型

#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);

功能

设置某个信号的处理方式。处理方式可以被设置为忽略,捕获,默认。

进程的进程表(task_struct)中会有一个“信号处理方式登记表”,专门用于记录信号的处理方式,调用signal函数设置某个信号的处理方式时,会将信号的处理方式登记到该表中。每个进程拥有独立的task_struct结构体变量,因而每个进程的“信号处理方式登记表”都是独立的,所以每个进程对信号的处理方式自然也是独立的,互不干扰。

参数

signum:信号编号。handler个函数指针类型变量,函数指针类型是void (*)(int)

     这里涉及复杂指针的理解:参考     

指针理解——初步

指针理解——指针数组、数组指针、指针函数、函数指针

指针理解——复杂指针解析

handler可以取值如下:

技术分享图片
#define SIG_DFL    ((void (*)(int))0)    
#define SIG_IGN    ((void (*)(int))1)
#define SIG_ERR    ((void (*)(int))-1)
View Code

这几个宏定义在了<signal.h>头文件中。

①忽略:SIG_IGN

除了SIGKILL这个信号外,其它所有的信号都可被忽略和捕获。

②默认:SIG_DFL

③捕获:填写类型为void (*)(int)的捕获函数的地址,当信号发生时,会自动调用捕获函数来进行相应的处理。当然这个捕获函数需要我们自己来实现,捕获函数的int参数,用于接收信号编号。捕获函数也被称为信号处理函数。

技术分享图片
void signal_fun1(int signo)
{
    ...
}
                        
void signal_fun2(int signo)
{
    ...
}
                
int main(void)
{
    signal(SIGINT, signal_fun1);
    signal(SIGSEGV, signal_fun2);
                            
    return 0;
}
View Code

信号捕获函数调用时机:

进程接收到信号时就调用,调用时会中断进程的正常运行,当调用完毕后再会返回进程的正常运行。

更正前面的一个说法

在一开始提到:不管进程是被哪一个信号给终止了,只要是被信号终止的,都是异常终止。

这种表达方式不严谨,应该说如果是在信号处理函数里面调用exit、_exit来终止进程的这种方式的话,准确来讲不应该算是“”信号异常终止进程”的情况。信号异常终止进程,准确来讲指的是信号默认的终止方式,这种情况才是“异常终止”。

返回值

成功:返回上一次的处理方式

失败:返回宏值SIG_ERR,并且设置errno。

 

kill

函数原型

技术分享图片
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
View Code

功能

向PID所指向的进程发送指定的信号。

返回值

成功返回0,失败返回-1,errno被设置。

 raise

函数原型

技术分享图片
#include <signal.h>
int raise(int sig);
View Code

功能

向当前进程发送指定信号。

返回值

成功返回0,失败返回非0。

 

alarm

函数原型

技术分享图片
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
View Code

功能

设置一个定时时间,当所设置的时间到后,内核会向调用alarm的进程发送SIGALRM信号。SIGALRM的默认处理方式是终止。

测试代码

技术分享图片
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include<signal.h>
int main(int argc,char**argv,char**environ)
{
    alarm(5);
    while(1);
    return 0;
}
View Code

返回值

返回上一次调用alarm时所设置时间的剩余值。如果之前没有调用过alarm,又或者之前调用alarm所设置的时间早就到了,那么返回的剩余值就是0。

 

pause

函数原型

技术分享图片
#include <unistd.h>

int pause(void);
View Code

功能

调用该函数的进程会永久挂起(阻塞或者休眠),直至被信号(任意一个信号)唤醒为止。

返回值

只要一直处于休眠状态,表示pause函数一直是调用成功的。当被信号唤醒后会返回-1,表示失败了,errno的错误号被设置EINTR(表示函数被信号中断)。

 

abort

函数原型

技术分享图片
#include <stdlib.h>

void abort(void);
View Code

功能

向当前进程发一个SIGABRT信号,这个信号的默认处理方式是终止,因此如果不忽略和捕获的话,会将当前进程终止掉。abort相当于raise的特例,只发送SIGABRT信号自当前进程。这个函数有个绰号:自杀函数

返回值

以上是关于IPC——信号的主要内容,如果未能解决你的问题,请参考以下文章

用于IPC的命名信号的POSIX实现

使用IPC_PRIVATE信号量简单的例子

IPC信号

IPC——信号

IPC——线程信号问题

在 Linux 上使用信号的 IPC