现在的位置: 首页 > 综合 > 正文

Linux进程调度

2018年10月02日 ⁄ 综合 ⁄ 共 5604字 ⁄ 字号 评论关闭

一、策略

1.1 IO消耗性进程和CPU消耗性进程

        IO消耗性进程:

                大部分时间用来提交IO请求或等待IO请求。

                缩短运行时间,提高运行频率(优先调度)。

        CPU消耗性进程:

                大部分时间用在执行代码上。

                延长运行时间,降低运行频率。

1.2 进程优先级

        优先级高的先运行,低的后运行,相同优先级的轮流运行。

        动态优先级。

        nice值: 

                范围-20到19 ,默认为0 。

                值越小优先级越高。

        实时优先级:

                范围0到99。

                优先级高于普通进程。

1.3 时间片

        太长太短都不好。

        进程不一定一次用完它所有的时间片。

        所有进程时间片用完后,重新计算时间片。

1.4 进程抢占

        进程切换到TASK_RUNNING状态,会检查其优先级,决定是否抢占。

        时间片为0的进程,会被抢占。

二、Linux调度算法

2.1 可执行队列

        运行队列struct runqueue,每个CPU一个。

        Linux-2.6.32-220.el6.x86_64代码中的可执行队列结构体:

/*
 * 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: */
	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 long nr_running;
	#define CPU_LOAD_IDX_MAX 5
	unsigned long cpu_load[CPU_LOAD_IDX_MAX];
#ifndef __GENKSYMS__
	unsigned long last_load_update_tick;
#endif
#ifdef CONFIG_NO_HZ
#ifndef __GENKSYMS__
	unsigned char nohz_balance_kick;
#else
	unsigned long last_tick_seen;
	unsigned char in_nohz_recently;
#endif
#endif

#ifndef __GENKSYMS__
	unsigned int skip_clock_update;
#endif
	/* capture load from *all* tasks on this cpu: */
	struct load_weight load;
	unsigned long nr_load_updates;
	u64 nr_switches;
#ifdef __GENKSYMS__
	u64 nr_migrations_in;
#endif
	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;
#endif
#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;
	unsigned long next_balance;
	struct mm_struct *prev_mm;

	u64 clock;

	atomic_t nr_iowait;

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

	unsigned char idle_at_tick;
	/* For active balancing */
	int post_schedule;
	int active_balance;
	int push_cpu;
	/* cpu of this runqueue: */
	int cpu;
	int online;

	unsigned long avg_load_per_task;

	struct task_struct *migration_thread;
	struct list_head migration_queue;

	u64 rt_avg;
	u64 age_stamp;
	u64 idle_stamp;
	u64 avg_idle;
#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_switch;
	unsigned int sched_count;
	unsigned int sched_goidle;

	/* try_to_wake_up() stats */
	unsigned int ttwu_count;
	unsigned int ttwu_local;

	/* BKL stats */
	unsigned int bkl_count;
#endif
};

        运行队列相关函数:

//在<linux/sched.c>中实现

//返回给定处理器上的可执行队列。
#define cpu_rq(cpu)		(&per_cpu(runqueues, (cpu)))   
//返回当前处理器上的可执行队列。
#define this_rq()		(&__get_cpu_var(runqueues))   
//返回给定进程所在的可执行队列。
#define task_rq(p)		cpu_rq(task_cpu(p))                 

//操作可执行队列时加锁。
static struct rq *task_rq_lock(struct task_struct *p, unsigned long *flags)
static inline void task_rq_unlock(struct rq *rq, unsigned long *flags)
static struct rq *this_rq_lock(void)

//需要加2次锁时,保证加锁顺序与解锁顺序相反。
static void double_rq_lock(struct rq *rq1, struct rq *rq2)
static void double_rq_unlock(struct rq *rq1, struct rq *rq2)

2.2 优先级数组

        struct prio_array优先级数组:

                使可运行处理器的每一种优先级都包含一个相应的队列,而这些队列包含对应优先级上的可执行进程链表。

        位图:

                开始时位图所有位置0,当某个优先级的进程开始准备执行时,位图中相应的位置1。

        MAX_PRIO:

                定义了系统拥有的优先级个数。默认值为140。

        每个处理器维护2个优先级数组,一个活跃数组,一个过期数组。

2.3 重新计算时间片

        所有进程时间片都用完,再重新计算时间片,相当耗时。

        每个处理器维护2个优先级数组,一个活跃数组,一个过期数组。

2.4 schedule

        

        1. 找到活动优先级数组中第一个被设置的位(该位对应着优先级最高的活动进程);

        2. 调度程序选择这个级别链表里的头一个进程;

        3. 如果该进程不是当前进程,context_switch被调用。

2.5 计算优先级和时间片

        nice值:static_prio 

                 -20到19, 默认0 。

                用户设置不可改变。

        动态优先级:prio 

                 effective_prio()返回 在nice上+5或-5。

        动态优先级计算:

                判断一个进程是IO消耗型,还是处理器消耗型;

                主要通过休眠时间推断;

                sleep_avg 0到MAX_SLEEP_AVG 默认10毫秒。

        时间片计算:

                以静态优先级为基础计算;

                创建一个新进程时,父子进程均分时间片。

2.6 唤醒与睡眠

        睡眠:

                1. 进程把自己设置成休眠状态;

                2. 从可执行队列移出,放入等待队列;

                3. 调用schedule选择和执行一个其他进程。

        唤醒:

                1. 进程被设置成休眠状态;

                2. 从等待队列移出,放入可执行队列。

        休眠通过等待队列wait_queue_header_t处理。

2.7 负载平衡程序

        负载平衡程序:

                有多个处理器时,负载平衡程序使得各个处理器上调度的进程数均衡。

       负载平衡程序有kernel/sched.c中 load_balance()函数实现。

       调用时机:

              schedule()调用时,可执行队列为空。

              定时器定时调用。

三、抢占和上下文切换

3.1 上下文切换

       上下文切换:

               从一个可执行进程切换到另一个可执行进程。

              context_switch()负责切换,schedule()会调用该函数。

        知道什么时候调用schedule():

              need_resched来标志是否需要重新执行一次调度。

void set_tsk_need_resched(struct task_struct *tsk) //设置指定进程中的need_resched标志
void clear_tsk_need_resched(struct task_struct *tsk) //清除指定进程中的need_resched标志
int need_resched(void) //检查need_resched标志的值,如果被设置就返回真,否则返回假。

3.2 用户抢占

        用户抢占发生在:

                从系统调用返回用户空间。

                从中断处理程序返回用户空间。

3.3 内核抢占

        调度安全:

                只要调度是安全的,内核可以在任何时间抢占。

                没有锁,调度就是安全的。

                thread_info引入了preempt_count锁计数器,为0时,表示没有锁。

        内核抢占发生在:

                从中断处理程序,返回内核空间之前。

                内核代码再一次具有可抢占性的时候。

                内核显示的调用schedule()。

                内核中的任务阻塞(会导致调用schedule())。

四、实时调度

4.1 SCHED_FIFO

        比普通进程高。

        没有时间片,一直运行,直到运行完或让出处理器。

4.2 SCHED_RR

        比普通进程高。

        有时间片。

4.3 SCHED_NORMAL

        实时优先级范围:

                0到MAX_RT_PRIO-1(100)。

        nice值范围 :

                -20到19。

                共享了MAX_RT_PRIO到MAX_RT_PRIO+40。

五、与调度相关的系统调用

#include <linux/sched.h>

int sched_setscheduler(struct task_struct *, int, struct sched_param *); //设置进程调度策略
long sched_setaffinity(pid_t pid, const struct cpumask *new_mask); //设置进程的处理器的亲和力
long sched_getaffinity(pid_t pid, struct cpumask *mask); //获取进程的处理器的亲和力

抱歉!评论已关闭.