1. 异常
从给处理器加电开始,直到你断电为止
程序计数器假设一个值的序列(\(a_k\)是\(I_k\)的地址)
每次从\(a_k到a_{k+1}\)的过渡称为控制转移,这样的控制转移称为处理器的控制流
系统必须对系统状态的变化(\(I_k和I_{k+1}\)的地址不相邻)做出反应,这些变化不是一些必要的机制(比如跳转,调用,返回)
比如从网络传输的数据包需要到达网络适配器之后,需要将数据放到内存中
现代系统通过控制流发生突变来对这些情况做出反应。我们把这些突变称为异常控制流
处理器正在执行当前指令\(I_{curr}\)
处理器状态中发生一个重要的变化时,状态被编码为不同的位和信号。状态变化称为事件。
- 事件可能和当前指令执行有关
比如发生虚拟内存缺页,算术溢出,一条指令试图除以0 - 也可能和当前指令执行无关
比如一个I/O请求完成
在任何情况,处理器检测到有事情发生时,都会通过异常表进行跳转,到一个专门用来处理这类事件的操作系统子系统
异常处理
系统为每种异常分配了异常号。
当系统启动时,操作系统分配和初始化一张称为异常表的跳转表
异常表的起始地址放在一个叫做异常表基址寄存器的特殊cpu寄存器中
通过异常表的起始地址和异常号来转到相应的异常处理程序
异常的类别
异步同步的区别主要是异常产生的原因来自cpu内部还是外部
1. 中断
当敲一下键盘时,键盘控制器会向处理器的中断引脚发送信号,异常号放到系统总线上。这个异常号标识了引起中断的设备
2. 陷阱
陷阱是故意触发的异常。
当应用程序需要读取文件或者创建新的进程时,此时需要向内核请求服务,处理器提供了一个特殊的指令:syscall
执行syscall指令会导致一个到异常处理程序的陷阱,这个处理程序解析参数,并调用适当的内核程序。
3. 故障
故障由错误情况引起,可能能被故障处理程序修正。
一个经典实例是缺页异常
当指令引用一个虚拟地址,但该地址对应的物理页面不在内存中,此时需要从磁盘中取出。就会发生故障。
缺页处理程序将页面加载到内存,然后将控制返回给引起故障的指令。
指令再次执行时就没有故障的运行完成了。
4. 终止
终止是不可恢复的致命错误造成的结果,比如DRAM或者SRAM被损坏时发生的错误。
终止处理程序从不将控制返回给应用程序,只会终止这个应用程序
私有地址空间
进程为每个程序提供一种假象,好像它独占地使用系统地址空间
地址空间底部是保留给用户程序的。代码段总是从地址0x400000开始
地址空间顶部保留给内核
上下文
内核为每个进程维持一个上下文。上下文就是内核重新启动一个被抢占的进程所需的状态。
上下文由一些对象的值组成,比如通用目的寄存器,浮点寄存器,程序计数器,用户栈,状态寄存器,内核栈和各种内核数据结构(页表)
内核抢占当前进程并重新开始一个先前被抢占了的进程被叫做进程调度,由内核中的调度器处理。并使用一种上下文切换的机制来将控制转移给新的进程
上下文切换:
- 保存当前进程的上下文
- 恢复某个先前被抢占的进程被保存的上下文
- 将控制转移给新进程
中断也可以引发上下文切换
进程A初始运行在用户模式中。当他执行系统调用read陷入到内核
磁盘读取数据需要较长的时间,所以内核执行从进程A到进程B的上下文切换。
当磁盘发出中断信号,表示数据已经从磁盘传送到内存,内核执行上下文切换从进程B到A
进程控制
1. 进程创建
父进程通过调用fork()函数创建进程
fork()执行一次返回两次
在父进程执行返回子进程的PID,在子进程执行返回0
因为所有进程的PID都是正数(非0),所以可以根据fork的返回值来判断是在哪个进程执行的。
我们还可以判断父进程和子进程哪个先执行,如下图
父进程和子进程会并发地执行fork()后的所有语句,且子进程在创建时会拷贝父进程的副本。
如下图,调用第一次fork()会创建一个子进程。
再调用第二次fork(),此时的两个进程会分别执行fork()
此时程序中有四个进程
2. execve函数
execve函数作用是调用加载器执行一个新的程序
第一个参数是执行的程序名字,第二个参数是参数列表,第三个参数是环境变量列表
对于下面这条指令
ls -lt /home/csapp
argv如下
信号
信号是一种更高层的软件形式的异常,允许进程和内核中断其他进程
比如,如果一个进程试图除以0,内核就会发送给该进程一个SIGFPE信号,这个信号对应的是浮点异常的事件
发送信号
1. 进程组:
每个进程都只属于一个进程组,进程组是由一个正整数进程组ID来标识的。
2. 用/bin/kill 发送信号
linux> /bin/kill -9 15213 //发送信号9给进程15213
linux> /bin/kill -9 -15213 //发送信号9给进程组15213的每个进程
3. 从键盘发送信号
对于
linux> ls|sort
会创建一个由两个进程组成的前台作业,这两个进程是通过Unix管道连接起来的
一个进程运行ls程序,一个进程运行sort程序
shell为每个作业创建一个独立的进程组,进程组id取自作业中父进程中的一个
4. 用kill函数发送信号
kill函数会根据pid的不同,做出不同的判断
5. 用alarm函数发送信号
alarm函数安排内核在secs秒后发送一个SIGALRM 信号给调用进程