信号是在软件层面上对中断机制的一种模拟。在原理上,一个进程收到一个信号与处理器收到一个中断请求可以说在某种程度上是一样的。
信号是异步的:一个进程不必通过任何操作来等待信号的到达。事实上,进程也不知道信号什么时候会到达。
信号可以直接进行用户空间进程和内核进程之间的交互,内核进程也可以利用它来通知用户进程发生了那些系统事件。
信号可以在任何时候发给某一进程,而无需知道该进程的状态。如果该进程当前并未处于执行态,则该信号就由内核保存起来,直到该进程恢复执行再传递给它为止。
如果一个信号被进程设置为阻塞,则该信号的传递被延迟,直到其阻塞被取消时才被传递给进程。
信号是进程间通信机制中唯一的异步通信机制。我们可以把这种机制看作异步通知,通知接收信号的进程有哪些事情发生了。
信号机制经过POSIX实时扩展后,功能更加强大,除了基本的通知功能外,还可以传递附加信息。
信号事件的产生有硬件来源(比如按下键盘或其他引荐故障)和软件来源,常用的信号相关函数有kill()、raise()、alarm()、settitimer()和sigqueue()等。软件来源还包括一些非法运算等。
进程可以通过以下三种方式来响应一个信号。
SIGKILL
和SIGSTOP
。Linux对每种信号都规定了默认操作。如下表:
信号的先关函数包括信号的发送和设置,具体如下:
kill()函数同我们熟悉的kill系统命令一样,可以发送信号给进程或进程组(实际上,kill系统命令就是用kill()函数实现的)。
需要注意的是,它不仅可以终止进程(发送SIGTERM信号),也可以向进程发送其他信号。
kill()
函数所不同的是,raise()
函数只允许进程向自身发送信号。
kill()函数语法
所需头文件
#include <signal.h>
#include <sys/types.h>
函数原型
int kill(pid_t pid, int sig);
函数传入值
pid
整数:发送信号给进程号为pid的进程
0:信号被发送到所有和当前进程在同一个进程组的进程
-1:信号发送给所有进程表中的进程(除了进程号最大的进程外)
<-1:信号发送给进程组号为-pid的每一进程
sig
信号类型
函数返回值
成功:0
出错:-1
raise()函数语法
所需头文件
#include <signal.h>
#include <sys/types.h>
函数原型
int raise(int sig);
函数传入值
sig:信号类型
函数返回值
成功:0
出错:-1
定时器信号
alarm()函数语法
所需头文件
#include <unistd.h>
函数原型
unsigned int alarm(unsigned int seconds);
函数传入值
seconds:指定秒数,系统经过seconds秒之后向该进程发送SIGALRM信号。
函数返回值
成功:如果调用此alarm()前,进程中已经设置了闹钟时间,则返回上一个闹钟时间剩余的时间,否则返回0。
出错:-1
pause()函数语法
所需头文件
#include <unistd.h>
函数原型
int pause(void);
函数返回值
-1,并且errno值设为EINTR
注意,这里pause()函数只返回-1,错误代码EINTR 表示有信号到达中断了此函数。
下面来看个小例子,揭示alarm()函数和pause()函数的用法:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
alarm(5); //调用alarm()定时器函数,定时结束后会向当前进程发送SIGALARM信号
pause(); //将调用级进程挂起,知道收到信号为止
printf("This is my world\n"); //词句不会被执行,包括后面的
return 0;
}
由于 SIGALARM的系默认操作是终止该进程,所以当进程接收到该信号时就会终止该进程,所以printf内容不会被输出,因为进程提前终止了。运行该程序后,进程在等待5s后,会输出如下:
信号处理的主要的主要方法有两种:
signal()
函数sigaction()
函数。signal()函数
所需头文件
#include <signal.h>
函数原型
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
函数传入值
signum :指定信号代码
handler:
SIG_IGN:忽略该信号
SIG_DFL:采用系统默认方式处理信号
自定义的信号处理函数
函数返回值
成功:以前的信号处理函数
出错:-1
注意,这里signal()
的返回值和第二个参数都是一个无返回值带一个int型参数的函数指针。
sigaction()函数
所需头文件
#include <signal.h>
函数原型
int sigaction(int signum,const struct sigaction *act,struct sigaction *oldact);
函数传入值
signum:信号类型,除SIGKILL及SIGSTOP之外的任何一个信号
act:指向sigaction结构体的指针,包含对特定信号的处理
oldact:保存信号原先的处理方式
函数返回值
成功:0
出错:-1
sigaction()
函数中的第2个和第三个参数用到了sigaction结构体,我们来看一下:
struct sigaction
{
void (*sa_handler)(int signo); // 对应的信号处理函数
sigset_t sa_mask; // 用来设置在处理该信号时暂时将sa_mask 指定的信号搁置
int sa_flags; // 用来设置在处理该信号时暂时将sa_mask 指定的信号搁置
void (*sa_restore)(void); // 一般未使用到
}
sa_handler是一个函数指针,指向信号处理函数。它既可以是用户自定义的处理函数,也可以为SIG_DEL(采用默认的处理方式或SIG_IGN(忽略信号)。信号处理函数只有一个参数,即信号类型。
sa_mask
是一个信号集合,用来指定在信号处理函数执行过程中哪些信号被屏蔽。
sig_flags中包含了许多标志位,都是和信号处理相关的选项。常见的选项值如下:
通过下面这个小例子,我们来看一下sigaction()函数的用法:
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
void my_func(int sign_no)
{
if (sign_no == SIGINT)
{
printf("I have got SIGINT\n");
} else if (sign_no == SIGQUIT){
printf("I have got SIGQUIT\n");
}
}
int main()
{
struct sigaction action;
sigaction(SIGINT,0,&action); // 信号SIGINT,前面讲到过键盘输入ctrl+c
action.sa_handler = my_func; // 注册我们自己的信号处理函数
sigaction(SIGINT,&action,0);
sigaction(SIGQUIT,0,&action); // 信号SIGQUIT,前面讲到过键盘输入ctrl+\
action.sa_handler = my_func;
sigaction(SIGQUIT,&action,0);
printf("Waiting for signal SIGINT or SIGQUIT...\n");
pause(); // 阻塞进程直到收到信号
Sexit(0);
}
运行上面的进程后,进程会阻塞,等待信号的到来。信号到来后会调用我们自己的信号处理函数。此时当我们在键盘输入ctrl+c时,会打印如下: