进程管理

理解系统中的进程

Posted by BY beta on June 10, 2020

操作系统的三个任务:CPU调度、内存管理、IO管理。

CPU调度就是进程管理

前言:为何需要进程切换

操作系统为了实现多任务并行。

多任务操作系统的前提:

  • 多个任务
  • 任务切换足够快
  • CPU足够快

从任务角度出发,出现进程切换的场景:

  1. 进程A需要等待某件事情的完成
  2. 进程A和进程B都需要执行
  3. 进程B更重要
  4. …….

进程级别,出现调度的具体情况:

  1. 进程A出发某些内核函数,比如sleep等
  2. 进程A是循环,也就是说不会主动交出控制权。实际上操作系统在启动进程A的时候,设置了中断的时间
  3. 某些硬件的操作,比如鼠标移动、键盘输入都会导致进入kernel的处理

进程管理概念

进程管理是多任务操作系统中的基本功能。操作系统将资源分配给各个进程,让进程可以分享和交换信息,保护每个进程拥有的资源,以及使进程同步。

interrupt 与 context switch

两者是不同的,CPU应该允许在内核态可以interrupt,但是context switch则不被允许,可能会涉及状态保存的问题。

进程控制块PCB

为了实现进程管理,操作系统为每个进程分配了一个数据结构,用来描述进程的状态以及进程拥有的资源。操作系统可以透过这个数据结构来控制每个进程的运作。

PCB在内核中的数据结构成为task_struct,主要包括4方面信息:

  1. 进程标识符:唯一标识一个进程,一个进程通常有两种标识符:内部标识符和外部标识符。
  2. 处理及状态
  3. 进程调度信息:与进程调度的相关信息,如进程状态、进程优先级、其他与调度相关的信息
  4. 进程控制信息:程序和数据地址

进程状态的描述

进程的三个基本状态:运行态、就绪态、阻塞态

image-20200617202328030

  • 运行态running:当一个进程在处理机上运行时,则称该进程处于运行状态
  • 就绪态ready:一个进程获得了除处理机外的一切所需资源,一旦得到处理机资源就可以运行,称进程处于就绪态
  • 阻塞态blocked:一个进程正在等待某一个事件发生(如请求I/O),而暂时停止运行。

三个基本状态之间可能转换和转换原因如下:参考文章

就绪态–>运行态:当处理机空闲时,进程调度程序必将处理机分配给一个处于就绪态的进程 ,该进程便由就绪态转换为运行态。 运行态–>阻塞态:处于运行态的进程在运行过程中需要等待某一事件发生后(例如因I/O请求等待I/O完成后),才能继续运行,则该进程放弃处理机,从运行态转换为阻塞态。 阻塞态–>就绪态:处于阻塞态的进程,若其等待的事件已经发生,于是进程由阻塞态转换为就绪态。 运行态–>就绪态:处于运行状态的进程在其运行过程中,因分给它的处理机时间片已用完,而不得不让出(被抢占)处理机,于是进程由运行态转换为就绪态。 而阻塞态–>运行态和就绪态–>阻塞态这二种状态转换不可能发生。 处于运行态进程:如系统有一个处理机,则在任何一时刻,最多只有一个进程处于运行态。 处于就绪态进程:一般处于就绪态的进程按照一定的算法(如先来的进程排在前面,或采用优先权高的进程排在前面)排成一个就绪队列RL。 处于阻塞态进程:处于阻塞态的进程排在阻塞队列中。由于等待事件原因不同,阻塞队列也按事件分成几个队列WLi。

context

  1. process context
  2. interrupt context

Q:用户态允许中断,那么如果处于内核态,是否允许中断呢?

进程管理

进程A调动进程B的过程:

进程A->内核提供的系统调用

进程如何产生操作系统的调用

整体过程:

image-20200610192753966

1.1 进程在系统中的布局

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

image-20200610194724122

### 1.2进程A调用操作系统

进程通过软中断soft interrupt,比如int80

image-20200610195451739

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

image-20200610195939128

异常和中断处理

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

image-20200610200320638

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

image-20200613214415806

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

image-20200610202125020

image-20200610202749569

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

image-20200610203753492

程序和进程的区别

程序是静态的文件,通常格式为ELF(可执行文件,executable file),保存在磁盘;进程存在在内存中,并已经执行。

程序是一组指令的有序集合,是静态的实体。而进程是程序在某个数据集的执行。进程和程序并非一一对应的关系,一个程序执行在不同的数据集就成为不同的进程。

进程 = 程序 + 数据

image-20200610204513973

进程和线程的区别 process thread

出现线程是因为同一个程序可能会被同时使用多次,某些资源可以复用。比如打开多个word文档。

根本区别:进程是操作系统资源分配的基本单位,而线程是处理器任务调度和执行的基本单位

资源开销:每个进程都有独立的代码和数据空间(程序上下文),程序之间的切换会有较大的开销;线程可以看做轻量级的进程,同一类线程共享代码和数据空间,每个线程都有自己独立的运行栈和程序计数器(PC),线程之间切换的开销小。

包含关系:如果一个进程内有多个线程,则执行过程不是一条线的,而是多条线(线程)共同完成的;线程是进程的一部分,所以线程也被称为轻权进程或者轻量级进程。

1.3 fork过程

每个进程在kernel中都会存储一个结构保存重要信息,如PID等。进程A fork进程B的过程中,会直接复制进程A的结构,然后修改这个结构,比如PID。如下图,

image-20200610210720634

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

image-20200610211001999

进程的状态变化图

image-20200610211938233

1.4 进程调度

中断

寻找下一个运行的进程。进程调度的场景:

  • 当前进程需要其他工作。进程自身调用kernel的函数,如sysytem call。

  • 当前进程占据太多时间。内核在进程运行时会设置一个时间,时间用完会调用kernel。如下,进程调度有可能会导致进程切换,也有可能回到原进程。

    image-20200613214848413

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

    image-20200613212709297

分段

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

image-20200614095730819

抢占

用户抢占(user preemption)和内核抢占(kernel preemption),注意抢占不等于中断

非抢占式内核支持用户抢占(eg. linux kernel 2.4)

抢占式内核支持用户抢占和内核抢占(eg. linux kernel 2.6)

image-20200614100828400

调度算法

考虑的因素,进程优先级,等待时间等等。

调度在linux中像是一个双向链表

image-20210222203555449

进入schedule的场景

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

    image-20210222202718396

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

    image-20210222202736000

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

    image-20210222202806650

  4. 进程调用schedule,比如call sleep

image-20210222202819484

### 1.5 context 切换

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

image-20210222203919111

存储当前进程的状态

image-20200614105626821

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啦

image-20200614105912746

GDT(global description table)全局描述符

LDT(local description table)局部描述符

如果只是进程切换,那根据TSS就可以呀,为什么还需要GDT和LDT呢?

image-20200614112017725

问题

  1. 内核有几个进程?
  2. 为什么有了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文中提到,为了使用段,必须创建段描述符。

image-20200617214742752

参考文献

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