免费视频淫片aa毛片_日韩高清在线亚洲专区vr_日韩大片免费观看视频播放_亚洲欧美国产精品完整版

打開APP
userphoto
未登錄

開通VIP,暢享免費電子書等14項超值服

開通VIP
網易博客歡迎您

        unsigned long nr_uninterruptible;

下面幾個指針分別指向當前運行的進程、空閑進程、被停止運行的進程

        struct task_struct *curr, *idle, *stop;

        unsigned long next_balance;

        struct mm_struct *prev_mm;進程切換時存放被替換進程內存描述符

字段clock用于實現就緒隊列自身的時鐘,每次調用周期性調度器時,都會更新clock的值。內核還提供了函數update_rq_clock,可在操作就緒隊列的調度中多次調用。

        u64 clock; 

......

};

調度類結構體在文件include/linux/sched.h中定義,如下:

struct sched_class {

next字段將各個調度類按重要性鏈接起來,最重要的是實時進程,其次是完全公平進程,再次是空閑進程

        const struct sched_class *next;

向就緒隊列添加一個新進程,在進程從睡眠狀態(tài)變?yōu)榭蛇\行狀態(tài)調用該操作

        void (*enqueue_task) (struct rq *rq, struct task_struct *p, int flags);

將一個進程從就緒隊列中去除。

        void (*dequeue_task) (struct rq *rq, struct task_struct *p, int flags);

當進程志愿放棄對處理器的控制權時,可使用系統(tǒng)調用sched_yield,這將導致內核調用

函數yield_task

        void (*yield_task) (struct rq *rq);

進程放棄CPU控制權并將控制權交給p進程

        bool (*yield_to_task) (struct rq *rq, struct task_struct *p, bool preempt);

用一個新喚醒的進程來搶占當前進程,

        void (*check_preempt_curr) (struct rq *rq, struct task_struct *p, int flags);

選擇下一個需要運行的進程

        struct task_struct * (*pick_next_task) (struct rq *rq);

通知調度器類當前運行的進程將要被另一個進程代替。

        void (*put_prev_task) (struct rq *rq, struct task_struct *p);

......

用于設置調度隊列的當前進程,例如cfs_rq->curr

        void (*set_curr_task) (struct rq *rq);

由周期性調度器調用,更新一些時間統(tǒng)計量

        void (*task_tick) (struct rq *rq, struct task_struct *p, int queued);

當新進程創(chuàng)建時調用初始化一些特定調度器類的東西,并設置重調度標志TIF_NEED_RESCHED

        void (*task_fork) (struct task_struct *p);

......

};

調度器操作比進程更一般的實體,每個進程描述結構中都嵌有一個調度實體結構,但并不是一個調度實體就等于一個進程,有時候將一組進程集合起來表示一個調度實體。調度實體結構在文件include/linux/sched.h中定義,如下:

struct sched_entity {

load記錄一個調度實體負荷權重

        struct load_weight      load;           /* for load-balancing */

紅黑樹節(jié)點,使得實體可以在紅黑樹上排序

        struct rb_node          run_node;

用于鏈接到就緒隊列的cfs_tasks字段上

        struct list_head        group_node;

表示實體是否在就緒隊列上

        unsigned int            on_rq;

當新進程被加入就緒隊列或者在周期性調度器中,會計算當前值和exec_start之間的差值,將差值加到sum_exec_runtime中,exec_start則更新為當前時間

        u64                     exec_start;

記錄消耗的CPU時間

        u64                     sum_exec_runtime;

保存進程的虛擬時間,關于虛擬時間后面講詳細講解

        u64                     vruntime;

當進程失去CPU控制權時將sum_exec_runtime保存到prev_sum_exec_runtime中

        u64                     prev_sum_exec_runtime;

......

下面是跟組調度相關的字段

#ifdef CONFIG_FAIR_GROUP_SCHED

        struct sched_entity     *parent; 建立組調度中調度實體的層次關系

        struct cfs_rq           *cfs_rq; 組所屬的調度隊列

        /* rq "owned" by this entity/group: */

        struct cfs_rq           *my_q;組內部的調度隊列

#endif

};

2 優(yōu)先級與負荷權重2.1優(yōu)先級的計算

前面講過優(yōu)先級有3個,static_prio、normal_prio和prio。Static_prio在進程啟動時靜態(tài)設置可用nice系統(tǒng)調用修改;normal_prio與靜態(tài)優(yōu)先級和調度策略有關,調度策略可能被修改;prio是調度算法中用到的優(yōu)先級,他可能被內核臨時改變,比如一個高優(yōu)先級的進程在使用的互斥量,被一個低優(yōu)先級進程占據了,這是就要臨時提高低優(yōu)先級的優(yōu)先級,這個優(yōu)先級就保存在prio中。

在用戶空間中,用nice系統(tǒng)調用設置進程的靜態(tài)優(yōu)先級,該nice值的范圍是-20到19之間,值越低優(yōu)先級越高。內核內部表示優(yōu)先級的的范圍是0到139,同樣也是值越低優(yōu)先級越高。從0到99專事實進程使用。Nice值-20到19映射的范圍是100到139.

優(yōu)先級prio的獲取是通過函數effective_prio來實現的,如下:

程序首先計算進程的普通優(yōu)先級然后判斷進程的優(yōu)先級是否小于MAX_RT_PRIO,這里并不考慮調度策略。如果進程優(yōu)先級在實時進程優(yōu)先級范圍內就直接返回其優(yōu)先級(prio)否則返回其普通優(yōu)先級。

static int effective_prio(struct task_struct *p)

{

        p->normal_prio = normal_prio(p);

        if (!rt_prio(p->prio))

                return p->normal_prio;

        return p->prio;

}

【effective_prio--->normal_prio】

由于實時進程中,進程描述符p->rt_priority字段表示的優(yōu)先級是其數值越大優(yōu)先級越高,與內核內部表示優(yōu)先級的方法剛好相反,所以在內核內部優(yōu)先級的計算方法是 MAX_RT_PRIO-1 - p->rt_priority;。函數__normal_prio僅僅是返回進程的靜態(tài)優(yōu)先級 return p->static_prio;。

static inline int normal_prio(struct task_struct *p)

{

        int prio;

        if (task_has_rt_policy(p))

                prio = MAX_RT_PRIO-1 - p->rt_priority;

        else

                prio = __normal_prio(p);

        return prio;

}

2.2 負荷權重的計算

進程每降低一個nice值,多獲得10%的CPU時間,每升高一個nice值,則放棄10%的CPU時間。為執(zhí)行該策略,內核將優(yōu)先級轉換為權重。優(yōu)先級--權重轉換表:

kernel/sched/sched.h

static const int prio_to_weight[40] = {

 /* -20 */     88761,     71755,     56483,     46273,     36291,

 /* -15 */     29154,     23254,     18705,     14949,     11916,

 /* -10 */      9548,      7620,      6100,      4904,      3906,

 /*  -5 */      3121,      2501,      1991,      1586,      1277,

 /*   0 */      1024,       820,       655,       526,       423,

 /*   5 */       335,       272,       215,       172,       137,

 /*  10 */       110,        87,        70,        56,        45,

 /*  15 */        36,        29,        23,        18,        15,

};

計算權重的函數是set_load_weight,空閑進程的權重最小。負荷權重包含在數據結構load_weight中如下:

struct load_weight { 

        unsigned long weight, inv_weight;

};

Weight表示優(yōu)先級在表prio_to_weight中映射的權重,inv_weight表示被負荷權重除的結果,這些值也存在表prio_to_wmult中,這個表與表prio_to_weight中的值一一對應。這些值的計算方法是(2^32/x),x表示在表prio_to_weight中對應位置中的值。

static void set_load_weight(struct task_struct *p)

{

        int prio = p->static_prio - MAX_RT_PRIO;

        struct load_weight *load = &p->se.load;

        if (p->policy == SCHED_IDLE) {

                load->weight = scale_load(WEIGHT_IDLEPRIO);

                load->inv_weight = WMULT_IDLEPRIO;

                return;

        }

 

        load->weight = scale_load(prio_to_weight[prio]);   

        load->inv_weight = prio_to_wmult[prio];  

}

3核心調度器

調度器的實現基于兩個函數:周期性調度器函數和主調度器函數。

周期性調度器函數scheduler_tick:在內核頻率HZ中斷處理函數中調用該函數。周期性調度函數的主要工作是更新各種時間統(tǒng)計量,在必要的時候進行進程 調度。在多CPU系統(tǒng)中它還進行負載均衡處理。

主調度器函數schedule:如果進程主動放棄執(zhí)行而選擇另一個活動進程運行就調用該函數。

周期性調度器函數和主調度器函數都不直接操作進程,而是調用進程所屬的操作類來操作進程。每個進程都屬于某個調度類(完全公平調度類、實時調度類)。

3.1 周期性調度器

kernel/sched/core.c

void scheduler_tick(void)

{

        int cpu = smp_processor_id(); 獲取當前CPU的cpu id

        struct rq *rq = cpu_rq(cpu);獲取當前CPU對應的就緒隊列結構

        struct task_struct *curr = rq->curr;獲取當前進程的進程描述符

在函數update_rq_clock中,更新就緒隊列的兩個成員rq->clock_task,rq->clock。

        update_rq_clock(rq);

函數update_cpu_load_active負責更新就緒隊列的cpu_load[]數組。將數組中先前存儲的負荷值向后移動一個位置,將當前就緒隊列的負荷計入數組的第一個位置。

        update_cpu_load_active(rq);

調用當前進程所屬的調度類的task_tick函數,在該函數中更新一些時間計數,必要時調度其他活動進程來執(zhí)行。

        curr->sched_class->task_tick(rq, curr, 0);

......

#ifdef CONFIG_SMP

        rq->idle_balance = idle_cpu(cpu);

在多處理器系統(tǒng)中進行負載均衡,保證不同CPU的運行隊列包含數量基本相同的可運行進程

        trigger_load_balance(rq, cpu);

#endif

}

3.2 主調度器

kernel/sched/core.c

asmlinkage void __sched schedule(void)

{

        struct task_struct *tsk = current;

提交一些阻塞的IO請求避免死鎖。

        sched_submit_work(tsk);

主調度器的組要工作都在函數__schedule中實現。

        __schedule();

}     

【schedule--->__schedule】

static void __sched __schedule(void)

{

        struct task_struct *prev, *next;

        unsigned long *switch_count;

        struct rq *rq;

        int cpu;

need_resched:

        preempt_disable(); 禁止搶占

        cpu = smp_processor_id(); 獲取當前進程CPU id

        rq = cpu_rq(cpu);獲取當前cpu對應的就緒隊列結構

因為要切換到其他活動進程,所以當前進程就變成了“以前的”進程了。

        prev = rq->curr;

......

保存進程切換計數

        switch_count = &prev->nivcsw;

標志 PREEMPT_ACTIVE可以保證進程被搶占但是不被移除就緒隊列

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

如果進程有非阻塞掛起信號,而且狀態(tài)為TASK_INTERRUPTIBLE,就不將進程移除就緒隊列,并將其狀態(tài)設為TASK_RUNNING。

                if (unlikely(signal_pending_state(prev->state, prev))) {

                        prev->state = TASK_RUNNING;

                } else {

調用函數 p->sched_class->dequeue_task將進程移除隊列。

                        deactivate_task(rq, prev, DEQUEUE_SLEEP);

                        prev->on_rq = 0;

                        if (prev->flags & PF_WQ_WORKER) {

                                struct task_struct *to_wakeup;

每一個工作隊列都有一個工作者線程,如果當前進程是一個工作者線程,就查看是否有其他處于等待中的工作者線程,如果有在本地CPU上喚醒。

                                to_wakeup = wq_worker_sleeping(prev, cpu);

                                if (to_wakeup)

                                        try_to_wake_up_local(to_wakeup);

                        }

                }

                switch_count = &prev->nvcsw;

        }

如果CPU將變?yōu)榭臻e狀態(tài),即就緒隊列上沒有可運行的進程,就試圖從其他CPU上可運行的進程移動到本地CPU的就緒隊列上。

        if (unlikely(!rq->nr_running))

                idle_balance(cpu, rq);

通知調度器類當前運行的進程將要被另一個進程代替。

        put_prev_task(rq, prev);

選擇下一個應該執(zhí)行的進程

        next = pick_next_task(rq);

清除重調度標志TIF_NEED_RESCHED,重調度標志是由進程調度類設置的,表示調度請求,內核會在適當的時機完成該標志。

        clear_tsk_need_resched(prev);

        rq->skip_clock_update = 0;

        if (likely(prev != next)) {

                rq->nr_switches++;

                rq->curr = next;

                ++*switch_count; 遞增進程切換計數

完成進程上下文切換,下面詳細分析。

                context_switch(rq, prev, next); /* unlocks the rq */

進程已經切換到其他進程去運行了,在后來的某個時候當前進程再次被調度時進程從這里開始運行,但是它在這中間可能已經被移動到其他CPU上了,所以此時棧上的cpu和rq變量的只可能就不一樣了。

                cpu = smp_processor_id();

                rq = cpu_rq(cpu);

        } else

                raw_spin_unlock_irq(&rq->lock);

        post_schedule(rq);

        sched_preempt_enable_no_resched();

        if (need_resched())

                goto need_resched;

}

【schedule--->__schedule--->context_switch】

static inline void context_switch(struct rq *rq, struct task_struct *prev,

               struct task_struct *next)

{

        struct mm_struct *mm, *oldmm;

        mm = next->mm;

        oldmm = prev->active_mm;

如果將要運行的進程是一個內核線程,則它是沒有進程虛擬地址空間的,就將上一個進程的虛擬地址空間報保存到 next->active_mm 中,

        if (!mm) {

                next->active_mm = oldmm;

                atomic_inc(&oldmm->mm_count);

                enter_lazy_tlb(oldmm, next);

        } else

                switch_mm(oldmm, mm, next); 更換虛擬內存上下文

如果前一個進程是內核線程就將前一個進程的prev->active_mm重置為NULL,rq->prev_mm指向上一個進程的虛擬地址空間

        if (!prev->mm) {

                prev->active_mm = NULL;

                rq->prev_mm = oldmm;

        }

......

進程寄存器和棧的切換,有特定體系架構的匯編實現。switch_to之后進程已經運行在了新進程之上,當前進程再次被調度運行時這中間可能已經經歷過很多次切換了,但是要保證prev指向上一個進程。這上一個進程可能并不是當前進程,所以要傳遞三個參數,下面代碼等效于prev=switch_to(next, prev);

        switch_to(prev, next, prev);

        barrier();

      

        finish_task_switch(this_rq(), prev);

本站僅提供存儲服務,所有內容均由用戶發(fā)布,如發(fā)現有害或侵權內容,請點擊舉報
打開APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
(轉載)linux內核進程調度以及定時器實現機制
深入理解Linux內核進程的創(chuàng)建、調度和終止
linux內核分析筆記----調度
淺析Linux內核調度
Linux 2.6 調度系統(tǒng)分析
進程調度的數據結構和優(yōu)先級 (轉)
更多類似文章 >>
生活服務
分享 收藏 導長圖 關注 下載文章
綁定賬號成功
后續(xù)可登錄賬號暢享VIP特權!
如果VIP功能使用有故障,
可點擊這里聯(lián)系客服!

聯(lián)系客服