​ 现代系统通过使控制流发生突变来对系统状态的变化(例如:硬件定时器产生的周期信号,包到达网络适配器…)做出反应,我们把这些突变称作异常控制流。异常控制流发生在系统的各个层次。(包括硬件层,内核层,应用层)。理解ECF将有助于你理解OS的I/O,进程和虚拟存储器机制,有助于理解应用程序如何与OS交互(system call/trap),有助于理解并发,软件异常,编写有趣的程序。 8.1异常:概述 异常的机制 ​ 系统为每种异常分配一个非负的异常号。一部分是处理器的设计者分配,另一部分则由操作系统的设计者分配。前者的示例包括:零除,缺页,存储器访问违例,断点以及算术溢出【主要是硬件相关的】。后者的示例包括系统调用和来自I/O设备的信号【主要是操作系统能够处理的】。由操作系统分配的异常号在操作系统启动时初始化一张称为异常表的跳转表。当发生异常时通过下面的方式计算出异常处理程序的地址: 异常表基址寄存器 + 异常号*4 异常处理后 返回触发异常的指令(eg:内存缺页异常) 返回到下一指令(eg:系统调用) 终止被中断的程序 异常的分类 异常分类 异常产生原因/说明 异常处理之后的操作 备注 中断 I/O设备的信号 返回到下一条指令 I/O设备指:网卡,磁盘,定时器等 陷阱 有意引起的异常 返回到下一条指令 系统调用引起 故障 可恢复的异常 可能返回到当前指令 如缺页故障 终止 不可恢复的异常 不返回 硬件错误 注:中断是四种异常中唯一的异步异常。 Linux的系统调用 在x86架构上,Linux系统的系统调用通过一条成为 int n的陷阱指令完成。历史上是通过128(0*80)异常号实现。 按照惯例,寄存器%eax包含系统调用号,寄存器ebx,ecx,edx,edi,esi和ebp存储最多6个任意的参数【esp不能使用,原因是进入内核模式时会被覆盖掉】。以上的规则我们称之为调用规约。 注:为提高性能,在最新的架构和操作系统下有一种新的系统调用的实现机制(利用 SYSENTER / SYSEXIT指令) 扩展阅读: https://github.com/cch123/llp-trans/blob/master/part3/translation-details/function-calling-sequence/calling-convention.md https://github.com/cch123/asmshare/blob/master/layout.md 8.2进程 进程为程序提供了两个非常重要的抽象: 一个独立的逻辑控制流:好像我们的程序独占的使用处理器 一个私有的地址空间:好像我们的程序独占的使用存储器系统 用户模式和内核模式 处理器通常是用某个控制寄存器中的一个模式位来标记程序是否可以执行的某些指令和访问某些特定的地址空间[即内核模式]。用户从用户模式进入内核模式的唯一方式是通过某种异常,当异常处理完毕后重新改为用户模式。 上下文切换(调度) 内核为每一个进程维持一个重新开始一个先前被抢占的进程所需的状态,称之为上下文(包括各种寄存器,用户栈,内核栈和各种内核数据结构,如页表,进程信息表,文件表)。通过上下文切换的机制实现控制转移到新的进程。当内核代表用户执行系统调用时可能发生上下文切换(典型的那些慢系统调用),中断也可能引发上下文切换。 高速缓存污染 一般而言硬件高速缓存存储器不能和诸如中断和上下文切换这样的异常控制流很好地交互。当中断发生或返回时告诉缓存对于当前执行的代码段总是冷的(即程序所需的数据都不在告高速缓存中,这也很好理解,因为局部性原理)。 8.3 系统调用错误处理 unix中的系统调用发生错误时通常返回-1并设置errno变量。我们可以通过strerror(errno)函数得到出错原因对应的字符串。程序员应该像例如demo1这样总是去检查错误(像go的返回值总是包含一个error类型,更极端的rust返回一个枚举类型强制你去处理)。然而这样会导致程序变得臃肿难懂。我们可以简化这个代码(参见demo2)。更进一步,习惯上可以为系统调用进行一种例如demo3这样的封装(似乎APUE里提到过这玩意),称之为包装函数。 //demo1: if( (pid = fork()) < 0){ fprintf(stderr,"fork err:%s\n",strerror(errno)); exit(0); } //demo2 void unix_error(char *msg){ fprintf(stderr,"fork err:%s\n",strerror(errno)); exit(0); } if( (pid = fork()) < 0){ unxi_error("fork error"); } //demo3 // Fork是一个参数和返回值与fork均相同的函数 pid_t Fork(void){ pid_t pid; if( (pid = fork()) < 0) unxi_error("fork error"); return pid; } 8.……

Continue reading