Unix 系统信号集与编程

Signal Description
SIGABRT 由调用abort函数产生,进程非正常退出
SIGALRM 用alarm函数设置的timer超时或setitimer函数设置的interval timer超时
SIGBUS 某种特定的硬件异常,通常由内存访问引起
SIGCANCEL 由Solaris Thread Library内部使用,通常不会使用
SIGCHLD 进程Terminate或Stop的时候,SIGCHLD会发送给它的父进程。缺省情况下该Signal会被忽略
SIGCONT 当被stop的进程恢复运行的时候,自动发送
SIGEMT 和实现相关的硬件异常
SIGFPE 数学相关的异常,如被0除,浮点溢出,等等
SIGFREEZE Solaris专用,Hiberate或者Suspended时候发送
SIGHUP 发送给具有Terminal的Controlling Process,当terminal被disconnect时候发送
SIGILL 非法指令异常
SIGINFO BSD signal。由Status Key产生,通常是CTRL+T。发送给所有Foreground Group的进程
SIGINT 由Interrupt Key产生,通常是CTRL+C或者DELETE。发送给所有ForeGround Group的进程
SIGIO 异步IO事件
SIGIOT 实现相关的硬件异常,一般对应SIGABRT
SIGKILL 无法处理和忽略。中止某个进程
SIGLWP 由Solaris Thread Libray内部使用
SIGPIPE 在reader中止之后写Pipe的时候发送
SIGPOLL 当某个事件发送给Pollable Device的时候发送
SIGPROF Setitimer指定的Profiling Interval Timer所产生
SIGPWR 和系统相关。和UPS相关。
SIGQUIT 输入Quit Key的时候(CTRL+\)发送给所有Foreground Group的进程
SIGSEGV 非法内存访问
SIGSTKFLT Linux专用,数学协处理器的栈异常
SIGSTOP 中止进程。无法处理和忽略。
SIGSYS 非法系统调用
SIGTERM 请求中止进程,kill命令缺省发送
SIGTHAW Solaris专用,从Suspend恢复时候发送
SIGTRAP 实现相关的硬件异常。一般是调试异常
SIGTSTP Suspend Key,一般是Ctrl+Z。发送给所有Foreground Group的进程
SIGTTIN 当Background Group的进程尝试读取Terminal的时候发送
SIGTTOU 当Background Group的进程尝试写Terminal的时候发送
SIGURG 当out-of-band data接收的时候可能发送
SIGUSR1 用户自定义signal 1
SIGUSR2 用户自定义signal 2
SIGVTALRM setitimer函数设置的Virtual Interval Timer超时的时候
SIGWAITING Solaris Thread Library内部实现专用
SIGWINCH 当Terminal的窗口大小改变的时候,发送给Foreground Group的所有进程
SIGXCPU 当CPU时间限制超时的时候
SIGXFSZ 进程超过文件大小限制
SIGXRES Solaris专用,进程超过资源限制的时候发送



Linux支持的信号列表如下。很多信号是与机器的体系结构相关的。
首先列出的是POSIX.1中列出的信号:
信号 值 处理动作 发出信号的原因
----------------------------------------------------------------------
SIGHUP  1 A 终端挂起或者控制进程终止
SIGINT  2 A 键盘中断(如ctrl+c被按下)
SIGQUIT 3   C 键盘的退出键被按下(ctrl+\)
SIGILL  4 C 非法指令
SIGABRT 6   C 由abort(3)发出的退出指令
SIGFPE  8   C 浮点异常
SIGKILL 9   AEF Kill信号
SIGSEGV 11   C 无效的内存引用
SIGPIPE 13   A 管道破裂: 写一个没有读端口的管道
SIGALRM 14    A 由alarm(2)发出的信号
SIGTERM 15    A 终止信号
SIGUSR1 30,10,16  A 用户自定义信号1
SIGUSR2 31,12,17  A 用户自定义信号2
SIGCHLD 20,17,18  B 子进程结束信号
SIGCONT 19,18,25 进程继续(曾被停止的进程)
SIGSTOP 17,19,23  DEF 终止进程
SIGTSTP 18,20,24  D 控制终端(tty)上按下停止键
SIGTTIN 21,21,26  D 后台进程企图从控制终端读
SIGTTOU 22,22,27  D 后台进程企图从控制终端写

=====================================================================================================
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就将进程停止在那里。

======================================================================================================
http://bbs.loveunix.net/viewthread.php?tid=35190

信号是进程之间互传消息的一种方法,俗称软件中断。它提供了一种处理异步事件的方法。

在SCO openserver 5.05上 kill -l得到
CODE
        HUP SYS STOP
        INT PIPE TSTP
        QUIT ALRM CONT
        ILL TERM TTIN
        TRAP USR1 TTOU
        ABRT USR2 VTALRM
        EMT CHLD PROF
        FPE PWR XCPU
        KILL WINCH XFSZ
        BUS URG
        SEGV POLL   

在Aix 4.3上kill -l 得到
CODE
      1) HUP       14) ALRM      27) MSG       40) bad trap  53) bad trap
      2) INT       15) TERM      28) WINCH     41) bad trap  54) bad trap
      3) QUIT      16) URG       29) PWR       42) bad trap  55) bad trap
      4) ILL       17) STOP      30) USR1      43) bad trap  56) bad trap
      5) TRAP      18) TSTP      31) USR2      44) bad trap  57) bad trap
      6) ABRT      19) CONT      32) PROF      45) bad trap  58) bad trap
      7) EMT       20) CHLD      33) DANGER    46) bad trap  59) CPUFAIL
      8) FPE       21) TTIN      34) VTALRM    47) bad trap  60) GRANT
      9) KILL      22) TTOU      35) MIGRATE   48) bad trap  61) RETRACT
     10) BUS       23) IO        36) PRE       49) bad trap  62) SOUND
     11) SEGV      24) XCPU      37) bad trap  50) bad trap  63) SAK
     12) SYS       25) XFSZ      38) bad trap  51) bad trap
     13) PIPE      26) bad trap  39) bad trap  52) bad trap

在Redhat 13 上kill -l 得到
CODE
 1) SIGHUP         2) SIGINT             3) SIGQUIT        4) SIGILL            5) SIGTRAP
 6) SIGABRT         7) SIGBUS        8) SIGFPE            9) SIGKILL        10) SIGUSR1
11) SIGSEGV        12) SIGUSR2        13) SIGPIPE        14) SIGALRM        15) SIGTERM
16) SIGSTKFLT        17) SIGCHLD        18) SIGCONT        19) SIGSTOP        20) SIGTSTP
21) SIGTTIN        22) SIGTTOU        23) SIGURG        24) SIGXCPU        25) SIGXFSZ
26) SIGVTALRM    27) SIGPROF        28) SIGWINCH        29) SIGIO            30) SIGPWR
31) SIGSYS        34) SIGRTMIN        35) SIGRTMIN+1    36) SIGRTMIN+2    37) SIGRTMIN+3
38) SIGRTMIN+4    39) SIGRTMIN+5    40) SIGRTMIN+6    41) SIGRTMIN+7    42) SIGRTMIN+8
43) SIGRTMIN+9    44) SIGRTMIN+10    45) SIGRTMIN+11    46) SIGRTMIN+12    47) SIGRTMIN+13
48) SIGRTMIN+14    49) SIGRTMIN+15    50) SIGRTMAX-14    51) SIGRTMAX-13    52) SIGRTMAX-12
53) SIGRTMAX-11    54) SIGRTMAX-10    55) SIGRTMAX-9    56) SIGRTMAX-8    57) SIGRTMAX-7
58) SIGRTMAX-6    59) SIGRTMAX-5    60) SIGRTMAX-4    61) SIGRTMAX-3    62) SIGRTMAX-2
63) SIGRTMAX-1    64) SIGRTMAX    

    信号出现在UNIX 的早期版本中,但早期的信号模型是不可靠的, 信号可能被丢失,也很难处理关键段.UNIX 的两个重要分支BSD 和System V 分别对早期的信号进行了扩展,但这两个系统的扩展并不兼容,POSIX 统一了这两种实现, 最终提供了可靠的信号模型。

信号的产生条件
    **用户按某些终端键时,产生信号
    **硬件异常:除0、无效存储访问etc.
    **kill函数,发送信号给另一个进程或进程组
    **kill命令,发送信号给其他进程
    **某种软件条件已经发生,发送信号通知有关进程

接到信号的处理办法
1    忽略,但是SIGKILL和SIGSTOP不能忽略。
2    捕捉
3    执行系统的默认处理

信号的分类

1.    非可靠信号:早期unix下的不可靠信号主要指的是进程可能对信号做出错误的反应以及信号可能丢失。
2.    可靠信号: 信号值位于SIGRTMIN和SIGRTMAX之间的信号都是可靠信号,可靠信号克服了信号可能丢失的问题。
注:    信号的可靠与不可靠只与信号值有关,与信号的发送及安装函数无关。当然也可以称为实时信号或者非实时信号,非实时信号都不支持排队,都是不可靠信号;实时信号都支持排队,都是可靠信号。

信号的生命周期
    从信号发送到信号处理函数的执行完毕。对于一个完整的信号生命周期(从信号发送到相应的处理函数执行完毕)来说,可以分为三个重要的阶段,这三个阶段由四个重要事件来刻画:信号产生;信号在进程中注册完毕;信号在进程中的注销完毕;信号处理函数执行完毕。相邻两个事件的时间间隔构成信号生命周期的一个阶段。
当一个实时信号发送给一个进程时,不管该信号是否已经在进程中注册,都会被再注册一次,因此,信号不会丢失,因此,实时信号又叫做"可靠信号"。这意味着同一个实时信号可以在同一个进程的未决信号信息链中占有多个sigqueue结构(进程每收到一个实时信号,都会为它分配一个结构来登记该信号信息,并把该结构添加在未决信号链尾,即所有诞生的实时信号都会在目标进程中注册);当一个非实时信号发送给一个进程时,如果该信号已经在进程中注册,则该信号将被丢弃,造成信号丢失。因此,非实时信号又叫做"不可靠信号"。这意味着同一个非实时信号在进程的未决信号信息链中,至多占有一个sigqueue结构(一个非实时信号产生后,1如果发现相同的信号已经在目标结构中注册,则不再注册,对于进程来说,相当于不知道本次信号发生,信号丢失;2如果进程的未决信号中没有相同信号,则在进程中注册自己)。

需要注意的要点是:
1)信号注册与否,与发送信号的函数(如kill()或 sigqueue()等)以及信号安装函数(signal()及sigaction())无关,只与信号值有关(信号值小于 SIGRTMIN的信号最多只注册一次,信号值在SIGRTMIN及SIGRTMAX之间的信号,只要被进程接收到就被注册)。
2)在信号被注销到相应的信号处理函数执行完毕这段时间内,如果进程又收到同一信号多次,则对实时信号来说,每一次都会在进程中注册;而对于非实时信号来说,无论收到多少次信号,都会视为只收到一个信号,只在进程中注册一次。当然还有有些需要知道的概念比如 低速系统调用,中断系统调用,可重入函数等等概念,请查阅环境高级编程。

一些概念性的东西大概就这些东西吧,下面就分类介绍一下具体的函数。
我下面介绍的,如果没有特殊说明,都是对于linux上的实现,当然都是符合POSIX标准喽。

二,函数详细介绍

A: 信号的发送
发送信号的主要函数有:kill(),raise(),sigqueue(),alarm(),setitimer()以及 abort()。

1, int kill(pid_t pid,int signo)

用到的头文件:
#include <sys/types.h>
#include <signal.h>

参数pid的值 信号的接收进程
pid>0 进程ID为pid的进程
pid=0 同一个进程组的进程
pid<0 pid!=-1 进程组ID为 -pid的所有进程
pid=-1 除发送进程自身外,所有进程ID大于1的进程

Sinno 是信号值,当为0时(即空信号),实际不发送任何信号,但照常进行错误检查,因此,可用于检查目标进程是否存在,以及当前进程是否具有向目标发送信号的权限(root权限的进程可以向任何进程发送信号,非root权限的进程只能向属于同一个session或者同一个用户的进程发送信号)。kill最常用于pid>0时的信号发送,调用成功返回 0; 否则,返回 -1。注:对于pid<0时的情况,对于哪些进程将接受信号,各种版本说法不一,其实很简单,参阅内核源码 kernal/signal.c。

2, int raise(int signo)
用到的头文件:
#include <signal.h>

向进程本身发送信号,参数为即将发送的信号值。调用成功返回 0;否则,返回 -1。

3, int sigqueue(pid_t pid, int sig, const union sigval val)

用到的头文件:
#include <sys/types.h>
#include <signal.h>

调用成功返回 0;否则,返回 -1。

sigqueue()是比较新的发送信号系统调用,主要是针对实时信号提出的(当然也支持前32种),支持信号带有参数,

与函数sigaction()配合使用。sco 5.05没有此函数。

sigqueue的第一个参数是指定接收信号的进程ID,第二个参数确定即将发送的信号,第三个参数是一个联合数据结构union sigval,

指定了信号传递的参数,即通常所说的4字节值。

typedef union sigval
{
int sival_int;
void *sival_ptr;
}sigval_t;


sigqueue() 比kill()传递了更多的附加信息,但sigqueue()只能向一个进程发送信号,而不能发送信号给一个进程组。如果sig为0,

将会执行错误检查,但实际上不发送任何信号,0值信号可用于检查pid的有效性以及当前进程是否有权限向目标进程发送信号。

在调用 sigqueue时,sigval_t指定的信息会拷贝到3参数信号处理函数(3参数信号处理函数指的是信号处理函数由sigaction安装,

并设定了sa_sigaction指针,稍后将阐述)的siginfo_t结构中,这样信号处理函数就可以处理这些信息了。由于sigqueue系统调用

支持发送带参数信号,所以比kill()系统调用的功能要灵活和强大得多。

注:sigqueue()发送非实时信号时,第三个参数包含的信息仍然能够传递给信号处理函数; sigqueue()发送非实时信号时,仍然不支持排队,

即在信号处理函数执行过程中到来的所有相同信号,都被合并为一个信号。


4, unsigned int alarm(unsigned int seconds)

用到的头文件:

#include <unistd.h>

专门为 SIGALRM信号而设,在指定的时间seconds秒后,将向进程本身发送SIGALRM信号,又称为闹钟时间。进程调用alarm后,

任何以前的alarm()调用都将无效。如果参数seconds为零,那么进程内将不再包含任何闹钟时间。

函数返回是这样的,如果调用 alarm()前,进程中已经设置了闹钟时间,则返回上一个闹钟时间的剩余时间,否则返回0。

5, int setitimer(int which, const struct itimerval *value, struct itimerval *ovalue));

用到的头文件:
#include <sys/time.h>

setitimer() 比alarm功能强大,支持3种类型的定时器:

ITIMER_REAL: 设定绝对时间;经过指定的时间后,内核将发送SIGALRM信号给本进程;
ITIMER_VIRTUAL 设定程序执行时间;经过指定的时间后,内核将发送SIGVTALRM信号给本进程;
ITIMER_PROF 设定进程执行以及内核因本进程而消耗的时间和,经过指定的时间后,内核将发送ITIMER_VIRTUAL信号给本进程;

Setitimer() 第一个参数which指定定时器类型(上面三种之一);第二个参数是结构itimerval的一个实例,结构itimerval。

结构 itimerval:

struct itimerval
{
     struct timeval it_interval; /* next value */
     struct timeval it_value; /* current value */
};

struct timeval
{
      long tv_sec; /* seconds */
      long tv_usec; /* microseconds */
};




第三个参数可不做处理。

Setitimer() 调用成功返回0,否则返回-1。


这是我man setitimer时看到的

getitimer and setitimer are not part of any currently supported standard;
they were developed at the University of California at Berkeley and are
used by permission.


6, void abort(void);

用到的头文件:
#include <stdlib.h>


向进程发送SIGABORT信号,默认情况下进程会异常退出,当然可定义自己的信号处理函数。

即使SIGABORT被进程设置为阻塞信号,调用abort()后,SIGABORT仍然能被进程接收。该函数无返回值。




B:信号的捕获与安装(设置信号关联动作)



如果进程要处理某一信号,那么就要在进程中安装该信号。安装信号主要用来确定信号值及进程针对该信号值

的动作之间的映射关系,

即进程将要处理哪个信号;该信号被传递给进程时,将执行何种操作。

linux主要有两个函数实现信号的安装:signal()、 sigaction()。其中signal()在可靠信号系统调用的基础上实现,

是库函数。它只有两个参数,不支持信号传递信息,主要是用于前32种非实时信号的安装;而sigaction()是较新的

函数(由两个系统调用实现:sys_signal以及 sys_rt_sigaction),有三个参数,支持信号传递信息,主要用来与

sigqueue() 系统调用配合使用,当然,sigaction()同样支持非实时信号的安装。sigaction()优于signal()主要体现

在支持信号带有参数。

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

#include <signal.h>

如果该函数原型不容易理解的话,可以参考下面的分解方式来理解:

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

第一个参数指定信号的值,第二个参数指定针对前面信号值的处理,

a,可以忽略该信号(参数设为SIG_IGN);

b,可以采用系统默认方式处理信号(参数设为 SIG_DFL);

b,也可以自己实现处理方式(参数指定一个函数地址)。

如果signal()调用成功,返回最后一次为安装信号signum而调用signal()时的handler值;失败则返回SIG_ERR。

2, int sigaction(int signum,const struct sigaction *act,struct sigaction *oldact));

#include <signal.h>


sigaction函数用于改变进程接收到特定信号后的行为。该函数的第一个参数为信号的值,可以为除SIGKILL及SIGSTOP外的任何

一个特定有效的信号(为这两个信号定义自己的处理函数,将导致信号安装错误)。第二个参数是指向结构sigaction的一个实例

的指针,在结构sigaction的实例中,指定了对特定信号的处理,可以为空,进程会以缺省方式对信号处理;第三个参数oldact指向

的对象用来保存原来对相应信号的处理,可指定oldact为NULL。如果把第二、第三个参数都设为NULL,那么该函数可用于检查信号的有效性。


第二个参数最为重要,其中包含了对指定信号的处理、信号所传递的信息、信号处理函数执行过程中应屏蔽掉哪些函数等等。

sigaction结构定义如下:

struct sigaction
{

union
{
__sighandler_t _sa_handler;
void (*_sa_sigaction)(int,struct siginfo *, void *);
}_u
sigset_t sa_mask;
unsigned long sa_flags;
void (*sa_restorer)(void);
}

其中,sa_restorer,已过时,POSIX不支持它,不应再被使用。

a,联合数据结构中的两个元素_sa_handler以及*_sa_sigaction指定信号关联函数,即用户指定的信号处理函数。除了可以是用户自

定义的处理函数外,还可以为SIG_DFL(采用缺省的处理方式),也可以为 SIG_IGN(忽略信号)。

b,由_sa_handler指定的处理函数只有一个参数,即信号值,所以信号不能传递除信号值之外的任何信息;由_sa_sigaction是指定的

信号处理函数带有三个参数,是为实时信号而设的(当然同样支持非实时信号),它指定一个3参数信号处理函数。

第一个参数为信号值,第三个参数没有使用(POSIX没有规范使用该参数的标准),第二个参数是指向siginfo_t 结构的指针,结构中包含

信号携带的数据值,参数所指向的结构如下:


siginfo_t {
int si_signo; /* 信号值,对所有信号有意义*/
int si_errno; /* errno值,对所有信号有意义*/
int si_code; /* 信号产生的原因,对所有信号有意义*/
union{ /* 联合数据结构,不同成员适应不同信号 */
//确保分配足够大的存储空间
int _pad[SI_PAD_SIZE];
//对SIGKILL有意义的结构
struct{
...
}...

... ...
... ...
//对SIGILL, SIGFPE, SIGSEGV, SIGBUS有意义的结构
struct{
...
}...
... ...
}
}




注:为了更便于阅读,在说明问题时常把该结构表示为如下所表示的形式。

siginfo_t {
int si_signo; /* 信号值,对所有信号有意义*/
int si_errno; /* errno值,对所有信号有意义*/
int si_code; /* 信号产生的原因,对所有信号有意义*/
pid_t si_pid; /* 发送信号的进程ID,对kill(2),实时信号以及SIGCHLD有意义 */
uid_t si_uid; /* 发送信号进程的真实用户ID,对kill(2),实时信号以及SIGCHLD有意义 */
int si_status; /* 退出状态,对SIGCHLD有意义*/
clock_t si_utime; /* 用户消耗的时间,对SIGCHLD有意义 */
clock_t si_stime; /* 内核消耗的时间,对SIGCHLD有意义 */
sigval_t si_value; /* 信号值,对所有实时有意义,是一个联合数据结构,可以为一个整数(由si_int标示,也可以为一个指针,由si_ptr标示)*/

void * si_addr; /* 触发fault的内存地址,对SIGILL,SIGFPE,SIGSEGV,SIGBUS 信号有意义*/
int si_band; /* 对SIGPOLL信号有意义 */
int si_fd; /* 对SIGPOLL信号有意义 */

}


实际上,除了前三个元素外,其他元素组织在一个联合结构中,在联合数据结构中,又根据不同的信号组织成不同的结构。

注释中提到的对某种信号有意义指的是,在该信号的处理函数中可以访问这些域来获得与信号相关的有意义的信息,只不过特定信号只

对特定信息感兴趣而已。

siginfo_t结构中的联合数据成员确保该结构适应所有的信号,比如对于实时信号来说,则实际采用下面的结构形式:

typedef struct {
int si_signo;
int si_errno;
int si_code;
union sigval si_value;
} siginfo_t;

结构的第四个域同样为一个联合数据结构:

union sigval {
int sival_int;
void *sival_ptr;
}

采用联合数据结构,说明siginfo_t结构中的si_value要么持有一个4字节的整数值,要么持有一个指针,这就构成了与信号相关的数据。

在信号的处理函数中,包含这样的信号相关数据指针,但没有规定具体如何对这些数据进行操作,操作方法应该由程序开发人员根据具体

任务事先约定。

前面在讨论系统调用sigqueue发送信号时,sigqueue的第三个参数就是sigval联合数据结构,当调用 sigqueue时,该数据结构中的数据就将

拷贝到信号处理函数的第二个参数中。这样,在发送信号同时,就可以让信号传递一些附加信息。信号可以传递信息对程序开发是非常有意义

的。


c,sa_mask指定在信号处理程序执行过程中,哪些信号应当被阻塞。缺省情况下当前信号本身被阻塞,防止信号的嵌套发送,除非指定

SA_NODEFER或者SA_NOMASK标志位。

注: 请注意sa_mask指定的信号阻塞的前提条件,是在由sigaction()安装信号的处理函数执行过程中由sa_mask指定的信号才被阻塞。

d,sa_flags 中包含了许多标志位,包括刚刚提到的SA_NODEFER及SA_NOMASK标志位。另一个比较重要的标志位是SA_SIGINFO,当设定了该标志

位时,表示信号附带的参数可以被传递到信号处理函数中,因此,应该为sigaction结构中的sa_sigaction指定处理函数,而不应该为 sa_handler

指定信号处理函数,否则,设置该标志变得毫无意义。即使为sa_sigaction指定了信号处理函数,如果不设置 SA_SIGINFO,信号处理函数同样

不能得到信号传递过来的数据,在信号处理函数中对这些信息的访问都将导致段错误(Segmentation fault)。

注:很多文献在阐述该标志位时都认为,如果设置了该标志位,就必须定义三参数信号处理函数。实际不是这样的,验证方法很简单:自己实现

一个单一参数信号处理函数,并在程序中设置该标志位,可以察看程序的运行结果。实际上,可以把该标志位看成信号是否传递参数的开关,如果设置该位,

则传递参数;否则,不传递参数。


C:信号集及信号集操作函数:

信号集被定义为一种数据类型:
typedef struct {
unsigned long sig[_NSIG_WORDS];
} sigset_t




信号集用来描述信号的集合,linux所支持的所有信号可以全部或部分的出现在信号集中,主要与信号阻塞相关函数配合使用。下面是为

信号集操作定义的相关函数:



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);

头文件
#include <signal.h>

sigemptyset(sigset_t *set)初始化由set指定的信号集,信号集里面的所有信号被清空;
sigfillset(sigset_t *set)调用该函数后,set指向的信号集中将包含linux支持的64种信号;
sigaddset(sigset_t *set, int signum)在set指向的信号集中加入signum信号;
sigdelset(sigset_t *set, int signum)在set指向的信号集中删除signum信号;
sigismember(const sigset_t *set, int signum)判定信号signum是否在set指向的信号集中。



D:信号阻塞与信号未决:

每个进程都有一个用来描述哪些信号递送到进程时将被阻塞的信号集,该信号集中的所有信号在递送到进程后都将被阻塞。下面是与信号阻塞

相关的几个函数:

int sigprocmask(int how, const sigset_t *set, sigset_t *oldset));
int sigpending(sigset_t *set));
int sigsuspend(const sigset_t *mask));

头文件:
#include <signal.h>

sigprocmask() 函数能够根据参数how来实现对信号集的操作,操作主要有三种:

参数how 进程当前信号集
SIG_BLOCK 在进程当前阻塞信号集中添加set指向信号集中的信号
SIG_UNBLOCK 如果进程阻塞信号集中包含set指向信号集中的信号,则解除对该信号的阻塞
SIG_SETMASK 更新进程阻塞信号集为set指向的信号集

sigpending(sigset_t *set))获得当前已递送到进程,却被阻塞的所有信号,在set指向的信号集中返回结果。

sigsuspend(const sigset_t *mask))用于在接收到某个信号之前, 临时用mask替换进程的信号掩码, 并暂停进程执行,直到收到信号为止。

sigsuspend 返回后将恢复调用之前的信号掩码。信号处理函数完成后,进程将继续执行。该系统调用始终返回-1,并将errno设置为EINTR。


三, 示例程序

实例一:信号发送及处理,看看函数sigaction是怎么使用的。
CODE

#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
void user_func(int,siginfo_t*,void*);

int main(int argc,char**argv)
{
struct sigaction act;
int sig;
sig=atoi(argv[1]);

sigemptyset(&act.sa_mask);
act.sa_flags=SA_SIGINFO;
act.sa_sigaction=(void * )user_func;

if(sigaction(sig,&act,NULL) < 0)
{
 printf("install sigal error\n");
}

while(1)
{
 sleep(2);
 printf("wait for the signal\n");
}
}
void user_func(int signum,siginfo_t *info,void *myact)
{
printf("receive signal %d\n\n\n", signum);
sleep(5);
}



在一终端执行cc -o act act.c
$./act 8&
[1] 992
$ wait for the signal
$

在另一终端执行

#kill -s 8 992


看看。。

$receive signal 8


实例二:信号阻塞及信号集操作

CODE

#include <signal.h>
#include <unistd.h>
void user_func(int);
main()
{
sigset_t new_mask,old_mask,pending_mask;
struct sigaction act;

sigemptyset(&act.sa_mask);
act.sa_flags=SA_SIGINFO;
act.sa_sigaction=(void*)user_func;

if(sigaction(SIGRTMIN+10,&act,NULL))
 printf("install signal SIGRTMIN+10 error\n");

sigemptyset(&new_mask);
sigaddset(&new_mask,SIGRTMIN+10);

if(sigprocmask(SIG_BLOCK, &new_mask,&old_mask))
 printf("block signal SIGRTMIN+10 error\n");

sleep(20);

printf("\n\nNow begin to get pending mask and unblock SIGRTMIN+10\n\n");
if(sigpending(&pending_mask)<0)
 printf("get pending mask error\n");
if(sigismember(&pending_mask,SIGRTMIN+10))
 printf("signal SIGRTMIN+10 is pending\n");

if(sigprocmask(SIG_SETMASK,&old_mask,NULL)<0)
 printf("unblock signal error\n");

printf("signal unblocked ,ok ... ...\n\n\n");
}
void user_func(int signum)
{
printf("receive signal %d \n",signum);
}



$cc -o sus sus.c
$./sus&
[1] 997



another console
#kill -s 42 pid

看看...

$
Now begin to get pending mask and unblock SIGRTMIN+10

signal SIGRTMIN+10 is pending
receive signal 42
signal unblocked ,ok ... ...



[1]+ Exit 31 ./d
$



实例三:signal例子

CODE

#include <signal.h>
#include <unistd.h>
#include <stdio.h>
#define damo
void user_func(int no)
{
       switch (no)
       {
               case 1:
                       printf("Get SIGHUP.\n");
                       break;
               case 2:
                       printf("Get SIGINT\n");
                       break;
               case 3:
                       printf("Get SIGQUIT \n");
                       break;
               default:
                       printf("What wan yi a \n\n");
                       break;
       }

}
int main()
{
       printf("Process id is %d\n ",getpid());

#ifdef damo
       signal(SIGHUP, user_func);
       signal(SIGINT, user_func);
       signal(SIGQUIT, user_func);
       signal(SIGBUS, user_func);
#else
       signal(SIGHUP, SIG_IGN);
       signal(SIGINT, SIG_IGN);
       signal(SIGQUIT, SIG_IGN);
       signal(SIGBUS, SIG_DFL);
#endif

       while(1)
              ;
}



这个就是signal的用法集中展示,也是我经常用的一个方法。说实话,sco太古老了。。俺只能用这个函数了。
测试时你可以把#define damo这个注释和不注释看看。深刻体会signal的用法。

BTW:signal(SIGINT, SIG_IGN);
signal(SIGQUIT, SIG_IGN);

等等忽略了这些信号后,可以让一个进程终端无关,即使你退出这个tty.当然kill信号还是不能屏蔽的。
这种方式多在守护进程中采用。

$ cc -o si si.c
$./si

Process id is 1501

在另一个tty上
#ps -ef
bank 1501 1465 51 04:07 pts/0 00:00:13 ./si
bank 1502 800 0 04:08 pts/1 00:00:00 ps -ef
#
注意观察这时候是1456
你把./si这个tty关掉。
这时候你再
#ps -ef看看
bank 1501 1 50 04:07 ? 00:00:59 ./si
bank 1503 800 0 04:09 pts/1 00:00:00 ps -ef
注意这个1和?,知道这个进程成啥了吧?成精拉。哈哈~~




实例四:pause函数
CODE

#include <unistd.h>
#include <stdio.h>
#include <signal.h>
void user_func()
{
       printf("\n\nCatch a signal SIGINT \n");
}

int main()
{
       printf ("pid = %d \n\n ",getpid());
       signal(SIGINT, user_func);
       pause();
       printf("receive a signal \n\n");
}



在这个例子中,程序开始执行,就象进入了死循环一样,这是因为进程正在等待信号,
当我们按下Ctrl-C时,信号被捕捉,并且使得pause退出等待状态。


实例五:下面是关于setitimer调用的一个简单例子。

CODE

#include <signal.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/time.h>

void user_func(int sig)
{
 if ( sig ==  SIGALRM)
   printf("Catch a signal  SIGALRM \n");
 else if ( sig == SIGVTALRM)
   printf("Catch a signal  SIGVTALRM\n");
}

int main()
{
       struct itimerval value,ovalue,value2;

       printf("Process id is  =  %d \n",getpid());

       signal(SIGALRM, user_func);
       signal(SIGVTALRM, user_func);

       value.it_value.tv_sec = 1;
       value.it_value.tv_usec = 0;
       value.it_interval.tv_sec = 1;
       value.it_interval.tv_usec = 0;

       setitimer(ITIMER_REAL, &value, &ovalue);

       value2.it_value.tv_sec = 1;
       value2.it_value.tv_usec = 500000;
       value2.it_interval.tv_sec = 1;
       value2.it_interval.tv_usec = 500000;

       setitimer(ITIMER_VIRTUAL, &value2, &ovalue);

       while(1);
}





在该例子中,每隔1秒发出一个SIGALRM,每隔1.5秒发出一个SIGVTALRM信号:

结果如下
$ ./ti
Process id is = 1734
Catch a signal SIGALRM
Catch a signal SIGVTALRM
Catch a signal SIGALRM
Catch a signal SIGALRM
Catch a signal SIGVTALRM
Catch a signal SIGALRM
Catch a signal SIGVTALRM
Catch a signal SIGALRM
Catch a signal SIGALRM
Catch a signal SIGVTALRM
Catch a signal SIGALRM
Catch a signal SIGVTALRM
Catch a signal SIGALRM
Catch a signal SIGALRM
Catch a signal SIGVTALRM


ctrl+c中断。
....

开始喜欢setitimer函数了。。。

四, 补充

不得不介绍一下setjmp和longjmp的作用。

在用信号的时候,我们看到多个地方要求进程在检查收到信号后,从原来的系统调用中直接返回,而不是等到该调用完成。
这种进程突然改变其上下文的情况,就是使用setjmp和longjmp的结果。 setjmp将保存的上下文存入用户区,并继续在旧的
上下文中执行。这就是说,进程执行一个系统调用,当因为资源或其他原因要去睡眠时,内核为进程作了一次setjmp,如果
在睡眠中被信号唤醒,进程不能再进入睡眠时,内核为进程调用longjmp,该操作是内核为进程将原先setjmp 调用保存在进程
用户区的上下文恢复成现在的上下文,这样就使得进程可以恢复等待资源前的状态,而且内核为setjmp返回1,使得进程知道
该次系统调用失败。这就是它们的作用。

有时间再man 一下waitpid吧。。都是很有用的。

你可能感兴趣的:(unix)