操作系统的三个任务:CPU调度、内存管理、IO管理。
CPU调度就是进程管理
前言:为何需要进程切换
操作系统为了实现多任务并行。
多任务操作系统的前提:
- 多个任务
- 任务切换足够快
- CPU足够快
从任务角度出发,出现进程切换的场景:
- 进程A需要等待某件事情的完成
- 进程A和进程B都需要执行
- 进程B更重要
- …….
进程级别,出现调度的具体情况:
- 进程A出发某些内核函数,比如sleep等
- 进程A是循环,也就是说不会主动交出控制权。实际上操作系统在启动进程A的时候,设置了中断的时间
- 某些硬件的操作,比如鼠标移动、键盘输入都会导致进入kernel的处理
进程管理概念
进程管理是多任务操作系统中的基本功能。操作系统将资源分配给各个进程,让进程可以分享和交换信息,保护每个进程拥有的资源,以及使进程同步。
interrupt 与 context switch
两者是不同的,CPU应该允许在内核态可以interrupt,但是context switch则不被允许,可能会涉及状态保存的问题。
进程控制块PCB
为了实现进程管理,操作系统为每个进程分配了一个数据结构,用来描述进程的状态以及进程拥有的资源。操作系统可以透过这个数据结构来控制每个进程的运作。
PCB在内核中的数据结构成为task_struct,主要包括4方面信息:
- 进程标识符:唯一标识一个进程,一个进程通常有两种标识符:内部标识符和外部标识符。
- 处理及状态
- 进程调度信息:与进程调度的相关信息,如进程状态、进程优先级、其他与调度相关的信息
- 进程控制信息:程序和数据地址
进程状态的描述
进程的三个基本状态:运行态、就绪态、阻塞态

- 运行态running:当一个进程在处理机上运行时,则称该进程处于运行状态
- 就绪态ready:一个进程获得了除处理机外的一切所需资源,一旦得到处理机资源就可以运行,称进程处于就绪态
- 阻塞态blocked:一个进程正在等待某一个事件发生(如请求I/O),而暂时停止运行。
三个基本状态之间可能转换和转换原因如下:参考文章
就绪态–>运行态:当处理机空闲时,进程调度程序必将处理机分配给一个处于就绪态的进程 ,该进程便由就绪态转换为运行态。 运行态–>阻塞态:处于运行态的进程在运行过程中需要等待某一事件发生后(例如因I/O请求等待I/O完成后),才能继续运行,则该进程放弃处理机,从运行态转换为阻塞态。 阻塞态–>就绪态:处于阻塞态的进程,若其等待的事件已经发生,于是进程由阻塞态转换为就绪态。 运行态–>就绪态:处于运行状态的进程在其运行过程中,因分给它的处理机时间片已用完,而不得不让出(被抢占)处理机,于是进程由运行态转换为就绪态。 而阻塞态–>运行态和就绪态–>阻塞态这二种状态转换不可能发生。 处于运行态进程:如系统有一个处理机,则在任何一时刻,最多只有一个进程处于运行态。 处于就绪态进程:一般处于就绪态的进程按照一定的算法(如先来的进程排在前面,或采用优先权高的进程排在前面)排成一个就绪队列RL。 处于阻塞态进程:处于阻塞态的进程排在阻塞队列中。由于等待事件原因不同,阻塞队列也按事件分成几个队列WLi。
context
- process context
- interrupt context
Q:用户态允许中断,那么如果处于内核态,是否允许中断呢?
进程管理
进程A调动进程B的过程:
进程A->内核提供的系统调用
进程如何产生操作系统的调用
整体过程:

1.1 进程在系统中的布局
当只有进程A时,操作系统内存布局如下:

### 1.2进程A调用操作系统
进程通过软中断soft interrupt,比如int80

glibc中的fork函数为了调用kernel,需要先修改寄存器,然后使用中断

异常和中断处理
同步异常,因为异常都是由于CPU处理到异常的指令造成的,与CPU同步。操作系统启动的时候,会将异常向量表(vector table)加载到内存中,如果操作系统遇到不会处理的指令,回去向量表中查找。

异常为synchronous exception,中断为asynchronous exception,下图可以看到,除了初始的阶段不同,后期两者对于cpu内部的操作是一样,不过是中断需要外部的“刺激”

举例而言:上一节中的glib库的fork函数,通过int 0x80引起中断,80表示这个中断是系统调用,接着内核使用syscall handler 来捕捉,并在syscall table找到第二个(eax寄存器指定)操作,sys_fork()函数


操作系统不是一个会执行的程序,进程A进入到内核态时,上下文【context】不会变化

程序和进程的区别
程序是静态的文件,通常格式为ELF(可执行文件,executable file),保存在磁盘;进程存在在内存中,并已经执行。
程序是一组指令的有序集合,是静态的实体。而进程是程序在某个数据集的执行。进程和程序并非一一对应的关系,一个程序执行在不同的数据集就成为不同的进程。
进程 = 程序 + 数据

进程和线程的区别 process thread
出现线程是因为同一个程序可能会被同时使用多次,某些资源可以复用。比如打开多个word文档。
根本区别:进程是操作系统资源分配的基本单位,而线程是处理器任务调度和执行的基本单位
资源开销:每个进程都有独立的代码和数据空间(程序上下文),程序之间的切换会有较大的开销;线程可以看做轻量级的进程,同一类线程共享代码和数据空间,每个线程都有自己独立的运行栈和程序计数器(PC),线程之间切换的开销小。
包含关系:如果一个进程内有多个线程,则执行过程不是一条线的,而是多条线(线程)共同完成的;线程是进程的一部分,所以线程也被称为轻权进程或者轻量级进程。
1.3 fork过程
每个进程在kernel中都会存储一个结构保存重要信息,如PID等。进程A fork进程B的过程中,会直接复制进程A的结构,然后修改这个结构,比如PID。如下图,

进程描述符【结构】如下图所示,在linux中的形式

进程的状态变化图

1.4 进程调度
中断
寻找下一个运行的进程。进程调度的场景:
-
当前进程需要其他工作。进程自身调用kernel的函数,如sysytem call。
-
当前进程占据太多时间。内核在进程运行时会设置一个时间,时间用完会调用kernel。如下,进程调度有可能会导致进程切换,也有可能回到原进程。

-
更重要的进程出现。如鼠标、键盘等操作。

分段
用户进程可以被打断,那么kernel能否被打断呢

抢占
用户抢占(user preemption)和内核抢占(kernel preemption),注意抢占不等于中断
非抢占式内核支持用户抢占(eg. linux kernel 2.4)
抢占式内核支持用户抢占和内核抢占(eg. linux kernel 2.6)

调度算法
考虑的因素,进程优先级,等待时间等等。
调度在linux中像是一个双向链表

进入schedule的场景
-
用户程序需要等待一个事件的完成, 如数据的读取。那么内核就会将这个进程设为idle,并进入schedule

-
进程等待一个kernel的资源,而这个资源正在被lock

-
进程被某些外部中断,如鼠标移动。此时也会进入schedule,判断是否要切换进程

-
进程调用schedule,比如call sleep

### 1.5 context 切换
context switch简单的理解就是进程对应的TSS段切换。

存储当前进程的状态

e.g 进程A切换到进程B的过程(CPU存储了当前进程的基本状态,如IP指向下一个指令,SP指向栈等等)。
- 进程A由于某些原因进入内核进程调度模块
-
进程调度决定将进程A切换到进程B
- kernel将进程A的状态,即当前cpu的某些寄存器,存储到kernel space中,linux kernel将这些进程状态的信息所在文件命名为TSS(task state segment)
- 将进程B的TSS文件读取,并存入CPU
- 读取CPU,就开始运行进程B啦

GDT(global description table)全局描述符
LDT(local description table)局部描述符
如果只是进程切换,那根据TSS就可以呀,为什么还需要GDT和LDT呢?

问题
- 内核有几个进程?
- 为什么有了TSS,还需要GDT和LDT,进程切换的顺序是什么?
GDT和LDT的目的是为了什么?
80x86体系结构包括一个特殊的段类型,叫任务状态段(Task State Segment, TSS)来存放硬件上下文。尽管Linux并不使用硬件上下文切换,但是强制它为系统中每个不同的CPU创建一个TSS。这样做的两个主要理由为?
https://blog.csdn.net/nkguohao/article/details/9187381
某个作者提到了一句。。
http://www.kancloud.cn:8080/digest/protectedmode/121466文中提到,为了使用段,必须创建段描述符。

参考文献
- 深入理解操作系统原理之进程管理,https://blog.csdn.net/xiaokang123456kao/article/details/73773474