linux调度器源码分析 - 初始化(二)

简介: 本文为原创,转载请注明:http://blog.chinaunix.net/uid/26772321.html 引言   上期文章linux调度器源码分析 - 概述(一)已经把调度器相关的数据结构介绍了一遍,本篇着重通过代码说明调度器在系统启动初始化阶段是如何初始化和工作的。
本文为原创,转载请注明:http://blog.chinaunix.net/uid/26772321.html

引言

  上期文章linux调度器源码分析 - 概述(一)已经把调度器相关的数据结构介绍了一遍,本篇着重通过代码说明调度器在系统启动初始化阶段是如何初始化和工作的。通过上期文章我们知道,在多核CPU和SMP系统中,每个CPU(多核COU中的每个核)都有自己的struct rq队列,而rq队列中又有着自己的struct cfs_rq和struct rt_rq。在初始化时就是对这三个结构进行初始化。

init_task和init进程

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

/* 代码地址: linux/init/Main.c */
asmlinkage __visible void __init start_kernel(void)
   在start_kerenl函数中,进行了系统启动过程中几乎所有重要的初始化(有一部分在boot中初始化,有一部分在start_kernel之前的汇编代码进行初始化),包括内存、页表、必要数据结构、信号、调度器、硬件设备等。而这些初始化是由谁来负责的?就是由init_task这个进程。init_task是静态定义的一个进程,也就是说当内核被放入内存时,它就已经存在,它没有自己的用户空间,一直处于内核空间中运行,并且也只处于内核空间运行。当它执行到最后,将start_kernel中所有的初始化执行完成后,会在内核中启动一个kernel_init内核线程和一个kthreadd内核线程,kernel_init内核线程执行到最后会通过execve系统调用执行转变为我们所熟悉的init进程,而kthreadd内核线程是内核用于管理调度其他的内核线程的守护线程。在最后init_task将变成一个idle进程,用于在CPU没有进程运行时运行它,它在此时仅仅用于空转。


sched_init

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

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

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

/* 代码路径: 内核源代码目录/kernel/sched/Core.c */

/* 执行到此时内核只有一个进程init_task,current就为init_task。之后的init进程在初始化到最后的rest_init中启动 */
void __init sched_init(void)
{
    int i, j;
    unsigned long alloc_size = 0, ptr;

    /* 计算所需要分配的数据结构空间 */
#ifdef CONFIG_FAIR_GROUP_SCHED
    alloc_size += 2 * nr_cpu_ids * sizeof(void **);
#endif

#ifdef CONFIG_RT_GROUP_SCHED
    alloc_size += 2 * nr_cpu_ids * sizeof(void **);
#endif
#ifdef CONFIG_CPUMASK_OFFSTACK
    alloc_size += num_possible_cpus() * cpumask_size();
#endif
    if (alloc_size) {
        /* 分配内存 */
        ptr = (unsigned long)kzalloc(alloc_size, GFP_NOWAIT);

#ifdef CONFIG_FAIR_GROUP_SCHED
        /* 设置 root_task_group 每个CPU上的调度实体指针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 **);

#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 每个CPU上的实时运行队列指针rt_rq */
        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) = (void *)ptr;
            ptr += cpumask_size();
        }
#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
    /* 将分配好空间的 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);
    /* 自动分组初始化,每个tty(控制台)动态的创建进程组,这样就可以降低高负载情况下的桌面延迟 */
    autogroup_init(&init_task);

#endif /* CONFIG_CGROUP_SCHED */
    /* 遍历设置每一个CPU */
    for_each_possible_cpu(i) {
        struct rq *rq;
        /* 获取CPUi的rq队列 */
        rq = cpu_rq(i);
        /* 初始化rq队列的自旋锁 */
        raw_spin_lock_init(&rq->lock);
        /* CPU运行队列中调度实体(sched_entity)数量为0 */
        rq->nr_running = 0;
        /* CPU负载 */
        rq->calc_load_active = 0;
        /* 负载下次更新时间 */
        rq->calc_load_update = jiffies + LOAD_FREQ;
        /* 初始化CFS运行队列 */
        init_cfs_rq(&rq->cfs);
        /* 初始化实时进程运行队列 */
        init_rt_rq(&rq->rt, rq);
        init_dl_rq(&rq->dl, rq);
#ifdef CONFIG_FAIR_GROUP_SCHED
        root_task_group.shares = ROOT_TASK_GROUP_LOAD;
        INIT_LIST_HEAD(&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).
         */
        /* 初始化CFS的带宽限制,用于设置普通进程在CPU中所占用比的 */
        init_cfs_bandwidth(&root_task_group.cfs_bandwidth);
        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
        /* 初始化该队列所保存的每个CPU的负载情况 */
        for (j = 0; j CPU_LOAD_IDX_MAX; j++)
            rq->cpu_load[j] = 0;
        /* 该队列最后一次更新cpu_load的时间值为当前 */
        rq->last_load_update_tick = jiffies;

#ifdef CONFIG_SMP
        /* 这些参数都是负载均衡使用的 */
        rq->sd = NULL;
        rq->rd = NULL;
        rq->cpu_capacity = SCHED_CAPACITY_SCALE;
        rq->post_schedule = 0;
        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
        /* 动态时钟使用的标志位,初始时动态时钟是不使用的 */
        rq->nohz_flags = 0;
#endif
#ifdef CONFIG_NO_HZ_FULL
        /* 也是动态时钟才使用的标志位,用于保存上次调度tick发生时间 */
        rq->last_sched_tick = 0;
#endif
#endif
        /* 初始化运行队列定时器,这个是高精度定时器,但是只是初始化,这时并没有使用 */
        init_rq_hrtick(rq);
        atomic_set(&rq->nr_iowait, 0);
    }
    /* 设置 init_task 进程的权重 */
    set_load_weight(&init_task);

#ifdef CONFIG_PREEMPT_NOTIFIERS
    /* 初始化通知链 */
    INIT_HLIST_HEAD(&init_task.preempt_notifiers);
#endif

    /*
     * The boot idle thread does lazy MMU switching as well:
     */
    atomic_inc(&init_mm.mm_count);
    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;

    /*
     * During early bootup we pretend to be a normal task:
     */
    /* 设置idle进程使用CFS调度策略 */
    current->sched_class = &fair_sched_class;

#ifdef CONFIG_SMP
    zalloc_cpumask_var(&sched_domains_tmpmask, GFP_NOWAIT);
    /* May be allocated at isolcpus cmdline parse time */
    if (cpu_isolated_map == NULL)
        zalloc_cpumask_var(&cpu_isolated_map, GFP_NOWAIT);
    idle_thread_set_boot_cpu();
    set_cpu_rq_start_time();
#endif
    init_sched_fair_class();
    /* 这里只是标记调度器开始运行了,但是此时系统只有一个init_task(idle)进程,并且定时器都还没启动。并不会调度到其他进程,也没有其他进程可供调度 */
    scheduler_running = 1;
}

总结

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






目录
相关文章
|
安全 Shell Linux
【Shell 命令集合 系统设置 】Linux 初始化系统设置setup命令 使用指南
【Shell 命令集合 系统设置 】Linux 初始化系统设置setup命令 使用指南
264 0
|
Shell Linux C语言
【Shell 命令集合 磁盘维护 】Linux 创建一个初始化内存盘 mkinitrd命令使用教程
【Shell 命令集合 磁盘维护 】Linux 创建一个初始化内存盘 mkinitrd命令使用教程
279 0
|
安全 关系型数据库 MySQL
Linux 实用小脚本系列(2)----mysql安全初始化脚本的免交互执行--mysql_secure_installation
Linux 实用小脚本系列(2)----mysql安全初始化脚本的免交互执行--mysql_secure_installation
426 0
|
算法 Linux 调度
深入理解Linux内核调度器:从基础到优化####
本文旨在通过剖析Linux操作系统的心脏——内核调度器,为读者揭开其高效管理CPU资源的神秘面纱。不同于传统的摘要概述,本文将直接以一段精简代码片段作为引子,展示一个简化版的任务调度逻辑,随后逐步深入,详细探讨Linux内核调度器的工作原理、关键数据结构、调度算法演变以及性能调优策略,旨在为开发者与系统管理员提供一份实用的技术指南。 ####
437 4
|
缓存 算法 Linux
深入理解Linux内核调度器:公平性与性能的平衡####
真知灼见 本文将带你深入了解Linux操作系统的核心组件之一——完全公平调度器(CFS),通过剖析其设计原理、工作机制以及在实际系统中的应用效果,揭示它是如何在众多进程间实现资源分配的公平性与高效性的。不同于传统的摘要概述,本文旨在通过直观且富有洞察力的视角,让读者仿佛亲身体验到CFS在复杂系统环境中游刃有余地进行任务调度的过程。 ####
247 6
|
7月前
|
Linux C语言
Linux读写锁源码分析
本文分析了读写锁的实现原理与应用场景,基于glibc 2.17源码。读写锁通过读引用计数、写线程ID、条件变量等实现,支持读优先(默认)和写优先模式。读优先时,写锁可能饥饿;写优先时,读线程需等待写锁释放。详细解析了`pthread_rwlock_t`数据结构及加解锁流程,并通过实验验证:2000个读线程与1个写线程测试下,读优先导致写锁饥饿,写优先则正常抢占锁。
236 19
|
8月前
|
自然语言处理 监控 Linux
Linux 内核源码分析---proc 文件系统
`proc`文件系统是Linux内核中一个灵活而强大的工具,提供了一个与内核数据结构交互的接口。通过本文的分析,我们深入探讨了 `proc`文件系统的实现原理,包括其初始化、文件的创建与操作、动态内容生成等方面。通过对这些内容的理解,开发者可以更好地利用 `proc`文件系统来监控和调试内核,同时也为系统管理提供了便利的工具。
424 16
|
12月前
|
负载均衡 算法 Linux
深入探索Linux内核调度器:公平与效率的平衡####
本文通过剖析Linux内核调度器的工作机制,揭示了其在多任务处理环境中如何实现时间片轮转、优先级调整及完全公平调度算法(CFS),以达到既公平又高效地分配CPU资源的目标。通过对比FIFO和RR等传统调度策略,本文展示了Linux调度器如何在复杂的计算场景下优化性能,为系统设计师和开发者提供了宝贵的设计思路。 ####
285 26
|
缓存 算法 Linux
Linux内核的心脏:深入理解进程调度器
本文探讨了Linux操作系统中至关重要的组成部分——进程调度器。通过分析其工作原理、调度算法以及在不同场景下的表现,揭示它是如何高效管理CPU资源,确保系统响应性和公平性的。本文旨在为读者提供一个清晰的视图,了解在多任务环境下,Linux是如何智能地分配处理器时间给各个进程的。
|
缓存 负载均衡 Linux
深入理解Linux内核调度器
本文探讨了Linux操作系统核心组件之一——内核调度器的工作原理和设计哲学。不同于常规的技术文章,本摘要旨在提供一种全新的视角来审视Linux内核的调度机制,通过分析其对系统性能的影响以及在多核处理器环境下的表现,揭示调度器如何平衡公平性和效率。文章进一步讨论了完全公平调度器(CFS)的设计细节,包括它如何处理不同优先级的任务、如何进行负载均衡以及它是如何适应现代多核架构的挑战。此外,本文还简要概述了Linux调度器的未来发展方向,包括对实时任务支持的改进和对异构计算环境的适应性。
267 6