教材内容总结
(一) 异常控制流概述
1.控制转移:从一条指令到下一条指令的过渡。
2.控制流:控制转移序列。
- 最简单的控制流:平滑的序列,每条指令在存储器中都是相邻的。
- 平滑流的突变:由于跳转、调用和返回等指令造成两条指令不相邻。
3.异常控制流(Exceptional Control Flow, ECF):现代操作系统通过使控制流发生突变来对系统状态做出反应,这些突变称为异常控制流。
4.异常控制流发生在计算机系统的各个层次
- 硬件层:硬件检测到的事件会触发控制突然装移到异常处理程序
- 操作系统层:内核通过上下文转换将控制从一个用户进程转移到另一个用户进程。
- 应用层:一个进程可以发送信号到到另一个进程,而接收者将会控制突然转移到它的一个信号处理程序。
- 一个程序可以通过回避通常的栈规则,并执行到其它函数中任意位置的非本地跳转来对错误做出反应。
5.ECF是操作系统用来实现I/O、进程和虚拟存器的基本机制。
6.应用程序通过使用一个叫做陷阱或者系统调用的ECF形式,向操作系统请求服务。
7.操作系统为应用程序提供了强大的ECF机制,用来创建新进程、等待进程终止、通知其他进程中系统的异常事件、检测和相应这些事件。
8.ECF是计算机系统中实现并发的基本机制。
9.软件异常允许程序进行非本地跳转来响应错误情况。非本地跳转是一种应用层ECF,在C中通过setjump和longjmp提供。
(二) 异常
1.异常剖析
- 异常是异常控制流的一种形式,是控制流中的突变,用来响应处理器状态中的某些变化,由硬件和操作系统实现。
- 事件:状态变化,可能和当前指令的执行有关。
异常处理后
- 1)处理程序将控制返回给事件发生时正在执行的当前指令
- 2)处理程序将控制返回给没有发生异常将会执行的下一条指令
- 3)处理程序终止被中断的程序
2.异常处理
异常表:当处理器检测到有事件发生时,它会通过跳转表,进行一个间接过程调用(异常),到异常处理程序。系统启动时操作系统分配和初始化一张异常表。
异常号:系统中可能的某种类型的异常都分配了一个唯一的非负整数的异常号。异常号是到异常表中的索引,异常表的起始地址放在一个叫做异常表基址寄存器的特殊CPU寄存器里。
异常与过程调用
异常类似于过程调用,但是有一些重要的不同:
- 处理器压入栈的返回地址,是当前指令地址或者下一条指令地址。
- 处理器也把一些额外的处理器状态压到栈里
- 如果控制一个用户程序到内核,所有项目都压到内核栈里。
- 异常处理程序运行在内核模式下,对所有的系统资源都有完全的访问权限。
3.异常的类别
4.Linux/IA32系统中的异常
(三) 进程
进程的经典定义:一个执行中的程序的实例
系统中每个程序都是运行在某个进程上下文中的。
上下文是由程序正确运行所需的状态组成的。状态包括存放在存储器中的程序代码和数据,栈、通用目的寄存器内容、程序计数器、环境变量和打开文件描述符的集合。
进程提供给应用程序的关键抽象
- 一个独立的逻辑控制流,提供一个假象:程序独占地使用处理器
- 一个私有的地址空间,提供一个假象:程序独占地使用存储器系统
1.逻辑控制流
程序计数器(PC)值的序列叫做逻辑控制流,简称逻辑流。
进程是轮流使用处理器的。每个进程执行它的流的一部分,然后被抢占,然后轮到其他进程。
2.并发流
一个逻辑流的执行在时间上与另一个流重叠(与是否在同一处理器无关),这两个流并发的运行。
并发:多个流并发的执行
多任务:一个进程和其他进程轮流运行(也叫时间分片)
时间片:一个进程执行它的控制流的一部分的每一时间段
并行:两个流并发的运行在不同的处理机核或者计算机上。
并行流并行的运行,并行的执行。
3.私有地址空间
进程为程序提供的假象,好像它独占的使用系统地址空间。一般而言,和这个空间中某个地址相关联的那个存储器字节是不能被其他进程读写的。
4.用户模式和内核模式
用户模式和内核模式的区别就在于用户的权限上,权限指的是对系统资源使用的权限。
(1)模式位
处理器通常用某个控制寄存器中的一个模式位来描述进程当前享有的特权。
设置模式位:内核模式(超级用户)
没有设置模式位:用户模式
(2)转换模式
运行应用程序代码的进程初始时是在用户模式中的。进程从用户模式变为内核模式的唯一方法是通过如中断、故障或陷入系统调用之类的异常。
(3)/proc文件系统
将许多内核数据结构的内容输出为一个用户程序可以读的文本文件的层次结构。它允许用户模式进程访问内核数据结构的内容。
5.上下文切换
操作系统内核使用上下文切换这种较高层形式的异常控制流来实现多任务。上下文切换机制建立在较底层异常机制之上。
(1)上下文
内核重新启动一个被抢占的进程所需的状态。
由一些对象的值组成:通用目的寄存器、浮点寄存器、程序计数器、用户栈、状态寄存器、内核栈、内核数据结构(页表、进程表、文件表)。
(2)上下文切换机制
- 保存当前进程的上下文
- 恢复某个先前被抢占的进程被保存的上下文
- 将控制传递给这个新恢复的进程。
(3)上下文切换原因
- 内核代表用户执行系统调用时(进程休眠)
- 中断
(四) 进程控制
1.每个进程都有一个唯一的正数进程ID(PID)。
#include <sys/types.h>
#include <unistd.h>
pid_t getpid(void); 返回调用进程的PID
pid_t getppid(void); 返回父进程的PID(创建调用进程的进程)
2.创建和终止进程
(1)进程的三种状态
- 运行
- 停止:被挂起且不会被调度
- 终止:永远停止。
- 收到信号,默认行为为终止进程
- 从主程序返回
- 调用exit函数
(2)创建进程
父进程通过调用fork函数来创建一个新的运行子进程。fork函数定义如下:
#include <sys/types.h>
#include <unistd.h>
pid_t fork(void);
fork函数只被调用一次,但是会返回两次:父进程返回子进程的PID,子进程返回0.如果失败返回-1。
(3)终止进程
exit函数
#include <stdlib.h>
void exit(int status);
exit函数以status退出状态来终止进程。
3.回收子进程
进程终止后还要被父进程回收,否则处于僵死状态。
如果父进程没有来得及回收,内核会安排init进程来回收他们。init进程的PID为1.
一个进程可以通过调用waitpid函数来等待它的子进程终止或停止。
#include <sys/types.h>
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *status, int options);
- 成功返回子进程PID,如果WNOHANG,返回0,其他错误返回-1。
(1)判定等待集合的成员
pid>0 等待集合是一个单独子进程,进程ID等于pid
pid=-1 等待集合是由父进程所有的子进程组成
(2)修改默认行为
设置常量WNOHANG和WUNTRACED
-
WHOHANG
- 默认:挂起调用进程
- 修改:如果等待集合中的任何子程序都没有终止,立即返回
- 返回值:0
-
WUNTRACED
- 默认:返回已终止的子进程
- 修改:挂起调用进程的执行,直到等待集合中的一个进程变成已终止或者被停止
- 返回值:导致返回的已终止或被终止子进程的PID
-
WNOHANG | WUNTRACED
- 修改:立即返回
- 返回值:0,或者返回值等于被停止或者已停止的子进程PID
(3)检查已回收子进程的退出状态
status参数:在wait.h头文件中定义了解释status参数的几个宏
WIFEXITED:如果子进程通过调用exit或一个返回正常终止,就返回真
WEXITSTATUS:返回一个正常终止的子进程的退出状态。只有在WIFEXITED返回为真时,才会定义这个状态
WIFSIGNALED:如果子进程是因为一个未被捕获的信号终止的,那么返回真
WTERMSIG:返回导致子进程终止的信号的编号。只有在WIFSIGNALED返回为真时才定义这个状态
WIFSTOPPED:如果引起返回的子进程当前是被停止的,那么返回真
WSTOPSIG:返回引起子进程停止的信号的数量。只有在WIFSTOPPED返回为真时才定义这个状态
(4)错误条件
- 若调用进程没有子进程,waitpid返回-1,设置errno为ECHILD。
- 若waitpid被一个信号中断,返回-1,设置errno为EINTR。
(5)wait函数
wait函数是waitpid函数的简单版本,wait(&status)等价于waitpid(-1,&status,0).
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);
- 成功返回子进程pid,出错返回-1
代码托管截图
学习进度条
代码行数(新增/累积) | 博客量(新增/累积) | 学习时间(新增/累积) | 重要成长 | |
---|---|---|---|---|
目标 | 5000行 | 30篇 | 400小时 | |
第一周 | 0/0 | 1/1 | 10/10 | |
第二周 | 0/0 | 1/2 | 10/20 | |
第三周 | 0/0 | 1/3 | 10/30 | |
第四周 | 0/0 | 1/4 | 10/40 | |
第五周 | 100/100 | 3/7 | 10/40 | |
第六周 | 150/200 | 2/9 | 10/50 | 安装Y86模拟器 |
第七周 | 150/200 | 2/11 | 10/60 | 安装kali与学会了代码托管 |
第八周 | 150/350 | 2/13 | 10/70 | 开始系统复习 |
第九周 | 120/470 | 2/15 | 10/70 | 掌握Unix/Linux系统级I/O:open close read write seek stat |
第十周 | 100/470 | 2/17 | 10/80 | 分析实践老师教的代码 |
第十一周 | 100/470 | 2/19 | 10/90 | 了解异常及其种类;理解进程和并发的概念;掌握进程创建和控制的系统调用及函数使用 |