跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C

Linux 进程信号(上):信号产生方式与闹钟机制

Linux 信号是操作系统向进程发送的异步通知机制,用于处理突发状况如用户中断或硬件异常。信号分为普通信号和实时信号,进程通过位图在 PCB 中保存未处理的信号。信号产生方式包括系统命令(kill)、键盘输入(Ctrl+C)、系统调用(raise/abort)、异常(除零/段错误)及软件条件。信号处理方式有默认、忽略和自定义捕捉。前台进程组接收终端信号,后台进程无法获取键盘输入。alarm 函数设置一次性定时器,触发 SIGALRM 信号,常用于看门狗或延迟任务,需注意返回值及重复设置逻辑。

星辰大海发布于 2026/3/24更新于 2026/10/2688 浏览
Linux 进程信号(上):信号产生方式与闹钟机制

Linux 进程信号(上):信号产生方式与闹钟机制

1. 理解信号是什么,为什么要有?

1.1 信号是什么?

1.1.1 普通信号和实时信号

用 kill -l 可以查看。

常用的只有 1~31,34~64,没有 0 号——一共 62 个。前 31 个是普通信号,后面带 RT(real-time)实时信号。我们主要考虑 1-31。

1.1.2 信号的本质

什么是通知?什么是异步?

  • 通知:事件通知。
  • 异步:通知的到来,跟我不同步。 举个例子,点了外卖打游戏,外卖小哥敲门是一个通知,我和外卖小哥是不同步的。 异步关系就是没关系(你做你的,我做我的);同步关系就是有关系(我得等你做完再做)。

再比如,老师讲课,快递电话打过来,叫张三去取快递。如果等张三回来再继续讲,这是同步;如果继续讲课,张三找快递,这是异步。

1.2 生活中有哪些信号?以及一些结论总结

大抵有如下这些:

上面所有的信号产生的几乎都是异步的。

人能够识别对应的、甚至还没有发生的这些信号。为什么能够识别?人是经过教育的,早就知道信号对应处理动作的对应关系。

操作系统给目标进程发送信号,目标进程能不能识别呢?答案是:进程天然能够识别。进程和信号都是程序员写的代码,进程相当于人,已经被程序员教育过了,设计好了如何处理信号。

1.2.1 man 7 signal:查看信号部分的内容

man 7 本身不是查看信号的指令,而是指定要查看的手册章节为第 7 章。在 Linux 系统中,信号相关的概述通常位于第 7 章,因此要查看信号的详细信息,可以使用命令:

man 7 signal

会显示信号的概念、列表、默认行为等全局说明。此外,信号相关的系统调用(如 signal()、sigaction())一般在第 2 章,库函数(如 sigsetops())可能在第 3 章,而第 7 章主要提供更上层的概述和标准约定。

1.2.2 信号没有产生也知道怎么处理

在这里插入图片描述

1.2.3 收到一个信号会立即处理它吗?

当我们收到一个信号,准备处理这个信号,这里的我们即进程,会立即处理这个信号吗?有时候中断不了呢!处理不了信号。

对于信号的处理——不会立即处理——信号可能会立即处理,只有合适的时候会处理。

既然有一定概率不会立即处理,那么得要有把信号临时保存起来的能力——不会立即处理,就得有临时保存的能力。

比如外卖小哥打电话,答应去取外卖之后如果没有保存信号,是不是就打游戏打着打着就忘了取外卖了。

1.2.4 信号怎么处理?
  • 默认:我后面打完游戏去拿外卖回来吃
  • 忽略:我直接就不想拿了 (但是并不是忘了,只是不想)
  • 自定义:比如我拿完外卖直接丢了或者干别的事,自定义行为

我们就以红绿信号灯为例:

  • 红灯——自动停了——默认信号。
  • 收到信号,也处理了,但是处理的方式是忽略——忽略信号。
  • 红灯亮了,别人要么忽略要么停下,而你在跳舞——自定义信号。
1.2.5 总结一些这些小结论
  • 设别信号是内置的,进程能自己识别信号,是内核程序员写的内置特性。
  • 信号的处理方法,在信号产生之前就已经准备好了。
  • 处理信号不是立即处理的,因为我可能正在做优先级更高的事情,会选择合适的时候进行处理。比如我在打游戏,外卖员给我打电话叫拿外卖,那我肯定是先忙完手头的事再去拿。
  • 信号不会被立即处理,所以就注定了进程要有临时保存信号的能力!
  • 信号处理的动作有三种:默认、忽略、自定义,后续都叫信号捕捉。
1.2.6 图示

在这里插入图片描述

1.3 信号具体化

1.3.1 为什么是大写?这些大写的名字是什么?

没有 0 号信号,我们知道,信号是有编号的。

这些大写的名字是什么呢?这些大写的名字就是宏。

1.3.2 头文件(含内核查看)

我们查看内核——

1~31 是普通信号,我们现在是分时操作系统;34~64 是实时信号(有 RT,就是 real-time)。

1.3.3 保存信号
1.3.3.1 进程一定要有临时保存信号的能力

进程一定要有临时保存信号的能力——原因我们前面已经分析了。

  • 信号就是个数字吗?还不够具体。
  • 进程需要使用特定的数据类型来保存对应的信号!

我们要分析两个问题——

  • 1、我们要保存什么信号?哪一个信号?
  • 2、我们要保存信号的侧重点?哪一个信号是否产生了?
1.3.3.2 我们可以用什么样的特定的数据类型来保存信号?位图
  • 1、比特位的位置表示信号编号
  • 2、比特位的内容要么是 1 要么是 0,1 或者 0——是否收到对应的信号!

用位图表示对应的 1~31 个编号。

关键是这个位图在哪里?

  • 这个位图应该在哪里?设计在 进程的 PCB 里面。
1.3.4 信号的位图在进程 PCB 里面,谁有资格修改内核数据结构中的字段?
  • 信号的位图在进程 PCB 里面,谁有资格修改内核数据结构中的字段?
  • 谁有资格——这个世界上,能够给进程写入信号的家伙只有一个——就是操作系统 OS。

操作系统不能决定全部的往进程写入信号(有能力,但是也只是一个办事的)——用户让你办的!

  • 用户通过系统调用让操作系统向目标进程发送信号。

1.4 为什么要有信号?

Linux 之所以需要信号,是为了提供一种 异步且轻量级的进程间通信与事件通知机制,使操作系统能够立即打断进程的正常执行流,以通知其发生了诸如用户中断(Ctrl+C)、硬件异常(段错误)、定时器到期或子进程退出等突发状况。信号机制弥补了管道或共享内存等同步通信方式的不足,让进程能够以一种统一、即时的方式响应外部与内部的高优先级事件,是实现进程控制、异常处理和系统紧急交互的核心基础。

1.5 信号是什么的思维导图

如下图所示:

在这里插入图片描述

1.6 如何自定义捕捉信号?

一个函数,我们写一段代码——

对任何信号进行一下捕捉!

man signal

信号是发给进程——处理信号是进程自己去处理的。

回调函数

signal 函数其实是对指定信号未来进行自定义处理的一种延时设定。

我们挑一个信号——这里挑选 2 号信号:SIGINT

运行一下:

kill -2 [pid]

查看信号手册:

我们在手册(man 7 signal)里面查看 2 号信号——

要让 2 号信号不要终止进程,调用我自定义的方法——

这就叫做信号捕捉。

Ctrl C(就是给前台发送 2 号信号)也终止不了了。

我们修改一下代码:

信号最终是由进程自己去处理的,通过上面的代码可以验证——两个 pid 一样。

对某个信号进行设定,那这个参数 sig 是什么?

我们继续验证一下:

本来就是设定了是 2 号信号,为什么还要把 2 号信号传进来呢?

Linux 内部,不同的的信号捕捉的处理方法可以是同一个!

所以我们得知道是哪一个信号——我们多个信号设置处理方法,可以是同一个处理方法。

我们再来验证一下:

可以通过传入的参数来区分是哪个信号触发了自定义捕捉。

IGN 的参数是 1:

我们验证一下:

  • 所有的 2 号信号都被忽略了!

还有一种是恢复默认:SIG_DFL

我们再运行一下:

符合预期,就是进程终止,说明恢复默认了!

代码跑完对还是不对由退出码决定。

运行一下我们发现,进程果然就杀不掉了!

虽然 1~31 都能够设定自定义捕捉,但是有两个信号是不能够设定的:9 号信号和 19 / 20 号新号(看系统,具体是哪个)

  • 9 号信号很重要:9 号信号不能被自定义捕捉、不能被忽略。

2. 信号的产生

  • 让 OS 给目标进程写信号。

操作系统给目标进程写信号——不管怎么样,必须经过操作系统——因为只有操作系统能够有资格修改内核数据结构。

产生信号这个话题内容有点多。信号的产生是异步产生的,所以不会立即处理。

2.1 信号产生的方式

2.1.1 使用系统命令产生,kill 命令

在【自定义捕捉信号】那里我们已经见识过了。

2.1.2 通过键盘产生信号

放开循环:

我们运行一下:

我们查看手册,来一一对应一下,看看它对于当前进程的处理动作:

  • term 和 core 都是进程终止

Ctrl C 证明我们可以通过键盘产生信号(组合键的方式)。

  • 为什么你如果启动一个进程之后,Ctrl C 可以终止这个进程! Ctrl C:发送了 2 号信号,2 号信号的处理动作是终止!
  • 我们保留一个问题:键盘怎么能够向目标进程发送信号呢? 键盘是个硬件啊,进程是个软件啊,怎么发送信号?
2.1.3 产生信号的还有一种方式:系统调用

kill:发送信号给进程。

第一个参数:发送给哪个进程;第二个参数:发送哪个信号。

系统底层肯定要调用这个 kill。

管道那里有一个对应的命令:mkfifo

Stat:获取函数的属性

printf:底层也是调用了同名系统调用

很多命令底层用到了它的同名系统调用!

我们来测试一下——

  • 输出参数不匹配,就给你打印一个使用手册,类似于平常编译时候的报错
  • 类型要转成整数

然后就可以使用 kill 系统调用,向目标进程写入信号——

运行一下,我们发现多了个 (左边黄色框框):

我们再修改一下源代码:

把 都去掉

我们再运行,还是前面的那种运行的图,这次我们看本次运行的结果:

kill 命令底层调用了 kill 系统调用——让操作系统向目标进程发送指定信号。

  • 如下图,验证了9 号信号不能被捕捉

  • raise:自己给自己可以发任何信号

  • 修改代码,让进程自己给自己发送信号

自己给自己发,1 秒打一次——

一个系统调用可以给自己发送任意信号。

  • raise 底层封装了 kill

  • abort

我们修改一下代码,加入 abort 函数:

运行:当前进程立即终止了

我们发现信号是 6 号信号!

  • abort:也是 Core(终止进程)

6 号信号通常用来进行异常终止。

既把信号捕捉了,进程也终止了。

  • 6 号信号还很特殊:可以理解为不可以被捕捉

可以设置捕捉动作,但是最后还是会终止进程!——异常。

  • 6 号信号也可以被算做是不能被捕捉、忽略的
2.1.4 第四种产生信号的方式:异常

获取到哪个信号呢?

  • 我们放开信号捕捉

  • 运行

信号捕捉函数一直被触发(不是因为 while 循环)——死循环。

杀掉进程

11 号信号——

  • 证明不是因为 while(true) 循环

照样还是死循环:

我们故意来一个 /0 错误:

  • 8 号信号:SIGFPE(浮点异常)

尽管名字包含'浮点',但它实际上涵盖了更广泛的算术错误,包括整数运算中的错误。

观察一下:

除零错误会被当做信号发出来。

进程自己就会收到对应的信号!

为什么会把进程异常当成信号?进程异常是如何被 OS 识别、并且解释成为信号的?!

2.1.5 第五种信号产生的方式:由软件条件产生信号

这里就有点抽象了。

  • 管道:r 端关闭,W 端打开,W 端一直写 --> OS 就会把写进程直接就杀掉了

操作系统认为管道不具备写条件——OS 就把写进程杀掉了。

你得罪了软件或者硬件,操作系统都要把你杀掉。

进程退出一定是异常了,收到信号了。

关于闹钟(alarm)的相关话题我们在 2.4 细说。

2.2 三个系统调用层层递进

2.2.1 三个系统调用层层递进

前面的 kill、raise、abort 三个系统调用我们发现是层层递进的!

2.2.2 OS 先收到键盘输入,再由操作系统转换成信号发送给目标进程

接下来,先不往后面看,先复盘一个问题:

我们已经说过啦,必须只能是 OS!

只有操作系统可以在内核操作系统里面对 PCB 内部的位图进行修改。

换句话说,通过键盘产生信号也并不是键盘直接产生的信号,必然是 OS 先收到键盘的输入的,再由操作系统转换成信号发送给目标进程!

2.3 键盘怎么能够目标进程发送信号呢?

  • 1、如何理解键盘输入?键盘是基于硬件中断来进行工作的!
  • 2、OS 如何解释快捷键?(组合键)ctrl c、ctrl \,……可能会直接解释成为信号!
  • 3、OS 怎么知道信号应该发送给哪一个进程呢?操作系统怎么知道?要补充一点网络时要用的知识!
2.3.1 如何理解键盘输入?
  • 如何理解键盘输入?

我们通过图来理解一下——

我的操作系统怎么知道用户把键盘按下了、按下了哪个(键)字符?——驱动去处理的。

我们关心的是,OS 怎么知道键盘上有数据了?!

计算机的外设很多,操作系统要做各种工作,你能够轮询吗?这种做法在技术上可以实现,但是这会让操作系统变得很慢!

  • 硬件中断

作为 CPU,只要被动等待就行。

  • 中断:是计算机组成原理的最重要的重点,没有之一!没有学懂中断,就是没有学懂操作系统。

操作系统有些东西没有学懂,问题可能不在操作系统上面,可能是在理解计算机组成原理上。

  • 跨学科学习是一个比较重要的思路

除了冯诺依曼告诉我们的外设,还有……

键盘和 CPU 是一根线上连着的。

  • 针脚如下所示(英特尔的):会和主板相连。

这些针脚会和主板连上,所以主板走线会和外设勾连。

网卡的例子。

告诉 CPU,数据已经到来了,触发中断。

操作系统并不知道键盘上有数据了,而是中断告诉 CPU 了。

2.3.2 OS 如何解释快捷键?

键盘的字符既有'ABCD……'这样的正常的字符,也有'Ctrl C''Ctrl V'……这样的组合键,操作系统会把组合键解释成信号(会判断,ctrl c 是特殊字符,在 ASCII 码表里存在)——操作系统会有系统调用:给目标进程发送信号——kill(pid,signum)。

2.3.3 OS 怎么知道信号应该发送给哪一个进程呢?
  • 说到这个,我们先要谈一个进程组和作业的话题。

如下图,我们这里创建了三个进程:

三个进程的父进程都是同一个:bash;即这三个进程是兄弟进程。

2.3.3.1 进程组
  • 进程是有组的概念
2.3.3.2 会话 ID:SID

这三个兄弟进程的 SID 一样,属于同一个会话,以 bash 进程命名:

一般情况下,同一个组里面大家会使用同一个终端文件(因为我们是远程登录的 Xshell,这里的终端文件是虚拟网络文件)。

每一个用户登录,都要建立会话,打开终端文件。

如果在 bash 命令行里再启动一个进程

  • 我们把代码改一下,把 abort 注释掉

同一个终端下,在创建一个进程——

我们发现这个进程和那三个进程不在一个进程组里面,但是在一个会话组里。

本质是一个会话里面创建一个新的进程组(哪怕进程组只有我一个进程)。

  • 我们把 for 循环也注释掉,方便 ctrl c 等操作
2.3.3.3 前台进程和后台进程 + 前后台进程切换

进程分前台进程和后台进程:

我们取地址 &,就变成后台进程了——

  • 我们今天要把这个说法变一变。

我们发现 Ctrl C 不能干掉进程了——

再看一下:

观察运行的打印结果:

  • 我们在代码里面把休眠时间改长一点

前台进程:

后台进程:

我们只能这样删除:

kill -9 [pid]
  • 我们把打印也注释掉,只看到时候的任务号

再创建一大批的任务:

每一个进程都有自己的进程组,都属于同一个会话:

  • 启动后台任务,Ctrl C 是杀不掉的!
2.3.3.3.1 前台进程

定义:占用当前终端,必须等该进程执行完毕才能输入其他命令。

示例:直接执行 sleep 100,终端会被占用 100 秒,无法输入新命令。

2.3.3.3.2 后台进程

定义:在后台运行,不占用终端,用户可继续执行其他命令。

启动方式:在命令末尾加 &。

sleep 100 &

输出会显示作业号 [1] 和进程 PID。

2.3.3.3.3 作业控制命令
1 jobs
jobs

查看当前终端中的后台作业列表(带作业号)。

输出示例:

[1]+ Running sleep 100 &
2 fg(foreground)

将后台作业调回前台继续运行。

fg %1 # 将作业号 1 的作业调到前台
fg # 默认将最近一个后台作业调到前台
3 bg(background)

将暂停(挂起)的作业放到后台继续运行。

  • 先用 Ctrl+Z 暂停前台进程,此时作业变为'已停止'状态。
  • 执行 bg %1 让它在后台继续运行。
sleep 200 # 执行,然后按 Ctrl+Z 暂停
bg %1 # 在后台继续运行
jobs # 可看到状态变为 Running
4 Ctrl+Z

暂停当前前台进程,将其放入后台(停止状态)。

5 Ctrl+C

终止前台进程。

进程可以在前台或后台运行,并通过 fg 和 bg 命令进行切换

2.3.3.4 一些结论
结论 1:Ctrl C 只能用于终止前台进程!

Ctrl C 只能用于终止前台进程!

作业 / 独立的进程组有什么关系?
  • 进程组是用来完成作业的!

进程组允许是一个进程。

作业是由进程组完成的!

在命令行./进程(启动一个进程)属于上面所说的特殊情况。

task(作业 / 任务)——进程组和作业就像是硬币,一体两面。

如下图所示:

会话与进程组(作业)的作业之间的关系?

会话内部至少包含一个进程组(就是 bash)。

会话: 是由一个或者多个进程组构成的 进程组的集合,通常会有属于自己的终端文件!

通过 fg [任务号] 把后台任务变成前台之后,就可以 Ctrl C 删掉了。

在一个会话中,任何时刻,只允许一个进程(组)在前台!

只要一个进程在前台,我的命令就没法被其它进程执行了。

这里存在两个问题:

  • 1、为什么其他命令就无法执行了?

因为在一个会话中,任何时刻,只允许一个进程(组)在前台,能够接受、执行命令的 bash 在哪里?自动切换成为后台进程组了(为什么 bash 变成后台了,命令就无法进行了)!获取不到命令,就无法执行命令了!

键盘只有一个,在会话里有这么多进程,谁拥有键盘,谁就是前台进程。

  • 2、什么叫做前后台?

  • 能够直接获取用户输入的基础,叫做前台进程;

  • 否则叫做后台进程

因为键盘只有一个,任何时刻只允许一个人输入(只有一个人,一个输入),谁拥有终端文件(主要是键盘),谁就是前台!获取不到键盘,所以就无法输入了。

  • 终端文件:键盘和显示器的集合体

后台进程可以写数据(向显示器打印),但是后台进程无法从键盘读取数据(读取的时候会被系统直接暂停掉)。

这就叫做 【会话管理】!

任何时刻,前台进程(组)只有一个!

  • 键盘输入:操作系统怎么知道要把信号发送给谁?

前台进程只有一个,所以操作系统知道未来要把信号发给哪个进程!

只能发送给前台进程(组),而且前台进程只有一个!

前后台切换就像是 '孔融让梨'。

  • Ctrl C 严格意义上不是杀掉一个进程,而是杀掉一个进程组。

父进程子进程都被 Ctrl C 杀掉了。

登录两次、三次、……系统是不是每次都要创建这些东西?

剩下的,在网络讲【守护进程】的时候再介绍。

后台不允许获取

代码再重编一下:

把前台进程被暂停了,前台进程就会自动切换到后台进程。

  • 这里证明了,后台进程能够打印、但是不能获取,一旦获取就会暂停。

  • 谁拥有键盘,谁就是前台;谁能够获取键盘的输入,谁就是前台。

当你按键盘一定是和某个会话或者某个终端相关联。

2.4 alarm:设置一个若干秒之后的闹钟

学习一个新的函数:alarm。

再认识一个信号:14 号信号。

操作系统收到这个信号默认的处理动作是:Term。

2.4.1 闹钟的返回值问题

我想看到的现象是收到闹钟前,进程会疯狂打印 cnt 计数器:

效果:

  • 计数器才加了 1 万多次

为了对比为什么 CPU 计算速度这么快还是只打印 1 万多,证明是收到了闹钟信号:

一万多次肯定对于当前的 CPU 还是太少了,我们纯在内存当中做++:

真实情况下是把一个整数加到 4 亿多次:

至于有没有回绕呢?应该是没有的。

为什么会有这么大的数量级上的差别的呢?

输出的本质是 IO,IO 比较慢,这个 IO 是要经过网络的,要访问外设。

4 亿多次是因为没有访问外设,直接访问了内存,内存级别的操作。

这样我们对 CPU 访问外设速度非常慢有了一个量化的认识。

我们验证了闹钟本身的一些操作。

如果我们设置一个 20 秒的闹钟,在第五秒的时候就把闹钟提前唤醒了,这种闹钟给一个进程只能设置一个,已经设置了,过了一段时间后悔了,要重设闹钟,下一次返回值就是返回上一个闹钟的剩余时间。

验证一下:

如果后悔了,设置闹钟为 0 秒:

就是返回上一个闹钟的剩余时间,0 就是取消闹钟。

如下图,我们先设置 alarm(10);,再设置成 alarm(0); 之后,因为第一个闹钟是 10 秒,使用返回了 10 - 0 = 10 秒。

我们把闹钟取消了,收不到闹钟,就会死循环:

我们改一下代码:

再过 n 个 2 秒我们就再也收不到闹钟了。

这就说明,alarm 是一个一次性闹钟,而且这个闹钟只对这个进程只会有同一个闹钟(从头到尾调用 alarm 就一个闹钟)。

我们可能要对闹钟进行重复设置,时间得卡好!

  • 下面这样写是不行的

当我调用下一个的时候闹钟就收不到闹钟了——上一个闹钟根本没有响!

重复设置一定要保证上一个闹钟已经被触发了。

一定要写在 handlersig 函数里:

此时我们就可以每隔 2 秒使用一次闹钟:

这就是闹钟的返回值问题。

2.4.2 闹钟的应用场景

闹钟功能具有延时触发的能力。

Windows 中的关机程序:

shutdown -t10 -s(带选项) 

实现 延迟关机 的功能。

其实严格意义上这个工作不是闹钟完成的,而是定时器完成的。

有点类似于'看门狗'——

  • 使用 alarm 实现看门狗(B 进程)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/shm.h>
#include <sys/ipc.h>
#define MAX_COUNT 100
#define SHM_KEY 0x1234
int* counter; // 共享内存指针
void watchdog_handler(int signum){
    // 每次闹钟触发,计数器减 1
    (*counter)--;
    printf("watchdog: counter = %d\n", *counter);
    if(*counter <= 0){
        printf("watchdog: no feed, shutting down...\n");
        // 执行关机命令,如 system("shutdown -h now");
        exit(0);
    }
    // 重新设置 1 秒后的闹钟
    alarm(1);
}
int main(){
    // 创建/获取共享内存
    int shmid = shmget(SHM_KEY, sizeof(int), IPC_CREAT | 0666);
    if(shmid < 0){
        perror("shmget");
        exit(1);
    }
    counter = (int*)shmat(shmid, NULL, 0);
    *counter = MAX_COUNT; // 初始值
    // 注册信号处理函数
    signal(SIGALRM, watchdog_handler);
    // 启动第一个 1 秒闹钟
    alarm(1);
    // 进入无限循环,等待信号
    while(1){
        pause(); // 等待闹钟信号触发
    }
    return 0;
}
  • A 进程喂狗

A 进程只需要通过共享内存访问计数器,并定期重置:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/shm.h>
#define MAX_COUNT 100
#define SHM_KEY 0x1234
int* counter;
int main(){
    // 获取共享内存
    int shmid = shmget(SHM_KEY, sizeof(int), 0666);
    if(shmid < 0){
        perror("shmget");
        exit(1);
    }
    counter = (int*)shmat(shmid, NULL, 0);
    while(1){
        // 模拟正常工作
        printf("working...\n");
        sleep(80); // 每隔 80 秒喂狗一次(小于 100 秒即可)
        // 喂狗:重置计数器
        *counter = MAX_COUNT;
        printf("feed watchdog, counter reset to %d\n", *counter);
    }
    return 0;
}
2.4.3 理解闹钟

理解闹钟,看看操作系统内同时存在多种闹钟(定时器)?操作系统要不要管理?怎么管理?先描述,再组织。

我们是先描述了:

怎么再组织呢?

  • 如果未来 5 秒的闹钟没有响,那么后面的 15 秒的 50 秒的、150 秒的一定没响。

只要关注未来超时时间最近的闹钟即可:

我们可以用什么数据结构来管理?

OS 给目标进程发送 SIGALARM。

未来自己使用定时器应该会使用堆结构,这就是为了方便理解。

堆结构有缺陷,不能遍历,不能很好地支持取消。

  • 闹钟,是单独硬件来计时吗?如果是 CPU 来计时的话,它不得很忙,一边处理进程,还一边计时。

问题 1:闹钟是软件实现的!

问题 2:闹钟计时取决于操作系统是如何运行的,操作系统也是个软件,谁运行操作系统?原因是,键盘可以产生中断,操作系统内部也存在一种硬件单元,晶振,也会触发中断,中断向量表,操作系统是基于中断的一种集合。

一般触发时钟中断的硬件周期是固定的,比如每隔一纳秒,因此在 OS 内部就可以定义一个全局变量记录中断次数,如果是 100,等于说从开机到现在已经过了 100 纳秒了,次数 = 时间。时钟中断,token 中断,操作系统能够算出来从开机到现在过了多少秒,5 秒会变成 5^9 次!如果次数超过了就是超时。计算机里把时间转化成次数,计时不是由 CPU 计时的,由外部晶振的振动周期决定的。中断越强,CPU 响应能力越强。

操作系统并不连续,而是一卡一卡的。

  • 除 0 死循环,标志位没清,再次上下文时,要先检查 core dumped 标志位,才运行吗

CPU 已经识别出来了,也会走中断,走中断也会走中断向量表,也属于异常。

只要 core dump 一次就可以了。

自定义捕捉了就没有 core dump 了,没有走自定义捕捉就没有走系统那一套,就没有 core dump 了。

目录

  1. Linux 进程信号(上):信号产生方式与闹钟机制
  2. 1. 理解信号是什么,为什么要有?
  3. 1.1 信号是什么?
  4. 1.1.1 普通信号和实时信号
  5. 1.1.2 信号的本质
  6. 1.2 生活中有哪些信号?以及一些结论总结
  7. 1.2.1 man 7 signal:查看信号部分的内容
  8. 1.2.2 信号没有产生也知道怎么处理
  9. 1.2.3 收到一个信号会立即处理它吗?
  10. 1.2.4 信号怎么处理?
  11. 1.2.5 总结一些这些小结论
  12. 1.2.6 图示
  13. 1.3 信号具体化
  14. 1.3.1 为什么是大写?这些大写的名字是什么?
  15. 1.3.2 头文件(含内核查看)
  16. 1.3.3 保存信号
  17. 1.3.3.1 进程一定要有临时保存信号的能力
  18. 1.3.3.2 我们可以用什么样的特定的数据类型来保存信号?位图
  19. 1.3.4 信号的位图在进程 PCB 里面,谁有资格修改内核数据结构中的字段?
  20. 1.4 为什么要有信号?
  21. 1.5 信号是什么的思维导图
  22. 1.6 如何自定义捕捉信号?
  23. 2. 信号的产生
  24. 2.1 信号产生的方式
  25. 2.1.1 使用系统命令产生,kill 命令
  26. 2.1.2 通过键盘产生信号
  27. 2.1.3 产生信号的还有一种方式:系统调用
  28. 2.1.4 第四种产生信号的方式:异常
  29. 2.1.5 第五种信号产生的方式:由软件条件产生信号
  30. 2.2 三个系统调用层层递进
  31. 2.2.1 三个系统调用层层递进
  32. 2.2.2 OS 先收到键盘输入,再由操作系统转换成信号发送给目标进程
  33. 2.3 键盘怎么能够目标进程发送信号呢?
  34. 2.3.1 如何理解键盘输入?
  35. 2.3.2 OS 如何解释快捷键?
  36. 2.3.3 OS 怎么知道信号应该发送给哪一个进程呢?
  37. 2.3.3.1 进程组
  38. 2.3.3.2 会话 ID:SID
  39. 2.3.3.3 前台进程和后台进程 + 前后台进程切换
  40. 2.3.3.3.1 前台进程
  41. 2.3.3.3.2 后台进程
  42. 2.3.3.3.3 作业控制命令
  43. 1 jobs
  44. 2 fg(foreground)
  45. 3 bg(background)
  46. 4 Ctrl+Z
  47. 5 Ctrl+C
  48. 2.3.3.4 一些结论
  49. 结论 1:Ctrl C 只能用于终止前台进程!
  50. 作业 / 独立的进程组有什么关系?
  51. 会话与进程组(作业)的作业之间的关系?
  52. 在一个会话中,任何时刻,只允许一个进程(组)在前台!
  53. 后台不允许获取
  54. 2.4 alarm:设置一个若干秒之后的闹钟
  55. 2.4.1 闹钟的返回值问题
  56. 2.4.2 闹钟的应用场景
  57. 2.4.3 理解闹钟

更多推荐文章

查看全部
  • 滑动窗口算法核心思路与四道经典题解
  • C 语言基础转向 C++ 的实战指南
  • Java 对象比较详解:基本类型、String 与自定义类
  • Java AQS 核心原理与源码解析
  • Java Map 常用方法与实现类深度解析
  • Java 填充 Word 模板实战:文本、列表与复选框处理
  • AI 绘画在商业设计中的应用与代码实践
  • 基于 SpringBoot+Vue 的 Web 多媒体素材管理系统设计与实现
  • Flutter 开源鸿蒙适配:基于 eip55 的以太坊地址校验方案
  • 支持向量机(SVM)原理、分类与回归详解及 Python 代码实现
  • Python 代码打包为 EXE 完整指南与常见问题排查
  • 量化金融领域主要职位解析:研究、开发与交易
  • AI Agent 中的 Skills 是什么?有什么用?
  • Java 算法实践:动态规划
  • 基于宝塔面板在阿里云部署 Java+React 项目
  • Java 后端 Web API 开发实战:从架构到部署
  • 四大 AI 编程工具横评:TRAE、Qoder、Cursor 与 Copilot 选型指南
  • DeerFlow 实战指南:控制台与 Web UI 双模式深度解析
  • Flutter inappwebview_cookie_manager 鸿蒙适配与安全隔离
  • MIT 电机混合模式控制参数配置与调试实战

相关免费在线工具

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online

  • JSON 压缩

    通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online

  • JSON美化和格式化

    将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online