信号基本原理

Linux是一种多用户多任务的操作系统,系统内会有多个进程存在。无论是操作系统与用户进程之间,还是用户进程之间,经常需要共享数据和交换信息。进程间相互通信的方法有多种,信号便是其中最为简单的一种,它用以指出某事件的发生。在Linux系统中,根据具体的的软硬件情况,内核程序会发出不同的信号来通知进程某个事件的发生。对于信号的发送,尽管可以由某些用户进程发出,但是大多数情况下,都是由内核程序在遇到以下几种特定情况的时候向进程发送的,例如:

1. 系统测出一个可能出现的硬件故障,如电源故障。

2. 程序出现异常行为,如企图使用该进程之外的存贮器。

3. 该进程的子进程已经终止。

4. 用户从终端向目标程序发出中断(BREAK)键、继续(CTRL-Q)键等。

当一个信号正在被处理时,所有同样的信号都将暂时搁置(注意,并没有删除),直到这个信号处理完成后,才加以理会。

当一个进程收到信号后,用下列方式之一做出反应∶

1.忽略该信号;

2.捕获该信号(即,内核在继续执行该进程之前先运行一个由用户定义的函数);

3.让内核执行与该信号相关的默认动作。

现在用一个例子来简要说明信号的发送、捕获和处理,通过它,你就可以对信号有一个大致的印象。例如,当某程序正在执行期间,如果发现它的运行有问题,我们可以用ctrl-c或delete键打断它的执行,这实际上就是向进程发送了一个中止信号。该进程收到这个中止信号后,可以根据事先的设定,对该信号做出相应的处理,如ctrl-c或delete键被定义为一个中止信号,进程接受到这个信号,便中途退出了。上面是用信号去中断另一个进程的实例。除此以外,内核还可以通过发信号来通知一个进程:它的子进程已经终止,或通知一个超时进程:它已被设置警报(alarm)。

接下来我们开始详细介绍Linux系统中的一些与信号相关的函数。

我们介绍的第一个函数是signal ()函数,它定义在ANSI <signal.h>库中 ,该函数原型如下:

void * signal(int signum, void * handler);

它的第一个参数是将要处理的信号。第二参数是一个指针,该指针指向以下类型的涵数∶

void func();

当信号signum产生时,内核会尽快执行handler函数。一旦handler返回,内核便从中断点继续执行进程。第二参数可以取两个特殊值:SIG_IGN和SIG_DFL。SIG_IGN用以指出该信号应该被忽略;SIG_DFL用以指示,内核收到信号后将执行默认动作。尽管一个进程不能捕获SIGSTOP和SIGKILL信号,但是内核可以执行与该信号有关的默认动作作为替代,这些默认动作分别是暂停进程和终止进程。


重发信号

当一个正在为SIGx运行handler的进程收到另一个SIGx信号时,它应该如何处理?通常,人们会希望内核中断该进程而再一次运行handler。为此,就要求无论何时调用handler,它都必须完全可用——即使当时她正在运行也必须如此。也就是说,要求handler必须是“可重入的(re - entrant)”。然而,设计可重入的handlers是件相当复杂的事情,因此, linux没有采用此种方案。

对于重发信号问题,起初的解决方案是:在执行用户定义的handler之前,重新将handler设置为SIG_DFL。然而,事实证明这是一个拙劣的解决方案,因为当两个信号迅速出现时,每个都给以不同地处理。内核为第一个信号执行handler而为第二个执行默认动作。这样,第二信号有时会导致该进程终止。因此,这个实现被称作"不可靠的信号( unreliable signals) "。

在下一节中,我们将看到POSIX signal API是怎样“漂亮地”解决这个难题的。

POSIX信号
POSIX signal API提供了一种新的机制,它能够处理多个信号而不必中断当前进程。

对于POSIX的信号实现来说,当一个进程正在处理一个信号时,如果有其他的信号到达,那末这些“其他的”信号将被挂起,直到该handler返回为止。可是,当一个SIGx信号在挂起之际,如果又发来另一个SIGx信号,那么内核仅把一个信号递送给该进程——也就是说,有一个信号丢失了。实际上这算不上是个大问题,因为信号除了信号号码本身之外不传送任何的信息。因此,在一个非常短的周期内多次发送一个信号相当于只将它发送一次。

信号集

POSIX signal函数(在<signal.h>中)运行在用sigset_t数据类型封装的信号集之上。这里是他们的原型和说明∶

int sigemptyset(sigset_t * pset); -- 将pset中的信号全部清除

int sigfillset(sigset_t * pset); -- 将全部有效的信号填入pset

int sigaddset(sigset_t * pset, int signum); -- 将信号signum添加到pset

int sigdelset(sigset_t * pset, int signum); -- 从pset中删除信号signum。

int sigismember(const sigset_t * pset, int signum); -- 如果signum属于pset,返回一个非零值;否则为0。

记录Handler

为记录handler,需要调用sigaction()函数,原型如下∶

int sigaction(int signum, struct sigaction * act, struct sigaction * prev);

sigaction ()的作用是为信号signum设置handler。内核对signum的处理将在参数act中加以描述,sigaction类型如下:

struct sigaction {
sighanlder_t sa_hanlder; sigset_t sa_mask; unsigned long sa_flags; void (*sa_restorer)(void); /*从来不用* /};

sa_hanlder是一个指针,它指向以下类型的函数∶

void handler (int signum);

另外,它还有两个特值:SIG_DFL和SIG_IGN。sa_mask域包含了所有当handler运行时内核将阻塞的信号。注意,不管sa_mask的值为何,正在被处理的信号总是被阻塞。当然,通过适当设定sa_flags域的flags,你还是可以逾越这个特性的。通过逐位或运算,这些flags可以取下列一个或多个值:

1.SA_NOCLDSTOP - -确保父进程当它的一个子进程停止时不会收到一个SIGCHLD信号。
2.SA_NOMASK - -逾越该信号的默认阻塞,如果它的handler当前正在执行的话。这些flag能模拟ANSI的不可靠的信号。
3.SA_ONESHOT -重新设置signum的handler为SIG_DFL。这些flag模拟ANSI signal ()函数的行为
4.SA_RESTART - -当handler退出时,确保syscall重新启动。

如果sigactions的最后的参数不是NULL,在sigaction ()被调用之前用signum的序列填入。为了能够获得当前的信号序列而不改变它,应该把NULL作为第二个参数传递,然后将正确的sigaction指针作为第三个参数传递.
 

第3节 高级信号处理
在我们最后的讨论中,将涉及信号处理的一些高级话题,如信号挂起和等待一个信号等。


获得当前信号掩码

进程当前被阻塞的信号集被称作“信号掩码”。为了获得或改变当前信号掩码 ,可以调用 sigprocmask ()函数∶

int sigprocmask(int mode, const sigset_t * newmask, sigset_t

oldmask);
第一个参数可以取下列值∶

SIG_BLOCK - -将newmask中的全部信号加入到当前信号掩码中。
SIG_UNBLOCK - -从当前信号掩码中,将newmask的所有信号全部删除
SIG_SETMASK -仅阻塞newmask内的信号;将其余的信号解除阻塞。

如果oldmask不是NULL,那么就把当前的信号掩码( sigprocmask被调用之前)拷贝给它。下列代码可用于检索一个进程的当前信号掩码∶

sigset_t oldmask;
sigemptyset(&oldmask); sigprocmask(SIG_BLOCK, NULL, &oldmask);


阻塞信号

我们经常需要在一段程序代码中阻塞进入的信号,而这段代码和信号的handler却可能在同时修改数据。对于一个SIGALRM信号,考虑下面的handler∶

char *Flags=someString; /*一个全局字符串*/
void handleAlarm(int sig){ free(someString); someString=NULL; }

当程序正在处理Flags时,如果一个SIGALRM信号被送达,这时将发生什么?

for (i=0; i<10; ++i)
Flags[i]=CLEAR;

后果是灾难性的——循环语句正在往其中写数据,而handler却释放了Flags。为避免上述情况,在进入循环之前我们必须阻塞SIGALRM,过后再为其解除阻塞。

sigset_t alrm;
sigemptyset(&alrm); sigaddset(&alrm, SIGALRM); sigprocmask(SIG_BLOCK, &alrm, NULL); /*阻塞*/ for (i=0; i<10; ++i) /*安全地处理Flags*/ Flags[i]=OK; sigprocmask(SIG_UNBLOCK, &alrm, NULL);/*解除阻塞*/

为了列出所有当前挂起的信号,可以调用sigpending ()函数∶

int sigpending(sigset_t * pending);

调用sigpending ()后, pending将包含全部当前闭塞信号。

等待信号

基于事件的应用程序,总是等待一个信号的出现,直到信号出现才开始运行。为此,要使用<unistd.h>中的pause()函数:

int pause(void);

直到一个信号已经投递给该进程之后,pause ()才返回。如果存在一个与信号匹配的handler, 那么handler将在pause ()返回之前执行。换句话说,你可以调用在<signal.h>中的sigsuspend (),如同下述∶

int sigsuspend(const sigset_t * tempmask);

sigsuspend ()暂停该进程直到一个信号已经被投递。但是不同于pause (),在等待一个信号出现之前,它临时设置该进程的掩码为tempmask。一旦信号出现,该进程的信号掩码就会恢复为sigsuspend ()被调用之前的值。同时,pause ()(它和 sigsuspend ()两者都返回- 1)将设定errno为EINTR.

结束语

在Linux这种多用户多任务的环境中,为了完成一个任务有时候需要多个进程协同工作,这必然牵扯到进程间的相互通信。本文给出了作为进程间通信的最简单的一种——信号的发送、捕获和处理的大致过程,并介绍了与其相关的几个常用的函数,希望阅读本文后能够使你加深对它的了解。

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/ljx0305/archive/2008/09/09/2904048.aspx

 

常用信号汇总

SIGHUP    终止进程    终端线路挂断
SIGINT    终止进程    中断进程
SIGQUIT   建立CORE文件终止进程,并且生成core文件
SIGILL   建立CORE文件      非法指令
SIGTRAP  建立CORE文件      跟踪自陷
SIGBUS   建立CORE文件      总线错误
SIGSEGV  建立CORE文件      段非法错误
SIGFPE   建立CORE文件      浮点异常
SIGIOT   建立CORE文件      执行I/O自陷
SIGKILL  终止进程    杀死进程
SIGPIPE  终止进程    向一个没有读进程的管道写数据
SIGALARM  终止进程    计时器到时
SIGTERM  终止进程    软件终止信号
SIGSTOP  停止进程    非终端来的停止信号
SIGTSTP  停止进程    终端来的停止信号
SIGCONT  忽略信号    继续执行一个停止的进程
SIGURG   忽略信号    I/O紧急信号
SIGIO    忽略信号    描述符上可以进行I/O
SIGCHLD  忽略信号    当子进程停止或退出时通知父进程
SIGTTOU  停止进程    后台进程写终端
SIGTTIN  停止进程    后台进程读终端
SIGXGPU  终止进程    CPU时限超时
SIGXFSZ  终止进程    文件长度过长
SIGWINCH  忽略信号    窗口大小发生变化
SIGPROF  终止进程    统计分布图用计时器到时
SIGUSR1  终止进程    用户定义信号1
SIGUSR2  终止进程    用户定义信号2
SIGVTALRM 终止进程    虚拟计时器到时

1) SIGHUP 本信号在用户终端连接(正常或非正常)结束时发出, 通常是在终端的控
制进程结束时, 通知同一session内的各个作业, 这时它们与控制终端
不再关联.
2) SIGINT 程序终止(interrupt)信号, 在用户键入INTR字符(通常是Ctrl-C)时发出
3) SIGQUIT 和SIGINT类似, 但由QUIT字符(通常是Ctrl-)来控制. 进程在因收到
SIGQUIT退出时会产生core文件, 在这个意义上类似于一个程序错误信
号.
4) SIGILL 执行了非法指令. 通常是因为可执行文件本身出现错误, 或者试图执行
数据段. 堆栈溢出时也有可能产生这个信号.
5) SIGTRAP 由断点指令或其它trap指令产生. 由debugger使用.
6) SIGABRT 程序自己发现错误并调用abort时产生.
6) SIGIOT 在PDP-11上由iot指令产生, 在其它机器上和SIGABRT一样.
7) SIGBUS 非法地址, 包括内存地址对齐(alignment)出错. eg: 访问一个四个字长
的整数, 但其地址不是4的倍数.
8) SIGFPE 在发生致命的算术运算错误时发出. 不仅包括浮点运算错误, 还包括溢
出及除数为0等其它所有的算术的错误.
9) SIGKILL 用来立即结束程序的运行. 本信号不能被阻塞, 处理和忽略.
10) SIGUSR1 留给用户使用
11) SIGSEGV 试图访问未分配给自己的内存, 或试图往没有写权限的内存地址写数据.
12) SIGUSR2 留给用户使用
13) SIGPIPE Broken pipe
14) SIGALRM 时钟定时信号, 计算的是实际的时间或时钟时间. alarm函数使用该
信号.
15) SIGTERM 程序结束(terminate)信号, 与SIGKILL不同的是该信号可以被阻塞和
处理. 通常用来要求程序自己正常退出. shell命令kill缺省产生这
个信号.
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 Power failure

有两个信号可以停止进程:SIGTERM和SIGKILL。 SIGTERM比较友好,进程能捕捉这个信号,根据您的需要来关闭程序。在关闭程序之前,您可以结束打开的记录文件和完成正在做的任务。在某些情况下,假如进程正在进行作业而且不能中断,那么进程可以忽略这个SIGTERM信号。

对于SIGKILL信号,进程是不能忽略的。这是一个 “我不管您在做什么,立刻停止”的信号。假如您发送SIGKILL信号给进程,Linux就将进程停止在那里。


本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/ljx0305/archive/2008/09/09/2904056.aspx

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值