unix环境高级编程之信号篇(一)

5 篇文章 0 订阅
2 篇文章 0 订阅

文章是我读《Unix环境高级编程》这本书的一些笔记,大部分保持了原文,还加了一些自己的解释,转载请注明!!


一、引言:

信号是软件中断,很比较重要的应用程序都需要处理信号。信号提供了一种处理异步事件的方法,例如,中断用户键入中断键,则会通过信号机制停止一个程序,或及早终止管道中的下一个程序。

二、概念:
每一个信号都有一个名字。这些名字都是以SIG开头,如SIGABRT是夭折信号,当进程调用abort函数时产生这种信号。SIGALRM是闹钟信号,当由alarm函数设置的计时器超时后产生此信号。
在头文件<signal.h>中,信号都会被定义成正整数。内核包括对用户级别应用程序有意义的头文件,这被认为是一种糟糕的形式,所以如果应用程序和内核两者都需要使用同一种定义,那么久需要将有关信息放置在内核头文件中,然后再用户级头文件中再包括该内核头文件。
不存在编号为0的信号,kill函数都信号编号为0的油特殊的应用。此种信号称为空信号。
信号是异步事件的一种经典实例。产生信号的事件对进程而言是随机出现的,进程不能简单的测试一个变量如errno来判断是否出现一个信号,二是必须告诉内核“在出现这种信号时应该执行下列操作”。
可以要求内核在某个信号出现时按照下列三种方式之一去处理,我们称之为信号的处理或者信号的相关的动作。
1、忽略此信号。大多数信号都可以使用这种信号,但是有两种信号不能忽略:SIGKILL和SIGSTOP,因为这两种信号向超级用户
提供了可以终止或者停止任何进程的可靠方法。另外,如果进程发生硬件异常的话,如果被忽略,进程的运行行为是不可预测的。
2、捕捉信号。为了做到这一点,要通知内核在某种信号发生时调用一个用户函数,在用户函数中,可执行用户希望对这种时间的处理。
3、执行默认操作。每一种信号系统都提供了默认的操作。注意,针对大多数信号的默认操作动作是终止进程。
需要注意:如果进程在处理一个信号返回前的时候有产生了一个信号,那么同等或高优先级的信号会中断低优先级的信号处理


三、常用信号详解
SIGABRT:调用abort函数时产生此信号,进程异常终止
SIGALRM:在调用alarm函数设置的计时器超时时,产生此信号,若有setitimer(2)函数设置的间隔时间超时,也会产生此信号
SIGBUS:指示一个实现定义的硬件故障,如出现某个类型的内存故障时会产生此信号
SIGCHLD:当一个进程终止或者停止时,发送这个信号给其父进程,按系统默认,将忽略此信号。如果父进程希望被告知其子进程的这种状态的改变,则应捕捉此信号。捕捉函数中通常调用一个wait函数以取得子进程id和其终止状态
SIGFPE:此信号表示一个算术运算异常,例如除以0,浮点溢出等。
SIGHUP:中断接口连接断开,将此信号发送给与该中端相关的控制进程
SIGILL:此信号指示进程已执行一条非法硬件命令
SIGINFO:BSD信号,用户按状态键(ctrl+T),终端驱动程序产生此种信号并送至前台进程组中的每一个进程,此信号经常导致在终端上显示前台进程组中各进程的状态信息
SIGINT:当用户按下中断键时(delete或者crtl+C),终端产生此信号并送至前台进程组中的每一个进程。当一个进程失控时,常用此信号终止它
SIGKILL:这是两个不能被忽略的信号之一,它向系统管理员提供了一种可以杀死任意进程的可靠方法
SIGPIPE:如果在写管道时读进程已经终止,则产生此信号。当类型为SOCK_STREAM的套接字已不在连接时,进程写到该套接字也产生此信号
SIGQUIT:用户按下退出键(ctrl+\),产生此信号,并送至前台进程组中的每一个进程,此信号不仅终止前台进程组(如SIGINT),还会产生core文件
SIGSEGV:该信号指示进程进行了一次无效内存的引用
SIGSTOP:这是一个作业控制信号,用户停止一个进程。它类似于交互停止信号(SIGTSTP),但是这个信号不能被忽略
SIGSYS:此信号指示一个无效的系统调用,如版本不支持的系统函数调用
SIGTERM:这是由kill(1)命令发送的系统默认终止信号
SIGTSTP:交互时停止信号,用户按下(ctrl+Z)时产生,
SIGUSR1:用户自定义信号1
SIGUSR2:用户自定义信号2
SIGVTALRM:当一个有setitimer(2)函数设置的虚拟间隔时间到期时产生此信号。


四、signal函数
说了那么多,信号接收和处理使用哪个系统api呢?unix系统信号机制最简单的借口是signal函数。
#include <signal.h>
void (*signal(int signo,void(*func)(int)))(int);
返回值:若成功则返回信号以前的处理配置,出错返回SIG_ERR;

signo:该参数是上述三中的信号名,包括但不仅限于此。
func:该参数可以是常量SIG_IGN,或者常量SIG_DFL,或者接收到信号时需要调用的函数的地址。
如果是SIG_IGN,则向内核表示忽略此信号,(记住有两个信号不能忽略)
如果是SIG_DFL,则表示接收到信号时执行系统默认的动作
如果是指定函数地址,则在发生和接收到信号时,调用该函数,我们称这种处理为捕捉信号。此函数称为信号处理程序或者信号捕捉函数
 
signal函数原型说明该函数需要两个参数,并且返回一个函数指针,而该函数指针所指向的函数是无返回值的(void*)而且需要一个整形参数(最后的int)。
第一个参数signo是一个整数(其实就是信号编号),第二个参数是一个函数指针,该函数需要一个整形参数,无返回值。
signal的返回值其实指向之前的信号处理函数的指针。这句话得意思是:
signal(SIGUSR1,sig_usr1);//此时应该返回值为系统默认处理动作值,也就是SIG_DFL
...
signal(SIGUSR1,sig_usr2);//这时返回值的函数指针就指向了sig_usr1
系统的<signal.h>中会找到以下形式的声明:

define SIG_ERR (void (*) ())-1
define SIG_DFL (void (*) ())0
define SIG_IGN (void (*) ())1

简单例子sig.c:
#include <stdio.h>
#include <signal.h>

static void sig_usr(int signo)
{
if(signo == SIGUSR1)
printf("recv sig_usr1\n");
else if(signo == SIGUSR2)
printf("recv sig_usr2\n");
else
printf("recv signal signo = %d\n",signo);
}

int main()
{
if(SIG_ERR == signal(SIGUSR1,sig_usr))
printf("err sig_usr1\n");
else if(SIG_ERR == signal(SIGUSR2,sig_usr))
printf("err sig_usr2\n");
for(;;)
pause();
}

$./sig & 后台启动
[1] 7172 作业控制shell打印作业和进程号
$kill -SIGUSR1 7172 向进程发送SIGUSR1信号
recv sig_usr1
$kill -SIGUSR2 7172 向进程发送SIGUSR2信号
recv sig_usr2
$kill 7172 向进程发送SIGTERM信号
[1]+ 已终止 ./sig


五、可重入函数和不可重入函数
可重入函数:在执行时,允许中断,然后返回;
不可重入函数:在执行函数时,由于函数内部存在诸如全局数据结构变量,所以不允许被中断
不可重入的原因:1、函数中使用了一些全局数据结构
2、函数调用了malloc和free
3、它们是标准I/O函数,标准I/O库的实现很多都利用了全局数据结构

如果在信号处理函数中调用了一个不可重入函数,那么进程的运行结果是不可预测的,进程很容易被SIGSEGV信号终止


关于信号未决和信号集和其涉及到的函数会在下一节介绍,敬请关注。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值