信号
- 信号: 通知进程系统中发生了一个某种类型的事件
- 每种信号类型都对应于某种系统事件. 低层的硬件异常时由内核异常处理程序处理的, 正常情况下, 对用户进程而言是不可见的. 信号提供了一种机制, 通知用户进程发生了这些异常
信号术语
- 传送一个信号到目标进程的过程:
- 发送信号: 内核通过更新目的进程上下文中的某个状态, 发送一个信号给目的进程
- 发送信号的两种原因:
- 内核检测到一个系统事件
- 一个进程调用了
kill
函数, 显式的要求内核发送一个信号到目的进程
- 发送信号的两种原因:
- 接收信号: 当目的进程被内核强迫以某种方式对信号的发送做出反应时, 即接收到了信号
- 进程可以忽略这个信号, 终止或者通过执行一个称为信号处理程序的用户层函数捕获这个信号
- 信号处理程序:
- 发送信号: 内核通过更新目的进程上下文中的某个状态, 发送一个信号给目的进程
- 待处理信号: 发出但是没有被接收
- 在任何时刻, 一种类型至多只会有一个待处理信号
- 后续到来的该类型信号不会进行排队等待, 而是会被丢弃
- 一个进程可以有选择性的阻塞接收某种信号
- 当一种信号被阻塞时候, 仍然可以被发送, 但是产生的待处理信号不会被接收, 直到进程取消对该信号的阻塞
- 一个待处理信号最多只能被接收一次
发送信号
- UNIX提供了大量想进程发送信号的机制, 而这些机制都是基于进程组(process group) 的
进程组
- 每个进程都只属于一个进程组
- 使用
getpgrp
函数返回当前进程的进程组ID - 一个进程可以使用
setpgid
函数来改变自己或者其他进程的进程组:- 如果
pid
是0, 则使用当前进程的PID - 如果
pgid
是0, 则使用pid指定的进程PID作为进程组IDsetpgid(0, 0)
: 创建一个新的进程组, 进程组ID为我自己
- 如果
使用/bin/kill
程序发送信号
从键盘发送信号
- 在任何时刻, 至多只有一个前台作业和0个或多个后台作业:
ls | sort
- 这个命令会创建一个由两个进程组成的前台作业, 这两个进程是通过unix管道连接的:
ls
与sort
- 这个命令会创建一个由两个进程组成的前台作业, 这两个进程是通过unix管道连接的:
Ctrl+C
: SIGINT, 终止前台作业Ctrl+Z
: SIGSTP, 挂起前台作业
使用kill
函数发送信号
使用alarm
函数发送信号
接收信号
- 当内核把进程p从内核模式切换到用户模式时, 它会检查进程p的未被阻塞的待处理信号集合
- 如果这个集合为空, 则内核将控制传递给p的逻辑控制流的下一条指令(正常执行)
- 非空: 内核选择集合中的某个信号k, 并且强制p接收信号k, 执行信号处理程序
- 信号处理程序的行为:
- 进程终止
- 进程终止并转储内存
- 进程停止直到被SIGCONT信号重启
- 进程忽略该信号
- 进程可以通过使用
signal
函数修改和信号相关联的默认行为- 例外:
SIGSTOP
和SIGKILL
的默认行为是不可修改的 signal
函数可以通过下列三种方法之一来改变和信号signum
相关联的行为:handler==SIG_IGN
: 忽略类型为signum
的信号handler==SIG_DFL
: 类型为signum
的信号行为恢复为默认行为handler
为用户定义的函数地址
- 例外:
- 解析: - 按下Ctrl+C时, sleep被强制中断, sleep函数返回剩余的秒数(因为我们定义的信号处理, 仅仅是返回, 而不是结束程序), 接下来打印出剩余的秒数即可, 并且程序会正常的结束
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
unsigned int snooze(unsigned int secs) {
// 按下Ctrl+C时, sleep被强制中断
unsigned int secs_left = sleep(secs);
printf("Slept for %d of %d secs\n", secs - secs_left, secs);
return secs_left;
}
// 信号处理的方法, 直接返回
void sign_handler(int sig) { return; }
int main(int argc, char *argv[]) {
unsigned int secs;
// 设置信号处理程序
if (signal(SIGINT, sign_handler) == SIG_ERR) {
perror("signal error");
}
if (argc > 1) {
secs = atoi(argv[1]);
}
unsigned int rc = snooze(secs);
printf("rc");
return 0;
}
阻塞和解除阻塞信号
-
两种阻塞信号的机制:
- 隐式阻塞机制: 内核默认阻塞任何当前处理程序正在处理信号类型的待处理的信号
- 显式阻塞机制: 应用程序可以使用
sigprocmask
函数和它的辅助函数, 明确的阻塞和接触阻塞选定的信号#include <signal.h> int sigprocmask(int how, const sigset_t *set, sigset_t *oldset); int sigemptyset(sigset_t *set); int sigfillset(sigset_t *set); int sigaddset(sigset_t *set, int signum); // 返回: 如果成功则为0, 出错则为-1 int sigdelset(sigset_t *set, int signum); // 返回: 若signum是set的成员则为1, 如果不是则为0, 出错则为-1 int sigismember(const sigset_t *set, int signum);
-
sigprocmask
:how
:SIG_BLOCK
: 将set
中的信号添加到blocked
中SIG_UNBLOCK
: 从blocked
中删除set
中的信号SIG_SETMASK
:block=set
- 如果
oldset
非空, 则blocked
位向量之前的值保存在oldset
中
- 对
set
信号集合进行操作的函数sigemptyset
: 初始化set
集合为空集合sigfillset
: 将每个信号都添加进set
中sigaddset
: 把signum
添加到set
中sigdelset
: 从set
中删除signum