Re:Linux系统篇(四十四)信号篇·二:Linux 信号的产生原理:硬件异常、软件条件与内核处理机制

📅 2026/7/18 13:27:09
Re:Linux系统篇(四十四)信号篇·二:Linux 信号的产生原理:硬件异常、软件条件与内核处理机制
◆ 博主名称 小此方-CSDN博客大家好欢迎来到小此方的博客。⭐️Linux系列个人专栏 【主题曲】Linux⭐️此方的GitHub github_此方⭐️Re系列专栏我们思考 (Rethink) · 我们重建 (Rebuild) · 我们记录 (Record)文章目录概要序論一、 硬件异常产生信号1.1什么是硬件异常1.2硬件异常举例1.2.1浮点数错误1.2.2段错误1.3 CPU是怎么知道硬件错误1.3.1算术溢出与除0错误1.3.2 内存访问段错误1.4 MMU 查表时的权限与状态判定1.4.1 根本没有映射空指针引发的未分配错误1.4.2 有映射但越界或越权非法访问限制区域1.5 操作系统的接管与进程上下文读取1.6 合法与非法的甄别产生信号二、软件条件产生信号2.1管道产生信号2.2闹钟产生信号2.2.1先见一见闹钟怎么用2.2.2闹钟的用法详细介绍2.2.3递归闹钟与操作系统的基本运行原理2.2.4时间戳与时间片2.2.5操作系统对闹钟的先描述再组织概要序論Hello大家好我是此方今天继续介绍信号是如何产生的问题。上一篇我们介绍了信号的两种产生机制键盘产生和系统调用/命令产生。本文将讲解剩下两类信号产生机制——“硬件异常”与“软件条件”。同时带大家初步接触“时钟中断”等内容。一、 硬件异常产生信号1.1什么是硬件异常在 Linux 操作系统中程序的崩溃或异常终止如除0错误、野指针访问会触发硬件异常CPU 首先捕捉到硬件异常并立刻通过中断机制将其移交给操作系统内核。信号由操作系统发送。1.2硬件异常举例1.2.1浮点数错误8号信号SIGFPE (Floating-point exception)浮点数异常。1.2.2段错误11号信号SIGSEGV (Segmentation violation / Segmentation fault)段错误。1.3 CPU是怎么知道硬件错误操作系统作为软硬件资源的管理者之所以能知道进程的犯错得益于 CPU 内部各种寄存器和硬件单元的实时监控1.3.1算术溢出与除0错误当程序执行a / 0时CPU 的算术逻辑单元ALU在计算时会触发异常并自动将状态寄存器如 x86 的EFLAGS寄存器中的特定标志位例如溢出或错误标志置位。1.3.2 内存访问段错误当程序尝试访问int *p nullptr; *p 100;负责地址翻译的集成硬件MMU内存管理单元会拿着该虚拟地址和CR3 寄存器中保存的当前进程页表物理地址去进行转换。MMU 在将虚拟地址转换为真正的物理地址时主要根据页表项中的“权限和状态”来判断访问是否合法。1.4 MMU 查表时的权限与状态判定1.4.1 根本没有映射空指针引发的未分配错误页表项中有一个最关键的位叫Present存在位通常是 Bit 0Present 1代表该虚拟地址已经映射了真实的物理内存。Present 0代表这块虚拟地址在物理内存中根本不存在。当 MMU 发现对应行的Present位为 0 时硬件逻辑电路就会自动触发中断信号硬件报错。1.4.2 有映射但越界或越权非法访问限制区域即使地址存在映射页表项中还包含R/W读写权限位和U/S用户/内核权限位如果代码企图去修改写一个只读的常量字符串R/W位为 0MMU 就会报错。如果普通应用程序用户态企图去访问内核才能看的地址U/S位为 0MMU 同样会报错。1.5 操作系统的接管与进程上下文读取当 MMU 或 CPU 触发硬件报错后CPU 会瞬间暂停当前正在执行的应用程序强行切换到操作系统的缺页异常处理程序。此时操作系统必须立刻、无条件地读取当前进程的上下文即内核中的current-task_struct以及 CPU 寄存器以此弄清楚两件事是谁在哪个地方犯了错在 Linux 内核源码中current 是一个极其常用的宏或者指针。它永远指向当前正在 CPU 上执行的那个进程的 task_struct。操作系统会读取以下一组特殊的寄存器CR2 寄存器这个寄存器极其重要MMU 报错时会自动把导致报错的那个虚拟地址塞进 CR2 里。操作系统读取它才能知道程序刚才是在访问哪块内存时翻车的。程序计数器CS:RIP / EIP操作系统要看看底是哪一行代码哪条指令触发了这次内存访问。通用寄存器和错误码Error Code搞清楚程序当时是在执行“读”还是“写”以及当时是在用户态还是内核态。1.6 合法与非法的甄别产生信号拿到上下文并读取 CR2 里的虚拟地址后操作系统会去对比当前进程的VMA虚拟内存区域即 vm_area_struct 链表来甄别这次报错是否合法合法错误真正的缺页比如使用mmap分配了虚拟内存但在第一次读写时物理内存还没落实Present 0。操作系统会执行补齐页表的操作分配物理内存并建立映射随后恢复上下文重新执行那条代码程序得以“复活”。非法错误真正的越界/野指针如果操作系统发现该虚拟地址根本不在进程合法的虚拟内存区域内或者违反了读写权限。操作系统就会将其判定为非法并向目标进程发送SIGSEGV段错误信号若是除0错误则发送SIGFPE浮点异常信号。最终强行杀死进程造成程序崩溃。二、软件条件产生信号2.1管道产生信号软件条件产生信号的方式有很多种我们曾经学过管道产生信号的方式。ReLinux系统篇三十七通信篇·二深入理解匿名管道父子进程是如何“看到同一个文件”操作系统不会做任何浪费资源的事情。当读端关闭的时候写段任在写入就毫无意义。此时操作系统必须发送信号SIGPIPE终止进程。2.2闹钟产生信号2.2.1先见一见闹钟怎么用这一块本来应该放在时钟中断讲但是放在这里也可以。补充一下pause系统调用让这个进程处于暂停状态。#includeiostream#includeunistd.h#includesignal.hvoidHander(inti){std::cout收到一个信号istd::endl;}intmain(){for(inti0;i32;i)signal(i,Hander);alarm(10);std::cout定一个闹钟std::endl;std::cout开始睡觉std::endl;pause();std::cout睡醒std::endl;return0;}如上。我们让进程设置一个闹钟。时间到了就会发送14号信号。终止进程。2.2.2闹钟的用法详细介绍调用alarm(seconds)后系统开始倒计时。时间到了会怎样系统会向你的程序发送一个SIGALRM 信号。默认后果如果你的程序没有提前做好准备收到这个信号后会直接退出终止运行。一次只能设一个闹钟如果先调用了alarm(10)紧接着又调用了alarm(3)前一个闹钟会被直接覆盖。程序只会在 3 秒后收到闹钟信号。如何取消闹钟调用alarm(0)即可直接关闭倒计时不会再触发信号。2.2.3递归闹钟与操作系统的基本运行原理我们让闹钟执行的自定义捕捉函数调用新的闹钟。让每一个闹钟响起后执行随机的工作#includeiostream#includeunistd.h#includesignal.h#includetime.h#includevector#includefunctionalstd::vectorstd::functionvoid(int)work;voidfunc_1(intx){std::cout我是一个网络任务参数为: xstd::endl;}voidfunc_2(intx){std::cout我是一个磁盘任务参数为: xstd::endl;}voidfunc_3(intx){std::cout我是一个日志任务参数为: xstd::endl;}voidfunc_4(intx){std::cout我是一个计算任务参数为: xstd::endl;}voidfunc_5(intx){std::cout我是一个数据库任务参数为: xstd::endl;}voidHander(inti){std::cout###########################std::endl;work[rand()%5](rand()%100);alarm(1);std::cout###########################std::endl;}intmain(){work.push_back(func_1);work.push_back(func_2);work.push_back(func_3);work.push_back(func_4);work.push_back(func_5);srand((unsignedint)time(NULL));for(inti0;i32;i)signal(i,Hander);alarm(1);//std::cout定一个闹钟std::endl;//std::cout开始睡觉std::endl;while(true)pause();//std::cout睡醒std::endl;return0;}进程可以处于暂停状态通过外部信号传达实现不同功能的调用。——这就是操作系统以及操作系统去做进程管理和内存管理等工作的基本原理。比如拿进程管理为例子进程被创建好之后PCB就被push_back到一个管理结构中然后操作系统在运行的时候会去遍历这个管理结构找到时间片的下一个进程然后执行他。操作系统的本质就像是我们这个进程登录以后就处于一种死循环。操作系统不会去主动执行这些任务。alarm () 间隔时间为0…0几秒这样的时间间隔我们的操作系统就会以非常高的频率来执行我们的任务操作系统居然不是主动干活的后面会讲这就是时钟中断2.2.4时间戳与时间片时间戳的本质就是一个计数器外部每一秒刺激操作系统一次操作系统调用一次就实时记录操作系统一共开机了多少时间操作系统自启动后的时间间隔2.2.5操作系统对闹钟的先描述再组织OS内会不会同时存在很多的闹钟OS要不要对闹钟进行管理先描述在组织闹钟结构体的伪代码:structtimer_list{structlist_headentry;unsignedlongexpires;void(*function)(unsignedlong);unsignedlongdata;structtvec_t_base_s*base;};我们的很多闹钟会被按照这个时间间隔值unsigned long expires;操作系统自启动后的时间间隔进行建小堆操作系统对比自己的时间间隔值和闹钟的时间间隔值10001005 不响。10061005响。然后调用闹钟的函数传递信号SIGALRM。新建一个闹钟就是在堆里面插入一个闹钟结构体。但是实际上在内核中并没有使用这个堆。闹钟产生信号的这个过程没有硬件参与这就是软件条件触发信号的另一种方式。扯的有点远了信号的产生。我们就讲完了。好的本期内容就到这里如果对你有帮助还不要忘记点赞三联支持。我是此方我们下期再见。bye! Linux、C、算法持续连载中欢迎关注WeChat Official Account 【此方的技术栈】。