[CSAPP笔记][第八章异常控制流]

异常控制流

  • 控制转移
  • 控制流

系统必须能对系统状态的变化做出反应,这些系统状态不是被内部程序变量捕获,也不一定和程序的执行相关。

现代系统通过使控制流 发生突变对这些情况做出反应。我们称这种突变异常控制流( Exceptional Control Flow,ECF)

异常控制流发生在系统的各个层次。

理解ECF很重要

  • 理解ECF将帮助你理解重要的系统概念。
  • 理解ECF将帮助你理解应用程序如何与操作系统交互
    • 通过陷阱(trap)或者系统调用(system call)的ECF形式,向操作系统请求服务。
  • 理解ECF将帮助你编写有趣的应用程序
  • 理解ECF将帮助你理解并发
  • 理解ECF将帮助你理解软件异常如何工作。

这一章你将理解如何与操作系统交互,这些交互都围绕ECF

8.1 异常

异常异常控制流的一种,一部分由硬件实现,一部分由操作系统实现。

  • 异常(exception)就是控制流的突变,用来响应处理器状态的某些变化。
  • 状态变化又叫做事件(event)
    • 事件可能与当前执行指令有关

      • 存储器缺页,算数溢出
      • 除0
    • 也可能与当前执行指令无关
      • I/O请求
      • 定时器产生信号
  • 通过异常表(exception table)的跳转表,进行一个间接过程调用,到专门设计处理这种事件的操作系统子程序(异常处理程序(exception handler))
  • 异常处理完成后,根据事件类型,会有三种情况
    • 返回当前指令,即发生事件时的指令。
    • 返回没有异常,所执行的下一条指令
    • 终止被中断的程序

8.1.1 异常处理

  • 为每个异常分配了一个非负的异常号(exception number)

    • 一些号码由处理器设计者分配
    • 其他号码由操作系统内核的设计者分配。
  • 系统启动时,操作系统分配和初始化一张称为异常表的跳转表。
    • 条目k包含异常k的处理程序的地址。
  • 异常表的地址放在叫异常表基址寄存器的特殊CPU寄存器中。)
  • 异常类似过程调用,不过有以下不同
    • 过程调用,跳转到处理程序前,处理器将返回地址压入栈中。对于异常,返回地址是当前,或下一跳指令。
    • 处理器会把额外的处理器状态压入栈中。
    • 如果控制一个用户程序到内核,那么所有这些项目会被压入内核栈中,而是用户栈。
    • 异常处理程序运行在内核模式下,这意味他们对所有系统资源有完整访问权限。

8.1.2 异常的类别

异常分为一下四类:中断(interrupt),陷阱(trap),故障(fault)和终止(abort)。

  1. 中断

    • 中断异步发生,是来自处理器外部的I/O设备的信号的结果。硬件中断不是由任何一条专门的指令造成,从这个意义上它是异步的。
    • 硬件中断的异常处理程序通常称为中断处理程序(interrupt handle)
      • I/O设备通过向处理器芯片的一个引脚发信号,并将异常号放到系统总线上,以触发中断。
      • 在当前指令执行完后,处理器注意到中断引脚的电压变化,从系统总线读取异常号,调用适当的中断处理程序。
      • 当处理程序完成后,它将控制返回给下一条本来要执行的指令。
      • 剩下的异常类型(陷阱,故障,终止)是同步发生,执行当前指令的结果。我们把这类指令叫做故障指令(faulting instruction).

  2. 陷阱和系统调用
    • 陷阱有意的异常,是执行一个指令的结果。也会返回到下一跳本来要执行的指令。
    • 陷阱最重要的用途是在用户程序和内核之间提供一个像过程一样的接口,叫做系统调用
      • 用户程序经常需要向内核请求服务。

        • 读文件(read)
        • 创建进程(fork)
        • 新的程序(execve)
        • 终止当前进程(exit)
      • 为了运行对这些内核服务的受控访问,处理器提供了一条特殊的syscall n的指令
      • 系统调用是运行在内核模式下,而普通调用是用户模式下。
  3. 故障
    • 故障由错误引起,可能被故障处理程序修正。

      • 如果能被修正,返回引起故障的指令。
      • 否则返回abort例程,进行终结。
  4. 终止
    • 终止是不可恢复的致命错误造成的结果,通常是一些硬件错误,比如DRAM和SRAM被损坏。
    • 终止处理程序从不将控制返回给应用程序。返回一个abort例程。

8.1.3 Linux/IA32 系统中的异常

  • 有高达256种不同的异常

    • 0~31 由Intel架构师定义的异常,对任何IA32系统都一样。
    • 23~255 对应操作系统定义的中断和陷阱。
      1. Linux/IA32 故障和终止
    • 除法错误
    • 一般保护故障
    • 缺页
    • 机器检查
      1. Linux/IA32 系统调用

  • 在IA32系统中,系统调用是通过一条称为int n的陷阱指令完成,其中n可能是IA32异常表256个条目中任何一个索引,历史中,系统调用是通过异常128(0x80)提供的。
  • C程序可用syscall函数来直接调用任何系统调用
    • 实际上没必要这么做
    • C库提供了一套方便的包装函数。这些包装函数将参数打包到一起,以适当的系统调用号陷入内核,然后将系统调用的返回状态传递回调用函数。
    • 我们将系统调用与他们相关联的包装函数称为系统级函数

研究程序如何使用int指令直接调用Linux 系统调用是很有趣的。所有到Linux系统调用的参数都是通过通用寄存器而不是栈传递。

惯例

  • %eax 包含系统调用号
  • %ebx,%ecx,%edx,%esi,%edi,%ebp包含六个任意的参数。
  • %esp不能使用,进入内核模式后,内核会覆盖它。
  • 系统级函数写的hello world

    int main()
    {
        write(1,"hello,world\n",13);
        exit(0);
    }
    
  • 汇编写的hello world
    string:
            "hello world\n"
    main:
            movl $4,%eax
            movl $1,%ebx
            movl $String,%ecx
            movl $len,%edx
            int $0x80
    
            movl $1,%eax
            movl $0,%ebx
            int $0x80
    

8.2 进程

  • 异常是允许操作系统提供进程的概念的基本构造快,进程是计算机科学中最深刻,最成功的概念之一。

    • 假象,觉得我们的程序是系统中唯一运行着的程序。我们的程序好像独占处理器和存储器。
    • 这些假象都是通过进程概念提供给我们的。
  • 进程经典定义:一个执行中的程序实例.
    • 系统中每个程序都是运行某个进程的上下文中的。

      • 上下文是由程序正确运行所需的状态组成。
      • 这个状态包括存储器中的代码和数据,它的栈,通用目的寄存器,程序计数器,环境变量等。
  • 进程提供的假象
    • 一个独立的逻辑控制流
    • 一个私有的地址空间

8.2.1 逻辑控制流

  • PC值的序列叫做逻辑控制流,或者简称逻辑流

8.2.2 并发流

  • 逻辑流也有不同的形式。

    • 异常处理程序,进程,信号处理程序,线程和Java进程都是逻辑流的例子。
  • 一个逻辑流的执行在执行上与另一个流重叠,称为并发流,这两个流被称为并发地运行
    • 更准确地说,流X和Y互相并发。
  • 多个流并发执行的一般现象称为并发
    • 一个进程和其他进程轮流执行的概念称为多任务
    • 一个进程执行它的控制流的一部分的每一时间段叫做时间片
    • 因此,多任务 又叫时间分片
  • 并发的思想与流运行的处理器核数与计算机数无关。
    • 如果两个流在时间上重叠,即使运行在同一处理器,也是并发。
    • 并行流是并发流的一个真子集。
      • 两个流并发地运行在不同的处理器核或者计算机上,我们称为并行流
      • 它们并行地运行,且并行地执行

你吃饭吃到一半,电话来了,你一直到吃完了以后才去接,这就说明你不支持并发也不支持并行

你吃饭吃到一半,电话来了,你停了下来接了电话,接完后继续吃饭,这说明你支持并发

你吃饭吃到一半,电话来了,你一边打电话一边吃饭,这说明你支持并行

并发的关键是你有处理多个任务的能力,不一定要同时。

并行的关键是你有同时处理多个任务的能力。

8.2.3 私有地址空间

进程 为个程序好像独占了系统地址空间。

  • 一个进程为每个程序提供它自己的私有地址空间
  • 不同系统一般都用相同的结构。

8.2.4 用户模式和内核模式

处理器提供一种机制,限制一个应用程序可以执行的指令以及它可以访问的地址空间范围。这就是用户模式内核模式

  • 处理器通过控制寄存器中的一个模式位来提供这个功能。

    • 该寄存器描述了进程当前享有的特权。

      • 设置了模式位后,进程就运行在内核模式中(有时也叫超级用户模式)

        • 内核模式下的进程可以执行指令集的任何指令,访问系统所有存储器的位置。
      • 没有设置模式位时,进程运行在用户模式。
        • 用户模式不允许程序执行特权指令。

          • 比如停止处理器,改变模式位,发起一个I/O操作。
        • 不允许用户模式的进程直接引用地址空间的内核区代码和数据。
        • 任何尝试都会导致保护故障
        • 用户通过系统调用间接访问内核代码和数据。
    • 进程从用户模式转变位内核模式的方法
      • 通过中断,故障,陷入系统调用这样的异常。
      • 在异常处理程序中会进入内核模式。退出后,又返回用户模式。
  • Linux提供一种聪明的机制,叫/proc文件系统。
    • 允许用户模式访问内核数据结构的内容。
    • /proc文件将许多内核数据结构输出为一个用户程序可以读的文本文件的层次结构。
      • 如CPU类型(/proc/cpuinfo)
      • 特殊进程使用的存储器段(‘/proc//maps’)
    • 2.6 版本引入Linux内核引入/sys文件系统。
      • 输出关于系统总线和设备的额外的底层信息。

8.2.5 上下文切换

操作系统内核使用一种称为上下文切换的 较高层次 的异常控制流来实现多任务。
  • 上下文切换机制建立在之前讨论的较低层次异常机制上的。
内核为每个进程维护一个上下文
  • 上下文就是重新启动一个被抢占的进程所需的状态。

    • 由一些对象的值组成

      • 通用目的寄存器
      • 浮点寄存器
      • 程序计数器(PC)
      • 用户栈
      • 状态寄存器
      • 内核栈
      • 各种内核数据结构
        • 描绘地址空间的页表
        • 包含当前进城信息的进程表
        • 进程已打开文件信息的文件表
  • 在进程执行的某些时刻,内核可以决定抢占当前进程,并重新开始一个先前被抢占的进程。这种决定叫做调度(shedule),由内核中称为调度器(scheduler)的代码处理的。
    • 当内核选择一个新的进程运行时,我们就说内核调度了这个进程。
  • 当调度进程时,使用一种上下文切换的机制来控制转移到新的进程
    • 保存当前进程的上下文
    • 恢复某个先前被抢占的进程被保存的上下文
    • 将控制传递给这个新恢复的进程
  • 什么时候会发生上下文切换
    • 内核代表用户执行系统调用

      • 如果系统调用因为某个事件阻塞,那么内核可以让当前进程休眠,切换另一个进程。
      • 或者可以用sleep系统调用,显式请求让调用进程休眠。
      • 即使系统调用没有阻塞,内核可以决定执行上下文切换
    • 中断也可能引发上下文切换。
      • 所有系统都有某种产生周期性定时器中断的机制,典型为1ms,或10ms。
      • 每次定时器中断,内核就能判断当前进程运行了足够长的时间,切换新的进程

高速缓存污染和异常控制流

一般而言,硬件高速缓存存储器不能和诸如中断和上下文切换这样的异常控制流很好地交互,如果当前进程被一个中断暂时中断,那么对于中断处理程序来说高速缓存器是冷的。如果处理程序从主存访问足够多的表项,被中断的进程继续的时候,高速缓存对于它来说也是冷的,我们称中断处理程序污染了高速缓存。使用 上下文切换也会发生类似的现象。

8.3 系统调用错误处理

  • 当Unix系统级函数遇到错误时,他们典型地返回-1,并设置全局变量errno来表示什么出错了。

    if((pid=fork()<0){
            fprintf(stderr,"fork error: %s\n", strerror(errno));
            exit(0);
    }
    
  • strerror 函数返回一个文本串,描述了个某个errno值相关联的错误。

8.4 进程控制

8.4.1 获取进程ID

#include<sys/types.h>
#include<unistd.h>

pid_t getpid(void);
pid_t getppid(void);
  • PID是每个进程唯一的正数。
  • getpid()返回调用进程的PID,getppid()返回它的父进程的PID。
  • 返回一个类型pid_t的值,在Linux系统下在type.h被定义为int

8.4.2 创建和终止进程

进程总是处于下面三种状态

  • 运行。进程要么在CPU中执行,要么等待执行,最终被内核调度。
  • 停止。进程的执行被挂起,且不会被调度。
    • 收到SIGSTOP,SIGTSTP,SIDTTIN或者SIGTTOU信号,进程就会停止。
    • 直到收到一个SIGCONT信号,在这个时刻,进程再次开始运行。
    • 信号是一种软件中断的形式。
  • 终止。进程永远停止。
    • 收到一个信号。信号默认行为是终止进程。
    • 从主程序返回
    • 调用exit函数
      • exit函数以status退出状态来终止进程(另一种设置方式在main中return )

子进程

父进程通过调用fork函数创建一个新的运行子进程

#include<sys/types.h>
#include<unistd.h>

pid_t fork(void);
返回:子进程返回0,父进程返回子进程的PID,如果出错,返回-1;

新创建的子进程几乎但不完全与父进程相同。

  • 子进程得到与父进程用户级虚拟地址空间相同的(但是独立的)一份拷贝。

    • 包括文本,数据和bss段,堆以及用户栈。子进程还获得与父进程任何打开文件描述符相同的拷贝。
    • 意味着当父进程调用fork时,子进程可以读写父进程中打开的任何文件。
    • 父进程和新创建的子进程之间最大的区别在于有不同的PID 。
  • fork()函数会第一次调用,返回两次,一次在父进程,一次在子进程。
    • 返回值用来明确是在父进程还是在子进程中执行。

  • 调用一次,返回两次

    • 对于具有多个fork实例的需要仔细推敲了
  • 并发执行
    • 父进程和子进程是并发运行的独立进程。
    • 内核可能以任意方式觉得执行他们的顺序。
    • 不能对不同进程中指令的交替执行做任何假设。
  • 相同但是独立的地址空间
    • 在刚调用时,几乎什么都是相同的。
    • 但是它们都有自己的私人空间,之后对x的改变是相互独立的。
  • 共享文件
    • 父进程和子进程都把他们的输出显示在屏幕上。
    • 子进程继承了父进程所有打开的文件。

画进程图会有帮助。

8.4.3 回收子进程

当一个进程由于某种原因终止时,内核并不是立即把它从系统中清除。相反,进程被保持在一种已终结的状态,知道被它的父进程 回收(reap)。

当父进程回收已终止的子进程时,内核将子进程的退出状态传递给父进程,然后抛弃已终止的进程。

一个终止了但还未被回收的进程叫做僵死进程

如果父进程没有回收,而终止了,那么内核安排init进程来回收它们。

  • init进程的的PID位1,在系统初始化时由内核创建的。
  • 长时间运行的程序,如shell,服务器,总是应该回收他们的僵死子进程


一个进程可以通过调用waitpid函数来等待它的子进程终止或停止

#include<sys/types.h>
#include<sys/wait.h>

pid_t waitpid(pid_t pid ,int *status, int options);
返回:如果成功,则为子进程的PID,如果WNOHANG,则为0,如果其他错误,则为-1.

waitpid函数有点复杂。默认(option=0)时,waitpid挂起调用进程的执行,知道它的等待集合中的一个子进程终止,如果等待集合的一个子进程在调用时刻就已经终止,那么waitpid立即返回。在这两种情况下,waitpid返回导致waitpid返回的已终止子进程的PID,并且将这个已终止的子进程从系统中去除。

  • 判断等待集合的成员

    等待集合的成员通过参数pid确定

    • 如果pid>0,那么等待集合就是一个独立的子进程,它的进程ID等于PID
    • 如果pid=-1,那么等待集合就是由父进程所有的子进程组成的。
    • waitpid函数还支持其他类型的等待集合,包括UNIX进程组等,不做讨论。
  • 修改默认行为(此处书中有问题,作用写反了)

    可以通过将options设置为常量WHOHANGWUNTRACED的各种组合,修改默认行为。

    • WHOHANG: 如果等待集合中的任何子进程都还没有终止,那么立即返回(返回值为0)

      • 默认的行为返回已终止的子进程。
      • 当你要检查已终止和被停止的子进程,这个选项会有用。
    • WUNTRACED:挂起调用进程的执行,知道等待集合中的一个进程变为已终结或被停止。
      • 返回的PID为导致的已终止或被停止的子进程PID·
      • 默认的行为是挂起调用进程,直到有子进程终止。
    • WHOHANG|WUNTRACED: 立即返回,如果等待集合中没有任何子进程被停止或已终止,那么
  • 检查已回收子进程的退出状态
时间: 2024-08-04 09:34:49

[CSAPP笔记][第八章异常控制流]的相关文章

CSAPP:第八章 异常控制流1

CSAPP:第八章 异常控制流1 关键点:异常 8.1 异常8.2 进程 ??现代系统通过使控制流发生突变来对这些情况做出反应,一般而言,我们把这些突变称为异常控制流(Exceptional Control Flow,ECF).异常控制流发生在计算机系统的各个层次. 8.1 异常 ??异常是异常控制流的一种形式,它的一部分由硬件实现,一部分由操作系统实现.异常就是控制流中的突变,用来响应处理器中的一些变化.如图所示,当处理器状态发生一个重要的变化时,处理器正在执行某个当前指令Icurr.在处理器

csapp:第八章 异常控制流ECF

第八章 异常控制流ECF 8.1 异常 Exception graph LR E[异常Exception]-->E2[中断:异步异常] E-->E3[同步异常] E3-->陷阱 E3-->故障 E3-->中止 异常是异常控制流的一种形式,他一部分由硬件实现,一部分由操作系统实现. 在任何情况下,当处理器检测到有事情发生时,他就会通过一张叫做异常表(exception table)的跳转表,进行一个简介过程调用(异常),到一个专门用来处理这类事件操作系统子程序(异常处理程序 e

第八章 异常控制流

第八章 异常控制流 ECF:(异常控制流)突变集合 平滑:顺序结构的指令 突变:跳转.调用.和返回等指令,不在同一栈 基本机制:ECF是操作系统用来实现I/O.进程和虚拟存器的基本机制   ECF是计算机系统中实现并发的基本机制 异常 控制流的突变 异常号: 系统为每种类型的异常分配的唯一的非负整数 异常表: 系统启动时操作系统就会初始化一张条转变,使得条目k包含异常k的处理程序的地址 用法: 从异常号到异常表的索引 类别: 中断:来自处理器外部的I/O设备的信号的结果,返回下一条指令 陷阱:陷

第八章 异常控制流 笔记

异常控制流存在于操作系统的方方面面,最底层的机制称为异常(Exception),由硬件和操作系统共同实现.另外还有: 进程切换(Process Context Switch): 硬件计时器和操作系统实现: 信号(Signal): 操作系统实现: 非本地跳转(Nonlocal Jumps):运行时实现. 异常 这里的异常指的是把控制交给系统内核来响应某些事件(例如处理器状态的变化),其中内核是操作系统常驻内存的一部分,而这类事件包括除以零.数学运算溢出.页错误.I/O 请求完成或用户按下了 ctr

深入理解计算机系统 第八章 异常控制流(2)

进程总是处于下面三种状态之一: 运行.进程要么在CPU上执行,要么在等待被执行且最终会被内核调度. 停止.进程的执行被挂起(suspended),且不会被调度. 终止.进程永远的停止了.进程会因为三种原因终止:1)收到一个信号,该信号的默认行为是终止进程,2)从主程序返回,3)调用exit函数. 父进程通过调用fork函数创建一个新的运行的子进程. fork函数的特点: 调用一次,返回两次.fork函数被父进程调用一次,但是却返回两次-----一次是返回到父进程,一次是返回到新创建的子进程. 当

异常控制流 第十周11.15~11.22

第八章 异常控制流 控制流:控制转移序列. 控制转移:从一条指令到下一条指令. 异常控制流:现代操作系统通过使控制流发生突变来对系统状态做出反应,这些突变称为异常控制流. 作为程序员,理解ECF很重要,这有很多原因: 理解ECF将帮助你理解重要的系统概念.ECF是操作系统用来实现I/O.进程和虚拟存储器的基本机制,在能够真正理解这些重要概念之前,你必须须理解ECF. 理解ECF将帮助你理解应用程序是如何与操作系统交互的.应用程序通过一个叫做陷阱或者系统调用的ECF形式,向操作系统请求服务. 理解

CSAPP:异常控制流

在一般的情况下,处理器处理的指令序列是相邻的(顺序执行). 异常控制流提供了指令的跳转,它一部分是由硬件实现的,一部分是由操作系统实现的. 异常处理 在系统启动时,操作系统分配和初始化一张称为异常表的跳转表: 触发异常时将从跳转表中找到并执行相应的异常处理程序的代码(所谓的内核态代码?): 系统调用 每个系统调用都属于异常,当调用C库中的系统调用函数时将触发异常. IA32系统中,系统调用是通过一条称为int 0x80(异常号)的陷阱指令来提供的. 所有Linux的系统调用都是通过寄存器来传递的

Android群英传笔记——第八章:Activity与Activity调用栈分析

Android群英传笔记--第八章:Activity与Activity调用栈分析 开篇,我们陈述一下Activity,Activity是整个应用用户交互的核心组件,了解Activity的工作模式,生命周期和管理方式,是了解Android的基础,本节主讲 Activity的生命周期与工作模式 Activity调用栈管理 一.Activity Activity作为四大组建出现平率最高的组件,我们在哪里都能看到他,就让我们一起先来了解一下他的生命周期 1.起源 Activity是用户交互的第一接口,他

8.0 异常控制流 第8章 《深入理解计算机系统 原书第2版》

异常控制流 定义:现代操作系统对于控制流发生突变所作出的反应 全称:Exception Control Flow 缩写:ECF 各层形态: 1.硬件层:硬件检测到的事件会触发控制突然转移到异常处理程序: 2.操作系统层:在操作系统层,内核通过上下文转换,将控制从一个用户进程转移到另外一个用户进程: 3.应用层:一个进程可以发信号到另外一个进程,而接收者会将控制突然转移到它的一个信号处理程序. 描述:一个程序可以通过回避通常的栈规则,并执行到其他函数中任意位置的非本地跳转来对错误做出的反应. 工作