进程-调度

调度策略

对于操作系统来说,cpu的数量是有限的,有些任务优先级较高,有些任务优先级较低,为了有效的利用cpu,这时就有了进程的调度的概念。

在task_struct中,有一个成员变量,用来表示调度策略

	
   
   
    
    unsigned
   
    int			policy;

在sched.h文件头 有这样一个#include <uapi/linux/sched.h>打开这个文件,会看到如下定义,是 policy 的几个定义。

/*
 * 
   
   
    
    Scheduling
   
    
   
   
    
    policies
   
   
 */
#
    
    
     
     define
    
     SCHED_NORMAL		0
#
    
    
     
     define
    
     SCHED_FIFO		1
#
    
    
     
     define
    
     SCHED_RR		2
#
    
    
     
     define
    
     SCHED_BATCH		3
/* SCHED_ISO: 
   
   
    
    reserved
   
    
   
   
    
    but
   
    
   
   
    
    not
   
    
   
   
    
    implemented
   
    
   
   
    
    yet
   
    */
#
    
    
     
     define
    
     SCHED_IDLE		5
#
    
    
     
     define
    
     SCHED_DEADLINE		6

有了调度策略的定义,那么每个策略的优先级是如何划分的?

为了配合调度策略,当然也有优先级的字段,在task_struct中,

	int				prio;
	int				static_prio;
	int				normal_prio;
	
   
   
    
    unsigned
   
    int			rt_priority;

优先级是通过数字来表示的,可以在#include <linux/sched/prio.h>文件里找到定义。

有这样一块注释

/*
 * 
   
   
    
    Priority
   
    of a 
   
   
    
    process
   
    
   
   
    
    goes
   
    
   
   
    
    from
   
    0..MAX_PRIO-1, 
   
   
    
    valid
   
    RT
 * 
   
   
    
    priority
   
    is 0..MAX_RT_PRIO-1, 
   
   
    
    and
   
    SCHED_NORMAL/SCHED_BATCH
 * 
   
   
    
    tasks
   
    
   
   
    
    are
   
    in 
   
   
    
    the
   
    
   
   
    
    range
   
    MAX_RT_PRIO..MAX_PRIO-1
 */
#
    
    
     
     define
    
     MAX_USER_RT_PRIO	100
#
    
    
     
     define
    
     MAX_RT_PRIO		MAX_USER_RT_PRIO
#
    
    
     
     define
    
     MAX_PRIO	(MAX_RT_PRIO + NICE_WIDTH)

/*
 *RT 
   
   
    
    priority
   
    to be 
   
   
    
    separate
   
    
   
   
    
    from
   
    
   
   
    
    the
   
    
   
   
    
    value
   
    
   
   
    
    exported
   
    to
 * 
   
   
    
    user
   
   -
   
   
    
    space
   
   .  
   
   
    
    This
   
    
   
   
    
    allows
   
    
   
   
    
    kernel
   
    
   
   
    
    threads
   
    to 
   
   
    
    set
   
    
   
   
    
    their
   
   
 * 
   
   
    
    priority
   
    to a 
   
   
    
    value
   
    
   
   
    
    higher
   
    
   
   
    
    than
   
    
   
   
    
    any
   
    
   
   
    
    user
   
    
   
   
    
    task
   
   .
 */
  • 通过代码可以得到 SCHED_NORMAL/SCHED_BATCH等普通进程的优先级数值是100-139

  • 通过注释可以看到实时进程的优先级要高于普通进程的,其数值是0-99

进程的分类

上面我们知道了进程优先级实时进程高于普通进程,那么具体的分类是什么

  • 实时进程
    • SCHED_FIFO 先进先出算法。
    • SCHED_RR 轮流调度算法。
    • SCHED_DEADLINE 按照deadline的时间来选择,每次选择最近的deadline时间的任务来执行。
  • 普通进程
    • SCHED_NORMAL 普通进程
    • SCHED_BATCH 后台进程,与我们平时了解的后台任务一样。
    • SCHED_IDEL优先级最低的任务,在空闲期执行。

调度的执行逻辑

有了调度的策略,此时就需要执行调度任务,在 task_struct 中有这样一个字段



   
   
    
    const
   
    
   
   
    
    struct
   
    sched_class *sched_class;

这个结构体定义在kernel/sched/sched.h文件中,其中有如下实现


   
   
    
    extern
   
    
   
   
    
    const
   
    
   
   
    
    struct
   
    sched_class stop_sched_class;

   
   
    
    extern
   
    
   
   
    
    const
   
    
   
   
    
    struct
   
    sched_class dl_sched_class;

   
   
    
    extern
   
    
   
   
    
    const
   
    
   
   
    
    struct
   
    sched_class rt_sched_class;

   
   
    
    extern
   
    
   
   
    
    const
   
    
   
   
    
    struct
   
    sched_class fair_sched_class;

   
   
    
    extern
   
    
   
   
    
    const
   
    
   
   
    
    struct
   
    sched_class idle_sched_class;
  • stop_sched_class: 优先级最高,会中断所有其他进程,且不会被其他任务打断
  • dl_sched_class: 对应deadline调度策略
  • rt_sched_class:对应RR或者FIFO调度策略,具体由policy决定
  • fair_sched_class: 普通进程调度策略
  • idle_sched_class: 空闲进程调度策略

普通进程

完全公平调度算法

CFS调度算法:Completely Fair Scheduling

CSF中依靠vruntime实现进程优先级,vruntime小的先进行调度,csf需要每次对vruntime进行排序并且进行平衡,那么所使用的数据结构是红黑树

具体的实现逻辑在kernel/sched/fair.c文件中


   
   
    
    static
   
    
   
   
    
    void
   
    update_curr(
   
   
    
    struct
   
    cfs_rq *cfs_rq)
{
	
   
   
    
    struct
   
    sched_entity *curr = cfs_rq->curr;
	u64 now = rq_clock_task(rq_of(cfs_rq));
	u64 delta_exec;

	if (
   
   
    
    unlikely
   
   (!curr))
		
   
   
    
    return
   
   ;

	delta_exec = now - curr->exec_start;
	if (
   
   
    
    unlikely
   
   ((s64)delta_exec <= 0))
		
   
   
    
    return
   
   ;

	curr->exec_start = now;

	schedstat_set(curr->statistics.exec_max,
		      max(delta_exec, curr->statistics.exec_max));

	curr->sum_exec_runtime += delta_exec;
	schedstat_add(cfs_rq->exec_clock, delta_exec);

	curr->vruntime += calc_delta_fair(delta_exec, curr);
	update_min_vruntime(cfs_rq);

	if (entity_is_task(curr)) {
		
   
   
    
    struct
   
    task_struct *curtask = task_of(curr);

		trace_sched_stat_runtime(curtask, delta_exec, curr->vruntime);
		cgroup_account_cputime(curtask, delta_exec);
		account_group_exec_runtime(curtask, delta_exec);
	}

	account_cfs_rq_runtime(cfs_rq, delta_exec);
}

下面是红黑树的结构,节点中包括vruntime和load_weight



   
   
    
    struct
   
    sched_entity {
	/* 
   
   
    
    For
   
    
   
   
    
    load
   
   -
   
   
    
    balancing
   
   : */
	
   
   
    
    struct
   
    load_weight		load;
	
   
   
    
    unsigned
   
    
   
   
    
    long
   
   			runnable_weight;
	
   
   
    
    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;

	
   
   
    
    struct
   
    sched_statistics		statistics;
执行流程

CPU -> 队列rq -> rt_rq和cfs_rq

CPU需要找下一个任务执行的时候,rt_sched_class先调用,它会在rt_rq队列上找,如果rt_rq找不到,才轮到fair_sched_class被调用,它会在cfs_rq队列上查找,这样保证了实时进程的优先级永远大于普通进程。

进程调度

1.主动调度
  • Btrfs,等待写入
  • 从Tap网络设备等待一个读取

使用schedule 函数进行调度,实现代码位于kernel/sched/core.cstatic void __sched notrace __schedule(bool preempt**)


   
   
    
    static
   
    
   
   
    
    void
   
    __sched notrace __schedule(bool preempt)
{
	
   
   
    
    struct
   
    task_struct *prev, *next;
	
   
   
    
    unsigned
   
    
   
   
    
    long
   
    *switch_count;
	
   
   
    
    struct
   
    rq_flags rf;
	
   
   
    
    struct
   
    rq *rq;
	int cpu;

	cpu = smp_processor_id();
	rq = cpu_rq(cpu);
	prev = rq->curr;
...
	next = pick_next_task(rq, prev, &rf);
	clear_tsk_need_resched(prev);
	clear_preempt_need_resched();
...
}
...
  	if (
   
   
    
    likely
   
   (prev != next)) {
		rq->nr_switches++;
		rq->curr = next;
		/*
		 * 
   
   
    
    The
   
    
   
   
    
    membarrier
   
    
   
   
    
    system
   
    
   
   
    
    call
   
    
   
   
    
    requires
   
    
   
   
    
    each
   
    
   
   
    
    architecture
   
   
		 * to 
   
   
    
    have
   
    a 
   
   
    
    full
   
    
   
   
    
    memory
   
    
   
   
    
    barrier
   
    
   
   
    
    after
   
    
   
   
    
    updating
   
   
		 * rq->curr, 
   
   
    
    before
   
    
   
   
    
    returning
   
    to 
   
   
    
    user
   
   -
   
   
    
    space
   
   .
		 *
		 * 
   
   
    
    Here
   
    
   
   
    
    are
   
    
   
   
    
    the
   
    
   
   
    
    schemes
   
    
   
   
    
    providing
   
    
   
   
    
    that
   
    
   
   
    
    barrier
   
    on 
   
   
    
    the
   
   
		 * 
   
   
    
    various
   
    
   
   
    
    architectures
   
   :
		 * - mm ? switch_mm() : 
   
   
    
    mmdrop
   
   () 
   
   
    
    for
   
    x86, s390, 
   
   
    
    sparc
   
   , 
   
   
    
    PowerPC
   
   .
		 *   switch_mm() 
   
   
    
    rely
   
    on membarrier_arch_switch_mm() on 
   
   
    
    PowerPC
   
   .
		 * - finish_lock_switch() 
   
   
    
    for
   
    
   
   
    
    weakly
   
   -
   
   
    
    ordered
   
   
		 *   
   
   
    
    architectures
   
    
   
   
    
    where
   
    spin_unlock is a 
   
   
    
    full
   
    
   
   
    
    barrier
   
   ,
		 * - switch_to() 
   
   
    
    for
   
    arm64 (
   
   
    
    weakly
   
   -
   
   
    
    ordered
   
   , spin_unlock
		 *   is a 
   
   
    
    RELEASE
   
    
   
   
    
    barrier
   
   ),
		 */
		++*switch_count;

		trace_sched_switch(preempt, prev, next);

		/* 
   
   
    
    Also
   
    
   
   
    
    unlocks
   
    
   
   
    
    the
   
    rq: */
		rq = context_switch(rq, prev, next, &rf);
	} 
   
   
    
    else
   
    {
		rq->clock_update_flags &= ~(RQCF_ACT_SKIP|RQCF_REQ_SKIP);
		rq_unlock_irq(rq, &rf);
	}

pick_next_task方法是获取下一个调度任务

/*
 * 
   
   
    
    Pick
   
    up 
   
   
    
    the
   
    
   
   
    
    highest
   
   -
   
   
    
    prio
   
    
   
   
    
    task
   
   :
 */

   
   
    
    static
   
    
   
   
    
    inline
   
    
   
   
    
    struct
   
    task_struct *
pick_next_task(
   
   
    
    struct
   
    rq *rq, 
   
   
    
    struct
   
    task_struct *prev, 
   
   
    
    struct
   
    rq_flags *rf)
{
	
   
   
    
    const
   
    
   
   
    
    struct
   
    sched_class *class;
	
   
   
    
    struct
   
    task_struct *p;

	/*
	 * 
   
   
    
    Optimization
   
   : we 
   
   
    
    know
   
    
   
   
    
    that
   
    if 
   
   
    
    all
   
    
   
   
    
    tasks
   
    
   
   
    
    are
   
    in 
   
   
    
    the
   
    
   
   
    
    fair
   
    
   
   
    
    class
   
    we 
   
   
    
    can
   
   
	 * 
   
   
    
    call
   
    
   
   
    
    that
   
    
   
   
    
    function
   
    
   
   
    
    directly
   
   , 
   
   
    
    but
   
    
   
   
    
    only
   
    if 
   
   
    
    the
   
    @prev 
   
   
    
    task
   
    
   
   
    
    wasn
   
   't of a
	 * 
   
   
    
    higher
   
    
   
   
    
    scheduling
   
    
   
   
    
    class
   
   , 
   
   
    
    because
   
    
   
   
    
    otherwise
   
    
   
   
    
    those
   
    
   
   
    
    loose
   
    
   
   
    
    the
   
   
	 * 
   
   
    
    opportunity
   
    to 
   
   
    
    pull
   
    in 
   
   
    
    more
   
    
   
   
    
    work
   
    
   
   
    
    from
   
    
   
   
    
    other
   
    
   
   
    
    CPUs
   
   .
	 */
	if (
   
   
    
    likely
   
   ((prev->sched_class == &idle_sched_class ||
		    prev->sched_class == &fair_sched_class) &&
		   rq->nr_running == rq->cfs.h_nr_running)) {

		p = fair_sched_class.pick_next_task(rq, prev, rf);
		if (
   
   
    
    unlikely
   
   (p == RETRY_TASK))
			
   
   
    
    goto
   
    again;

		/* Assumes fair_sched_class->next == idle_sched_class */
		if (
   
   
    
    unlikely
   
   (!p))
			p = idle_sched_class.pick_next_task(rq, prev, rf);

		
   
   
    
    return
   
    p;
	}

again:
	for_each_class(class) {
		p = class->pick_next_task(rq, prev, rf);
		if (p) {
			if (
   
   
    
    unlikely
   
   (p == RETRY_TASK))
				
   
   
    
    goto
   
    again;
			
   
   
    
    return
   
    p;
		}
	}

	/* 
   
   
    
    The
   
    
   
   
    
    idle
   
    
   
   
    
    class
   
    
   
   
    
    should
   
    
   
   
    
    always
   
    
   
   
    
    have
   
    a 
   
   
    
    runnable
   
    
   
   
    
    task
   
   : */
	BUG();
}

优先判断的是普通进程,如果普通进程则调用fair_sched_class,位于fair.c文件中,并且实现了 pick_next_task_fair



   
   
    
    static
   
    
   
   
    
    struct
   
    task_struct *
pick_next_task_fair(
   
   
    
    struct
   
    rq *rq, 
   
   
    
    struct
   
    task_struct *prev, 
   
   
    
    struct
   
    rq_flags *rf)
{
	
   
   
    
    struct
   
    cfs_rq *cfs_rq = &rq->cfs;
	
   
   
    
    struct
   
    sched_entity *se;
	
   
   
    
    struct
   
    task_struct *p;
	int new_tasks;

again:
	if (!cfs_rq->nr_running)
		
   
   
    
    goto
   
    idle;
...
  
   
   
    
    struct
   
    sched_entity *curr = cfs_rq->curr;
  if (curr) {
			if (curr->on_rq)
				update_curr(cfs_rq);
			
   
   
    
    else
   
   
				curr = 
   
   
    
    NULL
   
   ;
...
  	se = pick_next_entity(cfs_rq, curr);
		cfs_rq = group_cfs_rq(se);
	} 
   
   
    
    while
   
    (cfs_rq);
	p = task_of(se);
  if (prev != p) {
		
   
   
    
    struct
   
    sched_entity *pse = &prev->se;
...
  	put_prev_entity(cfs_rq, pse);
		set_next_entity(cfs_rq, se);

可以看到从cfs_rq队列中取出当前运行的任务,如果当前任务是可运行的,则调用update_curr更新vruntime。接着 pick_next_entity 从红黑树中取出最小的vruntime的节点。

task_of获得一个task_struct实例,继任和前任如果不相同,就更新红黑树,将前任的vruntime 更新后放入put_prev_entityset_next_entity将继任者设置为当前任务。

进程上下文切换

从__schedule()中我们可以看到当继任和前任不同时,会调用rq = context_switch(rq, prev, next, &rf);即进行上下文切换,包括下列2个功能

  • 切换进程空间

  • 切换寄存器和CPU上下文

    • x86结构体系中,提供了结构体 TSS ( Task State Segment ),用以存放所有的寄存器。

    • 特殊的寄存器( Task Register )任务寄存器,指向某个进程的TSS,更改TR的值,将会触发硬件将CPU所有寄存器的值保存到TSS中,然后从新的进程中读取出 TSS 保存的寄存器的值,并加载到CPU对应的寄存器中。

    • 系统层次的优化:cpu_init 时 给每一个cpu关联一个TSS,然后TR一直指向这个TSS,在task_struct 中结构体 thread_struct 用来存放切换进程需要修改的寄存器

      	/* 
             
             
              
              CPU
             
             -
             
             
              
              specific
             
              
             
             
              
              state
             
              of 
             
             
              
              this
             
              
             
             
              
              task
             
             : */
      	
             
             
              
              struct
             
              thread_struct		thread;
      

      所以进程切换就是将 thread_struct 存放的寄存器的值写入到CPU的TR指向的TSS。

2.抢占式调度
  • 一个进程执行时间过长,是时候切换到另一个进程

    1. 时钟中断处理函数scheduler_tick 位于kernel/sched/core.c
    2. 处理时钟事件curr->sched_class->task_tick(rq, curr,0 );,通过调用可以发现每一个sched_class 实现的task_tick不同。
    3. 当发现一个进程被抢占时,不会将它踢下来,而是标记成被抢占。要一直等待该进程运行到__schedule()。
  • 当一个进程被唤醒的时候

    • 检查并标记抢占进程

      1. try_to_wake_up()->ttwu_queue()将唤醒任务加入队列 调用 ttwu_do_activate 激活任务
      2. 调用 tt_do_wakeup()->check_preempt_curr()检查是否应该抢占, 若需抢占则标记
    • 用户态抢占时机

      • 从系统调用中返回, 返回过程中会调用 exit_to_usermode_loop, 若打了标记, 则调用 schedule()
      • 从中断中返回, 中断返回分为返回用户态和内核态(汇编代码: arch/x86/entry/entry_64.S),
    • 内核态抢占时机

      • 一般发生在 preempt_enable() 中, 内核态进程有的操作不能被中断, 想要进行抢占之前,会调用 preempt_disable()关闭抢占, 在开启(调用 preempt_enable()) 是一个抢占时机, 会调用 preempt_count_dec_and_test(), 检测 preempt_count 和标记, 若可抢占则最终调用 preempt_schedule->preempt_schedule_common->__schedule
      • 发生在中断返回, 也会调用 preempt_schedule_irq->__schedule
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值