异常控制流

异常是什么

异常就是控制流中的突变(也就是正常的程序指令流被打乱了),但是请记住,异常不代表一定就有问题

异常有多种形式,由软件和硬件共同实现

  1. 异常的种类

  1. 中断是异步的!比如键盘不知道什么时候会输入,网卡不知道什么时候数据会到达
  2. 陷阱,比如应用程序的read指令,称为陷阱是因为是让用户主动触发的。在应用程序上看着实际上和普通函数没有什么不同,但是系统调用在内核中执行,并调用的内核栈。系统调用其实就是一种内核服务,应用想要访问内核服务,就要通过这种方式
  3. 故障,是一种意料之外的异常,但是并不意味不可修复,如缺页
  4. 终止,一些致命的错误,比如除0

异常控制流的处理方式

它和过程调用很类似,因为也是一种指令的结构控制形式

当异常发生时,其实也是执行一些指令(当然这些指令已经是在其他位置的指令,指令流不是顺序的了),这些会破坏当前处理器的状态,比如寄存器的值,所以这些值需要被保存在某个地方,根据层级的不同,可能在内核栈上,可能在用户层的堆上等,当异常处理完毕后,恢复异常发生前的处理器状态。

但是有一些区别,异常的返回地址可能当前指令,也可能是下一条指令

异常控制流的实现

异常控制流有很多的层次,以下按层次从低到高依次书写。但是要记住,异常控制流中,当控制流改变过后又回到源控制流的时,需要保证当前上下文的状态和之前一样(寄存器等),所以在切换之前需要把当时的上下文保存在某个地方(根据层级的不同,而不同,可能在内核栈上,也可能在堆上)

硬件与内核之间的异常流控制(x86-64 系统中定义的异常)

x86-64系统中定义的异常 是 CPU 运行过程中 意外发生 的错误,通常由 硬件和软件错误 触发,会导致 CPU 进入异常处理程序。这些由CPU赋予异常号

这也是异常控制流能被支持的脊柱

异常号的展示

异常号描述异常类别详细解释
0除法错误(Divide Error)故障(Fault)除数为 0 或结果溢出时触发,通常会导致 SIGFPE
信号
6无效操作码(Invalid Opcode)故障(Fault)CPU 试图执行一个无效指令,如执行不支持的指令
13一般保护错误(General Protection Fault)故障(Fault)访问受保护的内存地址,如访问内核地址或非法访问某些寄存器
14缺页异常(Page Fault)故障(Fault)访问的内存页未映射,操作系统可能会加载该页,或者终止进程
18机器检查(Machine Check)终止(Abort)硬件故障或严重的系统错误,例如 CPU 内部错误
32-255操作系统定义的异常中断或陷阱(Interrupt/Trap)操作系统使用这些编号定义自己的异常处理,如系统调用
32任务切换(Task Switch)中断可能用于任务切换或调度
33调试陷阱(Debug Trap)陷阱进入调试模式,通常在 gdb 调试时触发
128系统调用(Syscall)陷阱旧的 int 0x80 方式的系统调用

x86-64系统中的异常实现方式

在系统启动时有一张异常表的跳转表被操作系统分配和初始化

通过异常表的跳转表,转移到一个专门设计用来处理这类事件的操作系统子程序

异常表其实是一张跳转表,跳转到异常处理程序,它是一块连续的内存块,数组,只需要基地址和异常号就能跳转到异常处理程序的入口上。

检测到异常的过程是当执行完当前指令后,会发现中断引脚的电压变高,然后从系统总线读取异常号。然后跳入异常处理程序中,

内核与进程之间的异常流控制(系统调用,syscall)

系统调用是基于syscall的异常号 128 ,的基础上实现的,这个系统调用又对应于一个单独的系统调用跳转表

系统调用是 用户进程主动请求 操作系统提供的服务,通常用于 文件操作、进程管理、内存管理等。

系统调用的返回值为负,表明发生了错误,可以使用errno来进行查看

系统调用是 用户进程主动请求 操作系统提供的服务,通常用于 文件操作、进程管理、内存管理等

系统调用会导致进程的上下文切换,陷入内核态

编号名称描述详细解释
0read读取文件从文件描述符读取数据到用户空间
1write写文件将用户数据写入文件描述符
2open打开文件以指定模式打开文件
3close关闭文件关闭打开的文件描述符
4stat获取文件信息读取文件的元数据(如大小、权限等)
9mmap将文件映射到内存用于内存映射 I/O,允许进程访问文件内容
12brk调整堆内存大小用于扩展或收缩进程的堆空间
32dup2复制文件描述符复制一个文件描述符,通常用于重定向
33pause让进程挂起直到信号到达进程挂起,直到接收到信号
37alarm设定定时器让进程在 N 秒后接收 SIGALRM
信号
39getpid获取进程 ID返回当前进程的 PID
57fork创建子进程复制当前进程,创建新进程
59execve执行程序运行一个新程序,替换当前进程映像
60_exit终止进程立即终止当前进程
61wait4等待子进程结束等待子进程终止,回收资源
62kill发送信号到进程终止或控制另一个进程

在c语言中,read,write,print都是包装函数

本质上都是通过syscall进行包装的结果,

printf("hello, world!")
write(1, "hello, world!", 13); // 1 输出流  13 个字节

进程之间的异常控制流(信号)

进程与进程之间的异常控制流主要是通过信号来进行实现的,,

信号的实现方式是:

struct task_struct {
    ...
    sigset_t blocked;         // 进程当前屏蔽的信号集(位向量)
    ...
};

每个进程的进程信息中都有一个位向量集合,每一位代表一个信号是否被接收到。当这个信号被多次触发,而进程还在处理上个信号的时候,多次触发会被丢弃(因为位只有0 和 1 的区别,无法计数)。

信号是一种异步通知机制,这就好像内核无法知道你什么时候敲键盘一样。

基于以上俩个原则,不可以使用信号处理函数来处理计数操作,同时信号处理函数共享与进程一样的数据,可以理解为并发访问,要么就加锁,要么就不要有临界区。或者说这个信号处理函数是可重入的(不要有临界资源)

当然在执行信号处理函数的时候,进程的控制流已经被打乱了,在执行之前需要保存上下文,当信号处理函数return的时候,返回到上次应该执行的那条指令下。

系统调用是可以被中断的,像read、write、accept这种,慢速系统调用被中断时,在信号处理程序返回时,将不再继续,而是返回一个errno: EINTR

正因为系统调用是可以被中断,返回值可能是错误的,比如不可重入的系统调用localtime,如果被中断,那么它的值将是NULL,如果不处理错误,应用层就会挂掉了。

进程内的异常控制流(try,catch)

应用层的异常控制流是通过非本地跳转来实现的

c语言, 非本地跳转(nonlocal jump),setjmp 保存当前调用环境,longjmp 恢复环境

c++和java中的try catch 是 c语言这俩个函数的更结构化的版本

具体详细没有再了解过

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值