要对进程进行调度,那么进程中就需要保存对应的信息:
- prio和normal_prio:动态优先级;
- static_prio:静态优先级,在进程创建的时候设置的,在运行的时候可以改变;
- rt_priority:实时进程的优先级;
- sched_class:进程所属的调度类;
- sched_entity:调度器不只是能调度进程,还能调度进程组;
- policy:该进程应用的调度策略;
- cpus_allow:用一个位域保存进程可以在哪些处理器上执行;
- run_list和time_slice:用于循环实时调度器,保存各进程的一个运行表和进程可使用CPU的剩余时间段;
linux中进程调度类型有如下:
sched_normal: 普通分时进程,(主要分析CFS 完全公平调度)
sheed_RR:时间片轮转的实时进程;
sched_fifo:先进先出的实时进程;
普通进程的调度:
新进程总是继承父进程的静态优先级;
nice()系统调用可以改变进程的优先级;内核用从100到139的数表示普通进程的静态优先级;数字越小优先级越高;
每个进程都有一个时间片。具有较高静态静态优先级的普通进程可以获得较大的cpu时间片,为了防止其余进程饥饿,一个高优先级进程时间片用完后应该被没有用完时间片的进程所替换;
活动进程:
这些进程时间片没有用完,允许继续运行
过期进程:
进程时间片被用完,被禁止运行,知道所有活动进程都运行完最后过期,
实时进程的调度:
每个实时进程都和一个实时优先级相关,实时优先级从1到99,调度器总是让优先级高的先运行,实时进程都是活动进程
实时进程被取代的情况:
进程被高优先级进程抢
进程阻塞并进入睡眠
进程停止或者被杀死
通过系统调用放弃cpu
进程调用中主要有:运行实体、调度类、运行队列。
调度队列:
runqueue为每CPU变量,每个cpu都拥有一个;
/*
* 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; 记录cpu的rq中执行task的数量
#define CPU_LOAD_IDX_MAX 5
/*用以表示处理器的负载,在每个处理器的rq中
都会有对应到该处理器的cpu_load参数配置,在每次
处理器触发scheduler tick时,都会呼叫函数
update_cpu_load_active,进行cpu_load的更新。在系统初始化的时候
会呼叫函数sched_init把rq的cpu_load array初始化为0.
了解他的更新方式最好的方式是通过函数update_cpu_load,公式如下澹?
cpu_load[0]会直接等待rq中load.weight的值。
cpu_load[1]=(cpu_load[1]*(2-1)+cpu_load[0])/2
cpu_load[2]=(cpu_load[2]*(4-1)+cpu_load[0])/4
cpu_load[3]=(cpu_load[3]*(8-1)+cpu_load[0])/8
cpu_load[4]=(cpu_load[4]*(16-1)+cpu_load[0]/16
呼叫函数this_cpu_load时,所返回的cpu load值是cpu_load[0]
而在进行cpu blance或migration时,就会呼叫函数
source_load target_load取得对该处理器cpu_load index值,
来进行计算*/ ?????仍有疑问
unsigned long cpu_load[CPU_LOAD_IDX_MAX];
unsigned long last_load_update_tick;
#ifdef CONFIG_NO_HZ
u64 nohz_stamp;
unsigned long nohz_flags;
#endif
int skip_clock_update;
/* capture load from *all* tasks on this cpu: */
/*load->weight值,会是目前所执行的schedule entity的
load->weight的总和,也就是说rq的load->weight越高,
也表示所负责的排程单元load->weight总和越高
表示处理器所负荷的执行单元也越重*/
struct load_weight load;
/*在每次scheduler tick中呼叫update_cpu_load时,
这个值就增加一,可以用来反馈目前cpu load更新的次数*/
unsigned long nr_load_updates;
u64 nr_switches;//表示cpu进行switch_context的次数 在schedule中累加计数;
struct cfs_rq cfs; //公平调度cfs
struct rt_rq rt; //实时调度
#ifdef CONFIG_FAIR_GROUP_SCHED
/* list of leaf cfs_rq on this cpu: */
struct list_head leaf_cfs_rq_list;
#ifdef CONFIG_SMP
unsigned long h_load_throttle;
#endif /* CONFIG_SMP */
#endif /* CONFIG_FAIR_GROUP_SCHED */
#ifdef CONFIG_RT_GROUP_SCHED
struct list_head leaf_rt_rq_list;
#endif
/*
* This is part of a global counter where only the total sum
* over all CPUs matters. A task can increase this counter on
* one CPU and if it got migrated afterwards it may decrease
* it on another CPU. Always updated under the runqueue lock:
*/
/*一般来说,linux kernel 的task状态可以为TASK_RUNNING
TASK_INTERRUPTIBLE(sleep),
TASK_UNINTERRUPTIBLE(Deactivate Task,此时Task会从rq中
移除)或TASK_STOPPED.
透过这个变数会统计目前rq中有多少task属于
TASK_UNINTERRUPTIBLE的状态。当呼叫函数
active_task时,会把nr_uninterruptible值减一,并透过 该函数
enqueue_task把对应的task依据所在的scheduling class
放在 对应的rq中,并把目前rq中nr_running值加一*/
unsigned long nr_uninterruptible;
/*curr:指向目前处理器正在执行的task;
idle:指向属于idle-task scheduling class 的idle task;
stop:指向目前最高等级属于stop-task scheduling class
的task;*/
struct task_struct *curr, *idle, *stop;
----------------/*基于处理器的jiffies值,用以记录下次进行处理器
balancing 的时间点*/
unsigned long next_balance;
/*用以存储context-switch发生时,前一个task的memory management
结构并可用在函数finish_task_switch中,透过函数mmdrop释放前一个
task的记忆体资源*/
struct mm_struct *prev_mm;
/*用以记录目前rq的clock值,基本上该值会等于透过sched_clock_cpu
(cpu_of(rq))的回传值,并会在每次呼叫scheduler_tick时透过
函数update_rq_clock更新目前rq clock值。
在实作部分,函数sched_clock_cpu会透过sched_clock_local或
ched_clock_remote取得对应的sched_clock_data,而处理的sched_clock_data
值,会透过函数sched_clock_tick在每次呼叫scheduler_tick时进行更新;
*/
u64 clock;
u64 clock_task;
/*用以记录目前rq中有多少task处于等待i/o的sleep状态
在实际的使用上,例如当driver接受来自task的调用,但处于等待i/o
回复的阶段时,为了充分利用处理器的执行资源,这时
就可以在driver中呼叫函数io_schedule,此时
就会把目前rq中的nr_iowait加一,并设定目前task的io_wait为1
然后触发scheduling 让其他task有机会可以得到处理器执行时间*/
atomic_t nr_iowait;
#ifdef CONFIG_SMP
/*root domain是基于多核心架构下的机制,
会由rq结构记住目前采用的root domain,其中包括了
目前的cpu mask(包括span,online rt overload), reference count 跟cpupri
当root domain有被rq参考到时,refcount 就加一,反之就减一。而cpu
mask span表示rq可挂上的cpu mask,noline为rq目前已经排程的
cpu mask cpu上执行real-time task.可以参考函数pull_rt_task,当一个rq中属于
real-time的task已经执行完毕,就会透过函数pull_rt_task从该
rq中属于rto_mask cpu mask 可以执行的处理器上,找出是否有一个处理器
有大于一个以上的real-time task,若有就会转到目前这个执行完成
real-time task 的处理器上
而cpupri不同于Task本身有区分140個(0-139)
Task Priority (0-99為RT Priority 而 100-139為Nice值 -20-19).
CPU Priority本身有102個Priority (包括,-1 為Invalid,
0為Idle,1為Normal,2-101對應到Real-Time Priority 0-99).
參考函式convert_prio, Task Priority如果是 140就會對應到
CPU Idle,如果是大於等於100就會對應到CPU Normal,
若是Task Priority介於0-99之間,就會對應到CPU Real-Time Priority 101-2之間.)
在實際的操作上,例如可以透過函式cpupri_find
帶入一個要插入的Real-Time Task,此時就會依據cpupri中
pri_to_cpu選擇一個目前執行Real-Time Task且該Task
的優先級比目前要插入的Task更低的處理器,
並透過CPU Mask(lowest_mask)返回目前可以選擇的處理器Mask.
實作的部份可以參考檔案kernel/sched_cpupri.c.
在初始化的過程中,會透過函式sched_init呼叫函式init_defrootdomain,
對Root Domain與 CPU Priority機制進行初始化.
*/
struct root_domain *rd;
/*Schedule Domain是基於多核心架構下的機制.
每個處理器都會有一個基礎的Scheduling Domain,
Scheduling Domain可以有階層性的架構,透過parent
可以找到上一層的Domain,或是透過child找到
下一層的 Domain (NULL表示結尾.).並可透過span
栏位,表示這個Domain所能涵蓋的處理器範圍.
通常Base Domain會涵蓋系統中所有處理器的個數,
而Child Domain所能涵蓋的處理器個數不超過它的
Parent Domain. 而當在進行Scheduling Domain 中的Task Balance
時,就會以該Domain所能涵蓋的處理器為最大範圍.
同時,每個Schedule Domain都會包括一個或一個以上的
CPU Groups (結構為struct sched_group),並透過next變數把
CPU Groups串連在一起(成為一個單向的Circular linked list),
每個CPU Group都會有變數cpumask來定义這個CPU Group
所涵蓋的處理器範圍.並且CPU Group所包括的處理器
範圍,必需涵蓋在所屬的Schedule Domain處理器範圍中.
當進行Scheduling Domain的Balancing時,會以其下的CPU Groups
為單位,根據cpu_power (會是該Group所涵蓋的處理器
Tasks Loading的總和)來比較不同的CPU Groups的負荷,
以進行Tasks的移動,達到Balancing的目的.
在有支援SMP的架構下,會在函式sched_init中,呼叫open_softirq,
註冊 SCHED_SOFTIRQ Software IRQ与其对应的 Callback函式
run_rebalance_domains. 並會在每次呼叫函式scheduler_tick時,
透過函式trigger_load_balance确认是否目前的jiffies值已經
大於RunQueue下一次要觸發Load Balance的next_balance時間值,
並透過函式raise_softirq觸發SCHED_SOFTIRQ Software IRQ.
在Software IRQ觸發後,就會呼叫函式run_rebalance_domains,
並在函式rebalance_domains中,進行后续處理器上的
Scheduling Domain Load Balance動作.
有關Scheduling Domain進一步的內容,也可以參考
Linux Kernel文件 Documentation/scheduler/sched-domains.txt.
*/
struct sched_domain *sd;
unsigned long cpu_power;
unsigned char idle_balance;
/* For active balancing */
/*若這值不為0,表示會有在Schedule排程動作
結束前,要呼叫的收尾函式. (实作為inline函式
post_schedule in kernel/sched.c),目前只有Real-Time Scheduling
Class有支援這個機制(會呼叫函式has_pushable_tasks
in kernel/sched_rt.c).*/
int post_schedule;
int active_balance;
int push_cpu;
struct cpu_stop_work active_balance_work;
/* cpu of this runqueue: */
int cpu;
int online;
struct list_head cfs_tasks;
u64 rt_avg;
u64 age_stamp;
u64 idle_stamp;
u64 avg_idle;
#endif
#ifdef CONFIG_IRQ_TIME_ACCOUNTING
u64 prev_irq_time;
#endif
#ifdef CONFIG_PARAVIRT
u64 prev_steal_time;
#endif
#ifdef CONFIG_PARAVIRT_TIME_ACCOUNTING
u64 prev_steal_time_rq;
#endif
/* calc_load related fields */
unsigned long calc_load_update;
long calc_load_active;
#ifdef CONFIG_SCHED_HRTICK
#ifdef CONFIG_SMP
/*在函式init_rq_hrtick初始化RunQueue High-Resolution
Tick時,此值預設為0.
在函式hrtick_start中,會判斷目前觸發的RunQueue
跟目前處理器所使用的RunQueue是否一致,
若是,就直接呼叫函式hrtimer_restart,反之就會
依據RunQueue中hrtick_csd_pending的值,如果
hrtick_csd_pending為0,就會透過函式
__smp_call_function_single讓RunQueue所在的另一個
處理器執行rq->hrtick_csd.func 所指到的函式
__hrtick_start. 並等待該處理器執行完畢後,
才重新把hrtick_csd_pending設定為1.
也就是說, RunQueue的hrtick_csd_pending是用來作為
SMP架構下,由處理器A觸發處理器B執行
_hrtick_start函式的一個保護機制.而有關在
SMP下如何由一個處理器觸發另一個處理器
執行函式的機制,可以參考kernel/smp.c中
相關smp_call_function_xxxxxxx的實作.s*/
int hrtick_csd_pending;
struct call_single_data hrtick_csd;
#endif
struct hrtimer hrtick_timer;
#endif
#ifdef CONFIG_SCHEDSTATS
/* latency stats */
struct sched_info rq_sched_info;
unsigned long long rq_cpu_time;
/* could above be rq->cfs_rq.exec_clock + rq->rt_rq.rt_runtime ? */
/* sys_sched_yield() stats */
unsigned int yld_count;
/* schedule() stats */
unsigned int sched_count;
unsigned int sched_goidle;
/* try_to_wake_up() stats */
unsigned int ttwu_count;
unsigned int ttwu_local;
#endif
#ifdef CONFIG_SMP
struct llist_head wake_list;
#endif
};
调度类:sched_class
struct sched_class {
const struct sched_class *next;指向下一个schedule_class
当task 可以运行时,就会回调enqueue_task,将task放在runqueue RBtree中,同时调用inc_nr_running将runqueue中的nr_runqueue的值加一
void (*enqueue_task) (struct rq *rq, struct task_struct *p, int flags);
void (*dequeue_task) (struct rq *rq, struct task_struct *p, int flags);
void (*yield_task) (struct rq *rq);
bool (*yield_to_task) (struct rq *rq, struct task_struct *p, bool preempt);
void (*check_preempt_curr) (struct rq *rq, struct task_struct *p, int flags);
struct task_struct * (*pick_next_task) (struct rq *rq);
void (*put_prev_task) (struct rq *rq, struct task_struct *p);
#ifdef CONFIG_SMP
int (*select_task_rq)(struct task_struct *p, int sd_flag, int flags);
void (*pre_schedule) (struct rq *this_rq, struct task_struct *task);
void (*post_schedule) (struct rq *this_rq);
void (*task_waking) (struct task_struct *task);
void (*task_woken) (struct rq *this_rq, struct task_struct *task);
void (*set_cpus_allowed)(struct task_struct *p,
const struct cpumask *newmask);
void (*rq_online)(struct rq *rq);
void (*rq_offline)(struct rq *rq);
#endif
void (*set_curr_task) (struct rq *rq);
void (*task_tick) (struct rq *rq, struct task_struct *p, int queued);
void (*task_fork) (struct task_struct *p);
void (*switched_from) (struct rq *this_rq, struct task_struct *task);
void (*switched_to) (struct rq *this_rq, struct task_struct *task);
void (*prio_changed) (struct rq *this_rq, struct task_struct *task,
int oldprio);
unsigned int (*get_rr_interval) (struct rq *rq,
struct task_struct *task);
#ifdef CONFIG_FAIR_GROUP_SCHED
void (*task_move_group) (struct task_struct *p, int on_rq);
#endif
};
struct load_weight {
unsigned long weight, inv_weight;
};
调度类中:CFS和实时调度使用不同的调度队列,对于CFS调度,使用红黑树,对于实时调度使用链表;
调度实体:调度器可以操作比进程一般的实体,因此需要如下结构来表示:
struct sched_entity {
struct load_weight load; /* for load-balancing */
struct rb_node run_node;
struct list_head group_node;
unsigned int on_rq;
u64 exec_start;
u64 sum_exec_runtime;
u64 vruntime;
u64 prev_sum_exec_runtime;
u64 nr_migrations;
#ifdef CONFIG_SCHEDSTATS
struct sched_statistics statistics;
#endif
#ifdef CONFIG_FAIR_GROUP_SCHED
struct sched_entity *parent;
/* rq on which this entity is (to be) queued: */
struct cfs_rq *cfs_rq;
/* rq "owned" by this entity/group: */
struct cfs_rq *my_q;
#endif
};
struct sched_rt_entity {
struct list_head run_list;
unsigned long timeout;
unsigned int time_slice;
struct sched_rt_entity *back;
#ifdef CONFIG_RT_GROUP_SCHED
struct sched_rt_entity *parent;
/* rq on which this entity is (to be) queued: */
struct rt_rq *rt_rq;
/* rq "owned" by this entity/group: */
struct rt_rq *my_q;
#endif
};
进程调度优先级:
负荷:
进程的优先级不仅由优先级指定,而且还要考虑保存在task_struct中的se.load的负荷权重。set_load_weight负责根据进程类型及其静态优先级计算负荷权重。
负荷权重
struct load_weight {unsigned long weight, inv_weight;
};