码迷,mamicode.com
首页 > 移动开发 > 详细

CSAPP读书笔记--第八章 异常控制流

时间:2017-11-13 21:19:06      阅读:238      评论:0      收藏:0      [点我收藏+]

标签:计划执行   for   order   笔记   进入   改变   处理器   会计   地址   

第八章 异常控制流

概述

控制转移序列叫做控制流。目前为止,我们学过两种改变控制流的方式:

  1)跳转和分支;

  2)调用和返回。

但是上面的方法只能控制程序本身,发生以下系统状态的变化复杂问题时就没法使用上面的方法控制:

  • 数据从磁盘或者网络适配器到达
  • 指令除以了零
  • 用户按下 ctrl+c
  • 系统的计时器到时间

  现代系统通过使控制流发生突变来对系统状态的变化做出反应,这些突变称为异常控制流

  异常控制流有四种实现机制:

1)异常(低层级);2)进程上下文切换;3)信号;4)非本地跳转。(2-4高层级)

8.1 异常

        异常(exception)就是控制流中的突变,用来响应处理器状态中的某些变化。这里的异常是指将控制交给系统内核来处理某些事情。

内核是操作系统常驻内存的部分。

  任何情况下,当处理器检测到有事件发生时,它会通过一张就做异常表的跳转表,进行一个间接过程的调用,使用异常处理程序(运行在内核模式下)来处理这类事件。这类事件包括被零除、缺页、算术溢出、I/O请求完成。异常处理程序完成处理后,会发生以下三种情况:

  1)处理程序将控制放回给当前指令Icurr,即事件发生时正在执行的指令。

  2)将控制返回给Inext;

  3)终止被中断的程序。

 技术分享

  系统中为每种类型的异常都分配了一个唯一的非负整数的异常号,系统会通过异常表来确定跳转的位置,每个事件都有对应的异常号,发生对应事件就调用对应的异常处理代码。 

异常的类别

 异常分为异步异常和同步异常。异步异常是硬件中断(称为中断处理程序),是有外部IO设备造成的,同步异常是执行当前指令的结果(称为故障指令)。

陷阱属于系统调用,运行在内核模式中,而普通的程序调用运行在用户模式中,限制了函数可以执行的指令的类型。

类别 原因 异步/同步 返回行为
中断 来自I/O设备的信号 异步 总是返回到下一条指令
陷阱 有意的异常(read,exit) 同步 总是返回到下一条指令
故障 潜在可恢复的错误 同步 可能返回到当前指令
终止 不可恢复的错误 同步 不会返回

 

 这里重点分析一下故障异常,以缺页异常故障为例:

缺页异常发生的条件是:指令引用一个虚拟地址,但是与该地址相对应的物理页面不在内存中,因此必须从磁盘中读取,就会发生故障。

  • 用户写入内存位置
  • 但该位置目前还不在内存中

 

int a[1000];
main ()
{
    a[500] = 13;
}

那么系统会通过 Page Fault 把对应的部分载入到内存中,然后重新执行赋值语句:

技术分享

 

 8.2 进程

  通俗的定义是:占用内存空间的正在运行的程序。经典的定义是:一个执行中的程序的实例。进程提供给应用程序的关键抽象:1)一个独立的逻辑控制流;2)一个私有的地址空间。

  并发流的概念:流X和Y互相并发,当且仅当X在Y开始之后和Y结束之前进行。或者Y在X开始之后和X结束之前开始。

  上下文切换:1)保存当前进程的上下文;2)恢复某个先前被抢占的进程被保存的上下文;3)将控制传递给这个新恢复的进程。

8.3 进程控制

获取进程ID:

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

pid_t getpid(void);
pid_t getppid(void);

getpid返回调用进程的PID,getppid返回它的父进程的PID。

我们可以认为,进程有三个主要状态:

  • 运行 Running
    • 正在被执行、正在等待执行或者最终将会被执行
  • 停止 Stopped
    • 执行被挂起,在进一步通知前不会计划执行
  • 终止 Terminated
    • 进程被永久停止

另外的两个状态称为新建(new)和就绪(ready),这里不再赘述。

fork()函数创建进程。

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

pid_t fork(void);

//子进程返回0,父进程返回子进程的PID,如果出错,则返回-1

需要注意

  • 调用一次,返回两次;
  • 并发执行;
  • 相同但是独立的地址空间;
  • 共享文件。

进程图

通过画进程图来理解fork函数:

  • 每个节点代表一条执行的语句
  • a -> b 表示 a 在 b 前面执行
  • 边可以用当前变量的值来标记
  • printf 节点可以用输出来进行标记
  • 每个图由一个入度为 0 的节点作为起始
int main()
{
    pid_t pid;
    int x = 1;
    
    pid = Fork();
    if (pid == 0) 
    {   // Child
        printf("child! x = %d\n", --x);
        exit(0);
    }
    
    // Parent
    printf("parent! x = %d\n", x);
    exit(0);
}

技术分享

 

在下面三种情况时,进程会被终止

  1. 接收到一个终止信号
  2. 返回到 main
  3. 调用了 exit 函数

exit 函数会被调用一次,但从不返回,具体的函数原型是

// 以 status 状态终止进程,0 表示正常结束,非零则是出现了错误
void exit(int status)

 

 8.4 回收子进程

  一个终止了但是没有被回收的进程称为僵尸进程。如果父进程已经终止了,那么对应的没终止的子进程就称为孤儿进程。对于孤儿进程,内核会安排init进程称为他的孤儿进程的养父。init进程的PID是1,是在操作系统启动后由内核创建的,init进程会回收孤儿进程。

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

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

pid_t waitpid(pid_t pid,int *statusp,int options);
//pid 等待终止的目标子进程的ID,如果传递-1,则与wait函数相同,可以等待任意子进程终止
//statusp 传入变量的地址值
//如果设置为WNOHANG,即使没有终止的子进程也不会进入阻塞状态,而是返回0并且退出
//如果成功则返回子进程的ID,如果是WNOHANG,则为0,出错则为-1
  • WIFEXITED子进程正常终止时则返回真;
  • WEXITSTATUS返回子进程的返回值。
if(WIFEXITED(statusp)){//是正常终止吗?
    puts("Normal termination!");
    printf("Child pass num:%d",WEXITSTATUS(statusp));//那么返回值是多少?
}

如果想在子进程载入其他的程序,就需要使用 execve 函数,具体可以查看对应的 man page,这里不再深入。

 

8.5 信号

  Linux 的进程树,可以通过 pstree 命令查看。

  对于前台进程来说,我们可以在其执行完成后进行回收,而对于后台进程来说,因为不能确定具体执行完成的时间,所以终止之后就成为了僵尸进程,无法被回收并因此造成内存泄露。

编号名称默认动作对应事件
2 SIGINT 终止 用户输入 ctrl+c
9 SIGKILL 终止 终止程序(不能重写或忽略)
11 SIGSEGV 终止且 Dump 段冲突 Segmentation violation
14 SIGALRM 终止 时间信号
17 SIGCHLD 忽略 子进程停止或终止

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

CSAPP读书笔记--第八章 异常控制流

标签:计划执行   for   order   笔记   进入   改变   处理器   会计   地址   

原文地址:http://www.cnblogs.com/dingxiaoqiang/p/7827887.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!