Read the fucking source code!
--By 鲁迅A picture is worth a thousand words.
--By 高尔基说明:算法
从这篇文章开始,将开始Linux调度器的系列研究了。
本文也会从一些基础的概念及数据结构入手,先打造一个粗略的轮廓,后续的文章将逐渐深刻。缓存
struct task_struct
结构来进行抽象描述。Linux内核使用task_struct
结构来抽象,该结构包含了进程的各种信息及所拥有的资源,好比进程的状态、打开的文件、地址空间信息、信号资源等等。task_struct
结构很复杂,下边只针对与调度相关的某些字段进行介绍。数据结构
struct task_struct { /* ... */ /* 进程状态 */ volatile long state; /* 调度优先级相关,策略相关 */ int prio; int static_prio; int normal_prio; unsigned int rt_priority; unsigned int policy; /* 调度类,调度实体相关,任务组相关等 */ const struct sched_class *sched_class; struct sched_entity se; struct sched_rt_entity rt; #ifdef CONFIG_CGROUP_SCHED struct task_group *sched_task_group; #endif struct sched_dl_entity dl; /* 进程之间的关系相关 */ /* Real parent process: */ struct task_struct __rcu *real_parent; /* Recipient of SIGCHLD, wait4() reports: */ struct task_struct __rcu *parent; /* * Children/sibling form the list of natural children: */ struct list_head children; struct list_head sibling; struct task_struct *group_leader; /* ... */ }
就绪态
和运行态
对应的都是TASK_RUNNING
标志位,就绪态
表示进程正处在队列中,还没有被调度;运行态
则表示进程正在CPU上运行;内核中主要的状态字段定义以下函数
/* Used in tsk->state: */ #define TASK_RUNNING 0x0000 #define TASK_INTERRUPTIBLE 0x0001 #define TASK_UNINTERRUPTIBLE 0x0002 /* Used in tsk->exit_state: */ #define EXIT_DEAD 0x0010 #define EXIT_ZOMBIE 0x0020 #define EXIT_TRACE (EXIT_ZOMBIE | EXIT_DEAD) /* Used in tsk->state again: */ #define TASK_PARKED 0x0040 #define TASK_DEAD 0x0080 #define TASK_WAKEKILL 0x0100 #define TASK_WAKING 0x0200 #define TASK_NOLOAD 0x0400 #define TASK_NEW 0x0800 #define TASK_STATE_MAX 0x1000 /* Convenience macros for the sake of set_current_state: */ #define TASK_KILLABLE (TASK_WAKEKILL | TASK_UNINTERRUPTIBLE) #define TASK_STOPPED (TASK_WAKEKILL | __TASK_STOPPED) #define TASK_TRACED (TASK_WAKEKILL | __TASK_TRACED) #define TASK_IDLE (TASK_UNINTERRUPTIBLE | TASK_NOLOAD)
内核默认提供了5个调度器,Linux内核使用struct sched_class
来对调度器进行抽象:工具
Stop调度器, stop_sched_class
:优先级最高的调度类,能够抢占其余全部进程,不能被其余进程抢占;Deadline调度器, dl_sched_class
:使用红黑树,把进程按照绝对截止期限进行排序,选择最小进程进行调度运行;RT调度器, rt_sched_class
:实时调度器,为每一个优先级维护一个队列;CFS调度器, cfs_sched_class
:彻底公平调度器,采用彻底公平调度算法,引入虚拟运行时间概念;IDLE-Task调度器, idle_sched_class
:空闲调度器,每一个CPU都会有一个idle线程,当没有其余进程能够调度时,调度运行idle线程;Linux内核提供了一些调度策略供用户程序来选择调度器,其中Stop调度器
和IDLE-Task调度器
,仅由内核使用,用户没法进行选择:性能
SCHED_DEADLINE
:限期进程调度策略,使task选择Deadline调度器
来调度运行;SCHED_RR
:实时进程调度策略,时间片轮转,进程用完时间片后加入优先级对应运行队列的尾部,把CPU让给同优先级的其余进程;SCHED_FIFO
:实时进程调度策略,先进先出调度没有时间片,没有更高优先级的状况下,只能等待主动让出CPU;SCHED_NORMAL
:普通进程调度策略,使task选择CFS调度器
来调度运行;SCHED_BATCH
:普通进程调度策略,批量处理,使task选择CFS调度器
来调度运行;SCHED_IDLE
:普通进程调度策略,使task以最低优先级选择CFS调度器
来调度运行;Linux内核使用struct rq
结构来描述运行队列,关键字段以下:ui
/* * This is the main, per-CPU runqueue data structure. * * Locking rule: those places that want to lock multiple runqueues * (such as the load balancing or the thread migration code), lock * acquire operations must be ordered by ascending &runqueue. */ struct rq { /* runqueue lock: */ raw_spinlock_t lock; /* * nr_running and cpu_load should be in the same cacheline because * remote CPUs use both these fields when doing load calculation. */ unsigned int nr_running; /* 三个调度队列:CFS调度,RT调度,DL调度 */ struct cfs_rq cfs; struct rt_rq rt; struct dl_rq dl; /* stop指向迁移内核线程, idle指向空闲内核线程 */ struct task_struct *curr, *idle, *stop; /* ... */ }
task_group
后,调度器的调度对象不单单是进程了,Linux内核抽象出了sched_entity/sched_rt_entity/sched_dl_entity
描述调度实体,调度实体能够是进程或task_group
;struct task_group
来描述任务组,任务组在每一个CPU上都会维护一个CFS调度实体、CFS运行队列,RT调度实体,RT运行队列
;Linux内核使用struct task_group
来描述任务组,关键的字段以下:this
/* task group related information */ struct task_group { /* ... */ /* 为每一个CPU都分配一个CFS调度实体和CFS运行队列 */ #ifdef CONFIG_FAIR_GROUP_SCHED /* schedulable entities of this group on each cpu */ struct sched_entity **se; /* runqueue "owned" by this group on each cpu */ struct cfs_rq **cfs_rq; unsigned long shares; #endif /* 为每一个CPU都分配一个RT调度实体和RT运行队列 */ #ifdef CONFIG_RT_GROUP_SCHED struct sched_rt_entity **rt_se; struct rt_rq **rt_rq; struct rt_bandwidth rt_bandwidth; #endif /* task_group之间的组织关系 */ struct rcu_head rcu; struct list_head list; struct task_group *parent; struct list_head siblings; struct list_head children; /* ... */ };
调度程序依靠几个函数来完成调度工做的,下边将介绍几个关键的函数。操作系统
schedule()
schedule()
函数,是进程调度的核心函数,大致的流程如上图所示。pick_next_task
函数来实现的,不一样的调度器实现的方法不同;进程的替换是经过context_switch()
来完成切换的,具体的细节后续的文章再深刻分析。schedule_tick()
schedule_tick()
函数;_TIF_NEED_RESCHED
);schedule()
调度;hrtick()
wake_up_process()
wake_up_process()
函数,被唤醒的进程可能抢占当前的进程;上述讲到的几个函数都是经常使用于调度时调用。此外,在建立新进程时,或是在内核抢占时,也会出现一些调度点。线程
本文只是粗略的介绍了一个大概,后续将针对某些模块进行更加深刻的分析,敬请期待。