linux信号

在这里插入图片描述

在生活中有哪些信号的例子呢?
1 起跑的时候的发令枪(听到枪响之后就开始跑步)
2 闹钟
3 红绿灯
4 低电量通知

我们以红绿灯为例子:
我们能识别红绿灯是不是有人教过我们的啊 ,我们生下来是不理解红绿灯这个概念的。是因为小学老师说,红灯停 ,绿灯行。我们长期以往学到了。
当我们看到红绿灯的时候,我们不一定会马上处理这个信号。就比如我们点了一个外卖,但是我们现在正在打王者荣耀,这时候就喊外卖员把外卖放在门口,我们将团战打完了我们再去拿外卖。
再上诉的例子中,我们信号到来,然后再到信号被处理。中间是有一个时间窗口,我们必须要记住这个信号。

信号的处理动作呢? 是有默认动作 , 自定义动作 ,还有忽略动作(这个忽略动作其实就是在处理信号,但是没有执行体)
在这里插入图片描述
我们把上面的概念迁移到进程中。这里我们需要有一个共识 , 信号是给进程发的。
进程是如何识别信号的呢?我们程序员先教进程遇到某某某信号该怎么处理。我们要让机器能够认识信号,并且识别到了信号要有对应的方法体。
进程本身就是被程序员编写的属性和逻辑的集合 — 程序员编码完成的。当进程收到信号的时候 , 进程可能正在执行更重要的代码,所以信号不一定会被马上处理。进程本身就需要对信号具有保存能力。
进程再处理信号的时候,一般会有三个默认的动作, 默认,自定义 ,忽略(信号被捕捉)

查看所有的信号

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 是普通信号。后面的信号在了解。
可以通过 查看信号手册

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
       SIGALRM      14       Term    Timer signal from alarm(2)
       SIGTERM      15       Term    Termination signal
       SIGUSR1   30,10,16    Term    User-defined signal 1
       SIGUSR2   31,12,17    Term    User-defined signal 2
       SIGCHLD   20,17,18    Ign     Child stopped or terminated
       SIGCONT   19,18,25    Cont    Continue if stopped
       SIGSTOP   17,19,23    Stop    Stop process
       SIGTSTP   18,20,24    Stop    Stop typed at terminal
       SIGTTIN   21,21,26    Stop    Terminal input for background process
       SIGTTOU   22,22,27    Stop    Terminal output for background process

首先我们要保存31个信号采用的是位图的方式
那我们应该怎么保存信号呢?,就我们有32 个字节。每一个自己都代表一种信号,若我们收到了第一种信号就把第一个比特位的值由改为1 . 这样就保存了1号信号。
在这里插入图片描述
信号的发送
在这里插入图片描述

在这里插入图片描述

以上就是信号的预备知识

在这里插入图片描述

信号的产生

1 终端产生信号

有一段代码理解一下

#include <stdio.h>
#include <signal.h>
#include <iostream>
#include <sys/types.h>
#include <unistd.h>
using namespace std;

void handler(int sig)
{
    printf("catch a sig : %d\n", sig);
    cout << "catch a sig " << sig << endl;
}
int main()
{
    signal(2, handler); // 前文提到过,信号是可以被自定义捕捉的,siganl函数就是来进行信号捕捉的,提前了解一下
    while (1)
    {
        cout << "我是赠酒 " << getpid() << endl;
        sleep(1);
    }
    return 0;
}

结果

我是赠酒 14006
我是赠酒 14006
我是赠酒 14006
我是赠酒 14006
^Ccatch a sig : 2
catch a sig 2
我是赠酒 14006
我是赠酒 14006
我是赠酒 14006
^Ccatch a sig : 2
catch a sig 2
我是赠酒 14006
^Ccatch a sig : 2
catch a sig 2
我是赠酒 14006
^Ccatch a sig : 2
catch a sig 2

帮我在键盘中输入ctrl + c的时候。 就会发现终止不了进程, 而是Ccatch a sig : 2 打印类似信息。我们的ctrl+c就是2 号信号。
ctrl +/ 就是3号信号

这里我们写一个小的demo .我想实现一个代码。自己写于一个kill 的效果

test.cc代码

#include <stdio.h>
#include <signal.h>
#include <iostream>
#include <sys/types.h>
#include <unistd.h>
using namespace std;

void handler(int sig)
{
    printf("catch a sig : %d\n", sig);
    cout << "catch a sig " << sig << endl;
}
int main()
{
    signal(3, handler); // 前文提到过,信号是可以被自定义捕捉的,siganl函数就是来进行信号捕捉的,提前了解一下
    while (1)
    {
        cout << "我是赠酒 " << getpid() << endl;
        sleep(1);
    }
    return 0;
}

mysignal.cc代码

#include <iostream>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
using namespace std;

void Usage (const char* arr)
{
    cout<<arr<<endl;
}
int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        Usage(argv[0]);
        exit(1);
    }
    pid_t pid = atoi(argv[1]);
    pid_t signo = atoi(argv[2]);
    int n = kill(pid,signo);
    if(n!=0)
    {
        perror("kill");
    }
    return 0;
}

结果
在这里插入图片描述

2. 调用系统函数向进程发信号

kill -9 pid

3 由软件产生的信号

函数 alarm() 在 Unix 和类 Unix 系统中用于设置一个定时器(计时器),当定时器到达设定的时间后,操作系统会向当前进程发送 SIGALRM 信号。这个函数是基于信号的简单定时功能,广泛用于实现超时检测和定时任务。

在这里插入图片描述
代码


#include <iostream>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
#include <unistd.h>
#include <stdio.h>
using namespace std;
int cnt =0;
void catchSig(int signo)
{
    cout << "我捕捉到了一个信号" << getpid() << endl;
    cout<<cnt<<endl;
    exit(1);
}
int main()
{
    signal(SIGALRM, catchSig);
    alarm(1);
    while(true)
    {
        cnt++;
    }
    return 0;
}

结果

[zk@VM-24-17-centos lesson23]$ ./test2
我捕捉到了一个信号12901
405781115

在这里插入图片描述
在这里插入图片描述
可用堆来对时间排序,把小的放在堆顶。

4 由硬件产生的信号

我们先用一个代码验证一下会报那种信号

#include<iostream>
#include<signal.h>
#include<unistd.h>
using namespace std;

void catchSig(int signo)
{
    cout<<"我捕捉到了一个信号 "<<signo<<endl;
}
int main()
{
    cout<<"我正在运行中 。。。"<<endl;
    sleep(1);
    int a =10/0;
    return 0;
}

结果

[zk@VM-24-17-centos lesson23]$ ./test1 
我正在运行中 。。。
Floating point exception

查看kill - l,发现报的是 8号信号

#include<iostream>
#include<signal.h>
#include<unistd.h>
using namespace std;

void catchSig(int signo)
{
    cout<<"我捕捉到了一个信号 "<<signo<<endl;
}
int main()
{
    signal(8,catchSig);
    cout<<"我正在运行中 。。。"<<endl;
    sleep(1);
    int a =10/0;
    return 0;
}

结果:发现一直在报8号信号 ,没有中断进程

我捕捉到了一个信号 8
我捕捉到了一个信号 8
我捕捉到了一个信号 8
我捕捉到了一个信号 8
我捕捉到了一个信号 8
我捕捉到了一个信号 8
我捕捉到了一个信号 8
我捕捉到了一个信号 8
我捕捉到了一个信号 8
我捕捉到了一个信号 8
我捕捉到了一个信号 8
我捕捉到了一个信号 8
我捕捉到了一个信号 8
我捕捉到了一个信号 8
我捕捉到了一个信号 8
我捕捉到了一个信号 8

这里有一个疑问?明明是我写的代码报的错误,为什么是硬件错误呢?
首先我们计算需要在cpu上去计算 ,cpu上有很多寄存器 ,分别为 eax 到 edx 。将10 放进eax ,将0放入ebx。这样计算。当 CPU 执行除法指令并遇到除数为零的情况时,可能会返回一个未定义的结果或特定值(如最大整数等)。CPU 会在状态寄存器中设置特定的错误或异常标志。这可以是一个特定的异常位,如 溢出标志(OF) 或特定的错误标志。这个标志位的设置通知操作系统或应用程序有一个异常情况发生。依赖于具体的架构和操作系统,操作系统要感知到状态寄存器的异常,会给当前进程发送8号信号。从而触发了我们之前自定义信号的代码?

为什么一直发8号信号

信号处理返回到错误点:
当你的程序中发生除零操作(10/0),产生 SIGFPE 信号,你的信号处理函数 catchSig 被调用。如果这个函数执行完后,程序的控制流返回到了产生信号的那条指令(即除零操作),除非有某种机制来改变程序的状态或修改执行流,否则同样的操作会再次产生 SIGFPE 信号。
信号处理函数行为:
在默认情况下,许多系统上,当信号处理函数被触发后,相关信号的处理动作会重置为默认动作(通常是终止进程)。但如果信号处理器通过 signal 调用设置,而不是 sigaction,并且在处理函数中没有更改任何执行状态,那么当控制权返回到触发信号的代码位置时,相同的错误操作将重复执行,并再次触发信号。
操作系统的信号递送机制:
操作系统在检测到特定的错误(如除零错误)时,会向执行该操作的进程发送信号。如果进程的信号处理不足以解决问题(如简单地打印消息而不改变程序状态或逻辑流),则操作系统在程序继续执行时会不断重新检测到相同的错误条件,并持续发送同一信号。

在这里插入图片描述

硬件产生异常
在这里插入图片描述

另外一个例子


#include<iostream>
using namespace std;

int main()
{
    int* p =100;
    p = nullptr;
    *p=100;
    return 0;
}

会报错,11号信号

11) SIGSEGV

当你访问0位置的时候,这个CPU的mmu就会报错。给你发送11号信号。
在这里插入图片描述
以下是我的疑问??
虚拟内存管理:现代操作系统使用虚拟内存管理技术,将虚拟地址空间映射到物理内存。虚拟内存不是直接等同于物理内存的大小,它可以更大。例如,即使物理内存是4GB,操作系统可以通过虚拟内存技术提供更大的地址空间(如在32位系统中高达64GB),这是通过内存分页和交换(swap)文件或页文件(page file)实现的。
内存分页:操作系统将虚拟内存分为多个页(通常大小为4KB),这些页通过页表映射到物理内存的页框。如果虚拟内存页当前不在物理内存中,则操作系统可以将它们存储到硬盘上的交换空间,当需要时再加载回物理内存。
PCB和页表存储:尽管PCB和页表占用一部分内存,但这通常是非常小的一部分。页表可能相对较大,特别是在大型系统或具有大量内存的系统中,但它们通常仅占用物理内存的一小部分。操作系统设计时会考虑到这些结构的内存占用,并优化其内存使用。
物理内存的利用:你提到的4GB物理内存限制是指可用于用户进程和系统资源的总内存。操作系统内核、PCB、页表等系统资源确实占用了一部分物理内存,但是操作系统通过高效的内存管理技术(如内存分页和虚拟内存管理),确保了系统的正常运行,同时尽可能地为用户进程提供最大的可用内存空间

进程退出的时候的核心转储问题

在这里插入图片描述
在这里插入图片描述
term 是正常结束
core 是段错误·可以生一个文件 ,用gd加载该文件就可以看到在哪有错误。

在这里插入图片描述
在这里插入图片描述
代码想要被调试,需要使用 -g选项

信号的保存

先看看进程信号的执行流程

信号其他相关常见概念
实际执行信号的处理动作称为信号递达(Delivery)
信号从产生到递达之间的状态,称为信号未决(Pending)。
进程可以选择阻塞 (Block )某个信号。
被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作.
注意,阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略是在递达之后可选的一种处理动作

首先,
信号的保存采用的是位图结构,位图结构是什么呢??

00000000000000000000000000000000 来表示0
00000000000000000000000000000001 表示1出现
00000000000000000000000000000010 表示2出现
00000000000000000000000000000100 表示3出现
....
10000000000000000000000000000000 表示31出现

那么一个int就可以表示32个值
这样通过一个数字就可以表示32个数字。可以极大的节省内存空间。
这样在数组中:
第1个数字表示的范围为0 ~ 31;
第2个数字表示的范围为32 ~ 63;
第3个数字表示的范围为64 ~ 95;
第4个数字表示的范围为96 ~ 127;
第5个数字表示的范围为128 ~ 159;

就是这样一个结构,具体就是
如果我想要表示92102这个数字出现过,则用92102除以32,找出在数组上的第几位数字。所以92102这个数字在数组上第2878个数的范围内,接下来找这个数字上的第几位,取92102模上32的余数 ,即在数组上第2878个数二进制的6位,标记为1。标志92102这个数字出现过。

简易的原理就是 一个类里面然后有个数组 ,数组的长度 x32 就是我们能保存的实际数字。

在这里插入图片描述
信号的捕捉

这一段代码的逻辑就可以表示 ,我们对信号的处理。 我们判断信号signo在不在 , 假设signo是1 ,就不需要左移位置 。 就相当于

0000 0000 0000 0000 0000 0000 0000 0001

表示1号信号。然后先判断是否阻塞,阻塞了就不需要继续判断了。
在这里插入图片描述
这里有三个结构 ,分别是pending 机构 block结构 。 然后函数指针结构的数组表示对应信号的处理方法。
在这里插入图片描述同一时间来了很多相同的信号,这个信号只会被记录一次。其他的信号被丢失了。

在这里插入图片描述
在这里插入图片描述
正常情况下 ,我们调用了getpid这种需要内核态作为访问的。访问完成之后就要回到用户态调用内核态资源之后的代码的位置继续执行。但是往往调用系统资源是非常耗时的。操作系统呢就会去检查信号表?检查的顺序呢如下图所示!先检查block ,在检查pending 。在检查handler函数指针表。先检查block是否为1 ,不为1就继续检查pending表 。然后就找对应的handler的函数指针执行相应的代码。
在这里插入图片描述
这是一些常见的概连介绍,cpu里面有一个寄存器,指向pcb 。

在这里插入图片描述
CPU会保存进程的上下文信息
在这里插入图片描述
cpu的寄存器是可以直接指向进程的。
在这里插入图片描述
同时CPU里面还有一个CR3寄存器,表征当前进程的运行级别。这里就需要内核态和用户态之间的切换。
在这里插入图片描述
首先,我们要理解,系统调用其实是在内存的3 - 4G的区域。同时内核区也有一张页表,这张页表是所有进程共享的。也就是只有一张。我们写的代码在代码段里面想调用系统接口。就会到系统去区找方法 ,并通过页表映射到物理内存。执行完成之后返回到系统区,然后在返回代码段。
在这里插入图片描述
这里是详细的流程
在这里插入图片描述
这里分为了两种不同的情况 。 1 是对信号的捕捉有自定义方法 ,另外一种是没有。

问题 : 第三步有必要存在吗?为啥非要先从内核态变为用户态之后再执行函数。

解答: 因为handler是我们自己写在用户区域的。里面执行代码的权限是用户的执行权限,但是如果我们用户再编写代码的时候,加入了一些只有才做系统权限才能运行的代码 ?本来运行不起的,但是如果这里直接用内核区来运行就能直接运行。操作系统不相信任何人,只相信自己的接口。
在这里插入图片描述
在这里插入图片描述
实例代码

#include<iostream>
#include <signal.h>
#include <unistd.h>
using namespace std;

#define BLOCK_SIGNAL 2
#define MAX_SIGNAL_NUM 32

static void showPending(const sigset_t& pending)
{
    for (int signo = MAX_SIGNAL_NUM; signo >= 1; signo--)
    {
        /* code */
        if(sigismember(&pending,signo))
        {
            cout<<"1";
        }else{
            cout<<"0";
        }
    }
    cout<<endl;
    
}
int main()
{
    // 生成三个sigset_t(这个就是位图)
    sigset_t block,oblock,pending;
    // 对生成的位图置0
    sigemptyset(&block);
    sigemptyset(&oblock);
    

    //生成一个位图 ,其中二号位置为0的。相当于屏蔽2号信号
    sigaddset(&block,BLOCK_SIGNAL);

    // 设置阻塞队列
    sigprocmask(SIG_SETMASK,&block,&oblock);

    while (true)
    {
        sleep(1);
        /* code */
        sigemptyset(&pending);

        sigpending(&pending);
        showPending(pending);

    }
    
    return 0;
}
[zk@VM-24-17-centos lesson24]$ ./test 
00000000000000000000000000000000
00000000000000000000000000000000
00000000000000000000000000000000
00000000000000000000000000000000
00000000000000000000000000000000
^C00000000000000000000000000000010
00000000000000000000000000000010
00000000000000000000000000000010
00000000000000000000000000000010
00000000000000000000000000000010
^\Quit

信号的捕捉

之前我们学习了signal函数 ,今天在学习一个对信号执行捕捉的函数

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

我们写一个简单的代码。

#include<iostream>
#include<signal.h>
#include<unistd.h>
using namespace std;

//    struct sigaction {
//        void     (*sa_handler)(int);
//        void     (*sa_sigaction)(int, siginfo_t *, void *);
//        sigset_t   sa_mask;
//        int        sa_flags;
//        void     (*sa_restorer)(void);
//    };

void handler(int signo)
{
    cout<<"有一个信号被捕捉了: "<<signo<<endl;
}
int main()
{
    struct sigaction act,oact;
    act.sa_handler = handler;
    // 把三号信号屏蔽了
    // sigaddset(&act.sa_mask,3);
    // act.sa_flags = SA_NODEFER;
    sigaction(2,&act,&oact);
    while(1)
    {
        sleep(1);
        cout<<"zk正在学习linux"<<endl;
    }
    return 0;
}

发现我们只能屏蔽一下,但是我们想要一直屏蔽啊。

[zk@VM-24-17-centos lesson25]$ ./mysignal 
zk正在学习linux
zk正在学习linux
^C有一个信号被捕捉了: 2
zk正在学习linux
zk正在学习linux
^C

对flag进行设置

act.sa_flags = SA_NODEFER;
[zk@VM-24-17-centos lesson25]$ ./mysignal 
zk正在学习linux
zk正在学习linux
^C有一个信号被捕捉了: 2
zk正在学习linux
^C有一个信号被捕捉了: 2
zk正在学习linux
^C有一个信号被捕捉了: 2
zk正在学习linux
^C有一个信号被捕捉了: 2
zk正在学习linux
^C有一个信号被捕捉了: 2
zk正在学习linux
^C有一个信号被捕捉了: 2
zk正在学习linux
^C有一个信号被捕捉了: 2
zk正在学习linux
zk正在学习linux
zk正在学习linux
^\Quit

发现可以一直屏蔽了

SA_NOCLDSTOP
如果设置了这个标志,当子进程停止(通常是因为接收到 SIGSTOP、SIGTSTP、SIGTTIN 或 SIGTTOU 信号)或重新开始执行(通常是因为接收到 SIGCONT 信号)时,将不会向父进程发送 SIGCHLD 信号。
这个标志通常用于控制对子进程状态改变的通知方式。
SA_NOCLDWAIT
当设置这个标志时,如果子进程终止,系统不会创建僵尸进程(zombie process),且子进程的退出状态也不会被保存。这意味着父进程不需要(也不能)等待子进程结束来回收其资源。
这可以用来避免在某些长时间运行的程序中积累僵尸进程。
SA_NODEFER
默认情况下,在信号处理函数执行期间,正在处理的信号会被自动添加到进程的信号掩码中,防止信号处理函数被相同的信号中断。如果设置了 SA_NODEFER(或 SA_NOMASK),则不会自动阻塞正在处理的信号。
这允许信号处理函数被同一信号再次中断。
SA_ONSTACK
指示如果进程有一个备用信号栈(通过 sigaltstack 设置),则信号处理函数应在该栈上运行。
这对于在常规栈空间不足时安全处理信号非常有用,可以防止栈溢出导致的问题。
SA_RESETHAND
当设置此标志时,信号处理函数被调用后,该信号的处理方式将被重置为默认值。这意味着如果同一个信号再次发生,程序将执行默认的信号处理行为。
这可以用来确保信号处理函数只被调用一次。
SA_RESTART
使被信号中断的某些系统调用(如 read(), write(), select() 和 open())自动重新开始,而不是返回错误。
这可以使程序更容易编写,因为不需要处理这些系统调用可能因信号而失败的复杂性。
SA_SIGINFO
允许使用更复杂的信号处理函数,该函数可以接收额外的信号相关信息。设置此标志后,应使用 sa_sigaction 成员而非 sa_handler 作为信号处理函数,并且 siginfo_t 结构将提供关于信号的详细信息,如发送信号的进程ID、信号代码等。
这对于需要根据信号的具体来源或原因做出反应的程序特别有用。

可重入函数

在这里插入图片描述
main函数调用insert函数向一个链表head中插入节点node1,插入操作分为两步,刚做完第一步的 时候,因
为硬件中断使进程切换到内核,再次回用户态之前检查到有信号待处理,于是切换 到sighandler函
数,sighandler也调用insert函数向同一个链表head中插入节点node2,插入操作的 两步都做完之后从
sighandler返回内核态,再次回到用户态就从main函数调用的insert函数中继续 往下执行,先前做第一步
之后被打断,现在继续做完第二步。结果是,main函数和sighandler先后 向链表中插入两个节点,而最后只
有一个节点真正插入链表中了。
像上例这样,insert函数被不同的控制流程调用,有可能在第一次调用还没返回时就再次进入该函数,这称
为重入,insert函数访问一个全局链表,有可能因为重入而造成错乱,像这样的函数称为 不可重入函数,反之,
如果一个函数只访问自己的局部变量或参数,则称为可重入(Reentrant) 函数。想一下,为什么两个不同的
控制流程调用同一个函数,访问它的同一个局部变量或参数就不会造成错乱?

volatile关键字

#include <stdio.h>
#include <signal.h>
int flag = 0;
void handler(int sig)
{
    printf("chage flag 0 to 1\n");
    flag = 1;
}
int main()
{
    signal(2, handler);
    while(!flag);
    printf("process quit normal\n");
    return 0;
}

结果

[zk@VM-24-17-centos lesson25]$ ./test 
^Cchage flag 0 to 1
process quit normal

但是我们这里把编译优化选择一下makefile文件

test:test.c
	gcc -o $@ $^  -O2
.PHONY:clean
clean:
	rm -f test

结果,发现我们2好捕捉,值改了,但是我们还是不能终止循环。

[zk@VM-24-17-centos lesson25]$ ./test 
^Cchage flag 0 to 1
^Cchage flag 0 to 1
^Cchage flag 0 to 1
^Cchage flag 0 to 1
^Cchage flag 0 to 1
^Cchage flag 0 to 1
^\Quit

加上这个关键字。

volatile flag = 0;
[zk@VM-24-17-centos lesson25]$ ./test 
^Cchage flag 0 to 1
process quit normal

优化情况下,键入 CTRL-C ,2号信号被捕捉,执行自定义动作,修改 flag=1 ,但是 while 条件依旧满足,进
程继续运行!但是很明显flag肯定已经被修改了,但是为何循环依旧执行?很明显, while 循环检查的flag,
并不是内存中最新的flag,这就存在了数据二异性的问题。 while 检测的flag其实已经因为优化,被放在了
CPU寄存器当中。如何解决呢?很明显需要volatile

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

SIGCHLD信号 - 选学了解

进程一章讲过用wait和waitpid函数清理僵尸进程,父进程可以阻塞等待子进程结束,也可以非阻 塞地查询是否有子进
程结束等待清理(也就是轮询的方式)。采用第一种方式,父进程阻塞了就不 能处理自己的工作了;采用第二种方式,父
进程在处理自己的工作的同时还要记得时不时地轮询一 下,程序实现复杂。
其实,子进程在终止时会给父进程发SIGCHLD信号,该信号的默认处理动作是忽略,父进程可以自 定义SIGCHLD信号
的处理函数,这样父进程只需专心处理自己的工作,不必关心子进程了,子进程 终止时会通知父进程,父进程在信号处理
函数中调用wait清理子进程即可。
请编写一个程序完成以下功能:父进程fork出子进程,子进程调用exit(2)终止,父进程自定 义SIGCHLD信号的处理函数,
在其中调用wait获得子进程的退出状态并打印。
事实上,由于UNIX 的历史原因,要想不产生僵尸进程还有另外一种办法:父进程调 用sigaction将SIGCHLD的处理动作
置为SIG_IGN,这样fork出来的子进程在终止时会自动清理掉,不 会产生僵尸进程,也不会通知父进程。系统默认的忽
略动作和用户用sigaction函数自定义的忽略 通常是没有区别的,但这是一个特例。此方法对于Linux可用,但不保证
在其它UNIX系统上都可 用。请编写程序验证这样做不会产生僵尸.

意义就是,我们知道了子进程发来的信信号,于是我们就可以在这个时候选择wait 和 waitpid操作。
但是这里要采用非阻塞式等待。因为我们采用阻塞式等待的话,程序和代码就会卡在相应的位置,而且子进程一般不会同一时间一下僵尸完,我们要用WNOHAN的方式。

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值