相关函数列表
//系统信号机制最简单的接口是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 | 后台进程组成员写控制终端 |
参考