操作系统详解(5)——信号(Signal)

news2025/3/2 4:33:33

系列文章:
操作系统详解(1)——操作系统的作用
操作系统详解(2)——异常处理(Exception)
操作系统详解(3)——进程、并发和并行
操作系统详解(4)——进程控制(fork, waitpid, sleep, execve)

文章目录

  • 概述
  • 信号的种类
    • Hardware Events
    • Software Events
  • 信号的原理
    • 信号发送
    • 信号接收
    • Pendning Signal(等待的信号)
    • Block a Signal
    • 信号的 Internal Data Structures
  • 信号的实现机制
    • 进程组
      • process group
      • 实例应用: kill Program
      • 实例应用: shell
    • 发送信号
      • kill Function
      • alarm Function
    • 接收信号
      • signal handler
      • 更改默认行为
    • Block & Unblock Signals
  • 总结

概述

与Exception(异常处理)相比,signal是软件层面的,更高级的处理机制。signal能使当前的进程和kernel打断其它的进程。

低等级的 hardware exceptions:

  • processed by kernel’s exception handlers
  • not normally be visible to user processes
  • Signals 提供了机制,能把exceptions的出现暴露给用户进程

高等级的 software events:

  • in the kernel
  • in other user processes

信号的种类

Hardware Events

  • SIGFPE signal (number 8)
    • process 尝试除0
    • kernel向其发送SIGFPR signal
  • SIGILL signal (number 4)
    • process 执行非法指令
    • kernel向其发送
  • SIGSEGV signal (number 11)
    • process 访问非法内存

Software Events

  • SIGINT signal (number 2)
    • 按ctrl-c,kernel对在foreground(bash前台)执行的进程,发送SIGINT signal
  • SIGKILL signal (number 9)
    • 一个进程可以向另一个进程发送SIGKILL signal
    • 来强制终止另一个进程
  • SIGCHLD signal (number 17)
    • 当子进程终止(terminate)或阻塞(stop)时
    • kernel向父进程发送SIGCHLD siganl

以下是linux系统中的一些信号:(可以通过>man signal查询)
image.png

信号的原理

信号发送

信号发送的两种方式:

  • kernel检测到的 system event
    • Divide by zero
    • Termination of a child process
  • 一个进程调用kill function
    • 显式请求kernel向 destination process 发送signal
    • 进程可以向自身发送信号

以下是kill函数的定义:

#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
//returns: 0 if OK, -1 on error

发送signal后, kernel会更新destination process的上下文状态. 具体一点地来说, 每一个进程都有其维护的上下文.
![[进程#上下文内容]]

kernel会为每一个process维护:

  • pending bit vector
  • blocked bit vector
    (意思将在下面介绍)
    发送信号后, kernel会更新目标进程的pending bit vector, 并把相应的 信号位 置为1, 表示信号已经发送.

信号接收

目标进程收到信号,指的是kernel迫使它对向它发送的信号作出反应:

  • Ignore the signal
  • Terminate
  • Catch the signal
    • by executing a user-level function called a signal handler

Pendning Signal(等待的信号)

Pending signal 是已经发送但还没有接收到的信号

Attention:

  • Only one
    • 对一个进程而言, 每一种类型的 pending siganl 若存在仅有一个
  • Not queued
    • 如果一个进程已经有了编号为k的pending signal,则后续发送到该进程的signal k不会被排序
    • 意思是它们会直接被丢弃(忽视)
    • 一个pending signal 一次最多只会接收到一个(在处理信号之前)

Block a Signal

一个进程可以选择性地对特定的信号进行阻塞.
如果信号被阻塞, 意味着可以向它发送信号, 并把pending bit置为1, 但是pending signal不会被接收(不会执行相应的action), 直到process unblock 该信号.

信号的 Internal Data Structures

kernel维护每一个进程的:

  • the set of pending signals in the pending bit vector
  • the set of blocked signals in the blocked bit vector

The kernel sets bit k in pending
whenever a signal of type k is delivered

The kernel clears bit k in pending
whenever a signal of type k is received.
(当接收到该信号后清除pending bit)

信号的实现机制

进程组

process group

每一个进程都只属于一个process group
以一个正整数标识(process group ID, 即pgid)

默认情况下, 子进程和父进程属于同一个进程组.

#include <unist.h>
pid_t getpgrp(void);
// returns: process group ID of calling process

#include <unistd.h>
pid_t setpgid(pid_t pid, pid_t pgid);
// returns: 0 on success, -1 on error
// change the process group of process pid to pgid
//If pid == zero, 则使用当前进程的PID
//If pgid == zero, 则将被pid指定的进程的PID作为PGID 

举个栗子: setpgid(0, 0)
如果当前进程ID是15213, 则这个函数调用将:

  • 新创建一个进程组, 且process group ID is 15213
  • 将进程15213添加到该组

实例应用: kill Program

kill是 linux 里的一个程序, 能够将任意的信号发送到其它进程.

unix> kill -9 15213
- sends signal 9 (SIGKILL) to process 15213
unix> kill -9 -15213
- a negative PID 指代的则是group ID
- 这里的作用是: sends a SIGKILL signal to every process in process group 15213.

实例应用: shell

我们平时使用的命令行就使用了进程相关的知识.
在shell中, 每当我们键入一个合法的命令行, 就会创建一个Job. 这是一个抽象的概念, 下面是一个例子:

unix> ls | sort
-a foreground job consisting of two processes connected by a pipe

在shell中, 最多只有一个foreground job在运行, 但可能有零个或者更多(没有限制)background job

对于每一个job, shell都为他们创建了各自的process group
一般来说, process group ID 就来自于job中的parent process

下图是一个shell里process group的结构:
image.png

当我们在键盘按下ctrl-c时, 将会导致SIGINT signal被发送到shell.
Shell 首先捕捉(catch)到这个信号,然后执行相应的handler, 将SIGINT signal 发送到foreground process group里的每一个进程(包括父进程和子进程)
默认情况下这将终止foreground job

发送信号

kill Function

上面有提到过kill Function, 在我们已经掌握进程组的概念后再来回顾一下:

#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
//returns: 0 if OK, -1 on error
  • pid > 0:
    • 向number=sig的信号发送给process pid
  • pid < 0:
    • 发送给每一个在进程组 abs(pid) 里的进程

下面是个使用kill函数的代码片段:

#include <stdio.h>
#include <stdlib,h>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>
int main()
{
	pid_t pid;

	// Child sleeps until SIGKILL signal received
	if ((pid = Fork()) == 0) {
	    pause(); // Wait for a signal to arrive
	    printf("Control should never reach here!\n");
	    exit(0);
	}

	// Parent sends a SIGKILL signal to a child
	Kill(pid, SIGKILL);
	exit(0);
}

很有意思的是, printf语句将永远不会被执行. 子进程在pause()之后陷入沉睡, 当收到SIGINT信号后, 会直接终止.

alarm Function

#include <unist.h>
unsigned int alarm(unsigned int secs);
// returns: 前一个alarm调用剩余的秒数, 如果没有previous alarm则返回0

alarm的意思是计时器. 这个系统调用让kernel在secs秒后向该进程发送 SIGALRM 信号
如果secs为0, 则会取消当前的alarm

alarm函数会取消之前的pending alarms

接收信号

signal handler

当kernel结束一次异常处理, 即将将控制流交还给进程p的用户态时, 会检查 set of unblocked pending signals(pending & blocked)
这里指的异常处理, 广义上应指Exception Handler, 包含 异步中断同步中断 , 关于Exception Handling 的相关内容可回顾[[异常处理]]

如果有pending signal, 且没有被blocked(即pending bit = 1, blocked bit = 0) , 那么该信号就属于unblocked pending signals 的集合.

如果该集合为空(usual), 则用户态执行控制流(被中断前执行)的下一条语句
但如果集合非空, 那么kernel就会选择set中的一个信号k(通常是最小的sig数), 并迫使进程p receive signal k.

信号的接收会引发相关的操作, 上面提到, 是Ignore, Terminate or catch.
当执行完后, 程序执行回到逻辑控制流原来的下一条语句 I n e x t I_{next} Inext
屏幕截图 2024-01-13 205753.png
每一种信号原来都有默认的action, 为以下之一:

  • The process terminates.
  • The process terminates and dumps core.
  • The process stops until restarted by a SIGCONT signal.
  • The process ignores the signal.

注: dump core 指的是核心转储. 程序异常终止或崩溃后会生成核心转储文件, 保存程序的相关数据, 用于定位错误.

一般情况下, 信号的默认action都可以更改, 但以下两种除外:

  • SIGKILL
  • SIGSTOP

关于各种信号的默认行为, 可以在文章开始部分的表里查询.

更改默认行为

用户可以使用 signal 函数来更改特定信号的行为(当然, SIGKILL 和 SIGSTOP 除外)

#include <signal.h>
typrdef void handler_t(int)
handler_t *signal(int signum, handler_t *handler)
//returns: ptr to previous handler if OK,
//SIGERR on error (does not set errno(全局错误变量))
  • handler is SIG_IGN: ignore signal of type signum
  • handler is SIG_DFL: 恢复默认行为
  • 另外的情况, call signal handler
    这里的handler是用户自定义的一个函数

下面是一个例子:

void handler(int sig) /* SIGINT handler */
{
    printf("Caught SIGINT\n");
    exit(0);
}

int main()
{
    /* Install the SIGINT handler */
    if (signal(SIGINT, handler) == SIG_ERR)
        unix_error("signal error");

    pause(); /* wait for the receipt of a signal */

    exit(0);
}

当handler返回时, 会触发一个 sigreturn 系统调用. kernel这时会再次check pending and blocked vector,如果有
unblocked pending signals,则重复以上步骤;没有,则返回原先执行语句的下一条。
image.png

PS: 相同的 handler function 可以用来catch不同种类的信号

Block & Unblock Signals

Signal Handlers 也可以被打断,如下图:
image.png

Attention: Signal Handler 不会被与自身绑定的信号再次打断。在上例中,如果handler S在执行时收到了signal s, 那么handler不会从头再开始执行。
这是由于当kernel强迫process catch信号后,将该信号的pending bit设为0,blocked bit设为1, 来表示没有延迟的信号, 并防止handler再一次被相同类型的信号打断.

上面所说的, 是隐式的阻塞机制.
用户也可以显式地设置信号的阻塞与否.

#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
int sigemptyset(sigset_t *set);
//清空set(置为全0)
int sigfillset(sigset_t *set);
//把当前blocked的状态赋给set
int sigaddset(sigset_t *set, int signum);
//往set里增加编号为signum的信号
int sigdelset(sigset_t *set, int signum);
//从set里删除编号为signum的信号
//Above: Return: 0 if OK, -1 on error

int sigismember(const sigset_t *set, int signum);
//查找编号为signum的信号是否在set中
//Returns: 1 if member, 0 if not, -1 on error

sigprocmask函数: 设置blocked signals
set相当于一个遮罩, 存储的其实就是一个vector, 里面为1的值就是被阻塞的信号
blocked是实际上的blocked vector
(set是辅助用的容器,而blocked是实际上进程信号被blocked的情况)

how:

  • SIG_BLOCK: 将set中的信号全部添加到blocked
    • blocked = blocked | set
  • SIG_UNBLOCK: 将set中的信号全部从blocked移除
    • blocked = blocked & ~set
  • SIG_SETMASK: 将set中信号的状态赋给blocked
    • blocked = set

oldset: 如果不是NULL, 则传入的指针存储blocked bit vector先前的状态, 以便恢复

其它的函数在注释里给予了说明.

总结

本文介绍了signal的基本原理与实现机制, 并给予了signal发送,接收,处理的使用用例. 但信号的知识点非常复杂, 接下来的文章将辅以实际的案例, 通过题目以及手搓一个简单的shell来加深对signal的理解.

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

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

相关文章

PostgreSQL认证考试PGCA、PGCE、PGCM

PostgreSQL认证考试PGCA、PGCE、PGCM 【重点&#xff01;重点&#xff01;重点&#xff01;】PGCA、PGCE、PGCM 直通车快速下正&#xff0c;省心省力&#xff0c;每2个月一次考试 PGCE考试通知 &#xff08;2024&#xff09; 一、考试概览 &#xff08;一&#xff09; 报名要…

【C++】wxWidgets库实现窗体程序

一、安装wxWidgets库 在Debian系统上使用wxWidgets库来创建一个基本的窗体程序&#xff0c;首先需要确保已经安装了wxWidgets相关的库和开发工具。下面是安装wxWidgets的步骤&#xff1a; 打开终端&#xff0c;使用下述命令安装wxWidgets库及其开发文件&#xff1a; sudo ap…

恒通未来-大数据传输中的WDM解决方案

DWDM的出现是光纤传输技术发展中最新的重要现象之一。本教程将介绍DWDM技术的基本原理&#xff0c;如组件、DWDM系统中使用的光放大器等。 组件和操作&#xff1a; DWDM是光传输网络中的一种核心技术。DWDM的基本组件可以根据其在系统中的位置进行分类。在发射方面&#xff0…

hardware simulation——框架搭建

目录 引子 代码风格约束 代码结构和模板 引子 前几天有人拿个word文档&#xff0c;问我怎么实现&#xff0c;概括一下就是用c实现数码管显示。 但是咱们肯定不做这么简单这么点&#xff0c;我打算做个开源的项目&#xff0c;可以一直更新底层软件库&#xff0c;和上层显示库…

在centos系统安装mqtt

在CentOS系统上安装MQTT&#xff0c;通常意味着要安装一个MQTT代理&#xff08;broker&#xff09;&#xff0c;比如Mosquitto。下面是在CentOS上安装Mosquitto的步骤&#xff1a; 添加EPEL仓库&#xff1a; 由于Mosquitto可能不在CentOS默认的Yum仓库中&#xff0c;你可能需要…

关于 setData 同步异步的问题

小程序官方文档中的回答解释: 所以大概意思就是: 1.setData在逻辑层的操作是同步&#xff0c;因此this.data中的相关数据会立即更新,比如下面的例子: const a 1 this.setData({b: a ? a : , }) console.log(that.data.b) // 1 2. setData在视图层的操作是异步&#xff0c;…

八爪鱼拉拉手

欢迎来到程序小院 八爪鱼拉拉手 玩法&#xff1a;点击鼠标左键拖动移动八爪鱼&#xff0c;当他的手很忙的时候他会很高兴&#xff0c; 不同关卡不同的八爪鱼的位置摆放&#xff0c;快去闯关吧^^。开始游戏https://www.ormcc.com/play/gameStart/248 html <div id"gam…

QuEra 10,000个物理量子位和100个逻辑量子位的量子计算机2026

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

Python新年文字烟花简单代码

简单的Python新年烟花代码示例&#xff1a; import random import timedef create_firework():colors [红色, 橙色, 黄色, 绿色, 蓝色, 紫色]flashes [爆裂, 闪光, 旋转, 流星, 喷射]color random.choice(colors)flash random.choice(flashes)print(f"发射一枚{color…

综合评价 | 基于EW、EW-BP、EW-ELM的地区发展水平综合评价(Matlab)

目录 效果一览基本介绍程序设计参考资料 效果一览 基本介绍 综合评价 | 基于EW、EW-BP、EW-ELM的地区发展水平综合评价&#xff08;Matlab&#xff09; 程序设计 完整程序和数据获取方式&#xff1a;私信博主回复基于EW、EW-BP、EW-ELM的地区发展水平综合评价&#xff08;Matl…

最新域名群站开源系统:打造强大网站矩阵,引领SEO优化新潮流!

搭建步骤 第一步&#xff1a;安装PHP和MYSQL服务器环境 对于想要深入了解网站建设的人来说&#xff0c;自己动手安装PHP和MYSQL服务器环境是必不可少的步骤。这将使你能够更好地理解网站的运行机制&#xff0c;同时为后续的网站开发和优化打下坚实基础。 第二步&#xff1a;…

uniapp滑动页面切换和下拉刷新,触底加载更多(swiper + scroll-view)

因为官方文档乱七八糟的&#xff0c;所以自己来总结下 需求&#xff1a; 常见的上方tag标签切换&#xff0c;下方是页面&#xff0c;上面点击切换&#xff0c;下面页面也切换&#xff0c;下方列表有下拉刷新&#xff0c;触底加载更多 因为这两个组件都是固定高度的&#xff0c;…

java swing UI第三方设计器JFormDesiner和FlatLaf UI

安装JFormDesiner 官网&#xff1a;https://www.formdev.com/ 先去IDEA的插件市场安装吧 JFormDesiner是非开源&#xff0c;且付费的插件&#xff0c;可以自己去找找不付费的使用方法。在swing可视化设计UI非常高效快捷&#xff0c;初学者可能需要一定时间探索&#xff0c;熟…

高性能RPC框架解密

专栏集锦&#xff0c;大佬们可以收藏以备不时之需&#xff1a; Spring Cloud 专栏&#xff1a;http://t.csdnimg.cn/WDmJ9 Python 专栏&#xff1a;http://t.csdnimg.cn/hMwPR Redis 专栏&#xff1a;http://t.csdnimg.cn/Qq0Xc TensorFlow 专栏&#xff1a;http://t.csdni…

【数据结构】串,数组,广义表 | 笔记整理 | C/C++实现

文章目录 前言一、串1.1、串的定义1.2、案例引入1.3、串的类型定义和存储结构1.4、串的模式匹配算法1.4.1、BF算法1.4.2、KMP算法 二、数组2.1、数组的定义2.2、数组的抽象数据类型定义2.3、数组的顺序存储2.4、特殊矩阵的压缩存储 三、广义表四、病毒案例 前言 参考视频&…

大模型实战营Day4 作业

基础作业&#xff1a; 构建数据集&#xff0c;使用 XTuner 微调 InternLM-Chat-7B 模型, 让模型学习到它是你的智能小助手&#xff0c;效果如下图所示&#xff0c;本作业训练出来的模型的输出需要将不要葱姜蒜大佬替换成自己名字或昵称&#xff01; 微调前&#xff08;回答比较…

CF1178F1 Short Colorful Strip 题解

Short Colorful Strip 传送门 题面翻译 题目描述 这是F题的第一个子任务。F1和F2的区别仅在对于m和时间的限制上 有n1种颜色标号从0到n&#xff0c;我们有一条全部染成颜色0的长为m的纸带。 Alice拿着刷子通过以下的过程来给纸带染色&#xff1a; 我们按照从1到n的顺序进…

redis夯实之路-主从复制详解

Redis中可以通过执行slaveof命令或者设置slaveof选项&#xff0c;让一个服务器区复制另一个服务器&#xff0c;被复制的为主服务器&#xff0c;复制的为从服务器。 复制 Redis中可以通过执行slaveof命令或者设置slaveof选项&#xff0c;让一个服务器区复制另一个服务器&#…

科大讯飞星火大模型接入API js 部分 接口 安装注意事项

下载以下链接例子运行程序 https://xfyun-doc.xfyun.cn/static%2F16968175055332330%2Fspark_js_demo.zip 官网给的说明 准备 1.在demo中填写APPID、APISecret、APIKey&#xff0c;可到控制台-我的应用-大模型页面获取 2.安装nodejs 本地运行 1.打开cmd&#xff0c;进入dem…

2024三掌柜赠书活动第三期:Rust系统编程

目录 前言 Rust语言概念 关于《Rust系统编程》 Rust系统编程的核心点 Rust系统编程的关键技术和工具 编辑推荐 内容简介 作者简介 图书目录 书中前言/序言 《Rust系统编程》全书速览 结束语 前言 在技术圈&#xff0c;最近的编程语言新秀当属Rust莫属&#xff0c;R…