信号
信号是在软件层次上对中断机制的一种模拟,在原理上,一个进程收到一个信号与处理器收到一个中断请求可以说是一样的。
信号是异步的,一个进程不必通过任何操作来等待信号的到达,事实上,进程也不知道信号到底什么时候到达。
信号是进程间通信机制中唯一的异步通信机制,可以看作是异步通知,通知接收信号的进程有哪些事情发生了。
信号机制经过POSIX实时扩展后,功能更加强大,除了基本通知功能外,还可以传递附加信息。
信号来源 信号事件的发生有两个来源: 硬件来源(比如我们按下了键盘或者其它硬件故障);
软件来源,最常用发送信号的系统函数是kill, raise, alarm和setitimer以及sigqueue函数,软件来源还包括一些非法运算等操作。
进程可以通过三种方式来响应一个信号:
(1)忽略信号,即对信号不做任何处理,其中,有两个信号不能忽略:SIGKILL及SIGSTOP;
(2)捕捉信号。定义信号处理函数,当信号发生时,执行相应的处理函数;
(3)执行缺省操作,Linux对每种信号都规定了默认操作,详细情况请参考资料。 注意,进程对实时信号的缺省反应是进程终止。
Linux究竟采用上述三种方式的哪一个来响应信号,取决于传递给相应API函数的参数。
所有信号都是以SIG开头的的一个宏。
信号通常用来向进程发生的一个通知事件。
信号是随时发生的,不可预知的,是异步的。
#include<signal.h>
信号一般处理:
捕获信号。
忽略信号。
执行信号默认操作。
捕获信号:
#include <unistd.h>
int pause(void);
总是返回:-1,并设置errno为EINTR
只有捕获到一个信号的时候pause才有返回,如果传递的信号引发了对信号的处理,那么处理工作将在pause返回之前执行。
发送信号和捕捉信号是相辅相成的。每个进程能够决定处理sigstop和sigkill之外的其他所有信号。
SIGSTOP SIGKILL信号是不能捕捉或者忽略的。
捕捉信号不是真的捕捉它,而是等它发送过来。
当执行一个程序的时候,所有信号状态都是是系统默认或者忽略的
当一个进程fork的时候,其子进程继承了父进程的信号处理方式,所以信号捕捉函数的地址在子进程中是有意义的。
定义信号处理器
某些情况下,一个信号的默认动作就是所希望的行为,但更多场合,你可以改变默认行为或者执行额外的代码。
如果你想默认行为,就必须定义并安装一个自动的信号处理器
函数指针:
回调函数:就是通过一个函数指针调用的函数。
int fun(int a, int b)
{
return a+b;
}
int main()
{
int (*pfun)(int ,int );
pfun = fun;
printf("p = %d\n" , pfun(4 , 5));
return 0;
}
signal函数://不建议使用了,1969年出现的,比较老了。可能存在潜在的问题。
linux处理信号最常用的接口是signal函数
void ( *signal(int signum, void (*handler)(int)) ) (int);
signum 是SIGKILL这种宏定义的型号名称
handler是接到此信号后要回调的函数,该函数有一个int参数,代表捕捉到的信号名称如SIGKILL
进程捕捉到信号,并对信号进行处理时,进程正在执行的指令序列,就会被信号处理程序临时中断,它首先要执行信号处理程序中的指令。
如果信号处理程序返回,(没有调用exit ,abort函数)则继续执行捕捉到信号时进程正在执行的正常程序指令。
在信号处理程序中,不能判断捕捉到信号时,进程正在何处执行。
常用消息说明:
1) SIGHUP 本信号在用户终端连接(正常或非正常)结束时发出, 通常是在终端的控制进程结束时, 通知同一session内的各个作业, 这时它们与控制终端不再关联.
2) SIGINT 程序终止(interrupt)信号, 在用户键入INTR字符(通常是Ctrl+C)时发出,用于通知前台进程组终止进程。
3) SIGQUIT SIGQUIT 和SIGINT类似, 但由QUIT字符(通常是Ctrl-)来控制. 进程在因收到 SIGQUIT退出时会产生core文件, 在这个意义上类似于一个程序错误信号.
4) SIGILL 执行了非法指令. 通常是因为可执行文件本身出现错误, 或者试图执行 数据段. 堆栈溢出时也有可能产生这个信号.
5) SIGTRAP 由断点指令或其它trap指令产生. 由debugger使用.
6) SIGABRT ----abort()函数产生的信号,进程异常终止。
7) SIGBUS 非法地址, 包括内存地址对齐(alignment)出错. eg: 访问一个四个字长 的整数, 但其地址不是4的倍数.
8) SIGFPE 在发生致命的算术运算错误时发出. 不仅包括浮点运算错误, 还包括溢 出及除数为0等其它所有的算术的错误.
9) SIGKILL 用来立即结束程序的运行. 本信号不能被阻塞, 处理和忽略.
10) SIGUSR1 留给用户使用
11) SIGSEGV 试图访问未分配给自己的内存, 或试图往没有写权限的内存地址写数据.
12) SIGUSR2 留给用户使用
13) SIGPIPE 如果在写到管道时,读进程已经终止,就产生这个消息,--- 默认行为也是退出程序。
14) SIGALRM 时钟定时信号, 计算的是实际的时间或时钟时间. alarm函数使用该信号. 调用alarm设置计时器超时时,产生此信号 。如果没有在程序中捕捉,默认行为是退出程序。
15) SIGTERM 程序结束(terminate)信号, 与SIGKILL不同的是该信号可以被阻塞和处理. 通常用来要求程序自己正常退出. shell命令kill缺省产生这个信号.
16) SIGSTKFLT
17) SIGCHLD 子进程结束时, 父进程会收到这个信号.
18) SIGCONT 让一个停止(stopped)的进程继续执行. 本信号不能被阻塞. 可以用 一个handler来让程序在由stopped状态变为继续执行时完成特定的工作. 例如, 重新显示提示符。
19) SIGSTOP 停止(stopped)进程的执行. 注意它和terminate以及interrupt的区别: 该进程还未结束, 只是暂停执行. 本信号不能被阻塞, 处理或忽略.
20) SIGTSTP 停止进程的运行, 但该信号可以被处理和忽略. 用户键入SUSP字符时 (通常是Ctrl+Z)发出这个信号。
21) SIGTTIN 当后台作业要从用户终端读数据时, 该作业中的所有进程会收到SIGTTIN 信号. 缺省时这些进程会停止执行.
22) SIGTTOU 类似于SIGTTIN, 但在写终端(或修改终端模式)时收到.
23) SIGURG 有"紧急"数据或out-of-band数据到达socket时产生
24) SIGXCPU 超过CPU时间资源限制. 这个限制可以由getrlimit/setrlimit来读取/ 改变
25) SIGXFSZ 超过文件大小资源限制.
26) SIGVTALRM 虚拟时钟信号. 类似于SIGALRM, 但是计算的是该进程占用的CPU时间.
27) SIGPROF 类似于SIGALRM/SIGVTALRM, 但包括该进程用的CPU时间以及系统调用的时间.
28) SIGWINCH 窗口大小改变时发出
29) SIGIO 文件描述符准备就绪, 可以开始进行输入/输出操作.
30) SIGPWR
31) SIGSYS
34) SIGRTMIN
signal 例子:
void catch_signal(int sign)
{
switch(sign)
{
case SIGINT:
puts("signal is SIGINT!");
break;
case SIGALRM:
puts("signal is SIGALRM!");
break;
}
}
int main(int argc , char* argv[])
{
signal(SIGINT , catch_signal); //信号安装
signal(SIGALRM , catch_signal);
printf("SIGINT = %d , SIGALRM = %d\n " , SIGINT, SIGALRM);
int i = 0;
while(1)
{
pause();//死等
//sleep(100);//只等100秒
printf("hello%d\n" , i++);
}
return 0;
}
shell中给进程发消息 kill -s signalID PID signalID消息ID PID进程ID
发送信号的主要函数有:
kill()
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid,int signo)
参数pid的值为信号的接收进程
Kill()最常用于pid>0时的信号发送,调用成功返回 0; 否则,返回 -1。
注:对于pid<0时的情况,对于哪些进程将接受信号,各种版本说法不一,其实很简单,参阅内核源码kernel/signal.c即可
例子:通过函数发送消息
int main(int argc , char* argv[])
{
if(argc > 1)
{
int pid = atoi(argv[1]);
kill(pid , SIGINT);
}
return 0;
}
raise()
#include <signal.h>
int raise(int signo) 向进程本身发送信号,参数为即将发送的信号值。
调用成功返回 0;否则,返回 -1。
raise(signo); 等价于kill(getpid() , signo);
改进捕获消息机制:
sigaction();
#include <signal.h>
int sigaction( int signum , const struct sigaction *act , struct sigaction *oldact);
检查或者修改与指定消息相关联的处理函数,取代signal()。
int signum :信号ID
const struct sigaction *act :如果指针非空则修改其动作。信号的一个状态。
struct sigaction *oldact:如果指针非空,则系统由oact指针返回该信号的上一个动作。
成功返回0 ,失败-1 。
封装sigaction();定义自己的signal函数。
int mysignal(int signo , void*(func)(int))
{
struct sigaction act , oact;
act.sa_handler = func;
sigemptyset(&act.sa_mask);//初始化结构
act.sa_flags = 0;
return sigaction(signo , &act , &oact);
}
######################################################
自定义信号: SIGUSR1 SIGUSR2
简单列子:
sigqueue()
#include <sys/types.h>
#include <signal.h>
int sigqueue(pid_t pid, int sig, const union sigval val)
调用成功返回 0;否则,返回 -1。 sigqueue()是比较新的发送信号系统调用,
主要是针对实时信号提出的(当然也支持前32种),支持信号带有参数,与函数sigaction()配合使用。
sigqueue的第一个参数是指定接收信号的进程ID,第二个参数确定即将发送的信号,
第三个参数是一个联合数据结构union sigval,指定了信号传递的参数,即通常所说的4字节值。
typedef union sigval
{
int sival_int;
void *sival_ptr;
}sigval_t;
sigqueue()比kill()传递了更多的附加信息, sigqueue() 只能向一个进程发送信号,而不能发送信号给一个进程组。
如果signo=0,将会执行错误检查,但实际上不发送任何信号,0值信号可用于检查pid的有效性以及当前进程是否有权限向目标进程发送信号。 在调用sigqueue时,sigval_t指定的信息会拷贝到3参数信号处理函数(3参数信号处理函数指的是信号处理函数由sigaction安装,并设定了sa_sigaction指针)
alarm() 计时器,一个进程只能设置一次alarm(5) 表示5秒钟后发送SIGALRM信号;给自己发信号。
#include <unistd.h>
unsigned int alarm(unsigned int seconds)
专门为SIGALRM信号而设,在指定的时间seconds秒后,将向进程本身发送SIGALRM信号,又称为闹钟时间。
进程调用alarm后,任何以前的alarm()调用都将无效。如果参数seconds为零,那么进程内将不再包含任何闹钟时间。
返回值,如果调用alarm()前,进程中已经设置了闹钟时间,则返回上一个闹钟时间的剩余时间,否则返回0。
setitimer()
#include <sys/time.h>
int setitimer(int which, const struct itimerval *value, struct itimerval *ovalue);
setitimer()比alarm功能强大,支持3种类型的定时器:
ITIMER_REAL: 设定绝对时间;经过指定的时间后,内核将发送SIGALRM信号给本进程;
ITIMER_VIRTUAL 设定程序执行时间;经过指定的时间后,内核将发送SIGVTALRM信号给本进程;
ITIMER_PROF 设定进程执行以及内核因本进程而消耗的时间和,经过指定的时间后,内核将发送ITIMER_VIRTUAL信号给本进程;
Setitimer()第一个参数which指定定时器类型(上面三种之一);
第二个参数是结构itimerval的一个实例,结构itimerval形式见附录1。
第三个参数可不做处理。
Setitimer()调用成功返回0,否则返回-1。
abort()
#include <stdlib.h>
void abort(void);
向进程发送SIGABORT信号,默认情况下进程会异常退出,当然可定义自己的信号处理函数。
即使SIGABORT被进程设置为阻塞信号,调用abort()后,SIGABORT仍然能被进程接收。该函数无返回值。
信号的安装
如果进程要处理某一信号,那么就要在进程中安装该信号。
安装信号主要用来确定信号值及进程针对该信号值的动作之间的映射关系, 即进程将要处理哪个信号;
该信号被传递给进程时,将执行何种操作。
linux主要有两个函数实现信号的安装: signal()、sigaction()。 signal()在可靠信号系统调用的基础上实现, 是库函数。
它只有两个参数,不支持信号传递信息,主要是用于前32种非实时信号的安装;
sigaction()是较新的函数(由两个系统调用实现:sys_signal以及sys_rt_sigaction),有三个参数,支持信号传递信息,主要用来与 sigqueue() 系统调用配合使用,当然,sigaction()同样支持非实时信号的安装。sigaction()优于signal()主要体现在支持信号带有参数。