信号是在软件层面对中断机制的一种模拟,信号的出现使得进程直接的通信不在是被动的,不在向之前那样,read()操作往往需要等待write()操作结束。因为信号是对中断的一种模拟。既然是中断,那么它的发生就是不确定。就不会发生一个进程阻塞在这里等待另一个进程执行的结果。这样的异步性通信机制无疑是更加强大的。
在终端输入kill -l可以查看当前系统所支持的所有信号。(我这个是Ubuntu)
可以看到有64个信号,其中有两个较为特殊的信号是SIGRTMIN和SIGRTMAX。Linux下的通信机制是遵从POSIX标准的。34号信号SIGRTMIN信号之前的是早期UNIX操作系统的。它们是不可靠的信号。它的主要问题是:进程每次处理信号后,会设置对该信号的默认处理动作,有时候我们不想让他这么处理了(按照默认处理),这时候就需要调用signal()函数重新安装一次信号。这样会形成新的默认动作。还有更加讨厌的是,信号有可能会丢失。
Linux对不可靠信号做了一些改进,现在的主要问题变成了“信号会丢失”。
后来POSIX仅仅只对可靠信号做了标准化。信号值位于SIGRTMIN和SIGRTMAX之间的信号都是可靠信号。可靠信号它不会丢失。
可靠信号都是实时信号,不可靠信号都是非实时信号。可靠信号都支持队列处理,不可靠信号不支持队列处理。在UNIX时代就定义好了前面的不可靠信号的功能,而后来增加的可靠信号是让用户自定义使用的。
信号处理的三种方式:
发送信号
发送信号的函数有kill(),raise(),sigqueue(),alarm(),setittimer(),abort()。常用的是kill()。它们依赖的头文件是#include<signal.h>和#include<sys/types.h>
函数原型:int kill(pid_t pid,int sig);
函数功能:用来将sig所指定的信号发送到pid所指定的进程。
pid有下面几种情形,分别对应于不同情况下应用。
- pid > 0:把信号传递到进程ID为pid的进程
- pid == 0:把信号传送给当前进程所在组的所有进程
- pid == -1:将信号以广播的形式传送给系统内所有进程
- pid < -1: 讲信号传递给进程组识别码为pid绝对值的所有进程
函数执行成功返回0,否则返回-1.
测试代码如下:
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<signal.h>
#include<wait.h>
#include<unistd.h>
int main()
{
pid_t pid;
int statu;
pid = fork();
if(0 == pid)
{
printf("son\n");
sleep(5);
printf("I am son!\n");
exit(0);
}
if(0 < pid)
{
sleep(2);
printf("father\n");
kill(pid,SIGABRT); //SIGABRT是终止子进程
wait(NULL);
printf("My son GG\n");
exit(0);
}
return 0;
}
让子进程先执行,打印出son。然后让子进程挂起。轮到父进程执行,父进程执行到kill()函数的时候给子进程发了个SIGABRT信号,让子进程终止了。然后wait()回收子进程,打印My son GG.
执行结果如下:
可以看到,子进程收到SIGABRT信号后,终止了。没有向屏幕打印I am son.关于信号的详解,看这里:
上面的kill函数发送的信号是不可靠信号,它执行默认操作。即:终止进程。如果我们需要自定义信号处理方式,那么就需要安装信号。Linux安装信号主要由signal()和sigaction()完成。signal是在可靠信号系统调用的基础上实现的,是库函数。
signal()的原型很复杂,我们还是从signal.h这个头文件来看一下吧!
extern __sighandler_t signal (int __sig, __sighandler_t __handler)
__THROW;
可以看到signal有两个参数,一个是信号值,另一个我们再来看看
typedef void (*__sighandler_t) (int);
注意:__handler如果不是函数指针,它只能是SIG_IGN或者是SIG_DFL.
SIG_IGN:忽略参数指定的信号。(忽略该信号)
SIG_DFL:将参数指定的信号重新设置为内核默认的处理方式。
所以这就要求自定义的信号处理函数的函数原型是这样的:
void 函数名(int 参数名);即:函数必须有一个int类型的参数。
signal()函数只是定义了将指定信号传送到指定进程。还需要一个用于捕捉信号的函数。在Linux下pause()函数用于捕捉信号,如果没有信号发生,pause函数将会一直等待。直到有信号发生。
函数原型:int pause();
当pause函数捕捉到信号的时候返回-1(注意不是捕捉到的信号的值)。
测试程序如下:
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<unistd.h>
#include<signal.h>
#include<wait.h>
//自定义的信号处理函数
void My_Fun(int sig)
{
if(SIGRTMIN == sig)
{
printf("MIN!\n");
}
if(SIGRTMAX == sig)
{
printf("MAX!\n");
}
}
int main()
{
//注册信号处理函数
signal(SIGRTMIN,My_Fun);
signal(SIGRTMAX,My_Fun);
//挂起10s
sleep(3);
//发出信号
kill(getpid(),SIGRTMIN); //getpid()函数用于获取当前进程的pid.
kill(getpid(),SIGRTMAX);
return 0;
}
输出结果如下:
这样就完成了自定义信号的使用。使用自定义信号有两个关键点。一是必须注册自定义信号的处理函数,二是必须发送自定义信号。怎么样发送自定义信号由你自己来定义,这为程序设计带来了极大的便利。比如上面我们只是直接了当的发送两个信号。你也可以使当满足一定条件的时候才发送信号。比如下面这样。
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<unistd.h>
#include<signal.h>
#include<wait.h>
//自定义的信号处理函数
void My_Fun(int sig)
{
if(SIGRTMIN == sig)
{
printf("MIN!\n");
}
if(SIGRTMAX == sig)
{
printf("MAX!\n");
}
}
int main()
{
//注册信号处理函数
signal(SIGRTMIN,My_Fun);
signal(SIGRTMAX,My_Fun);
//发出信号
char c;
while(1)
{
scanf("%c",&c);
getchar(); //吸收回车
if('a' == c)
{
kill(getpid(),SIGRTMIN); //getpid()函数用于获取当前进程的pid.
}
else
{
kill(getpid(),SIGRTMAX);
}
}
return 0;
}
运行结果如下:
这样就实现了发送信号的控制。可以想象,键盘,鼠标等发送的信号很有可能就是被系统采取这样的方式处理的。
另外一个函数是sigaction()函数。
函数原型:int sigaction(int sig,const struct sigaction *newact,const sigaction *oldact);
函数功能:sigaction函数根据参数sig指定的信号来处理信号。参数可以是SIGKILL和SIGSTOP以为的其他信号。newact是新的信号处理方式,oldact是旧的信号处理方式。这个结构体包含如下成员
struct sigaction()
{
void(*sa_handler)(int); //指向信号处理函数的函数指针
sigset_t sa_mask; //用来设置处理该信号的时候暂时屏蔽sa_mask指定的信号
int sa_flags; //设置信号处理的方式
int (*sa_restorer)(void); //输出参数, 指向struct sigaction 结构的指针
}
在C语言里结构体的名字可以和函数名相同。(其实是C语言的结构体名称应该是struct xxx。带上关键字才是真正的结构体名)。
信号集
信号集被定义为一种数据类型:
typedef struct
{
unsigned long sig[_NSIG_WORDS];
}sigset_t;
信号集主要配合一下的信号阻塞函数来使用。
- sigemptyset()函数:
函数原型:int sigemptyset(sigset_t *set);
函数功能:用来将set信号集给初始化并清空。 - sigaddset()函数:
函数原型:int sigaddset(sigset_t *set,int sig);
函数功能:将参数sig指定的信号加入信号集set。 - sigfillset()函数:
函数原型:int sigfillset(sigset_t *set);
函数功能:把所有信号加入到set中。 - sigdelset()函数:
函数原型:int sigdelset(sigset_t *set,ing sig);
函数功能:将参数sig指定的信号从set中删除
使用信号注意的问题:
- 注意信号是否会丢失这个问题,尽量使用可靠信号。
- 注意信号的可移植性,POSIX标准指定的信号函数和信号
- 信号处理函数应当是一个可重入函数。