欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

019 UNIX再学习 -- 信号

程序员文章站 2022-07-13 11:59:11
...
                                          
                                              019 UNIX再学习 -- 信号                                      
                                                                                
                                           

终于讲到信号部分,很多比较重要的应用程序都需处理信号。第 9 章需要先了解信号机制再看,所以先跳过不讲。现在开始详解信号。

一、信号概念

信号是提供异步事件处理机制的软件中断

这些异步事件可能来自硬件设备,如用户同时按下 Ctrl+ C 键,也可能来自系统内核,如试图访问尚未映射的虚拟内存,又或者来自用户进程,如尝试计算整数除 0 的表达式。进程之间可以相互发送信号,这使信号称为一种进程间通信的基本手段

信号的异步特性不仅表现为它的产生是异步的,对它的处理同样也是异步的。程序的设计者不可能也不需要精确地预见什么时候触发什么信号,也同样无法预见该信号究竟在什么时候会被处理。一切都在内核的操控下,异步地运行。信号是软件层面对中断机制的一种模拟。


这里简单提一句什么是中断?

中断就是指暂时停止当前程序的执行转而去执行新的程序或者处理出现的意外情况。

中断分为:软件中断 / ctrl+c 和 硬件中断 / 电源拔出。

二、信号处理

信号有一个非常明确的生命周期。

首先,信号被生成,并发送至系统内核。

其次,系统内核存储信号,直到可以处理它。

最后,一旦有空闲,内核即按以下三种方式之一处理信号。

(1)忽略信号:什么也不做。大多数信号都可使用这种方式进行处理,但 SIGKILL 和 SIGTOP 信号决不能被忽略其原因是:它们向内核和超级用户提供了使进程终止或停止的可靠方法。另外,如果忽略某些由硬件异常产生的信号(如非法内存引用或除以 0),则进程的运行行为是未定义的。

(2)捕获信号:内核暂停收到信号的进程正在执行的代码,跳转到事先注册的信号处理函数,执行该函数并返回,跳转到捕获信号的地方继续执行。SIGKILL 和 SIGTOP 信号不能捕获

(3)默认操作:不同信号有不同的默认操作,对大多数信号的系统默认动作是终止该进程,但也有一些信号的默认操作是视而不见的,即忽略。

三、信号名称与编号

每个信号都有唯一的名称和编号。信号的名称是以 SIG 开头的文本,如 SIGHUP、SIGINT 等。信号的编号是从 1 开始连续增加的整数,如 1、2、3 等。信号的名称与编号之间的对应关系,依赖于具体实现。

/usr/include/i386-linux-gnu/bits/signum.h 文件中通过宏定义,把每个信号的名称和编号建立了一一映射。

  1. /* Signals.  */
  2. #define SIGHUP  1 /* Hangup (POSIX).  */
  3. #define SIGINT  2 /* Interrupt (ANSI).  */
  4. #define SIGQUIT  3 /* Quit (POSIX).  */
  5. #define SIGILL  4 /* Illegal instruction (ANSI).  */
  6. #define SIGTRAP  5 /* Trace trap (POSIX).  */
  7. #define SIGABRT  6 /* Abort (ANSI).  */
  8. #define SIGIOT  6 /* IOT trap (4.2 BSD).  */
  9. #define SIGBUS  7 /* BUS error (4.2 BSD).  */
  10. #define SIGFPE  8 /* Floating-point exception (ANSI).  */
  11. #define SIGKILL  9 /* Kill, unblockable (POSIX).  */
  12. #define SIGUSR1  10 /* User-defined signal 1 (POSIX).  */
  13. #define SIGSEGV  11 /* Segmentation violation (ANSI).  */
  14. #define SIGUSR2  12 /* User-defined signal 2 (POSIX).  */
  15. #define SIGPIPE  13 /* Broken pipe (POSIX).  */
  16. #define SIGALRM  14 /* Alarm clock (POSIX).  */
  17. #define SIGTERM  15 /* Termination (ANSI).  */
  18. #define SIGSTKFLT 16 /* Stack fault.  */
  19. #define SIGCLD  SIGCHLD /* Same as SIGCHLD (System V).  */
  20. #define SIGCHLD  17 /* Child status has changed (POSIX).  */
  21. #define SIGCONT  18 /* Continue (POSIX).  */
  22. #define SIGSTOP  19 /* Stop, unblockable (POSIX).  */
  23. #define SIGTSTP  20 /* Keyboard stop (POSIX).  */
  24. #define SIGTTIN  21 /* Background read from tty (POSIX).  */
  25. #define SIGTTOU  22 /* Background write to tty (POSIX).  */
  26. #define SIGURG  23 /* Urgent condition on socket (4.2 BSD).  */
  27. #define SIGXCPU  24 /* CPU limit exceeded (4.2 BSD).  */
  28. #define SIGXFSZ  25 /* File size limit exceeded (4.2 BSD).  */
  29. #define SIGVTALRM 26 /* Virtual alarm clock (4.2 BSD).  */
  30. #define SIGPROF  27 /* Profiling alarm clock (4.2 BSD).  */
  31. #define SIGWINCH 28 /* Window size change (4.3 BSD, Sun).  */
  32. #define SIGPOLL  SIGIO /* Pollable event occurred (System V).  */
  33. #define SIGIO  29 /* I/O now possible (4.2 BSD).  */
  34. #define SIGPWR  30 /* Power failure restart (System V).  */
  35. #define SIGSYS  31 /* Bad system call.  */
  36. #define SIGUNUSED 31
当然,我们之前讲 kill 指令时,参看:UNIX再学习 -- ps、top、kill 指令  讲到,执行 kill -l 命令,可以查看系统支持的信号列表。

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

列表中,编号为1 ~ 31的信号为传统UNIX支持的信号,是不可靠信号(非实时的),编号为32 ~ 63的信号是后来扩充的,称做可靠信号(实时信号)。不可靠信号和可靠信号的区别在于前者不支持排队,可能会造成信号丢失,而后者不会。总共 62 个信号,注意没有 32 和 33 信号。也不存在编号为 0 的信号,POSIX.1 将此信号编号值称为空信号,这部分后面会详解讲到的。

四、逐一说明这些信号

上面提到默认操作对大多数信号的系统默认动作是终止该进程,但也有一些信号的默认操作是视而不见的,即忽略。

下面给出对每一种信号的系统默认动作:

019 UNIX再学习 -- 信号019 UNIX再学习 -- 信号

上图列出了所有信号的名字,说明了哪些系统支持此信号以及对于这些信号的系统默认动作。在 SUS 列中,“.”表示此种信号定义为基本 POSIX.1 规范部分,“XSI”表示该信号定义在 XSI 扩展部分。

在系统默认动作列,“终止+core”表示在进程当前工作目录的 core 文件中复制了该进程的内存映像(该文件名为 core,由此可以看出这种功能很久之前就是 UNIX 的一部分)。大多数 UNIX 系统调试程序都使用 core 文件检查进程终止时的状态

其中在 Linux 3.2.0 中,core 文件名通过 /proc/sys/kernel/core_pattern 进行配置。

  1. /proc/sys/kernel# cat core_pattern
  2. |/usr/share/apport/apport %p %s %c


这里出现了 core 文件 是什么东东?

参看:Core文件作用、设置及用法

1、core文件简介

core 文件起始就是内存的映像,当程序崩溃时,存储内存的相应信息,主要用于对程序进行调试。当程序崩溃时便会产生 core 文件,其实准确的应该说 core dump 文件,默认生成位置与可执行程序位于同一目录下,文件名 core.***,其中 *** 是某一数字。

2、开启或关闭 core 文件的生成

(1)关闭或阻止 core 文件生成:

# ulimit -c 0
(2)打开 core 文件生成:

# ulimit -c unlimited
(3)检查 core 文件的选项是否打开:

  1. # ulimit -a
  2. core file size          (blocks, -c) unlimited
  3. data seg size           (kbytes, -d) unlimited
  4. scheduling priority             (-e) 0
  5. file size               (blocks, -f) unlimited
  6. pending signals                 (-i) 7892
  7. max locked memory       (kbytes, -l) 64
  8. max memory size         (kbytes, -m) unlimited
  9. open files                      (-n) 1024
  10. pipe size            (512 bytes, -p) 8
  11. POSIX message queues     (bytes, -q) 819200
  12. real-time priority              (-r) 0
  13. stack size              (kbytes, -s) 8192
  14. cpu time               (seconds, -t) unlimited
  15. max user processes              (-u) 7892
  16. virtual memory          (kbytes, -v) unlimited
  17. file locks                      (-x) unlimited
其中 ulimit 函数我们之前有讲过,参看:UNIX再学习 -- 文件描述符
以上配置只对当前会话起作用,下次重新登录后,还得重新配置。要想配置永久生效,得在 /etc/profile 或者 /etc/security/limits.conf 文件中进行配置,将 0 改为 ulimited。

  1. #gedit /etc/security/limits.conf
  2. #<domain>      <type>  <item>         <value>
  3. #
  4. #*               soft    core         unlimited
或者在 /etc/profile 中作如下配置:
  1. #vi /etc/profile
  2. ulimit -S -c unlimited >/dev/null 2>&1
或者想配置只针对某一用户有效,则修改此用户的 ~/.bashrc 或者~/.bash_profile文件

ulimit -c unlimited
ulimit -c 0 是禁止产生core文件,而 ulimit -c 1024 则限制产生的 core 文件的大小不能超过 1024kb。

3. 设置Core Dump的核心转储文件目录和命名规则

/proc/sys/kernel/core_uses_pid 可以控制产生的 core 文件的文件名中是否添加 pid 作为扩展,如果添加则文件内容为1,否则为 0

/proc/sys/kernel/core_pattern 可以设置格式化的 core 文件保存位置或文件名,比如原来文件内容是 core-%e
可以这样修改:
echo "/corefile/core-%e-%p-%t" > /proc/sys/kernel/core_pattern
将会控制所产生的 core 文件会存放到 /corefile 目录下,产生的文件名为 core-命令名-pid-时间戳

以下是参数列表:

  1.     %p - insert pid into filename 添加pid
  2.     %u - insert current uid into filename 添加当前uid
  3.     %g - insert current gid into filename 添加当前gid
  4.     %s - insert signal that caused the coredump into the filename 添加导致产生core的信号
  5.     %t - insert UNIX time that the coredump occurred into filename 添加core文件生成时的unix时间
  6.     %h - insert hostname where the coredump happened into filename 添加主机名
  7.     %e - insert coredumping executable name into filename 添加命令名

4. 使用core文件

在core文件所在目录下键入:

gdb -c core

它会启动 GNU 的调试器,来调试 core 文件,并且会显示生成此 core 文件的程序名,中止此程序的信号等等。
如果你已经知道是由什么程序生成此 core 文件的,比如 MyServer 崩溃了生成 core.12345,那么用此指令调试:
gdb -c core MyServer
gdb使用,参看:C语言再学习 -- GCC编译过程

5. 一个小方法来测试产生core文件

直接输入指令:
kill -s SIGSEGV $$

6. 为何有时程序Down了,却没生成 Core文件。

Linux下,有一些设置,标明了resources available to the shell and to processes。 可以使用
#ulimit -a 来看这些设置。

从这里可以看出,如果 -c 是显示:core file size          (blocks, -c) 

如果这个值为 0,则无法生成core文件。所以可以使用:
#ulimit -c 1024 或者 #ulimit -c unlimited 来使能 core文件。
如果程序出错时生成 core 文件,则会显示 Segmentation fault (core dumped)。

7. Core Dump的核心转储文件目录和命名规则:

/proc/sys/kernel/core_uses_pid 可以控制产生的 core 文件的文件名中是否添加 pid 作为扩展,如果添加则文件内容为1,否则为 0。

看到这里是不是有点蒙,core 文件其实我们之前有接触过的,比上面讲的更清楚。

参看:C语言再学习 -- 段错误(核心已转储)


接着讲信号,在下列条件下不产生 core 文件:

(1)进程是设置用户 ID 的,而且当前用户并非程序文件的所有者。

(2)进程是设置组 ID 的,而且当前用户并非该程序文件的组所有者。

(3)用户没有写当前工作目录的权限。

(4)文件已存在,而且用户对该文件设有写权限。

(5)文件太大。其中 RLIMIT_CORE  表示 core 文件的最大字节数,若其值为 0 则阻止创建 core 文件。


下面较详细地逐一说明这些信号:

参看:linux中的signal机制

1) SIGHUP

本信号在用户终端连接(正常或非正常)结束时发出, 通常是在终端的控制进程结束时, 通知同一 session 内的各个作业, 这时它们与控制终端不再关联。

登录Linux时,系统会分配给登录用户一个终端(Session)。在这个终端运行的所有程序,包括前台进程组和后台进程组,一般都属于这个Session。当用户退出 Linux 登录时,前台进程组和后台有对终端输出的进程将会收到 SIGHUP信号。这个信号的默认操作为终止进程,因此前台进程组和后台有终端输出的进程就会中止。不过可以捕获这个信 号,比如 wget 能捕获 SIGHUP 信号,并忽略它,这样就算退出了 Linux 登录,wget 也能继续下载。
此外,对于与终端脱离关系的守护进程,这个信号用于通知它重新读取配置文件。

2) SIGINT (常用)

程序终止(interrupt)信号, 在用户键入INTR字符 (通常是Ctrl-C) 时发出,用于通知前台进程组终止进程

3) SIGQUIT (常用)

和SIGINT类似, 但由QUIT字符 (通常是Ctrl-\) 来控制.。进程在因收到 SIGQUIT 退出时会产生 core 文件, 在这个意义上类似于一个程序错误信号。

4) SIGILL

执行了非法指令. 通常是因为可执行文件本身出现错误, 或者试图执行数据段. 堆栈溢出时也有可能产生这个信号。

5) SIGTRAP

由断点指令或其它trap指令产生. 由debugger使用。

6) SIGABRT

调用 abort 函数生成的信号。进程异常终止。

7) SIGBUS

非法地址, 包括内存地址对齐(alignment)出错。比如访问一个四个字长的整数, 但其地址不是4的倍数。它与 SIGSEGV 的区别在于后者是由于对合法存储地址的非法访问触发的 (如访问不属于自己存储空间或只读存储空间)。

8) SIGFPE

在发生致命的算术运算错误时发出. 不仅包括浮点运算错误, 还包括溢出及除数为0等其它所有的算术的错误。

9) SIGKILL (常用)

用来立即结束程序的运行. 本信号不能被阻塞、处理和忽略。如果管理员发现某个进程终止不了,可尝试发送这个信号。

10) SIGUSR1

这是一个用户定义的信号,可用于应用程序。

11) SIGSEGV

试图访问未分配给自己的内存, 或试图往没有写权限的内存地址写数据.指示进程进程了一次无效的内存引用

12) SIGUSR2

这是另一个用户定义的信号,与 SIGUSR1 相似,可用于应用程序。

13) SIGPIPE

管道破裂。这个信号通常在进程间通信产生,比如采用FIFO(管道)通信的两个进程,读管道没打开或者意外终止就往管道写,写进程会收到 SIGPIPE 信号。此外用 Socket 通信的两个进程,写进程在写 Socket 的时候,读进程已经终止。

14) SIGALRM

时钟定时信号, 计算的是实际的时间或时钟时间. alarm函数使用该信号.

15) SIGTERM

程序结束(terminate)信号, 与 SIGKILL 不同的是该信号可以被阻塞和处理。通常用来要求程序自己正常退出,shell命令kill缺省产生这个信号。如果进程终止不了,我们才会尝试SIGKILL。

17) SIGCHLD

子进程结束时, 父进程会收到这个信号。
如果父进程没有处理这个信号,也没有等待(wait)子进程,子进程虽然终止,但是还会在内核进程表中占有表项,这时的子进程称为僵尸进程。这种情况我们应该避免(父进程或者忽略SIGCHILD信号,或者捕捉它,或者wait它派生的子进程,或者父进程先终止,这时子进程的终止自动由init进程来接管)。

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

31) SIGSYS

非法的系统调用。

在以上列出的信号中,程序不可捕获、阻塞或忽略的信号有:SIGKILL,SIGSTOP

不能恢复至默认动作的信号有:SIGILL,SIGTRAP

默认会导致进程流产的信号有:

SIGABRT,SIGBUS,SIGFPE,SIGILL,SIGIOT,SIGQUIT,SIGSEGV,SIGTRAP,SIGXCPU,SIGXFSZ

默认会导致进程退出的信号有:

SIGALRM,SIGHUP,SIGINT,SIGKILL,SIGPIPE,SIGPOLL,SIGPROF,SIGSYS,SIGTERM,SIGUSR1,SIGUSR2,SIGVTALRM

默认会导致进程停止的信号有:SIGSTOP,SIGTSTP,SIGTTIN,SIGTTOU

默认进程忽略的信号有:SIGCHLD,SIGPWR,SIGURG,SIGWINCH

此外,SIGIO在SVR4是退出,在4.3BSD中是忽略;SIGCONT在进程挂起时是继续,否则是忽略,不能被阻塞。


这里顺便提一句:
ctrl-c 是发送 SIGINT 信号,终止一个进程;进程无法再重续。
ctrl-z 是发送 SIGSTOP 信号,挂起一个进程;进程从前台转入后台并暂停,可以用 bg 使其后台继续运行,fg 使其转入前台运行。
ctrl-d 不是发送信号,而是表示一个特殊的二进制值,表示 EOF,通常是表示输入终止,通常进程接收到终止符可以完成运行并退出。

五、产生信号的条件

1、bash按下时,终端会发送信号给前台,Ctrl-C 产生 SIGINT 信号,Ctrl-\产生SIGQUIT信号,Ctrl-Z产生SIGTSTP 信号,上述信号使得进程停止。
2、硬件异常信号,条件由硬件检测到并通知内核,然后内核向当前进程发送信号。例如执行了除以 0 的指令,CPU 的运算单元会产生异常,内核将这个异常解释为 SIGFPE 信号发送给进程。再比如当前进程访问了非法内存地址,MMU 会产生异常,内核将这个异常解释为 SIGSEGV 信号发送给进程。
3、一个进程调用 kill(2) 函数可以发送信号给另一个进程。
4、可以用 kill(1)命令发送信号给某个进程,kill(1)命令也是调用kill(2)函数实现的,如果不明确指定信号则发送 SIGTERM 信号,该信号的默认处理动作是终止进程。
5、当内核检测到某种软件条件发生时也可以通过信号通知进程,例如闹钟超时产生SIGALRM信号,向读端已关闭的管道写数据时产生SIGPIPE信号。

六、signal 函数

  1. #include <signal.h>
  2. typedef void (*sighandler_t)(int);
  3. sighandler_t signal(int signum, sighandler_t handler);

1、函数功能

设置对指定信号的指定处理方式

2、返回值

成功返回原信号处理方式,失败返回 SIG_ERR

3、参数解析

signum:信号编号
handler:信号处理方式,可取以下值
    SIG_IGN    忽略信号
    SIG_DFL    默认操作
    信号处理函数指针    捕获信号
查看系统头文件 /usr/include/i386-linux-gnu/bits/signum.h,则可以找到下列形式的声明:
  1. /* Fake signal functions.  */
  2. #define SIG_ERR ((__sighandler_t) -1)  /* Error return.  */
  3. #define SIG_DFL ((__sighandler_t) 0)  /* Default action.  */
  4. #define SIG_IGN ((__sighandler_t) 1)  /* Ignore signal.  */
这些常量可用于表示“指向函数的指针,该函数要求一个整型参数,而且无返回值”。signal 的第二个参数及其返回值就可用它们表示。

4、示例说明

  1. //示例一
  2. #include <stdio.h>
  3. #include <signal.h>
  4. int main (void)
  5. {
  6.  signal (SIGINT, SIG_IGN);
  7.  while (1);
  8.  return 0;
  9. }
  10. ctrl + c 将无法结束
  1. //示例二
  2. #include <stdio.h>
  3. #include <signal.h>
  4. int main (void)
  5. {
  6.  signal (SIGINT, SIG_DFL);
  7.  while (1);
  8.  return 0;
  9. }
  10. ctrl + c 可结束
  1. //示例三
  2. #include <stdio.h>
  3. #include <signal.h>
  4. //自定义信号处理函数
  5. void fa(int signo)
  6. {
  7.  printf("捕获到了信号%d\n",signo);
  8. }
  9. int main(void)
  10. {
  11.  signal(2,fa);
  12.  while(1);
  13.  return 0;
  14. }
  15. ctrl + c 可打印 捕获到了信号2
  1. //示例四
  2. #include <stdio.h> 
  3. #include <signal.h> 
  4. #include <stdlib.h>
  5.  
  6. int main (void) 
  7.  //if (signal (SIGKILL, SIG_IGN) == SIG_ERR)
  8.  //if (signal (SIGKILL, SIG_DFL) == SIG_ERR)
  9.  //if (signal (SIGSTOP, SIG_DFL) == SIG_ERR)
  10.  if (signal (SIGSTOP, SIG_IGN) == SIG_ERR)
  11.     perror ("signal"), exit (1); 
  12.  while (1); 
  13.  return 0
  14. 输出结果:
  15. signal: Invalid argument

5、示例解析

示例一:使用 SIG_IGN 为忽略信号,所以使用 ctrl+c 终止进程失效
示例二:使用 SIG_DEL 为默认操作,所以有无 signal (SIGINT, SIG_DFL); 都可以使用ctrl+c 终止进程的。
示例三:使用自定义信号处理函数,注意该函数无返回值,参数为整型。SIGINT 可用信号编号 2 代替。
如果想结束,可用 ctrl+\ 即 SIGQUIT 退出。或打印进程号使用 kill -9 PID 杀死进程。 
示例四:说明 SIGKILL (9) 和 SIGSTOP (19) 信号即不能被忽略,也不能被捕获,只能按缺省方式终止或停止接收到信号的进程。如果返回失败则返回 SIG_ERR。

七、信号的分类

可以从两个不同的分类角度对信号进行分类:
(1)可靠性方面,分为可靠信号与不可靠信号
(2)与时间的关系上,分为实时信号与非实时信号。
上面也有提到了,编号为1 ~ 31的信号为传统UNIX支持的信号,是不可靠信号(非实时的),编号为32 ~ 63的信号是后来扩充的,称做可靠信号(实时信号)。

1、不可靠信号

Linux信号机制基本上是从 UNIX 系统中继承过来的。早期 UNIX 系统中的信号机制比较简单和原始,后来在实践中暴露出一些问题,因此,把那些建立在早期机制上的信号叫做“不可靠信号”,信号小于 SIGRTMN 的信号都是不可靠信号。这就是“不可靠信号”的来源。它的主要问题是:
进程每次处理信号后,就将对信号的响应设为默认动作。在某些情况下,就导致对信号的错误处理;因此,用户如果不希望这样的操作,那么就要在信号处理函数结尾再一次调用 signal ( ),重新安装该信号。
信号可能丢失,后面将对此详细阐述。因此,早期 UNIX 下的不可靠信号主要指的是进程可能对信号做出错误的反应以及信号可能丢失。
Linux支持不可靠信号,但是对不可靠信号机制做了改进,在调用完信号处理函数后,不必重新调用该信号的安装函数(信号安装函数时在可靠机制上的实现)。因此,Linux 下的不可靠信号问题主要是指的是信号可能丢失

2、可靠信号

随着时间的发展,实践证明了有必要对信号的原始机制加以改进和扩充。所以,后来出现的各种 UNIX 版本分别在这方面进行了研究,力图实现“可靠信号”。由于原来定义的信号已有许多应用,不好再做改动,最终只好又新增加了一些信号,并在一开始就把它们定义为可靠信号,这些信号支持排队,不会丢失。同时,信号的发送和安装也出现了新版本:信号发送函数 sigqueue() 及信号安装函数 sigaction() 。POSIX.4 对可靠信号机制做了标准化。但是,POSIX 只对可靠信号机制应具有的功能以及信号机制的对外接口做了标准化,对信号机制的实现没有作具体的规定。
信号值位于 SIGRTMIN 和 SIGRTMAX 之间的信号都是可靠信号,可靠信号克服了信号可能丢失的问题。Linux在支持新版本的信号安装函数 sigation()以及信号发送函数 sigqueue ( ) 的同时,仍然支持早期的 signal()信号安装函数,支持信号发送函数 kill()。
注:不要有这样的误解:由 sigqueue() 发送、sigaction 安装的信号就是可靠的。事实上,可靠信号是指后来添加的新信号(信号值位于SIGRTMIN及SIGRTMAX之间);不可靠信号是信号值小于SIGRTMIN的信号信号的可靠与不可靠只与信号值有关,与信号的发送及安装函数无关。目前linux中的 signal() 是通过 sigation() 函数实现的,因此,即使通过 signal()安装的信号,在信号处理函数的结尾也不必再调用一次信号安装函数。同时,由 signal() 安装的实时信号支持排队,同样不会丢失。
对于目前 linux 的两个信号安装函数: signal() 及 sigaction() 来说,它们都不能把 SIGRTMIN 以前的信号变成可靠信号(都不支持排队,仍有可能丢失,仍然是不可靠信号),而且对 SIGRTMIN 以后的信号都支持排队。这两个函数的最大区别在于,经过 sigaction 安装的信号都能传递信息给信号处理函数(对所有信号这一点都成立),而经过signal 安装的信号却不能向信号处理函数传递信息。对于信号发送函数来说也是一样的

3、实时信号与非实时信号

当多个相同的信号被发送到正在处理该信号的进程时,那些还来不及处理的信号会按先后顺序排成队列。等进程处理完手里的信号以后,再依次处理队列中的信号。整个过程中,所有发送给进程的信号一个也不会丢,都能得到处理。这样的信号就叫做可靠信号。实时信号都是可靠信号,都支持排队。
反之,不可靠信号不支持排队,可能丢失,在多个相同的信号里进程可能只收到一个。非实时信号都是不可靠信号。
                                   
                                   
               
                   
相关标签: UNIX