第七章 进程调度

假定系统使用统一内存访问模型,并且系统时钟设置为1ms。

       统一内存访问模型(UMA):指所有的处理器一致的共享全部物理内存。与之相对的是非统一内存访问(NUMA),这种架构下,CPU访问本地内存比访问共享内存快的多。

7.1:调度策略

       进程被分成两类,一类是普通进程,另一类是实时进程。普通进程又分为交互式进程和批处理进程。

       交互式进程:这些进程经常和用户交互,要花很多时间键盘和鼠标的操作。当收到输入的时候,进程必须马上被唤醒。

       批处理进程:这些进程不需要和用户交互,经常在后台运行。这样的进程不必很快被响应。

       实时进程:这些进程有很强的调度需求。体现在优先级小于100。

7.2:调度算法

       调度算法根据进程是普通进程还是实时进程有很大的区别。并且,在进程结构体中,通过policy字段,来标识进程的调度类型:

policy标志

进程类型

备注

SCHED_FIFO

先进先出的实时进程

当调度程序把CPU分给这个进程的时候,将进程描述符放在运行队列的当前位置。

SCHED_RR

时间片轮转的实时进程

当调度程序把CPU分给这个进程的时候,将进程描述符放在运行队列的末尾位置。这样能够保证同等优先机的实时进程公平的分配时间片。

SCHED_NORMAL

普通的分时进程

7.2.1:普通进程的调度

       每个普通进程都有静态优先级,从100(最高静态优先级)~139(最低静态优先级)。子进程会继承其父进程的静态优先级,当然也可以通过系统调用改变进程的优先级。

7.2.1.1:基本时间片

       静态优先级决定了每次分配给进程的基本时间片。静态优先级越高,其基本时间片也就越长。基本时间片使用task结构体中的time_slice成员描述。

7.2.1.2:动态优先级和平均睡眠时间

       动态优先级与静态优先级有关。它是根据某个公式从静态优先级算出来的。动态优先级的范围也是100~139。动态优先级是调度程序选用新的进程来执行的时候使用的值。

       根据函数的实现可以明确普通进程优先级的确认方式:

static int effective_prio(task_t *p)

{

       int bonus, prio;

       if (rt_task(p))  //判断是否是实时进程,依据就是优先级是否小于100

              return p->prio;

       bonus = CURRENT_BONUS(p) - MAX_BONUS / 2;  //根据进程的平均睡眠时间来计算bonus。

       prio = p->static_prio - bonus;

       if (prio < MAX_RT_PRIO)

              prio = MAX_RT_PRIO;

       if (prio > MAX_PRIO-1)

              prio = MAX_PRIO-1;   //依然限制普通分时进程的优先级在100~139之间。这时候算出的优先级是动态优先级。

       return prio;

}

7.2.1.3:活动和过期进程

       对于普通分时进程而言,高优先级的进程获得了较大的时间片。在他执行完成后,又会重新给他赋时间片。这样的后果就是,低优先级的任务永远都不会被执行。因此,调度器维持了两个正交的可运行进程队列。分别是活动进程和过期进程。

prio_array_t *active

包含了140项链表头的数组的数组指针,里面存放的是活动进程

prio_array_t *expired

包含了140项链表头的数组的数组指针,里面存放的是过期进程

       活动进程指的是还没有用完他的时间片的进程。过期进程指的是已经用完了这次分给他的时间片的进程。

       调度器在活动进程数组不为空的情况下调用活动进程。在没有活动进程的时候调用过期进程。

7.2.2:实时进程的调度

       实时进程的优先级是0~99。因此实时进程总是先于普通进程执行。此外,实时进程总是被当做是活动进程(也就是他的时间片用完后,不会被放到过期进程的队列中)。

       只有在以下情况中,实时进程才会被另一个进程取代:

       1:进程被另一个更高优先级的实时进程抢占

       2:进程执行了阻塞操作并且进入睡眠。

       3:进程停止或者被杀死

       4:进程调度策略为时间片轮转(SCHED_RR),并且用完了他的时间片。

       总之,这些都反映在调度程序中。

7.3:调度程序所使用的数据结构

       在前面进程的介绍是已知,我们总共有五个链表链接进程描述符。进程链表连接了所有的进程描述符,而运行队列链表链接了所有可运行的进程描述符(idle进程除外)。

7.3.1:数据结构runqueue

       这是一个每CPU变量,在这里进行定义

static DEFINE_PER_CPU(struct runqueue, runqueues);

       每个成员都是struct runqueue类型,结构体定义如下:

struct runqueue {

       spinlock_t lock;   //保护进程链表的自旋锁

       unsigned long nr_running;          //链表中可运行进程的数目

       unsigned long cpu_load;      //运行队列的CPU负载因子,在函数rebalance_tick中更新

       unsigned long long nr_switches;

       unsigned long nr_uninterruptible;   //之前在运行队列,但是现在睡眠在TASK_UNINTERRUPTIBLE状态的进程数量。TASK_INTERRUPTIBLE是正常的阻塞睡眠进程,可以被异步信号唤醒。TASK_UNINTERRUPTIBLE是不可中断的睡眠进程,不能被异步信号唤醒

       unsigned long expired_timestamp;

       unsigned long long timestamp_last_tick;   //上一次时钟中断时候的纳秒值

       task_t *curr;   //当前运行队列中正在运行的进程的PCB

    task_t *idle;   //当前CPU的swapper进程的PCB

       struct mm_struct *prev_mm;

       prio_array_t *active;  //指向活动进程链表的指针,也就是下方的arrays[0]

    prio_array_t *expired;  //指向过期进程链表的指针,也就是下方的arrays[1]

prio_array_t arrays[2]; //两个结构体,prio_array_t结构体在之前已经讲过,包含了140个链表,分别链接者优先级不同的进程。这两个结构体分别代表了过期进程和活动进程。调度程序会在调度时,改变被调度任务在arrays中的位置。

       int best_expired_prio;

       atomic_t nr_iowait;

       struct sched_domain *sd;

       /* For active balancing */

       int active_balance;

       int push_cpu;

       task_t *migration_thread;

       struct list_head migration_queue;

};

       arrays,active和expired的关系如图所示,active和expired分别指向arrays的两个成员。arrays的每个成员类型如下:

struct prio_array {

       unsigned int nr_active;

       unsigned long bitmap[BITMAP_SIZE];

       struct list_head queue[MAX_PRIO];   //包含了140个链表头,链接不同优先级的进程

};

 

7.3.2:进程描述符

       进程描述符中也有一些字段用于调度。

thread_info->flags

用于存放TIF_NEED_RESCHED,表示必须进行调度

thread_info->cpu

进程所在运行队列的CPU逻辑号

prio_array_t *array

指向前面所说的prio_array_t arrays[2]中的一个

time_slice

进程的时间片中还剩余的时钟节拍数

run_list

双向链表,链表头就是array中的queue

       在创建子进程的时候,会初始化子进程中和调度相关的字段。函数如下:sched_fork

void fastcall sched_fork(task_t *p)

{

       p->state = TASK_RUNNING;   //这里将任务状态设置为running,但是还没有将这个任务添加到就绪队列中。也就导致现在还不能通过调度器唤醒这个任务。

       INIT_LIST_HEAD(&p->run_list);

       p->array = NULL;

       spin_lock_init(&p->switch_lock);

#ifdef CONFIG_PREEMPT

       p->thread_info->preempt_count = 1;

#endif

       local_irq_disable();

       p->time_slice = (current->time_slice + 1) >> 1;  //子进程的时间片是父进程时间片的一半

       p->first_time_slice = 1;    //这个标志的作用是,表示如果子进程还没有用完自己的时间片就退出的话,会将时间片返还给父进程

       current->time_slice >>= 1;   //父进程时间片也减半,这样才能保证调度公平

       p->timestamp = sched_clock();   //系统启动以来的纳秒数

       if (unlikely(!current->time_slice)) {   //如果父进程已经没有时间可用,进入这里

              current->time_slice = 1;

              preempt_disable();

              scheduler_tick();

              local_irq_enable();

              preempt_enable();

       } else

              local_irq_enable();

}

7.4:调度程序所使用的函数

7.4.1:scheduler_tick函数

       函数作用是维持当前任务的time_slice计数器。函数实现如下:

//这个函数会在时钟中断处理函数中调用(关中断);或者是在创建进程的时候调用

void scheduler_tick(void)

{

       int cpu = smp_processor_id();

       runqueue_t *rq = this_rq(); //拿本地CPU的就绪队列(每个CPU都有一个就绪队列)

       task_t *p = current;

       rq->timestamp_last_tick = sched_clock();

       if (p == rq->idle) {   //如果当前任务是idle任务

              if (wake_priority_sleeper(rq))   //这个函数的作用就是,如果当前就绪队列中还有其他任务,就设置当前任务(也就是idle_task)的调度标志,在中断结束后会进行调度。是否有其他任务是通过rq->nr_running的值判断的。

                     goto out;

          return;

       }

       /* Task might have expired already, but not scheduled off yet */

       if (p->array != rq->active) {//进程没有指向准备队列的active成员,说明进程已经过期(这时候p->array==NULL,但是还是在执行),但是还没有被替换。设置任务的调度标志(TIF_NEED_RESCHED)。产生的原因是,将任务移出运行队列的函数deactivate_task中没有调度点?

              set_tsk_need_resched(p);

              goto out;

       }

       spin_lock(&rq->lock);

       if (rt_task(p)) {   //这里判断进程的优先级是否小于100。Linux认为优先级小于100的任务是实时任务。

               *///根据进程的调度类型有普通进程,先进先出进程和时间片轮转进程

              if ((p->policy == SCHED_RR) && !--p->time_slice) {//如果进程的调度类型是时间片,并且现在进程的时间片已经用完了

                     p->time_slice = task_timeslice(p);   //根据进程的静态优先级,重新设置进程的时间片数量

                     p->first_time_slice = 0;  //这个标志在创建任务的时候设置为1(copy_process),在进程的第一个时间片用完的时候清0。

                     set_tsk_need_resched(p);  //设置调度标志,这样的话,进程会被优先级相同或者更高的任务抢占

                     requeue_task(p, rq->active);  //将这个任务移动到相应的优先级等待队列的最后

              }

              goto out_unlock;

       }

//上面的操作就是针对实时进程的操作。可知,如果是policy不为SCHED_RR的实时进程,会一直执行下去,直到这个进程因为其他原因放弃CPU。也就是说,这种实时进程不会因为时间片用完而退出。

只有policy为SCHED_RR的实时进程,会在时间片用完之后,重新设置任务的时间片。然后设置调度标志。不过这种实时任务也不会变成过期任务,而是将他放入运行队列的active的末尾。

可见这些内容和之前介绍的实时任务的调度策略相同。

       if (!--p->time_slice) {   //这种情况下,任务不是实时任务。还是递减时间片

              dequeue_task(p, rq->active);   //进程时间片用完,将这个进程从active队列中删除。这是链表操作。这时候task的run_list成员没有加入任何队列中,在下面才被插入到过期进程链表中。

              set_tsk_need_resched(p);

              p->prio = effective_prio(p);

              p->time_slice = task_timeslice(p);   //重新写任务的时间片

              p->first_time_slice = 0;

              if (!rq->expired_timestamp)   //如果调度器发现,只有过期进程而没有活动进程的时候,就会将指向过期进程的指针和指向活动进程的指针相互交换(rq->active与rq->expired互换)。这时候会将expired_timestamp这个值设置为0.

                     rq->expired_timestamp = jiffies;   // jiffies为当前tick数

              if (!TASK_INTERACTIVE(p) || EXPIRED_STARVING(rq)) {

                     enqueue_task(p, rq->expired);   //将进程插入到队列的过期进程链表中

              } else

                     enqueue_task(p, rq->active);   //将进程插入到队列的活动进程链表中

       } else {   //非实时任务,并且其时间片没有用完

              if (……) {

                     requeue_task(p, rq->active);   //一些条件下,任务没有用完他的时间片,也会设置调度标志并且将任务移动到队尾。以防止有些任务的时间片过长

                     set_tsk_need_resched(p);

              }

       }

out_unlock:

       spin_unlock(&rq->lock);

out:

       rebalance_tick(cpu, rq, NOT_IDLE);

}

       总之,排除这些和时间相关的算法代码可以看到,这个函数做的主要事情就是,判断当前任务的时间片是否还有剩余。有的话继续执行,不然的话,就设置调度标志TIF_NEED_RESCHED。并且,在这里就将任务移出活动进程链表,移入过期进程链表,但是此时并没有发生任务切换,还是这个任务在执行,任务切换要到schedule函数时才会发生。

       结合之前的时钟中断的处理流程,可以知道,时钟中断的处理例程中,除了一些监视热点等不重要操作外,重要操作只有设置和时间相关的全局变量,以及根据当前任务是否还有时间片,设置调度标志。

7.4.2:try_to_wake_up函数

       try_to_wake_up函数将睡眠或者停止的进程设置为TASK_RUNNING状态,并且将进程插入到本地CPU的运行队列中。代码如下所示:

static int try_to_wake_up(task_t * p, unsigned int state, int sync)    //sync的作用:用于禁止被唤醒的进程抢占本地CPU上正在运行的进程

{

       int cpu, this_cpu, success = 0;

       unsigned long flags;

       long old_state;

       runqueue_t *rq;

#ifdef CONFIG_SMP     //表示内核支持对称多处理器

       unsigned long load, this_load;

       struct sched_domain *sd;

       int new_cpu;

#endif

       rq = task_rq_lock(p, &flags);  //函数的作用包含:关中断,拿就绪队列的锁,以及拿就绪队列。就绪队列就是之前任务在的队列。

       schedstat_inc(rq, ttwu_cnt);  //增加就绪队列中ttwu_cnt的值,表示try_to_wake_up_count

       old_state = p->state;

       if (!(old_state & state))   //如果要被唤醒的进程状态不在掩码中,那么不唤醒这个进程

              goto out;

       if (p->array)    //如果进程的就绪队列指针不为空,说明已经被加到其他CPU的就绪队列中

              goto out_running;

       cpu = task_cpu(p);   //进程之前所在的CPU

       this_cpu = smp_processor_id();

#ifdef CONFIG_SMP    //在多处理器系统中,函数要检查被唤醒的进程是否要从最近运行的CPU迁移到另外的CPU的运行队列上去。和负载平衡相关。这部分代码不讨论

#endif /* CONFIG_SMP */

       if (old_state == TASK_UNINTERRUPTIBLE) {

              rq->nr_uninterruptible--;

              p->activated = -1;

       }

       activate_task(p, rq, cpu == this_cpu);    //将任务添加到就绪队列中

       if (!sync || cpu != this_cpu) {

              if (TASK_PREEMPTS_CURR(p, rq))   //如果任务比当前运行的任务的优先级高的话,就设置当前任务的调度标志

                     resched_task(rq->curr);

       }

       success = 1;

out_running:

       p->state = TASK_RUNNING;

out:

       task_rq_unlock(rq, &flags);

       return success;

}

       对task_rq_lock进行分析:

static runqueue_t *task_rq_lock(task_t *p, unsigned long *flags)

       __acquires(rq->lock)

{

       struct runqueue *rq;

repeat_lock_task:

       local_irq_save(*flags);

       rq = task_rq(p);   //这里拿到任务所在的就绪队列

       spin_lock(&rq->lock);

       if (unlikely(rq != task_rq(p))) {   //如果这时候任务所在的就绪队列和之前不一致,那么就要重新拿一遍就绪队列

              spin_unlock_irqrestore(&rq->lock, *flags);

              goto repeat_lock_task;

       }

       return rq;

}

       对函数activate_task进行分析。在此之前首先介绍进程描述符中activated字段的含义。

说明

0

进程处于TASK_RUNNING状态

1

进程处于TASK_INTERRUPTIBLE或TASK_STOPPED状态,并且正在被系统调用服务例程或者内核线程唤醒

2

进程处于TASK_INTERRUPTIBLE或TASK_STOPPED状态,并且正在被中断处理程序或者可延迟函数唤醒

-1

进程处于TASK_UNINTERRUPTIBLE状态而且正在被唤醒

static void activate_task(task_t *p, runqueue_t *rq, int local)//把进程p加到就绪队列rq中,参数local表示当前CPU是否是进程p之前使用的CPU

{

       unsigned long long now;

       now = sched_clock();   //获取当前时间(单位为纳秒)

#ifdef CONFIG_SMP   //对称多处理,每个核的功能一致

#endif

       recalc_task_prio(p, now);

       if (!p->activated) {

              if (in_interrupt())

                     p->activated = 2;

              else {

                     p->activated = 1;

              }

       }

       p->timestamp = now;   //可知timestamp字段表明了任务被放入就绪队列的时间

       __activate_task(p, rq);  //将任务添加到就绪队列的active队列

}

7.4.3:recalc_task_prio()函数

       函数recalc_task_prio用于计算进程的平均睡眠事件和动态优先级。函数实现如下:

static void recalc_task_prio(task_t *p, unsigned long long now)

{

       unsigned long long __sleep_time = now - p->timestamp; //now是当前时间,timestamp是任务上一次切出的时间。因此得到任务的睡眠时间

       unsigned long sleep_time;

       if (__sleep_time > NS_MAX_SLEEP_AVG)

              sleep_time = NS_MAX_SLEEP_AVG;

       else

              sleep_time = (unsigned long)__sleep_time;

       if (likely(sleep_time > 0)) {

              if (p->mm && p->activated != -1 &&  // 当任务从TASK_UNINTERRUPTIBLE状态下被唤醒时,activated位设置为-1

                     sleep_time > INTERACTIVE_SLEEP(p)) {   //这个条件表示,进程不是内核线程(内核线程的mm结构体是NULL),进程不是从TASK_UNINTERRUPTIBLE被唤醒,进程连续睡眠时间超过极限

                            p->sleep_avg = JIFFIES_TO_NS(MAX_SLEEP_AVG -

                                          DEF_TIMESLICE);

              } else {

                     sleep_time *= (MAX_BONUS - CURRENT_BONUS(p)) ? : 1;

                     if (p->activated == -1 && p->mm) {

                            if (p->sleep_avg >= INTERACTIVE_SLEEP(p))

                                   sleep_time = 0;

                            else if (p->sleep_avg + sleep_time >=

                                          INTERACTIVE_SLEEP(p)) {

                                   p->sleep_avg = INTERACTIVE_SLEEP(p);

                                   sleep_time = 0;

                            }

                     }

                     p->sleep_avg += sleep_time;

                     if (p->sleep_avg > NS_MAX_SLEEP_AVG)

                            p->sleep_avg = NS_MAX_SLEEP_AVG;

              }

       }

       p->prio = effective_prio(p);     //更新进程的动态优先级

}

7.4.4:schedule()函数

asmlinkage void __sched schedule(void)     //asmlinkage表示所有的参数都从栈上取

{

       long *switch_count;

       task_t *prev, *next;

       runqueue_t *rq;

       prio_array_t *array;

       struct list_head *queue;

       unsigned long long now;

       unsigned long run_time;

       int cpu, idx;

need_resched:

       preempt_disable();   //关抢占

       prev = current;

       release_kernel_lock(prev);   //释放大内核锁

need_resched_nonpreemptible:

       rq = this_rq();

       schedstat_inc(rq, sched_cnt);

       now = sched_clock();   //获取当前的时间戳,单位为纳秒

       if (likely(now - prev->timestamp < NS_MAX_SLEEP_AVG))

              run_time = now - prev->timestamp;   //获得任务的执行时间

       else

              run_time = NS_MAX_SLEEP_AVG;

       /*

        * Tasks charged proportionately less run_time at high sleep_avg to

        * delay them losing their interactive status

        */

       run_time /= (CURRENT_BONUS(prev) ? : 1);

       spin_lock_irq(&rq->lock);   //关中断,获取就绪队列的锁

       if (unlikely(prev->flags & PF_DEAD))

              prev->state = EXIT_DEAD;

       switch_count = &prev->nivcsw;

       if (prev->state && !(preempt_count() & PREEMPT_ACTIVE)) {

              switch_count = &prev->nvcsw;

              if (unlikely((prev->state & TASK_INTERRUPTIBLE) &&

                            unlikely(signal_pending(prev))))

                     prev->state = TASK_RUNNING;

              else {

                     if (prev->state == TASK_UNINTERRUPTIBLE)

                            rq->nr_uninterruptible++;

                     deactivate_task(prev, rq);    //将任务从运行队列中移出

              }

       }

       cpu = smp_processor_id();

       if (unlikely(!rq->nr_running)) {   //如果运行队列中没有可运行的任务,那么需要使用负载平衡技术,从其他CPU中选择任务

go_idle:

              idle_balance(cpu, rq);

              if (!rq->nr_running) {

                     next = rq->idle;

                     rq->expired_timestamp = 0;

                     wake_sleeping_dependent(cpu, rq);

                     /*

                      * wake_sleeping_dependent() might have released

                      * the runqueue, so break out if we got new

                      * tasks meanwhile:

                      */

                     if (!rq->nr_running)

                            goto switch_tasks;

              }

       } else {

              if (dependent_sleeper(cpu, rq)) {

                     next = rq->idle;

                     goto switch_tasks;

              }

              if (unlikely(!rq->nr_running))

                     goto go_idle;

       }

       array = rq->active;

       if (unlikely(!array->nr_active)) {   //如果active队列中没有任务,那么说明所有的任务都从active变成了expired状态

              schedstat_inc(rq, sched_switch);

              rq->active = rq->expired;

              rq->expired = array;

              array = rq->active;

              rq->expired_timestamp = 0;

              rq->best_expired_prio = MAX_PRIO;

       } else

              schedstat_inc(rq, sched_noswitch);

       idx = sched_find_first_bit(array->bitmap);   //找到最高优先级对应的数组

       queue = array->queue + idx;

       next = list_entry(queue->next, task_t, run_list);   //找到下一个任务

       if (!rt_task(next) && next->activated > 0) {

              unsigned long long delta = now - next->timestamp;

              if (next->activated == 1)

                     delta = delta * (ON_RUNQUEUE_WEIGHT * 128 / 100) / 128;

              array = next->array;

              dequeue_task(next, array);

              recalc_task_prio(next, next->timestamp + delta);

              enqueue_task(next, array);

       }

       next->activated = 0;

switch_tasks:

       if (next == rq->idle)

              schedstat_inc(rq, sched_goidle);

       prefetch(next);

       clear_tsk_need_resched(prev);

       rcu_qsctr_inc(task_cpu(prev));

       prev->sleep_avg -= run_time;

       if ((long)prev->sleep_avg <= 0)

              prev->sleep_avg = 0;

       prev->timestamp = prev->last_ran = now;   //更新进程时间。应该是进程每次切入切出的时间都会记在这个变量中

       sched_info_switch(prev, next);

       if (likely(prev != next)) {

              next->timestamp = now;

              rq->nr_switches++;

              rq->curr = next;

              ++*switch_count;

              prepare_arch_switch(rq, next);

              prev = context_switch(rq, prev, next);   //发生进程切换

              barrier();   //prev再次执行这里的时候,是下一个任务又切换到prev的时候。但是这时候上下文是perv切换到next的时候

              finish_task_switch(prev);

       } else

              spin_unlock_irq(&rq->lock);

       prev = current;

       if (unlikely(reacquire_kernel_lock(prev) < 0))

              goto need_resched_nonpreemptible;

       preempt_enable_no_resched();

       if (unlikely(test_thread_flag(TIF_NEED_RESCHED)))

              goto need_resched;

}

7.5:多处理系统中运行队列的平衡

       内核为了充分利用CPU,需要实现不同CPU之间的负载平衡,在需要的时候,将一些进程从一个CPU的运行队列转移到其他CPU的运行队列上。负载平衡算法需要考虑到CPU的拓扑结构。因此,Linux提出了调度域的概念。

7.5.1:调度域

7.5.2:rebalance_tick函数

static void rebalance_tick(int this_cpu, runqueue_t *this_rq,

                        enum idle_type idle)

{

       unsigned long old_load, this_load;

       unsigned long j = jiffies + CPU_OFFSET(this_cpu);  // jiffies每次时钟中断的时候增加1

       struct sched_domain *sd;

       /* Update our load */

       old_load = this_rq->cpu_load;

       this_load = this_rq->nr_running * SCHED_LOAD_SCALE;

       /*

        * Round up the averaging division if load is increasing. This

        * prevents us from getting stuck on 9 if the load is 10, for

        * example.

        */

       if (this_load > old_load)

              old_load++;

       this_rq->cpu_load = (old_load + this_load) / 2;

       for_each_domain(this_cpu, sd) {

              unsigned long interval;

              if (!(sd->flags & SD_LOAD_BALANCE))

                     continue;

              interval = sd->balance_interval;

              if (idle != SCHED_IDLE)

                     interval *= sd->busy_factor;

              /* scale ms to jiffies */

              interval = msecs_to_jiffies(interval);

              if (unlikely(!interval))

                     interval = 1;

              if (j - sd->last_balance >= interval) {

                     if (load_balance(this_cpu, this_rq, sd, idle)) {

                            /* We've pulled tasks over so no longer idle */

                            idle = NOT_IDLE;

                     }

                     sd->last_balance += interval;

              }

       }

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值