Li Jiaheng's blog
1811 字
9 分钟
程序控制流(1) 概念性内容
WARNING

这是《深入理解计算机系统》(CS)的笔记,彼时还未上CO,OS等计算机系统基础课,存在理解不到位与错误之处.

从此章(Computer System: A Programmer Perspective)开始,将接触更多系统级编程。本章将涉及基本的进程控制函数接口,shell程序、web服务器都大量应用相关技术。
本文是针对Linux的学习。

一、异常#

异常并非 error 错误,而是 exception,发生异常后,内核暂停当前运行的程序,进入异常处理函数,处理完后如果没有 exit 结束进程,则返回到被暂停的程序中。注意,事件发生后跳转到异常处理函数,这个异常处理函数和原程序属于同一个进程,共享同样的上下文(比如全局变量等)
(还记得单片机里的中断函数 interrupt 吗?)
异常时间发生时触发异常处理函数,程序通过一个叫异常表的跳转表间接跳转,相当于异常事件种类编号为索引、异常处理函数地址为内容的索引表。表头地址存储在CPU中“异常表基址寄存器”的特殊寄存器中。每种异常都有自己的编号(编号由硬件工程师和操作系统架构师共同决定),用编号当偏移量可以轻易访问相应异常处理程序的地址。
比如X86-64系统中除法错误(除以0)编号为0,缺页为14,机器检查为18,33及以后为操作系统定义的异常(应该和各种信号有关)。

异常处理程序完成后,通常有三种返回行为:
1、返回当前指令 Icurr,即执行到哪条指令接受了异常,就回到哪条指令。
2、返回顺序下一条指令 Inext
3、在处理函数中终止了被中断的程序,不返回(exit)。

异常主要有四类:
1、中断。由外部 I/O 设备随时不确定产生,是异步的。默认返回行为是返回到下一条指令。
2、陷阱。有意的异常,往往是程序进程主动放出的信号。同步的。默认返回行为是返回到下一条指令。
3、故障。潜在可恢复的错误。同步。可能回到当前指令,亦可能无法修复,直接终止。
4、终止。不可恢复错误。同步。直接终止。
举例而言,除法错误,比如除数为0,会发生除法错误(Linux下为异常0),它会直接终止程序,Linux shell一般报告为“浮点异常”。一般故障保护,通常是因为程序引用了未定义的虚拟内存区域(简单而言,越界),会直接终止。Linux shell 报告为 Segmentation fault,万恶的段错误。

由此看来,异常处理程序的调用很像普通的函数调用,但又不同:
1、异常处理程序的返回地址可能是下一条指令、当前指令,也可能不返回,直接终止。
2、如果异常处理程序和内核函数,要将控制交给内核函数,当前程序的所有状态会放进内核栈而非平时说的用户栈中。
3、异常处理程序运行在内核模式下,有对所有系统资源完全的访问权,即使这个异常处理程序时自定义的(可能?)。
4、调用异常处理函数被理解为处于和被中断的函数同一个进程中,有所有相同的全局变量等状态,也可以读写。
5、异常处理程序中如果再接收到异常,可以套娃再套一层异常处理程序。4、5两点决定异常处理容易冒出各种奇怪的问题,比如错误地对外部(相对异常处理程序的外部)状态修改等。

二、系统调用#

Linux系统提供了几百种系统调用,在程序员想获得操作系统内核服务的时候可以调用。每个系统调用都有专门的编号,对应与内核中一个跳转表的偏移量。
用户级程序无法直接进行系统调用,必须通过给出的接口。用syscall 指令(机器指令)可以进行系统调用。系统调用以类似异常处理的方式进行,停止当前进程,将控制交到系统内核函数,然后运行系统函数…注意syscall是一个机器指令。
但是实际上Linux下几乎所有系统调用C都有相应的包装函数,用户层面上直接调用这个函数就行。也即系统级函数
比如创建一个新的子进程的函数 fork() 函数就是这样一个包装。
而常见的 system() 函数可以以命令的形式进行系统调用。

系统调用的参数只在寄存器中,%rax 放系统调用编号,其它和普通函数一样的参数拜访寄存器规范,最多6个参数。

三、进程#

程序只是一个文件,而进程则是对这个文件执行的实例。类比一下,就像类和实例对象。
进程由操作系统实现。进程给应用程序提供了关键的两个抽象:1、应用程序有一个独立的逻辑流,好像独占处理器。2、一个私有的地址空间,好像程序独占地使用内存系统。

上下文#

每个进程都有自己的上下文,英文是 context (语境),就是进程和其运行的程序当前所处的语境(环境)。它由一些对象的值组成,比如通用目的寄存器、浮点寄存器、程序计数器PC、用户栈、状态寄存器、内核栈、内核数据结构,包括信号阻塞集合等等。上下文非常宽泛。
切换进程就要进行上下文切换,保存当前进程的上下文,复刻目标进程目标位置的上下文,然后将控制交给新进程。这一过程由内核控制。

并发流#

多进程(或线程)中,用户创建了多个并列的进程,它们各自独立运行(但实际上大多不独立,这是许多难以调试的bug的来源),这就称为并发流。并发流并不意味着有多个核,在不同核上同时运行不同进程,操作系统通过进程之间切换假装并列运行这些进程。这导致执行顺序、时间上的随机,必须小心处理。
当真的有多个硬件同时执行这些进程时,就称为并行流

私有地址空间#

每个进程有自己的私有地址空间,它映射到真实物理内存中的某段。这段物理内存不能由其它进程读写,因而称其私有。进程地址空间同《Computer System / Complier-linking / 链接概述》中程序加载一节所述一样,因而提供了假象:进程在独占内存。