进程管理--CFS调度器(2)

CFS调度流程分析

主要以CFS调度器为例,介绍一下进程调度的一般流程。首先是调度的时机,基本调度器会根据TIF_NEED_RESHED标记判断是否需要进行调度,所以这个标记的设置就是调度的开始。有以下几种情况都会设置这个标记。例如周期性调度会触发中断设置调度标记,唤醒或者新建一个进程也会进行设置,负载均衡需要迁移进程时也会进行标记,还有修改NICE值导致权重变化的时候也需要设置标记。需要注意的是,设置这个标记不代表立刻开始调度,具体什么时候被调度到还是要看具体的策略和整个就绪队列的情况。

进程创建中的调度初始化sched_fork()

fork()---->kernel_clone()---->copy_process()---->sched_fork()。针对sched_fork()函数,

int sched_fork(unsigned long clone_flags, struct task_struct *p)
{
	p->state = TASK_NEW;
	p->prio = current->normal_prio;
	p->sched_class = &fair_sched_class;         /* 1 */
 
	if (p->sched_class->task_fork)
		p->sched_class->task_fork(p);           /* 2 */
 
	return 0;
}

以CFS为例,为task选择调度类。fair_sched_class是CFS调度类。
调用调度类中的task_fork函数。task_fork方法会调用到task_fork_fair。
传递的参数p就是创建的task_struct。

sched_fork函数进行调度相关的初始化比如设置优先级,调度类,就绪队列等,把进程设置为TASK_NEW,无法运行也无法唤醒。task_fork_fair主要用于设置新进程的vruntime,从而也能确定子任务的调度实体在红黑树中的位置。

新进程加入就绪队列wake_up_new_task

fork()---->kernel_clone()---->copy_process()---->wake_up_new_task

经过copy_process的大部分初始化工作完成之后,就可以唤醒新进程准备运行。也就是将新进程加入就绪队列准备调度。

void wake_up_new_task(struct task_struct *p)
{
	struct rq_flags rf;
	struct rq *rq;
 
	p->state = TASK_RUNNING;
#ifdef CONFIG_SMP
	p->recent_used_cpu = task_cpu(p);

    /*
    通过调用调度类中select_task_rq方法选择最空闲的cpu
    */
	__set_task_cpu(p, select_task_rq(p, task_cpu(p), SD_BALANCE_FORK, 0));  
#endif
	rq = __task_rq_lock(p, &rf);
    //通过调用调度类中enqueue_task方法, 将进程加入就绪队列
	activate_task(rq, p, ENQUEUE_NOCLOCK);                                  
	p->on_rq = TASK_ON_RQ_QUEUED;

    /*既然新进程已经准备就绪,那么此时需要检查新进程是否满足抢占当前正在运行进程的条件,
    如果满足抢占条件需要设置TIF_NEED_RESCHED标志位
    */
	check_preempt_curr(rq, p, WF_FORK);                                     
}

检测抢占

void check_preempt_curr(struct rq *rq, struct task_struct *p, int flags)
{
	const struct sched_class *class;
 
	if (p->sched_class == rq->curr->sched_class) {
		rq->curr->sched_class->check_preempt_curr(rq, p, flags);   /* 1 */
	} else {
		for_each_class(class) {                                    /* 2 */
			if (class == rq->curr->sched_class)
				break;
			if (class == p->sched_class) {
				resched_curr(rq);
				break;
			}
		}
	}
}

  1. 唤醒的进程和当前的进程同属于一个调度类,直接调用调度类的check_preempt_curr方法检查抢占条件。毕竟调度器自己管理的进程,自己最清楚是否适合抢占当前进程。
  2. 如果唤醒的进程和当前进程不属于一个调度类,就需要比较调度类的优先级。例如,当期进程是CFS调度类,唤醒的进程是RT调度类,自然实时进程是需要抢占当前进程的,因为优先级更高。
static void check_preempt_wakeup(struct rq *rq, struct task_struct *p, int wake_flags)
{
	struct sched_entity *se = &curr->se, *pse = &p->se;
	struct cfs_rq *cfs_rq = task_cfs_rq(curr);
 
	if (wakeup_preempt_entity(se, pse) == 1)    /* 1 */
		goto preempt;
 
	return;
preempt:
	resched_curr(rq);                           /* 2 */
}
  1. 检查唤醒的进程是否满足抢占当前进程的条件。

满足抢占的条件就是,唤醒的进程的虚拟时间首先要比正在运行进程的虚拟时间小,并且差值还要大于一定的值才行(这个值是sysctl_sched_wakeup_granularity,称作唤醒抢占粒度)。这样做的目的是避免抢占过于频繁,导致大量上下文切换影响系统性能。

  1. 如果可以抢占当前进程,设置TIF_NEED_RESCHED flag。

选择下一个合适进程运行

当进程被设置TIF_NEED_RESCHED flag后会在某一时刻触发系统发生调度或者进程调用schedule()函数主动放弃cpu使用权,触发系统调度。

schedule()的核心函数是__schedule()

__schedule进程调度

static void __sched notrace __schedule(unsigned int sched_mode)
{
    struct task_struct *prev, *next;
    unsigned long *switch_count;                        /*记录当前进程切换的次数*/
    struct rq *rq;
 
    cpu = smp_processor_id();                           /*获取所在的cpu*/
    rq = cpu_rq(cpu);                                   /*切换到该cpu的rq*/
    prev = rq->curr;
 
    update_rq_clock(rq);                                /*更新rq时钟值*/
 
    /*非0表示处于非running状态,主动调用schedule前会设置此状态*/
    prev_state = READ_ONCE(prev->__state);  
    if (!(sched_mode & SM_MASK_PREEMPT) && prev_state)  /*发生非抢占调度,比如主动调出*/                        
        deactivate_task(rq, prev, DEQUEUE_SLEEP | DEQUEUE_NOCLOCK);    /*将当前进程摘除出就绪队列*/
 
    next = pick_next_task(rq, prev, &rf);               /*选择要运行的进程(如果当前进程还在运行,在这里摘除)*/
    clear_tsk_need_resched(prev);                       /*清除当前进程的TIF_NEED_RESCHED标记*/
    clear_preempt_need_resched();
  
    if (likely(prev != next)) {                         //不等于说明rq上还有可运行进程 
        rq->nr_switches++;                              /*更新队列切换次数*/
        ++*switch_count;                                /*更新进程切换次数*/
        rq = context_switch(rq, prev, next, &rf);       /*切换进程上下文*/
    } else                                             
        __balance_callbacks(rq);                        /*当前rq没有可运行进程,进行负载平衡*/
}
  1. 针对主动放弃cpu进入睡眠的进程,需要从对应的就绪队列上删除该进程。如果
  2. 选择下个合适的进程开始运行。
  3. 清除TIF_NEED_RESCHED flag。
  4. 上下文切换,从prev进程切换到next进程

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值