Linux进程调度

December 29th, 2013 by JasonLe's Tech Leave a reply »

进程分为I/O消耗性和处理器消耗性进程。Linux为了保证交互式应用,对进程响应做了优化(缩短响应时间),倾向于优先调度i/o消耗性进程。
Linux实现了一种基于动态优先级的调度方法,一开始,先设置基本的优先级,然而它允许调度程序根据需要加减优先级,如果进程在IO等待上小号的时间杜宇其运行时间,那么该进程属于IO消耗性进程。动态优先级会动态提高,如果一个进程的全部时间片一下子被耗尽,那么该进程属于处理器消耗型进程,动态优先级会被动态降低。
nice值-20到+19 默认值是0,nice值越大优先级越低。nice值为-20的进程获得的时间片最长,nice为19获得时间片最短。
另外,如果我们打上了RT-patch,打开top,nice列的值是rt,这类的优先级高于普通进程。

 
在我研究的linux-3.11.8中调度模块位置是kernel/sched/core.c
调度程序中最基本的数据结构就是runqueue,定义在kernel/sche.h中line 400-528

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;
	#define CPU_LOAD_IDX_MAX 5
	unsigned long cpu_load[CPU_LOAD_IDX_MAX];
	unsigned long last_load_update_tick;
#ifdef CONFIG_NO_HZ_COMMON
	u64 nohz_stamp;
	unsigned long nohz_flags;
#endif
#ifdef CONFIG_NO_HZ_FULL
	unsigned long last_sched_tick;
#endif
	int skip_clock_update;

	/* capture load from *all* tasks on this cpu: */
	struct load_weight load;
	unsigned long nr_load_updates;
	u64 nr_switches;

	struct cfs_rq 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:
	 */
	unsigned long nr_uninterruptible;

	struct task_struct *curr, *idle, *stop;
	unsigned long next_balance;
	struct mm_struct *prev_mm;

	u64 clock;
	u64 clock_task;

	atomic_t nr_iowait;

#ifdef CONFIG_SMP
	struct root_domain *rd;
	struct sched_domain *sd;

	unsigned long cpu_power;

	unsigned char idle_balance;
	/* For active balancing */
	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
	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

	struct sched_avg avg;
};

其中cpu_rq(processor)用于返回给定处理器可执行队列,this_rq()返回当前处理器的可自行队列。在调度中对调度队列进行操作,要使用task_rq_lock()与task_rq_unlock()的配合,防止特定任务在运行。在调度中,一个cpu至少对应两个队列,一个是可运行队列(active),另一个是过期队列(expire)。同时对二者上锁解锁,就要涉及到顺序的问题,否则会出现死锁。
linux中提供了double_rq_lock()与double_rq_unlock()完成上述操作。
当然我们通过看上面的rq结构信息,发现有一个unsigned int nr_running;主要保存该队列中的运行进程的数目。

过去的OS进行进程时间片计算的时候,使用for结构计算每个进程的优先级,并重新计算时间片。这样会耗费相当长的时间。现在由于存在两个队列,一个可运行,另一个是过期队列。只要过期就把进程移到过期队列,然后把时间片重置,如果nr_running为0就把expire值赋给rq_running.上代码!

struct rt_prio_array {                                                          
    DECLARE_BITMAP(bitmap, MAX_RT_PRIO+1); /* include 1 bit for delimiter */
    struct list_head queue[MAX_RT_PRIO];
};
struct rt_prio_array *array = rq->active
if(!array->nr_active)
{
     rq->active = rq->expire;
     rq->expire = array
}//等于两个队列指向的队列互换,达到O(1)的时间复杂度。

选取下一个进程,一般使用空间换时间的方式,优先级放在一个总数是140bitmap中,使用sched_find_first_bit()来找到bitmap中第一个置为一的进程,加入到运行队列。
在计算时间片上,每个进程拥有初始的优先级nice值,然后再通过动态优先级调整算法,在struct_rq结构体里有一个struct sched_avg avg;它会根据曾经睡眠的状况来判断是IO密集型还是计算密集型。

但是再怎么调整也是有限度的,下面这个表是每个时间片的极限
Unnamed QQ Screenshot20131229201046
对于交互性程序,如果nice值为-19,进程会很容易加到运行队列中准备调度。

另外休眠有两种状态:TASK_INTERRUPTIBLE与TASK_UNINTERRUPTBLE区别在于是否忽略SIG
在新版的linux调度程序文件被放置在了kernel/sched/proc.c中这一块还没来的及细看,放假前要把这个文件过一遍
调度的总过程在kernel/sched/core.c 2389-2473
实时调度中主要使用FIFO与RR算法,算法思想不再赘述,我们在top中看到实时的优先级是0-MAX_RT_PRIO(100)-1,对应的非实时进程是MAX_RT_PRIO到MAX_RT_PRIO+40