信号处理函数
设置信号处理函数
Linux使用 signal 函数将处理函数加载,并且通知系统,其函数原型为:
#include<signal.h>
void (*signal (int signo,void(* func)(int))) (int);
参数说明:
- signal : 需要加载处理的信号的编号,例如SIGKILL;
- func : 是一个函数指针,这个函数捕捉第一个函数指定的信号,并对其进行处理,该参数可以是以下三个值中的一个:
(1)SIG_IGN:表示忽略该信号,即捕捉到后不做处理。#define SIG_IGN ( (void*) (*) () ) 1
注意: SIGKILL、SIGSTOP信号不能忽略!!
(2)SIG_DFL: 表示使用默认的信号处理方式 #define SIG_DFL ( (void) (*) () ) 0*
(3)其他已定义的函数指针:信号处理函数原型:void handler(int); - 返回值:返回值也是一个函数指针,这个函数指向上一次的信号处理程序,因此这个函数和signal的第二个参数所表示的函数的原型一致,如果出错返回SIG_ERR,#define SIG_ERR ( (void*) (*) () ) -1
信号处理函数应用:
- 信号处理程序的参数表示当前所捕捉到的信号的编号,也就是说和signal的第一个参数等效,因此可以多个信号公用一个处理程序:
void signal_handler(int signo){
switch(signo){
case SIG1:/*处理信号1*/
case SIG2:/*处理信号2*/
......
case SIGn:/*处理信号n*/
}
}
signal函数原型简化:
typedef void HANDLER(int);
HANDLER * signal(int signo,HANDLER * handler);
SIGUSR1和SIGUSR2:
- Linux系统不允许用户创建新的信号,但是提供两个信号SIGUSR1和SIGUSR2专门用于应用程序之间进行信号通信。这两个信号没有特殊的语义,系统的默认处理方式是忽略。
- 实例:父子进程使用SIGUSR1和SIGUSR2通信
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
void handler(int signo)
{
switch(signo){
case SIGUSR1:
printf("Parent : catch SIGUSR1\n");
break;
case SIGUSR2:
printf("Child : catch SIGUSR2\n");
break;
default:
printf("should not be here\n");
break;
}
return ;
}
int main(void)
{
pid_t ppid, cpid;
if(signal(SIGUSR1, handler) == SIG_ERR){
perror("can’t set handler for SIGUSR1");
exit(1);
}
if(signal(SIGUSR2, handler) == SIG_ERR){
perror("can’t set handler for SIGUSR2");
exit(1);
}
ppid = getpid();
if((cpid = fork()) <0){
perror("fail to fork");
exit(1);
}else if(cpid == 0){
if(kill(ppid, SIGUSR1) == -1){
perror("fail to send signal");
exit(1);
}
while(1);
}else{
sleep(1);
if(kill(cpid, SIGUSR2) == -1){
perror("fail to send signal");
exit(1);
}
printf("kill child\n");
if(kill(cpid, SIGKILL) == -1){
perror("fail to send signal");
exit(1);
}
if(wait(NULL) == -1){
perror("fail to wait");
exit(1);
}
}
return 0;
}
发送信号
Linux环境使用 kill 函数向进程或者进程组发送信号,其函数原型为:
#include<signal.h>
int kill(pid_t pid,int signo);
参数说明:
- pid:有四种不同的取值情况
- signo:需要发送的信号编号
- 返回值:成功发送返回0,失败返回-1.
- 进程可以调用 kill 函数向自身发送一个信号
kill(getpid(),signo);
一个进程向另一个进程发送信号必须注意:
- 该进程有向指定进程发信号的权限。
- 系统进程不能接收信号,例如init进程。
- 进程之间发送信号需要权限检查,基本规则如下表所示:
向进程本身发送信号
Linux 同样提供可以向进程本身发送信号的函数,该函数可以替代上述形式,其函数原型如下:
#include<signal.h>
int raise(int signal);
参数说明:signal是要发送的信号编号,成功返回0,失败返回-1.raise函数可以实现exit函数的功能使进程退出,所不同的是此用法不做任何善后处理(例如冲洗流,关闭文件等),值得一提的是,如果信号的处理方式是终结进程,那么当进程捕捉到该信号时就会终结退出,同样不做善后处理。因此,如果一个进程有可能向其他进程发送信号终结的时候,应该编写一个处理所有善后事宜的函数作为此信号处理程序,使用signal设置后使用。
#include<stdio.h>
#include<signal.h>
int main(void){
printf("kill myself\n");
raise(SIGKILL);//终结当前进程
return 0;
}
设置Linux定时器
时间是编程中一个重要的概念,有些场合需要设置一个定时器,在经过若干时间后通知设置定时器的进程,定时器示意图如图所示。
Linux环境使用alarm函数设置一个定时器,函数原型为:
#include<unistd.h>
uinsigned int alarm(unsigned int seconds);
注意: alarm函数设置的不是一个很精确的定时器,理由如下:
首先,该函数设置的定时器的精度是秒,秒的级别时间在计算机内部而言是相当长的,因为一条机器指令的执行时间是纳秒级。 其次,在定时器超时发送了SIGALRM信号后,进程从捕捉该信号到进行处理还会有一-定的延时, 因为只有被调度或者从内核态返回到用户态的时候进程才能做检查信号的操作。而且,在shell中执行该程序后(下面程序代码),在打印the time is now之前会打印许多too early (视系统的负载而定)。printf 函数调用很费时,而且还要使用I/O,即使是这样仍可以执行多次,如果仅仅是一些不使用I/O和函数调用的指令,那么执行的次数还会增加很多,而1秒又是alarm所能设置的最小精度了。由此可知,alarm 函数用来设置精确的定时器还未够资格,同时也说明1秒钟对于计算机而言是一个很长的时间。
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
void timer_handler(int signo)
{
if(signo == SIGALRM){
printf("the time is now\n");//捕捉后打印提示信息
exit(0);
}else
printf("unexpected signal\n");//不应该执行到这里
}
int main(void)
{
if(signal(SIGALRM, timer_handler) == SIG_ERR){//设置信号处理程序
perror("can’t set handler for SIGALRM");
exit(0);
}
alarm(1);//设置定时器,定时时间为1秒
while(1)
printf("too early\n");
printf("should not be here, never\n");
return 0;
}
定时等待I/O
对外设进行读写操作的时候,外设有可能处于-一个不可用的状态。例如,网络设备中指定的数据包未到达等。这种时候往往会导致读写操作的阻塞,这种阻塞是没有时间限制的,直到所请求的设备准备好为止。alarm函数可以用来实现定时阻塞,当需要读写的设备未就绪时,只等待有限的时间。该函数设置一个定时器,如果I/O系统调用超时,则会返回-1。
挂起进程
-
运行、就绪和阻塞:是进程的三个基本状态,任一时刻只有一个进程处于运行状态。,当进程运行条件尚不具备时进程处于阻塞态。进程的运行条件已经具备时进程处于就绪态,就绪的进程可以被调度,阻塞态的进程不参与调度。
-
进程挂起:有时当一个进程的运行条件已经具备时仍需要使进程阻塞(例如期望进程延迟执行,sleep函数经常起到这个作用),这种由进程自愿进入阻塞态的情况成为进程挂起。Linux环境下使用 pause 函数挂起-一个进程,其函数原型如下:
#include<unistd.h>
int pause(void);
说明:pause函数使调用该函数的进程进入挂起状态,直到一个信号到来,并且执行了一个信号处理程序从其返回后,pause函数才返回,返回值是-1. pause函数的返回值只有-1一种情况,errmo 被设置为EINTER,返回-1表示执行正确。
进程休眠
pause使进程无时间限制的挂起,若想使进程在-一定时间内恢复运行则使用sleep函数,其函数原型如下:
#include<unistd.h>
unsigned int sleep(unsigned int nsec);