----◼ 对于前台进程,用户可以通过输入特殊的终端字符来给它发送信号。比如输入Ctrl+C 通常会给进程发送一个中断信号。
----◼ 硬件发生异常,即硬件检测到一个错误条件并通知内核,随即再由内核发送相应信号给相关进程。比如执行一条异常的机器语言指令,诸如被 0 除,或者引用了无法访问的内存区域。
----◼ 系统状态变化,比如 alarm 定时器到期将引起 SIGALRM 信号,进程执行的 CPU 时间超限,或者该进程的某个子进程退出。
----◼ 运行 kill 命令或调用 kill 函数。
使用信号的两个主要目的是:
----◼ 让进程知道已经发生了一个特定的事情。
----◼ 强迫进程执行它自己代码中的信号处理程序。
信号的特点:
----◼ 简单
----◼ 不能携带大量信息
----◼ 满足某个特定条件才发送
----◼ 优先级比较高
查看系统定义的信号列表:kill –l
前 31 个信号为常规信号,其余为实时信号。
编号 | 信号名称 | 对应事件 | 默认动作 |
---|---|---|---|
2 | SIGINT | 当用户按下了组合键时,用户终端向正在运行中的由该终端启动的程序发出此信号 | 终止进程 |
3 | SIGQUIT | 用户按下组合键时产生该信号,用户终端向正在运行中的由该终端启动的程序发出些信号 | 终止进程 |
9 | SIGKILL | 无条件终止进程。该信号不能被忽略,处理和阻塞 | 终止进程,可以杀死任何进程 |
11 | SIGSEGV | 指示进程进行了无效内存访问(段错误) | 终止进程并产生core文件 |
13 | SIGPIPE | Broken pipe 向一个没有读端的管道写数据 | 终止进程 |
17 | SIGCHLD | 子进程结束时,父进程会收到这个信号 | 忽略这个信号 |
18 | SIGCONT | 如果进程已停止,则使其继续运行 | 继续/忽略 |
19 | SIGSTOP | 停止进程的执行。信号不能被忽略,处理和阻塞 | 为终止进程 |
查看信号的详细信息:man 7 signal
信号的 5 种默认处理动作
----◼ Term 终止进程
----◼ Ign 当前进程忽略掉这个信号
----◼ Core 终止进程,并生成一个 Core 文件用来保存进程异常退出的错误信息
----◼ Stop 暂停当前进程
----◼ Cont 继续执行当前被暂停的进程
信号的几种状态:产生、未决、递达
SIGKILL 和 SIGSTOP 信号不能被捕捉、阻塞或者忽略,只能执行默认动作。
kill 函数
#include
#include
int kill(pid_t pid, int sig);
作用:给任何的进程或者进程组发送任何的信号(sig)
参数:
---- pid:
-------- > 0:将信号发送给指定的进程
-------- = 0:将信号发送给当前的进程组
-------- = -1:将信号发送给每一个有权限接受这个信号的进程
-------- < -1:这个 pid 就是某个进程组的 ID 的相反数,给这个进程组发送信号
------- sig:需要发送的信号的编号或者是宏值,0 表示不发送任何信号
例子:
#include
#include
#include
#include
int main()
{
pid_t pid = fork();
if (pid == 0) {
//子进程
for (int i = 0; i < 5; ++i) {
printf("child \n");
sleep(1);
}
}
else if (pid > 0) {
//父进程
printf("parent\n");
sleep(2);
printf("kill child\n");
kill(pid, 9);
}
return 0;
}
raise 函数
#include
int raise(int sig);
作用:给当前进程发送信号
参数:要发送的信号
返回:成功返回 0,失败返回非 0
等价于:kill(getpid(), sig);
abort 函数
#include
void abort(void);
作用:发送 SIGABORT 信号给当前进程,杀死当前进程
等价于:kill(getpid(), SIGABORT);
alarm 函数
#include
unsigned int alarm(unsigned int seconds);
作用:设置定时器(闹钟),函数调用开始倒计时,当倒计时为 0 的时候,函数会给当前的进程发送一个信号:SIGALRM 默认终止当前的进程,每一个进程都有且只有唯一的一个定时器。
参数:倒计时的时长,单位是秒,参数为 0 表示定时器无效(不进行倒计时,不发信号)。可以通过传递 0 参数来取消一个定时器。
返回:
---- 之前没有定时器:返回 0
---- 之前有定时器:返回之前的定时器倒计时剩余时间
alarm 与进程的状态(运行、阻塞等)无关。
首先设置一个 5 秒的定时器,之后修改为 10 秒,过了 10 秒时候发送 SIGALRM 信号默认关闭进程:
#include
#include
int main()
{
int seconds = alarm(5);
printf("seconds = %d\n", seconds);
sleep(2);
seconds = alarm(10);
printf("seconds = %d\n", seconds);
while (1) {}
return 0;
}
#include
int setitimer(int which, const struct itimerval *new_val, struct itimerval *old_value);
作用:设置定时器,可以替代 alarm 函数。精度是微秒(us)。
参数:
---- which:定时器以什么时间定时
-------- ITIMER_REAL:真实时间,时间到达后发送 SIGALRM ,是最常用的
-------- ITIMER_VIRTUAL:用户时间,时间到达后发送 SIGVTALRM
-------- ITIMER_PROF:以该进程在用户态和内核态下所消耗的时间来计算,时间到达后发送 SIGPROF
---- new_val:设置定时器的属性
struct itimerval { //定时器结构体
struct timeval it_interval; //每个阶段的时间,间隔时间
struct timeval it_value; //延迟多长时间执行定时器
};
struct timeval { //时间的结构体
time_t tv_sec; //秒数
suseconds_t tv_usec; //微秒
};
---- old_value:记录上一次的定时的时间参数,一般不使用,指定 NULL
返回:成功返回 0,失败返回 -1
#include
sighandler_t signal(int signum, sighandler_t handler);
作用:设置某个信号的捕捉行为
参数:
---- signum:要捕捉的信号
---- handler:捕捉到信号要如何处理
-------- SIG_IGN:忽略信号
-------- SIG_DFL:使用信号默认的行为
-------- 回调函数:函数指针类型,由内核调用,程序员只负责提前写好捕捉到信号后如何处理信号
返回:
---- 成功:返回上一次注册的信号处理函数的地址。第一次调用返回 NULL
---- 失败:返回 SIG_ERR,设置错误号
过3秒之后定时开始,每隔2秒钟定时一次的示例:
#include
#include
#include
#include
void myalarm(int num)
{
printf("捕捉到了的信号的编号是:%d\n", num);
printf("xxxxxx\n");
}
int main()
{
//注册信号捕捉
__sighandler_t ret0 = signal(SIGALRM, myalarm);
if (ret0 == SIG_ERR) {
perror("signal");
exit(0);
}
struct itimerval new_value;
//设置间隔时间
new_value.it_interval.tv_sec = 2;
new_value.it_interval.tv_usec = 0;
//设置延迟时间
new_value.it_value.tv_sec = 3;
new_value.it_value.tv_usec = 0;
int ret = setitimer(ITIMER_REAL, &new_value, NULL);
printf("定时器开始了\n");
if (ret == -1) {
perror("setitimer");
exit(0);
}
getchar();
return 0;
}
程序三秒之后输出“定时器开始了”,然后每隔两秒输出后面的内容
阻塞信号集和未决信号集:
用户通过键盘 Ctrl + C, 产生 2 号信号 SIGINT (信号被创建)
信号产生但是没有被处理 (未决)
---- 在内核中将所有的没有被处理的信号存储在一个集合中 (未决信号集)
---- SIGINT 信号(2 号信号)状态被存储在第二个标志位上
---- 如果这个标志位的值为 0, 说明信号不是未决状态
---- 如果这个标志位的值为 1, 说明信号处于未决状态
这个未决状态的信号,需要被处理,处理之前需要和另一个信号集(阻塞信号集),进行比较
---- 阻塞信号集默认不阻塞任何的信号
---- 如果想要阻塞某些信号需要用户调用系统的 API
在处理的时候和阻塞信号集中的标志位进行查询,看是不是对该信号设置阻塞了
---- 如果没有阻塞,这个信号就被处理
---- 如果阻塞了,这个信号就继续处于未决状态,直到阻塞解除,这个信号就被处理
以下信号集相关的函数都是对自定义的信号集进行操作:
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
#include
int main()
{
//创建一个信号集
sigset_t set;
//清空信号集的内容
sigemptyset(&set);
//判断 SIGINT 是否在信号集 set 里
int ret = sigismember(&set, SIGINT);
if (ret == 0) {
printf("SIGINT不阻塞\n");
}
else if (ret == 1) {
printf("SIGINT阻塞\n");
}
//添加信号到信号集中
sigaddset(&set, SIGINT);
ret = sigismember(&set, SIGINT);
if (ret == 0) {
printf("SIGINT不阻塞\n");
}
else if (ret == 1) {
printf("SIGINT阻塞\n");
}
//从信号集中删除一个信号
sigdelset(&set, SIGINT);
ret = sigismember(&set, SIGINT);
if (ret == 0) {
printf("SIGINT不阻塞\n");
}
else if (ret == 1) {
printf("SIGINT阻塞\n");
}
return 0;
}
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
作用:将自定义信号集中的数据设置到内核中(设置阻塞,解除阻塞,替换)
参数:
---- how :如何对内核阻塞信号集进行处理
-------- SIG_BLOCK::将用户设置的阻塞信号集添加到内核中,内核中原来的数据不变,mask | set
-------- SIG_UNBLOCK::根据用户设置的数据,对内核中的数据进行解除阻塞 mask &= ~set
-------- SIG_SETMASK:覆盖内核中原来的值
---- set :已经初始化好的用户自定义的信号集
---- oldset : 保存设置之前的内核中的阻塞信号集的状态,可以是 NULL
返回:成功返回 0,失败返回 -1 并设置错误号 EFAULT、EINVAL
SIGKILL 和 SIGSTOP 不能被捕捉,不能被忽略。