Exception(CSAPP-8.1)
在第三章中,我们已经知道程序执行中的两个控制流 —— 跳转和调用,但是这只是对于一个程序内部的控制,如果系统本身发生变化或者程序需要和系统交互,就需要新的控制方式
- 系统交互举例:
- 从硬盘读取数据
- 用户按下Ctrl+c 终止程序
- 产生除0错误
- 新的方式——异常控制流
- 用户代码通系统内核提供的接口将异常号传递给内核,内核通过内置的异常表决定采取的行动
- 用户代码通系统内核提供的接口将异常号传递给内核,内核通过内置的异常表决定采取的行动
- 系统交互举例:
异常分类
- 异步异常(中断)
- 计数器中断
- 某一进程运行足够时间后,内核通过计数器中断拿回控制权
- I/O中断
- 从硬盘读取出数据后
- 键入Ctrl+ C
- 网路中的一个包接受完毕
- 计数器中断
- 同步异常
- Trap
- 故意的异常
- 返回到下一条指令
- 作用:在用户程序和内核中提供接口
- 当用户程序需要向内核请求服务时,例如读取文件,加载一个新的程序,转到内核程序
- 用户模式的行为是被限制的,不可以访问一些内存和执行一些命令,所以要转移到内核模式
- Fault
- 可以恢复的错误
- 返回到当前指令
- 举例:缺页故障
- 内存中没有需要的数据,需要从硬盘中读取
- abort
- 不可恢复的错误
- 举例:
- 非法指令
- exit
- 举例:
- 不可恢复的错误
- Trap
- 异步异常(中断)
Process(CSAPP-8.2)
- 两个关键的抽象
- 逻辑控制流
- 通过上下文切换让每个程序好像单独占用处理器
- 上下文切换保存的状态举例
- 存放在内存中的程序代码和数据
- 栈和寄存器
- PC
- 环境变量
- 逻辑控制流
- 私有地址空间
- 通过虚拟内存让每个程序感觉在单独占用内存
- 每个虚拟地址空间都有相同的通用结构
- 每个虚拟地址空间都有相同的通用结构
- 通过虚拟内存让每个程序感觉在单独占用内存
进程控制(CSAPP-8.3,4)
- fork
- 创建一个子进程,除了返回的PID(进程ID)不同,其余全部相同
- 两者互不影响,并发执行
- fork返回两次,一次返回到父进程,一次返回到子进程
- 父进程中返回子进程的PID(不为0)
- 子进程返回0
- 通过这个来判断父子进程
- 创建一个子进程,除了返回的PID(进程ID)不同,其余全部相同
- waitpid
- 回收子进程
- 当子进程结束时,保持终止状态知道父进程回收,此时子进程任然占据内存
- 当子进程结束时,保持终止状态知道父进程回收,此时子进程任然占据内存
- 第一个参数大于零,则等待ID为第一个参数的子进程结束
- 若为-1,这等待任意进程结束
- 如果没有子进程,则返回-1
- 回收子进程
- execve
- 在当前进程中加载一个新的程序,覆盖当前进程的地址空间,但没有创建一个新的进程
- 在当前进程中加载一个新的程序,覆盖当前进程的地址空间,但没有创建一个新的进程
Signal(CSAPP-8.5)
- 当子进程完成后,会发送一个信号给父进程让他来回收自己
- 内核对不同的信号有唯一的编码
- 信号传递的整个过程分两个部分
- 第一个部分为信号发送,由内核发送给进程
- 当一个进程正在使用信号处理程序处理某一类型的型号时,同种信号不会被接受,直接被丢弃
- 实际上第二个同种信号不会被丢弃,它被记录到pending组中,而之后的同种信号会被丢弃
- 原因是pending组实际上是在接收到某一信号后,将对应的位设置为一,所以当存在pending时,我们只能确认有多的信号,而不能确认有几个
- 上面说的pending是系统默认的一种信号block形式,当然我们可以显式block各种信号
- 当进程处理完一个信号处理程序后,会查看pending组中待处理的信号,从最小的信号开始处理,知道没有待处理信号,然后会执行下一条程序
- 第一个部分为信号发送,由内核发送给进程
- 安全的信号处理程序
- 阻塞所有的信号
- 处理程序和主程序共享全局变量
- 用volatile声明全局变量
- 阻塞所有的信号
- 竞争
- 不能确定子进程和父进程谁先执行导致了竞争的出现
- 难以稳定浮现错误
- 具体见CSAPP第8.5.6节例子 -
非本地跳转(CSAPP-8.6)
- 本地跳转指goto操作
- 但是goto无法跳转到别的函数
- 通过setjmp和longjmp可以直接从一个函数跳转到另一个函数,不用通过调用-返回操作
- 但是这样可能会产生内存泄漏
- setjmp记录当前的调用环境
- longjump恢复调用函数
- 这样可以从一个深层嵌套的函数中直接跳转到一个错误处理程序