Linux学习记录——이십이 进程信号(1)

news2024/10/6 12:31:53

文章目录

  • 1、了解信号
  • 2、了解信号处理
  • 3、信号产生
    • 1、键盘按键产生
    • 2、系统接口产生
    • 3、软件条件产生
    • 4、硬件异常
  • 4、Core和Term的区别
  • 5、信号保存
    • 1、在系统中的表现形式
    • 2、信号集操作函数
      • 1、sigprocmask
      • 2、sigpending
  • 6、重谈地址空间
  • 7、信号处理与捕捉
    • sigaction


1、了解信号

信号没有产生前,进程在没有收到这个信号前,进程也知道怎么处理。

进程能够认识并处理一个信号,说明进程是能识别这个信号的。

进程是怎么做到这些的?是靠程序员来设计识别信号的能力。

信号可能随时产生,所以在信号产生前,如果进程在做优先级更高的事情,就不会立马处理这个信号,会在之后合适的时间再去处理。信号产生和信号处理之间有个时间窗口,在这段时间内,信号会被保存起来,所以进程需要有记录信号的能力。

在这里插入图片描述

这是linux中所有的信号,1 - 31是普通信号,34 - 64是实时信号,数字就是信号,后面的是信号名称,其实就是宏。但是会发现没有0, 32, 33号信号。

信号的产生对于进程来说是异步的,意思是进程不太关心信号的产生,信号产生就产生,进程继续做自己的东西,识别信号也不是什么重要的事情。

进程记录信号是先描述,再组织。分时操作系统像Linux,Windows,追求公平调度;实时操作系统则要求高响应,新的任务会优先给到反馈。普通信号的保存只保存有无产生,而实时信号则是立马处理,可以发出很多,并且都能保存下来。

0表示无,1表示有,用位图结构来保存信号,1 - 31, 34 - 64,正好一个4字节二进制数。在进程的pcb内部有一个位图结构去保存信号,发送信号时,其实就是写入信号,会修改进程的信号位图中的对应的比特位。比特位的位置就是信号的编号,比特位的内容表示是否收到信号。改变位图结构的只能是系统,无论信号如何产生,最后都由系统来发送信号,也就是写入信号,改变位图结构。

2、了解信号处理

信号的处理有默认处理,忽略信号,用户自定义等方式。

一个进程执行时,此时无法执行其他命令,因为这时候这个进程是前台进程,系统只会去运行它,一般bash调起的进程会是前台进程,如果./程序名 &,这个进程就变成后台程序了,执行其它命令也可以。

前台进程执行时,我们可以Ctrl + C停掉这个进程,本质也是发送信号,但如何保证一按这个键杀掉的就是这个进程呢?

在这里插入图片描述

有一个函数signal,返回值是函数指针类型,参数为int类型;signal的参数里,第一个参数是信号编号,第二个则是要处理的方式。快捷键就调用了这个函数。

在这里插入图片描述

像这样,程序运行时,不断按Ctrl + c就会打印handler里的内容,系统会自动把2分配给signo。想要终止进程,可以用Ctrl + \来发送3号信号,当然也可以把3号信号变成自定义动作,+ C是2号信号。

无论怎样,kill -9不会被替代,它是管理员信号,可以用它来直接杀掉进程。

3、信号产生

1、键盘按键产生

CPU有很多针脚来接到主板上,键盘有一定的硬件连接到CPU,比如中断控制器。键盘有一个键被按下时就立马通过中断控制器等向CPU发送中断号,CPU内部有相应的寄存器,如果有传过来中断号,寄存器里就存入这个号。这是硬件中断。

系统维护了一张中断向量表,里面有很多指针,中断号就是对应的下标,执行对应的方法,从键盘中读取对应的数据,比如a b, 比如shift, 比如Ctlr + C等等,这些操作是系统做的,它会把这些读到的数据转换成信号,然后找前台进程,写入2号进程。实际上读取中断号等等靠的是驱动。

2、系统接口产生

kill命令,两个参数,一个是进程pid,一个几号信号。

模拟实现: https://gitee.com/kongqizyd/linux-beginner/blob/master/mysignal/mykill.cc(48行以下)

raise命令,只有一个参数sig,谁调用这个接口,就给谁发信号。

在这里插入图片描述

先捕捉信号,然后再用raise发送信号,就会看到它每隔1s自动发一次信号,signal除了传信号编号,也可以传信号名。

abort函数,void abort(void),自己给自己发6信号,假如6信号被改变处理方式了,执行一遍后abort必须退出,它会用别的方法去结束进程。

在这里插入图片描述

3、软件条件产生

指的是因为在软件层面上不符合某些要求,导致这个进程做的工作无意义,那么会收到信号。

alarm函数,调用alarm函数可以设定一个闹钟,也就是告诉内核在second秒之后给当前进程发送SIGALRM信号,该信号的默认处理动作时终止当前进程,函数返回值是0或者是以前设定的闹钟时间还余下的秒数。

在这里插入图片描述

会在一秒之后结束进程,但多运行几次后就会发现每次最后的count数都不一样。这里是在计算1s内计算机能将一个整数累计到多少,但这很不正确,因为有网络,IO等影响因素,减少IO影响可以这样

在这里插入图片描述

count为全局变量。对比两个结果就会发现IO的效率很低下。闹钟是一次性的。如果想持续闹钟,可以在myhandler打印语句后写一个alarm(1)。

对于另一种返回值:

在这里插入图片描述

开另一个窗口,在等待过程中手动发一遍信号,就会看到返回上一个闹钟剩余的值。想取消闹钟就alarm(0)。

系统对于闹钟也是先描述再组织,因为有很多进程都会开闹钟。系统里有一个闹钟结构体,里面有各种属性,比如timestamp,就是当前时间+未来设置的时间

4、硬件异常

硬件异常被硬件以某种方式被硬件检测到并通知内核,然后内核向当前进程发送适当的信号。例如当前进程执行了除以0的指令,CPU的运算单元会产生异常,内核将这个异常解释 为SIGFPE信号发送给进程。再比如当前进程访问了非法内存地址,MMU会产生异常,内核将这个异常解释为SIGSEGV信号发送给进程。

以一个除0操作来举例子。代码中创建变量n,然后让它 /= 0。在CPU的寄存器中,会存入n,0,然后进行操作 /=。在进行计算时,有一个状态寄存器去存储本次计算是否有溢出等问题,当然也有寄存器来存储结果。如果有溢出,状态寄存器的计数标志位就由0变为1,随后系统会检查到这个异常位置,CPU也知道,系统会发送信号,浮点数异常,8号信号,进程接受信号后就会执行默认信号Core,也就是终止进程。

在运行进程时,CPU里还会有寄存器去存储pcb地址,当出现异常是,系统就会在这里面找对应的进程,具体的之后再写。

除0的本质就是触发硬件异常。

为了验证这个信号,我们可以使用signal接口来自定义处理动作,但如果这样运行,程序会一直运行,停不下来。这是因为进程出现异常后,收到8号信号后,就做我们自定义的动作,它并没有退出,标志位还是1。所以自定义处理动作里也得有退出动作exit(1)。

看这个代码

int* p = nullptr;
*p = 100;

典型的野指针问题。在内部,p指向的地址是在地址空间的,它会通过页表映射到物理内存中。页表进行查表的动作是通过MMU硬件完成,内存管理单元,先把映射关系放到MMU里,再去映射到物理内存中。

现在*p = 100。那么地址是否有映射关系,我们对映射关系是否有写的权限?实际上是没有的,只能读,所以直接错了。当执行这行代码时,会先找到这块空间,也就是进行虚拟到物理地址的转换,如果没有映射关系,那么MMU硬件报错;如果有映射关系,但是没有写权限,那么就无法把新值放进内存中,所以MMU报错。系统会收到这个报错,然后向进程写入信号。

像野指针,越界等这样的段错误是11号信号,SEGV就是代表段错误。所有信号可以通过kill -l查看。处理动作是Core。

MMU是集成在CPU里的.

4、Core和Term的区别

刚才提到了Core,除此之外,信号的默认处理动作该有Term。

一个进程的status中,低8位表示退出状态,另有7位表示收到的终止信号,而中间那一位就是core dump标志,0或者1.

Linux系统级别提供了一种能力,一个进程在异常的时候,系统可以将核心代码进行转储,将内存中的相关数据全部dump(转储)到磁盘中,一般会在当前进程的运行目录下,形成core.pid这样的二进制文件。

不过云服务默认关闭了这个功能,ulimit -a可以查看当前系统特定资源的上限,不过不一定准。

在这里插入图片描述
会发现core file size为0。我们可以这样设置ulimit -c 数字。

在这里插入图片描述

测试一下

在这里插入图片描述

运行后,用默认处理动作为Core的信号来终止进程,会发现目录出现了一个新文件,core.pid文件,并且在结束进程时报出的错误后面有(core dumped)。

这里区分出了Term和Core,Term只是终止进程,而Core会进程核心转储。

核心转储有什么用?方便异常后进行调试,不过默认程序生成时是release,在生成时,g++语句最后加个-g就行。进入gdb后,core file core.pid命令就可以直接看到都有什么错。

云服务器核心转储为什么要默认关闭?云服务器是生产环境,还有开发环境,测试环境。测试会在开发环境测试,通过后放到生产环境形成成品服务于人。转储文件是比较大的,在服务器上一个程序挂掉,会有专门的检测程序去重启它,如果这个程序一直挂掉,就要一直重启,就会有大量的core dumped文件产生,对于磁盘的占用就纯属浪费空间,所以在云服务器上核心转储要关掉。

回到一开始的core dumped标志位,为0就是没开,为1就是打开了,信号处理动作如果是Term那就是0,如果是Core,那么设置为1和0对于整体的status各有什么影响?我们可以用父子进程来验证,子进程造一个野指针问题,父进程获取子进程信息。如果是设置为1,那么标志位上确实是1,如果没有,就是0.

5、信号保存

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

1、在系统中的表现形式

在进程的pcb中,系统会维护三张表。

pending表

位图结构。比特位的位置,表示哪一个信号,比特位的内容代表是否收到该信号。系统发信号就是在这个表中修改位图结构。pending就是一个32位数字,pending |= (1 << (signo - 1))。

block表

位图结构。比特位的位置,表示哪一个信号;比特位的内容代表对应的信号是否被阻塞,1表示被屏蔽,0表示可处理,不屏蔽

handler表

函数指针数组,指针类型是void(*sighandler_t)(int),该数组下标表示信号编号,下标对应的内容表示信号的递达动作。

进程靠这三张表识别信号。

在这里插入图片描述

报错,终止,忽略动作。

在这里插入图片描述

每个信号都有两个标志位分别表示阻塞(block)和未决(pending),还有一个函数指针表示处理动作。信号产生时,内核在进程控制块中设置该信号的未决标志,直到信号递达才清除该标志。在上图的例子中,SIGHUP信号未阻塞也未产生过,当它递达时执行默认处理动作。
SIGINT信号产生过,但正在被阻塞,所以暂时不能递达。虽然它的处理动作是忽略,但在没有解除阻塞之前不能忽略这个信号,因为进程仍有机会改变处理动作之后再解除阻塞。
SIGQUIT信号未产生过,一旦产生SIGQUIT信号将被阻塞,它的处理动作是用户自定义函数sighandler。
如果在进程解除对某信号的阻塞之前这种信号产生过多次,将如何处理?POSIX.1允许系统递送该信号一次或多次。Linux是这样实现的:常规信号在递达之前产生多次只计一次,而实时信号在递达之前产生多次可以依次放在一个队列里。

sigset_t

从上图来看,每个信号只有一个bit的未决标志,非0即1,不记录该信号产生了多少次,阻塞标志也是这样表示的。因此,未决和阻塞标志可以用相同的数据类型sigset_t来存储,sigset_t称为信号集,这个类型可以表示每个信号的“有效”或“无效”状态,在阻塞信号集中“有效”和“无效”的含义是该信号是否被阻塞,而在未决信号集中“有效”和“无效”的含义是该信号是否处于未决状态。阻塞信号集也叫做当前进程的信号屏蔽字(Signal Mask),这里的“屏蔽”应该理解为阻塞而不是忽略。

2、信号集操作函数

#include <signal.h>
int sigemptyset(sigset_t *set); 清空
int sigfillset(sigset_t *set); 全部设为1
int sigaddset (sigset_t *set, int signo); 信号添加
int sigdelset(sigset_t *set, int signo); 删除信号
int sigismember(const sigset_t *set, int signo); 判断信号是否存在

1、sigprocmask

调用函数sigprocmask可以读取或更改进程的信号屏蔽字(阻塞信号集)(bloc-、k)。

#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oset);
返回值:若成功则为0,若出错则为-1

how是怎么改,*set有三个参数

SIG_BLOCK 添加信号,相当于与mask = mask | set。
SIG_UNBLOCK 删除某个信号,相当于mask = mask & ~set。
SIG_SETMASK 设置当前信号屏蔽字为set所指向的值,相当于 mask = set。
信号屏蔽字是一个管理block的sigset_t对象,管理pending的是pending信号集。

第三个参数*oset是输出型参数,会返回改之前的信号集。

哪个进程调用这个接口,就设置谁的信号集。

写一个代码来理解这个函数

在这里插入图片描述

2、sigpending

查看pending表。

根据上面的接口,我们可以写个demo样例

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

static void handler(int signo)
{
    cout << "对特定信号: " << signo << "执行捕捉动作" << endl;

}

static void PrintPending(const sigset_t &pending)
{
    for(int signo = 1; signo <= 31; signo++)
    {
        if(sigismember(&pending, signo)) cout << "1";
        else cout << "0";
    }
    cout << "\n";
}

int main()
{
    //1、2信号,进程的默认处理动作是终止进程
    //2、signal可以进行对指定的信号设定自定义处理动作
    //3、signal(2, handler)调用完这个函数的时候,handler方法被调用了吗?没有,只是更改了2信号的处理方式,并没有调用handler,需要在handler内部调用一下handler函数才会执行handler函数
    /*signal(2, handler);
    while(true)
    {
        std::cout << "我是一个进程,我正在运行, pid: " << getpid() << endl;
        sleep(1);
    }*/
    //1、屏蔽2号信号
    sigset_t set, oset;
    //1、1 初始化
    sigemptyset(&set);
    sigemptyset(&oset);
    //1、2 将2号信号添加到set中
    sigaddset(&set, SIGINT);
    //1、3 将新的信号屏蔽字设置进程
    sigprocmask(SIG_BLOCK, &set, &oset);
    //2、 while获取进程的pending信号集合,并按照01打印
    while(true)
    {
        //2、1 设置2号信号的自定义捕捉
        signal(2, handler);
        sigset_t pending;
        sigemptyset(&pending);
        int n = sigpending(&pending);
        assert(n == 0);
        (void)n;
        //2、2 打印pending信号集
        PrintPenging(pending);
        //2、3 休眠一下
        sleep(1);
        //2、4 10s之后,恢复对所有信号的block动作
        if(cnt++ == 10)
        {
            cout << "解除对2号信号的屏蔽" << endl;

        }
    }
    return 0;
}

6、重谈地址空间

之前写到过,系统会在适当的时候去处理信号。

信号可以立即被处理,如果一个信号之前被block,当它解除block的时候,对应的信号会被立即递达。这是一种特殊情况,大多数的时候,信号不是立即处理的,信号的产生是异步的,当前进程可能正在做更重要的事情。

那么什么是适当的时候呢?当进程从内核态切换回用户态的时候,进程会在系统的指导下,进行信号的检测与处理。

什么是用户态和内核态?执行用户写的代码的时候,进程所处的状态是用户态;执行系统的代码的时候,进程所处的状态是内核态。什么是系统代码?比如进程时间片到了,系统就会切换进程;系统调用接口等等。我们写的代码运行的时候,系统的代码也在运行,系统代码怎么运行的?

在地址空间中,内核空间有1G,用户空间有3G,用户空间就有栈,堆,共享区等等,内核空间在高地址处,用户空间在低地址处。之前所写的页表,链接关系,物理内存等等,都是在用户角度写的,页表也是用户级页表,那操作系统在哪里?开机的时候,系统得把自己的东西加载到物理内存中,从哪开始加载的?其实系统也在地址空间,而系统也有自己的内核级页表,从内核空间的那1G中找到系统的代码和数据,然后通过内核级页表,加载到物理内存中。对于所有的进程,0-3GB是不同的,是用户空间,但3-4GB,也就是那个内核空间,是一样的,无论进程如何切换,都不影响这个1G空间,所有进程都可以通过统一的窗口看到同一个系统。所以系统调用的本质,就是在自己的地址空间中进行函数跳转并返回即可。

但这里就有一个问题,用户可以访问系统调用接口,那系统的代码和数据呢?这些东西不能让用户访问到,所以就定义了用户态和内核态,只要访问用户空间,就是用户态,如果访问到内核空间,系统会检查身份,如果不是内核态,那么一些非法的操作就不被执行。在CPU中,有一个CR3寄存器,如果对应的比特位是3,就代表正在运行的进程级别是用户态,是0就表示是内核态。谁来更改这个比特位?肯定不能让用户修改,但是还得给用户提供调用接口,怎样控制用户根本接触不到系统的数据?操作系统提供的所有的系统调用接口中,内部在正式执行调用逻辑的时候,会去修改执行级别。

清楚这些后,看这个问题,进程是如何被调度的?先看下面所写。

在一些老的计算机中,调起系统后,它会有一个1号进程,这是在没有任何一个进程时系统自己打开的进程。

当我们在计算机上操作的时候,即使什么都不做,系统也会在管理一些数据,管理整个计算机。如果我们打开某个软件,系统就会打开,加载,无论我们做什么,系统都有反应,且及时。操作系统是怎么做到的?实际上,系统的本质是一个软件,是一个死循环的软件,刚才写的1号进程就是系统在调用自己;系统能够无时无刻处理我们的操作,也是因为有一些硬件在帮忙,比如时钟硬件,主板上有一个纽扣电池,它一直在给时钟硬件充电,当电脑关机时,电脑还有硬件在运行,这样当你下一次打开电脑的时候,会发现时间是对的,而不是上一次关机时的时间。这个时钟硬件每隔很短的时间向系统发送中断,系统1就会执行对应的中断处理方法。

回到上面的问题,进程被调度的本质就是时间片到了,系统将进程对应的上下文等进行保存并切换,选择合适的进程,做这些事情的是schedule函数。系统的中断处理方法是检测当前进程的时间片,进程pcb中有调度时间,系统拿到这一次的减去上一次的,如果超过规定的时间,就让进程调用schedule函数,然后把其他进程的地址空间换过来,运行下一个进程就好。

所以系统调用的本质也就清楚了。当进入内核空间执行系统接口时,此时的用户就不是用户了,而是系统,系统借助这个执行流区执行自己的一些代码,返回来时改一下执行级别,变回了用户级别。

总结

什么时候从用户态到内核态?要检查时间片,开始调度进程;调用系统调用接口。

什么时候从内核态到用户态?调用接口结束后,此时信号也会被处理。

7、信号处理与捕捉

内核态转回到用户态时不是直接回去,而是先去检查进程pcb中的信号相关部分,然后执行处理动作。这之中自定义处理动作比较特殊,当检测到自定义时,就会转到相应的函数去运行代码,之后回到pcb中,然后再回到用户态,再去执行下一个代码。

这里还有一些细节。当跳转到handler函数,应该以什么状态去执行?用户态,因为系统不相信用户,它不知道handler函数里做了什么,所以要转换成用户态。当运行完函数代码后,不能直接跳到下一个要执行的代码,因为在用户态转为内核态时中断的位置只有系统,所以还要通过系统调用,再次嵌入内核,转为内核态,这个调用是系统自己做的,这个接口就是sys_sigreturn()。

进程不会一直在内核态,即使一直死循环,系统也会把你给拉出来。

如果信号的处理动作是用户自定义函数,在信号递达时就调用这个函数,这称为捕捉信号。由于信号处理函数的代码是在用户空间的,处理过程比较复杂,举例如下: 用户程序注册了SIGQUIT信号的处理函数sighandler。 当前正在执行main函数,这时发生中断或异常切换到内核态。 在中断处理完毕后要返回用户态的main函数之前检查到有信号SIGQUIT递达。 内核决定返回用户态后不是恢复main函数的上下文继续执行,而是执行sighandler函 数,sighandler和main函数使用不同的堆栈空间,它们之间不存在调用和被调用的关系,是 两个独立的控制流程。 sighandler函数返回后自动执行特殊的系统调用sigreturn再次进入内核态。 如果没有新的信号要递达,这次再返回用户态就是恢复main函数的上下文继续执行了

sigaction

对于pending和block表都有对应的函数,对于handler表除了之前的signal函数,还有sigaction函数。

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

在这里插入图片描述

    struct sigaction act, oldact;
    memset(&act, 0, sizeof(act));
    memset(&oldact, 0, sizeof(oldact));
    act.sa_handler = handler;
    act.sa_flags = 0;
    sigaction(2, &act, &oldact);
    while(true)
    {
        sleep(1);
    }
//与之前的signal同样作用的代码

当某个信号的处理函数被调用时,内核自动将当前信号加入进程的信号屏蔽字,当信号处理函数返回时自动恢复原来的信号屏蔽字,这样就保证了在处理某个信号时,如果这种信号再次产生,那么它会被阻塞到当前处理结束为止。 如果在调用信号处理函数时,除了当前信号被自动屏蔽之外,还希望自动屏蔽另外一些信号,则用sa_mask字段说明这些需要额外屏蔽的信号,当信号处理函数返回时自动恢复原来的信号屏蔽字。

为了看一下mask字段的作用,这样改代码

sigemptyset(&act.sa_mask);
sigaction(2, &act, &oldact);

在action之前写上一行代码,然后handler内部

static void handler(int signo)
{
    cout << "对特定信号: " << signo << "执行捕捉动作" << endl;
    int cnt = 10;
    while(cnt)
    {
        cnt--;
        sigset_t pending;
        sigemptyset(&pending);
        sigpending(&pending);
        PrintPending(pending);
        sleep(1);
    }
}

通过这个就能看出来,在调用handler之前,pending表中对应的信号就由1变为0了。

现在要求屏蔽2号信号时顺便把其他信号给屏蔽了。

    sigemptyset(&act.sa_mask);
    sigaddset(&act.sa_mask, 3);
    sigaddset(&act.sa_mask, 4);
    sigaction(2, &act, &oldact);

cnt改长一些,在while(true)可以打印pid。

static void handler(int signo)
{
    cout << "对特定信号: " << signo << "执行捕捉动作" << endl;
    int cnt = 30;
    while(cnt)
    {
        cnt--;
        sigset_t pending;
        sigemptyset(&pending);
        sigpending(&pending);
        PrintPending(pending);
        sleep(1);
    }
}

static void PrintPending(const sigset_t &pending)
{
    for(int signo = 1; signo <= 31; signo++)
    {
        if(sigismember(&pending, signo)) cout << "1";
        else cout << "0";
    }
    cout << "\n";
}

int main()
{
    struct sigaction act, oldact;
    memset(&act, 0, sizeof(act));
    memset(&oldact, 0, sizeof(oldact));
    act.sa_handler = handler;
    act.sa_flags = 0;
    sigemptyset(&act.sa_mask);
    sigaddset(&act.sa_mask, 3);
    sigaddset(&act.sa_mask, 4);
    sigaction(2, &act, &oldact);
    while(true)
    {
        cout << getpid() << endl;
        sleep(1);
    }
        //1、2信号,进程的默认处理动作是终止进程
    //2、signal可以进行对指定的信号设定自定义处理动作
    //3、signal(2, handler)调用完这个函数的时候,handler方法被调用了吗?没有,只是更改了2信号的处理方式,并没有调用handler,需要在handler内部调用一下handler函数才会执行handler函数
    /*signal(2, handler);
    while(true)
    {
        std::cout << "我是一个进程,我正在运行, pid: " << getpid() << endl;
        sleep(1);
    }*/
    //1、屏蔽2号信号
    /*sigset_t set, oset;
    //1、1 初始化
    sigemptyset(&set);
    sigemptyset(&oset);
    //1、2 将2号信号添加到set中
    sigaddset(&set, SIGINT);
    //1、3 将新的信号屏蔽字设置进程
    sigprocmask(SIG_BLOCK, &set, &oset);
    //2、 while获取进程的pending信号集合,并按照01打印
    while(true)
    {
        //2、1 设置2号信号的自定义捕捉
        signal(2, handler);
        sigset_t pending;
        sigemptyset(&pending);
        int n = sigpending(&pending);
        assert(n == 0);
        (void)n;
        //2、2 打印pending信号集
        PrintPenging(pending);
        //2、3 休眠一下
        sleep(1);
        //2、4 10s之后,恢复对所有信号的block动作
        if(cnt++ == 10)
        {
            cout << "解除对2号信号的屏蔽" << endl;
            
        }
    }*/
    return 0;
}

结束。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

数据挖掘、数据分析——异常值处理、归一化处理

数据&#xff1a; # 下载需要用到的数据集 !wget http://tianchi-media.oss-cn-beijing.aliyuncs.com/DSW/Industrial_Steam_Forecast/zhengqi_test.txt !wget http://tianchi-media.oss-cn-beijing.aliyuncs.com/DSW/Industrial_Steam_Forecast/zhengqi_train.txt数据异常值分…

【Python】flask实现登录注册

一、jinjia2 1、控制结构 控制结构 Flask中的Jinja2模板提供了多种控制结构&#xff0c;通过这些控制结构可以改变模板的渲染过程。例如&#xff0c;下面展示的条件控制语句。 2、使用flask成功渲染到模板 【首先你要】 首先要创建一个templates目录&#xff0c;这里面放想要渲…

构造函数的复习,析构函数,拷贝构造函数与由此关于引用的思考

TIPS 在类当中不受访问限定符的限制&#xff0c;在类外面才会受到限制由于内存栈区的使用习惯是先使用高地址&#xff0c;再使用低地址&#xff1b;因此比方说有两个实例化对象依次创建&#xff0c;并且这两个实例化对象当中都有析构函数&#xff0c;也就是当退出销毁的时候&a…

MySQL数据库基础表格——增删改查(上)

♥️作者&#xff1a;小刘在C站 ♥️个人主页&#xff1a;小刘主页 ♥️每天分享云计算网络运维课堂笔记&#xff0c;努力不一定有收获&#xff0c;但一定会有收获加油&#xff01;一起努力&#xff0c;共赴美好人生&#xff01; ♥️树高千尺&#xff0c;落叶归根人生不易&…

有哪些网络安全小知识可以科普?

每日畅游在网络世界中的你&#xff0c;可曾遇到过计算机莫名中毒、文档意外丢失、黑客异常攻击、网络行骗诈骗、个人信息泄露等风险和危害&#xff1f;一起来看看这些你不得不知道的网络安全小知识吧&#xff01; 如何防范病毒或木马的攻击&#xff1f; 1. 为计算机安装杀毒软…

synchronized 关键字基础总结

synchronized 关键字 说一说你对 synchronized 关键字的理解&#xff1f; synchronized 翻译成中文是同步的的意思&#xff0c;主要解决的是多个线程之间访问资源的同步性&#xff0c;可以保证被它修饰的方法或者代码块在任意时刻只能有一个线程执行。 在 Java 早期版本中&a…

第一眼看到就喜欢上了

有多少小伙伴是被标题 骗 吸引进来的呢&#xff0c;小编可不是标题党。 今天给大家推荐的是一款开源3D 博客&#xff0c;确实是第一眼看到就喜欢上了。 相信大家跟我一样&#xff0c;曾经可能花费大量的时间和精力打造过自己的专属博客。只为自己的博客看上去与众不同&#x…

2023Java商城毕业设计(附源码和数据库文件下载链接)Spring Boot + mysql + maven + mybatis-plus

2023Java商城毕业设计Spring Boot mysql maven mybatis-plus 用户注册用户登录修改密码商品列表&#xff08;分类模糊查询&#xff09;个人信息用户信息修改订单信息添加至购物车商品列表商铺详情商品详情商铺列表 资源目录如下&#xff1a;&#xff08;源码sql文件&#xf…

scratch判断亲和数 中国电子学会图形化编程 少儿编程 scratch编程等级考试四级真题和答案解析2023年3月

目录 scratch判断亲和数 一、题目要求 1、准备工作 2、功能实现 二、案例分析 <

案例分享|CPU监控异常

CPU使用率监控很关键&#xff0c;综合反应系统的负载情况&#xff0c;是监控的重要指标之一。CPU的使用率&#xff0c;对业务系统性能有重要的影响&#xff0c;根据CPU使用率监控&#xff0c;可以对系统或应用进一步分析调优。 4月25日22点&#xff0c;平台收到某县级医院HIS数…

回炉重造十二----网络文件共享服务

网络文件共享服务 1、FTP文件传输协议 1.1 FTP工作原理 FTP的20和21端口的区别 20端口是用来传输数据的 21端口是客户端用来连接FTP服务器 主动模式&#xff08;PORT&#xff09;&#xff1a; 客户端连接到FTP服务端的21号端口&#xff0c;发送用户名和密码当客户端成功登…

对线面试官,JUC面试专题强化

一、AQS高频问题 1.1 AQS是什么&#xff1f; AQS是JUC下大量工具的基础类&#xff0c;很多工具都基于AQS实现的&#xff0c;比如lock锁&#xff0c;CountDownLatch&#xff0c;Semaphore&#xff0c;线程池等等都用到了AQS。 AQS中有一个核心属性state&#xff0c;还有一个…

Android 自定义View实战—制作一个简易输入框

这次我们来做一个简易输入框&#xff0c;可以用于密码输入和验证码输入。 依然在EasyView中进行创建&#xff0c;在com.easy.view下新建一个EasyEditText&#xff0c;继承自View &#xff0c;实现里面的构造方法。 ① 构造方法 然后我们继承自View&#xff0c;重写里面的构造…

一些良心软件的推荐

推荐软件一&#xff1a;硬盘规划——SpaceSniffer SpaceSniffer 是一款免费的硬盘空间管理软件&#xff0c;可以帮助你快速分析你电脑硬盘中的文件和文件夹&#xff0c;让你更加清楚地了解硬盘的使用情况。通过SpaceSniffer&#xff0c;你可以轻松地找到占用大量空间的文件和文…

Java—JDK8新特性—接口增强

目录 JDK引言 1.相关组织和规范 1.1 JCP (Java Community Process) 1.2 JSR (Java Specification Requests) 1.3 JEP (Java Enhancement Proposal) JDK8新特性 1.接口增强 1.1 默认方法 1.1.1 为什么引入默认方法 1.1.2 如何使用默认方法 1.1.3 如何调用默认方法 1…

用Radare2模拟shellcode运行

当我们在编写汇编时&#xff0c;可能有的时候你需要看看编译器中到底发生了什么。如果你正在排除shellcode出现的问题&#xff0c;你那么更需要耐心地、慎重地运行指令。 本文将探讨如何在x86_64的Ubuntu系统上模拟32位ARM shellcode。由于大多数笔记本电脑和工作站还没有运行…

单篇笔记涨粉8w,10秒视频播放超1000w,小红书最近在流行什么?

四月&#xff0c;小红书平台又涌现出哪些优质博主&#xff1f;品牌在投放种草方面有何亮眼表现&#xff1f; 为洞察小红书平台的内容创作趋势及品牌营销策略&#xff0c;新红推出4月月度榜单&#xff0c;从创作者及品牌两方面入手&#xff0c;解析月榜数据&#xff0c;为从业者…

iOS总结_UI层自我复习总结

UI层复习笔记 在main文件中&#xff0c;UIApplicationMain函数一共做了三件事 根据第三个参数创建了一个应用程序对象 默认写nil&#xff0c;即创建的是UIApplication类型的对象&#xff0c;此对象看成是整个应用程序的一个抽象&#xff0c;负责存储应用程序的状态。根据第四…

SpringBoot访问静态资源

SpringBoot项目中没有WebApp目录&#xff0c;只有src目录。在src/main/resources下面有static和templates两个文件夹。SpringBoot默认在static目录中存放静态资源&#xff0c;而templates中放动态页面。 static目录 SpringBoot通过/resources/static目录访问静态资源&#xff…

怎么衡量纸白银走势图的强弱?

目前国内银行提供的纸白银交易基本实现了全天候连续的交易时间&#xff0c;但由于银行所提供的交易终端的技术分析功能有限&#xff0c;投资者在分析行情时绑手绑脚&#xff0c;因此小编建议大家可以尝试使用国际上主流的MT4的平台&#xff0c;作为观察国际银价走势的参考和技术…