Linux 进程信号【信号产生】

💓博主CSDN主页:麻辣韭菜💓

⏩专栏分类:Linux知识分享⏪

🚚代码仓库:Linux代码练习🚚

🌹关注我🫵带你学习更多Linux知识
  🔝 

目录

 前言

信号概念

 1. 生活角度的信号

2. 技术应用角度的信号 

 ctrl+c如何转化成终止进程的信号

 信号的产生

组合键

 kill命令

系统调用

raise函数

 abort 函数

异常 

除0异常

 野指针异常

   

页表属性      

 核心转储

查看核心转储 

 ​编辑

 核心转储的作用

软件条件 

alarm函数


 

 前言

在前面的进程控制篇章里,父进程是怎么知道子进程退出了?并且回收子进程。 以及我们之前写的代码报错之后,进程就终止了。这背后的一切都是基于信号,那进程中信号是什么?

信号概念

 1. 生活角度的信号

  • 你在网上买了很多件商品,再等待不同商品快递的到来。但即便快递没有到来,你也知道快递来临时,你该怎么处理快递。也就是你能“识别快递
  • 当快递员到了你楼下,你也收到快递到来的通知,但是你正在打游戏,需5min之后才能去取快递。那么在在这5min之内,你并没有下去去取快递,但是你是知道有快递到来了。也就是取快递的行为并不是一定要立即执行,可以理解成“在合适的时候去取
  • 在收到通知,再到你拿到快递期间,是有一个时间窗口的,在这段时间,你并没有拿到快递,但是你知道有一个快递已经来了。本质上是你“记住了有一个快递要去取
  • 当你时间合适,顺利拿到快递之后,就要开始处理快递了。而处理快递一般方式有三种:1. 执行默认动作(幸福的打开快递,使用商品)2. 执行自定义动作(快递是零食,你要送给你你的女朋友)3. 忽略快递(快递拿上来之后,扔掉床头,继续开一把游戏)
  • 快递到来的整个过程,对你来讲是异步的,你不能准确断定快递员什么时候给你打电话

2. 技术应用角度的信号 

         从生活中还有许多的信号比如红绿灯,这里就有个问题了,你是怎么识别这些信号的?肯定是有人教我,我才知道这些信号是什么意思,并且识别这些信号并做出相应的动作处理。即使有些信号没有发生在我面前,我也知道那些没有发生的信号,我也知道该做什么。

        那对于进程而言,也是需要像人一样知道什么信号,接受到信号知道自己该做什么!也会像人一样有信号并不会马上去做。那么也会时间窗口,这就需要进程需要具备保存已经发生信号的能力。 

下面我们用一段简单的代码来演示进程是如何接收到信号,并作出相应的动作。

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

using namespace std;


int main()
{
    while(true)
    {
        cout << "I am process...." << endl;
        sleep(1);
    }
    return 0;
}

代码运行起来变成进程,这时我按ctrl+c这个组合键。你会发现进程终止了。 

​ 

这里有人就会问了,ctrl+c为什么能够在linux中杀掉进程?

要搞清楚,先要明白两个概念:前台进程和后台进程。

键盘输入首先是前台进程获取的,在linux中,一次登陆中,一个终端,一般会配上一个bash,每一个登陆,只允许一个进程是前台进程,但是可以允许多个后台进程。

 如何切换成后台进程?

指令:./进程名 &   

 

切换之后我们在ctrl+c发现终止不了进程了。 

这时候我们需要用 kill -9 pid 杀掉进程。 

 

 从前后进程我们知道信号是OS发给前台进程的,那OS又是如何知道键盘上有数据?键盘的数据如何写入到内核中?以及ctrl+c如何转化成终止进程的信号?

这里就要谈谈硬件了,我们只有从硬件层面才能理解这里面的原理。

  1. 硬件层面:当用户按下键盘上的一个键时,键盘的硬件会检测到这个动作,并将相应的信号发送给计算机。

  2. 中断处理:键盘作为一个输入设备,会通过中断的方式通知CPU,告诉它有输入事件发生了。

  3. 驱动程序:操作系统中有一个专门的键盘驱动程序,负责处理这些中断。当驱动程序接收到中断信号时,它会读取键盘的输入状态,并将其转换为一个扫描码(scan code)。

  4. 转换为键码:扫描码是键盘上每个键的唯一标识符。驱动程序会将扫描码转换为键码(key code),键码通常是与特定字符或命令相关联的。

  5. 输入子系统:键码随后被传递给操作系统的输入子系统,该子系统负责将键码转换为实际的字符或命令。

  6. 字符编码:如果键码对应于一个字符,输入子系统会将其转换为相应的字符编码(如ASCII或Unicode)。

  7. 内核处理:内核会接收到这些字符编码,并根据当前的进程和线程的上下文,将它们传递给相应的应用程序。

  8. 应用程序接收:最终,应用程序的事件循环会捕获到这些字符,应用程序可以据此更新其状态或执行相应的操作。

  9. 缓冲区管理:内核通常会维护一个缓冲区来存储键盘输入,直到应用程序准备好读取它们。

  10. 用户空间与内核空间的交互:在许多操作系统中,用户空间(用户程序运行的地方)和内核空间(操作系统内核运行的地方)是分开的。键盘输入数据需要通过系统调用从用户空间传递到内核空间。

 ctrl+c如何转化成终止进程的信号

在Linux操作系统中,当用户按下Ctrl+C组合键时,会触发一个名为SIGINT(信号中断)的信号。这个信号是由终端驱动程序生成的,然后传递给前台进程组的领导者。以下是Ctrl+C如何转化成进程终止信号的详细步骤:

  1. 用户按下组合键:用户在终端或控制台中按下Ctrl+C

  2. 终端驱动程序识别:终端驱动程序识别到这个特定的组合键,并将其转换为SIGINT信号。

  3. 信号发送:终端驱动程序将SIGINT信号发送给当前在前台运行的进程组的领导者。

 

这里解释一下终端驱动程序,说直白的点就是一个函数指针数组。也是中断向量表,里面存放的就是各种方法地址。

 信号的产生

前面我们演示了可以用组合键和kill命令产生信号。那还有没有其他的方法可以产生信号?

有的 系统调用、异常、软件条件

组合键

ctrl+c     ctrl+\

 kill命令

用kill命令可以查看系统定义的信号列表

指令:kill -l 

上图红色框起来的是普通信号,绿色框起来的是实时信号。关于为什么没有32和33那是历史问题。这里我们重点讲解普通信号,实时信号只有很少一部分场景能用到,比如车祸时的安全气囊弹出。

在Linux中,signal()函数用于为程序设置信号处理函数。当一个进程接收到一个信号时,它可以选择忽略该信号、执行默认操作,或者通过signal()函数设置一个自定义的处理函数。

函数原型如下:

#include <signal.h>

void (*signal(int signum, void (*action)(int)))(int);

参数说明:

  • signum:指定要处理的信号,常见的信号有SIGINT(中断,通常由Ctrl+C产生)、SIGTERM(终止)、SIGKILL(立即终止,不能被捕获或忽略)等。
  • action:指向信号处理函数的指针。如果设置为SIG_IGN,则忽略该信号;如果设置为SIG_DFL,则执行默认操作。

返回值:

  • 成功时,返回先前的信号处理函数的指针;失败时,返回SIG_ERR,并设置errno以指示错误。

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

void myhandler(int signo)
{
    cout << "process get a signal: " << signo << endl;
}

int main()
{
    for(int i = 1; i<=31 ; i++)
    {
        signal(i,myhandler);
    }
    while(true)
    {
        cout << "I am process.... pid: "  << getpid() << endl;
        sleep(1);
    }
    return 0;
}

 这里我就演示了1到9的信号,我们自定义的handler方法对9号信号不起作用。大家下去可以用试试其他的,这里除了9、19这两个信号,其他的信号都可以用自定义的方法来处理信号。有人就会问了为什么9和19不行,你想想如果是一个恶意程序,永不退出。OS不就挂了吗?为了自己的安全,OS的生杀大权还要牢牢掌握在自己手中。

这里还有一个问题从代码来说,你的handler函数是在cout函数前面的,按照执行流程它是不会执行的。它是怎么执行的?就像前面说的你正在打游戏和你等下拿快递是两个流程,是异步的。

系统调用

在Linux中,kill函数用于发送信号给进程。每个信号都有一个特定的编号,并且可以用于不同的目的,比如终止进程、暂停进程等。

函数原型如下:

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

参数说明:

  • pid:要发送信号的目标进程的进程标识符(PID)。如果pid是负数,则信号会被发送到与pid的绝对值相同的进程组中的所有进程。
  • sig:要发送的信号。可以是如SIGKILLSIGTERMSIGSTOP等预定义的信号常量。

SIGKILL

  • 定义SIGKILL是一个强制终止进程的信号,它不能被忽略或被捕获。当一个进程接收到SIGKILL信号时,操作系统会立即结束该进程,不会给进程清理资源或保存状态的机会。
  • 用途:通常用于在进程不响应其他终止信号时强制终止进程。由于SIGKILL不能被捕获或忽略,它是一个强有力的工具,但也应该谨慎使用,因为它不允许进程正常关闭。
  • 发送方式:可以使用kill命令或kill()函数发送SIGKILL信号。

SIGTERM

  • 定义SIGTERM(终止信号)是一种更为温和的终止信号,它允许进程进行清理操作并优雅地关闭。进程可以捕获SIGTERM信号,并执行一些必要的清理工作,如保存数据、释放资源、关闭网络连接等,然后再终止。
  • 用途SIGTERM通常用于请求进程终止,它给进程一个机会来正确地结束。例如,许多服务和守护进程在接收到SIGTERM时会尝试优雅地关闭。
  • 发送方式:可以使用kill命令或kill()函数发送SIGTERM信号。

SIGSTOP

  • 定义SIGSTOP是一个暂停信号,它会使进程停止执行。当进程接收到SIGSTOP信号时,它会立即暂停,直到它接收到SIGCONT(继续信号)才会继续执行。
  • 用途SIGSTOP通常用于调试或控制进程的执行流。它可以暂停一个正在运行的进程,而不会导致进程终止,之后可以使用SIGCONT信号来恢复进程的执行。

返回值:

  • 如果成功,返回0。
  • 如果失败,返回-1,并设置全局变量errno以指示错误。

代码示例

 

#include <iostream>
#include <cstdlib>
#include <string>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
using namespace std;
void Usage(string proc)
{
    cout << "Usage:\n\t" << proc << " signum pid\n\n";
}


int main(int argc, char *argv[])
{
    

    if(argc != 3) //提示用户怎么用kill命令
    {
        Usage(argv[0]);
        exit(1);
    }
    int signum = stoi(argv[1]); //stoi将字符转换成数字
    pid_t pid = stoi(argv[2]);

    int n = kill(pid, signum);
    if(n == -1) //差错处理
    {
        perror("kill");
        exit(2);
    }

    return 0;
}

提示用户怎么使用,这时我们没有进程,直接创建一个进程。试试!

raise函数

 函数原型

#include <signal.h>

int raise(int sig);

 

raise() 函数用于向调用进程或线程发送一个信号。在单线程程序中,它等同于:

kill(getpid(), sig);

而在多线程程序中,它等同于:

pthread_kill(pthread_self(), sig);

如果发送的信号导致调用了信号处理函数,raise() 将在信号处理函数返回后才返回。

返回值(RETURN VALUE)

  • 成功时,raise() 返回0。
  • 出错时,返回非零值。

 

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

void myhandler(int signo)
{
    cout << "process get a signal: " << signo << endl;
    // exit(1);
}

int main(int argc, char *argv[])
{
    signal(2, myhandler);
    int cnt = 0;
    while (true)
    {
        cout << "I am a process, pid: " << getpid() << endl;
        sleep(1);
        cnt++;
        if (cnt % 2 == 0)
        {
            // kill(getpid(), 2);
            raise(2);
            
        
        }
    }
    return 0;
}

 

raise(2)相当于 kill(getpid(),2)

 abort 函数

abort 是 C 语言提供的一个函数,它的作用是 给自己发送 6 号 SIGABRT 信号

 

abort - 导致进程异常终止。

函数原型

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

描述

abort 函数首先解除对 SIGABRT 信号的阻塞,然后为调用进程引发该信号。这将导致进程异常终止,除非 SIGABRT 信号被捕获,并且信号处理函数没有返回(参见 longjmp(3))。

如果 abort() 函数导致进程终止,所有打开的流都会被关闭并刷新。

如果 SIGABRT 信号被忽略,或者被一个返回的处理器捕获,abort() 函数仍将终止进程。它通过恢复 SIGABRT 的默认处理方式,然后再次引发该信号来实现这一点。

返回值

abort() 函数从不返回。

代码示例

 

void myhandler(int signo)
{
    cout << "process get a signal: " << signo << endl;
    // exit(1);
}

int main(int argc, char *argv[])
{
    //signal(2, myhandler);
    signal(SIGABRT, myhandler);
    int cnt = 0;
    while (true)
    {
        cout << "I am a process, pid: " << getpid() << endl;
        sleep(1);
        cnt++;
        if (cnt % 2 == 0)
        {
            // kill(getpid(), 2);
            //raise(2);
            abort();
            //  kill(getpid(), 6);
        }
    }
    return 0;
}

 

异常 

除0异常

 所谓 硬件异常 其实就是我们在写程序最常遇到的各种报错,比如 除 0、野指针

 先来看一段简单的代码

int main()
{
    int a = 10;
    a /= 0;
    return 0;
}

 

点击运行出现错误,我们可以根据报错 用 man 7 signal 查看是几号信号。

 

发现是8号信号,也就是传说中的浮点溢出!!!

 我们自定义捕捉函数,看是不是8号信号

#include <iostream>
#include <cstdlib>
#include <string>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
using namespace std;
void myhandler(int signo)
{
    cout << "process get a signal: " << signo << endl;
    // exit(1);
}
int main()
{
    signal(SIGFPE,myhandler);
    int a = 10;
    a /= 0;
    return 0;
}

 

 结果:一直在死循环似的发送信号,明明只发生了一次 除 0 行为

这时有人就会问了 为什么会一直死循环 

 

 当FPU触发内部异常时,状态寄存器中的相应位会被设置,以指示发生了哪种类型的异常

  1. 一旦检测到异常,处理器的异常处理机制会被激活。这通常涉及到以下几个步骤:

    将当前的状态和一些寄存器的值(如程序计数器、状态寄存器、浮点状态寄存器等)保存到一个安全的地方,通常是堆栈。如上图将状态寄存器的比特位设置为1
  2. 将程序计数器更新为指向异常处理程序的地址。在大多数操作系统中,这个地址是在操作系统的内存中的一个固定位置。  

操作系统会识别这个异常,并将其转换为一个信号(如 SIGFPE)。操作系统会查找该信号的当前处理函数。

如果程序已经注册了一个信号处理函数,操作系统会将控制权传递给这个函数。在信号处理函数执行期间,相关的寄存器和状态可能会被进一步修改。

如果信号处理函数返回到主程序,而主程序中的异常状态没有得到妥善处理,程序可能会重新尝试执行导致异常的指令,从而陷入死循环。

 野指针异常

         代码示例:

#include <iostream>
using namespace std;

int main()
{
    int* ptr = nullptr;
    *ptr = 10;

    return 0;
}

 

 Segmentation fault 段错误 出现段错误问题时,操作系统会发送 11 号 SIGSEGV 信号终止进程

那么 野指针 问题是如何引发的呢?

看下图 

 
野指针两类问题:       

1. 内存访问越界

2.权限不匹配,只读的区域,进行写入操作

在执行 *ptr = 10 这句代码时,首先会进行 虚拟地址 -> 真实(物理)地址 之间的转换

指向不该指向的空间:这很好理解,就是页表没有将 这块虚拟地址空间 与 真实(物理)地址空间 建立映射关系,此时进行访问时 MMU 识别到异常,于是 MMU 直接报错,操作系统识别到 MMU 异常后,向对应的进程发出终止信号

权限不匹配:页表中除了保存映射关系外,还会保存该区域的权限情况,比如 是否命中 / RW 等权限,当发生操作与权限不匹配时,比如 nullptr 只允许读取,并不允许其他行为,此时解引用就会触发 MMU 异常,操作系统识别到后,同样会对对应的进程发出终止信号


   

页表属性      

在操作系统中,页表(Page Table)是用于实现虚拟内存管理的关键数据结构,它将虚拟地址空间映射到物理地址空间。页表中的每个表项(通常称为页表条目或PTE, Page Table Entry)包含了多种属性,这些属性定义了虚拟页和物理页之间的关系以及内存访问的权限和特性。以下是一些常见的页表属性:

  1. 有效位(Valid/Present Bit): 这个位指示页表条目是否有效。如果有效,表示该条目包含指向物理内存的合法地址。

  2. 脏位(Dirty Bit): 当页从物理内存写回磁盘时,脏位用于标记该页自从被加载到物理内存后是否被修改过。

  3. 访问位(Access Bit): 这个位记录了页被访问的记录,可以用来跟踪内存使用模式,对某些调度算法是有用的。

  4. 修改位(Modified Bit): 与脏位类似,修改位也用于指示页内容是否被修改。某些系统可能会使用这个位来优化页面置换算法。

  5. 引用位(Reference Bit): 引用位记录了页是否被访问过,可以用来辅助页面置换算法决定哪些页面应该被换出到磁盘。

  6. 读/写权限(Read/Write Permissions): 这些位定义了页的访问权限,决定了是否可以读取或写入该页。在某些系统中,如果只允许读取,试图写入会触发一个异常。

  7. 用户/超级用户权限(User/Supervisor Bit): 这个位定义了页是否只对操作系统内核模式(超级用户)可用,或者对用户模式也可用。

  8. 物理地址(Physical Address): 页表条目中通常包含指向物理内存帧的地址。

  9. 全局位(Global Bit): 在某些处理器架构中,全局位用于指示该页表条目在所有进程的页表中都是有效的,如在x86的分页机制中。

  10. 锁定位(Locked Bit): 锁定位用于指示页表条目是否应该被锁定在物理内存中,以防止被页面置换算法换出。

  11. 缓存禁用(Cache-Disable Bit): 这个位可以禁止CPU缓存对该页的缓存,直接从物理内存中读取数据。

  12. 写穿/写合并(Write-Through/Write-Combine Bit): 这些位控制内存的写入策略,如是否将写入操作直接反映到物理内存中(写穿),或者先缓存起来再合并写入(写合并)。

页表属性的具体实现和使用会根据操作系统和处理器架构的不同而有所差异。操作系统的内存管理单元(MMU)使用这些属性来控制内存访问和优化性能。


 核心转储

 核心转储(Core Dump)是操作系统在进程异常终止时,如遇到段错误(Segmentation Fault)或其他严重错误时,保存的该进程的内存映像文件。这个文件包含了程序的内存、寄存器、程序计数器等状态信息,可以用于后续的调试和分析,以确定程序崩溃的原因。

  1. 内容: 核心转储文件通常包含以下内容:

    • 程序的代码和数据的内存映像。
    • 寄存器的状态,包括程序计数器、堆栈指针等。
    • 程序的堆栈跟踪,包括函数调用序列。
    • 打开的文件描述符。
    • 以及其他一些可能有助于调试的信息。
  2. 目的: 核心转储用于后续的调试分析。开发者可以使用调试器(如gdb)加载核心转储文件,检查崩溃时的程序状态,包括变量的值、调用栈、程序计数器等。

  3. 生成条件: 核心转储的生成可能由以下因素决定:

    • 操作系统的配置。
    • 进程的权限。
    • 系统的资源限制(如磁盘空间)。
  4. 配置: 在Linux系统中,可以通过以下方式配置核心转储的生成:

    • 使用 ulimit 命令设置核心转储的大小限制。
    • 使用 /proc/sys/kernel/core_pattern 配置核心转储文件的命名规则和存储位置。
  5. 安全性: 核心转储文件可能包含敏感信息,如密码、密钥等。因此,出于安全考虑,应该谨慎处理核心转储文件,避免未经授权的访问。

  6. 调试: 使用调试器加载核心转储文件进行调试的基本命令如下:

    gdb /path/to/program /path/to/core

    在gdb中,可以使用 bt(backtrace)命令来查看崩溃时的调用栈。

  7. 限制: 核心转储文件并不总是包含所有需要的信息。例如,如果程序使用了动态内存分配,一些内存可能已经被操作系统回收。此外,复杂的程序可能需要结合多个核心转储文件和源代码来定位问题。

  8. 性能影响: 生成核心转储文件可能会对系统性能产生一定影响,尤其是在资源受限的系统中。

 


在Linux系统中,当进程由于接收到某些信号而异常终止时,操作系统可以生成核心转储(Core Dump)。可以设置为核心转储的信号通常包括但不限于以下几种:

  1. SIGABRT: 由 abort() 函数引发,通常用于指示程序中有严重错误。

  2. SIGFPE: 浮点异常,如除以零、溢出等。

  3. SIGILL: 非法指令,当程序尝试执行非法、格式错误、或不被支持的机器语言指令时触发。

  4. SIGSEGV: 段错误,是最常见导致核心转储的信号,通常发生在程序试图访问未分配或不允许的内存区域时。

  5. SIGBUS: 总线错误,类似于段错误,但由硬件总线错误触发。

  6. SIGXCPU: 超过CPU时间限制,当进程超过其CPU时间配额时触发。

  7. SIGXFSZ: 超过文件大小限制,当进程尝试扩大文件超出其文件大小配额时触发。

这上面用一句话总结 

  • Trem -> 单纯终止进程
  • Core -> 先发生核心转储,生成核心转储文件(前提是此功能已打开),再终止进程

 我使用的是云服务器,默认是关闭了核心转储。原因等下看演示

查看核心转储 

指令:ulimit -a

 

核心转储的资源大小为0,需要我们手动设置。

指令:ulimit -c 4096 // 大小这个随意 

 

设置好了后,我们再运行之前的代码的得到下图 

 

可以发现核心转储的文件的是很大的,而有很多信号都会产生核心转储文件,所以云服务器一般默认是关闭的 
如果打开了核心转储,一旦程序 不断挂掉、又不断重启,那么必然会产生大量的核心转储文件,当文件足够多时,磁盘被挤满,导致系统 IO 异常,最终会导致整个服务器挂掉的
还有一个重要问题是 core 文件中可能包含用户密码等敏感信息,不安全

所以这就是云服务器默认关闭的原因

关闭非常简单 设置为0就行

 ulimit -c 0

 核心转储的作用

       刚才演示核心转储的感觉没上面了不起的,那它存在的意义是什么?

调试!!! 

 

 之前在 进程创建、控制、等待 中,我们谈到了 当进程异常退出时(被信号终止),不再设置退出码,而是设置 core dump 位 及 终止信号

 也就是说,父进程可以借此判断子进程是否产生了 核心转储 文件

 

被杀的第8位位图就是我们需要关注的

软件条件 

其实这种方式我们之前就接触过了:管道读写时,如果读端关闭,那么操作系统会发送信号终止写端,这个就是 软件条件 引发的信号发送,发出的是 13 号 SIGPIPE 信号 ​​​​​​​Linux 进程间通信之匿名管道

对此有疑问的小伙伴可以去看这篇博客!!!

alarm函数

在Linux中,alarm 函数用于设置一个定时器,当定时器超时后,会向调用进程发送一个 SIGALRM 信号。这个函数是POSIX标准的一部分,并且是UNIX系统中用于进程间同步的最基本的定时器之一。

函数原型

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

参数

  • seconds:设置定时器超时的时间,单位为秒。

返回值

  • alarm 函数返回在调用前已经安排的剩余时间(秒),如果之前没有设置闹钟或者闹钟已经过期,则返回0。

行为

  • 当定时器超时,如果程序没有处理 SIGALRM 信号,缺省行为是终止程序。
  • 如果已经为 SIGALRM 设置了信号处理函数,那么在定时器超时时会调用该函数。

示例用法 

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

void handle_alarm(int sig) {
    printf("Alarm clock!\n");
}

int main() {
    signal(SIGALRM, handle_alarm); // 设置信号处理函数

    printf("Sleeping for 5 seconds...\n");
    alarm(5); // 设置5秒的定时器

    pause(); // 挂起,直到一个信号到达

    return 0;
}

在这个示例中,我们首先为 SIGALRM 信号设置了自定义的信号处理函数 handle_alarm。然后在 main 函数中,我们使用 alarm(5) 设置了一个5秒的定时器。程序随后调用 pause() 挂起,直到接收到一个信号。当5秒定时器超时后,会发送 SIGALRM 信号给进程,调用 handle_alarm 函数,并打印出 "Alarm clock!"。 

注意事项

  • alarm 函数设置的定时器是实时的,它不会暂停即使进程不在运行状态。
  • 如果在定时器超时前再次调用 alarm,可以重置定时器。
  • alarm 函数提供的定时精度较低,不适合需要高精度计时的场合。
  • 在多线程程序中,使用 alarm 可能不是最佳选择,因为它只对整个进程有效,而不是单个线程。

alarm 函数是单次定时器,一旦超时就会失效,如果需要周期性定时,需要在信号处理函数中重新设置。

 如何理解返回上一次的的剩余时间?

代码示例

void myhandler(int signo)
{
    cout << "process get a signal: " << signo << endl;
    int n = alarm(10);
    cout << "上一个闹钟剩余时间: " << n << endl;
    // exit(1);
}


int main()
{
    signal(SIGALRM, myhandler);
    alarm(10);   //设定一个十秒后的闹钟

    while(true)
    {
         cout << "I am a crazy process " << getpid() << endl;
         sleep(1);
    };

    return 0;
}

 

 

 本篇是信号开篇之作,前面的生活案例打游戏,收到快递信号没有及时去拿快递,那么在系统中又是如何保存这个信号,然后处理?后篇就讲信号是如何保存的!!!

 

 

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

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

相关文章

解决常见的Android问题

常见问题&#xff1a; 1、查杀&#xff1a; 查杀一般分为两个方向一种是内存不足的查杀&#xff0c;一种的是因为温度限频查杀&#xff0c;统称为内存查杀&#xff0c;两个问题的分析思路不同 1、内存不足查杀&#xff1a; 主要是因为当用户出现后台运行多个APP或者是相机等…

一文了解spring的aop知识

推荐工具 objectlog 对于重要的一些数据&#xff0c;我们需要记录一条记录的所有版本变化过程&#xff0c;做到持续追踪&#xff0c;为后续问题追踪提供思路。objectlog工具是一个记录单个对象属性变化的日志工具,工具采用spring切面和mybatis拦截器相关技术编写了api依赖包&a…

【C++进阶】C++中的map和set

一、关联式容器 在初阶阶段&#xff0c;我们已经接触过STL 中的部分容器&#xff0c;比如&#xff1a; vector 、 list 、 deque&#xff0c; forward_list 等&#xff0c;这些容器统称为序列式容器&#xff0c;因为其底层为线性序列的数据结构&#xff0c;里面存储的是元素本…

Llama 3 是怎么回事?Arena 数据分析

4 月 18 日,Meta 发布了他们最新的开放权重大型语言模型 Llama 3。从那时起,Llama 3-70B 就在 English Chatbot Arena 排行榜上迅速上升,拥有超过 50,000 次对战。Meta 的这一非凡成就对开源社区来说是个好消息。在这篇博文中,我们旨在深入探讨为什么用户将 Llama 3-70b 与 GPT…

【第17章】spring-mvc之日志和拦截器

文章目录 前言一、整合log4j1. 引入库2. log4j2.xml 二、拦截器1.拦截器类2.注册拦截器 三、过滤器和拦截器顺序总结 前言 【第2章】整合log4j2框架 在前面的spring中已经完成了对日志框架log4j的整合&#xff0c;这里我们直接拿过来用就行。 场景描述&#xff1a;每个接口请…

JVM基础之垃圾回收

垃圾回收 1. Base 内存泄漏&#xff1a;不再使用的对象在系统中未被回收 内存溢出&#xff1a;内存泄漏的积累 手动触发垃圾回收&#xff1a;System.gc(),该方法不一定会立即回收垃圾&#xff0c;仅仅是向JVM发送一个垃圾回收请求&#xff0c;具体是否需要垃圾回收由JVM自行…

Web实时通信的学习之旅:轮询、WebSocket、SSE的区别以及优缺点

文章目录 一、通信机制1、轮询1.1、短轮询1.2、长轮询 2、Websocket3、Server-Sent Events 二、区别1、连接方式2、协议3、兼容性4、安全性5、优缺点5.1、WebSocket 的优点&#xff1a;5.2、WebSocket 的缺点&#xff1a;5.3、SSE 的优点&#xff1a;5.4、SSE 的缺点&#xff1…

实用的Chrome命令 帮你打开Chrome浏览器的隐藏功能

前言 Chrome作为主力浏览器&#xff0c;支持相当丰富的第三方扩展&#xff0c;其实浏览器本身也内置了大量实用的命令。许多实用的功能并没有直接显示在Chrome的菜单上。在这篇文章中&#xff0c;我们将介绍几个实用的chrome:// commands。 通过下面整理的 Chrome 命令&#x…

nginx_01

1.安装 yum install epel-release -y # 安装yum的扩展包 yum install nginx -y systemctl start nginx.service #启动nginx systemctl enable nginx.service # netstat -lntup # 查看端口占用情况 # 可以看到nginx默认占用了80端口 2.nginx配置 # 注意配置文件的语法格式…

macOS Sonoma 无法打开分段式Dmg文件的解决办法

在macOS Sonoma 14.X及更高版本的系统中&#xff0c;用户可能会遇到一个棘手的问题&#xff1a;无法直接打开“分段式”DMG&#xff08;磁盘映像&#xff09;安装包文件。这种情况通常发生在尝试安装一些大型软件或游戏时&#xff0c;尤其是那些因为文件体积巨大而采用分段压缩…

开源AI大模型测评网站

1、排行榜 多个 AI 模型的排行榜和详细的性能评估,包括总排行榜、基础能力排行榜、安全类模型排行榜、金融领域应用排行榜、汽车领域应用排行榜以及工业领域应用排行榜 地址:SuperCLUEhttps://www.superclueai.com/ 2.报告合集 内容体系:代表性的数据集、基线(预训练)模型…

Kivy UI界面

一、版本介绍 Ubuntu&#xff1a;18.04.6 LTS Conda&#xff1a;4.5.12 Python&#xff1a;3.6.15 Kivy&#xff1a;2.0.0 二、安装Kivy # 更新系统包列表 sudo apt-get update# 安装Kivy的依赖项 sudo apt-get install -y python-pip libsdl2-dev libsdl2-image-dev li…

【JavaWeb】网上蛋糕商城后台-商品管理

概念 本文讲解和实现网上蛋糕商城的后台管理系统中的商品管理功能。 商品列表 点击后台管理系统的head.jsp头部的“商品管理”功能选项&#xff0c;向服务器发送请求/admin/goods_list 因此需要在servlet包中创建AdminGoodsListServlet类&#xff0c;用于获取商品信息列表 …

TDM(BPM)-MIMO-FMCW雷达MATLAB仿真

本文通过对车载毫米波雷达信号流程和链路的仿真&#xff0c;建立基本的算法框架&#xff0c;可用于算法性能的验证。并提供基础MATLAB仿真代码&#xff0c;作为分享和参考。 一、信号的产生 车载毫米波雷达广泛使用线性调频连续波雷达&#xff0c;也即发射信号频率随时间线性变…

C++ | Leetcode C++题解之第79题单词搜索

题目&#xff1a; 题解&#xff1a; class Solution { public:bool exist(vector<vector<char>>& board, string word) {rows board.size();cols board[0].size();for(int i 0; i < rows; i) {for(int j 0; j < cols; j) {if (dfs(board, word, i, …

面向对象设计之套路——设计模式

1、总则 面向对象的分析设计编程思想&#xff0c;通过封装、继承、多态把程序的耦合度降低&#xff0c;用设计模式使得程序更加灵活&#xff0c;容易修改&#xff0c;并且易于复用。 让业务逻辑与界面逻辑分开&#xff0c;让它们的耦合度下降&#xff0c;只有分离&#xff0c;…

jenkins部署想定报错

报错&#xff1a; 解决办法&#xff1a; 登录被编译的设备&#xff0c;清楚旧代码&#xff0c;在重新执行

亲测有效!关键点检测——COCO格式转YOLO格式代码!!!

话不多收&#xff0c;直接上代码&#xff0c;这个我也是找了好久的&#xff0c;分享不易&#xff0c;给个鼓励&#xff01;&#xff08;记得点赞收藏&#xff09; 大家可以直接使用此代码转换你自己的数据集&#xff0c;路径换成你自己的就行了&#xff0c;注意路径格式&#x…

Springboot集成SpringbootAdmin实现服务监控管理-10

SpringbootAdmin Spring Boot Admin是一个用于管理和监控Spring Boot应用程序的开源软件。 概要介绍 Spring Boot Admin可以监控Spring Boot单机或集群项目&#xff0c;它提供了详细的健康&#xff08;Health&#xff09;信息、内存信息、JVM系统和环境属性、垃圾回收信息、…

AI自动生成PPT工具上新

AI大模型能力持续增强&#xff0c;零一万物&#xff08;李开复领导的团队&#xff09;推出的万知只是其中的一个缩影&#xff0c;生成PPT也只是其中一个能力。 如果你还没用WPSAI的PPT自动生成能力&#xff08;WPS Office AI实战总结&#xff0c;智能化办公时代已来&#xff09…