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

news/2024/5/15 18:14:53/文章来源:https://blog.csdn.net/YQ20210216/article/details/127777888

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

​ 🌈专栏

  • C语言初阶和进阶
  • C项目
  • Leetcode刷题
  • 初阶数据结构与算法
  • C++初阶和进阶
  • 《深入理解计算机操作系统》
  • 《高质量C/C++编程》
  • 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.信号本质
    • 🏆2.查看信号
    • 🏆3.信号处理方式
      • **Core Dump**
    • 🏆4.signal
  • 💎二、产生信号
    • 🏆1.kill
    • 🏆2.raise
    • 🏆3.abort
    • 🏆4.通过软件条件产生信号
    • 🏆5.通过硬件异常产生信号
  • 💎三、阻塞信号
    • 🏆1.概念
    • 🏆2.信号在内核中表示
    • 🏆3.信号集及信号集操作函数
      • sigprocmask
      • sigpending
  • 💎四、捕捉信号
    • 🏆1.捕捉过程
    • 🏆2.sigaction
    • 🏆3.可重入函数
    • 🏆4.volatile
    • 🏆5.SIGCHLD信号


💎一、信号概念

🏆1.信号本质

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

🏆2.查看信号

kill -l:查看信号

[Jungle@VM-20-8-centos:~/lesson29]$ kill -l1) SIGHUP       2) SIGINT       3) SIGQUIT      4) SIGILL       5) SIGTRAP6) 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类型的参数,无返回值,这个函数指针就是用户给信号自定义的处理动作,通过函数实现

实例:

#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]);//进程PIDint 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函数和SIGALRMsignal(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,不存在输出0if (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;// 选项 设置为0act.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);}// parentwhile (true){cout << "我是父进程: " << getpid() << endl;sleep(1);}
}

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_223804.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C++基本知识(二)---函数重载、引用、内联函数、auto关键字

目录 1.函数重载 2.引用 3.内联函数 4.auto关键字(C11) 5.指针空值nullptr(C11) 1.函数重载 重载函数是函数的一种特殊情况&#xff0c;为方便使用&#xff0c;C允许在同一范围中声明几个功能类似的同名函数&#xff0c;但是这些同名函数的形式参数&#xff08;指参数的个…

CEC2015:(二)动态多目标野狗优化算法DMODOA求解DIMP2、dMOP2、dMOP2iso、dMOP2dec(提供Matlab代码)

一、cec2015中测试函数DIMP2、dMOP2、dMOP2iso、dMOP2dec详细信息 CEC2015&#xff1a;动态多目标测试函数之DIMP2、dMOP2、dMOP2iso、dMOP2dec详细信息 二、动态多目标野狗优化算法 多目标野狗优化算法&#xff08;Multi-Objective Dingo Optimization Algorithm&#xff0…

瑞吉外卖强化(一):缓存优化

瑞吉外卖强化&#xff08;一&#xff09;&#xff1a;缓存优化瑞吉外卖 缓存优化Redis基本操作短信验证码 缓存实现缓存菜品数据SpringCache常用注解瑞吉外卖 缓存优化 Redis基本操作 redisTemplate需要配置类 这里的 需要对其进行 序列化操作 reidsTeplate.opsForValue().s…

论文精读:Swin Transformer V2: Scaling Up Capacity and Resolution

论文地址:https://arxiv.org/pdf/2111.09883.pdf 代码地址: GitHub - microsoft/Swin-Transformer: This is an official implementation for "Swin Transformer: Hierarchical Vision Transformer using Shifted Windows". Abstract 本篇论文主要致力于解决大型…

TCP三次握手和四次挥手基本知识

一、概述 TCP是面向连接、可靠的、基于字节流的传输层通讯协议。 如何确定一个TCP连接&#xff1a; 目的IP目的端口源IP源端口 二、TCP建立连接 序列号client_isn和server_isn是随机初始化&#xff0c;可以通过netstat -napt来查看网络状态。 为什么建立连接需要三次握手&…

c++哈希(哈希表闭散列线性探测实现)

文章目录0. 前言1. 线性探测2. 线性探测的代码实现2.0 定义2.1 插入实现--Insert2.2 查找实现--Find2.3 删除实现--Erase2.4 仿函数3. 完整代码实现4. 代码测试并运行结果&#xff1a;0. 前言 闭散列&#xff1a;也叫开放定址法&#xff0c;当发生哈希冲突时&#xff0c;如果哈…

Python画爱心——谁能拒绝用代码敲出来会跳动的爱心呢~

还不快把这份浪漫拿走&#xff01;&#xff01;节日就快到来了&#xff0c;给Ta一个惊喜吧~ 今天给大家分享一个浪漫小技巧&#xff0c;利用Python制作一个立体会动的心动小爱心 成千上百个爱心汇成一个大爱心&#xff0c;从里到外形成一个立体状&#xff0c;给人视觉上的冲击…

年轻人不用太过于努力

周末和一个毕业一年多的朋友聊天&#xff0c;我随口问了一句「你有什么想跟我分享的」&#xff0c;然后他就说了上面的那句话。「年轻人不用太过于努力」和读者聊天会做成我的一个公众号专栏&#xff0c;内容有也会越来越丰富&#xff0c;全部的内容都会收录到我的程序人生专栏…

采购管理主要流程有哪些?

采购管理流程是很多企业用于获取物资或服务的一种关键步骤。采购管理流程对企业至关重要&#xff0c;因为它们可以对利润和支出产生会有直接的影响。 由于各个企业有不同的需求和目标&#xff0c;采购管理流程可能会有所不同。虽然与其采购流程相关的细节可能有所不同&#xf…

web前端课程设计——动漫网页2个网页HTML+CSS web前端开发技术 web课程设计 网页规划与设计

HTML实例网页代码, 本实例适合于初学HTML的同学。该实例里面有设置了css的样式设置&#xff0c;有div的样式格局&#xff0c;这个实例比较全面&#xff0c;有助于同学的学习,本文将介绍如何通过从头开始设计个人网站并将其转换为代码的过程来实践设计。 ⚽精彩专栏推荐&#x1…

便宜又大碗!AI将画廊轻松搬到自家墙壁;用隐写术在图像中存储文件;免费书·算法高维鲁棒统计;关节式手部模型数据集;前沿论文 | ShowMeAI资讯日报

&#x1f440;日报合辑 | &#x1f4c6;电子月刊 | &#x1f514;公众号下载资料 | &#x1f369;韩信子 &#x1f4e2; Mixtiles&#xff1a;将画廊搬到自家墙壁&#xff0c;“便宜又大碗”的艺术平替 https://www.mixtiles.com/ Mixtiles 是一家快速发展的照片创业公司&…

JavaScipt基础(持续更新三)

JavaScipt基础 JavaScipt基础 九、对象&#xff08;Object&#xff09; 9.1什么是对象 9.2JavaScript中的对象 9.3如何得到一个对象 9.4this的指向 9.5对象的使用 十、标准库对象&#xff08;内置对象&#xff09; 10.1Math对象 10.1.1常用属性和方法 10.1.2案例 1…

什么是蜂窝移动网络?

文章目录前言移动网络 vs WIFI蜂窝移动通信网产生过程蜂窝网络实现移动上网通信网架构总结前言 本博客仅做学习笔记&#xff0c;如有侵权&#xff0c;联系后即刻更改 科普&#xff1a; 移动网络 vs WIFI 计网课外实验月&#xff0c;我走在宿舍一楼正数着AP有多少个&#xff…

F. Rats Rats(二分 or 预处理)[UTPC Contest 09-02-22 Div. 2 (Beginner)]

题面如下&#xff1a; 思路 or 题解 xkaix ^ k a_ixkai​ 我们可以去想办法去找到 最小的xxx 为什么去寻找xminx_{min}xmin​ 看样例&#xff1a; 52183521 8^352183 52129521 2^952129 一个数如果满足式子 xkaix ^ k a_ixkai​ 至少我们可以找到一个xxx 如果有多个xxx我们…

国考省考行测:问题型材料主旨分析,有问题有对策,主旨是对策,有问题无对策,要合理引申对策

国考省考行测&#xff1a;问题型材料主旨分析&#xff0c;有问题有对策&#xff0c;主旨是对策&#xff0c;有问题无对策&#xff0c;要合理引申对策 2022找工作是学历、能力和运气的超强结合体! 公务员特招重点就是专业技能&#xff0c;附带行测和申论&#xff0c;而常规国考…

FusionSphere虚拟化解决方案介绍

FusionSphere虚拟化解决方案介绍 Fusionsphere 云管理层&#xff1a;FusionManager 虚拟化层&#xff1a; 华为: Fusioncompute&#xff08;计算虚拟化&#xff0c;存储虚拟化&#xff0c;网络虚拟化&#xff09;Fusionstorage&#xff08;分布式块存储&#xff09;ebackup…

Python制作GUI学生管理系统毕设,大学生总会用得到

有很多可爱的大学生跟我吐槽&#xff1a; 咋这个大学跟我想象的不一样呢&#xff1f; 老师叫我们自己做… 还是那句话&#xff0c;技术才是硬道理 源码、资料电子书文末名片获取 有个经典案例就是 学生管理系统 写完了放在那也是放着&#xff0c;所以今天分享给大家吧&…

JAVA微信小程序实验室教室预约小程序系统毕业设计 开题报告

本文给出的java微信小程序系统毕业设计开题报告&#xff0c;仅供参考&#xff01;&#xff08;具体模板和要求按照自己学校给的要求修改&#xff09; 选题目的和意义 目的&#xff1a;本课题主要目标是设计并能够实现一个基于微信小程序实验室预约系统&#xff0c;前台用户使…

Python之魔幻切片——万物可切(只要是序列对象)。负整数步长一出,序列瞬间倒置,可以玩儿更多花样。

【点击此处跳转笔记正文】Python 官网&#xff1a;https://www.python.org/ Free&#xff1a;大咖免费“圣经”教程《 python 完全自学教程》&#xff0c;不仅仅是基础那么简单…… My CSDN主页、My HOT博、My Python 学习个人备忘录好文力荐、 老齐教室 自学并不是什么神秘的…

css:详解BFC块级格式化上下文

定义 BFC&#xff08;Block Formatting Context&#xff09;块级格式化上下文 一个BFC区域包含创建该上下文元素的所有子元素&#xff0c;但是不包括创建了新的BFC的子元素的内部元素&#xff0c;BFC是一块块独立的渲染区域&#xff0c;可以将BFC看成是元素的一种属性&#xf…