从上帝视角看OS进程调度

下面所述以Linux 0.1.1源码为例(Linux初期版本源码1w多行,推荐阅读源码,或看相关书籍 Linux内核(v0.1.2)注释

相关部件:

Linux OS

CPU:有自己的频率,通常很高(如笔者当前机器的CPU频率为1.3GHZ,即其寄存器的操作延迟为0.8ns左右),因此可以在1秒内做非常多的事。

可编程定时器/计数器Timer:有自己的频率,Linux源码中定为100,即 10ms 触发一次。OS内的很多时间计数以此为单位,如OS滴答数的单位、进程调度的时间片的单位都是Timer的周期。

背景:

进程是OS资源分配的基本单位、线程是CPU调度的基本单位。但在Linux内核中实际上没有线程概念,我们所说的线程在Linux内核中是轻量级进程LWP,其数据就结构表示与进程一样,从这角度看,内核中就是进程(或称任务)调度。

struct task_struct {
    long state; 
    long counter;
    long priority;
    struct tss_struct tss;
};
Linux中任务的数据结构表示

OS进程调度就是OS选择一个进程让它获得CPU执行权的过程。进程获得CPU执行权后就可以执行其代码了,但不是无限执行——会由OS设置一个可执行时长(称为时间片或时间计数器),通常是Timer周期的倍数。

CPU收到中断请求时会从固定的地址去查找中断向量表,并根据中断请求号从表中取得对应的中断处理函数执行。

对于不同的OS,中断向量表内容、同一个中断号对应的中断处理函数都可能不同,这些是在由OS在初始化时设置的。如(sched.c): set_intr_gate(0x20, &timer_interrupt); 。

进程调度的原理概要:

Timer 每次触发时向CPU发出时间中断请求:10ms一次,中断号为20。

CPU响应时间中断请求:找到该中断号对应的中断处理函数 timer_interrupt 并执行。

该函数逻辑:若当前在执行的进程的时间片未用完则进程继续执行且时间片减1,否则调用函数 sched.c 进行进程调度。后者的逻辑:从所有就绪(runnable)状态的态进程中选择时间片counter最大者,切换到该进程执行。

选择时:若counter值都为0则包括非runnable的所有进程的counter重新赋值 counter = counter/2 + priority,然后重新选进程。

切换:类似于函数调用,从CPU寄存器获取当前进程上下文信息并保存起来(如保存到该进程数据结构)、新进程上下文信息设置到CPU寄存器。并改变两进程的状态。

OS初始化时会创建一个空闲任务的进程作为系统第一个进程(该进程不做具体逻辑操作,本质上是在死循环地调用低功耗的CPU halt 指令),Linux下为0号进程、Windows下为“系统空闲进程”。当OS中没有任何用户操作(敲键盘、网络数据传输等)时,该进程被选中给与CPU执行权的概率大增,因此CPU的执行时间大部分花在这个进程上。参阅 CPU空闲时在干嘛

可见:

进程调度的始驱动力就是Timer,OS巧妙地利用了Timer、CPU、时间中断来定期地进行进程调度,这个“定期”就是Timer的周期长。

操作系统本质上是一个由中断来驱动的程序,它创建了第一个进程,其他进程都是直接或间接由该进程创建产生的。该程序执行后的主体是第一个进程的死循环,通过中断驱动进程调度来切换到其他进程执行。

上述过程是在V0.1.1源码中的原理,如今版本可能已经差别较大(如选择进程更加复杂而不是简单地依赖时间片等),但万变不离其宗,整个股价和流程相通。

详情推荐参阅:上帝视角看进程调度—公众号地并发编程Linux内核(v1.2.0)注释

原文地址:https://www.cnblogs.com/z-sm/p/15200785.html