信号是软中断,提供了一种处理异步事件的方法
1、信号概念
- 以SIG开头的,如夭折信号SIGABRT,闹钟信号SIGALRM等,linux 3.2.0支持31种,Solaris 10支持40种,POSIX实时扩展支持用户自定义的信号
- 很多条件可以产生信号
- 终端按键:如CTRL+C:SIGINT
- 硬件异常:除数为0、无效内存引用:SIGSEGV
- kill()函数和kill命令
- 某些软件发生,如带外数据SIGURG,SIGPIPE(管道读进程终止后一个进程写此管道)等
- 三种信号处理方式
- 1.忽略:除了SIGKILL和SIGSTOP(向内核和超级用户提供了使进程终止的可靠方法,捕获也不行),另外,忽略某些硬件异常产生的信号程序的行为是未定义的
- 2.捕捉:
- 3.执行默认动作:page261列出了信号的默认动作,linux下的core文件通过
/proc/sys/kernel/core_pattern进行配置
- 下列条件不产生core文件
- a.进程时设置用户ID的,且当前用户并非程序文件的所有者
- b.进程是设置组ID的,且当前用户并非程序文件的所有者
- c.用户没有写当前工作目录的权限
- d.文件已存在,且无权限
- e.文件太大(RLIMIT_CORE限制)
- 信号简介:
- SIGABRT:abort()产生
- SIGALRM:alarm设置定时器超时时
- SIGBUS:指示一个实现定义的硬件故障,如内存故障
- SIGCANCEL:solaris线程库内部信号
- SIGCHLD:进程结束或终止时发送给它的父进程
- SIGCONT:作业控制信号,发送给需要继续运行,处于停止状态的进程
- SIGEMT:指示一个定义的硬件故障(有些OS不支持)
- SIGFPE:算术运算异常,如除0、浮点溢出
- SIGFREEZE:solaris定义,通知进程在冻结系统状态前需要采取特定的动作
- SIGHUP:终端接口检测到一个连接断开,将此信号发送给终端相关的控制进程(会话首进程),通常用这个通知守护进程更新配置文件(因为正常不会有这个信号)
- SIGILL:执行了一条非法硬件指令
- SIGINFO:BSD信号,CTRL+T,发送给前台的每一个进程。
- SIGINT:中断按键(delete或Ctrl+C),发送至前台进程组的每一个进程
- SIGIO:linux下雨SIGPOLL具有相同的值,默认忽略
- SIGIOT:实现一个定义的硬件故障
- SIGJVM1:java虚拟机预留的一个信号
- SIGJVM2:java虚拟机预留的一个信号
- SIGKILL:不能捕捉的信号之一
- SIGLOST:
- SIGLWP:
- SIGPIPE:管道的读进程终止时写管道
- SIGPOLL:可轮询设备上发生一个特定事件
- SIGPROF:setitimer()函数设置的梗概统计间隔定时器已经超时时产生此信号
- SIGPWR:如电源失效,则UPS起作用,软件接到通知
- SIGQUIT:用户终端按退出键(CTRL+\),发送给前台进程组中的所有进程,默认终止且产生core文件
- SIGSEGV:进程进行了无效内存引用
- SIGSTKFLT:企图用于数学协助处理器的栈故障
- SIGSTOP:这是一个作业控制信号。停止一个进程,不能捕捉或忽略
- SIGSYS:指示一个无效的系统调用,进程执行了一条机器指令,内核认为是系统调用,但该指令指示系统调用类型的参数是无效的
- SIGTERM:kill命令发送给系统默认终止信号
- SIGTHAW:被挂起的系统恢复时通知相关进程
- SIGTHW:
- SIGTRAP:指示一个实现的硬件故障
- SIGTSTP:交互停止信号,挂起键(Ctrl+Z)产生
- SIGTTIN:后台进程组进程试图读其控制终端时,终端驱动程序产生此信号,下列情况不产生
- 读进程忽略或阻塞此信号
- 读进程所属的进程组是孤儿进程组
- SIGTTOU:后台进程组进程试图写其控制终端时…
- SIGURG:通知进程发生一个紧急情况,如网络上接到带外数据
- SIGUSR1:用户定义
- SIGUSR2:同上
- SIGVTALRM:setitimer()函数设置的虚拟间隔时间已经超时时
- SIGWAITING:
- SIGWINCH:内核维持与每个终端或伪终端相关联窗口的大小
- SIGXCPU:资源限制(如进程超过了CPU的限制)
- SIGXFSZ:进程超过了其软文件长度限制
- SIGXRES:通知进程超过了预配置的资源值
2、signal()函数
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
#define SIG_ERR (void (*)())-1
#define SIG_DFL (void (*)())0
#define SIG_IGN (void (*)())1
- func的值通常是常亮SIG_IGN、SIG_DFL或回调函数地址
- 返回值是之前的信号处理程序的指针
2.1程序启动
- 所有信号的默认状态都市系统默认或忽略,除非调用exec的进程忽略该信号(exec函数将原先设置为要捕捉的信号都更改为默认动作),其他信号不变(一个进程原先要捕捉的信号,执行新程序后就不能再捕捉了)
2.2 进程创建
- 子进程继承父进程的信号处理方式(因为信号捕捉函数的地址在子进程中是有意义的)
3、不可靠信号
不可靠指的是信号可能会丢失,早期unix版本不具备阻塞信号的能力,且只捕获一次
4、中断的系统调用
- 可将系统调用分为两类
- 低速系统调用:可能会使进程永远阻塞
- 其他系统调用
- 读或写某些低速设备可能会因为信号打断而使调用失败,POSIX.1 要求中断信号的SA_RESTART标志有效时才重启系统调用,在linux中,信号处理程序用signal函数时,被中断的系统调用会重启动
5、可重入函数
在信号处理程序中保证调用安全的函数,这些函数就是可重入的,并被称为异步信号安全的。
- 没有在page272中列出的函数大都是不可重入的,因为:
- 它们使用静态数据结构
- 它们调用malloc或free
- 它们是标准IO函数(很多以不可重入方式使用全局数据结构)
- page274的例子说明了再信号处理函数中调用不可重入函数其结果是不可预期的
6、SIGCLD语义
注意不是SIGCHLD,BSD上的信号,在linux3.2.0和solaris 10上,语义与SIGCHLD相同
7、可靠信号术语语义
- 信号产生: 造成信号发生时,为进程产生一个信号,事件可以是硬/软件异常,终端信号或kill函数,产生信号时,内核在进程表中以某种形式设置一个标志
- 信号递送: 对信号采取了上述动作时,叫向进程递送了一个信号
- 未决:在上述两个状态之间的时间间隔内
- 阻塞信号递送:如果产生了阻塞的信号,且对信号的动作是系统默认或捕捉,则为进程将此信号保持为未决状态直到解除阻塞或忽略。信号在递送给进程时才决定对它的处理方式(所以在这之前可改变对它的处理方式)
- 排队:支持POSIX.1实时扩展的才排队,否则只发送一次
- 顺序:多个信号递送给一个进程时,POSIX.1没有规定顺序,但建议在其他信号之前递送与进程当前状态有关的信号
- 信号屏蔽字:规定了当前要阻塞递送到该进程的信号集,sigset_t的位长度足以容纳一个信号集
8、kill()和raise()
int kill(pid_t pid, int sig);
int raise(int sig);//ISO没有多进程,所以只有一个参数
- kill()将信号发送给进程或进程组
- raise()函数允许进程向自身发送信号,等价于kill(getpid(),signo)
- pid
- > 0:发送给ID为pid的进程
- ==0:发送给属于同一进程组的所有进程(不包括实现定义的系统进程集)
- <0:发送给组ID绝对值等于pid的所有进程(不包括实现定义的系统进程集)
- == -1:发送给有权限发送的所有进程
- 信号编号为为0常来检查进程是否存在
9、alarm()和pause()
unsigned int alarm(unsigned int seconds);
int pause(void);
- alarm()产生SIGALRM信号,默认终止进程,由于未决时间,所以seconds决定的时间不准确
- 返回值为之前设置的闹钟的剩余时间,seconds为0意为取消闹钟
- pause()使进程挂起直到捕捉到一个信号(执行了信号处理程序并从其返回,pause才返回)
10、信号集
#include <signal.h>
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 sigismember(const sigset_t *set, int signum);
11、sigprocmask
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
- 更改或检测信号屏蔽集
- oldset:非空则返回当前的信号屏蔽集
- how:
- SIG_BLOCK:或操作
- SIG_UNBLOCK:set与当前的信号屏蔽字取交集
- SIG_SETMASK:赋值操作
- set:根据how的值对当前信号集进行更改,为空时不改变信号屏蔽字(可用来查询)
- 在调用sigprocmask()后如有任何未决的,不再阻塞的信号,返回前至少递送一个给进程
注意:
该函数为单线程定义,多线程中用另一个
12、sigpending
int sigpending(sigset_t *set);
- 返回一个信号集(当前未决的:阻塞不能递送的)
- sigprocmask()返回前虽然发送了多个信号但是只被捕获一次
13、sigaction
int sigaction(int signum, const struct sigaction *act,
struct sigaction *oldact);
struct sigaction {
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer)(void);
};
- 如果sa_handler包含一个捕捉函数的地址(非常量),则sa_mask字段说明了一个信号集,在调用之前,信号应该加到信号屏蔽字中。
- 一旦对给定的信号设置了一个动作,那么在调用sigaction显示改变之前,该设置就一直有效
- sa_flags:
- SA_NOCLDSTOP:此信号中断的系统调用不自动重启,如果是SIGCHLD,停止时不产生,终止时产生
- SA_NOCLDWAIT:如果是SIGCHLD,子进程终止时不产生僵死进程,若调用wait(),阻塞到所有子进程终止
- SA_NODEFER:执行捕捉函数时,不自动阻塞信号(除非sa_mask包括了此信号)
- SA_ONSTACK:传递给替换栈上的进程
- SA_RESETHAND:将信号的处理方式设置为SIG_DEF,并清除SA_SIGINFO标志(除了SIGILL和SIGTRAP)
- SA_RESTART:中断的系统调用自动重启
- SA_SIGINFO:对信号处理程序提供附加信息:一个指向siginfo结构的指针及一个指向进程上下文标识符的指针
- 即这时这样调用处理程序:void handler(int signo, siginfo_t *info, void *context);
- 而非 void handler(int signo);
- siginfo如下:
siginfo_t { int si_signo; /* Signal number */ int si_errno; /* An errno value */ int si_code; /* Signal code */ int si_trapno; /* Trap number that caused hardware-generated signal (unused on most architectures) */ pid_t si_pid; /* Sending process ID */ uid_t si_uid; /* Real user ID of sending process */ int si_status; /* Exit value or signal */ clock_t si_utime; /* User time consumed */ clock_t si_stime; /* System time consumed */ sigval_t si_value; /* Signal value */ int si_int; /* POSIX.1b signal */ void *si_ptr; /* POSIX.1b signal */ int si_overrun; /* Timer overrun count; POSIX.1b timers */ int si_timerid; /* Timer ID; POSIX.1b timers */ void *si_addr; /* Memory location which caused fault */ long si_band; /* Band event (was int in glibc 2.3.2 and earlier) */ int si_fd; /* File descriptor */ short si_addr_lsb; /* Least significant bit of address (since kernel 2.6.32) */ }
14、sigsetjmp和siglongjmp
在信号处理程序中使用longjmp跳出程序在linux环境并不会保存和恢复信号屏蔽字等操作,所以最好用下面的函数
int sigsetjmp(sigjmp_buf env, int savesigs);
void siglongjmp(sigjmp_buf env, int val);
15、sigsuspend(…)
- 原子的方式实现屏蔽恢复信号屏蔽字再使进程休眠
int sigsuspend(const sigset_t *mask);
16、abort()
#include <stdlib.h>
void abort(void);
- 将SIGABRT信号发送给调用进程,IOS规定使用raise(SIGABRT),ISO C要求若捕捉到此信号而且相应的信号处理程序返回,abort()仍不会返回到调用者。如果捕捉到了SIGABRT,那么只能调用exit、_exit、_Exit、longjmp等函数了,POSIX也说了不理会进程对此信号的忽略或阻塞
17、system()
POSIX要求忽略SIGINT和SIGQUIT
…
18、sleep()、nanosleep()、clock_nanosleep()
- sleep使进程挂起直到:
- 经过了second指定的时间
- 捕捉到一个信号并从信号处理函数返回