2015
引言
之前的文章已經(jīng)介紹了調(diào)度器已經(jīng)初始化完成,現(xiàn)在只需要加入一個(gè)周期定時(shí)器tick驅(qū)動(dòng)它進(jìn)行周期調(diào)度即可,而加入定時(shí)器tick在下一篇文章進(jìn)行簡(jiǎn)單說明(主要這部分涉及調(diào)度器比較少,更多的是時(shí)鐘、定時(shí)器相關(guān)知識(shí))。這篇文章主要說明系統(tǒng)如何把一個(gè)進(jìn)程加入到隊(duì)列中。
加入時(shí)機(jī)
之前的文章也有提到過,只有處于TASK_RUNNING狀態(tài)下的進(jìn)程才能夠加入到調(diào)度器,其他狀態(tài)都不行,也就說明了,當(dāng)一個(gè)進(jìn)程處于睡眠、掛起狀態(tài)的時(shí)候是不存在于調(diào)度器中的,而進(jìn)程加入調(diào)度器的時(shí)機(jī)如下:
- 當(dāng)進(jìn)程創(chuàng)建完成時(shí),進(jìn)程剛創(chuàng)建完成時(shí),即使它運(yùn)行起來立即調(diào)用sleep()進(jìn)程睡眠,它也必定先會(huì)加入到調(diào)度器,因?yàn)閷?shí)際上它加入調(diào)度器后自己還需要進(jìn)行一定的初始化和操作,才會(huì)調(diào)用到我們的“立即”sleep()。
- 當(dāng)進(jìn)程被喚醒時(shí),也使用sleep的例子說明,我們平常寫程序使用的sleep()函數(shù)實(shí)現(xiàn)原理就是通過系統(tǒng)調(diào)用將進(jìn)程狀態(tài)改為TASK_INTERRUPTIBLE,然后移出運(yùn)行隊(duì)列,并且啟動(dòng)一個(gè)定時(shí)器,在定時(shí)器到期后喚醒進(jìn)程,再重新放入運(yùn)行隊(duì)列。
sched_fork
在我的博文關(guān)于linux系統(tǒng)如何實(shí)現(xiàn)fork的研究(二)中專門描述了copy_process()這個(gè)創(chuàng)建函數(shù),而里面有一個(gè)函數(shù)專門用于進(jìn)程調(diào)度的初始化,就是sched_fork(),其代碼如下
- int sched_fork(unsigned long clone_flags, struct task_struct *p)
- {
- unsigned long flags;
- /* 獲取當(dāng)前CPU,并且禁止搶占 */
- int cpu = get_cpu();
-
- /* 初始化跟調(diào)度相關(guān)的值,比如調(diào)度實(shí)體,運(yùn)行時(shí)間等 */
- __sched_fork(clone_flags, p);
- /*
- * 標(biāo)記為運(yùn)行狀態(tài),表明此進(jìn)程正在運(yùn)行或準(zhǔn)備好運(yùn)行,實(shí)際上沒有真正在CPU上運(yùn)行,這里只是導(dǎo)致了外部信號(hào)和事件不能夠喚醒此進(jìn)程,之后將它插入到運(yùn)行隊(duì)列中
- */
- p->state = TASK_RUNNING;
-
- /*
- * 根據(jù)父進(jìn)程的運(yùn)行優(yōu)先級(jí)設(shè)置設(shè)置進(jìn)程的優(yōu)先級(jí)
- */
- p->prio = current->normal_prio;
-
- /*
- * 更新該進(jìn)程優(yōu)先級(jí)
- */
- /* 如果需要重新設(shè)置優(yōu)先級(jí) */
- if (unlikely(p->sched_reset_on_fork)) {
- /* 如果是dl調(diào)度或者實(shí)時(shí)調(diào)度 */
- if (task_has_dl_policy(p) || task_has_rt_policy(p)) {
- /* 調(diào)度策略為SCHED_NORMAL,這個(gè)選項(xiàng)將使用CFS調(diào)度 */
- p->policy = SCHED_NORMAL;
- /* 根據(jù)默認(rèn)nice值設(shè)置靜態(tài)優(yōu)先級(jí) */
- p->static_prio = NICE_TO_PRIO(0);
- /* 實(shí)時(shí)優(yōu)先級(jí)為0 */
- p->rt_priority = 0;
- } else if (PRIO_TO_NICE(p->static_prio) < 0)
- /* 根據(jù)默認(rèn)nice值設(shè)置靜態(tài)優(yōu)先級(jí) */
- p->static_prio = NICE_TO_PRIO(0);
-
- /* p->prio = p->normal_prio = p->static_prio */
- p->prio = p->normal_prio = __normal_prio(p);
- /* 設(shè)置進(jìn)程權(quán)重 */
- set_load_weight(p);
-
- /* sched_reset_on_fork成員在之后已經(jīng)不需要使用了,直接設(shè)為0 */
- p->sched_reset_on_fork = 0;
- }
-
- if (dl_prio(p->prio)) {
- /* 使能搶占 */
- put_cpu();
- /* 返回錯(cuò)誤 */
- return -EAGAIN;
- } else if (rt_prio(p->prio)) {
- /* 根據(jù)優(yōu)先級(jí)判斷,如果是實(shí)時(shí)進(jìn)程,設(shè)置其調(diào)度類為rt_sched_class */
- p->sched_class = &rt_sched_class;
- } else {
- /* 如果是普通進(jìn)程,設(shè)置其調(diào)度類為fair_sched_class */
- p->sched_class = &fair_sched_class;
- }
- /* 調(diào)用調(diào)用類的task_fork函數(shù) */
- 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);
- /* 設(shè)置新進(jìn)程的CPU為當(dāng)前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; */
- /* 初始化該進(jìn)程為內(nèi)核禁止搶占 */
- 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()函數(shù)中,主要工作如下:
- 獲取當(dāng)前CPU號(hào)
- 禁止內(nèi)核搶占(這里基本就是關(guān)閉了搶占,因?yàn)閳?zhí)行到這里已經(jīng)是內(nèi)核態(tài),又禁止了被搶占)
- 初始化進(jìn)程p的一些變量(實(shí)時(shí)進(jìn)程和普通進(jìn)程通用的那些變量)
- 設(shè)置進(jìn)程p的狀態(tài)為TASK_RUNNING(這一步很關(guān)鍵,因?yàn)橹挥刑幱赥ASK_RUNNING狀態(tài)下的進(jìn)程才會(huì)被調(diào)度器放入隊(duì)列中)
- 根據(jù)父進(jìn)程和clone_flags參數(shù)設(shè)置進(jìn)程p的優(yōu)先級(jí)和權(quán)重。
- 根據(jù)進(jìn)程p的優(yōu)先級(jí)設(shè)置其調(diào)度類(實(shí)時(shí)進(jìn)程優(yōu)先級(jí):0~99 普通進(jìn)程優(yōu)先級(jí):100~139)
- 根據(jù)調(diào)度類進(jìn)行進(jìn)程p類型相關(guān)的初始化(這里就實(shí)現(xiàn)了實(shí)時(shí)進(jìn)程和普通進(jìn)程獨(dú)有的變量進(jìn)行初始化)
- 設(shè)置進(jìn)程p的當(dāng)前CPU為此CPU。
- 初始化進(jìn)程p禁止內(nèi)核搶占(因?yàn)楫?dāng)CPU執(zhí)行到進(jìn)程p時(shí),進(jìn)程p還需要進(jìn)行一些初始化)
- 使能內(nèi)核搶占
可以看出sched_fork()進(jìn)行的初始化也比較簡(jiǎn)單,需要注意的是不同類型的進(jìn)程會(huì)使用不同的調(diào)度類,并且也會(huì)調(diào)用調(diào)度類中的初始化函數(shù)。在實(shí)時(shí)進(jìn)程的調(diào)度類中是沒有特定的task_fork()函數(shù)的,而普通進(jìn)程使用cfs策略時(shí)會(huì)調(diào)用到task_fork_fair()函數(shù),我們具體看看實(shí)現(xiàn):
- static void task_fork_fair(struct task_struct *p)
- {
- struct cfs_rq *cfs_rq;
-
- /* 進(jìn)程p的調(diào)度實(shí)體se */
- struct sched_entity *se = &p->se, *curr;
-
- /* 獲取當(dāng)前CPU */
- int this_cpu = smp_processor_id();
-
- /* 獲取此CPU的運(yùn)行隊(duì)列 */
- struct rq *rq = this_rq();
- unsigned long flags;
-
- /* 上鎖并保存中斷記錄 */
- raw_spin_lock_irqsave(&rq->lock, flags);
-
- /* 更新rq運(yùn)行時(shí)間 */
- update_rq_clock(rq);
-
- /* cfs_rq = current->se.cfs_rq; */
- cfs_rq = task_cfs_rq(current);
-
- /* 設(shè)置當(dāng)前進(jìn)程所在隊(duì)列為父進(jìn)程所在隊(duì)列 */
- 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();
- /* 設(shè)置此進(jìn)程所屬CPU */
- __set_task_cpu(p, this_cpu);
- rcu_read_unlock();
-
- /* 更新當(dāng)前進(jìn)程運(yùn)行時(shí)間 */
- update_curr(cfs_rq);
-
- if (curr)
- /* 將父進(jìn)程的虛擬運(yùn)行時(shí)間賦給了新進(jìn)程的虛擬運(yùn)行時(shí)間 */
- se->vruntime = curr->vruntime;
- /* 調(diào)整了se的虛擬運(yùn)行時(shí)間 */
- 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);
- }
-
- /* 保證了進(jìn)程p的vruntime是運(yùn)行隊(duì)列中最小的(這里占時(shí)不確定是不是這個(gè)用法,不過確實(shí)是最小的了) */
- se->vruntime -= cfs_rq->min_vruntime;
-
- /* 解鎖,還原中斷記錄 */
- raw_spin_unlock_irqrestore(&rq->lock, flags);
- }
在task_fork_fair()函數(shù)中主要就是設(shè)置進(jìn)程p的虛擬運(yùn)行時(shí)間和所處的cfs隊(duì)列,值得我們注意的是 cfs_rq = task_cfs_rq(current); 這一行,在注釋中已經(jīng)表明task_cfs_rq(current)返回的是current的se.cfs_rq,注意se.cfs_rq保存的并不是根cfs隊(duì)列,而是所處的cfs_rq,也就是如果父進(jìn)程處于一個(gè)進(jìn)程組的cfs_rq中,新創(chuàng)建的進(jìn)程也會(huì)處于這個(gè)進(jìn)程組的cfs_rq中。
wake_up_new_task()
到這里新進(jìn)程關(guān)于調(diào)度的初始化已經(jīng)完成,但是還沒有被調(diào)度器加入到隊(duì)列中,其是在do_fork()中的wake_up_new_task(p);中加入到隊(duì)列中的,我們具體看看wake_up_new_task()的實(shí)現(xiàn):
- 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
- */
- /* 為進(jìn)程選擇一個(gè)合適的CPU */
- set_task_cpu(p, select_task_rq(p, task_cpu(p), SD_BALANCE_FORK, 0));
- #endif
-
- /* Initialize new task's runnable average */
- /* 這里是跟多核負(fù)載均衡有關(guān) */
- init_task_runnable_average(p);
- /* 上鎖 */
- rq = __task_rq_lock(p);
- /* 將進(jìn)程加入到CPU的運(yùn)行隊(duì)列 */
- activate_task(rq, p, 0);
- /* 標(biāo)記進(jìn)程p處于隊(duì)列中 */
- p->on_rq = TASK_ON_RQ_QUEUED;
- /* 跟調(diào)試有關(guān) */
- trace_sched_wakeup_new(p, true);
- /* 檢查是否需要切換當(dāng)前進(jìn)程 */
- 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()函數(shù)中,將進(jìn)程加入到運(yùn)行隊(duì)列的函數(shù)為activate_task(),而activate_task()函數(shù)最后會(huì)調(diào)用到新進(jìn)程調(diào)度類中的enqueue_task指針?biāo)负瘮?shù),這里我們具體看一下cfs調(diào)度類的enqueue_task指針?biāo)负瘮?shù)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;
-
- /* 這里是一個(gè)迭代,我們知道,進(jìn)程有可能是處于一個(gè)進(jìn)程組中的,所以當(dāng)這個(gè)處于進(jìn)程組中的進(jìn)程加入到該進(jìn)程組的隊(duì)列中時(shí),要對(duì)此隊(duì)列向上迭代 */
- for_each_sched_entity(se) {
- if (se->on_rq)
- break;
- /* 如果不是CONFIG_FAIR_GROUP_SCHED,獲取其所在CPU的rq運(yùn)行隊(duì)列的cfs_rq運(yùn)行隊(duì)列
- * 如果是CONFIG_FAIR_GROUP_SCHED,獲取其所在的cfs_rq運(yùn)行隊(duì)列
- */
- cfs_rq = cfs_rq_of(se);
- /* 加入到隊(duì)列中 */
- 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不處于隊(duì)列中或者cfs_rq_throttled(cfs_rq)返回真才會(huì)運(yùn)行這個(gè)循環(huán) */
- 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);
- /* 當(dāng)前CPU運(yùn)行隊(duì)列活動(dòng)進(jìn)程數(shù) + 1 */
- add_nr_running(rq, 1);
- }
- /* 設(shè)置下次調(diào)度中斷發(fā)生時(shí)間 */
- hrtick_update(rq);
- }
在enqueue_task_fair()函數(shù)中又使用了enqueue_entity()函數(shù)進(jìn)行操作,如下:
- 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'.
- */
- /* 更新當(dāng)前進(jìn)程運(yùn)行時(shí)間和虛擬運(yùn)行時(shí)間 */
- update_curr(cfs_rq);
- enqueue_entity_load_avg(cfs_rq, se, flags & ENQUEUE_WAKEUP);
- /* 更新cfs_rq隊(duì)列總權(quán)重(就是在原有基礎(chǔ)上加上se的權(quán)重) */
- account_entity_enqueue(cfs_rq, se);
- update_cfs_shares(cfs_rq);
-
- /* 新建的進(jìn)程flags為0,不會(huì)執(zhí)行這里 */
- 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插入到運(yùn)行隊(duì)列cfs_rq的紅黑樹中 */
- if (se != cfs_rq->curr)
- __enqueue_entity(cfs_rq, se);
- /* 將se的on_rq標(biāo)記為1 */
- se->on_rq = 1;
-
- /* 如果cfs_rq的隊(duì)列中只有一個(gè)進(jìn)程,這里做處理 */
- if (cfs_rq->nr_running == 1) {
- list_add_leaf_cfs_rq(cfs_rq);
- check_enqueue_throttle(cfs_rq);
- }
- }
總結(jié)
需要注意的幾點(diǎn):
- 新創(chuàng)建的進(jìn)程先會(huì)進(jìn)行調(diào)度相關(guān)的結(jié)構(gòu)體和變量初始化,其中會(huì)根據(jù)不同的類型進(jìn)行不同的調(diào)度類操作,此時(shí)并沒有加入到隊(duì)列中。
- 當(dāng)新進(jìn)程創(chuàng)建完畢后,它的父進(jìn)程會(huì)將其運(yùn)行狀態(tài)置為TASK_RUNNING,并加入到運(yùn)行隊(duì)列中。
- 加入運(yùn)行隊(duì)列時(shí)系統(tǒng)會(huì)根據(jù)CPU的負(fù)載情況放入不同的CPU隊(duì)列中。