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;
}

 

 

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

 

 

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

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

相关文章

Java入门基础学习笔记1——初识java

1、为什么学习java&#xff1f; 几乎统治了服务端的开发&#xff1b;几乎所有的互联网企业都使用&#xff1b;100%国内大中型企业都用&#xff1b;全球100亿的设备运行java。开发岗位薪资高。 Java的流行度很高&#xff0c;商用占有率很高。 可移植性。 2、Java的背景知识 …

手机录屏怎么录?简单易懂的教程,让你轻松上手!

随着科技的不断发展&#xff0c;手机录屏已经成为人们日常生活中一个非常普遍的需求。无论是录制游戏精彩瞬间、分享App使用教程&#xff0c;还是保存线上会议、录制网课&#xff0c;手机录屏都发挥着重要作用。可是你知道手机录屏怎么录吗&#xff1f;本文将详细介绍三种手机录…

【漏洞复现】RuvarOA协同办公平台 WorkFlow接口处存在SQL注入

免责声明&#xff1a;文章来源互联网收集整理&#xff0c;请勿利用文章内的相关技术从事非法测试&#xff0c;由于传播、利用此文所提供的信息或者工具而造成的任何直接或者间接的后果及损失&#xff0c;均由使用者本人负责&#xff0c;所产生的一切不良后果与文章作者无关。该…

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

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

【https】怎么免费实现https!

一、简介 实现 HTTPS&#xff08;Hyper Text Transfer Protocol Secure&#xff09;通常需要购买和配置 SSL/TLS 证书&#xff0c;这是确保网站或应用程序安全传输数据的关键步骤。然而&#xff0c;有一些方法可以在某些情况下“免费”地实现 HTTPS&#xff0c;但这通常涉及到…

React - Input框绑定动态State和监听onChange事件,输入时失去焦点

React - Input框绑定动态State和监听onChange事件&#xff0c;输入时失去焦点 一. 案例复现二. 解决方案 一. 案例复现 案例代码如下&#xff1a; import React, { useState } from react; import { Table, Input } from antd; const Column Table.Column; const mockData …

linux打包流程

因为linux有俩个python版本&#xff0c;我们需要切换到python3这个版本&#xff0c;默认是python 2.7 alias pythonpython3 切换到python3 再次执行&#xff1a;python -V 显示出python的版本了&#xff0c;然后查看pip的配置&#xff0c;我们打包里面需要的第三方需要放到pip…

C++面向对象程序设计-北京大学-郭炜【课程笔记(八)】

C面向对象程序设计-北京大学-郭炜【课程笔记&#xff08;八&#xff09;】 1、虚函数和多态的基本概念1.1、虚函数1.2、多态多态的表现形式一多态的表现形式二 2、多态实例&#xff1a;魔法门之英雄无敌2.1、**非多态的实现方法&#xff1a;**2.2、**多态的实现方法** 3、多态实…

C#实现长方体棱锥圆柱棱柱圆锥展开折叠旋转缩放

C#实现长方体棱锥圆柱棱柱圆锥展开折叠旋转缩放 C#实现 模型边数 长方体 棱锥 圆柱 棱柱 圆锥 实现功能 展开 折叠 颜色 边框颜色 旋转 缩放 大小 视图方向 项目获取&#xff1a; 项目获取&#xff1a;typora: typora/img (gitee.com) 备用项目获取链接1&#xff1a;yife…

Electron学习笔记(三)

文章目录 相关笔记笔记说明 五、界面1、获取 webContents 实例&#xff08;1&#xff09;通过窗口对象的 webContent 属性获取 webContent 实例&#xff1a;&#xff08;2&#xff09;获取当前激活窗口的 webContents 实例&#xff1a;&#xff08;3&#xff09;在渲染进程中获…

微信小程序原生组件使用

1、video组件使用 <view class"live-video"><video id"myVideo" src"{{videoSrc}}" bindplay"onPlay" bindfullscreenchange"fullScreenChange" controls object- fit"contain"> </video&g…

ubuntu server 22.04 安装docker、docker-compose

ubuntu server 22.04安装docker有两种方式&#xff0c;第一种是使用ubuntu镜像源的软件包进行安装&#xff0c;第二种使用官方GPG密钥手动添加Docker存储库方式进行安装&#xff0c;两种方式都可以&#xff0c;但第二种方式略复杂&#xff0c;这里介绍第一种比较简单的安装方式…

轻松玩转Python文件操作:移动、删除

哈喽&#xff0c;大家好&#xff0c;我是木头左&#xff01; Python文件操作基础 在处理计算机文件时&#xff0c;经常需要执行如移动和删除等基本操作。Python提供了一些内置的库来帮助完成这些任务&#xff0c;其中最常用的就是os模块和shutil模块。这两个模块包含了许多与文…

无需公网IP、无需云服务器,异地组网实现远程直连NAS、游戏联机

手机图片、视频太多&#xff0c;存储空间不够用怎么办?出门在外无法直连家中NAS&#xff0c;远程访问NAS速度慢&#xff1f;自建私有云、多媒体服务器&#xff0c;如何多人远程共享媒体资源&#xff1f;幻兽帕鲁、我的世界、泰拉瑞亚…局域网游戏&#xff0c;想远程多人联机&a…

Chromium 调试指南2024 Windows11篇-VSCode必要依赖扩展(四)

1. 前言 为了在Visual Studio Code中更加方便地进行Chromium项目的开发和调试&#xff0c;我们需要安装一些必要的依赖扩展。本文将介绍如何安装中文语言包及其他依赖扩展&#xff0c;以提升我们在Visual Studio Code中的开发效率和使用体验。 2. 安装依赖扩展 在打开的Visu…

局域网手机端远程控制手机

局域网手机端远程控制手机 随着科技的进步和智能设备的普及&#xff0c;远程控制技术在日常生活与工作中的应用越来越广泛。其中&#xff0c;局域网内的手机端远程控制手机技术&#xff0c;因其便捷性和实用性&#xff0c;受到了众多用户的关注。本文将简要介绍该技术及其应用…

在装有centOS7的虚拟机上进行MySQL的安装部署

1.MySQL数据库介绍 1.开源的&#xff0c;跨平台的&#xff0c;社区版免费 2.支持多种存储引擎 3.支持多种主从复制 MySQL版本&#xff1a;5.6 5.7 8.0 https://www.mysql.com MySQL官网 2.安装MySQL5.7 1.配置MySQL仓库 2.安装MySQL服务端软件 3.启动MySQL服务 s…

3. 多层感知机算法和异或门的 Python 实现

前面介绍过感知机算法和一些简单的 Python 实践&#xff0c;这些都是单层实现&#xff0c;感知机还可以通过叠加层来构建多层感知机。 2. 感知机算法和简单 Python 实现-CSDN博客 1. 多层感知机介绍 单层感知机只能表示线性空间&#xff0c;多层感知机就可以表示非线性空间。…

Ubuntu20.04 设置路由器

1. 网络拓扑图 2. 查看网卡信息 ip a得出如下网卡信息&#xff0c;enp1s0和enp2s0为两个网卡名称&#xff0c;以及相关两个网卡的详细信息&#xff0c;不同设备的网卡名称可能不一样 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group defaul…

使用python获取一下microsoft的搜索积分

主要使用的库是pyautogui PyAutoGUI接管了鼠标、键盘使用权,基本上完全照搬人的操作; 主要步骤如下: 登录edge浏览器打开搜索页面 找到搜索框的位置坐标使用pyautogui模拟点击搜索框模拟输入搜索文字模拟点击键盘enter键重复以上动作伪代码如下: import pyautogui import ti…