【Linux信号】Linux进程信号(上):信号产生方式和闹钟

🎬 个人主页艾莉丝努力练剑
专栏传送门 :《C语言》《数据结构与算法》《C/C++干货分享&学习过程记录
Linux操作系统编程详解》《笔试/面试常见算法:从基础到进阶》《Python干货分享

⭐️为天地立心,为生民立命,为往圣继绝学,为万世开太平


🎬 艾莉丝的简介:


文章目录

  • [1 ~> 理解信号是什么,为什么要有?生活中的信号](#1 ~> 理解信号是什么,为什么要有?生活中的信号)
    • [1.1 信号是什么?](#1.1 信号是什么?)
      • [1.1.1 普通信号和实时信号](#1.1.1 普通信号和实时信号)
      • [1.1.2 信号的本质](#1.1.2 信号的本质)
    • [1.2 生活中有哪些信号?以及一些结论总结](#1.2 生活中有哪些信号?以及一些结论总结)
      • [1.2.1 man 7 signal:查看信号部分的内容](#1.2.1 man 7 signal:查看信号部分的内容)
      • [1.2.2 信号没有产生也知道怎么处理](#1.2.2 信号没有产生也知道怎么处理)
      • [1.2.3 收到一个信号会立即处理它吗?](#1.2.3 收到一个信号会立即处理它吗?)
      • [1.2.4 信号怎么处理?](#1.2.4 信号怎么处理?)
      • [1.2.5 总结一些这些小结论](#1.2.5 总结一些这些小结论)
      • [1.2.6 图示](#1.2.6 图示)
    • [1.3 信号具体化](#1.3 信号具体化)
      • [1.3.1 为什么是大写?这些大写的名字是什么?](#1.3.1 为什么是大写?这些大写的名字是什么?)
      • [1.3.2 头文件(含内核查看)](#1.3.2 头文件(含内核查看))
      • [1.3.3 保存信号](#1.3.3 保存信号)
        • [1.3.3.1 进程一定要有临时保存信号的能力](#1.3.3.1 进程一定要有临时保存信号的能力)
        • [1.3.3.2 我们可以用什么样的特定的数据类型来保存信号?位图](#1.3.3.2 我们可以用什么样的特定的数据类型来保存信号?位图)
      • [1.3.4 信号的位图在进程PCB里面,谁有资格修改内核数据结构中的字段?](#1.3.4 信号的位图在进程PCB里面,谁有资格修改内核数据结构中的字段?)
    • [1.4 为什么要有信号?](#1.4 为什么要有信号?)
    • [1.5 信号是什么的思维导图](#1.5 信号是什么的思维导图)
    • [1.6 如何自定义捕捉信号?](#1.6 如何自定义捕捉信号?)
  • [2 ~> 信号的产生](#2 ~> 信号的产生)
    • [2.1 信号产生的方式](#2.1 信号产生的方式)
      • [2.1.1 使用系统命令产生,kill命令](#2.1.1 使用系统命令产生,kill命令)
      • [2.1.2 通过键盘产生信号](#2.1.2 通过键盘产生信号)
      • [2.1.3 产生信号的还有一种方式:系统调用](#2.1.3 产生信号的还有一种方式:系统调用)
      • [2.1.4 第四种产生信号的方式:异常](#2.1.4 第四种产生信号的方式:异常)
      • [2.1.5 第五种信号产生的方式:由软件条件产生信号](#2.1.5 第五种信号产生的方式:由软件条件产生信号)
    • [2.2 三个系统调用层层递进](#2.2 三个系统调用层层递进)
      • [2.2.1 三个系统调用层层递进](#2.2.1 三个系统调用层层递进)
      • [2.2.2 OS先收到键盘输入,再由操作系统转换成信号发送给目标进程](#2.2.2 OS先收到键盘输入,再由操作系统转换成信号发送给目标进程)
    • [2.3 键盘怎么能够目标进程发送信号呢?](#2.3 键盘怎么能够目标进程发送信号呢?)
      • [2.3.1 如何理解键盘输入?](#2.3.1 如何理解键盘输入?)
      • [2.3.2 OS如何解释快捷键?](#2.3.2 OS如何解释快捷键?)
      • [2.3.3 OS怎么知道信号应该发送给哪一个进程呢?](#2.3.3 OS怎么知道信号应该发送给哪一个进程呢?)
        • [2.3.3.1 进程组](#2.3.3.1 进程组)
        • [2.3.3.2 会话ID:SID](#2.3.3.2 会话ID:SID)
        • [2.3.3.3 前台进程和后台进程 + 前后台进程切换](#2.3.3.3 前台进程和后台进程 + 前后台进程切换)
          • [2.3.3.3.1 前台进程](#2.3.3.3.1 前台进程)
          • [2.3.3.3.2 后台进程](#2.3.3.3.2 后台进程)
          • [2.3.3.3.3 作业控制命令](#2.3.3.3.3 作业控制命令)
            • [1 jobs](#1 jobs)
            • [2 fg(foreground)](#2 fg(foreground))
            • [3 bg(background)](#3 bg(background))
            • [4 Ctrl+Z](#4 Ctrl+Z)
            • [5 Ctrl+C](#5 Ctrl+C)
        • [2.3.3.4 一些结论](#2.3.3.4 一些结论)
    • [2.4 alarm:设置一个若干秒之后的闹钟](#2.4 alarm:设置一个若干秒之后的闹钟)
      • [2.4.1 闹钟的返回值问题](#2.4.1 闹钟的返回值问题)
      • [2.4.2 闹钟的应用场景](#2.4.2 闹钟的应用场景)
      • [2.4.3 理解闹钟](#2.4.3 理解闹钟)
  • 结尾

1 ~> 理解信号是什么,为什么要有?生活中的信号

1.1 信号是什么?

1.1.1 普通信号和实时信号

  • kill -l可以查看

常用的只有1~3134~64,没有0号------一共62个,不是64个哦!

前31个是普通信号,后面带RT(real-time)实时信号。我们只考虑1-31就好了。

1.1.2 信号的本质


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

  • 通知:这个通知需要我们理解一下------事件通知。
  • 异步:通知的到来,跟我不同步。
    举个例子,我点了个外卖,然后就打游戏,外卖小哥送货上门,敲门是一个通知,说明外卖到了,我和外卖小哥是不同步的。
    异步关系就是没关系(你做你的,我做我的);同步关系就是有关系(我得等你做完再做)。
  • 再比如,比如我是一个老师,我讲课,突然快递电话打过来了,我叫张三去帮我取快递,但是我等张三找完快递回来再继续讲,这就是个同步的关系,张三不回来,我就不往下讲。
  • 如果我继续讲课,张三也在找快递,我讲课、他帮我办事,这个就是个异步的关系------我讲我的课,张三找他要帮我找的快递。

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

大抵有如下这些:

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

  • 人能够识别对应的、甚至还没有发生的这些信号

为什么能够识别这些信号?

人是经过教育的,早就知道信号对应处理动作的对应关系。

操作系统给目标进程发送信号,目标进程能不能识别呢?

  • 答案是:进程天然能够识别------进程和信号都是程序员写的代码 (进程相当于我们人,已经被程序员教育过了,程序员设计好了)
    进程能够识别信号,并且已经知道怎么处理信号了。

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

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

bash 复制代码
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 如何自定义捕捉信号?

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

  • 对任何信号进行一下捕捉!
bash 复制代码
man signal

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

  • 回调函数

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

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

运行一下:

bash 复制代码
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系统调用,向目标进程写入信号------

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

我们再修改一下源代码:

  • \r都去掉

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

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 三个系统调用层层递进

前面的killraiseabort三个系统调用我们发现是层层递进的!

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

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

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

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

换句话说,通过键盘产生信号也并不是键盘直接产生的信号,必然是OS先收到键盘

的输入的,再由操作系统转换成信号发送给目标进程!

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

  • 1、如何理解键盘输入?键盘是基于硬件中断来进行工作的!
  • 2、OS如何解释快捷键?(组合键)ctrl cctrl \,......可能会直接解释成为信号!
  • 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不能干掉进程了------

再看一下:

观察运行的打印结果:

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

前台进程:

后台进程:

我们只能这样删除:

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

再创建一大批的任务:

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

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

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

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

2.3.3.3.2 后台进程

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

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

bash 复制代码
sleep 100 &

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

2.3.3.3.3 作业控制命令
1 jobs
bash 复制代码
jobs

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

输出示例:

bash 复制代码
[1]+ Running sleep 100 &
2 fg(foreground)

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

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

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

  • 先用 Ctrl+Z 暂停前台进程,此时作业变为"已停止"状态。

  • 执行 bg %1 让它在后台继续运行

bash 复制代码
sleep 200          # 执行,然后按 Ctrl+Z 暂停
bg %1              # 在后台继续运行
jobs               # 可看到状态变为 Running
4 Ctrl+Z

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

5 Ctrl+C

终止前台进程。

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

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中的关机程序:

bash 复制代码
shutdown -t 10 -s(带选项)

实现 延迟关机 的功能。

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

有点类似于"看门狗"------

  • 使用 alarm 实现看门狗(B进程)
c 复制代码
#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进程只需要通过共享内存访问计数器,并定期重置:

c 复制代码
#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了。


结尾

uu们,本文的内容到这里就全部结束了,艾莉丝在这里再次感谢您的阅读!

结语:希望对学习Linux相关内容的uu有所帮助,不要忘记给博主"一键四连"哦!

往期回顾

【Linux进程间通信:共享内存】为什么共享内存的 key 值由用户设置

🗡博主在这里放了一只小狗,大家看完了摸摸小狗放松一下吧!🗡 ૮₍ ˶ ˊ ᴥ ˋ˶₎ა

相关推荐
Bonnie3732 小时前
算力基建入门-AI时代,算力为何是数字底座
人工智能·程序人生·云原生·个人开发
前端摸鱼匠2 小时前
面试题6:因果掩码(Causal Mask)在Decoder中的作用是什么?训练、推理阶段如何使用?
人工智能·ai·语言模型·自然语言处理·面试
牛奶咖啡132 小时前
基于Cobbler的系统自动化安装部署——各类Linux系统镜像的导入配置与客户端安装测试
运维·自动化·devops·红帽系系统的批量自动化部署安装·德班系系统的批量自动化部署安装·系统导入cobbler步骤·系统部署实现批量自动化安装
这张生成的图像能检测吗2 小时前
(论文速读)ASFRMT:基于对抗的超特征重构元传递网络弱特征增强与谐波传动故障诊断
人工智能·深度学习·计算机视觉·故障诊断
qwfys2002 小时前
如何在Ubuntu 24.04上安装Cursor
ubuntu·安装·cursor·24.04
wengqidaifeng2 小时前
备战蓝桥杯----C/C++组 (一)数据结构与STL讲解(中):树、二叉树与堆——从层次结构到优先队列的进阶之路
c语言·c++·蓝桥杯
statistican_ABin2 小时前
Python数据分析-宝马全球汽车销售数据分析(可视化分析)
大数据·人工智能·数据分析·汽车·数据可视化
ARM+FPGA+AI工业主板定制专家2 小时前
基于ARM+FPGA+AI的船舶状态智能监测系统(一)总体设计
网络·arm开发·人工智能·机器学习·fpga开发·自动驾驶
前端摸鱼匠2 小时前
面试题7:Encoder-only、Decoder-only、Encoder-Decoder三种架构的差异与适用场景?
人工智能·深度学习·ai·面试·职场和发展·架构·transformer