信号
信号
1 进程间通信概述
进程间通信(IPC:Inter Processes Communication)
进程是一个独立的资源分配单元,不同进程(这里所说的进程通常指的是用户进程)之间的资源是独立的,没有关联,不能在一个进程中直接访问另一个进程的资源(例如打开的文件描述符)
进程不是孤立的,不同的进程需要进行信息的交互和状态的传递等,因此需要进程间通信。
进程间通信功能:
数据传输:一个进程需要将它的数据发送给另一个进程
资源共享:多个进程之间共享同样的资源。
通知事件:一个进程需要向另一个或一组进程发送消息,通知 它们发生了某种事件
进程控制:有些进程希望完全控制另一个进程的执行(如 Debug进程),此时控制进程希望能够拦截另一个进程的所有 操作,并能够及时知道它的状态改变
Linux操作系统支持的主要进程间通信的通信机制:
2 信号的概念
信号是Linux进程间通信的最古老的方式
信号是软中断,它是在软件层次上对中断机制的一种模拟,是一种异步通信的方式。
信号可以直接进行用户空间进程和内核空间的交互,内核进程可以利用它来通知用户空间进程发生了哪些系统事件
信号的特点:
简单,不能携带大量信息,满足某个特设条件才发出
每个信号的名字都以字符SIG开头。
每个信号和一个数字编码相对应,在头文件signum.h中,这些信号都被定义为正整数。
在Linux下,要想查看这些信号和编码的对应关系,可使用命令:kill -l
不存在编号为0的信号。其中1-31号信号称之为常规信号(也叫普通信号或标准信号),34-64称之为实时信号。
以下条件可以产生一个信号:
1. 当用户按某些终端键时,将产生信号,如ctrl+c
2. 硬件异常将产生信号。如除数为0,无效的内存访问等
3. 软件异常将产生信号。如当检测到某种软件条件已发生,并将其通知有关进程时,产生信号
4. 调用kill函数将发送信号
5. 运行kill命令将发送信号
一个进程收到一个信号的时候,可以用如下方法进行处理:
1. 执行系统默认动作
2. 忽略此信号
3. 执行自定义信号处理函数
【注意】SIGKILL和SIGSTOP不能更改信号的处理方式,因为它们向用户提供了一种使进程终止的可靠方法。
3 信号的基本操作
3.1 kill函数
给指定进程发送信号
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid,int signum);
signum:信号的编号,一般使用宏名(SGIKILL、SIGQUIT等)。
pid的取值有4种情况:
pid>0:将信号传送给进程ID为pid的进程。
pid=0:将信号传送给当前进程所在进程组中的所有进程。
pid=-1:将信号传送给系统内所有的进程。【不要轻易使用】
pid<-1:将信号传给指定进程组的所有进程。这个进程组号等于pid的绝对值。
【注意】使用kill函数发送信号,接收信号进程和发送信号进程的所有者必须相同,或者发送信号进程的所有者是超级用户。
3.2 alarm函数
在seconds秒后,向调用进程发送一个SIGALRM信号,SIGALRM信号的默认动作是终止调用alarm函数的进程【非阻塞的】
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
返回值:
若以前没有设置过定时器,或设置的定时器已超时,返回0:
否则返回定时器剩余的秒数,并重新设定定时器。
3.3 raise函数
给调用进程本身发送一个信号
#include <signal.h>
int raise(int signum);
返回值:成功返回0,失败返回-1。
3.4 abort函数
向进程发送一个SIGABRT信号,默认情况下进程会退出
#include <stdlib.h>
void abort(void);
【注意】即使SIGABRT信号被加入阻塞集,一旦进程调用了abort函数,进程也还是会被终止,且在终止前会刷新缓冲区,关文件描述符。
3.5 pause函数
将调用进程挂起直至捕捉到信号为止,这个函数通常用于判断信号是否已到
#include <unistd.h>
int pause(void);
返回值:直到捕获到信号,pause函数才返回-1,且errno被设置成EINTR。
3.6 处理信号
程序中可用函数signal()改变信号的处理方式
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum,sighandler_t handler);
注册信号屏蔽处理函数(不可用于SIGKILL、SIGSTOP信号),即确定收到信号后处理函数的入口地址。
handler的取值:
忽略该信号: SIG_IGN
执行系统默认动作:SIG_DFL
自定义信号处理函数:信号处理函数名
返回值:
成功:返回函数地址,该地址为此信号上一次注册的信号处理函数的地址
失败:返回 SIG_ERR
3.7 sigaction函数
检查或修改指定信号的设置(或同时执行这两种操作)
使用sigaction()处理信号时,必须在第一行声明宏
#define_XOPEN_SOURCE 700
#include <signal.h>
int sigaction(int signum,const struct sigaction *act,struct sigaction *oldact);
参数:
signum 要操作的信号。
act 要设置的对信号的新处理方式(传入参数)
oldact 原来对信号的处理方式(传出参数)
如果act指针非空,则要改变指定信号的处理方式(设置),如果oldact指针非空,则系统将此前指定信号的处理方式存入oldact,可以为NULL
返回值:成功:0 失败:-1
struct sigaction
{
void (*sa_handler)(int); //旧的信号处理函数指针
void (*sa_sigaction)(int,siginfo_t*,void *);//新的信号处理函数指针
sigset_t sa_mask;//信号阻塞集
int sa_flags;//信号处理的方式
void (*sa_restorer)(void);//已弃用
};
1)sa_handler、sa_sigaction:信号处理函数指针,和signal()里的函数指针用法一样,应根据情况给sa_sigaciton、sa_handler两者之一赋值,其取值如下
a)SIG_IGN:忽略该信号
b)SIG_DFL:执行系统默认动作
c)处理函数名:自定义信号处理函数
2)sa_mask:信号阻塞集,在信号处理函数执行过程中,临时屏蔽指定的信号
3)sa_flags:用于指定信号处理的行为,通常设置为0,表使用默认属性。它可以是以下值的"按位或"组合:
SA_RESTART:使被信号打断的系统调用自动重新发起(已经废弃)
SA_NOCLDSTOP:使父进程在它的子进程暂停或继续运行时不会收到SIGCHLD信号。
SA_NOCLDWAIT:使父进程在它的子进程退出时不会收到SIGCHLD信号,这时子进程如果退出也不会成为僵尸进程。
SA_NODEFER:使对信号的屏蔽无效,即在信号处理函数执行期间仍能发出这个信号。
SA_RESETHAND:信号处理之后重新设置为默认的处理方式。
SA_SIGINFO:使用sa_sigaction成员而不是sa_handler作为信号处理函数
信号处理函数:
void(*sa_sigaction)(int signum,siginfo_t *info,void *context);
参数说明:
signum:信号的编号。
info:记录信号发送进程信息的结构体
context:可以赋给指向ucontext_t类型的一个对象的指针,以引用在传递信号时被中断的接收进程。
4 可重入函数
可重入函数是指函数可以由多个任务并发使用,而不必担心数据错误
编写可重入函数:
1.不使用(返回)静态的数据、全局变量(除非用信号量互斥)。
2.不调用动态内存分配、释放的函数
3.不调用任何不可重入的函数(如标准I/O函数)。
即使信号处理函数使用的都是可重入函数(常见的可重入函数),也要注意进入处理函数时,首先要保存errno的值,结束时,再恢复原值。因为,信号处理过程中,errno值随时可能被改变。
常见的可重入函数列表:
5 信号集
5.1 信号集概述
一个用户进程常常需要对多个信号做出处理,为了方便对多个信号进行处理,在Linux系统中引入了信号集
在PCB中有两个非常重要的信号集,一个称为“阻塞信号集”,另一个称之为“未决信号集”。这两个信号集都是内核使用位图机制来实现的。但操作系统不允许我们直接对其进行位操作。而需自定义另外一个集合,借助信号集操作函数来对PCB中的这两个信号集进行修改。
信号集是用来表示多个信号的数据类型。
信号集数据类型:sigset_t
定义路径:/usr/include/x86_64-linux-gnu/bits/sigset.h
信号集相关的操作主要有如下几个函数:
sigemptyset sigfillset sigismembr sigaddset
sigdelset
5.2 sigemptyset函数
初始化由set指向的信号集,清除其中所有的信号即初始化一个空信号集
#include <signal.h>
int sigemptyset(sigset_t *set)
成功返回0,失败返回-1
5.3 sigfillset函数
初始化一个满的信号集
int sigfillset(sigset_t *set);
5.4 sigismember函数
判断某个集合中是否有某个信号
int sigismember(const sigset_t *set,int signum);
返回值:在信号集中返回1,不在信号集中返回0
5.5 sigaddset函数
向某个集合中添加一个信号
int sigaddset(sigset_t *set,int signum)
5.6 sigdelset函数
从某个信号集中删除一个信号
int sigdelset(sigset_t *set,int signum);
5.7 综合示例
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
int main(int argc,char const *argv[])
{
//定义一个信号集
sigset_t set;
//清空set集
sigemptyset(&set);
//将SIGINT添加到set集合中
sigaddset(&set,SIGINT);
//将SIGTSTP添加到set集合中
sigaddset(&set,SIGTSTP);
if(sigismember(&set,SIGINT))
{
printf("SIGINT是在set集合中\n");
}else
{
printf("SIGINT不在set集合中\n");
}
//将SIGINT从集合中删除
sigdelset(&set,SIGINT);
if(sigismember(&set,SIGINT))
{
printf("SIGINT是在set集合中\n");
}
else
{
printf("SIGINT不在set集合中\n");
}
return 0;
}
6 信号阻塞集(屏蔽集、掩码)
每个进程都有一个阻塞集,它用来描述哪些信号递送到该进程的时候被阻塞(在信号发生时记住它,直到进程准备好时再将信号通知进程)。
所谓阻塞并不是禁止传送信号,而是暂缓信号的传送。若将被阻塞的信号从信号阻塞集中删除,且对应的信号在被阻塞时发生了,进程将会收到相应的信号。
创建一个阻塞集合
int sigprocmask(int how,const sigset_t *set,sigset_t *oldset);
功能:
检查或修改信号阻塞集,根据how指定的方法对进程的阻塞集合进行修改,新的信号阻塞集由set指定,而原先的信号阻塞集合由oldset保存
参数:how:信号阻塞集的修改方法
SIG_BLOCK:向信号阻塞集合中添加set信号集
SIG_UNBLOCK:从信号阻塞集合中删除set集合
SIG_SETMASK:将信号阻塞集合设为set集合
返回值:0成功,-1失败
例:
#define _XOPEN_SOURCE 700
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
int main(int argc,char const *argv[])
{
sigset_t set;
sigemptyset(&set);//清空信号集
sigaddset(&set,SIGINT);
printf("--阻塞CTRL+C信号 10秒--\n");
sigprocmask(SIG_BLOCK,&set,NULL);
sleep(10);
sigprocmask(SIG_UNBLOCK,&set,NULL);
return 0;
}