UNIX环境高级编程 信号

 

 

相关函数列表

//系统信号机制最简单的接口是signal函数
#include <signal.h>
void (*signal(int signo, void (*func)(int))) (int);

//kill可以将信号发送给进程或进程组,raise允许进程向自身发送信号
#include <signal.h>
int kill(pid_t pid, int signo);
int raise(int signo);   //raise(signo)  等于  kill(getpid(), signo)
//kill的pid参数有以下4种不同的情况
//1.  pid>0    将该信号发送给进程ID为pid的进程
//2.  pid==0  将该信号发送给与发送进程属于同一进程组的所有进程(这些进程的
//        进程组ID等于发送进程的进程组ID),而且发送进程具有权限向这些进程发送
//        信号。这里用的术语"所有进程"不包括实现定义的系统进程集。对于大多数
//        UNIX系统,系统进程集包括内核进程和init
//3.   pid<0    将该信号发送给其他进程组ID等于pid绝对值,而且发送进程具有
//        权限向其发送信号的所有进程。如前所述,所有进程并不包括系统进程集中
//        的进程
//4.   pid==-1 将该信号发送给发送进程有权限向他们发送信号的所有进程。如前
//        所述,所有进程不包括系统进程集中的进程


//可以设置一个定时器(闹钟时间),在将来的某个时刻该定时器会超时。定时器
//超时时,产生SIGALRM信号
#include <unistd.h>
unsigned int alarm(unsigned int seconds);

//此函数使调用进程挂起直至捕捉到一个信号
#include <unistd.h>
int pause(void);


//一个能表示多个信号--信号集(signal set)的数据类型。以便告诉内核不允许发生该信号集合中的
//信号。不同的信号编号可能超过一个整型量锁包含的位数,所以一般而言,不能用整型量中的一位
//代表一种信号,也就是不能用一个整型表示信号集,下面定义5个信号集函数
#include <signal.h>
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset(sigset_t *set, int signo);
int sigdelset(sigset_t *set, int signo);
int sigismember(const sigset_t *set, int signo);

//下面函数可以检测或更改,或同时进行检测和更改进程信号屏蔽字
//注意不能阻塞SIGKILL和SIGSTOP信号
#include <signal.h>
int sigprocmask(int how, const sigset_t *restrict set, sigset_t *restrict oset);

//how的参数说明
//SIG_BLOCK      该进程新的信号屏蔽字是其当前信号屏蔽字和set指向的信号集的并集,set包含了
//               希望阻塞的附加信号
//SIG_UNBLOCK    该进程新的信号屏蔽字是其当前信号屏蔽字和set所指向信号集补集的交集。set包含
//               了希望解除阻塞的信号
//SIG_SETMASK    该进程新的信号屏蔽字是set指向的值

//下面函数返回一信号集,对于调用进程而言,其中的各信号是阻塞不能递送的,因而也一定是当前
//未决的。该信号通过set参数返回
#include <signal.h>
int sigpending(sigset_t *set);


//下面函数的功能是检查和修改(或检查并修改)与指定信号相关联的处理动作,此函数取代了UNIX早期
//版本使用的signal函数。
#include <signal.h>
int sigaction(int signo, const struct sigaction *restrict act, struct sigaction *restrict oact);

//setjmp和longjmp有一个问题,当捕捉到一个信号时。进入信号捕捉函数后,此时当前信号被自动的
//加入到进程的信号屏蔽字中。这阻止了后来产生的这种信号中断该信号处理程序。
//为了允许两种形式并存,POSIX.1定义了两个新函数
#include <setjmp.h>
int sigsetjmp(sigjmp_bug env, int savemask);
void siglongjmp(sigjmp_buf env, int val);

//
#include <signal.h>
int sigsuspend(const sigset_t *sigmask);

//使程序异常终止
#include <stdlib.h>
void abort(void);


//休眠函数
//此函数使调用进程被挂起直到满足下面两个条件之一
//1.已经过了seconds所指定的墙上时钟时间
//2.调用进程捕捉到一个信号并从信号处理程序返回
#include <unistd.h>
unsigned int sleep(unsigned int seconds);

//纳秒级精度的休眠
//reqtp参数用秒和纳秒指定了需要休眠的时间长度
//如果某个信号中断了休眠间隔,并存并没有终止remtp参数指向的timespec结构就会被设置为未休眠
//完的时间长度。如果对未休眠完的时间并不感兴趣,可以把该参数置为NULL
#include <time.h>
int nanosleep(const struct timespec *reqtp, struct timespec *remtp);

//随着多个系统时钟的引入,需要使用相对于特定时钟的延迟时间来挂起调用线程
#include <time.h>
int clock_nanosleep(clockid_t clock_id, int flag, const struct timespec *reqtp, 
                    struct timespec *remtp);


//在POSIX.1的实时扩展中有些系统开始增加对信号排队的支持,通常一个信号带有一个位信息:信号
//本身。除了对信号排队意外,这些扩展允许应用程序在递交信号时传递更多的信息,嵌入在siginfo
//结构中。使用排队信号必须做以下几个操作
//1.使sigaction函数安装信号处理程序时指定SA_SIGINFO标志,如果没有给出这个标准,信号会延迟,
//  但信号是否进入队列要取决于具体实现
//2.在sigaction结构的sa_sigaction成员中(而不是通常的sa_handler字段)提供信号处理程序。实现
//  可能允许用户使用sa_handler字段,但不能捕获sigqueue函数发送出来的额外信息
//3.使用sigqueue函数发送信号
#include <signal.h>
int sigqueue(pid_t pid, int signo, const union sigval value);


//如何在信号编号和信号名之间进行映射,某些系统提供数组
extern char *sys_siglist[];

//使用下面函数可以可移植打印与信号编号对应的字符串
#include <signal.h>
void psignal(int signo, const char *msg);

//如果sigaction信号处理程序中有siginfo结构,可以使用下面函数打印信号信息
#include <signal.h>
void psiginfo(const siginfo_t *info, const char *msg);

//如果只需要信号的字符描述不符,也不需要把它写入到标准错误文件中(如果可以写到日志文件中),
//可以使用strsignal函数,类似于strerror
#include <string.h>
char *strsignal(int signo);

//Solaris提供一对函数,一个函数将信号编号映射为信号名,另一个则反之
#include <signal.h>
int sig2str(int signo, char *str);
int str2sig(const char *str, int *signop);

 

 

信号概念

信号时软件终端,很多比较重要的应用程序都需要处理信号。信号提供了一种处理异步事件的方法

很多条件可以产生信号:

1.当用户按某些终端键时,引发终端产生的信号

2.硬件异常产生的信号,除数为0,无效内存引用等

3.进程调用kill函数可将任意信号发送给另一个进程或进程组

4.用户可用kill命令将信号发送给其他进程

5.当检测到某种软件条件发生,并将其统治有关进程时也产生信号。

信号时异步事件的经典实例。产生信号的事件对进程而言是随机出现的

 

在某个信号出现时,可以告诉内核按下列三种方式之一处理,我们称之为信号的处理或与信号相关的动作

1.忽略此信号。大多数信号都可使用这种方式进行处理,但有两种信号决不能被忽略,它们是SIGKILL和

   SIGSTOP。原因是:它们向内核和超级用户提供了使进程终止或停止的可靠版本。如果忽略某些硬件异常

   产生的信号,则进程运行的行为时未知的。

2.捕捉信号。为了做到这一点,要通知内核在某种信号发生时,调用一个用户函数。在用户函数中,可执行

   用户希望对这种事件进行的处理

3.执行系统默认的动作。大多数信号的系统默认动作时终止该进程

 

 

UNIX系统信号(终止+core 表示在当前工作目录的core文件中复制了该进程的内存映像,文件名为core)

名字 说明 默认动作 详细说明
SIGABRT 异常终止(abort) 终止+core 调用abort函数时产生此信号,进程异常终止
SIGALRM 定时器超时(alarm) 终止

当调用alarm函数设置的定时器超时时,产生此信号

settitimer函数设置的间隔已经超时时,也产生此信号

SIGBUS 硬件故障 终止+core

指示一个实现定义的硬件故障,当出现某些类型的内存故障时,

产生此信号

SIGCANCEL 线程库内部使用 忽略 这是Solaris线程库内部使用的信号,它不适用于一般应用
SIGCHLD 子进程状态改变 忽略

在一个进程终止时,SIGCHLD信号被发送给父进程。系统默认

将忽略此信号。如果父进程希望被告知子进程的这种状态改变,

则应捕捉此信号,使用wait函数取得子进程ID和终止状态

早起System V版本有一个SIGCLD(无H)的类似信号

SIGCONT 使暂停进程继续 继续/忽略

此作业控制信号发送给需要继续运行,但当前处于停滞状态的进程,

如果接受到此信号的进程处于停止状态,则系统默认动作时

使该进程继续运行;否则默认动

作时忽略此信号

SIGEMT 硬件故障 终止+core 指示一个实现定义的硬件故障
SIGFPE 算术异常  终止+core   此信号表示一个算术运算异常,如除0,浮点溢出等
SIGFREEZE 检查点冻结  忽略 

此信号仅由Solaris定义。它用于通知进程再冻结系统状态之前需要采取特定

动作,如系统进入休眠或挂起状态时可能需要做这种处理 

SIGHUP 连接断开  终止 

如果终端接口检测到一个连接断开,则将此信号发送给与该终

端相关的控制

进程(会话首进程)。注意,接收到此信号的会话首进程可能

在后台,这区别于

由终端正常产生的几个信号(中断,退出和挂起)这些信号总是传递给前

台进程组 

SIGILL  非法硬件指令  终止+core  此信号表示进程已执行一条非法硬件指令 
SIGINFO 键盘状态请求  忽略 

这是一种BSD信号,当用户按状态键(一般是ctrl+T)时,终端驱动程序产生此

信号并发送至前台进程组中的每一个进程,此信号通常造成在终端上显示前

台进程组中各进程状态的状态信息

SIGINET  终端终端符 终止 

当用户按中断键(一般是Delete或ctrl+C)时,终端驱动产生此信号并发送至

前台进程组中的每一个进程。当一个进程在运行时失控,特别是

它正在屏幕上产生大量不需要的输出时,常用此信号终止它 

SIGIO  异步IO  终止/忽略   此信号指示一个异步I/O事件
SIGIOT 硬件故障  终止+core  这指示一个实现定义的硬件故障 
SIGJVM1  java虚拟机内部使用  忽略 Solaris上为java虚拟机预留的一个信号 
SIGJVM2  java虚拟机内部使用  忽略  Solaris上为java虚拟机预留的另一个信号 
SIGKILL 终止  终止 

这是两个不能被捕捉或忽略信号中的一个。它向系统管理员

提供了一种可以杀死任意进程的可靠方法

SIGLOST  资源丢失  终止 

运行在Solaris NFSv4客户端系统中的进程,恢复阶段不能重新

获得锁,此时将由这个信号通知该进程 

SIGLWP  线程库内部使用  终止/忽略 

此信号Solaris线程库内部使用欧冠,并不做一般使用。在

FreeBSD系统中SIGLWP是SIGTHR的别名

SIGPIPE  写至无读进程的管道  终止 

如果在管道的读进程已经终止写管道,则产生此信号。

当类型为SOCK_STREAM的套接字已不再连接时,进程写该

套接字也产生此信号 

SIGPOLL 可轮询事件(poll)  终止 

这个信号在SUSv4中被标记为弃用,将来的标准可能会将此

信号移除,当在一个可轮询的设备上发生一个特定事件时产生

此信号

SIGPROF 

梗概事件超时

(setitimer) 

终止 

这个信号在SUSv4中被标记为弃用,将来的标准可能会将此

信号移除,当setitimer函数设置的梗概统计间隔定时器(profiling

interval timer)已经超时时产生此信号 

SIGPWR 电源失效/重启动  终止/忽略 

这是一种依赖于系统的信号,它主要用于具有不间断电源UPS

的系统,如果电源失效,则UPS起作用。并且通常软件会接到

通知。在这种情况下系统依靠蓄电池继续运行,所以无须作

任何处理。但是如果蓄电池也将不能支持工作,则软件通常会

再次接到通知。此时系统必须使其各部分都停止运行,这时

应当发送SIGPRW信号。大多数系统中接到蓄电池电压过低

信号的进程将信号SIGPRW发送给init进程,init处理停机操作 

SIGQUIT  终端退出符  终止+core 

当用户在终端上按退出键(ct+\)时,终端驱动程序产生此信号,

并发送给前台进程组中的所有进程,此信号不仅终止前台进程

组,同时产生一个core文件 

SIGSEGV  无效内存引用   终止+core

指示进程进行了一次无效的内存引用(通常说明程序有错,比如

访问了一个未经初始化的指针) 

SEGV表示段违例(segmentation violation)

SIGSTKFLT 协处理器栈故障  终止 

此信号仅由Linux定义。出现在早起的linux版本,企图用于数学

协处理器的栈故障,该信号并非由内核产生,但仍保留以向后

兼容

SIGSTOP 停止  停止进程

这是一个作业控制信号,它停止一个进程,它类似于交互停止

信号(SIGTSTP)但是SIGSTOP不能捕捉或忽略

SIGSYS  无效系统调用  终止+core 

该信号指示一个无效的系统调用,由于某种未知原因,进程执行

了一条机器执行,内核认为这是一条系统调用。但该指令指示

系统调用类型的参数却是无效的。如用户编写了一道用新系统

调用的程序然后运行该程序二进制代码,而所用的操作系统却

是不支持该系统调用的早起版本于是出现上述情况 

SGTERM 终止  终止 

这是由kill命令发送的系统默认终止信号。由于该信号是由应用

程序捕获的,使用SIGTERM也让程序有机会在退出之前做好

清理工作,从而优雅终止(相对于SIGKILL而言,SIGKILL不

能被捕获或忽略) 

SIGTHAW 检查点解冻  忽略 

此信号仅由Solaris定义,在被挂起的系统恢复时,该信号用于

通知相关进程,他们需要采取特定的动作。 

SIGTHR 线程库内部使用  忽略  FreeBSD线程库预留的信号,它的之定义或与SIGLWP相同
SIGTRAP  硬件故障  终止+core  指示一个实现定义的硬件故障 
SIGTSTP  终端停止符  停止进程

交互停止信号,当用户在终端上按挂起键(ctrl+Z)时,终端驱动

程序产生此信号,该信号发送至前台进程组中的所有进程 

SIGTTIN  后台读控制tty  停止进程

当一个后台进程组试图读其控制终端时,终端驱动程序产生此

信号,下列情况不产生此信号

a.读进程忽略或阻塞此信号

b.读进程所属的进程组是孤儿进程组,此时读操作返回出错 

SIGTTOU  后台写向控制tty  停止进程

当一个后台进程组进程试图写控制终端时,终端驱动程序产生

此信号,与上所述的SIGTTIN信号不同,一个进程可以选择

允许后台进程写控制终端。如果不允许后台进程写控制终端则

与SIGTTIN相似,也有两种特殊情况

a.写进程忽略或阻塞此信号

b.写进程所属进程组是孤儿进程组,此时不产生信号返回出错 

SIGURG 紧急情况(socket  忽略 

此信号通知进程已经发送了一个紧急情况,在网络连接上接到

带外的数据时,可选择的产生此信号 

SIGUSR1 用户定义信号 终止 这是一个用户定义的信号,可用于应用程序
SIGUSR2  用户定义信号  终止  这是另一个用户定义的信号,同样也可以用于应用程序 
SIGVTALRM

虚拟时间闹钟

(setitimer)

终止

当一个由setitimer函数设置的虚拟间隔时间已经超时时,

产生此信号

SIGWAITING 线程库内部使用 忽略 此信号由Solaris线程库内部使,不做他用
SIGWINCH 终端窗口大小改变 忽略

内核维持与每个终端或伪终端相关联窗口的大小,进程可以

用ioctl函数得到或设置窗口大小,如果用 ioctl的设置更改了

窗口大小,则内核将SIGWINCH信号发送至前台进程组

SIGXCPU

超过CPU限制

(setrlimit)

终止或

终止+core

Single UNIX Specification的XSI扩展支持资源限制的概念,

如果进程超过了其软CPU时间限制,则产生此信号

SIGXFSZ

超过文件长度限制(setrlimit)

终止或

终止+core

如果进程超过了其软文件长度限制,则产生此信号
SIGXRES 超过资源控制 忽略

此信号仅由Solaris定义。可选择的使用此信号以通知进程

超过了预配置的资源值。Solaris资源限制机制是一个通用

设施,用于控制在独立应用集之间共享资源的使用

 

 

kill命令

用过kill 命令可以给以进程发送信号

kill -l 可以查看能发送的所有信号

 

 

可重入函数

Single UNIX Specification说明了在信号处理程序中保证调用安全的函数,这些函数是可重入的并被称为是

   异步信号安全的(async-signal safe)。除了可重入以外,在信号处理操作期间,它会阻塞任何会引起不一致

   的信号发送

 

 

可靠信号术语和语义

当对进程发送一个信号时,在信号产生(generation)和递送(delivery)之间的事件间隔内信号时未决的(pending)

如果有多个信号要递送给一个进程,POSIX.1没有规定这些信号的递送顺序,但POXIS.1基础部分建议:

   在其他信号之前递送与进程当前状态有关的信号,如SIGSEGV

每个进程都有一个信号屏蔽字(signal mask),对规定了当前要阻塞递送到该进程的信号集,对于每种可能的

信号,该屏蔽字中都有一位与其对应。进程可以调用sigprocmask来检测和更改其当前信号屏蔽字

  

 

一段有问题的代码,alarm()和pause()模拟sleep

static void sig_alrm(int signo) {
    //...
}

unsigned int sleep1(unsigned int seconds) {
    if(signal(SIGALRM, sig_alrm) == SIG_ERR) {
        return (seconds);
    }
    alarm(seconds);
    pause();
    return (alarm(0));
}

1)在调用sleep1函数之前可能已经设置了alarm函数,那么再一次执行这个函数可能会将之前设置的参数给覆盖

2)程序中修改了对SIGALRM的配置,如果编写了一个函数供其他函数调用,则在该函数被调用时要先保存原先

   配置,在该函数返回前再恢复原配置。更正这一点的方法是:保存signal函数的返回值,在返回前重置原配置

3)在调用alarm()和pause()之间有一个竞争条件,假设调用alarm(1),即设置1秒的闹钟,然后系统中断,但是

   超过了一秒,这样会调用sig_alrm()函数,然后又继续执行后面的pause(),如果之后再不发生信号,那么

   程序将会永远暂停

 

 

sigpending用法

if(sigpending(&pendmask) < 0) {
    err_sys("sigpending error");
}

 

 

sigaction 的sa_flags参数

参数 说明
SA_INTERRUPT 由此信号中断的系统调用不自动重启动
SA_NOCLDSTOP

若signo是SIGCHLD,当子进程停止(作业控制),不产生此信号。若已设置此标志,则

当停止的进程继续运行时,作为XSI扩展,不产生SIGCHLD信号

SA_NOCLDWAIT

若signo是SIGCHLD,则当调用进程的子进程终止时,不创建僵死进程。若调用进程

随后调用wait,则阻塞到它所有子进程都终止,此时返回-1.errno设置为ECHILD

SA_NODEFER

当捕捉到此信号时,在执行其信号捕捉函数时,系统部自动阻塞此信号(除非sa_mask

包含了此信号)。注意此种类型的操作对应于早期的不可靠信号

SA_NOSTACK 若用sigaltstack已声明了一个替换栈,则此信号递送给替换栈上的进程
SA_RESETHAND

在此信号捕捉函数的入口处,将此信号的处理方式重置为SIG_DFL,并清除

SIG_SIGINFO标志。注意此种类型的信号对应于早期不可靠信号,但是不能自动重置

SIGILL和SIGTRAP这两个信号的配置。设置此标志使sigaction的行为如同设置了

SA_NODEFER标志

SA_RESTART

由此信号中断的系统调用自动重启动

SA_SIGINFO

此选项对信号处理程序提供了附加信息:一个指向siginfo结构的指针以及一个执行

进程上下文标识符的指针

 

 

 

 

 

 

sigsuspend,一段有问题的代码,对一个信号解除阻塞,然后pause等待以前被阻塞的信号发生

sigset_t    newmask, oldmask;
sigemptyset(&netmask);
sigaddset(&newmask, SIGINT);

//lbock SIGINT 
if(sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0) {
    err_sys("SIG_BLOCK error");
}

//critical region of code
if(sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0) {
    err_sys("SIG_SETMASK error");
}
//windows is open
pause();

如果在解除阻塞和pause()之间发生了信号,此时窗口中发生的信号会丢失,这样使得进程永远阻塞

应当使用 sigsuspend()函数,它是原子的执行上面两部(解除阻塞并等待)

 

 

函数system

POSIX.1要求system忽略SIGINT和SIGQUIT,因为键入的中断字符可以发送给前台进程组中的所有进程

而父进程(也就是shell)不需要捕获这个信号。

比如 a.out --> /bin/sh --> /bin/ed

这里a.out和/bin/ed需要捕获信号,所以应该讲/bin/sh的信号屏蔽

 

 

作业控制信号

POSIX.1认为以下六个与作业控制有关

参数 说明
SIGCHLD 子进程已停止或终止
SIGCONT 如果进程已停止,则使其继续运行
SIGSTOP 停止信号(不能被捕捉或忽略)
SIGTSTP 交互式停止信号
SIGTTIN 后台进程组成员读控制终端
SIGTTOU 后台进程组成员写控制终端

 

 

 

参考

Linux 信号signal处理机制

Linux信号详解

你可能感兴趣的:(unix)