异常控制流——(中断、陷阱、故障、终止、进程等操作系统干货)

异常

异常控制流

控制流:

假设从处理机上电运行一直到断电关机的这段时间内,程序计数器的值是下图序列,其中ak表示某一条指令Ik的地址。

image-20231118203846477

**控制转移:**每一次从ak到ak+1的过渡

**平滑:**Ik和Ik+1在内存中是相邻的,若平滑发生了突变,通常是由于跳转、函数调用和返回这类指令造成

**最简单的控制流:**一个平滑的序列

假设从网络中传输的数据包到达网络适配器之后,需要将数据放到内存中,此时发生的突变就称为异常控制流

异常控制流是操作系统用来实现I/O、进程、并发以及虚拟内存的基本机制。

系统为每种类型的异常都分配了唯一的异常编号,其中一些号码是由处理器的设计者分配的(例如被0除、缺页以及算术运算等),其他号码是由操作系统内核的设计者分配的(例如系统调用以及来自外部I/O设备的信号)

**异常表:**系统启动时,操作系统分配和初始化的一个跳转表,异常号为跳转表的索引号,发生异常时根据该表找到对应的异常处理程序,异常表的起始地址保存在CPU的一个特殊寄存器中,通过异常表基址寄存器和异常号可以确定异常表项,异常表项中的对应异常处理程序的起始地址。

image-20231118211053238

异常处理:

  • 返回当前指令或下一条指令
  • 处理器的状态压入栈中
  • 控制从用户态转向内核态,那么所有内容都压入内核栈中,而不是用户栈中
  • 异常处理程序运行在内核态,所以对所有系统资源都有访问权限

异常

异常分为中断、陷阱、故障和终止,中断是异步的(即异常来自CPU外部),另外三个是同步的(异常来自CPU内部)。

中断

处理过程:

假设I/O设备为键盘,敲一下键盘时,键盘控制器会向处理器的中断引脚发送信号来触发中断,同时会将异常号(标识引起中断的设备)放到系统总线上;CPU执行完当前指令后发现中断引脚电压变高,于是从系统总线上读取异常号,判断是哪个设备发生中断,然后调用对应中断处理程序,中断处理完毕后,CPU返回继续执行下一条指令(没发生中断前的下一条)

陷阱

用途:为用户程序和操作系统内核之间提供一个类似函数的接口,即系统调用。

处理过程:当应用程序需要读取文件或者创建新的进程时,此时需要向内核请求服务,处理器为其提供了一条特殊指令——syscall,执行syscall导致一个陷阱,接下来陷阱处理程序解析参数,并调用适当的内核程序提供系统级的服务,陷阱处理程序执行完毕后,返回到指令syscall之后的指令继续执行。

故障

故障是由错误情况引起的,有可能被故障处理程序修复

处理过程:当前指令若导致故障发生,处理器会将控制转移给故障处理程序,故障处理程序如果能修复这个故障情况,那么就将控制返回给引起故障的指令,然后重新执行该条指令,否则终止引起故障的应用程序(经典案例:缺页异常)。

终止

终止是由不可恢复的致命错误导致的,通常是一些硬件错误,例如DRAM或者SRAM存储为被损坏时,会导致奇偶校验出错,对于这类硬件错误,终止处理程序从不将控制返回给应用程序,而是直接终止这个应用程序。

x86系统中共定义了256种异常,编号0~31是intel架构师定义的,32~255是由操作系统定义

异常号描述异常类型
0除0异常故障
13引用了未定义的虚拟内存区域或程序尝试去写只读文本段(段错误)故障
14缺页异常故障
18机器检查(硬件发生错误)终止
32-255操作系统定义的异常中断或陷阱

进程与上下文

进程:进程就是一个正在执行的程序实例。

运行程序时的两个假象:

  • 程序独占的使用处理器
  • 程序独占的使用内存系统

**逻辑控制流:**假如用调试器控制程序单步执行,我们会看到一系列程序计数器(PC)的数值, 这些数值与可执行程序中的指令是一一对应的,把这个PC值的序列成为逻辑控制流,如下图,竖线表示逻辑控制流,这张图描述了不同进程之间轮流使用处理器的情况,每个进程执行逻辑流的一部分,而时间上有重叠的的流称为并发流,进程之间也称为并发流。

image-20231119061150069

地址空间地址分布:

低地址部分是预留给应用程序的,包括代码段、数据段、堆和栈,代码段总是从0x400000处开始,地址的高地址部分是留给操作系统内核的,属于用户代码不可见的内存区域

image-20231119062716031

储存其映像

tips:在Linux环境下使用有一个proc文件记录了内核相关数据结构,通过这个文件,用户模式下也能访问内核数据结构的内容image-20231119063238549

模式

通常处理器通过控制寄存器的模式位来实现用户模式和内核模式的切换

内核模式可以执行特权指令,特权指令可以停止处理器、改变模式位以及发起一个IO操作等

用户模式通过中断、陷阱(系统调用)、故障切换到内核模式,执行完异常处理程序后回到用户模式

上下文context

内核为每一个进程维持了一个上下文,上下文就是内容重新启动一个被抢占进程所需的状态,由**通用目的寄存器、浮点寄存器、程序寄存器、用户栈、状态寄存器、内核栈和各种内核数据结构(包括描述地址空间的页表、包含有关当前进程信息的进程表以及包含进程已打开文件的信息表)**的值组成。

上下文切换:

进程调度使用上下文切换机制。

分为三步:

  1. 保存当前进程的上下文
  2. 恢复某个先前被抢占进程的上下文
  3. 将控制传递给这个新恢复的进程

创建进程

fork函数

调用一次,返回两次,一次返回给父进程另一次返回给新创建的子进程。

int main()
{
    pid_t pid;
    int x = 1;
    pid = Fork();
    if(pid == 0){
		printf("child: x=%d\n", ++x);
        exit(0);
    }
    printf("parent: x=%d\n", --x);
    exit(0);
}

父进程中,fork的返回值是子进程的PID;子进程中,fork的返回值是0,由于子进程的进程号总是大于0,所以可以通过进程号不同来区分当前在哪个进程执行。

运行结果:

parent: x=0
child: 	x=2

image-20231119075338738

父进程与子进程的地址空间内容是相同的,且共享文件,但他们都有自己的私有地址空间

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

调用之后不会返回。

参数:

第一个参数:表示可执行程序的文件名

第二个参数:表示可执行程序需要输入的参数列表

image-20231119080240569

image-20231119080434238

第三个参数:表示环境变量列表

image-20231119080912164

运行下列程序,可以查看系统环境变量

#include<stdio.h>
int main(int argc, char *argv[], char *envp[]){
	printf("环境变量:\n");
	int i;
	for(i = 0; envp[i] != 0; i++){
		printf("envp[%2d]: %s\n", i, envp[i]);
	}
	return 0;
}

作用:

调用加载器,在执行可执行程序的main函数之前,启动代码需要设置用户栈,并将控制传递给新程序的主函数

僵死进程(zombie)

当一个进程由于某种原因终止时,内核并不是立即把它从系统中清楚,此时进程被保持在一种已终止的状态中,直到被它的父进程回收,我们把一个终止运行但未被回收的进程成为僵死进程。

僵死进程虽然没有在运行,但是仍然在消耗系统的内存资源。

waitpid函数

父进程等待子进程终止或停止

#include<sys/type.h>
#include<sys/wait.h>
pid_t waitpid(pid_t pid, int *statusp, int options);

第一个参数:

① pid > 0

表示等待的进程是一个单独的子进程,子进程ID就是pid的值。

②pid = -1

表示等待的进程是由父进程创建的所有子进程组成的集合

第二个参数:

statusp是非空的,函数waitpid会在statusp中放上导致返回的子进程的状态信息

statusp指向的参数status对应的几个宏

WIFEXITED(status)

子进程是通过函数exit或者return正常终止,那么该宏结果就是True

WIFSIGNALED(status)

子进程是通过一个未捕获的信号终止的,那么该宏结果就是True

WEXITSTATUS(status)

返回一个正常终止的子进程的退出状态。只有在WIFEXITED()返回为真时,才会定义这个状态。

WTERMSIG(status)

返回导致子进程终止的信号的编号。只有在WIFSIGNALED()返回为真时,才定义这个状态。

WIFSTOPPED(status)

如果引起返回的子进程当前是停止的,那么就返回真。

WSTOPSIG(status)

返回引起子进程停止的信号的编号。只有在WIFSTOPPED()返回为真时,才定义这个状态。

WIFCONTINUED(status)

如果子进程收到SIGCONT信号重新启动,那么就返回为真。

信号

信号允许内核和进程中断其他进程,信号提供了一种机制,用来通知进程发生了哪些异常情况。

关于信号类型,看书!!!

示例:

​ 例如当一个进程试图执行除以0的操作时,那么内核会给该进程发送一个SIGFPG的信号,这个信号对应的是浮点异常的事件。

​ 例如当一个进程执行了一条非法指令,那么内核会给该进程发送一个SIGILL的信号,该信号对应的事件是非法指令。

​ 例如当一个进程在Linux Shell中执行,此时按下Ctrl+C键,那么内核就会发送一个中断信号给当前进程,终止它的运行。

进程组:

每个进程都只属于一个进程组,每个进程组有自己唯一的ID值来唯一标识,ID为一个正整数。

可以使用getpgrp函数来获取当前进程所属的进程组ID。

#include<unistd.h>
pid_t getpgrp(void);

默认情况下,一个子进程和它的父进程属于一个进程组,不过进程可以通过下图这个函数改变自己或者其他进程的进程组。

#include<unistd.h>
pid_t setpgrp(pid_t pid, pid_t pgid);

如果pid值为0,那么使用当前进程的PID值,如果pgid值为0,那么就是用pid指定的进程PID作为进程组的ID值。

发送信号

几种信号传送机制

第一种:

通过/bin目录中的kill程序可以向其他进程发送任意信号,如下图。

linuc> /bin/kill -9 15213

信号9表示杀死进程,这条命令执行完后,15213进程终止运行。

linuc> /bin/kill -9 -15213

这条命令执行完后,15213进程组的每一条进程都终止运行。

第二种:

image-20231119085229594

当键盘输入Ctrl+C,默认终止前台作业,输入Ctrl+Z,会挂起前台作业。

第三种:

#include<sys/types.h>
#include<signal.h>
int kill(pid_t pid, int sig);

如果pid值大于0,那么函数kill发送信号sig给进程pid

如果pid等于0,那么函数kill发送信号给当前进程所在进程组的所有进程

如果pid小于0,那么函数kill发送信号给进程组pid中的每个进程

第四种:

#include<unistd.h>
unsigned int alarm(unsigned int secs);

参数secs表示函数alarm安排内核在secs秒后发送一个SIGALARM信号给当前进程,如果secs为0,就不会调用新的闹钟了。

接收信号

当内核把进程p从内核模式切换到用户模式时,此时会检查进程p未阻塞状态的待处理的信号集合,如果这个集合为空,那么内核将控制传递到进程p的逻辑控制流的下一条指令,如果集合时非空,那么内核选择集合中的一个信号k,强制进程p接受信号k,接受信号会触发控制转移到信号处理程序,在信号处理程序完成处理之后,它将控制返回给被中断的程序。

每个信号都有一个预定义的默认行为:

第一种行为是进程终止,例如收到信号SIGKILL后,接收进程终止运行。

第二种行为是进程终止并转储内存,转存内存的意思是把代码和数据的内存镜像写到磁盘上

第三种行为是进程挂起,进程挂起直到被SIGCONT信号重启

第四种行为是可以直接忽略的信号,例如SIGCHLD信号

image-20231119090640135

一个发出但未被接收的信号叫做待处理信号,一种类型只有一个待处理信号,例如进程有一个类型为k的待处理信号,那么任何接下来发送到该进程类型为k的信号都会被丢弃。

信号处理程序可以被其他信号处理程序中断。

image-20231119090719350

非本地跳转

C提供了一个用户级异常控制流形式,称为非本地跳转,将控制直接从一个函数转移到另一个正在执行的函数,而不需要经过正常的调用返回序列。其实现形如

#include <setjmp.h>
int setjmp(jum_buf env);

其中,在第一次调用时,setjmp()返回0,且不能赋值给变量。setjmp()在env缓冲区保存当前的调用环境,并使用longjmp函数从env缓冲区中恢复调用环境,然后触发一个从最近一次初始化env的setjmp调用的返回,形如

#include <setjmp.h>
int longjmp(jum_buf env, int retval);

非本地跳转的一个重要应用是允许从一个深层嵌套的函数调用中立即返回,例如发现错误情况,就可以直接返回到一个普通的本地化的错误处理程序,而不是费力的反复返回来解开调用栈。
  示例代码:

#include "csapp.h"
jmp_buf buf;
int error1 = 0;
int error2 = 1;
void foo(void);
void var(void);
int main(){
	swtich(setjmp(buf)){
	case 0:
		foo();
		break;
	case 1:
		printf("Detect an error1 condition in foo\n");
		break;
	case 2:
		printf("Detect an error2 condition in foo\n");
		break;
	default:
		printf("Unknown error condition in foo\n");
	}
	exit(0);
}

void foo(void){
    if (error1)
            longjmp(buf, 1);
        bar();
    }

    void bar(void){
        if (error2)
            longjmp(buf, 2);
    }
	exit(0);
}

其在进入main()后的开关语句中,setjmp()保存了环境,并返回了0,执行情况0,即调用foo(),foo()在正确的情况下调用bar(),完成调用,执行exit(0)关闭进程。如果调用foo()的过程中出现error1,则会跳转到开关语句,setjmp()返回1,执行情况1,报告error1的错误;如果调用bar()的过程出现error2,则会跳转到开关语句,setjmp()返回2,执行情况2,报告error2的错误。

;
}
exit(0);
}

void foo(void){
if (error1)
longjmp(buf, 1);
bar();
}

void bar(void){
    if (error2)
        longjmp(buf, 2);
}
exit(0);

}



其在进入main()后的开关语句中,setjmp()保存了环境,并返回了0,执行情况0,即调用foo(),foo()在正确的情况下调用bar(),完成调用,执行exit(0)关闭进程。如果调用foo()的过程中出现error1,则会跳转到开关语句,setjmp()返回1,执行情况1,报告error1的错误;如果调用bar()的过程出现error2,则会跳转到开关语句,setjmp()返回2,执行情况2,报告error2的错误。

  非本地跳转的另一个重要应用是使信号处理程序分支到一个特殊的代码位置,而不返回到被信号到达中断了的指令的位置。例如程序通过信号和非本地跳转重新定义中断的动作,使得进程不立即终止,而是回到主程序的入口,实现软重启。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

趣学python编程 (五、常用IDE环境推荐)

Python环境指的是在计算机上安装Python解释器和相关的库&#xff0c;它是运行Python代码所必需的。那么开始Python编程前&#xff0c;准备安装好开发环境是前提。 默认的电脑上只是让人办公使用的&#xff0c;不带python编程开发环境。只有安装python环境&#xff0c;才可以编写…

根据nginx日志统计页面访问次数

静态页面部署在nginx上&#xff0c;页面只有查看下载功能。 需求是统计每条访问次数和下载次数&#xff0c;根据日志分析写了一个shell脚本&#xff0c;触发脚本后生成一个html可以远程查看统计的数量。 #!/bin/bash # nginx日志文件路径 LOG_FILE"/usr/local/nginx/l…

Python---PyCharm调试技巧--Step over(F8)、Step into(F7)

Step over&#xff08;F8&#xff09;&#xff1a;代码一步一步向下执行&#xff0c;但是遇到了函数以后&#xff0c;不进入函数体内部&#xff0c;直接返回函数的最终的执行结果。------------遇到函数跳过&#xff0c;直接执行最后的结果。 Step into&#xff08;F7&#xf…

Visio免费版!Visio国产平替软件,终于被我找到啦!

作为一个职场人士&#xff0c;我经常需要绘制各种流程图和图表&#xff0c;而Visio一直是我使用的首选工具。但是&#xff0c;随着公司的发展和工作的需要&#xff0c;我逐渐发现了Visio的优点和不足。 首先&#xff0c;让我们来看看Visio的优点。Visio是一个专业的流程图和图…

10 Redis的持久化

Redis支持RDB和AOF两种持久化机制 1、RDB(Redis DataBase) 是对命令的全量快照随着key的数量增大&#xff0c;那么写入磁盘的开销也会越来越大 2、RDB文件的生成是否会阻塞主线程 save: 使用save的方式会阻塞主线程&#xff0c;影响redis的性能 bgsave: 一般情况下不会阻塞…

公司电脑文件透明加密、防泄密管理软件系统

天锐绿盾数据透明加密系统是一款采用驱动层透明加密技术实现电子文件安全加密的防护产品&#xff0c;可以对企业电子文件的存储、访问、传播和处理过程实施全方位保护。该系统遵循基于文件生命周期安全防护的思想&#xff0c;集成了密码学、访问控制和审计跟踪等技术手段&#…

Windows RS485\USB转换接头,连接modbus温度传感器接线方法

文章目录 背景接线方式安装RS485\USB转换接头的驱动程序查看COM口号&#xff08;Communication Port&#xff08;通讯端口&#xff09;&#xff09;测试modbus数据传输 背景 买了个rs485 modbus协议的温度传感器&#xff0c;因为想接到windows上&#xff0c;用传感器厂家提供的…

什么是RS485通信

RS-485是一种通讯接口标准&#xff0c;RS就是Recommended Standard的缩写&#xff08;推荐标准的意思&#xff09;485是标识号。 RS485采用总线的接线方式&#xff0c;广泛应用于数据采集和控制&#xff0c;它的主要优点之一是它允许将多个RS485设备放在同一条总线上。 多设备…

【DevOps】Git 图文详解(五):远程仓库

Git 图文详解&#xff08;五&#xff09;&#xff1a;远程仓库 1.远程用户登录1.1 &#x1f511; 远程用户登录&#xff1a;HTTS1.2 &#x1f511; 远程用户登录&#xff1a;SSH 2.远程仓库指令 &#x1f525;3.推送 push / 拉取 pull4.fetch 与 pull 有什么不同 &#xff1f; …

微信小程序配置企业微信的在线客服

配置企业微信后台 代码实现 <button tap"openCustomerServiceChat">打开企业微信客服</button>methods: {openCustomerServiceChat(){wx.openCustomerServiceChat({extInfo: {url: 你刚才的客服地址},corpId: 企业微信的id,showMessageCard: true,});} …

【miniQMT实盘量化4】获取实时行情数据

前言 上篇&#xff0c;我们介绍了如何获取历史数据&#xff0c;有了历史数据&#xff0c;我们可以进行分析和回测。但&#xff0c;下一步&#xff0c;我们更需要的是实时数据&#xff0c;只有能有效的监控实时行情数据&#xff0c;才能让我们变成市场上的“千里眼&#xff0c;…

计算机毕业设计 基于SpringBoot的企业内部网络管理系统的设计与实现 Java实战项目 附源码+文档+视频讲解

博主介绍&#xff1a;✌从事软件开发10年之余&#xff0c;专注于Java技术领域、Python人工智能及数据挖掘、小程序项目开发和Android项目开发等。CSDN、掘金、华为云、InfoQ、阿里云等平台优质作者✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精…

Android设计模式--责任链模式

无善无恶心之体&#xff0c;有善有恶意之动。知善知恶是良知&#xff0c;为善去恶是格物。 一&#xff0c;定义 使多个对象都有机会处理请求&#xff0c;从而避免了请求的发送者和接收者之间的耦合关系。将这些对象连成一条链&#xff0c;并沿着这条链传递该请求&#xff0c;直…

探索标准数字隔离ACML-7400-500E:主要特性与应用

ACML-7400-500E标准数字隔离是现代电子系统中的重要组成部分的一员&#xff0c;提供安全可靠的数字信号分离方法。本文深入探讨了该隔离器的核心特性&#xff0c;讨论了其双电源电压兼容性、宽工作温度范围、高速数据功能以及各种安全认证。 双电源电压兼容性 标准数字隔离器…

Motion Plan之搜素算法笔记

背景&#xff1a; 16-18年做过一阵子无人驾驶&#xff0c;那时候痴迷于移动规划&#xff1b;然而当时可学习的资料非常少&#xff0c;网上的论文也不算太多。基本就是Darpa的几十篇无人越野几次比赛的文章&#xff0c;基本没有成系统的文章和代码讲解实现。所以对移动规划的认…

代码随想录算法训练营第四十九天| 123.买卖股票的最佳时机III 188.买卖股票的最佳时机IV

文档讲解&#xff1a;代码随想录 视频讲解&#xff1a;代码随想录B站账号 状态&#xff1a;看了视频题解和文章解析后做出来了 123.买卖股票的最佳时机III class Solution:def maxProfit(self, prices: List[int]) -> int:if len(prices) 0:return 0dp [[0] * 5 for _ in…

Android Studio常见问题

Run一直是上次的apk 内存占用太大&#xff0c;导致闪退

python -opencv 边缘检测

python -opencv 边缘检测 边缘检测步骤: 第一步&#xff1a;读取图像为灰度图 第二步&#xff1a;进行二值化处理 第三步&#xff1a;使用cv2.findContours对二值化图像提取轮廓 第三步&#xff1a;将轮廓绘制到图中 代码如下&#xff1a; from ctypes.wintypes import SIZ…

【Java】抽象类和接口

文章目录 一、抽象类1.抽象类的概念2.抽象类的语法3.抽象类的特性4.抽象类的作用 二、接口1.接口的概念2.语法规则3.接口的使用4.接口的特性5.实现多个接口6.接口间的继承7.接口的使用实例8.Clonable 接口和深拷贝9.抽象类和接口的区别 三、Object类1.获取对象信息2.对象的比较…

《视觉SLAM十四讲》-- 建图

11 建图 11.1 概述 &#xff08;1&#xff09;地图的几类用处&#xff1a; 定位&#xff1a;导航&#xff1a;机器人在地图中进行路径规划&#xff1b;避障重建交互&#xff1a;人与地图之间的互动 &#xff08;2&#xff09;几类地图 稀疏地图稠密地图语义地图 11.2 单目…
最新文章