计算机系统-异常控制流

news2024/11/15 8:49:27

例行前言:

本篇不是学习课程时的笔记,是重看这本书时的简记。对于学习本课程的同学,未涉及的内容不代表考试不涉及。核心内容是信号部分。本章内容介绍了较多的信号处理函数,需要在实验中巩固本章所学内容及相关问题的处理(并发,信号阻塞等)。

计算机系统-异常控制流

本章主要介绍计算机对于异常控制流(ECF)的处理方式。

1.异常

异常是ECF的一种形式,既有硬件的异常,也有操作系统实现的软件异常。常见的异常事件有缺页、溢出、除零、IO等。处理器检测到异常时,会通过异常表跳转到异常处理程序进行处理。异常处理程序完成处理后,有三种可能的情况:

  • 回到发生异常的指令重新执行
  • 回到发生异常的下一条指令
  • 终止程序

异常处理

异常处理通常需要硬件和软件配合完成。在系统中,每个异常都会有一个异常号,这些号码一部分由处理器分配,一部分由操作系统分配。系统启动时,会初始化一个异常表,异常表的起始地址放在异常表基址寄存器中,根据异常号找到相应的异常处理程序的起始地址。

异常处理和过程调用相似,但有以下几点不同:

  • 跳转到处理程序之前,处理器将返回地址压栈,这个返回地址可能是当前指令或下一条指令。(OPENMIPS中,返回地址会存储到协处理器CP0的EPC寄存器当中,ctrl模块处理异常时向PC写入异常处理例程的地址,执行eret指令时,ctrl模块向PC写入来自CP0的EPC)

  • 处理器会将条件码和含有其他内容的寄存器压栈,以便恢复程序的执行

  • 如果控制从用户程序转移到内核,所有寄存器等信息被压到内核栈

  • 异常处理程序运行在内核模式下

两种类型的寄存器保存/恢复:
当控制从用户态到内核态时,所有的寄存器都被保存到内核栈当中,从内核态恢复时,也从内核栈中恢复寄存器;当发生进程切换时,处于内核态的程序的内核寄存器会被显式的保存到进程的进程结构当中,并从另一个进程的进程结构恢复内核寄存器。最终,新的进程会通过新进程的内核栈,恢复到用户态。(OSTEP:P43)

异常类别

不同的系统会将异常分为不同的类别。

CSAPP根据异步/同步、返回行为将异常分为四类:

  • 中断:中断是异步发生的,是外部IO设备的信号的结果。IO设备会向处理器发出中断信号,处理器检测到后会处理中断,然后返回到下一条指令。
  • 陷阱(系统调用):陷阱是用户向内核请求服务时主动发起的异常,提供了系统调用的接口。系统调用运行在内核模式当中,允许执行特殊指令和访问内核栈。
  • 故障:故障由错误引起,可能可以被修正。缺页异常就是一个故障,并且可以被解决。对于可以解决的故障,当处理结束后,会回到故障发生的指令重新执行。如果无法解决错误,会导致程序终止。
  • 终止:不可恢复的致命错误导致,通常是硬件错误。

IA32的系统调用

Linux提供上百种系统调用。系统调用通过一条int指令触发,参数为异常号,在异常表中对应一个异常处理例程的地址。IA32的系统调用为128号异常,即通过int 0x80进行系统调用。系统调用的参数(调用号等),通过寄存器来传递,按照惯例,%eax寄存器保存系统调用号。

2.进程

进程是一个执行中的程序的实例。

虚拟化部分内容已在OS中详细介绍。本节有一张已在前章节出现过的图:

3.系统调用错误处理

Unix系统函数遇到错误时,会返回-1,并设置errno来表示出错原因。检查错误是必要的,既是为了保证程序的正确性,也是为了在编写程序的过程中找到错误的来源。以下是调用fork时进行的错误检查:

if (pid=fork()<0){
    fprintf(stderror,"fork error:%s\n",stderror(errno));
    exit(0);
}

debug时的错误检查输出:
当你编写的代码触发了你无法意识到的异常,你可能需要输出中间结果来进行debug,而当编写的程序规模扩大,处理这些输出会变得麻烦,一个方式是使用宏定义:

#ifdef DEBUG std::cout<<...中间结果<<std::endl;
#endif g++ main.c -o main -D DEBUG 

可以宏定义不同的名称,对应不同级别(LOG,DEBUG等),控制程序的输出。

4.进程控制

本节介绍一些Unix中操作进程的系统调用。

获取进程ID

#include <sys/types.h>
#include <unistd.h>

pid_t getpid(void);
pid_t getppid(void);

创建和终止进程

可以认为进程总是处于以下三种状态之一:(实际上可能有更多状态)

  • 运行:正在执行或等待被调度
  • 停止:进程的执行被挂起(休眠),不会被调度。收到SIGSTOP,SIGTSTP,SIDTTIN等信号时,进程就会进入这种状态,直到收到一个SIGCONT信号,再次回到运行状态
  • 终止:进程永远停止。有三种原因会导致终止:
    • 收到一个信号,信号的处理行为是终止进程
    • 从主程序返回
    • 调用exit()函数
pid_t fork(void);		//创建子进程
void exit(int status);	//终止进程	

fork创建的子进程和父进程拥有相同的代码和数据,区别二者的方式是从fork返回时的值不同。子进程拥有逻辑上相同但独立的地址空间,和父进程有相同的数据,但独立的在fork之后的程序中修改数据。(如果不需要写数据,仅仅是读数据,父进程与子进程实际上使用的是同一份物理地址空间中的数据,即采用COW(COPY-ON-WRITE)机制)。

回收子进程

子进程由于某些原因终止时,并未直接清除,需要父进程将其回收,才能彻底释放资源。如果父进程没有回收子进程就已经终止了,内核会安排init进程回收子进程,init进程是系统初始化是创建的一个常驻进程。

一个进程可以用waitpid来等待子进程终止。

pid_t waipid(pid_t pid, int *status, int options);
  • pid:等待的子进程包括哪些
    • pid>0:等待一个单独子进程,进程id为pid
    • pid=-1:所有子进程
    • 其他(进程组)
  • status:status非空,则等待后返回子进程的状态信息到status,根据传入status的不同,返回值表示的意思也不同,以下是一些status的可选项:
    • WIFEXITED:如果子进程通过exit或return返回,则status返回真
    • WEXITSTATUS:返回一个正常终止的子进程的退出状态
    • WIFSINALED:如果子进程是因为一个未被捕获的信号终止的,返回真
    • WTERMSIG:返回导致子进程终止的信号的数量
    • WIFSTOPPED:如果引起返回的子进程是被停止的,返回真
    • WSTOPSIG:返回引起子进程停止的信号的数量
  • options:可以设置为WNOHANG和WUNTRACED的组合,修改默认行为。默认的行为是挂起调用waitpid的进程,直到有子进程终止
    • WNOHANG:等待集合中的任何子进程都没终止,就立即返回0。
    • WUNTRACED:挂起调用进程的执行,直到等待集合中的一个进程已变为终止或停止,返回停止或终止的子进程PID。当需要检查停止的子进程时,这个选项有用
    • WNOHANG|WUNTRACED:立即返回。返回值为0表示等待集合没有任何子进程终止或停止,否则返回停止或终止的进程的PID

进程休眠

sleep函数可以让系统挂起一个进程一段时间。而pause函数可以挂起一个进程,直到该进程收到一个信号。

unsigned int sleep(unsigned int secs);		//时间到返回0,因为信号而提前返回则返回剩余的秒数
int pause(void);

加载并运行程序

fork产生的子进程的代码和父进程是相同的,要将新进程替换为新的程序内容,需要使用execve函数。

int execve(const char *filename, const char *argv[], const char *envp[]);

5.信号

Unix系统提供了一种软件形式的异常,称为信号,允许进程中断其他进程。每种系统事件都对应一个信号,内核的异常处理程序处理完事件后,通过信号来告知用户进程发生了什么异常。信号也可以用于进程与进程之间的通信,接下来会仔细介绍信号的使用和机制。

Linux系统支持30种不同类型的信号,在命令行输入man 7 signal就能得到。

进程组

Unix系统中,信号的发送是基于进程组概念的。每个进程号都属于一个进程组,有进程组号。

pid_t getpgrp(void);					//获取进程组号
int setpgid(pid_t pid, pid_t pgid);		//改变进程组

setpgid将pid的进程组改为pgid。如果pid为0,就使用当前进程的pid。如果pgid是0,就用pid指定的进程pid作为进程组id。

信号发送

内核是通过更新目的进程上下文中的某个状态,发送信号给目的进程的。发送信号可能有两个原因:

  • 内核检测到系统事件
  • 进程显式的调用函数,使内核发送信号给另一个进程(可以是自己

发送信号的机制有许多种:

  • /bin/kill程序发送信号
/bin/kill -9 -15213 #给进程组15213发送SIGKILL信号
  • 从键盘发送信号:ctrl c会导致一个SIGINT信号被发送给shell,shell捕获该信号并发送SIGINT给前台进程组,终止前台作业
  • kill函数发送信号
int main(){
    pid_t pid;
    if((pid=fork()) == 0){		//省略了错误检查
        Pause();
        exit(0);
    }
    Kill(pid,SIGKILL);
    exit(0);
}
  • alarm函数发送信号(给自己)
void handler(int sig){
    //自定义接收到信号的处理行为
}
int main(){
    Signal(SIGALARM,handler);	//设置信号处理函数
    Alarm(1);					//1s后发送SIGALRM信号给自己
    while(1);
    exit(0);
}

信号接收

目的进程可以对内核发送的信号作出反应。进程可以忽略信号,终止或是执行信号处理程序来捕获信号。一个只发出而没有被进程接收的信号为待处理信号。一种类型至多只有一个待处理信号。如果一个进程有一个类型k的待处理信号,此后的k信号会被丢弃。进程还可以阻塞信号,一个信号被阻塞时,待处理信号不会被进程处理,直到取消阻塞。

待处理信号只能被接收一次的原因是内核通过位向量来维护待处理和被阻塞的信号,一位表示一个类型的信号是否待处理或被阻塞,无法累积多个信号。

当内核从一个异常处理程序返回时,会检查进程未被阻塞的待处理信号,如果存在,则选择一个要求进程进行处理。每个信号类型有默认的处理行为,是下面中的一种:

  • 进程终止
  • 进程终止并被转储存储器(dump core)
  • 进程停止直到被SIGCONT信号重启
  • 进程忽略该信号

可以使用signal函数来修改处理行为:

sighandler_t signal(int signum, sighandler_t handler)

handler取以下三种:

  • SIG_IGN:忽略signum信号
  • SIG_DFL:恢复signum信号的默认行为
  • 用户定义的函数

信号处理问题

当程序要捕获多个信号时,会产生一些问题。

  • 待处理信号被阻塞:当前处理的信号类型为k,则此时进程又收到的类型为k的信号会被阻塞,处于阻塞并待处理的状态
  • 待处理信号不会排队等待:一个类型的信号只能有一个待处理,此后到达的信号会被丢弃,因此不能用信号对事件进行计数
  • 系统调用可以被中断:read,wait和accept这样的系统调用会阻塞进程较长的一段时间,如果在这个时间捕获到信号,系统调用在处理信号后不再返回继续,而是返回错误

书P512给出了一个体现上述问题的例子。子进程终止后会向父进程发送SIGCHLD信号,设定父进程接收到SIGCHLD后回收子进程,有三个相同的子进程。在回收第一个子进程时,第二个子进程终止的SIGCHLD信号处于待处理被阻塞状态,而第三个子进程终止的SIGCHLD信号被丢弃了,因此没有回收最后一个子进程。

显式阻塞信号

程序可以使用sigprocmask函数阻塞和取消阻塞选择的信号。已阻塞信号是按照集合来维护的,因此还有一些其他函数对集合进行维护。

/*改变已阻塞信号的集合*/
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
/*清空阻塞集合*/
int sigemptyset(sigset_t *set);
/*将所有信号添加到set中*/
int sigfillset(sigset_t *set);
/*添加/删除信号到set*/
int sigaddset(sigset_t *set, int signum);
int sigdelset(sigset_t *set, int signum);
/*信号是否在set中*/
int sigismember(const sigset_t *set, int signum);

sigprocmask改变集合的方式依赖how的取值:

  • SIG_BLOCK:添加set中的信号到阻塞集合
  • SIG_UNBLOCK:从阻塞集合删除set中的信号
  • SIG_SETMASK:设置阻塞集合为set

避免并发错误

见书P518。通过阻塞信号等方式避免信号带来的并发错误,此部分内容在实验中很重要。

6.非本地跳转

C语言提供了另外一种用户级异常控制,称为非本地跳转。将控制从一个函数转移到另一个正在执行的函数。接口为setjmp,longjmp等函数。允许从一个深层嵌套的函数调用中返回。

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

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

相关文章

五彩斑斓的黑:Fun with PyQt5+CMake+C++

Fun Pain Fun with PyQt5CMakeC 本文相关代码GitCode地址 这个项目与PyQt5只有半毛钱关系。事情是这样发生的。当时&#xff0c;我在一个新电脑上干活&#xff0c;装了miniconda&#xff0c;装了PyQt5&#xff0c;干着干着突然要整一个Qt5。我想也挺好&#xff0c;据说C 17里…

Node.js和在浏览器之中的不同

在Node.js中编写JavaScript应用程序与在浏览器中为Web编程有何不同 1、在浏览器中&#xff0c;大多数时候您所做的是与DOM或其他Web平台API&#xff08;如Cookie&#xff09;进行交互。当然&#xff0c;Node.js中并不存在这些。您没有浏览器提供的文档、窗口和所有其他对象。 …

让测试更轻松:学习Selenium进行Web应用程序自动化测试

B站首推&#xff01;2023最详细自动化测试合集&#xff0c;小白皆可掌握&#xff0c;让测试变得简单、快捷、可靠https://www.bilibili.com/video/BV1ua4y1V7Db 目录 摘要&#xff1a; 什么是Selenium 安装Selenium 编写自动化测试脚本 第一步&#xff1a;导入Selenium库 …

前端006_头部快捷导航_标签导航栏

效果如下,红色方框里面有快捷导航 1、添加标签栏导航组件 拷贝vue-element-admin 的 @/layout/components/TagsView 目录及文件到 mengxuegu-blog-admin 对应目录下 [root@pgdb vue-element-admin]# cp -r src/layout/components/TagsView ../db-manager-system/src/layou…

UE蓝图基础学习笔记(未完待续2023/05/06)

文章目录 一、项目创建1&#xff09;准备流程&#xff08;选择模板、开发语言、平台、质量等&#xff09;2&#xff09;界面介绍 二、Actor三、操作关卡对象&#xff08;旋转、移动、缩放和坐标轴&#xff09;四、常用快捷键五、运行游戏六、蓝图介绍七、蓝图节点八、操作事件图…

Vben Admin 自学记录 —— Drawer组件的基本使用及练习(持续更新中...)

Drawer 抽屉组件 对 antv 的 drawer 组件进行封装&#xff0c;扩展拖拽&#xff0c;全屏&#xff0c;自适应高度等功能。 Drawer相关使用及概念 练习 —— 在之前table基础上&#xff0c;添加查看功能&#xff0c;点击查看按钮&#xff0c;弹出抽屉显示单条表格数据&#xf…

基于80C51单片机的电子钟设计与仿真

点击链接获取Keil源码与Project Backups仿真图&#xff1a; https://download.csdn.net/download/qq_64505944/87761539?spm1001.2014.3001.5503 源码获取 主要内容&#xff1a; 电子钟是一种利用数字电路来显示秒、分、时的计时装置&#xff0c;与传统的机械钟相比&#xf…

SpringBatch之实际操作

文章目录 1 SpringBatch操作1.1 SpringBatch介绍1.2 依赖配置相关1.2.1 pom.xml1.2.2 mysql 依赖库表1.2.3 启动配置1.2.4 数据库配置 1.3 示例Demo1.3.1 简单执行1.3.2 报错 1.4 流程控制1.4.1 多步骤任务1.4.2 Flow用法1.4.3 并发执行1.4.4 任务决策1.4.5 任务嵌套 1.5 数据操…

Illustrator如何使用图层与蒙版之实例演示?

文章目录 0.引言1.绘制可爱冰淇淋图标2.霓虹渐变立体文字海报3.炫彩花纹背景 0.引言 因科研等多场景需要进行绘图处理&#xff0c;笔者对Illustrator进行了学习&#xff0c;本文通过《Illustrator CC2018基础与实战》及其配套素材结合网上相关资料进行学习笔记总结&#xff0c;…

电影推荐算法2

模型创建 title _ count, title _ set, genres2int, features, targets _ values, ratings, users, movies, data, movies _ orig, users _ orig pickle.load (open (‘preprocess.p’, mode ‘rb’)) 加载数据后定义神经网络的模型结构&#xff1a; 1&#xff09;定义参数…

u盘文件名乱码的恢复方法

文件名全部变乱码了怎么恢复&#xff1f;U盘数据恢复方法 电脑里的目录文件名乱码了&#xff0c;这是什么状况呢&#xff1f;好端端的电脑突然就成这个样子了&#xff0c;真是令人摸不着头脑&#xff0c;对于这样的状况&#xff0c;多半是文件类型引起的&#xff0c;那么接下来…

Python:Python进阶:Python整数与 Numpy的数据溢出

numpy数据溢出 1.python 3 的整数上限和 python 2 的整数上限1.1 python 2的整数范围1.2 python 3 的整数范围 2. numpy数值表示2.1 那么numpy支持的数据类型和 python有什么不同了2.2 如何解决整数溢出问题 总结 实验一&#xff1a;使用 numpy库来表示正数 import numpy as n…

redis(4)

1)使用StringTemplateRedis操作String类型 1)判断redis中是否拥有key所对应的值&#xff0c;如果有返回true&#xff0c;没有那么直接返回false redisTemplate.hasKey(key); 2)如果redis中有key那么直接取出key所对应的值 redisTemplate.opsForValue().get(key) 3)删除单个key值…

2023.05.07 学习周报

文章目录 摘要文献阅读1.题目2.现存问题和解决方法3.本文贡献及相关工作4.GRU5.模型5.1 SESSION-PARALLEL MINI-BATCHES5.2 SAMPLING ON THE OUTPUT5.3 RANKING LOSS 6.实验6.1 准备6.2 基线6.3 优化6.4 结果 7.结论 数学建模1.综合评价模型的一般步骤2.层次分析法3.主成分分析…

【Java】中的多线程线程锁

多线程 文章目录 多线程线程的创建和启动sleep()stop() 线程的休眠和中断线程的优先级线程的礼让和加入yield()stop() 线程锁和线程同步synchronized 关键字 死锁概念 wait & notify methodThreadLocal的使用定时器 Timer守护线程再谈集合类parallelStreamforEachOrdered()…

怎么将三张图片合成一张图片?

怎么将三张图片合成一张图片&#xff1f;遇到这个问题&#xff0c;我们其实有很多方法来处理。我们首当其冲想到的是其中最常见的&#xff0c;可以使用我们手机的APP来处理&#xff0c;比如某秀秀等。但是此方法比较适合于尺寸比较小的图片进行合并&#xff0c;如果图片比较大的…

Dockerfile创建镜像文件

Dockerfile Docker镜像原理 Linux文件系统有bootfs和rootfs两部分组成 Docker镜像由特殊文件系统叠加 最底端bootfs,使用宿主机bootfs 第二次时rootfs,被称为基础镜像 向上可以叠加其他镜像文件 同一文件系统能将多层整合成一层&#xff0c;隐藏了多层存在 镜像可以放置…

智能优化算法:鱼鹰优化算法-附代码

智能优化算法&#xff1a;鱼鹰优化算法 文章目录 智能优化算法&#xff1a;鱼鹰优化算法1. 鱼鹰优化算法1.1 初始化1.2 阶段一&#xff1a;定位和捕鱼&#xff08;探索阶段&#xff09;1.3 阶段二&#xff1a;把鱼带到合适的位置&#xff08;开发阶段&#xff09; 2.实验结果3.…

RISC-V U-Boot 启动 Linux 内核的参数

RISC-V U-Boot 启动 Linux 内核的参数 U-Boot (the Universal Boot Loader简写U-Boot) flyfish U-Boot 启动Linux内核的参数举例说明 RISC-V U-Boot 启动 Linux 内核的参数方式一 文本操作earlyprintksunxi-uart,0x02500000clk_ignore_unusedconsolettyS0,115200init/sbin/i…

go-resiliency源码解析之-batcher

go-resiliency源码解析之-batcher 源代码地址 &#xff1a; https://github.com/eapache/go-resiliency/blob/master/batcher/batcher.go 1.batcher定义 创建一个batch对象需要2个参数: Timeout:超时,这是一个batch对象收集输入参数的时间。 work函数变量&#xff1a;在ti…