linux調度器源碼分析(二) - 初始化

每個CPU(多核COU中的每個核)都有自己的struct rq隊列,而rq隊列中又有着自己的struct cfs_rq和struct rt_rq。在初始化時就是對這三個結構進行初始化。

1 init_task和init進程

在start_kernel函數中,進行了系統啓動過程中幾乎所有重要的初始化(有一部分在boot中初始化,有一部分在start_kernel之前的彙編代碼進行初始化),包括內存、頁表、必要數據結構、信號、調度器、硬件設備等。而這些初始化是由誰來負責的?就是由init_task這個進程。init_task是靜態定義的一個進程,也就是說當內核被放入內存時,它就已經存在,它沒有自己的用戶空間,一直處於內核空間中運行,並且也只處於內核空間運行。當它執行到最後,將start_kernel中所有的初始化執行完成後,會在內核中啓動一個kernel_init內核線程和一個kthreadd內核線程,kernel_init內核線程執行到最後會通過execve系統調用執行轉變爲我們所熟悉的init進程,而kthreadd內核線程是內核用於管理調度其他的內核線程的守護線程。在最後init_task將變成一個idle進程,用於在CPU沒有進程運行時運行它,它在此時僅僅用於空轉。

 2 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中所佔用比的 */
-------------------------------------------------------------------(1)
    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 */
//設置rt隊列的每個調度週期的最大運行時間
        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;
}

step1:

       初始化def_rt_bandwidth

在單cpu環境中,rt_bandwidth限制了cpu上的實時任務在rt_period週期內運行時間不能夠超過rt_runtime;而在SMP多cpu環境中rt_bandwidth限制了系統中實時任務在rt_period週期內的cpu佔用時間比例不能夠超過rt_runtime/rt_period。

int sysctl_sched_rt_runtime = 950000;
#define NSEC_PER_USEC	1000L
static inline u64 global_rt_period(void)
{
	return (u64)sysctl_sched_rt_period * NSEC_PER_USEC;
}

static inline u64 global_rt_runtime(void)
{
	if (sysctl_sched_rt_runtime < 0)
		return RUNTIME_INF;

	return (u64)sysctl_sched_rt_runtime * NSEC_PER_USEC;
}
void init_rt_bandwidth(struct rt_bandwidth *rt_b, u64 period, u64 runtime)
{
	rt_b->rt_period = ns_to_ktime(period);
	rt_b->rt_runtime = runtime; //初始化一個調度週期中的運行時間

	raw_spin_lock_init(&rt_b->rt_runtime_lock);

	hrtimer_init(&rt_b->rt_period_timer,
			CLOCK_MONOTONIC, HRTIMER_MODE_REL);//該定時器和rt進程的週期調度相關
	rt_b->rt_period_timer.function = sched_rt_period_timer;
}

如果一個隊列調度限制使能的情況下(即rt_rq->rt_throttled爲1),將無法得到調度運行的機會;但是任務不可能一直處於調度限制,因爲那樣的話任務就永遠得不到執行了。這個時候就需要一種檢查機制,一旦調度限制已經讓任務得到了應有的“懲罰”,就需要解除這個限制,讓它重獲自由。內核中在struct task_group結構rt_bandwidth的高精度時鐘rt_period_timer來實現此功能。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章