抱歉,您的浏览器无法访问本站

本页面需要浏览器支持(启用)JavaScript


了解详情 >

信号概述

信号是 Linux进程间通信的最古老的方式之一,是事件发生时对进程的通知机制,有时也称之为软件中断,它是在软件层次上对中断机制的一种模拟,是一种异步通信的方式。信号可以导致一个丘在运行的进程被另一个正在运行的异步进程中断,转而处理某一个突发事件。

image-20220518150433415

发往进程的诸多信号,通常都是源于内核。引发内核为进程产生信号的各类事件如下:

  • 对于前台进程,用户可以通过输入特殊的终端字符来给它发送信号。比如输入Ctrl+C(9号信号)通常会给进程发送一个中断信号。
  • 硬件发生异常,即硬件检测到一个错误条件并通知内核,随即再由内核发送相应信号给相关进程。比如执行一条异常的机器语言指令,诸如被О除,或者引用了无法访问的内存区域
  • 系统状态变化,比如 alarm 定时器到期将引起 SIGALRM 信号,进程执行的CPU时间超限,或者该进程的某个子进程退出。
  • 运行 kill 命令或调用 kill 函数。

使用信号的两个主要目的是:

  • 让进程知道已经发生了一个特定的事情。
  • 强迫进程执行它自己代码中的信号处理程序。

信号的特点:

  • 简单
  • 不能携带大量的信息
  • 满足某个特定条件才能发送
  • 优先级比高

查看系统定义的信号列表:kill -l

前31个信号为常规信号,其余为实时信号。

信号一览表

编号 信号名称 对应事件 默认动作
1 SIGHUP 用户退出shell时,由该shell启动的所有进程将 收到这个信号 终止进程
2 SIGINT 当用户按下了组合键时,用户终端向正 在运行中的由该终端启动的程序发出此信号(CTRL + C) 终止进程
3 SIGQUIT 户按下组合键时产生该信号,用户终 端向正在运行中的由该终端启动的程序发出些信号 (CTRL + \) 终止进程
4 SIGILL CPU检测到某进程执行了非法指令 终止进程并产生core文件
5 SIGTRAP 该信号由断点指令或其他 trap指令产生 终止进程并产生core文件
6 SIGABRT 调用abort函数时产生该信号 终止进程并产生core文件
7 SIGBUS 非法访问内存地址,包括内存对齐出错 终止进程并产生core文件
8 SIGFPE 在发生致命的运算错误时发出。不仅包括浮点运算 错误,还包括溢出及除数为0等所有的算法错误 终止进程并产生core文件
9 SIGKILL 无条件终止进程。该信号不能被忽略,处理和阻塞 终止进程,可以杀死任何进程
10 SIGUSE1 用户定义的信号。即程序员可以在程序中定义并使用该信号 终止进程
11 SIGSEGV 指示进程进行了无效内存访问(段错误) 终止进程并产生core文件
12 SIGUSR2 另外一个用户自定义信号,程序员可以在程序中定义并使用该信号 终止进程
13 SIGPIPE Broken pipe向一个没有读端的管道写数据 终止进程
14 SIGALRM 定时器超时,超时的时间 由系统调用alarm设置 终止进程
15 SIGTERM 程序结束信号,与SIGKILL不同的是,该信号可以被阻塞和终止。通常用来要示程序正常退出。执行shell命令Kill时,缺省产生这个信号 终止进程
16 SIGSTKFLT Linux早期版本出现的信号,现仍保留向后兼容 终止进程
17 SIGCHLD 子进程结束时,父进程会收到这个信号 忽略这个信号
18 SIGCONT 如果进程已停止,则使其继续运行 继续/忽略
19 SIGSTOP 停止进程的执行。信号不能被忽略,处理和阻塞 为终止进程
20 SIGTSTP 停止终端交互进程的运行。按下<ctrl+z>组合键时发出这个信号 暂停进程
21 SIGTTIN 后台进程读终端控制台 暂停进程
22 SIGTTOU 该信号类似于SIGTTIN,在后台进程要向终端输出数据时发生 暂停进程
23 SIGURG 套接字上有紧急数据时,向当前正在运行的进程发出些信号,报 告有紧急数据到达。如网络带外数据到达 忽略该信号
24 SIGXCPU 进程执行时间超过了分配给该进程的CPU时间,系统产生该信号并发送给该进程 终止进程
25 SIGXFSZ 超过文件的最大长度设置 终止进程
26 SIGVTALRM 虚拟时钟超时时产生该信号。类似于SIGALRM,但是该信号只计算该进程占用CPU的使用时间 终止进程
27 SGIPROF 类似于SIGVTALRM,它不公包括该进程占用CPU时间还包括执行系统调用时间 终止进程
28 SIGWINCH 窗口变化大小时发出 忽略该信号
29 SIGIO 此信号向进程指示发出了一个异步IO事件 忽略该信号
30 SIGPWR 关机 终止进程
31 SIGSYS 无效的系统调用 终止进程并产生core文件
34 ~64 SIGRTMIN ~ SIGRTMAX LINUX的实时信号,它们没有固定的含义(可以由用户自定义) 终止进程

信号的 5 种默认处理动作

查看信号的详细信息:man 7 signal

信号的 5 中默认处理动作

  • Term 终止进程

  • Ign 当前进程忽略掉这个信号

  • Core 终止进程,并生成一个Core文件

  • Stop 暂停当前进程

  • Cont 继续执行当前被暂停的进程

信号的几种状态:产生、未决、递达

SIGKILL 和 SIGSTOP 信号不能被捕捉、阻塞或者忽略,只能执行默认动作。

core

0代表不会生成core文件 图2为设置core文件

image-20220518170911694

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/*************************************************************************
> File Name: kill.c
> Author: 秃头王
> Mail: 1658339000@qq.com
> Created Time: 2022年05月18日 星期三 18时39分29秒
************************************************************************/
// 这个程序的作用是测试生成文件
#include <stdio.h>
#include <string.h>

int main() {
char *buf;
strcpy(buf, "hello");
return 0;
}

kill、raise、abort函数

因为会抢占cpu的资源不确定执行几次

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
/*************************************************************************
> File Name: kill.c
> Author: 秃头王
> Mail: 1658339000@qq.com
> Created Time: 2022年05月18日 星期三 18时39分29秒
************************************************************************/


/*
* #include <sys/types.h>
* #include <signal.h>
* int kill(pid_t pid, int sig);
* - 功能: 给任何的进程或者进程组pid, 发送某个信号 sig
* - 参数:
* - pid : 需要发送给的进程的id
* > 0 : 将信号发送给指定的进程
* = 0 : 将信号发送给当前的进程组
* = -1 : 表示将信号发送给每一个有权限的接受这个信号的进程
* < -1 : 这个pid等于某个进程组的ID取反(1234) -> (-1234)
* - sig : 需要发送的信号的编号或者宏值,如果是0的话不发送任何信号
* kill(getppid(), 9);
* kill(getpid(), 9);
*
* int raise(int sig);
* - 功能: 给当前进程发送信号
* - 参数:
* - sug : 要发送的信号
* - 返回值;
* - 成功 0
* - 失败 非 0
* kill(getpid(), sig);
*
* void abort(void);
* - 功能: 发送SIGABRT信号给当前进程,杀死当前进程
* kill(getpid(), SIGABRT);
*/

#include <stdio.h>
#include <sys/types.h>
#include <signal.h>
#include <unistd.h>


int main() {

pid_t pid = fork();
if(pid == 0) {
// 子
for(int i = 0; i < 5; i++) {
printf("child process\n");
sleep(1);
}
} else if(pid > 0) {
// 父
printf("parent process\n");
sleep(2);
printf("kill child process now\n");
// 父进程返回是子进程的信号
kill(pid, SIGINT);
}

return 0;
}

alarm 函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
/*************************************************************************
> File Name: alarm.c
> Author: 秃头王
> Mail: 1658339000@qq.com
> Created Time: 2022年05月19日 星期四 12时59分47秒
************************************************************************/

/*
* #include <unistd.h>
* unisgned int alarm(unsigned int seconds);
* - 功能: 设置定时器 (闹钟),函数调用,开始倒计时,倒计时为0的时候函数会给当前的 进程发送一个信号:SIGALARM
* - 参数:
* seconds: 倒计时的时长, 单位: 秒。 如果参数为0, 定时器无效(不进行倒计时不发信号)
* 取消一个定时器,通过alarm(0);
* - 返回值: 倒计时剩余的时间
* - 之前没有定时器, 返回0
* - 之前有定时器, 返回之前的定时器剩余时间
* -SIGALARM:默认终止当前的进程, 每一个进程都有且只有唯一的一个定时器
* alarm(10); -> 返回 0
* 过了1秒 - 再定义一个
* alarm(5); -> 返回9
* alarm(100) -> 该函数是不阻塞的
*
*/

#include <stdio.h>
#include <unistd.h>

int main() {

int seconds = alarm(5);
printf("seconds = %d \n", seconds); // 0

sleep(2);

seconds = alarm(2);
printf("seconds = %d \n", seconds); // 3

while(1) {

}

return 0;
}

计算机一秒中电脑能数多少个数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/*************************************************************************
> File Name: alarm_One.c
> Author: 秃头王
> Mail: 1658339000@qq.com
> Created Time: 2022年05月19日 星期四 18时37分11秒
************************************************************************/

/*
* 实际时间 = 内核时间 + 用户时间 + 操作IO消耗的时间
* 进行文件IO操作的时间比较浪费时间
*
* 定时器,与进程的状态无关 (自然定时法)。无论进程处于什么状态,alarm都会计时
*
*/

#include <stdio.h>
#include <unistd.h>

int main() {
alarm(1); // 系统调用 -> 内核

int i = 0; // 内核 -> 用户区
while(1) {
printf("%d\n", i++);
}
return 0;
}

建议 g++ xxx.c ./a.out >> a.txt

setitimer 定时器函数 - 周期

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
/*************************************************************************
> File Name: setitimer.c
> Author: 秃头王
> Mail: 1658339000@qq.com
> Created Time: 2022年05月19日 星期四 19时32分31秒
************************************************************************/

/*
* #include <sys/time.h>
* int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value);
* - 功能: 设置定时器(闹钟)。可以代替alarm函数。精度微秒us, 可以实现周期性定时
* - 参数:
* - which: 定时器可以什么时间计时
* ITIMER_REAL: 真实时间,时间到达, 发送 SIGALRM 常用
* ITIMER_VIRTUAL: 用户时间,时间到达,发送SIGVTALRM
* ITIMER_PROF: 以该进程在用户态下所消耗的时间来计算,时间到达, 发送SIGPROF
* - new_value:
* // 定时器结构体
* struct itimerval {
* // 每个阶段的时间,间隔时间
* struct timeval it_intercal;
* // 延迟多长时间执行定时器
* struct timeval it_value;
* };
*
* // 时间结构体
* struct timeval {
* // 秒数
* time_t tv_sec;
* // 微妙
* suseconds_t tv_usec;
* };
* 过10(it_value)秒后,每隔2(it_intercal)秒定时一次
* - old_value: 记录上一次的定时的时间参数一般不使用,指定NULL
*
* - 返回值
* 成功 0
* 失败 -1 并设置错误号
*/

#include <stdio.h>
#include <sys/time.h>
#include <stdlib.h>

// 过3秒以后,每2秒钟定时一次
int main() {

struct itimerval new_value;
new_value.it_interval.tv_sec = 2;
// 微秒不设置就是随机的
new_value.it_interval.tv_usec = 0;

// 设置延迟的时间, 3秒之后开始第一次定时
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(-1);
}

getchar();

return 0;
}

signal 信号捕捉函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
/*************************************************************************
> File Name: signal.c
> Author: 秃头王
> Mail: 1658339000@qq.com
> Created Time: 2022年05月24日 星期二 09时34分06秒
************************************************************************/

/*
* #include <signal.h>
* typedef void (*sighandler_t)(int);
* sighandler_t signal(int signum, sighandler_t handler);
* - 功能: 设置某个信号的捕捉行为
* - 参数:
* - signum: 要捕捉的信号
* - handler 捕捉的信号要如何处理
* - SIG_IGN: 忽略信号
* - SIG_DFL: 使用信号默认的行为
* - 回调函数: 这个函数是内核调用的(系统), 程序负责写,捕获到信号后如何去处理信号。
* 回调函数:
* - 需要程序员实现,提前准好的,函数的类型根据实际需求,看函数指针的定义
* - 不是程序员调用,而是信号产生由内核调用
* - 函数指针是是现实函数回调的手段,函数实现之后,将函数名 放到函数指针的位置就可以了
* - 返回值:
* 成功 返回上一次注册的信号处理函数的地址。第一次调用返回NULL
* 失败 返回SIG_ERR,设置错误号
* SIGKILL(9号信号) SIGSTOP不能被捕捉,不能被忽略
*/


#include <stdio.h>
#include <sys/time.h>
#include <stdlib.h>
#include <signal.h>

void myalarm(int num) {
printf("捕捉到了信号的编号是: %d \n", num);
printf("xxxxxx\n");
return ;
}

// 过3秒以后,每2秒钟定时一次
int main() {

// 注册信号捕捉
// signal(SIGALRM, SIG_IGN);
// signal(SIGALRM, SIG_DFL);

// void (*sighandler_t)(int); 函数指针,int类型的参数表示捕捉的到的信号 值。
signal(SIGALRM, myalarm);

struct itimerval new_value;
new_value.it_interval.tv_sec = 2;
// 微秒不设置就是随机的
new_value.it_interval.tv_usec = 0;

// 设置延迟的时间, 3秒之后开始第一次定时
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(-1);
}

getchar();

return 0;
}

信号集

许多信号相关的系统调用都需要能表示一组不同的信号,多个信号可使用一个称之为 信号集的数据结构来表示,其系统数据类型为 sigset_t。

在 PCB 中有两个非常重要的信号集。一个称之为 “阻塞信号集” ,另一个称之为 “未决信号集” 。这两个信号集都是内核使用位图机制来实现的。但操作系统不允许我 们直接对这两个信号集进行位操作。而需自定义另外一个集合,借助信号集操作函数 来对 PCB 中的这两个信号集进行修改。

信号的 “未决” 是一种状态,指的是从信号的产生到信号被处理前的这一段时间。

信号的 “阻塞” 是一个开关动作,指的是阻止信号被处理,但不是阻止信号产生。

信号的阻塞就是让系统暂时保留信号留待以后发送。由于另外有办法让系统忽略信号, 所以一般情况下信号的阻塞只是暂时的,只是为了防止信号打断敏感的操作。

阻塞信号集和未决信号

未决信号集:信号没被处理之前都会在未决信号集

阻塞信号集:我们需要对某几个信号进行阻塞

image-20220524151141467

阻塞信号集和未决信号集

1.用户通过键盘 Ctrl + C, 产生2号信号SIGINT (信号被创建)

2.信号产生但是没有被处理 (未决)
- 在内核中将所有的没有被处理的信号存储在一个集合中 (未决信号集)
- SIGINT信号状态被存储在第二个标志位上
- 这个标志位的值为0, 说明信号不是未决状态
- 这个标志位的值为1, 说明信号处于未决状态

3.这个未决状态的信号,需要被处理,处理之前需要和另一个信号集(阻塞信号集),进行比较
- 阻塞信号集默认不阻塞任何的信号
- 如果想要阻塞某些信号需要用户调用系统的API

4.在处理的时候和阻塞信号集中的标志位进行查询,看是不是对该信号设置阻塞了

  • 如果没有阻塞,这个信号就被处理
    • 如果阻塞了,这个信号就继续处于未决状态,直到阻塞解除,这个信号就被处理

号集相关的函数 - 只对自定义的有效

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
以下信号集相关的函数都是对自定义的信号集进行操作
int sigemptyset(sigset_t *set);
- 功能: 清空信号集中的数据, 将信号集中的所有的标志位置 清空 - 0
- 参数: set,传出参数,需要操作的信号集
- 返回值:
- 成功返回 0
- 失败返回 -1
int sigfillset(sigset_t *set);
- 功能: 将信号集中的所有的标志位置为 1
- 参数: set,传出参数,需要操作的信号集
- 返回值:
- 成功返回 0
- 失败返回 -1
int sigaddset(sigset_t *set, int signum);
- 功能: 向信号集中的某一个信号对应标志位为1,表示阻塞这个信号
- 参数:
- set: 传出参数,需要操作的信号集
- signum: 需要设置阻塞的那个信号
- 返回值:
- 成功返回 0
- 失败返回 -1
int sigdelset(sigset_t *set, int signum);
- 功能: 向信号集中的某一个信号对应标志位为0,表示不阻塞这个信号
- 参数:
- set: 传出参数,需要操作的信号集
- signum: 需要设置不阻塞的那个信号
- 返回值:
- 成功返回 0
- 失败返回 -1
int sigismember(const sigset_t *set, int signum);
- 功能: 判断某个信号是否阻塞
- 参数:
- set: 需要操作的信号集
- signum: 需要判断的那个信号
- 返回值:
- 1 signum 被阻塞
- 0 signum 不阻塞
- -1 signum 调用失败

/***************下面就是系统的API可以操作内核***************/
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
int sigpending(sigset_t *set);

sigset - 设置未决信号集、阻塞信号集

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
/*************************************************************************
> File Name: sigset.c
> Author: 秃头王
> Mail: 1658339000@qq.com
> Created Time: 2022年05月24日 星期二 17时25分15秒
************************************************************************/

#include <stdio.h>
#include <signal.h>

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");
} else if(ret == -1) {
printf("调用失败\n");
}

// 添加几个信号到信号集中
sigaddset(&set, SIGINT);
sigaddset(&set, SIGQUIT);

ret = sigismember(&set, SIGINT);
if(ret == 0) {
printf("SIGINT 不阻塞\n");
} else if(ret == 1) {
printf("SIGINT 阻塞\n");
} else if(ret == -1) {
printf("调用失败\n");
}

ret = sigismember(&set, SIGQUIT);
if(ret == 0) {
printf("SIGQUIT 不阻塞\n");
} else if(ret == 1) {
printf("SIGQUIT 阻塞\n");
} else if(ret == -1) {
printf("调用失败\n");
}

// 从信号集中删除一个
sigdelset(&set, SIGQUIT);
ret = sigismember(&set, SIGQUIT);
if(ret == 0) {
printf("SIGQUIT 不阻塞\n");
} else if(ret == 1) {
printf("SIGQUIT 阻塞\n");
} else if(ret == -1) {
printf("调用失败\n");
}

return 0;
}

sigprocmask - 阻塞信号集 - 内核

image-20220525145910615

所有的常规信号 - 代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
/*************************************************************************
> File Name: sigprocmask.c
> Author: 秃头王
> Mail: 1658339000@qq.com
> Created Time: 2022年05月24日 星期二 18时42分13秒
************************************************************************/

/*
sigset_t == long int
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
- 功能: 将自定义信号集中的数据设置到内核中 (设置阻塞、解除阻塞、替换)
- 参数:
- how: 如何对内核阻塞信号进程处理

SIG_BLOCK: 将用户设置的阻塞信号集添加 到内核中,内核中原来的数据不变
列:
信号集 - mask
1 0 1 0 0 0 0
sigset_t - set
| 0 1 0 0 0 0 0
------------------
1 1 1 0 0 0 0
假设内核中默认的阻塞信号集是 mask, mask | set

SIG_UNBLOCK: 根据用户设置的数据,对内核中的数据进行解除阻塞
mask &= ~set;

SIG_SETMASK: 覆盖内核中原来的值

- set: 已经初始化好的用户自定义的信号集
- oldset: 保存设置之前的内核中的阻塞的信号集的状态, 可以是 NULL

- 返回值:
成功返回: 0
失败返回: -1
并设置错误号: EFAULF、EINVAL

int sigpending(sigset_t *set);
- 功能: 获取内核中的未决信号集
- 参数: set, 传出参数, 保存的是内核中的未决信号集中的 信息
*/

#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>

// 所有的常规信号 kill -l (1 - 31) 未决状态打印到屏幕
// 设置某些信号是阻塞的,通过键盘产生这些信号
int main() {

// 设置2、3信号阻塞 SIGINT(CTRL + C)、SIGQUIT(CTRL + \)
sigset_t set;
sigemptyset(&set);
// 将2号和3号信号添加到信号集中
sigaddset(&set, SIGINT);
sigaddset(&set, SIGQUIT);

// 修改内核中的阻塞信号集
sigprocmask(SIG_BLOCK, &set, NULL);
int cnt = 0;
while(1) {
cnt++;
// 获取当前的未决信号集的数据
sigset_t pendingset;
sigemptyset(&pendingset);
sigpending(&pendingset);
// 遍历前31位
for(int i = 1; i <= 31; i++) {
if(sigismember(&pendingset, i) == 1) {
printf("1");
}else if(sigismember(&pendingset, i) == 0) {
printf("0");
} else {
printf("sigismember");
exit(0);
}
}
printf("\n");
sleep(1);
if(cnt == 10) {
// 接触阻塞
sigprocmask(SIG_UNBLOCK, &set, NULL);
}
}

return 0;
}

前台进程和后台进程

run sigprocmask.c 直接运行程序

CTRL + z 也只是让程序后台运行


run sigprocmask.c & // 让程序在后台运行

fg // 切换到前台

sigaction信号捕捉

尽量使用 sigaction

因为标准不一样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
/*************************************************************************
> File Name: sigaction.c
> Author: 秃头王
> Mail: 1658339000@qq.com
> Created Time: 2022年05月25日 星期三 11时32分35秒
************************************************************************/

/*
* #include <signal.h>
* int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
* - 功能: 检查或者改变信号的处理,信号捕捉
* - 参数:
* - signum: 需要捕捉信号的编号或者宏值,不能捕捉的信号(SIGKILL and SIGSTOP)
* - act: 捕捉信号之后的处理动作
* - oldact: 上一次对信号的捕捉相关的设置,一般补使用,传递NULL
* - 返回值:
* 成功 0
* 失败 -1
*
* struct sigaction {
* 函数指针, 指向的函数就是信号捕捉到之后的处理函数
* void (*sa_handler)(int);
*
* 不常用 - 信号的编号, 信号的相关的信息, 信号的相关的信息
* void (*sa_sigaction)(int, siginfo_t *, void *);
*
* 临时阻塞信号集,信号捕捉函数执行过程中, 临时阻塞某些信号
* sigset_t sa_mask;
*
* 使用那一个信号处理对捕捉到的信号进行处理
* 这个值 0 表示使用sa_handler
* 也可以是 SA_SIGINFO 表示使用 sa_sigaction
* int sa_flags;
*
* 呗废弃掉了
* void (*sa_restorer)(void);
* };
*/

#include <stdio.h>
#include <sys/time.h>
#include <signal.h>
#include <stdlib.h>

void myalarm(int num) {
printf("捕获的信号的编号是: %d \n", num);
printf("xxxxxxx\n");
return ;
}

int main() {

struct sigaction act;
act.sa_flags = 0;
act.sa_handler = myalarm;

// 清空临时信号集
sigemptyset(&act.sa_mask);

// 注册信号捕捉
sigaction(SIGALRM, &act, NULL);

struct itimerval new_value;

// 设置间隔时间
new_value.it_interval.tv_sec = 2;
new_value.it_interval.tv_usec = 0;

// 设置延迟的时间, 3妙之后开始第一次定时
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(-1);
}
// getchar();
while(1);
return 0;
}

内核实现信号捕捉的过程

image-20220525145417017

SIGCHLD - 解决僵尸进程

SIGCHLD信号产生的条件

  • 子进程终止时
  • 子进程接收到 SIGSTOP 信号停止时
  • 子进程处在停止态,接受到SIGCONT后唤醒时

以上三种条件都会给父进程发送 SIGCHLD 信号, 父进程默认会忽略该信号

创建20个子进程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
/*************************************************************************
> File Name: sigchld.c
> Author: 秃头王
> Mail: 1658339000@qq.com
> Created Time: 2022年05月25日 星期三 17时19分37秒
************************************************************************/

/*
* SIGCHLD信号产生的3个条件:
* 1.子进程结束
* 2.子进程暂停了
* 3.子进程继续运行
* 都会给父进程发送该信号,父进程默认忽略该信号
* 使用SIGCHLD信号解决僵尸进程的问题
*/


#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
int main() {

// 创建一些子进程
pid_t pid;
for(int i = 0; i < 20; i++) {
pid = fork();
if(pid == 0) {
// 子
break;
}
}

if(pid > 0) {
// 父
while(1) {
printf("parent process pid : %d \n", getpid());
sleep(2);
}
} else if(pid == 0) {
// 子
printf("child process pid : %d \n", getpid());
}

return 0;
}

父进程结束子进程就死了 wait、waitpid 一次只能回收一个子进程 点我带你去看wait - waitpid

image-20220525173913588

段错误(核心内存以转换) - 代码清除僵尸进程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
/*************************************************************************
> File Name: sigchld.c
> Author: 秃头王
> Mail: 1658339000@qq.com
> Created Time: 2022年05月25日 星期三 17时19分37秒
************************************************************************/

/*
* SIGCHLD信号产生的3个条件:
* 1.子进程结束
* 2.子进程暂停了
* 3.子进程继续运行
* 都会给父进程发送该信号,父进程默认忽略该信号
* 使用SIGCHLD信号解决僵尸进程的问题
*/


#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>

void myFun(int num) {
printf("捕捉到的信号 : %d\n", num);
// 回收子进程PCB的资源
/*
while(1) {
wait(NULL);
}
*/
while(1) {
int ret = waitpid(-1, NULL, WNOHANG);
if(ret > 0) {
printf("child die , pid = %d\n", ret);
} else if(ret == 0) {
// printf("还有子进程活着\n");
break;
} else if(ret == -1) {
// 没有子进程了
break;
}
}
return ;
}

int main() {

// 创建一些子进程
pid_t pid;
for(int i = 0; i < 20; i++) {
pid = fork();
if(pid == 0) {
// 子
break;
}
}

if(pid > 0) {
// 父

// 捕捉子进程死亡时发送的SIGCHLD信号
struct sigaction act;
act.sa_flags = 0;
act.sa_handler = myFun;
sigemptyset(&act.sa_mask);
sigaction(SIGCHLD, &act, NULL);

while(1) {
printf("parent process pid : %d \n", getpid());
sleep(2);
}
} else if(pid == 0) {
// 子
printf("child process pid : %d \n", getpid());
}

return 0;
}

image-20220526094900263

sigaction(SIGCHLD, &act, NULL); 信号捕捉

信号捕捉还没有去注册成功 子进程就结束了

完了再去信号捕捉 就不能回收子进程了

回收改进

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
/*************************************************************************
> File Name: sigchld.c
> Author: 秃头王
> Mail: 1658339000@qq.com
> Created Time: 2022年05月25日 星期三 17时19分37秒
************************************************************************/

/*
* SIGCHLD信号产生的3个条件:
* 1.子进程结束
* 2.子进程暂停了
* 3.子进程继续运行
* 都会给父进程发送该信号,父进程默认忽略该信号
* 使用SIGCHLD信号解决僵尸进程的问题
*/


#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>

void myFun(int num) {
printf("捕捉到的信号 : %d\n", num);
// 回收子进程PCB的资源
/*
while(1) {
wait(NULL);
}
*/
while(1) {
int ret = waitpid(-1, NULL, WNOHANG);
if(ret > 0) {
printf("child die , pid = %d\n", ret);
} else if(ret == 0) {
// printf("还有子进程活着\n");
break;
} else if(ret == -1) {
// 没有子进程了
break;
}
}
return ;
}

int main() {
// 提前设置好阻塞信号集, 阻塞SIGCHLD,因为有可能子进程很快结束,父进程还没注册好信号捕捉
sigset_t set;
sigemptyset(&set);
sigaddset(&set, SIGCHLD);
sigprocmask(SIG_BLOCK, &set, NULL);

// 创建一些子进程
pid_t pid;
for(int i = 0; i < 20; i++) {
pid = fork();
if(pid == 0) {
// 子
break;
}
}

if(pid > 0) {
// 父

// 捕捉子进程死亡时发送的SIGCHLD信号
struct sigaction act;
act.sa_flags = 0;
act.sa_handler = myFun;
sigemptyset(&act.sa_mask);
sigaction(SIGCHLD, &act, NULL);

// 注册完信号捕捉以后,解除阻塞
sigprocmask(SIG_UNBLOCK, &set, NULL);

while(1) {
printf("parent process pid : %d \n", getpid());
sleep(2);
}
} else if(pid == 0) {
// 子
printf("child process pid : %d \n", getpid());
}

return 0;
}

image-20220526102642438

评论