Chinaunix首页 | 论坛 | 博客
  • 博客访问: 6276039
  • 博文数量: 2759
  • 博客积分: 1021
  • 博客等级: 中士
  • 技术积分: 4091
  • 用 户 组: 普通用户
  • 注册时间: 2012-03-11 14:14
文章分类

全部博文(2759)

文章存档

2019年(1)

2017年(84)

2016年(196)

2015年(204)

2014年(636)

2013年(1176)

2012年(463)

分类: LINUX

2013-09-26 10:08:29

3.1、信号概述
信号时UNIX中所使用的进程通信的一种最古老的方法。它是软件层次上对中断机制的一种模拟,是一种异步通信方式。信号可以直接进行用户空间和内核空间之间的交互,内核进程也可

以利用它来通知用户空间进程发生了哪些系统事件。它可以在任何时候发给某一进程,而无需知道该进程的状态。如果该进程当前并未处于执行状态,则该信号就由内核保存起来,知道

该进程恢复执行再传递它为止;如果信号被进程设置为阻塞,则该信号的传递被延迟,直到其阻塞被取消时才能传递给进程。

一个完整的信号生命周期可以分为3个重要的阶段,这3个阶段由4个重要事件来刻画的:信号产生、信号在进程中注册、信号在进程中注销、执行信号处理函数,如下图所示。相邻两个

事件的时间间隔构成信号生命周期的一个阶段。要注意这里的信号处理有多种方式,一般由内核完成的,当然也可以由用户进程来完成。

 一个不可靠信号的处理过程是这样的:如果发现该信号已经在进程中注册,那么就忽略该信号。因此,若钱一个信号还未注销又产生了相同的信号就会产生信号丢失。而当可靠信号发送

给一个进程时,不管该信号是否已经在进程总注册,都会被再注册一次,因此信号就不会丢失。所有可靠信号都支持排队,而不可靠信号则都不支持排队。

信号生命周期图:
用户进程对信号的响应可以有3中方式。
忽略信号:即对信号不做任何处理,但是有两个信号不能忽略,即SIGKILL和SIGSTOP。

捕捉信号:定义信号处理函数,当信号发生时,执行相应的处理函数。

执行缺省操作:Linux对每种信号都规定了默认操作。

Linux中的大多数信号时提供给内核的,下表是Linux中最为常见信号的含义及其默认操作。
SIGHUP 该信号在用户终端连接(正常或非正常)结束时发出,通常是在终端的控制进程结束时,通知同一会话的各个作业与控制终端不再关联 终止
SIGINT 该信号在用户键入INTR字符(通常Ctrl+C)时发出,终端驱动程序发送此信号并送到前台进程中的每一个进程 终止
SIGQUIT 该信号和SIGINT类似,但由QUIT字符(通常是Ctrl+\)来控制 终止
SIGILL 该信号在一个进程企图执行一条非法指令时发出 终止
SIGFPE 该信号在发生致命的算法运算错误时发出。这里不仅包括浮点运算错误,还包括溢出及除数为0等其他所有的算术的错误 终止
SIGKILL 该信号用来立即结束程序的运行,并且不能被阻塞,处理和忽略 终止
SIGALRM 该信号当一个定时器到时的时候发出 终止
SIGSTOP 该信号用于暂停一个进程,且不能被阻塞,处理和忽略 暂停
SIGTSTP 该信号用于交互停止进程,用户可键入SUSP字符(通常是Ctrl+Z)发出这个信号 停止
SIGCHLD 子进程改变状态时,父进程会收到这个信号 忽略
SIGABORT

3.2、信号的发送与捕捉

发送信号的函数主要有kill()、raise()、alarm()、以及pause(),下面依次对其进行介绍。

1>kill()和raise()

(1)函数说明

kill函数同系统命令一样,可以发送信号给进程或进程组(实际上,kill系统命令只是kill函数的一个用户接口)。这里它不仅仅可以中止进程(实际发出SIGKILL信号),也可以向进程发送
其他信号。与kill函数所不同的是raise函数允许进程向自身发送信号。

(2)函数格式

kill函数:

所需头文件:#include #include
函数原型: int kill(pid_t pid, int sig)
参数:pid: 正数:要发送信号的进程
0:信号被发送到所有和pid进程在同一个进程的进程
-1:信号发给所有的进程表中的进程(除了进程号最大的进程外)
sig:信号
返回值:成功: 0
失败: -1

raise函数:
所需头文件: :#include #include
函数原型: int raise(int sig)
参数:sig:信号
返回值: 成功:0,失败 : -1

(3)函数实例
下面这个实例首先使用fork()创建了一个子进程,接着为了保证子进程不在父进程调用kill之前退出,在子进程中使用raise函数向子进程发送SIGSTOP信号,使得子进程暂停。接下来再
在父进程中调用kill向子进程发送信号,在该实例中使用的是SIGKILL,也可以使用其他信号进行练习。
/*kill.c*/
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>

int main()
{
pid_t pid;
int ret;

/*创建一子进程*/
if((pid = fork()) < 0){
perror("fork");
exit(1);
}

if(pie == 0){
/*在子进程中使用raise函数发出SIGSTOP信号*/
raise(SIGSTOP);
exit(0);
}
else{
/*在父进程中收集子进程发出的信号,并调用kill函数进行相应的操作*/
printf("pid = %d\n",pid);
if((waitpid(pid,NULL,WNOHANG)) == 0){
if((ret = kill(pid,SIGKILL)) == 0)
printf("kill %d\n", pid);
else{
perror("kill");
}
}
}

}

2>alarm()和pause()

(1)函数说明
alarm也称为闹钟函数,它可以在进程中设置一个定时器,当定时器指的时间到时,它就向进程发送SIGALARM信号,要注意的是一个进程只能有一个闹钟时间,如果在调用alarm之前已经设置过闹钟时间,则任何以前的闹钟时间都被新值所代替。
pause函数是用于将调用进程挂起直至捕捉到信号为止。这个函数很常用,通常可以用于判断信号是否已到。

(2)函数格式
alarm函数:
所需头文件:#include
函数原型: unsigned int alarm(unsigned int seconds)
参数:seconds:指定秒数
返回值:成功:如果调用此alarm()前,进程中已经设置了闹钟时间则返回上一个闹钟时间的剩余时间,否则返回0.
失败:-1
pause函数:
所需头文件:#include
函数原型: int pause(void)
返回值: -1: 并且把error值设置为EINTR

(3)函数实例:
该实例实际上已完成了一个简单的sleep函数的功能,由于SIGALARM默认的系统动作为终止该进程,因此在程序调用pause之后,程序就终止了,如下所示:
/*alarm.c*/
# include < unistd.h >
#include <stdio.h>
#include <stdlib.h>

int main()
{
int ret;
/*调用alarm定时器函数*/
ret = alarm(5);
pause();
printf("I have been waken up.\n",ret);
}

3.3>信号的处理
特定的信号是与一定的进程相联系的。也就是说,一个进程可以决定该进程中需要对哪些信号进行什么样的处理。建立信号与进程之间的对应关系,这就是信号的处理。信号处理时一种是使用简单的signal函数,另一种是使用信号集函数组。

1、signal()

(1)函数说明
使用signal函数处理时,只需要处理的信号和处理函数列出即可。它主要是用于前32种非实时信号的处理,不支持信号传递信息,但是由于使用简单,易于理解,因此也受到很多程序员的欢迎。

(2)函数格式
所需头文件:#include
函数原型:void(signal(int signum,void(*handler)(int)))(int)
参数值: signal:指定信号
handler:SIG_IGN:忽略该信号
SIG_DFL:采用系统默认方式处理信号
自定义的信号处理函数指针。
返回值:成功: 以前的信号处理配置,出错 :-1
这个函数的原型非常复杂,可先用如下的typedef进行替换说明:
typedef void sign(int);
sign *signal (int ,handler *);
可见该函数原型整体指向一个无返回值带一个整形参数的函数指针,也就是原始配置函数,接着该原型又带了两个参数,其中的第二个 参数可以是用户自定义的信号处理函数指针。

(3)使用实例:
该实例表明了如何使用signal函数捕捉相应信号,并做出给定的处理,这里,my_func就是信号处理的函数指针,还可以将其改为SIG_IGN或SIG_DFL查看运行结果。
/*mysignal.c*/
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>

/*自定义信号处理函数*/
void my_func(int sign_no)
{
if(sign_no==SIGINT)
printf("I have get SIGINT\n");
else if(sign_no == SIGQUIT)
printf("I have get SIGQUIT\n");
}

int main()
{
printf("waiting for signal SIGINT or SIGQUIT \n");
/*发出相应的信号,并跳转到信号处理函数处*/
signal(SIGINT,my_func);
signal(SIGQUIT,my_func);
pause();
exit(0);
}
2、信号集函数组

(1)函数说明

使用信号集函数组处理信号涉及一系列的函数,这些函数按照调用先后次序科分为以下几个大功能模块;创建信号集合,登记信号处理器以及检测信号。
其中,创建信号集合主要用于创建用户感兴趣的信号,其函数包括以下几个。
sigemptyset:初始化信号集合为空。
sigfillset:初始化信号集合为所有信号的集合。
sigaddset:将指定信号加入到信号集合中去。
sigdelset:将指定信号从信号集中删去。
sigismember:查询指定信号是否在信号集合之中。

登记信号处理器主要用于决定进程如何处理信号。这里要注意的是,信号集里的信号并不是真正可以处理的信号,只有当信号的状态处于非阻塞状态时才真正起作用,因此,首先就要判断出当前阻塞能不能传递给该信号的信号集。这里首先要使用sigprocmask函数判断检测或更改信号屏蔽字然后使用sigaction函数用于改变进程接收到特定信号之后的行为。
检测信号时信号处理的后续步骤,但不是必须得。由于内核可以在任何时刻向某一进程发出信号,因此,若该进程必须保持非中断状态且希望某些信号阻塞,这些信号就处于“未决”状态(也就是进程不清楚它的存在)。所以,在希望保持非中断进程完成相应的任务之后,就应该将这些信号解除阻塞。sigpending函数允许进程检测“未决”信号,并进一步决定它们作何处理。

(2)函数格式:

创建信号集合的函数格式:

所需头文件:#include
函数原型:int sigemptyset(sigset_t *set)
int sigfillset(sigset_t *set)
int sigaddset(sigset_t *set, int signum)
int sigdelset(sigset_t *set, int signum)
int sigmember(sigset_t *set, int signum)
参数:set信号集
signum:指定信号值
返回值:成功:0(sigmember成功返回1,失败返回0)
出错:-1

sigprocmask函数:

所需头文件:#include
函数原型:int sigprocmask(int how, congst sigset_t *set, sigset_t *oset)
参数:how:决定函数操作方式:SIG_BLOCK:增加一个信号集合到当前进程的阻塞集合之中
SIG_UNBLOCK:从当前的阻塞集合中删除一个信号集合
SIG_SETMASK:将当前的信号集合设置为信号阻塞集合。
set:指定信号集
oset:信号屏蔽字
返回值:成功:0
出错:-1
在这里,若set是一个非空指针,则参数how表示函数的操作方式,若how为空,则表示忽略此操作。

sigaction函数:

所需头文件:#include
函数原型:int sigaction(int signum, const sigaction *act,struct sigaction *oldact)
参数: signum:信号的值,可以为除SIHKILL及SIGSTOP外的任何一个特定有效的信号
act:指向结构体sigaction的一个实例指针,指定对特定信号的处理
oldact:保存原来对相应信号的处理
函数返回值:成功:0 失败: -1

sigaction函数中的第2个和第3个参数用到的sigaction结构,这是一个看似非常复杂的结构。

首先给出了sigaction的定义:
struct sigaction {
void (*sa_handler)(int signo);
sigset_t sa_mask;
int sa_flags;
void (*sa_restore)void);
};

sa_handler是一个函数指针,指定信号关联函数,这里除可以是用户自定义的处理函数外,还可以为SIG_DFL(采用缺省的处理方式)或SIG_IGN(忽略信号)。它的处理函数只有一个参数,即信号值。

sa_mask是个信号集,它可以指定在信号处理程序执行过程中哪些信号应当被阻塞,在调用信号捕获函数之前,该信号集要加入到信号的信号屏蔽字中。

sa_flags中包含了许多标志位,是对信号进行处理的各个选择项,它的常见可选值如下图所示:

sigpending函数:

所需头文件: #include
函数原型:int sigpending(sigset_t *set)
参数:set:要检测的信号集
返回值: 成功:0, 出错:-1

在处理信号时,一般需要遵循的操作流程如下如所示:

(3)使用实例

该实例首先把SIGQUIT、SIGINT两个信号加入信号集,然后将该信号集设为阻塞状态,并在该状态下使程序暂停5秒,接下来再将信号集设置为非阻塞状态,再对这两个信号分别操作,其中SIGQUIT执行默认操作,而SIGINT执行用户自定义的函数操作,源代码如下:
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>

/*自定义的信号处理函数*/
void my_func(int signum)
{
printf("If you want to quie , please try SIGQUIT\n");

}

int main()
{
sigset_t set, pendset;
struct sigaction action1,action2;
/*初始化信号集为空*/
if(sigemptyset(&set)<0)
perror("sigemptyset");

/*将相应的信号加入信号集*/
if(sigaddset(&set,SIGQUIT)<0)
perror("sigaddset");
if(sigaddset(&set, SIGINT)<0)
perror("sigaddset");
/*设置信号集屏蔽字*/
if(sigprocmask(SIG_BLOCK,&set,NULL)<0)
perror("sigprocmask");
else
{
printf("blockde\n");
sleep(5);
}

if(sigprocmask(SIG_UNBLOCK,&set,NULL)<0)
perror("sigprocmask");
else
printf("unblock\n");

/*对相应的信号进行循环处理*/
while(1){
if(sigismember(&set,SIGINT)){
sigemptyset(&action1.sa_mask);
action1.sa_handler=my_func;
sigaction(SIGINT,&action1,NULL);
}else if(sigismember(&set,SIGQUIT)){
sigemptyset(&action2.sa_mask);
action2.sa_handler=SIG_DFL;
sigaction(SIGTERM,&action2,NULL);
}
}
}
阅读(601) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~