嵌入式Linux系统编程-》IPC之信号+面试题【第3天:13685字】

在这里插入图片描述

第一节 标准信号处理

1. 信号基本概念

信号相对其他IPC而言,其最大的特点是所谓的异步性,异步的含义是事件的发生和处理没有事先协同,就像朋友来按门铃,是一种与屋内事件平行的异步事件,由于门铃的响起是异步的,因此屋内的人就无法将门铃与其余事件统筹安排,比如无法这样安排:

收拾房间
吃早餐
出门遛狗
给朋友开门
一起玩游戏

朋友可能在房间收拾完毕之前就已经在按门铃了,因此只能是在门铃响起之前做好所需的准备,这个我们即将要学习的信号处理如出一辙:

在这里插入图片描述

							信号与门铃

2. 标准信号处理

Linux/Unix系统下,信号总共分成两大类,一类是最常用的标准信号,另一类是后面引入的实时信号,可以使用如下命令查看:

# 查看当前系统所支持的所有信号
gec@ubuntu:~$ kill -l
 1) SIGHUP	 2) SIGINT	 3) SIGQUIT	 4) SIGILL	 5) SIGTRAP
 6) SIGABRT	 7) SIGBUS	 8) SIGFPE	 9) SIGKILL	10) SIGUSR1
11) SIGSEGV	12) SIGUSR2	13) SIGPIPE	14) SIGALRM	15) SIGTERM
16) SIGSTKFLT	17) SIGCHLD	18) SIGCONT	19) SIGSTOP	20) SIGTSTP
21) SIGTTIN	22) SIGTTOU	23) SIGURG	24) SIGXCPU	25) SIGXFSZ
26) SIGVTALRM	27) SIGPROF	28) SIGWINCH	29) SIGIO	30) SIGPWR
31) SIGSYS	34) SIGRTMIN	35) SIGRTMIN+1	36) SIGRTMIN+2	37) SIGRTMIN+3
38) SIGRTMIN+4	39) SIGRTMIN+5	40) SIGRTMIN+6	41) SIGRTMIN+7	42) SIGRTMIN+8
43) SIGRTMIN+9	44) SIGRTMIN+10	45) SIGRTMIN+11	46) SIGRTMIN+12	47) SIGRTMIN+13
48) SIGRTMIN+14	49) SIGRTMIN+15	50) SIGRTMAX-14	51) SIGRTMAX-13	52) SIGRTMAX-12
53) SIGRTMAX-11	54) SIGRTMAX-10	55) SIGRTMAX-9	56) SIGRTMAX-8	57) SIGRTMAX-7
58) SIGRTMAX-6	59) SIGRTMAX-5	60) SIGRTMAX-4	61) SIGRTMAX-3	62) SIGRTMAX-2
63) SIGRTMAX-1	64) SIGRTMAX	
gec@ubuntu:~$

标准信号:前31个信号,它们是从Unix系统继承下来的经典系统元素,他们有如下的特点:

不排队,信号的响应会相互嵌套。 如果目标进程没有及时响应,那么随后到达的相同信号将会被丢弃。
每个信号都对应一个系统事件(除了SIGUSR1和SIGUSR2),当这个事件发生时,将产生这个信号。

在进程的挂起信号中,进程会优先响应实时信号。

实时信号:后31个信号,它们是Linux系统新增的实时信号,也被称为“可靠信号”,这些信号的特征是:

实时信号的响应次序按接收顺序排队,不嵌套。 即使相同的实时信号被同时发送多次,也不会被丢弃,而会依次挨个响应。
实时信号没有特殊的系统事件与之对应。 下面以标准信号入手,详细了解信号的各种特性。

2.1 信号的生命周期

所谓信号的生命周期,指的是信号从产生到被响应完毕的整个过程,这个过程可被描述为:
在这里插入图片描述

信号的产生

信号既可以由特定的事件产生(比如发生了内存访问异常导致产生信号SIGSEGV),也可由用户主动发起(比如调用了kill()函数),不管是哪种方式产生的信号,其本质都是触发了内核的信号发生器,并向特定进程(即目标进程)递送的过程。

信号的挂起

每个进程都保留有一个挂起信号集,所有被发送到这个进程的信号首先被放入这个信号集,挂起信号集存储了进程的待处理信号,这些信号必须要等到进程被系统调度,真正执行的时候才能被进一步响应。

信号的响应

信号的响应总共有如下四种方式:

屏蔽:也称为阻塞,即延缓对信号的响应,直到解除对该信号的屏蔽为止。
捕捉:执行一个预先设置的与信号相关联的响应函数。
默认:按信号默认的情况处理。
忽略:直接丢弃该信号。

2.2 信号的产生

除了由特定事件触发的信号外,信号的产生均可由用户调用如下接口来主动触发:

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

int kill(pid_t pid, int sig);

其中:

pid是接收信号的进程PID,称为目标进程。 sig是信号的编号。
比如,下面的代码实现向指定PID的进程发送2号信号SIGINT(该信号就是

平时按ctrl+c产生的键盘中断信号:

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

int main(int argc, char **argv)
{
    if(argc == 2)
    	kill(atoi(argv[1]), SIGSEGV);
    
    return 0;
}

注意:

由于函数接口kill的两个参数都是整型数据,因此如果不小心写错了它们的顺序,编译器是无法区分的,这个细节在写代码的时候要分外注意。

当然,有时候我们会在终端中直接使用kill命令来发送信号:

# 向PID号为1234的进程发送2号信号:
gec@ubuntu:~$ kill -2 1234
# 向PID号为1234的进程发送信号SIGKILL:
gec@ubuntu:~$ kill -s SIGKILL 1234
# 向名称为example的所有进程发送信号SIGTERM:
gec@ubuntu:~$ killall example 

2.3 信号的响应方式

如前所述,信号的响应有四种方式,下面来详细分析。

2.3.1 信号的屏蔽

屏蔽信号实际上就是暂缓对信号的响应,采用如下函数进行对信号的屏蔽:

#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);

参数简析:

how:操作命令字,比如阻塞、解除阻塞等
set:当前要操作的信号集
oldset:若为非空,则将原有阻塞信号集保留到该oldset中

注意到,该函数的操作参数不是单个信号,而是信号集(sigset_t),这意味着我们可以同时对多个信号设置阻塞或解除阻塞。

// 信号集操作函数组
#include <signal.h>
int sigemptyset(sigset_t *set);   // 清空信号集set
int sigfillset(sigset_t *set);    // 将所有信号加入信号集set中
int sigaddset(sigset_t *set, int signum);         // 将信号signum添加到信号集set中
int sigdelset(sigset_t *set, int signum);         // 将信号signum从信号集set中剔除
int sigismember(const sigset_t *set, int signum); // 测试信号signum是否在信号集set中

另外,how是具体的操作命令字,可以有如下取值:

SIG_BLOCK:阻塞set中的信号(原有正在阻塞的信号保持阻塞)。
SIG_SETMASK:阻塞set中的信号(原有正在阻塞的信号自动解除)。
SIG_UNBLOCK:解除set中的信号。

例如,想要对1、2号信号进行阻塞操作,首先要将这两个信号添加到一个信号集中,操作接口如下:

// 将1、2号信号加入信号集
sigset_t sig;
sigemptyset(&sig);
sigaddset(&sig, SIGHUP); // 加入1号信号
sigaddset(&sig, SIGINT); // 加入2号信号
// 阻塞1、2号信号
setprocmask(SIG_SETMASK, &sig, NULL);
2.3.2 信号的捕捉

所谓信号的捕捉,实际就是在信号到达之前,给信号关联一个指定的响应函数,让其到达之后自动运行该函数。

给信号指定关联函数的接口是:

#include <signal.h>
void (*signal(int sig, void (*func)(int)))(int);

该函数接口比较复杂,下面是其返回值和参数详解:

返回值类型:void (*)(int);
返回值含义:返回一个指向原有的与指定信号关联的函数
参数:
sig: 指定要关联的信号
func:指定要关联的响应函数

注意到,使用这种方式关联的响应函数的接口是固定的,如下所示:

// 标准信号响应函数接口
void func(int sig)
{
    // ...
}

显然,func中的参数sig就是触发该响应函数的信号,以下示例代码展示了如何捕捉信号SIGINT:

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

void func(int sig)
{
    printf("捕获到信号:%d\n", sig);
}

int main(int argc, char **argv)
{
    // 指定信号SIGINT关联函数
    signal(SIGINT, func);

    // 持续响应信号
    while(1)
        pause();

    return 0;
}

提示:

函数pause会在信号响应结束后退出,为了让程序可以持续响应信号,上述程序将pause函数放在while循环中。
由于上述代码捕捉了SIGINT,因此按ctrl+c将无法中断程序,此时可以按ctrl+\(触发另一个信号SIGQUIT)来退出程序的无限循环。

2.3.3 信号的默认处理

如果程序没有对信号做任何预先准备,那么当信号达到时,则会按照信号的默认规则进行响应,具体默认规则可使用如下命令查阅:

gec@ubuntu:~$ man 7 signal
# 会得到类似如下的表格:
       ...
       Signal     Value     Action   Comment
       ──────────────────────────────────────────────────────────────────────
       SIGHUP        1       Term    Hangup detected on controlling terminal
                                     or death of controlling process
       SIGINT        2       Term    Interrupt from keyboard
       SIGQUIT       3       Core    Quit from keyboard
       SIGILL        4       Core    Illegal Instruction
       SIGABRT       6       Core    Abort signal from abort(3)
       SIGFPE        8       Core    Floating-point exception
       SIGKILL       9       Term    Kill signal
       SIGSEGV      11       Core    Invalid memory reference
       SIGPIPE      13       Term    Broken pipe: write to pipe with no
                                     readers; see pipe(7)
       ...

列表中的 Action 一列就是系统对信号的默认处理规则,m默认规则如下:

Term:中断目标进程。
Core:中断目标进程,且产生核心转储文件core。
Stop:暂停目标进程,直到收到信号SIGCONT
Cont:恢复目标进程运行
Ign:忽略信号

其中需要说明的是:

Term和Core都是中断程序,但Core处理方式还会产生转储文件core,core文件即程序在被中断的瞬间其内存映像的快照,用来给后续的调试提供追踪信息。但一般情况下系统是禁止生成所谓转储文件的,放开此项限制的命令是:

# 查看当前系统对 core 文件的限制
gec@ubuntu:~$ ulimit -a
core file size          (blocks, -c) 0  # core 文件大小被限制为0
data seg size           (kbytes, -d) unlimited
scheduling priority             (-e) 0
file size               (blocks, -f) unlimited
pending signals                 (-i) 7635
max locked memory       (kbytes, -l) 16384
max memory size         (kbytes, -m) unlimited
open files                      (-n) 1024
pipe size            (512 bytes, -p) 8
POSIX message queues     (bytes, -q) 819200
real-time priority              (-r) 0
stack size              (kbytes, -s) 8192
cpu time               (seconds, -t) unlimited
max user processes              (-u) 7635
virtual memory          (kbytes, -v) unlimited
file locks                      (-x) unlimited
gec@ubuntu:~$ 
# 将 core 文件的大小设置为“不限制”
gec@ubuntu:~$ ulimit -c unlimited

Ign是默认就会被忽略的信号,典型的例子是SIGCHLD,此信号是子进程在状态转变时(比如变成僵尸时)自动发给其父进程的信号。


SIGKILL和SIGSTOP这两个信号只能采取默认处理,不能阻塞、捕捉,也不能忽略。

2.3.4 忽略信号

忽略信号就是直接将收到的信号丢弃,做法如下:

int main()
{
    // 忽略信号SIGINT
    signal(SIGINT, SIG_IGN);
}

第二节 扩展信号处理

1. 问题起源

仔细观察标准信号的处理过程容易发现,其信号的发送和接收是无法传递额外信息的,即发送者除了可以将信号发送给接收者之外,无法传递其他信息,对于接收者而言,收到某个信号的时候也无法很方便地得知发送进程是哪个,更无法得知控制发送者用户的信息。

1.1 标准信号处理与扩展信号处理

上一节中的信号发送接口和信号响应函数接口是kill和signal,实际上除了这一对函数对应关系之外,还有扩展的配套函数:

graph LR
kill-->|信号值|signal
sigqueue-->|信号值以及其他信息|sigaction

1.2 标准信号处理

也就是说,这四个函数的两两配对的。当使用kill发送信号时,由于不会携带任何额外的信息,因此对方只需使用signal来处理相应的信号即可,使用signal关联的响应函数称为标准响应函数,它们的协作示例代码如下所示:

// 信号发送者程序sender.c
int main()
{
    kill(PID, SIGINT);
}
// 信号接收者程序receiver.c
void func(int sig)
{
    // sig 是触发本函数的信号值
}
int main()
{
    signal(SIGINT, func);
    pause();
}

1.3 扩展信号处理

倘若要在发送信号的同时携带别的信息,那么就得用另一套相应的函数:

// 信号发送者程序sender2.c
int main()
{
    union sigval val;
    val.sival_int = 100;

    // 发送一个携带额外信息的信号
    sigqueue(PID, SIGINT, val);
}
// 信号接收者程序receiver2.c
void func(int sig, siginfo_t *info, void *arg)
{
    // sig 是触发本函数的信号值
}
int main()
{
    struct sigaction act;
    bzero(&act);

    // 指定函数响应函数
    act.sa_sigaction = func;
    act.sa_flags |= SA_SIGINFO;

    sigaction(SIGINT, &act, NULL);
    pause();
}

2. 函数接口

2.1 sigqueue函数接口

该函数的功能与kill一样,都是向指定进程发送信号,但sigqueue在发送信号的同时会携带很多额外信息,其接口如下:

#include <signal.h>
int sigqueue(pid_t pid, int sig, const union sigval value);

该函数的前两个参数与kill完全一样,不再赘述。第三个参数是一个联合体:

union sigval
{
    int   sival_int;
    void *sival_ptr;
};

由此联合体可见,系统允许发送者发送一个自定义的整型数据或者一个指针,当然,如果接受者与发送者不在同一个进程内,那么发送地址是无效的,只有当一个进程内部的线程间发送信号时,发送的地址才有效。

2.2 sigaction函数接口

该函数的功能与signal一样,都可以为指定信号关联其响应函数,但sigaction除了可以指定标准响应函数外,还可以指定扩展响应函数,以便接受从sigqueue发来的携带额外参数的信号:

#include <signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);

上述函数接口中,第2、3个参数都是如下结构体(该结构体名称恰好与函数名称一样):

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:扩展响应函数指针
sa_mask:临时信号阻塞掩码
sa_flags:配置标签
sa_restorer:已废弃

关注点:

sa_handler和sa_sigaction一个指向标准函数,一个指向扩展函数,二者只能取其一。


sa_mask是一个信号集,里面所包含的信号将在本函数所关联的响应函数执行期间被临时阻塞。


sa_flags可以用来配置选择标准响应模式、扩展响应模式,具体而言,可通过如下设置来使能扩展模式:

sa_flags |= SA_SIGINFO;

sa_restorer是一个已废弃的接口,无需关注。

第三节 面试题

问:老师,一个进程收到信号时,是会跳转到signal语句地方去执行吗?

答:不,不会。这是很多同学的误解,比如下面这段简单的代码:

1   void func(int sig)
2   {
3       printf("收到信号%d\n", sig);
4   }
5
6   int main()
7   {
8       signal(SIGINT, func);
9
10      for(int i=0;;i++)
11      {
12          printf("%d\n", i);
13          sleep(1);
14      }
15  }

很多人没有认真理解signal函数的作用,把它错当做“接收信号”的函数,以为程序会在for循环中收到信号时往上跳到signal去执行相关响应函数,这个想法是错误的,signal函数只是对信号和函数func做了关联,做完之后就没他什么事了,程序也不可能会跳回到第8行。


程序之所以会在收到信号时跳转到函数func处,是因为内核对进程的执行流程做了中断处理,函数func是典型的回调函数,是由内核主动调用的。

编程实现两个程序,模拟智能公交的某两个功能模块程序:

信号发送者A,每次发信号SIGUSR1通知B时顺带发去当前乘客数 信号接收者B,每次收到SIGUSR1时读取当前乘客数,并暂停响应其他信号。
在这里插入图片描述

解答: 这是一个利用扩展信号接口的典型应用,注意要使用sigqueue和sigaction配合。代码如下:

// 信号发送者A
#include <stdio.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>

int main(int argc, char **argv)
{
    union sigval val;
    val.sival_int = 100;

    // 给指定进程B发送信号SIGUSR1
    sigqueue(atoi(argv[1]), SIGUSR1, val);

    return 0;
}
// 信号接受者B
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>

void func(int sig, siginfo_t *info, void *arg)
{
    printf("当前乘客数:%d\n", info->si_int);
}

int main(int argc, char **argv)
{
    struct sigaction act;
    bzero(&act, sizeof(act));

    act.sa_sigaction = func;
    act.sa_flags |= SA_SIGINFO;

    // 在响应SIGUSR1期间,临时阻塞其他所有信号
    sigfillset(&act.sa_mask);
    sigaction(SIGUSR1, &act, NULL);

    while(1)
        pause();

    return 0;
}

(信号API)

【1】编写两个程序,一个代表司机,一个代表售票员。 他们之间的交互如下:


售票员向司机发某信号,司机输出“开车”。 售票员向司机发某信号,司机输出“靠站”。
司机向售票员发某信号,售票员输出“到总站,全部下车”之后退出,同时司机也退出。
在这里插入图片描述
解答:
本题的难点在于司机和售票员双方需要互相知道对方的PID方可发送信号,由于它们不属于亲缘进程,因此可以使用具名管道互相通知彼此的进程PID,并且由于管道在默认情况下是阻塞的,那么在对方尚未启动的情况下恰好可以利用管道的阻塞特性来等待对方的启动。

示例代码如下:

// 司机:
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>

pid_t saler;

void operate(int sig)
{
	switch(sig)
	{
	case SIGUSR1:
		fprintf(stderr, "司机:好的,坐好扶稳出发!\n\n");
		break;
	case SIGUSR2:
		fprintf(stderr, "司机:好的, 停稳靠站了!\n\n");
		break;
	}
}

int main(void)
{
	mkfifo("/tmp/driverPid", 0666);
	mkfifo("/tmp/salerPid", 0666);

	int fd1 = open("/tmp/driverPid", O_CREAT|O_RDWR, 0666);
	int fd2 = open("/tmp/salerPid", O_RDWR);

	// 将自身PID写入管道
	pid_t myPid = getpid();
	write(fd1, &myPid, sizeof(myPid));

	printf("正在尝试读取售票员PID... ...\n");
	read(fd2, &saler, sizeof(saler));
	printf("读取售票员PID成功\n");

	signal(SIGUSR1, operate);
	signal(SIGUSR2, operate);

	int cmd;
	printf("按下1代表到总站,线路停运。\n");
	while(1)
	{
		scanf("%d", &cmd);

		if(cmd == 1)
		{
			kill(saler, SIGQUIT);
			printf("欢迎下次再次乘坐本趟公交!再见!\n");
			break;
		}
	}

	// 删除相关管道文件
	unlink("/tmp/driverPid");
	unlink("/tmp/salerPid");

	return 0;
}
// 售票员:
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>

pid_t driver;

void quit(int sig)
{
	printf("售票员:到总站了,都下车吧!\n");

	unlink("/tmp/driverPid");
	unlink("/tmp/salerPid");

	exit(0);
}

int main(void)
{
	mkfifo("/tmp/driverPid", 0666);
	mkfifo("/tmp/salerPid", 0666);

	int fd1 = open("/tmp/driverPid", O_CREAT|O_RDWR, 0666);
	int fd2 = open("/tmp/salerPid", O_RDWR);

	// 将自身PID写入管道
	pid_t myPid = getpid();
	write(fd2, &myPid, sizeof(myPid));

	printf("正在尝试读取司机PID... ...\n");
	read(fd1, &driver, sizeof(driver));
	printf("读取司机PID成功\n");

	signal(SIGQUIT, quit);

	int cmd;
	printf("按下1代表靠站停车,按2代表开始出发。\n");
	while(1)
	{
		scanf("%d", &cmd);

		if(cmd == 1)
			kill(driver, SIGUSR1);
		else if(cmd == 2)
			kill(driver, SIGUSR2);
	}

	return 0;
}

(信号API)

【2】用信号集相关函数,信号发送和注册函数等相关知识。 编写代码验证以下结论:

进程的信号挂起队列中,没有相同的信号(即相同的信号会被丢弃)。 进程在响应信号时,信号会相互嵌套。
挂起信号不会被子进程继承,但信号阻塞掩码会被继承。 解答: 分别解释上述三个结论:


当一个信号发给目标进程后,在进程处理该信号前会存储在系统的信号挂起队列中,若此时继续向进程发送相同的信号,由于信号是以信号集方式存储的,一个信号集中无法储存多个相同的信号,因此这些后续到达的信号将不起作用。验证代码如下:

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

void sighand(int sig)
{
	if(sig == SIGUSR1)
		printf("receive a SIGUSR1!\n");

	return;
}

int main(void)
{
	sigset_t sig;
	sigemptyset(&sig);
	sigaddset(&sig, SIGUSR1);

	signal(SIGUSR1, sighand);
	
	// block SIGUSR1
	sigprocmask(SIG_BLOCK, &sig, NULL);

	printf("my pid: %d, send me two SIGUSR1 in 20s.\n", getpid());
	int i = 20;
	while(i--)
	{
		printf("%d\n", i);
		sleep(1);
	}

    // 在解除信号阻塞掩码前的20秒内,持续向本进程发送SIGUSR1
    // 20秒后解除阻塞会发现信号只被触发一次,由此可证明本期结论
	sigprocmask(SIG_UNBLOCK, &sig, NULL);

	while(1)
		pause();

	return 0;
}

不同信号的响应函数可以相互嵌套,也就会说当响应一个信号尚未结束而又来了另一个信号时,进程会被迫去执行另一个信号的响应函数,以下代码可供验证此结论:

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

void sighand(int sig)
{
    // 收到信号SIGUSR1时,缓慢输出SIGUSR1 received
	if(sig == SIGUSR1)
	{
		char str[] = "SIGUSR1 receiced";
		int i = 0;
		while(str[i] != '\0')
		{
			fprintf(stderr, "%c", str[i++]);
			sleep(1);
		}
	}

    // 收到信号SIGUSR2时,缓慢输出catch a SIGUSR2
	if(sig == SIGUSR2)
	{
		char str[] = "catch a SIGUSR2";
		int i = 0;
		while(str[i] != '\0')
		{
			fprintf(stderr, "%c", str[i++]);
			sleep(1);
		}
	}
	return;
}

int main(void)
{
	signal(SIGUSR1, sighand);
	signal(SIGUSR2, sighand);

	printf("my pid: %d, send me SIGUSR1 and SIGUSR2.\n", getpid());

    // 连续给本进程发送SIGUSR1和SIGUSR2,可验证本题结论
	while(1)
		pause();

	return 0;
}

第三个结论可以分两个小结论来看,一是挂起信号不会被子进程继承,二是信号阻塞掩码会被继承。那么只需在创建子进程之前,设置好阻塞掩码,并提前接收一些信号使之挂起不处理,然后在子进程中观察这些信号的行为即可。下述代码可验证本题结论:

#include <stdlib.h>
#include <sys/wait.h>
#include <stdio.h>
#include <unistd.h>

void catch_sig(int sig)
{
	if(sig == SIGINT)
		fprintf(stderr, "%d catch SIGINT.\n", getpid());

	else if(sig == SIGQUIT)
		fprintf(stderr, "%d catch SIGQUIT.\n", getpid());
}

int main(int argc, char **argv)
{
    // 捕捉信号SIGQUIT
	signal(SIGQUIT, catch_sig);

    // 阻塞信号SIGQUIT
	sigset_t sig;
	sigemptyset(&sig);
	sigaddset(&sig, SIGQUIT);
	sigprocmask(SIG_BLOCK, &sig, NULL);

    // 在接下来的15秒内给本进程发送SIGQUIT,这些信号将被挂起
	sleep(15);

	pid_t pid;
	pid = fork();

	if(pid == 0)
    {
		fprintf(stderr, "child: %d\n", getpid());

        // 在子进程中解除对SIGQUIT的阻塞
        // 若子进程没有响应该信号,则可验证父进程挂起的信号不会继承给子进程

		// 另外,注释掉以下语句后,单独给子进程发送SIGQUIT会发现子进程不会响应该信号
		// 则可验证父进程设置的信号掩码会被子进程继承
		sigprocmask(SIG_UNBLOCK, &sig, NULL);

		while(1)
			pause();
	}
	
	else if(pid > 0)
    {
		fprintf(stderr, "parent: %d\n", getpid());
		sigprocmask(SIG_UNBLOCK, &sig, NULL);
		while(1)
			pause();
	}

	return 0;
}

在这里插入图片描述
在这里插入图片描述
#端午趣味征文赛–用代码过端午#

  • 19
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 22
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 22
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Qt历险记

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值