进程(二)--- 进程切换与调度
在进程一中,我们知道程序启动后是被加载进内存,同时将第一条指令地址写入pc寄存器,接下来由cpu来处理各种指令。但是cpu并不是一直在执行这个进程的,在遇到IO或者一些其他事件的时候,就会让出cpu,让给其他的进程来使用cpu.
那么,到底是哪些情况下会导致进程被切换?
系统调用
操作系统向普通用户提供的接口被称作系统调用,系统调用提供了访问和使用操作系统提供的服务的接口。
- 系统调用的实现代码是操作系统级的
- 这个接口通常是面向程序员的
但是程序员一般不是直接使用系统调用的,程序员使用API
间接的调用了系统调用
再linux 系统中提供的api是POSIX API
( Portable Operating System Interface),
e.g :printf
这个api 被调用执行的时候,其实引发的系统调用是write
以下执行一段shell
for i in {1..100}; do printf 0;sleep 1 ;done
使用strace 来查看系统调用
strace -p 553467
通过上图可以看到有一个write
系统调用,还有一个sleep
系统调用
再说进程切换的时候我们需要了解一点就是操作系统其实是分为两个模式,如下:
双模式
上面了解了系统调用之后,我们需要知道为什么需要系统调用。
因为再现代操作系统上有一个特殊的硬件,用于划分系统的运行状态,至少需要两种单独运行模式:用户模式和内核模式
用户模式
当计算机系统运行用户应用程序(如文件创建或使用任何其他应用程序)时,系统处于用户模式。 此模式不能直接访问计算机的硬件。 为了执行与硬件相关的任务,例如当用户应用程序从操作系统请求服务或发生某些中断时,在这些情况下,系统必须切换到内核模式。 用户模式的模式位为1。这意味着,如果系统处理器的模式位为1,则系统将处于用户模式。
内核模式
执行操作系统代码,操作系统的所有底层任务均在内核模式下执行。 由于内核空间可以直接访问系统的硬件,因此内核模式可以处理所有需要硬件支持的进程。 除此之外,内核模式的主要功能是执行特权指令。 这些特权指令没有提供给用户访问权限,这就是为什么这些指令无法在用户模式下进行处理。因此,所有限制用户干预的进程和指令都在操作系统的内核模式下执行。 内核模式的模式位为0。因此,要使系统在内核模式下运行,处理器的模式位必须等于0
目的
确保操作系统正确运行,因为对于硬件的操作以及一些cpu 的特权指令如果由用户来执行处理,很可能会对硬件造成不可挽回的损伤导致计算机被破坏等危害。
上图中就是两种模式的切换,其中从用户模式切换到内核模式就叫做trap
,trap 就是将操作系统的模式为由 1变为0,在将控制权交给用户程序之前,系统总是切换到用户模式
系统调用的实现
每个系统调用都有一个唯一的数字编号,被称为系统调用号
。
当用户代码调用API 时,API会向系统调用接口
指明起所要用的系统调用号
,操作系统内核中维护了一张索引表,依据这个调用号可以检索到访系统调用代码再内核中的位置。
系统调用号和代码
系统调用号 | 入口点 | 函数名 | 源代码 |
---|---|---|---|
0 | read | sys_read | fs/read_write.c |
1 | write | sys_write | fs/read_write.c |
2 | open | sys_open | fs/open.c |
3 | close | sys_close | fs/open.c |
4 | stat | sys_newstat | fs/stat.c |
中断技术
什么是中断?
中断是指程序执行过程中,当发生某个事件的时候,中止cpu上现行程序的运行,从而执行该事件的处理程序,等到事件执行完毕后返回源程序中断的地方继续执行。
这句话中的事件其实可以理解为一个系统调用或者一个硬件响应,比如鼠标移动,键盘输入等。
然后事件的处理程序其实就是指的是,操作系统针对系统调用或者硬件进行响应而做的事情
中断源
上面介绍了上面是中断,从上面介绍的中断可以看出中断源其实应该可以分为两类,外中断和内中断
外中断(interrupt)
来自处理器之外的硬件中断信号,比如
-
时钟中断:一个进程最长使用cpu的时间,当时间到达后,就会触发时钟中断由操作系统接管cpu的执行
-
键盘中断: 键盘打字也是一个中断信号
-
外围设备中断: 比如麦克风,耳机,音响等
这些外中断都是异步中断(随机中断),因为这些外部设备发生的时机都是随机的
内中断(异常 exception)
来自于处理器内部,指令执行过程中发生的中断,比如
- 硬件异常: 掉电,奇偶校验错误
- 程序异常:非法操作,地址越界,断电,除数为0
- 系统调用
这些都是同步中断
中断处理过程
- 进程执行过程中触发了异常或者遇到了外部事件,操作系统就会保存这个进程的上下文信息(context)
进程上下文信息:当一个进程在执行时,CPU的所有寄存器中的值、进程的状态以及内存堆栈中的内容被称为该进程的上下文,当内核需要切换到另一个进程时,它需要保存当前进程的所有状态,即保存当前进程的上下文,以便在再次执行该进程时,能够回到切换时的状态执行下去
- 判断中断来源来选择对应的中断处理程序
- 选择一个进程恢复
- 恢复进程的上下文
- 恢复进程到刚刚中断的那个地方继续执行
进程切换
通过以上信息,可以大致了解进程进行切换所需要的信息。
总结
进程切换的时候,进程需要进入等待状态或者由于被强占cpu 进入就绪状态,
切换过程 (user mode ---> kernel mode)
- 需要保存中断进程的上下文信息
- 修改中断进程的控制信息(比如进程状态)
- 将被中断的进程加入相应的状态队列
- 从就绪队列调度一个新的进程并恢复它的上下文信息
进程调度
进程控制块(PCB)
PCB (process control Block) 包含了指定进程的一系列的信息
- process state : 进程状态
- process number: pid
- program counter: pc 值
- registers: 寄存器的值
- memory limits: 内存信息
- list of open files: 进程所打开的文件列表
之前有介绍过进程的内存空间信息是如下图:
但是其实真实的进程再内存中的信息其实还要加上PCB
的信息,所以如下图:
上图就是进程的上下文信息
进程队列
再上面进程切换的概念里面有描述过,一个进程的切换就是将运行时的进行放入就绪或者等待队列,然后再从就绪队列里面获取一个进程来再cpu上执行。那么进程队列又是怎么个形式呢?
如下图
进程队列中只存在PCB
信息,因为 PCB
信息里面以及包含了进程的元信息没必要包含所有进程的信息,因为进程是一直在切换的,那就回一直入队出队,如果信息过多就会造成不必要的资源浪费。这里没有运行时队列是因为,运行时的进程只有一个(单个cpu) 因此没必要设置一个运行时队列。
就绪队列
就绪队列只有一个队列
根据上图中如果 PCB2
的进程要开始运行,那么只需要将PCB7
的头指针置空。同时将就绪队列的尾指针指向 PCB7
等待队列
相对于就绪队列只有一个,等待队列有多个,等待队列针对不同的等待IO事件分为不同的队列,如果某些进程等待相同的事件发生,那么就将这些进程防盗一个队列
如上图中 分别是
- 等待第0号磁带机
- 等待第1号磁带机
- 等待第0号磁盘事件
- 等待第0号终端事件
进程调度总结
进程再整个生命周期中会再各个调度队列中迁移,由操作系统的一个调度器来执行
上图中i,每个矩形框代表一个队列。存在两种类型的队列:就绪队列和一组设备队列。圆圈表示为队列服务的资源,箭头表示系统中的进程流。
一个新进程最初被放入就绪队列。它在那里等待,直到它被选中执行或分派。一旦进程被分配了 CPU 并正在执行,可能会发生以下几个事件之一:
- 该进程可以发出 I/O 请求,然后放入 I/O 队列。
- 进程可以创建一个新的子进程并等待子进程终止。
- 由于中断,进程可以从 CPU 中强制移除,并放回就绪队列中
在前两种情况下,进程最终会从等待状态切换到就绪状态,然后放回就绪队列中。一个进程继续这个循环,直到它终止,此时它从所有队列中删除,并释放其 PCB 和资源。