进程管理1「Data structures and PID」
进程管理系列文章将主要关注Linux内核为了描述任务「task」所使用的各种数据结构。除此之外,还会阐述内核是如何管理用于标识任务的PID以及如何创建和终止任务。该系列文章主要涉及如下内容:
- 任务涉及的三个数据结构:
struct task_struct
、struct thread_info
、struct thread_struct
- PID分配流程以及用于标识任务的相关数据结构
- 任务在整个生命周期中涉及的执行状态以及相关的API
- 指示任务重要性的优先级「priority」
- 任务创建和终止的流程
- swapper任务的初始化过程
本文主要关注前两点:三个重要的数据结构和PID。
进程调度3「CFS-Completely Fair Scheduling」
CFS是内核使用的一种调度器或调度类,它主要负责处理三种调度策略:SCHED_NORMAL、SCHED_BATCH和SCHED_IDLE。从上一篇文章 可知,调度器的核心在挑选下一个运行的进程时有可能会遍历所有的调度类别。实际上系统大多数进程通常都是CFS调度类负责处理的,因此为了优化下一个进程的挑选调度器核心会先判断当前进程是否采用了CFS调度策略,若是,则直接调用CFS代码来挑选下一个进程,若不是或CFS代码未能挑选到一个合适的进程,则会调用各个调度类的挑选函数来寻找一个合适的进程。若CFS代码寻找到了合适的下一个运行的进程,则直接返回该进程的实例而不会再遍历调度类。本文将主要关注下列的CFS活动和行为:
- 将一个任务插入运行队列
- 从运行队列中挑选一个合适的任务
- 从运行队列中移除一个任务
- 处理周期滴答「periodic ticks」
- 管理调度实体「scheduling entities」的运行时间
- 管理时间片「time slices」
进程调度2「Core」
Linux内核是一个支持多任务「multitasking」的操作系统,可以同时执行多个任务,但是CPU在某个时刻只能运行一个任务,因此为了使Linux内核真的看起来像多个任务在同时运行,调度器将连续不断地替换当前正在运行的任务,这个替换的过程被称为任务切换「task switch」或者调度「scheduling」。
内核通过处理定时器「timer」中断能够周期循环地检查当前进程的need_resched标志是否设置以及时间片「time slice」是否用尽,若当前进程设置了need_resched标志或者用完了时间片,那么将发生任务切换,这个过程就是利用定时器「timer」中断处理函数实现的周期调度「periodic scheduling 」。除此之外,内核还有很多其他代码会随时检查当前进程的need_resched标志是否设置,若need_resched标志被设置了,则将进行任务切换,这个过程属于非周期调度。在内核中这两种调度能够保证调度器尽可能经常地进行任务切换,以致于所有现存的进程能够尽可能公平地被执行。与周期调度不同,内核常见的非周期调度大致可分两种:被动式非周期调度和主动式非周期调度,比如如下所述的两个场景:
- 进程相关事件发生时会调用设置need_resched标志的函数,比如一个进程的状态发生了变化(进程唤醒事件出现、进程优先级改变)或者当前进程主动让出「yield」CPU执行时间,这只是触发了调度请求「scheduling request」,而真正的任务切换却发生于调用schedule()的函数,或检测need_resched标志是否设置的函数。
- 与前者不同的是有些代码(sleep、blocking API、lock API)会使当前进程进入睡眠状而不能继续执行,则会直接显式调用核心调度函数__schedule()进行任务切换。
下面给出了触发任务切换的调度点「scheduling point」,它们将检查need_resched标志是否被设置,如果设置的话,则会调用核心调度函数进行任务切换。
- 在中断处理完成后
- 在系统调用处理完成后
- 在使能内核抢占后
总之,本文主要分析使用timer中断实现的周期调度、非周期调度涉及的调度请求「scheduling request」以及任务切换。
进程调度1「Key Concepts」
本文主要关注内核中用于调度进程的各种机制,以及为了实现这些机制而引入的各种概念和数据结构。首先了解调度器「scheduler」的如下关键概念,有了这些基础才能深入理解调度器的各种不同活动和行为。
- 刻画进程重要性的负载权重「load weight」
- 进程的虚拟运行时间「virtual runtime」
- 进程时间片「time slice」和CFS运行队列的调度延迟「scheduling latency」
- 运行队列「run queue」、CFS运行队列和红黑树「Red Black Tree」
- 组调度「group scheduling」涉及的任务组「task group」和调度实体「scheduling entity」
- 调度类别「scheduling class」和调度策略「scheduling policy」
后续第二篇文章会分析调度器的核心部分,也就是常用的调度代码。涉及的知识点如下:
- 基于定时器「timer」中断的周期调度「periodic scheduling 」
- 非周期调度「aperiodic scheduling」
- 触发任务切换的调度点「scheduling points」
- 设置need_resched标志的调度请求「scheduling request」
- 调度核心函数__schedule()
- 进程唤醒函数
在研究完调度器核心之后,最后一篇文章将分析CFS「Completely Fair Scheduling)」调度器,它负责处理SCHED_NORMAL、SCHED_BATCH和SCHED_IDLE三种调度策略。涉及的内容如下:
- 将一个进程插入运行队列
- 从运行队列中挑选一个合适的进程
- 从运行队列中移除一个进程
- 处理周期滴答「periodic ticks」
- 管理调度实体「scheduling entities」的运行时间
- 管理时间片「time slices」
本文在最后会分析sched_init()函数,它会完成调度器正常工作所要求的初始化任务。
ARMv8体系架构概述
为了更深入地剖析kernel,了解ARMv8体系架构是非常必要的。本文尽量全面地概述ARMv8体系架构涉及的基础知识。Linux内核的绝大部分代码是与体系架构无关的「independent」,但是还存在大量的代码「spinlock、cache管理」都是与体系架构相关的「dependent」,这些代码不仅会在启动时被运行而且在系统正常运行期间也会被经常调用执行。不仅仅Linux内核牵涉了很多的体系架构知识,而且编译器(比如LLVM「Low Level Virtual Machine」)以及与内核紧密相关的领域(比如虚拟机)也都涉及了大量体系架构方面的知识,为了使系统或软件最大地利用硬件的特点和性能,学习了解体系架构的知识就显得格外重要。尽管ARMv8设备已被广泛地应用到各个领域,但是关于ARMv8体系架构的书籍不是太多,还比较零散,因此我希望本文能够尽量全面的介绍ARMv8体系架构方面的知识点,使我们在研究Linux内核代码不会因为体系架构知识短缺而困惑。