linux之调度管理(1)-调度器的初始化

一、linux 启动内核时的第一个进程 init_task

linux 进程的鼻祖 0,1,2,我在其他文章中,有具体讲解,链接是: linux 之0号进程、1号进程、2号进程_linux下0号进程 swap-CSDN博客。

在这里就不具体展开了。请看上面的文章详解。

当linux启动时,最先会通过汇编代码进行硬件和CPU的初始化,最后会跳转到C代码,而最初跳转到的C代码入口为:

/* 代码地址: linux/init/Main.c */
asmlinkage __visible void __init start_kernel(void)

 补充一点:内核在执行C语言部分是有一段汇编语言,代码路径:kernel/arch/arm64/kernel/head.S

head.S  的汇编大致执行以下内容:

  • preserve_boot_args用来保留bootloader传递的参数,比如ARM上通常的dtb的地址
  • el2_setup: 用来trap到EL1,说明我们在运行此指令前还在EL2
  • __create_page_tables: 用来创建页表,linux才有的是页面管理物理内存的,在使用虚拟地址之前需要设置好页面,然后会打开MMU。目前还是运行在物理地址上的
  • __primary_switch: 主要任务是完成MMU的打开工作
  • 调用__primary_switched来设置0号进程的运行内核栈,然后调用start_kernel函数,至此,0号进程已经运行了,执行的函数就是 start_kernel 。

在start_kerenl函数中,进行了系统启动过程中几乎所有重要的初始化,包括内存、页表、必要数据结构、信号、调度器、硬件设备等。

而这些初始化是由谁来负责的?就是由init_task这个进程。init_task是静态定义的一个进程,也就是说当内核被放入内存时,它就已经存在,它没有自己的用户空间,一直处于内核空间中运行,并且也只处于内核空间运行。当它执行到最后,将start_kernel中所有的初始化执行完成后,会在内核中启动一个kernel_init内核线程和一个kthreadd内核线程,kernel_init内核线程执行到最后会通过execve系统调用执行转变为我们所熟悉的init进程,而kthreadd内核线程是内核用于管理调度其他的内核线程的守护线程。在最后init_task将变成一个idle进程,用于在CPU没有进程运行时运行它,它在此时仅仅用于空转。

init_task进程也就是0号进程,也是idle 进程,也是swapper进程, 执行完 start_kernel 函数后,运行队列rq 的idle 的指针会指向init_task的静态定义的地址,并不会放到运行队列中。

kernel_init进程 ,也是1号进程,也是系统的init进程,也有的会说systemd进程。

kthreadd进程,也是2号进程,负责创建内核线程,而用户空间创建进程,就需要调用fork系统调用。

上面这三个进程的随着内核启动,变化的流程图如下:

 

上面标注的idle进程其实不严谨,此时还没有变成idle进程,还是正常运行的进程。

二、sched_init 调度初始化

在start_kernel中对调度器进行初始化的函数就是sched_init,其主要工作为

  • 对相关数据结构分配内存
  • 初始化root_task_group
  • 初始化每个CPU的rq队列(包括其中的cfs队列和实时进程队列)
  • 将init_task进程转变为idle进程 (只是把rq的idle指针指向 init_task)

需要说明的是init_task在这里会被转变为idle进程,但是它还会继续执行初始化工作,相当于这里只是给init_task挂个idle进程的名号,它其实还是init_task进程,只有到最后init_task进程开启了kernel_init和kthreadd进程之后,才转变为真正意义上的idle进程。

内核源码路径:kernel/kernel/sched/core.c

void __init sched_init(void)
{
        unsigned long ptr = 0;
        int i;

        /* Make sure the linker didn't screw up */
        BUG_ON(&idle_sched_class + 1 != &fair_sched_class ||
               &fair_sched_class + 1 != &rt_sched_class ||
               &rt_sched_class + 1   != &dl_sched_class);
#ifdef CONFIG_SMP
        BUG_ON(&dl_sched_class + 1 != &stop_sched_class);
#endif

        wait_bit_init();
/* 计算所需要分配的数据结构空间 */
#ifdef CONFIG_FAIR_GROUP_SCHED
        ptr += 2 * nr_cpu_ids * sizeof(void **);
#endif
#ifdef CONFIG_RT_GROUP_SCHED
        ptr += 2 * nr_cpu_ids * sizeof(void **);
#endif
        if (ptr) {/* 分配内存 */
                ptr = (unsigned long)kzalloc(ptr, GFP_NOWAIT);

#ifdef CONFIG_FAIR_GROUP_SCHED
           /* 设置 root_task_group 每个CPU上的CFS调度实体指针se */
                root_task_group.se = (struct sched_entity **)ptr;
                ptr += nr_cpu_ids * sizeof(void **);

            /* 设置 root_task_group 每个CPU上的CFS运行队列指针cfs_rq */
                root_task_group.cfs_rq = (struct cfs_rq **)ptr;
                ptr += nr_cpu_ids * sizeof(void **);

                root_task_group.shares = ROOT_TASK_GROUP_LOAD;
                init_cfs_bandwidth(&root_task_group.cfs_bandwidth);
#endif /* CONFIG_FAIR_GROUP_SCHED */
#ifdef CONFIG_RT_GROUP_SCHED
            /* 设置 root_task_group 每个CPU上的实时调度实体指针se */
                root_task_group.rt_se = (struct sched_rt_entity **)ptr;
                ptr += nr_cpu_ids * sizeof(void **);

                root_task_group.rt_rq = (struct rt_rq **)ptr;
                ptr += nr_cpu_ids * sizeof(void **);

#endif /* CONFIG_RT_GROUP_SCHED */
        }
#ifdef CONFIG_CPUMASK_OFFSTACK
        for_each_possible_cpu(i) {
                per_cpu(load_balance_mask, i) = (cpumask_var_t)kzalloc_node(
                        cpumask_size(), GFP_KERNEL, cpu_to_node(i));
                per_cpu(select_idle_mask, i) = (cpumask_var_t)kzalloc_node(
                        cpumask_size(), GFP_KERNEL, cpu_to_node(i));
        }
#endif /* CONFIG_CPUMASK_OFFSTACK */
        /* 初始化实时进程的带宽限制,用于设置实时进程在CPU中所占用比的 */
        init_rt_bandwidth(&def_rt_bandwidth, global_rt_period(), global_rt_runtime());
        init_dl_bandwidth(&def_dl_bandwidth, global_rt_period(), global_rt_runtime());

#ifdef CONFIG_SMP
        /* 初始化默认的调度域,调度域包含一个或多个CPU,负载均衡是在调度域内执行的,相互之间隔离 */
        init_defrootdomain();
#endif

#ifdef CONFIG_RT_GROUP_SCHED/* 初始化实时进程的带宽限制,用于设置实时进程在CPU中所占用比的 */
        init_rt_bandwidth(&root_task_group.rt_bandwidth,
                        global_rt_period(), global_rt_runtime());
#endif /* CONFIG_RT_GROUP_SCHED */

#ifdef CONFIG_CGROUP_SCHED
        task_group_cache = KMEM_CACHE(task_group, 0);
        /* 将分配好空间的 root_task_group 加入 task_groups 链表 */
        list_add(&root_task_group.list, &task_groups);
        INIT_LIST_HEAD(&root_task_group.children);
        INIT_LIST_HEAD(&root_task_group.siblings);
        /* 自动分组初始化
        autogroup_init(&init_task);

#endif /* CONFIG_CGROUP_SCHED */
       for_each_possible_cpu(i) { /* 遍历设置每一个CPU */
                struct rq *rq;

                rq = cpu_rq(i);/* 获取CPUi的rq队列 */
                raw_spin_lock_init(&rq->lock);/* 初始化rq队列的自旋锁 */
                rq->nr_running = 0;/* CPU运行队列中调度实体(sched_entity)数量为0 */
                rq->calc_load_active = 0;/* CPU负载 */
                rq->calc_load_update = jiffies + LOAD_FREQ;/* 负载下次更新时间 */
                init_cfs_rq(&rq->cfs);/* 初始化CFS运行队列 */
                init_rt_rq(&rq->rt);/* 初始化实时进程运行队列 */
                init_dl_rq(&rq->dl);
#ifdef CONFIG_FAIR_GROUP_SCHED
                INIT_LIST_HEAD(&rq->leaf_cfs_rq_list);
                rq->tmp_alone_branch = &rq->leaf_cfs_rq_list;
                /*
                 * How much CPU bandwidth does root_task_group get?
                 *
                 * In case of task-groups formed thr' the cgroup filesystem, it
                 * gets 100% of the CPU resources in the system. This overall
                 * system CPU resource is divided among the tasks of
                 * root_task_group and its child task-groups in a fair manner,
                 * based on each entity's (task or task-group's) weight
                 * (se->load.weight).
                 *
                 * In other words, if root_task_group has 10 tasks of weight
                 * 1024) and two child groups A0 and A1 (of weight 1024 each),
                 * then A0's share of the CPU resource is:
                 *
                 *      A0's bandwidth = 1024 / (10*1024 + 1024 + 1024) = 8.33%
                 *
                 * We achieve this by letting root_task_group's tasks sit
                 * directly in rq->cfs (i.e root_task_group->se[] = NULL).
                 */
                init_tg_cfs_entry(&root_task_group, &rq->cfs, NULL, i, NULL);
#endif /* CONFIG_FAIR_GROUP_SCHED */

                rq->rt.rt_runtime = def_rt_bandwidth.rt_runtime;
#ifdef CONFIG_RT_GROUP_SCHED
                init_tg_rt_entry(&root_task_group, &rq->rt, NULL, i, NULL);
#endif
#ifdef CONFIG_SMP/* 这些参数都是负载均衡使用的 */
                rq->sd = NULL;
                rq->rd = NULL;
                rq->cpu_capacity = rq->cpu_capacity_orig = SCHED_CAPACITY_SCALE;
                rq->balance_callback = NULL;
                rq->active_balance = 0;
                rq->next_balance = jiffies;
                rq->push_cpu = 0;
                rq->cpu = i;
                rq->online = 0;
                rq->idle_stamp = 0;
                rq->avg_idle = 2*sysctl_sched_migration_cost;
                rq->max_idle_balance_cost = sysctl_sched_migration_cost;

                INIT_LIST_HEAD(&rq->cfs_tasks);
            /* 将CPU运行队列加入到默认调度域中 */
                rq_attach_root(rq, &def_root_domain);
#ifdef CONFIG_NO_HZ_COMMON
                /* 该队列最后一次更新cpu_load的时间值为当前 */
                rq->last_blocked_load_update_tick = jiffies;
                atomic_set(&rq->nohz_flags, 0);/* 动态时钟使用的标志位,初始时动态时钟是不使用的 */

                rq_csd_init(rq, &rq->nohz_csd, nohz_csd_func);
#endif
#ifdef CONFIG_HOTPLUG_CPU
                rcuwait_init(&rq->hotplug_wait);
#endif
#endif /* CONFIG_SMP *
   /* 初始化运行队列定时器,这个是高精度定时器,但是只是初始化,这时并没有使用 */
                hrtick_rq_init(rq);
                atomic_set(&rq->nr_iowait, 0);
        }
       /* 设置 init_task 进程的权重 */
        set_load_weight(&init_task, false);

        /*
         * The boot idle thread does lazy MMU switching as well:
         */
        mmgrab(&init_mm);
        enter_lazy_tlb(&init_mm, current);

        /*
         * Make us the idle thread. Technically, schedule() should not be
         * called from this thread, however somewhere below it might be,
         * but because we are the idle thread, we just pick up running again
         * when this runqueue becomes "idle".
         */
/* 将当前进程初始化为idle进程,idle进程用于当CPU没有进程可运行时运行,空转 */
        init_idle(current, smp_processor_id());
 /* 下次负载更新时间(是一个相对时间) */
        calc_load_update = jiffies + LOAD_FREQ;

#ifdef CONFIG_SMP
        idle_thread_set_boot_cpu();
#endif
        init_sched_fair_class();

        init_schedstats();

        psi_init();

        init_uclamp();
 /* 这里只是标记调度器开始运行了,但是此时系统只有一个init_task(idle)进程,并且定时器都还没启动。并不会调度到其他进程,也没有其他进程可供调度 */
        scheduler_running = 1;
}
 执行到此时内核只有一个进程init_task,current就为init_task。之后的init进程在初始化到最后的rest_init中启动 */

sched_init 初始化之后,各个CPU 上都有各自的rq,如下图所示: 

 

调度器的初始化还是比较简单的,毕竟调度器的核心不在此,重头戏在它的运行时处理,之后的文章会详细分析调度器的运行时处理。

三、进程加入运行队列的时机

只有处于TASK_RUNNING状态下的进程才能够加入到调度器,其他状态都不行,也就说明了,当一个进程处于睡眠、挂起状态的时候是不存在于调度器中的,而进程加入调度器的时机如下:

  • 当进程创建完成时,进程刚创建完成时,即使它运行起来立即调用sleep()进程睡眠,它也必定先会加入到调度器,因为实际上它加入调度器后自己还需要进行一定的初始化和操作,才会调用到我们的“立即”sleep()。
  • 当进程被唤醒时,也使用sleep的例子说明,我们平常写程序使用的sleep()函数实现原理就是通过系统调用将进程状态改为TASK_INTERRUPTIBLE,然后移出运行队列,并且启动一个定时器,在定时器到期后唤醒进程,再重新放入运行队列。

(1)进程创建的函数:sched_fork

//kernel/kernel/sched/core.c

/*
 * fork()/clone()-time setup:
 */
int sched_fork(unsigned long clone_flags, struct task_struct *p)
{
        /* 初始化跟调度相关的值,比如调度实体,运行时间等 */
        __sched_fork(clone_flags, p);
        /*
         * We mark the process as NEW here. This guarantees that
         * nobody will actually run it, and a signal or other external
         * event cannot wake it up and insert it on the runqueue either.
         */
/*标记为运行状态,表明此进程正在运行或准备好运行,实际上没有真正在CPU上运行,这里只是导致了外部信号和事件不能够唤醒此进程,之后将它插入到运行队列中*/

        p->state = TASK_NEW;

        /*
         * Make sure we do not leak PI boosting priority to the child.
         */
        p->prio = current->normal_prio;//根据父进程的运行优先级设置设置进程的优先级

        uclamp_fork(p);

        /*
         * Revert to default priority/policy on fork if requested.
         */
        //    如果需要重新设置优先级
        if (unlikely(p->sched_reset_on_fork)) {
                     /* 如果是dl调度或者实时调度 */
                if (task_has_dl_policy(p) || task_has_rt_policy(p)) {
                        p->policy = SCHED_NORMAL;//调度策略为SCHED_NORMAL,这个选项将使用CFS调度
                        p->static_prio = NICE_TO_PRIO(0);//根据默认nice值设置静态优先级
                        p->rt_priority = 0;实时优先级为0 
                } else if (PRIO_TO_NICE(p->static_prio) < 0)
                        p->static_prio = NICE_TO_PRIO(0);//根据默认nice值设置静态优先级

                p->prio = p->normal_prio = p->static_prio;
                set_load_weight(p, false);/* 设置进程权重 */

                /*
                 * We don't need the reset flag anymore after the fork. It has
                 * fulfilled its duty:
                 */
         /* sched_reset_on_fork成员在之后已经不需要使用了,直接设为0 */

                p->sched_reset_on_fork = 0;
        }

        if (dl_prio(p->prio))
                return -EAGAIN;
        else if (rt_prio(p->prio))/* 根据优先级判断,如果是实时进程,设置其调度类为rt_sched_class */
                p->sched_class = &rt_sched_class;
        else
                p->sched_class = &fair_sched_class;设置其调度类为fair_sched_class

        init_entity_runnable_average(&p->se);

#ifdef CONFIG_SCHED_INFO
        if (likely(sched_info_on()))
                memset(&p->sched_info, 0, sizeof(p->sched_info));
#endif
#if defined(CONFIG_SMP)
        p->on_cpu = 0;
#endif
        init_task_preempt_count(p); /* 初始化该进程为内核禁止抢占 */
#ifdef CONFIG_HAVE_PREEMPT_LAZY
        task_thread_info(p)->preempt_lazy_count = 0;
#endif
#ifdef CONFIG_SMP
        plist_node_init(&p->pushable_tasks, MAX_PRIO);
        RB_CLEAR_NODE(&p->pushable_dl_tasks);
#endif
        return 0;
}

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

  • 初始化进程p的一些变量(实时进程和普通进程通用的那些变量)
  • 根据进程p的优先级设置其调度类(实时进程优先级:0~99  普通进程优先级:100~139)
  • 初始化进程p禁止内核抢占(因为当CPU执行到进程p时,进程p还需要进行一些初始化)

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

源码路径:kernel/kernel/sched/fair.c

static void task_fork_fair(struct task_struct *p)
{
        struct cfs_rq *cfs_rq;
        struct sched_entity *se = &p->se, *curr;  /* 进程p的调度实体se */
        struct rq *rq = this_rq();/* 获取当前CPU rq */
        struct rq_flags rf;

        rq_lock(rq, &rf);上锁并保存中断记录
        update_rq_clock(rq);/* 更新rq运行时间 */

        cfs_rq = task_cfs_rq(current);
        curr = cfs_rq->curr;/* 设置当前进程所在队列为父进程所在队列 */
        if (curr) {
                /* 更新当前进程运行时间 */
                update_curr(cfs_rq);
                /* 将父进程的虚拟运行时间赋给了新进程的虚拟运行时间 */
                se->vruntime = curr->vruntime;
        }
        place_entity(cfs_rq, se, 1); /* 调整了se的虚拟运行时间 */

        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_lazy(rq);
        }
/* 保证了进程p的vruntime是运行队列中最小的(这里暂时不确定是不是这个用法,不过确实是最小的了) */
        se->vruntime -= cfs_rq->min_vruntime;
        rq_unlock(rq, &rf);/* 解锁,还原中断记录 */
}

在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中。

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

void wake_up_new_task(struct task_struct *p)
{
        struct rq_flags rf;
        struct rq *rq;

        raw_spin_lock_irqsave(&p->pi_lock, rf.flags);
        p->state = TASK_RUNNING;
#ifdef CONFIG_SMP
        /*
         * Fork balancing, do it here and not earlier because:
         *  - cpus_ptr can change in the fork path
         *  - any previously selected CPU might disappear through hotplug
         *
         * Use __set_task_cpu() to avoid calling sched_class::migrate_task_rq,
         * as we're not fully set-up yet.
         */
        p->recent_used_cpu = task_cpu(p);
        rseq_migrate(p);
        /* 为进程选择一个合适的CPU */
        __set_task_cpu(p, select_task_rq(p, task_cpu(p), SD_BALANCE_FORK, 0));
#endif
        rq = __task_rq_lock(p, &rf);
        update_rq_clock(rq);
        post_init_entity_util_avg(p);/* 这里是跟多核负载均衡有关 */

        activate_task(rq, p, ENQUEUE_NOCLOCK);/* 将进程加入到CPU的运行队列 */
        trace_sched_wakeup_new(p);/* 跟调试有关 */
        check_preempt_curr(rq, p, WF_FORK);
#ifdef CONFIG_SMP
        if (p->sched_class->task_woken) {
                /*
                 * Nothing relies on rq->lock after this, so its fine to
                 * drop it.
                 */
                rq_unpin_lock(rq, &rf);
                p->sched_class->task_woken(rq, p);
                rq_repin_lock(rq, &rf);
        }
#endif
        task_rq_unlock(rq, p, &rf);
}

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

//kernel/kernel/sched/fair.c
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;
        int idle_h_nr_running = task_has_idle_policy(p);
        int task_new = !(flags & ENQUEUE_WAKEUP);

        /*
         * The code below (indirectly) updates schedutil which looks at
         * the cfs_rq utilization to select a frequency.
         * Let's add the task's estimated utilization to the cfs_rq's
         * estimated utilization, before we update schedutil.
         */
        util_est_enqueue(&rq->cfs, p);

        /*
         * If in_iowait is set, the code below may not trigger any cpufreq
         * utilization updates, so do it here explicitly with the IOWAIT flag
         * passed.
         */
        if (p->in_iowait)
                cpufreq_update_util(rq, SCHED_CPUFREQ_IOWAIT);
/*这里是一个迭代,我们知道,进程有可能是处于一个进程组中的,所以当这个处于进程组中的进程加入到该进程组的队列中时,要对此队列向上迭代 */

        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);/* 加入到队列中 */

                cfs_rq->h_nr_running++;
                cfs_rq->idle_h_nr_running += idle_h_nr_running;

                /* end evaluation on encountering a throttled cfs_rq */
                if (cfs_rq_throttled(cfs_rq))
                        goto enqueue_throttle;

                flags = ENQUEUE_WAKEUP;
        }

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

                update_load_avg(cfs_rq, se, UPDATE_TG);
                se_update_runnable(se);
                update_cfs_group(se);

                cfs_rq->h_nr_running++;
                cfs_rq->idle_h_nr_running += idle_h_nr_running;

                /* end evaluation on encountering a throttled cfs_rq */
                if (cfs_rq_throttled(cfs_rq))
                        goto enqueue_throttle;

               /*
                * One parent has been throttled and cfs_rq removed from the
                * list. Add it back to not break the leaf list.
                */
               if (throttled_hierarchy(cfs_rq))
                       list_add_leaf_cfs_rq(cfs_rq);
        }

        /* At this point se is NULL and we are at root level*/
        add_nr_running(rq, 1); /* 当前CPU运行队列活动进程数 + 1 */

        /*
         * Since new tasks are assigned an initial util_avg equal to
         * half of the spare capacity of their CPU, tiny tasks have the
         * ability to cross the overutilized threshold, which will
         * result in the load balancer ruining all the task placement
         * done by EAS. As a way to mitigate that effect, do not account
         * for the first enqueue operation of new tasks during the
         * overutilized flag detection.
         *
         * A better way of solving this problem would be to wait for
         * the PELT signals of tasks to converge before taking them
         * into account, but that is not straightforward to implement,
         * and the following generally works well enough in practice.
         */
        if (!task_new)
                update_overutilized_status(rq);

enqueue_throttle:
        if (cfs_bandwidth_used()) {
                /*
                 * When bandwidth control is enabled; the cfs_rq_throttled()
                 * breaks in the above iteration can result in incomplete
                 * leaf list maintenance, resulting in triggering the assertion
                 * below.
                 */
                for_each_sched_entity(se) {
                        cfs_rq = cfs_rq_of(se);

                        if (list_add_leaf_cfs_rq(cfs_rq))
                                break;
                }
        }

        assert_list_leaf_cfs_rq(rq);

        hrtick_update(rq);/* 设置下次调度中断发生时间 */
}

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

// kernel/kernel/sched/fair.c
static void
enqueue_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, int flags)
{
        bool renorm = !(flags & ENQUEUE_WAKEUP) || (flags & ENQUEUE_MIGRATED);
        bool curr = cfs_rq->curr == se;

        /*
         * If we're the current task, we must renormalise before calling
         * update_curr().
         */
        if (renorm && curr)
                se->vruntime += cfs_rq->min_vruntime;
/* 更新当前进程运行时间和虚拟运行时间 */
        update_curr(cfs_rq);

        /*
         * Otherwise, renormalise after, such that we're placed at the current
         * moment in time, instead of some random moment in the past. Being
         * placed in the past could significantly boost this task to the
         * fairness detriment of existing tasks.
         */
        if (renorm && !curr)
                se->vruntime += cfs_rq->min_vruntime;

        /*
         * When enqueuing a sched_entity, we must:
         *   - Update loads to have both entity and cfs_rq synced with now.
         *   - Add its load to cfs_rq->runnable_avg
         *   - For group_entity, update its weight to reflect the new share of
         *     its group cfs_rq
         *   - Add its new weight to cfs_rq->load.weight
         */
/* 更新cfs_rq队列总权重(就是在原有基础上加上se的权重) */
        update_load_avg(cfs_rq, se, UPDATE_TG | DO_ATTACH);
        se_update_runnable(se);
        update_cfs_group(se);
        account_entity_enqueue(cfs_rq, se);

/* 新建的进程flags为0,不会执行这里 */
        if (flags & ENQUEUE_WAKEUP)
                place_entity(cfs_rq, se, 0);

        check_schedstat_required();
        update_stats_enqueue(cfs_rq, se, flags);
        check_spread(cfs_rq, se);
/* 将se插入到运行队列cfs_rq的红黑树中 */
        if (!curr)
                __enqueue_entity(cfs_rq, se);
        se->on_rq = 1;/* 将se的on_rq标记为1 */

        /*
         * When bandwidth control is enabled, cfs might have been removed
         * because of a parent been throttled but cfs->nr_running > 1. Try to
         * add it unconditionnally.
         */
/* 如果cfs_rq的队列中只有一个进程,这里做处理 */
        if (cfs_rq->nr_running == 1 || cfs_bandwidth_used())
                list_add_leaf_cfs_rq(cfs_rq);

        if (cfs_rq->nr_running == 1)
                check_enqueue_throttle(cfs_rq);
}

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/910371.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

雷池社区版7.1新版本自定义NGINX配置分析

简单介绍雷池&#xff0c;是一款简单好用, 效果突出的 Web 应用防火墙(WAF)&#xff0c;可以保护 Web 服务不受黑客攻击。 雷池通过阻断流向 Web 服务的恶意 HTTP 流量来保护 Web 服务。雷池作为反向代理接入网络&#xff0c;通过在 Web 服务前部署雷池&#xff0c;可在 Web 服…

基于 AIGC 的糖尿病健康管理平台

项目介绍 Healthoney为一款以大语言模型为核心&#xff0c;通过分析用户的各项信息为用户生成定制化的饮食运动计划&#xff0c;提高糖尿病患者生活质量的智能健康管理平台。通过集成体重趋势预测、健康数据分析、食物热量查询、饮食计划个性化定制等功能&#xff0c;帮助用户…

qt QTableWidgetItem详解

1、概述 QTableWidgetItem 是 Qt 框架中的一个类&#xff0c;专门用于在 QTableWidget&#xff08;一个基于项的表格视图&#xff09;中表示单个单元格的内容。QTableWidget 继承自 QAbstractItemView&#xff0c;而 QTableWidgetItem 则作为表格中的一个单元格项&#xff0c;…

常见 HTTP 状态码分类和解释及服务端向前端返回响应时的最完整格式

目前的开发项目&#xff0c;准备明年的国产化&#xff0c;用了十年的自研系统借这个机会全部重写&#xff0c;订立更严格的规范&#xff0c;这里把返回格式及对应状态码记录一下。 常见 HTTP 状态码及解释 HTTP 状态码用于表示客户端请求的响应状态&#xff0c;它们分为五类&a…

卖模版还能赚到钱吗?

说到赚钱&#xff0c;我想大部分人都会感兴趣。但如果告诉大家现阶段卖模板也能赚钱&#xff0c;可能还是有人不信。我要说说我的观察了。 本文可在公众号「德育处主任」免费阅读 我是一只临期程序猿&#xff0c;我最早接触到“模板能卖钱”这个概念是在模板王里。模板王平台上…

基于ssm的个人健康管理系统

项目描述 临近学期结束&#xff0c;还是毕业设计&#xff0c;你还在做java程序网络编程&#xff0c;期末作业&#xff0c;老师的作业要求觉得大了吗?不知道毕业设计该怎么办?网页功能的数量是否太多?没有合适的类型或系统?等等。这里根据疫情当下&#xff0c;你想解决的问…

DB-GPT系列(三):底层大模型设置(开源模型、在线模型)

前面两篇文章分别对 DB-GPT 的总体情况进行了介绍&#xff0c;同时涵盖了镜像一键部署与源码部署这两种部署方式。 DB-GPT系列&#xff08;一&#xff09;&#xff1a;DB-GPT能帮你做什么&#xff1f; DB-GPT系列&#xff08;二&#xff09;&#xff1a;DB-GPT部署&#xff0…

易思智能物流无人值守系统 ExportReport SQL注入漏洞复现

0x01 产品简介 易思无人值守智能物流系统‌是一款集成了人工智能、机器人技术和物联网技术的创新产品。它能够自主完成货物存储、检索、分拣、装载以及配送等物流作业,帮助企业实现无人值守的智能物流运营,提高效率、降低成本,为现代物流行业带来新的发展机遇。该系统旨在为…

stm32 ADC实例解析(3)-多通道采集互相干扰的问题

文章目录 一、问题现象&#xff1a;二、原因分析&#xff1a;1、测量值不准问题分析&#xff1a;2、采样干扰问题分析 三、解决办法&#xff1a;1、硬件&#xff1a;&#xff08;1&#xff09;、电源供电&#xff08;2&#xff09;、引脚电容&#xff08;3&#xff09;、减少采…

ASP.NET Core 路由规则,自定义特性路由 ,IActionConstraint 路由约束 总结 mvc

资料 资料 路由服务 路由服务是在 Program.cs 中使用 builder.Services.AddRouting()注册的&#xff0c; 只是默认在 builder 之前已经注册过了&#xff0c;无需我们再次注册。 AddRouting()方法必须在 UseRouting()方法之前运行&#xff0c;它是路由的基础服务。 MapContro…

在数据库设计中,如何避免全表扫描?

2024年10月NJSD技术盛典暨第十届NJSD软件开发者大会、第八届IAS互联网架构大会在南京召开。百度文心快码总经理臧志分享了《AI原生研发新范式的实践与思考》&#xff0c;探讨了大模型赋能下的研发变革及如何在公司和行业中落地&#xff0c;AI原生研发新范式的内涵和推动经验。 …

Maven的安装配置

文章目录 一、MVN 的下载二、配置maven2.1、更改maven/conf/settings.xml配置2.2、配置环境变量一、MVN 的下载 还是那句话,要去就去官网或者github,别的地方不要去下载。我们下载binaries/ 目录下的 cd /opt/server wget https://downloads.apache.org/maven/maven-3/3.9.6/…

ReactPress系列—NestJS 服务端开发流程简介

ReactPress Github项目地址&#xff1a;https://github.com/fecommunity/reactpress 欢迎提出宝贵的建议&#xff0c;感谢Star。 NestJS 服务端开发流程简介 NestJS 是一个用于构建高效、可靠和可扩展的服务器端应用程序的框架。它使用 TypeScript&#xff08;但也支持纯 Java…

Redis数据库测试和缓存穿透、雪崩、击穿

Redis数据库测试实验 实验要求 1.新建一张user表&#xff0c;在表内插入10000条数据。 2.①通过jdbc查询这10000条数据&#xff0c;记录查询时间。 ②通过redis查询这10000条数据&#xff0c;记录查询时间。 3.①再次查询这一万条数据&#xff0c;要求根据年龄进行排序&#…

JavaCV 图像边缘检测 之 Canny 算法

&#x1f9d1; 博主简介&#xff1a;CSDN博客专家&#xff0c;历代文学网&#xff08;PC端可以访问&#xff1a;https://literature.sinhy.com/#/literature?__c1000&#xff0c;移动端可微信小程序搜索“历代文学”&#xff09;总架构师&#xff0c;15年工作经验&#xff0c;…

Java Agent使用

文章目录 基本使用premain使用场景 agentmain 关于tools.jar https://docs.oracle.com/en/java/javase/20/docs/specs/jvmti.html com.sun的API&#xff0c;如果使用其他厂商的JVM&#xff0c;可能没有这个API了&#xff0c;比如Eclipse的J9 https://www.ibm.com/docs/en/sdk…

解决客服打字慢的快捷回复软件

客服每天都要打字回复咨询&#xff0c;打字慢必然影响回复效率&#xff0c;聊天宝就是一款解决客服打字慢的快捷回复软件 前言 总所周知&#xff0c;客服每天都要打字回复咨询&#xff0c;打字慢必然影响回复效率&#xff0c;所以解决打字速度慢的问题&#xff0c;就是很多客服…

【2024最新版Kotlin教程】Kotlin第一行代码系列第六课-嵌套类,数据类,密封类

【2024最新版Kotlin教程】Kotlin第一行代码系列第六课-嵌套类&#xff0c;数据类&#xff0c;密封类 数据类在之前的课程已经讲了&#xff0c;嵌套类和密封类可以不学&#xff0c;很少用到的。 但为了系统的完整性还是说一下 一、嵌套类&#xff1a; 选学&#xff1a;按我经…

uniapp 开发公众号 h5 页面 错误 “redirect_uri 参数错误“

公众号 h5 支付 调错避坑 1. 一直显示以下错误 ”redirect_uri 失效了“不用管直接 打包上传服务器 2. 直接在”微信开发者工具“ 进行调试&#xff08; 使用线上地址访问 &#xff09;就能看到真实 接口 错误&#xff0c;在进行调试

闪存学习_2:Flash-Aware Computing from Jihong Kim

闪存学习_2&#xff1a;Flash-Aware Computing from Jihong Kim【1】 一、三个闪存可靠性问题二、内存的分类三、NAND 闪存和 NOR 闪存四、HDD和SSD比较Reference 一、三个闪存可靠性问题 耐性&#xff08;即寿命&#xff09;&#xff1a;最多能经受编程和擦除的次数。数据保留…