【Linux】第十一章 进程信号(概念+产生信号+阻塞信号+捕捉信号)

🏆个人主页企鹅不叫的博客

​ 🌈专栏

⭐️ 博主码云gitee链接:代码仓库地址

⚡若有帮助可以【关注+点赞+收藏】,大家一起进步!

💙系列文章💙


【Linux】第一章环境搭建和配置

【Linux】第二章常见指令和权限理解

【Linux】第三章Linux环境基础开发工具使用(yum+rzsz+vim+g++和gcc+gdb+make和Makefile+进度条+git)

【Linux】第四章 进程(冯诺依曼体系+操作系统+进程概念+PID和PPID+fork+运行状态和描述+进程优先级)

【Linux】第五章 环境变量(概念补充+作用+命令+main三个参数+environ+getenv())

【Linux】第六章 进程地址空间(程序在内存中存储+虚拟地址+页表+mm_struct+写实拷贝+解释fork返回值)

【Linux】第七章 进程控制(进程创建+进程终止+进程等待+进程替换+min_shell)

【Linux】第八章 基础IO(open+write+read+文件描述符+重定向+缓冲区+文件系统管理+软硬链接)

【Linux】第九章 动态库和静态库(生成原理+生成和使用+动态链接)

【Linux】第十章 进程间通信(管道+system V共享内存)



💎一、信号概念

🏆1.信号本质

信号是通过位图记录的,信号的产生本质上就是操作系统直接去修改目标进程的task_struct中的信号位图

🏆2.查看信号

kill -l:查看信号

[Jungle@VM-20-8-centos:~/lesson29]$ 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

其中1 ~ 31号信号是普通信号,34~64号信号是实时信号。

SIGKILL(杀死任一进程)和SIGSTOP(该信号用于停止一个进程)无法被阻塞,无法被自定义,无法被忽。

Ctrl+C产生SIGINT信号

🏆3.信号处理方式

  1. 默认(default)
  2. 忽略(ignore)
  3. 自定义捕捉

man 7 signal:查看信号默认处理动作

描述 (DESCRIPTION)
       下面  列出  Linux  支持的  信号. 某些 信号 依赖于 体系结构(architec‐
       ture).

       首先, POSIX.1 描述了 下列 信号.

       信号         值      动作   说明
       ─────────────────────────────────────────────────────────────────────
       SIGHUP        1       A     在控制终端上是挂起信号, 或者控制进程结束
       SIGINT        2       A     从键盘输入的中断
       SIGQUIT       3       C     从键盘输入的退出
       SIGILL        4       C     无效硬件指令
       SIGABRT       6       C     非正常终止, 可能来自 abort(3)
       SIGFPE        8       C     浮点运算例外
       SIGKILL       9      AEF    杀死进程信号
       SIGSEGV      11       C     无效的内存引用
       SIGPIPE      13       A     管道中止: 写入无人读取的管道
       SIGALRM      14       A     来自 alarm(2) 的超时信号
       SIGTERM      15       A     终止信号

Core Dump

一个进程要异常终止时,可以选择把进程的用户空间内存数据全部 保存到磁盘上,文件名通常是core,这叫做Core Dump。

ulimit -a 查看系统允许我们产生多大的core文件,默认是0

系统是不允许产生这个core文件,ulimit -c size 修改,允许产生size大小的core文件

ulimit -c 1024
[Jungle@VM-20-8-centos:~/lesson29]$ ulimit -a
core file size          (blocks, -c) 1024

编写以下程序

int main()
{
	cout<<"waiting signal..."<<endl;
	while(1);
	return 0;
}

运行起来后,用Ctrl \信号终止

[Jungle@VM-20-8-centos:~/lesson29]$ ./mykill
waiting signal...
^\退出(吐核)

查看目录下的文件,会发现多了一个core文件

[Jungle@VM-20-8-centos:~/lesson29]$ ll
总用量 148
-rw------- 1 Jungle root  259655 118 15:48 core.11075
-rw-r--r-- 1 Jungle root   161 118 15:48 makefile
-rwxr-xr-x 1 Jungle root 25944 118 17:11 mykill

通过gdb调试器打开这个程序,然后通过指令core-file长core文件的错误信息,就可以发现这个进程是被收到3号信号如何退出的

在这里插入图片描述

🏆4.signal

#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);

功能: 对一个信号注册特定的处理动作(注册一个对信号的捕捉方式)

参数:

  • sig: 要注册的信号
  • handler: 处理动作,有三种:SIG_DFL(默认)SIG_IGN(忽略)自定义(函数指针)
    其中函数指针指向的函数有一个int类型的参数,无返回值,这个函数指针就是用户给信号自定义的处理动作,通过函数实现

实例:signal函数表示当信号SIGINT发生时,调用handler函数,当发生信号时,将信号作为参数传送到handler函数当中

#include <iostream>
#include <signal.h>
#include <unistd.h>

using namespace std;

void handler(int signo)
{
	cout << "我是一个进程,刚刚获取了一个信号: " << signo << endl;
}

int main()
{
	signal(SIGINT, handler); //注册2号信号
	while (1){
		cout<<"正在运行:"<<getpid()<<endl;
		sleep(1);
	}
	return 0;
}

结果:Ctrl+C代表的是2号信号,且这个2号信号不再做终止进程的动作,而是打印了一句话。因为signal这个函数修改了2号信号的默认动作,让它执行自定义动作。

[Jungle@VM-20-8-centos:~/lesson29]$ ./mykill
正在运行:27235
正在运行:27235
^C我是一个进程,刚刚获取了一个信号: 2
正在运行:27235
^C我是一个进程,刚刚获取了一个信号: 2
正在运行:27235

总结: 信号是进程之间事件通知的一种方式

💎二、产生信号

🏆1.kill

使用kill命令向一个进程发送信号时,我们可以以kill -信号名 进程ID的形式进行发送,比如kill -9 PID,杀死进程

#include <signal.h>
int kill(pid_t pid, int sig); 

功能: 给任意进程发送任意信号

参数:

  • pid:进程pid
  • sig:要发送的信号

返回值: 成功返回0,失败返回-1

实例:通过mykill程序加参数的方式发生9号信号杀死后台myproc进程

#include <iostream>
#include <signal.h>
#include <unistd.h>

using namespace std;

void Usage(char* proc)
{
	cout<<"Usage:"<< proc<< "pid signo"<<endl;
}

int main(int argc, char* argv[])
{
	if (argc != 3){
		Usage(argv[0]);
		return 1;
	}
	pid_t pid = atoi(argv[2]);//进程PID
	int signo = atoi(argv[1]);//信号
	kill(pid, signo);
	return 0;
}

结果:首先运行测试程序myproc,然后运行,mykill,杀死myproc,

[Jungle@VM-20-8-centos:~/lesson29]$ ./myproc
正在运行:29772
正在运行:29772
已杀死
[Jungle@VM-20-8-centos:~/lesson29]$ ./mykill 9 29772

🏆2.raise

#include <signal.h>  
int raise(int sig);  

功能: 给进程自己发送信号

参数:

  • sig:要发送的信号

返回值: 成功返回0,失败返回-1

实例:

#include <iostream>
#include <signal.h>
#include <unistd.h>

using namespace std;

void handler(int signo)
{
	cout << "我是一个进程,刚刚获取了一个信号: " << signo <<"-"<<getpid()<< endl;
}
int main()
{
	signal(SIGINT, handler);//注册2号信号
	while (1){
		sleep(1);
		raise(SIGINT);
	}
	return 0;
}

结果:每隔一秒收到一个2号信号

[Jungle@VM-20-8-centos:~/lesson29]$ ./mykill
我是一个进程,刚刚获取了一个信号: 2-32113
我是一个进程,刚刚获取了一个信号: 2-32113
我是一个进程,刚刚获取了一个信号: 2-32113
我是一个进程,刚刚获取了一个信号: 2-32113

🏆3.abort

#include <stdlib.h>  
void abort(void); 

功能: 使用当前进程收到信号而异常终止(发送6号信号,SIGABRT)

实例:

#include <iostream>
#include <signal.h>
#include <unistd.h>

using namespace std;

void handler(int signo)
{
	cout << "我是一个进程,刚刚获取了一个信号: " << signo <<"-"<<getpid()<< endl;
}
int main()
{
	signal(SIGABRT, handler);//注册6号信号
	while (1){
		sleep(1);
		abort();
	}
	return 0;
}

结果:虽然我们对SIGABRT信号进行了捕捉,并且在收到SIGABRT信号后执行了我们给出的自定义方法,但是当前进程依然是异常终止了。

[Jungle@VM-20-8-centos:~/lesson29]$ ./mykill
我是一个进程,刚刚获取了一个信号: 6-32752
已放弃

总结:abort函数的作用是异常终止进程,abort本质是通过向当前进程发送SIGABRT信号而终止进程的,使用abort函数终止进程总是成功的。

🏆4.通过软件条件产生信号

#include <unistd.h> 
unsigned alarm(unsigned seconds); 

功能: 设定一个闹钟,操作系统会在闹钟到了时送SIGALRM (时钟信号)信号给进程,默认处理动作是终止进程

参数:

  • second:设置时间,单位是s

返回值: 0或者此前设定的闹钟时间还余下的秒数

实例:第一个程序,不断打印count的数,第二个程序,最后打印count

#include <iostream>
#include <signal.h>
#include <unistd.h>

using namespace std;
int count = 0;

int main()
{
	alarm(1);
	while (1)
	{
		cout<<count<<endl;
		++count;
	}
	return 0;
}
--------------------------------------------------------------------------
#include <iostream>
#include <signal.h>
#include <unistd.h>

using namespace std;
int count = 0;

void handler(int signo)
{
	cout<<"count:"<<count<<endl;
	exit(0);
}

int main()
{

	// 由软件条件产生信号  alarm函数和SIGALRM
	signal(SIGALRM, handler);
	alarm(1);
	while (1)
	{
		count++;
	}
	return 0;
}

结果:程序2计算值比程序1大

[Jungle@VM-20-8-centos:~/lesson29]$ ./mykill
63694
63695
63696闹钟
-----------------------
[Jungle@VM-20-8-centos:~/lesson29]$ ./mykill
count:564323660

程序1每加1次都在打印,但是程序2只是最后一次才打印,所以程序1在运行的过程中不断的在进行IO操作,IO操作其实是很慢的。得出结论:一个体系结构中,IO是影响程序运行效率的最大一方面

🏆5.通过硬件异常产生信号

CPU产生异常 发生除零错误,OS会识别到CPU内部寄存器中报错,内核将这个异常解释为信号,最后OS发送SIGFPE信号给进程

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


int main()
{
// CPU运算单元产生异常,内核将这个异常处理为SIGFPE信号发送给进程
int a = 10;
int b = 0;
printf("%d", a/b); 
return 0;
}

MMU产生异常: 当进程访问非法地址时,mmu想通过页表映射来将虚拟转换为物理地址,此时发现页表中不存在该虚拟地址,此时会产生异常,然后OS将异常解释为SIGSEGV信号,然后发送给进程

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

int main()
{
// MMU硬件产生异常,内核将这个异常处理为SIGSEGV信号发送给进程
signal(11, handler);
int* p = NULL;
printf("%d\n", *p);
return 0;
}

总结:
C/C++程序会崩溃,程序当中出现的各种错误最终一定会在硬件层面上有所表现,进而会被操作系统识别到,然后操作系统就会发送相应的信号将当前的进程终止。

💎三、阻塞信号

🏆1.概念

  • 实际执行信号的处理动作称为信号递达
  • 信号递达的三种方式:默认、忽略和自定义捕捉
  • 信号从产生到递达之间的状态,称为信号未决(Pending)。
  • 进程可以选择阻塞 (Block )某个信号。
  • 被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作。
  • 阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略是在递达之后可选的一种处理动作。

🏆2.信号在内核中表示

在这里插入图片描述

信号阻塞位图block表,信号未决位图pending表,信号处理动作handler表

  • block表:每个信号对应1位,如果该位为1,那么代表该信号被阻塞,为0代表不被阻塞

  • pending表:如果该位为1,代表收到该信号,处于未决状态,为0代表还没收到该信号或者收到信号已经被递达了,本质是位图

  • handler表:代表对该信号处理动作,前面说过有三种,默认、忽略和自定义捕捉,其中自定义捕捉就是用户自定义的函数。handler表本质其实是函数指针数组,存放的是用户自定义函数的指针

  • 1号信号未阻塞也未产生过,但它递达时执行默认处理动作。

  • 2号信号产生过,但正在被阻塞,所以暂时不能递达。虽然它的处理动作是忽略,但在没有解除阻塞之前不能忽略这个信号,因为进程仍有机会改变处理动作之后再解除阻塞。

  • 3信号未产生过,一旦产生3信号将被阻塞,它的处理动作是用户自定义函数handler1。

🏆3.信号集及信号集操作函数

sigset_t: 未决和阻塞标志可以用相同的数据类型sigset_t来存储,sigset_t称为信号集,也被定义为一种数据类型。表示每个信号状态处于何种状态(是否被阻塞,是否处于未决状态)。

阻塞信号集也叫做当前进程的信号屏蔽字(Signal Mask),这里的“屏蔽”应该理解为阻塞而不是忽略。

信号集操作函数的原型

#include <signal.h>
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);
  • sigemptyset: 初始化set指向的信号集,将所有比特位置0
  • sigfillset: 初始化set指向的信号集,将所有比特位置1
  • sigaddset: 把set指向的信号集中signum信号对应的比特位置1
  • sigdelset: 把set指向的信号集中signum信号对应的比特位置0
  • sigismember: 判断在set所指向的信号集中是否包含某种信号,若包含则返回1,不包含则返回0,调用失败返回-1

sigprocmask

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

功能: 读取或更改进程的信号屏蔽字

参数:

how:三个选项

  • SIG_BLOCK:把set中的信号屏蔽字添加到进程的信号屏蔽字中,mask = mask|set

  • SIG_UNBLOCK:把set中的信号屏蔽字在进程信号屏蔽字的那些去掉,mask = mask&~set

  • SIG_SETMASK:设置当前进程的信号屏蔽字为set,mask = set

set:如果为非空指针,则根据how参数更改进程的信号屏蔽字

oset:

  • 如果oset是非空指针,则读取进程当前的信号屏蔽字通过oset参数传出。
  • 如果set是非空指针,则更改进程的信号屏蔽字,参数how指示如何更改。
  • 如果oset和set都是非空指针,则先将原来的信号屏蔽字备份到oset里,然后根据set和how参数更改信号屏蔽字。

返回值: 成功返回0,失败返回-1

sigpending

#include <signal.h> 
int sigpending(sigset_t *set);

功能: 读取进程的未决信号集

参数:

  • set:读取当前进程的信号屏蔽字到set指向的信号屏蔽中

返回值: 成功返回0,失败返回-1

实例1:把进程中信号屏蔽字所有信号进行阻塞,然后隔1s对未决信号集进行打印

#include <iostream>
#include <signal.h>
#include <unistd.h>

using namespace std;
void handler(int signo)
{
	cout << "我是一个进程,刚刚获取了一个信号: " << signo << endl;
}

//打印信号集
static void showPending(sigset_t *pendings)
{
 for (int sig = 1; sig <= 31; sig++)
 {
		//存在输出1,不存在输出0
     if (sigismember(pendings, sig))
     {
         cout << "1";
     }
     else
     {
         cout << "0";
     }
 }
 cout << endl;
}

int main(int argc, char *argv[])
{
 cout << "pid: " << getpid() << endl;
 // 1. 屏蔽所有信号
 sigset_t bsig, obsig;//初始化两个信号集
 sigemptyset(&bsig);
 sigemptyset(&obsig);
 for (int signo = 1; signo <= 31; signo++)
 {
     // 1.1 添加signo号信号到信号屏蔽字中
     sigaddset(&bsig, signo);
     // 1.2. signal注册
     signal(signo, handler);
 }
 // 1.4 设置用户级的信号屏蔽字到内核中,让当前进程屏蔽到signo信号
 sigprocmask(SIG_SETMASK, &bsig, &obsig);

 // 2. 不断的获取当前进程的pending信号集
 sigset_t pendings;
 while (true)
 {
     // 2.1 清空信号集
     sigemptyset(&pendings);
     // 2.2 获取当前进程(谁调用,获取谁)的pending信号集
     sigpending(&pendings)
     // 2.3 打印一下当前进程的pengding信号集
     showPending(&pendings);
     sleep(1);
 }
 return 0;
}


结果:进程收到信号时,且该信号被阻塞,处于未决状态,未决信号集中信号对应的比特位由0置1

[Jungle@VM-20-8-centos:~/lesson29]$ ./mykill
pid: 13761
0000000000000000000000000000000
0000000000000000000000000000000
0100000000000000000000000000000
0120000000000000000000000000000
0123000000000000000000000000000

实例2:代码进行修改,进行运行10s后,我们将信号屏蔽字中所有信号解除屏蔽

int cnt = 0;
 while (true)
 {
     cnt++;
     if(cnt == 10)
     {
         cout << "解除对所有信号的block...." << endl;
         sigprocmask(SIG_UNBLOCK, &bsig, &obsig);
     }
 }

结果:所有信号解除阻塞后,信号被递达了

[Jungle@VM-20-8-centos:~/lesson29]$ ./mykill
pid: 16596
0000000000000000000000000000000
0000000000000000000000000000000
0100000000000000000000000000000
0100000000000000000000000000000
解除对所有信号的block....
我是一个进程,刚刚获取了一个信号: 2
0000000000000000000000000000000

💎四、捕捉信号

🏆1.捕捉过程

进程地址空间由内核地址空间和用户地址空间组成

  • 用户态: 处于⽤户态的 CPU 只能受限的访问内存,用户的代码,并且不允许访问外围设备,权限比较低
  • 内核态: 处于内核态的 CPU 可以访问任意的数据,包括外围设备,⽐如⽹卡、硬盘等,权限比较高

在这里插入图片描述

进程有不同的用户空间,但是只有一个内核空间,不同进程的用户空间的代码和数据是不一样的,但是内核空间的代码和数据是一样的。
上面这些主要是想说:进程处于用户态访问的是用户空间的代码和数据,进程处于内核态,访问的是内核空间的代码和数据。

在这里插入图片描述

进程是在返回用户态之前对信号进行检测,检测pending位图,根据信号处理动作,来对信号进行处理。这个处理动作是在内核态返回用户态后进行执行的。

库函数并不会引起运行态的切换。

🏆2.sigaction

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

功能: 可以读取和修改与指定信号相关联的处理动作

  • signum代表指定信号的编号。
  • 若act指针非空,则根据act修改该信号的处理动作。
  • 若oldact指针非空,则通过oldact传出该信号原来的处理动作。

返回值: 成功返回0,失败返回-1

参数act和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:SIG_DFT(默认动作)、SIG_IGN(忽略信号)和handler(用户自定义处理函数)
  • sa_sigaction:实时信号的处理函数
  • sa_mask:说明这些需要额外屏蔽的信号
  • sa_flags:设置为0即可
  • sa_restorer:无作用

实例:用sigaction函数对2号信号进行了捕捉,将2号信号的处理动作改为了自定义的打印动作,并在执行一次自定义动作后将2号信号的处理动作恢复为原来默认的处理动作

#include <iostream>
#include <signal.h>
#include <unistd.h>

using namespace std;

void handler(int signo)
{
    cout << "子进程退出啦,我确实收到了信号: " << signo << " 我是: " << getpid() << endl;
}

int main()
{
    struct sigaction act, oact;

    act.sa_flags = 0;// 选项 设置为0
    act.sa_handler = handler;
    // 对2号信号修改处理动作
    sigaction(2, &act, &oact);
    while (1)
    {
        cout<<"I am a process..."<<endl;
        sleep(1);
    }
    return 0;
}

结果:每一次向进程发送2号信号,执行我们自定义的打印动作,之后继续执行原程序

[Jungle@VM-20-8-centos:~/lesson30]$ ./mysignal
I am a process...
I am a process...
^C子进程退出啦,我确实收到了信号: 2 我是: 1175
I am a process...
I am a process...

🏆3.可重入函数

主函数中调用insert函数向链表中插入结点node1,某信号处理函数中也调用了insert函数向链表中插入结点node2

在这里插入图片描述

1.main函数中调用了insert函数,将结点node1插入链表,但插入的时候,因为硬件中断使进程切换到内核,再次回到用户态之前检查到有信号待处理,于是切换到sighandler函数

在这里插入图片描述

2.而sighandler函数中也调用了insert函数,将结点node2插入到了链表中

在这里插入图片描述

3.当结点node2插入的两步操作都做完之后从sighandler返回内核态

在这里插入图片描述

4.次回到用户态就从main函数调用的insert函数中继续往下执行,即继续进行结点node1的插入操作

在这里插入图片描述

最终结果是,main函数和sighandler函数先后向链表中插入了两个结点,但最后只有node1结点真正插入到了链表中,而node2结点就再也找不到了,造成了内存泄漏。

  • 函数的重入指的是一个函数在不同执行流中同时进入运行
  • 不可重入指的是一旦重入就有可能会出问题

如果一个函数符合以下条件之一则是不可重入的:

  • 调用了malloc或free,因为malloc也是用全局链表来管理堆的。
  • 调用了标准I/O库函数。标准I/O库的很多实现都以不可重入的方式使用全局数据结构。

对于全局和局部

  • 函数的重入对局部变量并无影响,是可重入的
  • 对全局数据进行了原子操作,但是因为原子操作本身是不可被打断的,因此他是可重入的

函数是否可重入的关键在于函数内部是否对全局数据进行了不受保护的非原子操作,其中原子操作指的是一次完成,中间不会被打断的操作,表示操作过程是安全的

🏆4.volatile

volatile是C语言的一个关键字,防止编译器过度优化,因此具备 可见性与有序性功能。

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

int flag = 1;
void handler(int signo)
{
  flag = 0;
  printf("flag changs from 1 to 0\n");
}

int main()
{
  signal(2, handler);

  while (flag);

  printf("running here...\n");
  return 0;
}

使用gcc带上优化(-O2)进行编译

gcc -o mysignal mysignal.c  -O2

结果:此时按下Ctrl C 程序并没有往下指向,其实是因为flag的值没有发生改变,因为编译器优化,会把flag这个变量放入到寄存器中,handler指向流只会把内存中的flag变为1,但是flag在寄存器中的值并没有改变,但while检测flag是检测寄存器中flag值,所以这下循环是不会退出的。

[Jungle@VM-20-8-centos:~/lesson30]$ ./mysignal
^Cflag changs from 1 to 0
^Cflag changs from 1 to 0

解决办法就是使用volatile关键字,报错内存的可见性。

volatile: 保持内存的可见性,告知编译器,被该关键字修饰的变量,不允许被优化,对该变量的任何操作,都必须在真实的内存中进行操作

//保持内存可见性
volatile int flag = 1;

🏆5.SIGCHLD信号

子进程在终止时会给父进程发生SIGCHLD信号,该信号的默认处理动作是忽略,父进程可以自定义SIGCHLD信号的处理动作,这样父进程就只需专心处理自己的工作,不必关心子进程了,子进程终止时会通知父进程,父进程在信号处理函数中调用wait或waitpid函数清理子进程即可。

#include <iostream>
#include <signal.h>
#include <unistd.h>

using namespace std;

void handler(int signo)
{
    cout << "子进程退出啦,我确实收到了信号: " << signo << " 我是: " << getpid() << endl;
}

int main()
{
    signal(SIGCHLD, handler);
    pid_t id = fork();
    if (id == 0)
    {
        while (true)
        {
            cout << "我是子进程: " << getpid() << endl;
            sleep(1);
        }
        exit(0);
    }

    // parent
    while (true)
    {
        cout << "我是父进程: " << getpid() << endl;
        sleep(1);
    }
}

结果:此时父进程就只需专心处理自己的工作,不必关心子进程了,子进程终止时父进程收到SIGCHLD信号,会自动进行该信号的自定义处理动作,进而对子进程进行清理

[Jungle@VM-20-8-centos:~/lesson30]$ ./mysignal
我是父进程: 15426
我是子进程: 15427
我是父进程: 15426
我是子进程: 15427
子进程退出啦,我确实收到了信号: 17 我是: 15426
我是父进程: 15426
我是父进程: 15426
我是父进程: 15426

  • 18
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 14
    评论
评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

penguin_bark

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

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

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

打赏作者

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

抵扣说明:

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

余额充值