Linux进程里运行新代码,linux调度器源码分析 - 新进程加入(三)

中专门描述了copy_process()这个创建函数,而里面有一个函数专门用于进程调度的初始化,就是sched_fork(),其代码如下

int sched_fork(unsigned long clone_flags, struct task_struct *p)

{

unsigned long flags;

/* 获取当前CPU,并且禁止抢占 */

int cpu = get_cpu();

/* 初始化跟调度相关的值,比如调度实体,运行时间等 */

__sched_fork(clone_flags, p);

/*

* 标记为运行状态,表明此进程正在运行或准备好运行,实际上没有真正在CPU上运行,这里只是导致了外部信号和事件不能够唤醒此进程,之后将它插入到运行队列中

*/

p->state = TASK_RUNNING;

/*

* 根据父进程的运行优先级设置设置进程的优先级

*/

p->prio = current->normal_prio;

/*

* 更新该进程优先级

*/

/* 如果需要重新设置优先级 */

if (unlikely(p->sched_reset_on_fork)) {

/* 如果是dl调度或者实时调度 */

if (task_has_dl_policy(p) || task_has_rt_policy(p)) {

/* 调度策略为SCHED_NORMAL,这个选项将使用CFS调度 */

p->policy = SCHED_NORMAL;

/* 根据默认nice值设置静态优先级 */

p->static_prio = NICE_TO_PRIO(0);

/* 实时优先级为0 */

p->rt_priority = 0;

} else if (PRIO_TO_NICE(p->static_prio) < 0)

/* 根据默认nice值设置静态优先级 */

p->static_prio = NICE_TO_PRIO(0);

/* p->prio = p->normal_prio = p->static_prio */

p->prio = p->normal_prio = __normal_prio(p);

/* 设置进程权重 */

set_load_weight(p);

/* sched_reset_on_fork成员在之后已经不需要使用了,直接设为0 */

p->sched_reset_on_fork = 0;

}

if (dl_prio(p->prio)) {

/* 使能抢占 */

put_cpu();

/* 返回错误 */

return -EAGAIN;

} else if (rt_prio(p->prio)) {

/* 根据优先级判断,如果是实时进程,设置其调度类为rt_sched_class */

p->sched_class = &rt_sched_class;

} else {

/* 如果是普通进程,设置其调度类为fair_sched_class */

p->sched_class = &fair_sched_class;

}

/* 调用调用类的task_fork函数 */

if (p->sched_class->task_fork)

p->sched_class->task_fork(p);

/*

* The child is not yet in the pid-hash so no cgroup attach races,

* and the cgroup is pinned to this child due to cgroup_fork()

* is ran before sched_fork().

*

* Silence PROVE_RCU.

*/

raw_spin_lock_irqsave(&p->pi_lock, flags);

/* 设置新进程的CPU为当前CPU */

set_task_cpu(p, cpu);

raw_spin_unlock_irqrestore(&p->pi_lock, flags);

#if defined(CONFIG_SCHEDSTATS) || defined(CONFIG_TASK_DELAY_ACCT)

if (likely(sched_info_on()))

memset(&p->sched_info, 0, sizeof(p->sched_info));

#endif

#if defined(CONFIG_SMP)

p->on_cpu = 0;

#endif

/* task_thread_info(p)->preempt_count = PREEMPT_DISABLED; */

/* 初始化该进程为内核禁止抢占 */

init_task_preempt_count(p);

#ifdef CONFIG_SMP

plist_node_init(&p->pushable_tasks, MAX_PRIO);

RB_CLEAR_NODE(&p->pushable_dl_tasks);

#endif

/* 使能抢占 */

put_cpu();

return 0;

}

在sched_fork()函数中,主要工作如下:

获取当前CPU号

禁止内核抢占(这里基本就是关闭了抢占,因为执行到这里已经是内核态,又禁止了被抢占)

初始化进程p的一些变量(实时进程和普通进程通用的那些变量)

设置进程p的状态为TASK_RUNNING(这一步很关键,因为只有处于TASK_RUNNING状态下的进程才会被调度器放入队列中)

根据父进程和clone_flags参数设置进程p的优先级和权重。

根据进程p的优先级设置其调度类(实时进程优先级:0~99  普通进程优先级:100~139)

根据调度类进行进程p类型相关的初始化(这里就实现了实时进程和普通进程独有的变量进行初始化)

设置进程p的当前CPU为此CPU。

初始化进程p禁止内核抢占(因为当CPU执行到进程p时,进程p还需要进行一些初始化)

使能内核抢占

可以看出sched_fork()进行的初始化也比较简单,需要注意的是不同类型的进程会使用不同的调度类,并且也会调用调度类中的初始化函数。在实时进程的调度类中是没有特定的task_fork()函数的,而普通进程使用cfs策略时会调用到task_fork_fair()函数,我们具体看看实现:

static void task_fork_fair(struct task_struct *p)

{

struct cfs_rq *cfs_rq;

/* 进程p的调度实体se */

struct sched_entity *se = &p->se, *curr;

/* 获取当前CPU */

int this_cpu = smp_processor_id();

/* 获取此CPU的运行队列 */

struct rq *rq = this_rq();

unsigned long flags;

/* 上锁并保存中断记录 */

raw_spin_lock_irqsave(&rq->lock, flags);

/* 更新rq运行时间 */

update_rq_clock(rq);

/* cfs_rq = current->se.cfs_rq; */

cfs_rq = task_cfs_rq(current);

/* 设置当前进程所在队列为父进程所在队列 */

curr = cfs_rq->curr;

/*

* Not only the cpu but also the task_group of the parent might have

* been changed after parent->se.parent,cfs_rq were copied to

* child->se.parent,cfs_rq. So call __set_task_cpu() to make those

* of child point to valid ones.

*/

rcu_read_lock();

/* 设置此进程所属CPU */

__set_task_cpu(p, this_cpu);

rcu_read_unlock();

/* 更新当前进程运行时间 */

update_curr(cfs_rq);

if (curr)

/* 将父进程的虚拟运行时间赋给了新进程的虚拟运行时间 */

se->vruntime = curr->vruntime;

/* 调整了se的虚拟运行时间 */

place_entity(cfs_rq, se, 1);

if (sysctl_sched_child_runs_first && curr && entity_before(curr, se)) {

/*

* Upon rescheduling, sched_class::put_prev_task() will place

* 'current' within the tree based on its new key value.

*/

swap(curr->vruntime, se->vruntime);

resched_curr(rq);

}

/* 保证了进程p的vruntime是运行队列中最小的(这里占时不确定是不是这个用法,不过确实是最小的了) */

se->vruntime -= cfs_rq->min_vruntime;

/* 解锁,还原中断记录 */

raw_spin_unlock_irqrestore(&rq->lock, flags);

}

在task_fork_fair()函数中主要就是设置进程p的虚拟运行时间和所处的cfs队列,值得我们注意的是 cfs_rq = task_cfs_rq(current); 这一行,在注释中已经表明task_cfs_rq(current)返回的是current的se.cfs_rq,注意se.cfs_rq保存的并不是根cfs队列,而是所处的cfs_rq,也就是如果父进程处于一个进程组的cfs_rq中,新创建的进程也会处于这个进程组的cfs_rq中。

wake_up_new_task()

到这里新进程关于调度的初始化已经完成,但是还没有被调度器加入到队列中,其是在do_fork()中的wake_up_new_task(p);中加入到队列中的,我们具体看看wake_up_new_task()的实现:

void wake_up_new_task(struct task_struct *p)

{

unsigned long flags;

struct rq *rq;

raw_spin_lock_irqsave(&p->pi_lock, flags);

#ifdef CONFIG_SMP

/*

* Fork balancing, do it here and not earlier because:

* - cpus_allowed can change in the fork path

* - any previously selected cpu might disappear through hotplug

*/

/* 为进程选择一个合适的CPU */

set_task_cpu(p, select_task_rq(p, task_cpu(p), SD_BALANCE_FORK, 0));

#endif

/* Initialize new task's runnable average */

/* 这里是跟多核负载均衡有关 */

init_task_runnable_average(p);

/* 上锁 */

rq = __task_rq_lock(p);

/* 将进程加入到CPU的运行队列 */

activate_task(rq, p, 0);

/* 标记进程p处于队列中 */

p->on_rq = TASK_ON_RQ_QUEUED;

/* 跟调试有关 */

trace_sched_wakeup_new(p, true);

/* 检查是否需要切换当前进程 */

check_preempt_curr(rq, p, WF_FORK);

#ifdef CONFIG_SMP

if (p->sched_class->task_woken)

p->sched_class->task_woken(rq, p);

#endif

task_rq_unlock(rq, p, &flags);

}

在wake_up_new_task()函数中,将进程加入到运行队列的函数为activate_task(),而activate_task()函数最后会调用到新进程调度类中的enqueue_task指针所指函数,这里我们具体看一下cfs调度类的enqueue_task指针所指函数enqueue_task_fair():

static void

enqueue_task_fair(struct rq *rq, struct task_struct *p, int flags)

{

struct cfs_rq *cfs_rq;

struct sched_entity *se = &p->se;

/* 这里是一个迭代,我们知道,进程有可能是处于一个进程组中的,所以当这个处于进程组中的进程加入到该进程组的队列中时,要对此队列向上迭代 */

for_each_sched_entity(se) {

if (se->on_rq)

break;

/* 如果不是CONFIG_FAIR_GROUP_SCHED,获取其所在CPU的rq运行队列的cfs_rq运行队列

* 如果是CONFIG_FAIR_GROUP_SCHED,获取其所在的cfs_rq运行队列

*/

cfs_rq = cfs_rq_of(se);

/* 加入到队列中 */

enqueue_entity(cfs_rq, se, flags);

/*

* end evaluation on encountering a throttled cfs_rq

*

* note: in the case of encountering a throttled cfs_rq we will

* post the final h_nr_running increment below.

*/

if (cfs_rq_throttled(cfs_rq))

break;

cfs_rq->h_nr_running++;

flags = ENQUEUE_WAKEUP;

}

/* 只有se不处于队列中或者cfs_rq_throttled(cfs_rq)返回真才会运行这个循环 */

for_each_sched_entity(se) {

cfs_rq = cfs_rq_of(se);

cfs_rq->h_nr_running++;

if (cfs_rq_throttled(cfs_rq))

break;

update_cfs_shares(cfs_rq);

update_entity_load_avg(se, 1);

}

if (!se) {

update_rq_runnable_avg(rq, rq->nr_running);

/* 当前CPU运行队列活动进程数 + 1 */

add_nr_running(rq, 1);

}

/* 设置下次调度中断发生时间 */

hrtick_update(rq);

}

在enqueue_task_fair()函数中又使用了enqueue_entity()函数进行操作,如下:

static void

enqueue_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, int flags)

{

/*

* Update the normalized vruntime before updating min_vruntime

* through calling update_curr().

*/

if (!(flags & ENQUEUE_WAKEUP) || (flags & ENQUEUE_WAKING))

se->vruntime += cfs_rq->min_vruntime;

/*

* Update run-time statistics of the 'current'.

*/

/* 更新当前进程运行时间和虚拟运行时间 */

update_curr(cfs_rq);

enqueue_entity_load_avg(cfs_rq, se, flags & ENQUEUE_WAKEUP);

/* 更新cfs_rq队列总权重(就是在原有基础上加上se的权重) */

account_entity_enqueue(cfs_rq, se);

update_cfs_shares(cfs_rq);

/* 新建的进程flags为0,不会执行这里 */

if (flags & ENQUEUE_WAKEUP) {

place_entity(cfs_rq, se, 0);

enqueue_sleeper(cfs_rq, se);

}

update_stats_enqueue(cfs_rq, se);

check_spread(cfs_rq, se);

/* 将se插入到运行队列cfs_rq的红黑树中 */

if (se != cfs_rq->curr)

__enqueue_entity(cfs_rq, se);

/* 将se的on_rq标记为1 */

se->on_rq = 1;

/* 如果cfs_rq的队列中只有一个进程,这里做处理 */

if (cfs_rq->nr_running == 1) {

list_add_leaf_cfs_rq(cfs_rq);

check_enqueue_throttle(cfs_rq);

}

}

总结

需要注意的几点:

新创建的进程先会进行调度相关的结构体和变量初始化,其中会根据不同的类型进行不同的调度类操作,此时并没有加入到队列中。

当新进程创建完毕后,它的父进程会将其运行状态置为TASK_RUNNING,并加入到运行队列中。

加入运行队列时系统会根据CPU的负载情况放入不同的CPU队列中。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值