使用需要包含
signal(参数1,参数2);
参数1:我们要进行处理的信号。系统的信号我们可以再终端键入 kill -l 查看(共64个)。
参数2:我们处理的方式(系统默认 / 忽略 / 捕获)。
signal(SIGINT, SIG_ING ); // ignore the signal
signal(SIGINT, SIG_DFL); // use the default handler
signal(SIGINT, userfunc); // user defined handler
signal函数的原型为
void ( *signal( int sig, void (* handler)( int )))( int );
这个比较有意思,容易把人看懵。
void (*p)(int); // p 是一个函数指针,参数是 int 类型,无返回值
void (*p())(int); // p 是一个函数,p的返回值是一个函数指针,参数是 int 类型,无返回值
// 然后再来看signal的定义,有没有清楚一些...
signal 函数的返回值是一个函数指针,该指针指向一个参数是 int 类型,无返回值的函数。
signal 函数本身的参数是 int 类型以及一个函数指针,void (* handler)(int)。
// 简化的写法如下
typedef void (*handler)(int);
handler signal(int, handler);
// signal 函数返回的是上一次处理该信号的函数指针,如果没有就返回 NULL
signal 函数返回的是上一次处理该信号的函数指针,如果没有就返回 NULL。
如果不关心返回值也可以不写,好的编程习惯是加个判断, if (signal(SIGINT, sigint_handler)) == SIG_ERR)
。
#include
#include
#include
void catch1(int signo) {
printf("catch1 received signal %d\n", signo);
}
void catch2(int signo) {
printf("catch2 received signal %d\n", signo);
}
int main(int argc, char *argv[]) {
sig_t prev_sigint_handler1 = signal(SIGINT, catch1);
assert(prev_sigint_handler1 == NULL);
sig_t prev_sighup_handler1 = signal(SIGHUP, catch2);
assert(prev_sighup_handler1 == NULL);
raise(SIGINT); // calls catch1
raise(SIGHUP); // calls catch2
// Now let's swap the handlers
sig_t prev_sigint_handler2 = signal(SIGINT, catch2);
assert(prev_sigint_handler2 == catch1);
sig_t prev_sighup_handler2 = signal(SIGHUP, catch1);
assert(prev_sighup_handler2 == catch2);
raise(SIGINT); // calls catch2
raise(SIGHUP); // calls catch1
return 0;
}
% ./a.out
catch1 received signal 2
catch2 received signal 1
catch2 received signal 2
catch1 received signal 1
// man sigaction
#include
/**
* 注册信号处理函数,成功返回0,失败返回-1并置 errno
* 参数 act 存储待注册的信号处理函数结构体
* oldact 非空的话,旧的信号处理函数会存储到该结构体中
*/
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_sigaction 只能任选其一
该结构在注册信号处理函数sigaction中使用
1. sa_handler 是一个参数为信号值的处理函数
2. sa_sigaction 也是一个信号处理函数,不过它有三个参数,能够获取到处信号值以外更多
信息,当 sa_flags 中包含 SA_SIGINFO 标志位的时候需要用到该函数。
3. sa_mask 是信号处理函数执行期间的屏蔽信号集。就是说在信号处理函数执行期间,屏蔽某
些信号。但是不是所有信号都能够被屏蔽,SIGKILL 和 SIGSTOP 这两个信号就无法屏
蔽,因为 OS 自身要能够控制住进程。
4. sa_flags可以是下面这些值的集合:
1. SA_NOCLDSTOP,
这个标志位只用于SIGCHLD信号。父进程可以检测子进程三个事件,子进程终止、
子进程停止、子进程恢复。SA_NOCLDSTOP标志位用于控制后两个事件。即一旦父进程
为SIGCHLD信号设置了这个标志位,那么子进程停止和子进程恢复这两件事情,就无需
向父进程发送 SIGCHLD 信号。
2. SA_NOCLDWAIT
这个标志只用于 SIGCHLD 信号,它可控制子进程终止时候的行为,如果父进程
为 SIGCHLD 设置了 SA_NOCLDWAIT 标志位,那么子进程终止退出时,就不会进入僵尸
状态,而是直接自行了断。但是对 Linux 而言,子进程仍然会发送 SIGCHLD 信号,这
点和上面的 SA_NOCLDSTOP 略有不同。
3. SA_ONESHOT 和 SA_RESETHAND
这两个标志位本质是一样的,表示信号处理函数是一次性的,信号递送出去以后,信号
处理函数便恢复成默认值 SIG_DFL。
4. SA_NODEFER 和 SA_NOMASK
这两个标志位的作用是一样的,信号处理函数执行期间,不阻塞当前信号。
5. SA_RESTART
这个标志位表示,如果系统调用被信号中断,则不返回错误,而是自动重启系统调用。
6. SA_SIGINFO
这个标志位表示信号发送者会提供额外的信息。这种情况下,信号处理函数应该为
三参数的函数。
siginfo_t 的内容如下
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 Linux 2.6.32) */
void *si_lower; /* Lower bound when address violation
occurred (since Linux 3.19) */
void *si_upper; /* Upper bound when address violation
occurred (since Linux 3.19) */
int si_pkey; /* Protection key on PTE that caused
fault (since Linux 4.6) */
void *si_call_addr; /* Address of system call instruction
(since Linux 3.5) */
int si_syscall; /* Number of attempted system call
unsigned int si_arch; /* Architecture of attempted system call
(since Linux 3.5) */
}
其中SIGSTOP以及SIGKILL 无法被捕获和忽略。无法被捕获指不能指定用户的处理函数。
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专用,进程超过资源限制的时候发送 |
接收到信号后的处理动作有以下三种:
实际上我们还可以在接收信号前屏蔽它,叫做信号阻塞,这里不详述,感兴趣的可以再查阅资料。
注意:忽略此信号是指接收到了信号后不做处理,阻塞的意思是信号不送达,但是还是有可能在我们更改信号的处理方式后被处理的,比如将忽略信号调整为执行默认的动作后不再阻塞该信号。
信号 SIGCHLD
的产生条件
SIGSTOP
信号停止时SIGCONT
后唤醒时自看宝典
pid_t wait(int *status);
进程一旦调用了wait,就立即阻塞自己,由wait自动分析是否当前进程的某个子进程已经退出,如果让它找到了这样一个已经变成僵尸的子进程,wait就会收集这个子进程的信息,并把它彻底销毁后返回;如果没有找到这样一个子进程,wait就会一直阻塞在这里,直到有一个出现为止。
pid_t waitpid(pid_t pid, int *status, int option);
wait(&status)
== waitpid(-1, &status, 0);
These system calls are used to wait for state changes in a child of the calling process, and obtain information about the child whose state has changed. A state change is considered to be:
In the case of a terminated child, performing a wait allows the system to release the resources associated with the child; if a wait is not performed, then the terminated child remains in a “zombie” state.
By default, waitpid() waits only for terminated children, but this behavior is modifiable via the options argument, as described below. The value of options is an OR of zero or more of the following constants:
option | description |
---|---|
WNOHANG | 没有子进程结束,立即返回 |
WUNTRACED | 如果子进程由于被停止产生的SIGCHLD,waitpid则立即返回 |
WCONTINUED | 如果子进程由于被SIGCONT唤醒而产生的SIGCHLD,waitpid则立即返回 |
如果成功返回等待子进程的 ID,失败返回-1。
Marcro | description |
---|---|
WIFEXITED(status) | 子进程正常exit终止,返回真 |
WEXITSTATUS(status) | 返回子进程正常退出值 |
WIFSIGNALED(status) | 子进程被信号终止,返回真 |
WTERMSIG(status) | 返回终止子进程的信号值 |
WIFSTOPPED(status) | 子进程被信号停止,返回真 |
WSTOPSIG(status) | 返回停止子进程的信号值 |
WIFCONTINUED(status) | 子进程被信号SIGCONT恢复运行,返回真 |
#include
#include
#include
#include
void _exit1(void) {
printf("parent _exit1\n");
}
void _exit2(void) {
printf("child _exit2\n");
}
void signalhandler(int signum) {
if (signum == SIGIO) {
printf("pid: %d catch SIGIO\n", getpid());
} else if (signum == SIGUSR2) {
printf("pid: %d catch SIGUSR2\n", getpid());
} else {
printf("pid: %d catch error\n", getpid());
}
}
int main(int argc, char *argv[]) {
pid_t pid, ret;
int status;
atexit(_exit1); // child inherit the funtion
pid = fork();
if (pid < 0) {
perror("fork error");
exit(EXIT_FAILURE);
}
if( pid == 0) {
//signal(SIGUSR2, SIG_DFL); // exit abnomal with SIGUSR2
signal(SIGUSR2, signalhandler); // child handle this signal and exit normally
atexit(_exit2); // called first
printf("This is the child process\n");
//exit(100); // atexit functions called
//_exit(200); // no atexit functions called
//abort(); // exit abnormal with SIGABORT
//raise(SIGSTOP); // raise SIGSTOP to itself
raise(SIGUSR2); // raise SIGUSER2 to itself
return 0;
}
ret = waitpid(pid, &status, 0 | WUNTRACED); // wait for child termial or stopped
if (ret < 0) {
perror("wait error");
exit(EXIT_FAILURE);
}
printf("parent waitpid: ret = %d pid = %d\n", ret, pid);
if (WIFEXITED(status)) {
printf("child exited normal exit status = %d\n", WEXITSTATUS(status));
} else if (WIFSIGNALED(status)) {
printf("child exited abnormal signal number = %d\n", WTERMSIG(status));
} else if (WIFSTOPPED(status)) {
printf("child stoped signal number = %d\n", WSTOPSIG(status));
}
return 0;
}
#include
int sigqueue(pid_t pid, int sig, const union sigval value);
union sigval {
int sival_int;
void *sival_ptr;
};
raise(signum)
#include
#include
int kill(pid_t pid, int sig);
日常我们想结束一个进程会使用这样的命令,kill -9 pid
,实际上就是在向进程号为 pid 的进程发送 SIGKILL
。
关于 kill 函数,还有一点需要额外说明, kill 函数传入的 pid 可以是小于等于0的整数。
pid > 0:将发送给该 pid 的进程
pid == 0: 将会把信号发送给与发送进程属于同一进程组的所有进程,并且发送进程具有权限向这些进程发送信号。
pid < 0:将信号发送给进程组ID 为 pid 的绝对值的,并且发送进程具有权限向其发送信号的所有进程。
pid == -1:将该信号发送给发送进程的有权限向他发送信号的所有进程。
关于信号,还有更多的话题,比如,信号是否都能够准确的送达到目标进程呢?
答案其实是不一定,因为有可靠信号和不可靠信号的区分。
对于信号来说,信号编号<=31的信号都是不可靠信号,之后的信号为可靠信号,系统会根据有信号队列,将信号在递达之前进行阻塞。信号的阻塞和未决是通过信号的状态字来管理的,状态字按位管理。每个信号都有独立的阻塞字,规定了当前要阻塞到达该进程的信号集。
阻塞状态字用户可读写,未决状态字用户只读,它由内核来设置,表示信号递达状态。
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
how 变量决定了是如何操作该状态字。
上面的函数通过信号集的数据结构来进行信号阻塞的管理。
而信号集本身可以通过以下的函数进行配置。
#include
int sigemptyset(sigset_t *set); // 初始化 set 中传入的信号集,清空其中所有信号
int sigfillset(sigset_t *set); // 把信号集填1,让 set 包含所有的信号
int sigaddset(sigset_t *set, int signum); // 信号集对应位置 1
int sigdelset(sigset_t *set, int signum); // 信号集对应位清 0
int sigismember(const sigset_t *set, int signum); // 判断 signal 是否在信号集
常用的步骤如下:
sigset bset;
sigempty(&bset);
sigaddset(&bset, SIGINT);
sigprocmask(SIG_UNBLOCK, &bset, NULL);
https://jameshfisher.com/2017/01/10/c-signal-return-value/ ↩︎