unix编程之信号机制笔记
信号是进程间通信的一种方式,但是写代码中用到该机制并不多,调试程序时需要掌握些,故大概了解即可
基础环境:xcode+iterm+clang+macos
一、信号是什么?
信号是进程通信方式的一种,应该算是比较古老的一种吧。比较直观的一些用处是,譬如键盘上某个按键被按下(很常用的就是停止进程Ctrl+c的SIGINT),或者譬如系统用来停止某进程(进程管理器发送信号强制关闭进程)、或者系统用信号来处理某些严重的错误(一个进程试图向一块不存在的虚拟内存写入数据、或者是一条非法指令)、kill命令也是将信号SIGTERM信号发送给对应进程停止进程。
信号本身是不能携带信息的,这就限制了它作为进程间通信的能力。但是虽然你写代码用不到,但是对进程进行各种操作的时候,实际上很多时候系统都是依赖信号完成的功能。
二、标准信号
标准定义在bits/signum.h中
/* Signal number constants. Generic version.
Copyright (C) 1991-2017 Free Software Foundation, Inc.
This file is part of the GNU C Library.
The GNU C Library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
The GNU C Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with the GNU C Library; if not, see
<http://www.gnu.org/licenses/>. */
#ifdef _SIGNAL_H
/* Fake signal functions. */
#define SIG_ERR ((__sighandler_t) -1) /* Error return. */
#define SIG_DFL ((__sighandler_t) 0) /* Default action. */
#define SIG_IGN ((__sighandler_t) 1) /* Ignore signal. */
#ifdef __USE_UNIX98
# define SIG_HOLD ((__sighandler_t) 2) /* Add signal to hold mask. */
#endif
/* We define here all the signal names listed in POSIX (1003.1-2008);
as of 1003.1-2013, no additional signals have been added by POSIX.
We also define here signal names that historically exist in every
real-world POSIX variant (e.g. SIGWINCH).
Signals in the 1-15 range are defined with their historical numbers.
For other signals, we use the BSD numbers. */
/* ISO C99 signals. */
#define SIGINT 2 /* Interactive attention signal. */
#define SIGILL 4 /* Illegal instruction. */
#define SIGABRT 6 /* Abnormal termination. */
#define SIGFPE 8 /* Erroneous arithmetic operation. */
#define SIGSEGV 11 /* Invalid access to storage. */
#define SIGTERM 15 /* Termination request. */
/* Historical signals specified by POSIX. */
#define SIGHUP 1 /* Hangup. */
#define SIGQUIT 3 /* Quit. */
#define SIGTRAP 5 /* Trace/breakpoint trap. */
#define SIGKILL 9 /* Killed. */
#define SIGBUS 10 /* Bus error. */
#define SIGSYS 12 /* Bad system call. */
#define SIGPIPE 13 /* Broken pipe. */
#define SIGALRM 14 /* Alarm clock. */
/* New(er) POSIX signals (1003.1-2008, 1003.1-2013). */
#define SIGURG 16 /* High bandwidth data is available at a socket. */
#define SIGSTOP 17 /* Stopped (signal). */
#define SIGTSTP 18 /* Stopped. */
#define SIGCONT 19 /* Continued. */
#define SIGCHLD 20 /* Child terminated or stopped. */
#define SIGTTIN 21 /* Background read from control terminal. */
#define SIGTTOU 22 /* Background write to control terminal. */
#define SIGPOLL 23 /* Pollable event occurred (System V). */
#define SIGIO SIGPOLL /* I/O now possible (4.2 BSD). */
#define SIGXCPU 24 /* CPU time limit exceeded. */
#define SIGXFSZ 25 /* File size limit exceeded. */
#define SIGVTALRM 26 /* Virtual timer expired. */
#define SIGPROF 27 /* Profiling timer expired. */
#define SIGUSR1 30 /* User-defined signal 1. */
#define SIGUSR2 31 /* User-defined signal 2. */
/* Nonstandard signals found in all modern POSIX systems
(including both BSD and Linux). */
#define SIGWINCH 28 /* Window size change (4.3 BSD, Sun). */
#define _NSIG 32
/* Archaic names for compatibility. */
#define SIGIOT SIGABRT /* IOT instruction, abort() on a PDP-11. */
#define SIGCLD SIGCHLD /* Old System V name */
#endif /* <signal.h> included. */
这里举例几个比较重要常用的信号:
信号值 | 处理动作 | 发出信号的原因 |
---|---|---|
SIGHUP | 终止进程 | 终端挂起或者控制进程终止 |
SIGINT | 终止进程 | 键盘按退出,ctrl+c |
SIGQUIT | 终止进程,并进行内核映像转储 | 键盘按退出,ctrl+/ |
SIGILL | 终止进程,并进行内核映像转储 | 非法指令 |
SIGABRT | 终止进程,并进行内核映像转储 | 执行abort(3) |
SIGFPE | 终止进程,并进行内核映像转储 | 浮点异常 |
SIGKILL | 终止进程 | kill的终止信号 |
SIGSEGV | 终止进程,并进行内核映像转储 | 无效内存引用,sigmentfault |
SIGTERM | 终止进程 | 收到终止信号 |
三、忽略信号
因为基本上所有的信号都将会终止接收到该信号的进程。对于简单的进程来说完全足够,但是如果是对于一个大型进程,这将会产生一系列后果。
linux提供提供了signal()函数用来处理接收指定的信号,其函数声明如下:
int signal (int sig, __sighandler_t handler);
sig入参是信号枚举值,handler入参则是收到该信号时,会调用handler所指向的函数。
handler处可以传入三种值:SIG_IGN,表示对sig信号进行忽略;SIG_DFL,表示恢复该进程对信号的忽略;返回值为整数的函数地址,函数声明类似于int func(int sig);
现在用一段简单示例代码来让进程忽略SIGINT信号
#include "stdio.h"
#include "unistd.h"
#include "signal.h"
int main() {
signal(SIGINT, SIG_IGN);
printf("ignore SIGINT\n");
sleep(10);
signal(SIGINT, SIG_DFL);
printf("handle SIGINT\n");
return 0;
}
编译执行,SIGINT是在终端执行时,使用ctrl+c命令可以对进程发出该信号
feiqianyousadeMacBook-Pro:yousa yousa$ vim signal1.c
feiqianyousadeMacBook-Pro:yousa yousa$ clang -Wall signal1.c -o signal1
feiqianyousadeMacBook-Pro:yousa yousa$ ls
fork2exec.c signal1.c test.c
signal1 test test.dSYM
feiqianyousadeMacBook-Pro:yousa yousa$ ./signal1
ignore SIGINT
^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^Chandle SIGINT
这里我们可以看到,进程在忽略SIGINT信号之后,对于常用的ctrl+c退出进程命令已经不起作用了
但是可以尝试下ctrl+\停止进程的命令
feiqianyousadeMacBook-Pro:yousa yousa$ ./signal1
ignore SIGINT
^C^\Quit: 3
可以看到,ctrl+\终止进程的命令依然可以终止该进程。
从上面可以了解到
- 对该信号进行忽略后,进程会忽略收到的该信号
- 忽略的某种信号,并不会影响进程忽略其他信号
忽略信号、截获信号、同时出现多个信号会如何处理?如何给另一个进程发送信号?
在 Linux 程序中常常利用 SIG_IGN 和 SIG_DFL 屏蔽 SIGINT 和 SIGQUIT 来保证执行
四、处理信号
其实这里与忽略信号比较类似,忽略信号不过是直接使用系统定义的对信号进行忽略,而我们这里则是自己定义一个函数来捕捉信号,当然写的会很简单
#include "stdio.h"
#include "unistd.h"
#include "signal.h"
void catch_sig(int sig) {
printf("catch the sig : %d\n", sig);
}
int main() {
signal(SIGINT, (void (*)(int))catch_sig);
printf("ignore SIGINT\n");
sleep(10);
signal(SIGINT, SIG_DFL);
printf("handle SIGINT\n");
return 0;
}
注意catch_sig函数类型需要根据你自己的环境相应进行调整,我是在mac环境上,signal要求入参是这样。
feiqianyousadeMacBook-Pro:yousa yousa$ clang -Wall signal2.c -o signal2
feiqianyousadeMacBook-Pro:yousa yousa$ ./signal2
ignore SIGINT
^Ccatch the sig : 2
handle SIGINT
执行,可以看到我们截获到SIGINT信号:)
思考
考虑一种情况,如果进程已经收到了SIGINT信号,现在正在处理该信号,如果又收到了一个SIGINT信号会怎么样呢?如果这时候收到了SIGKILL信号又会怎么样呢?
这个可以简单试验下,这里这次就留到下次笔记自己再进行试验了。
PS
- 进程可以向自己发送信号(abort)
- SIGKILL。这是一个相当特殊的信号,它从一个进程发送到另一个进程,使接收到该信号的进程 终止。内核偶尔也会发出这种信号。SIGKILL 的特点是,它不能被忽略和捕捉,只能通过 用户定义的相应中断处理程序而处理该信号。因为其它的所有信号都能被忽略和捕捉,所 以只有这种信号能绝对保证终止一个进程
- 进程给进程发送信号的命令是使用kill函数,我们也可以通过命令行调用kill来给相应进程发送信号(当然基本是kill掉该进程)