日韩黑丝制服一区视频播放|日韩欧美人妻丝袜视频在线观看|九九影院一级蜜桃|亚洲中文在线导航|青草草视频在线观看|婷婷五月色伊人网站|日本一区二区在线|国产AV一二三四区毛片|正在播放久草视频|亚洲色图精品一区

分享

(轉(zhuǎn)載)linux內(nèi)核進程調(diào)度以及定時器實現(xiàn)機制

 雙木亭 2012-07-19

(轉(zhuǎn)載)linux內(nèi)核進程調(diào)度以及定時器實現(xiàn)機制

 

一、2.6版以前內(nèi)核進程調(diào)度機制簡介

Linux的進程管理由進程控制塊、進程調(diào)度、中斷處理、任務(wù)隊列、定時器、bottom half隊列、系統(tǒng)調(diào)用、進程通信等等部分組成。

進程調(diào)用分為實時進程調(diào)度和非實時進程調(diào)度兩種。前者調(diào)度時,可以采用基于動態(tài)優(yōu)先級的輪轉(zhuǎn)法(RR),也可以采用先進現(xiàn)出算法(FIFO)。后者 調(diào)度時,一律采用基于動態(tài)優(yōu)先級的輪轉(zhuǎn)法。某個進程采用何種調(diào)度算法由改進程的進程控制塊中的某些屬性決定,沒有專門的系統(tǒng)用來處理關(guān)于進程調(diào)度的相關(guān)事 宜。Linux的進程調(diào)度由schedule()函數(shù)負責,任何進程,當它從系統(tǒng)調(diào)用返回時,都會轉(zhuǎn)入schedule(),而中斷處理函數(shù)完成它們的響 應(yīng)任務(wù)以后,也會進入schedule()。

 

1.         進程控制塊數(shù)據(jù)結(jié)構(gòu)

Linux系統(tǒng)的進程控制塊用數(shù)據(jù)結(jié)構(gòu)task_struct表示,這個數(shù)據(jù)結(jié)構(gòu)占用1680個字節(jié),具體的內(nèi)容不在這里介紹,詳細內(nèi)容見《Linux內(nèi)核2.4版源代碼分析大全》第二頁。

進程的狀態(tài)主要包括如下幾個:

TASK_RUNNING   正在運行或在就緒隊列run-queue中準備運行的進程,實際參與進程調(diào)度。

TASK_INTERRUPTIBLE       處于等待隊列中的進程,待資源有效時喚醒,也可由其它進程通過信號或定時中斷喚醒后進入就緒隊列run-queue。

TASK_UNINTERRUPTIBLE         處于等待隊列的進程,待資源有效時喚醒,不也可由其它進程通過信號或者定時中斷喚醒。

TASK_ZOMBIE      表示進程結(jié)束但尚未消亡的一種狀態(tài)(僵死),此時,進程已經(jīng)結(jié)束運行并且已經(jīng)釋放了大部分資源,但是尚未釋放進程控制塊。

TASK_STOPPED    進程暫停,通過其它進程的信號才能喚醒。

 

所有進程(以PCB形式)組成一個雙向列表。next_task和prev_task就是鏈表的前后向指針。鏈表的頭尾都是init_task(init進程)。不過進程還要根據(jù)其進程ID號插入到一個hash表當中,目的是加快進程搜索速度。

 

2.         進程調(diào)度

Linux進程調(diào)度由schedule()執(zhí)行,其任務(wù)是在run-queue隊列中選出一個就緒進程。

每個進程都有一個調(diào)度策略,在它的task_struct中規(guī)定(policy屬性),或為SCHED_RR,SCHED_FIFO,或為SCHED_OTHER。前兩種為實時進程調(diào)度策略,后一種為普通進程調(diào)度策略。

用戶進程由do_fork()函數(shù)創(chuàng)建,它也是fork系統(tǒng)調(diào)用的執(zhí)行者。do_fork()創(chuàng)建一個新的進程,繼承父進程的現(xiàn)有資源,初始化進程時鐘、信號、時間等數(shù)據(jù)。完成子進程的初始化后,父進程將它掛到就緒隊列,返回子進程的pid。

進程創(chuàng)建時的狀態(tài)為TASK_UNINTERRUPTIBLE,在do_fork()結(jié)束前被父進程喚醒后,變?yōu)門ASK_RUNNING。處于TASK_RUNNING狀態(tài)的進程被移到就緒隊列中,當適當?shù)臅r候由schedule()按CPU調(diào)度算法選中,獲得CPU。

如果進程采用輪轉(zhuǎn)法,當時間片到時(10ms的整數(shù)倍),由時鐘中斷觸發(fā)timer_interrupt()函數(shù)引起新一輪的調(diào)度,把當前進程掛到 就緒隊列的尾部。獲得CPU而正在運行的進程若申請不到某個資源,則調(diào)用sleep_on()或interruptible_sleep_on()睡眠, 并進入就緒隊列尾。狀態(tài)尾TASK_INTERRUPTIBLE的睡眠進程當它申請的資源有效時被喚醒,也可以由信號或者定時中斷喚醒,喚醒以后進程狀態(tài) 變?yōu)門ASK_RUNNING,并進入就緒隊列。

首先介紹一下2.6版以前的的調(diào)度算法的主要思想,下面的schedule()函數(shù)是內(nèi)核2.4.23中摘錄的:

asmlinkage void schedule(void)

{

struct schedule_data * sched_data;

struct task_struct *prev, *next, *p;

struct list_head *tmp;

int this_cpu, c;

 

spin_lock_prefetch(&runqueue_lock);

 

BUG_ON(!current->active_mm);

need_resched_back:

       /*記錄當前進程和處理此進程的CPU號*/

prev = current;

this_cpu = prev->processor;

/*判斷是否處在中斷當中,這里不允許在中斷處理當中調(diào)用sechedule()*/

if (unlikely(in_interrupt())) {

        printk("Scheduling in interrupt/n");

        BUG();

}

 

release_kernel_lock(prev, this_cpu);

 

/*'sched_data' 是收到保護的,每個CPU只能運行一個進程。*/

sched_data = & aligned_data[this_cpu].schedule_data;

 

spin_lock_irq(&runqueue_lock);

 

/*如果當前進程的調(diào)度策略是輪轉(zhuǎn)RR,那么需要判斷當前進程的時間片是否已經(jīng)用完,如果已經(jīng)用完,則重新計算時間片值,然后將該進程掛接到就緒隊列run-queue的最后*/

if (unlikely(prev->policy == SCHED_RR))

        if (!prev->counter) {

               prev->counter = NICE_TO_TICKS(prev->nice);

               move_last_runqueue(prev);

        }

/*假如前進程為TASK_INTERRUPTTIBLE狀態(tài),則將其狀態(tài)置為TASK_RUNNING。如是其它狀態(tài),則將該進程轉(zhuǎn)為睡眠狀態(tài),從運行隊列中刪除。(已不具備運行的條件) */

switch (prev->state) {

        case TASK_INTERRUPTIBLE:

               if (signal_pending(prev)) {

                      prev->state = TASK_RUNNING;

                      break;

               }

        default:

               del_from_runqueue(prev);

        case TASK_RUNNING:;

}

/*當前進程不需要重新調(diào)度*/

prev->need_resched = 0;

 

/*下面是一般的進程調(diào)度過程*/

 

repeat_schedule:

next = idle_task(this_cpu);

c = -1000;

/*遍歷進程就緒隊列,如果該進程能夠進行調(diào)度(對于SMP來說就是判斷當前CPU未被占用能夠執(zhí)行這個進程,對于非SMP系統(tǒng)則為1),則計算該進程的優(yōu)先級,如果優(yōu)先級大于當前進程,則next指針指向新的進程,循環(huán)直到找到優(yōu)先級最大的那個進程*/

list_for_each(tmp, &runqueue_head) {

        p = list_entry(tmp, struct task_struct, run_list);

        if (can_schedule(p, this_cpu)) {

               int weight = goodness(p, this_cpu, prev->active_mm);

               if (weight > c)

                      c = weight, next = p;

        }

}

 

/* 判斷是否需要重新計算每個進程的時間片,判斷的依據(jù)是所有正準備進行調(diào)度的進程時間片耗盡,這時,就需要對就緒隊列中的每一個進程都重新計算時間片,然后返回前面的調(diào)度過程,重新在就緒隊列當中查找優(yōu)先級最高的進程執(zhí)行調(diào)度。 */

if (unlikely(!c)) {

        struct task_struct *p;

 

        spin_unlock_irq(&runqueue_lock);

        read_lock(&tasklist_lock);

        for_each_task(p)

               p->counter = (p->counter >> 1) + NICE_TO_TICKS(p->nice);

        read_unlock(&tasklist_lock);

        spin_lock_irq(&runqueue_lock);

        goto repeat_schedule;

}

 

/*CPU私有調(diào)度數(shù)據(jù)中記錄當前進程的指針,并且將當前進程與CPU綁定,如果待調(diào)度進程與前面一個進程屬于同一個進程,則不需要調(diào)度,直接返回。*/

sched_data->curr = next;

task_set_cpu(next, this_cpu);

spin_unlock_irq(&runqueue_lock);

 

if (unlikely(prev == next)) {

        /* We won't go through the normal tail, so do this by hand */

        prev->policy &= ~SCHED_YIELD;

        goto same_process;

}

/*全局統(tǒng)計進程上下文切換次數(shù)*/

kstat.context_swtch++;

/*如果后進程的mm為0 (未分配頁),則檢查是否被在被激活的頁里(active_mm),否則換頁。令后進程記錄前進程激活頁的信息,將前進程的active_mm中的 mm_count值加一。將cpu_tlbstate[cpu].state改為 TLBSTATE_LAZY(采用lazy模式) 如果后進程的mm不為0(已分配頁),但尚未激活,換頁。切換mm(switch_mm)。 如果前進程的mm 為0(已失效) ,將其激活記錄置空,將mm結(jié)構(gòu)引用數(shù)減一,刪除該頁。 */

prepare_to_switch();

{

        struct mm_struct *mm = next->mm;

        struct mm_struct *oldmm = prev->active_mm;

        if (!mm) {

               BUG_ON(next->active_mm);

               next->active_mm = oldmm;

               atomic_inc(&oldmm->mm_count);

               enter_lazy_tlb(oldmm, next, this_cpu);

        } else {

               BUG_ON(next->active_mm != mm);

               switch_mm(oldmm, mm, next, this_cpu);

        }

 

        if (!prev->mm) {

               prev->active_mm = NULL;

               mmdrop(oldmm);

        }

}

 

/*切換到后進程,調(diào)度過程結(jié)束*/

switch_to(prev, next, prev);

__schedule_tail(prev);

 

same_process:

reacquire_kernel_lock(current);

if (current->need_resched)

        goto need_resched_back;

return;

}

 

3.         進程上下文切換(摘自中國Linux論壇一片文章)

首先進程切換需要做什么?它做的事只是保留正在運行進程的"環(huán)境",并把將要運行的進程的"環(huán)境"加載上來,這個環(huán)境也叫上下文。它包括各個進程"公用"的東西,比如寄存器。
下一個問題,舊的進程環(huán)境保存在那,新的進程環(huán)境從那來,在i386上,有個tss段,是專用來保存進程運行環(huán)境的。在Linux來說,在結(jié)構(gòu)task_struct中有個類型為struct thread_struct的成員叫tss,如下:

struct task_struct {

。。。

/* tss for this task */

struct thread_struct tss;

。。。

};

它是專用來存放進程環(huán)境的,這個結(jié)構(gòu)體因CPU而異,你看它就能知道有那些寄存器是需要保存的了。

最后的問題就是切換了,雖然在i386上CPU可以自動根據(jù)tss去進行上下文的切換,但是Linux的程序員們更愿意自己做它,原因是這樣能得到更有效的控制,而且作者說這和硬件切換的速度差不多,這可是真的夠偉大的。

好了,現(xiàn)在來看源碼,進程切換是使用switch_to這個宏來做的,當進入時prev即是現(xiàn)在運行的進程,next是接下來要切換到的進程,

#define switch_to(prev,next,last) do { /

asm volatile(

"pushl %%esi/n/t" /

"pushl %%edi/n/t" /

"pushl %%ebp/n/t" /

 

// 首先它切換堆棧指針,prev->tss.esp = %esp;%esp = next->tss.esp,這以后的堆棧已經(jīng)是next的堆棧了。

"movl %%esp,%0/n/t" /* save ESP */ /

"movl %3,%%esp/n/t" /* restore ESP */ /

 

// 然后使進程prev的指針保存為標號為1的那一個指針,這樣下次進程prev可以運行時,它第一個執(zhí)行的就是pop指令。

"movl $1f,%1/n/t" /* save EIP */ /

 

// 把進程next保存的指針推進堆棧中,這句作用是,從__switch_to返回時,下一個要執(zhí)行的指令將會是這個指針所指向的指令了。

"pushl %4/n/t" /* restore EIP */ /

 

// 使用jump跳到__switch_to函數(shù)的結(jié)果是:調(diào)用switch_to函數(shù)但不象call那樣要壓棧,但是ret返回時,仍是要彈出堆棧的,也就是上條指令中推進去的指令指針。這樣,堆棧和指令都換了,進程也就被"切換"了。

"jmp __switch_to/n" /

 

// 由于上面所說的原因,__switch_to返回后并不會執(zhí)行下面的語句,要執(zhí)行到這,只有等進程prev重新被調(diào)度了。

"1:/t" /

"popl %%ebp/n/t" /

"popl %%edi/n/t" /

"popl %%esi/n/t" /

:"=m" (prev->tss.esp),"=m" (prev->tss.eip), /

"=b" (last) /

:"m" (next->tss.esp),"m" (next->tss.eip), /

"a" (prev), "d" (next), /

"b" (prev)); /

} while (0)

 

最后是__switch_to函數(shù),它雖然是c形式,但內(nèi)容還都是嵌入?yún)R編。

 

// 這句跟fpu有關(guān),我并不感興趣,就略過了。

unlazy_fpu(prev);

 

// 這句可能需要你去記起前面第二章中所描述的gdt表的結(jié)構(gòu)了,它的作用是把進程next的tss描述符的type中的第二位清0,這位表示這個描述符是不是當前正在用的描述符,作者說如果不清0就把它load進tss段寄存器的話,系統(tǒng)會報異常(我可沒試過)。

gdt_table[next->tss.tr >> 3].b &= 0xfffffdff;

 

// 把進程next的tss段load到tss段存器中。

asm volatile("ltr %0": :"g" (*(unsigned short *)&next->tss.tr));

 

// 保存進程prev的fs和gs段寄存器

asm volatile("movl %%fs,%0":"=m" (*(int *)&prev->tss.fs));

asm volatile("movl %%gs,%0":"=m" (*(int *)&prev->tss.gs));

 

然后下面就是load進程next的ldt,頁表,fs,gs,debug寄存器。

因為Linux一般并不使用ldt,所以它們一般會指向一個共同的空的ldt段描述符,這樣就可能不需要切換ldt了,如果進程next和prev是共享內(nèi)存的話,那么頁表的轉(zhuǎn)換也就不必要了(這一般發(fā)生在clone時)。

 

二、2.6版內(nèi)核對進程調(diào)度的優(yōu)化

1.         新調(diào)度算法簡介

2.6版本的Linux內(nèi)核使用了新的調(diào)度器算法,稱為O(1)算法,它在高負載的情況下執(zhí)行得非常出色,并在有多個處理器時能夠很好地擴展。

2.4版本的調(diào)度器中,時間片重算算法要求在所有的進程都用盡它們的時間片后,新時間片才會被重新計算。在一個多處理器系統(tǒng)中,當進程用完它們的時 間片后不得不等待重算,以得到新的時間片,從而導致大部分處理器處于空閑狀態(tài),影響SMP的效率。此外,當空閑處理器開始執(zhí)行那些時間片尚未用盡的、處于 等待狀態(tài)的進程時,會導致進程開始在處理器之間“跳躍”。當一個高優(yōu)先級進程或交互式進程發(fā)生跳躍時,整個系統(tǒng)的性能就會受到影響。

新調(diào)度器解決上述問題的方法是,基于每個CPU來分布時間片,并取消全局同步和重算循環(huán)。調(diào)度器使用了兩個優(yōu)先級數(shù)組,即活動數(shù)組和過期數(shù)組,可以 通過指針來訪問它們。活動數(shù)組中包含所有映射到某個CPU且時間片尚未用盡的任務(wù)。過期數(shù)組中包含時間片已經(jīng)用盡的所有任務(wù)的有序列表。如果所有活動任務(wù) 的時間片都已用盡,那么指向這兩個數(shù)組的指針互換,包含準備運行任務(wù)的過期數(shù)組成為活動數(shù)組,而空的活動數(shù)組成為包含過期任務(wù)的新數(shù)組。數(shù)組的索引存儲在 一個64位的位圖中,所以很容易找到最高優(yōu)先級的任務(wù)。

 

新調(diào)度器的主要優(yōu)點包括:

◆     SMP效率 如果有工作需要完成,所有處理器都會工作。

◆     等待進程 沒有進程需要長時間地等待處理器,也沒有進程會無端地占用大量的CPU時間。

◆     SMP進程映射 進程只映射到一個CPU,而且不會在CPU之間跳躍。

◆     優(yōu)先級 非重要任務(wù)的優(yōu)先級低,反之亦然。

◆     負載平衡 調(diào)度器會降低那些超出處理器負載能力的進程的優(yōu)先級。

◆     交互性能 即使在高負載的情況下,系統(tǒng)花費很長時間來響應(yīng)鼠標點擊或鍵盤輸入的情況也不會再發(fā)生。

 

2.6版內(nèi)核中,進程調(diào)度經(jīng)過重新編寫,調(diào)度程序不需每次都掃描所有的任務(wù),而是在一個任務(wù)變成就緒狀態(tài)時將其放到一個名為“當前”的隊列中。當進 程調(diào)度程序運行時,只選擇隊列中最有利的任務(wù)來執(zhí)行。這樣,調(diào)度可以在一個恒定的時間里完成。當任務(wù)執(zhí)行時,它會得到一個時間段,或者在其轉(zhuǎn)到另一線程之 前得到一段時間的處理器使用權(quán)。當時間段用完后,任務(wù)會被轉(zhuǎn)移到另一個名為“過期”的隊列中。在該隊列中,任務(wù)會根據(jù)其優(yōu)先級進行排序。

從某種意義上說,所有位于“當前”隊列的任務(wù)都將被執(zhí)行,并被轉(zhuǎn)移到“過期”隊列中。當這種事情發(fā)生時,隊列就會進行切換,原來的“過期”隊列成為 “當前”隊列,而空的“當前”隊列則變成“過期”隊列。由于在新的“當前”隊列中,任務(wù)已經(jīng)被排列好,調(diào)度程序現(xiàn)在使用簡單的隊列算法,即總是取當前隊列 的第一個任務(wù)進行執(zhí)行。這個新過程要比老過程快得多。

 

2.         2.6版新調(diào)度算法分析

下面的schedule()函數(shù)摘錄自2.6.6版內(nèi)核源碼:

asmlinkage void __sched schedule(void)

{

       long *switch_count;

       task_t *prev, *next;

       runqueue_t *rq;

       prio_array_t *array;

       struct list_head *queue;

       unsigned long long now;

       unsigned long run_time;

       int idx;

 

if (likely(!(current->state & (TASK_DEAD | TASK_ZOMBIE)))) {

              if (unlikely(in_atomic())) {

                     printk(KERN_ERR "bad: scheduling while atomic!/n");

                     dump_stack();

              }

       }

need_resched:

       preempt_disable();

       prev = current;

       /*獲取相應(yīng)CPU的進程就緒隊列,每一個CPU都會有一個進程就緒等待隊列,每個等待隊列包括兩個優(yōu)先級數(shù)組,活動數(shù)組和過期數(shù)組,兩個數(shù)組可以進行輪換。CPU相關(guān)等待隊列run-queue的數(shù)據(jù)結(jié)構(gòu)定義如下:

struct runqueue {

       spinlock_t lock;      /*隊列自旋鎖*/

       unsigned long long nr_switches;/*進程上下文切換次數(shù)計數(shù)器*/

       unsigned long nr_running, expired_timestamp, nr_uninterruptible,

              timestamp_last_tick;

       task_t *curr, *idle;  /*標記當前CPU正在執(zhí)行的任務(wù)及空閑任務(wù)的PCB指針*/

       struct mm_struct *prev_mm;       /*內(nèi)存數(shù)據(jù)結(jié)構(gòu)*/

       prio_array_t *active, *expired, arrays[2];     /*優(yōu)先級活動和過期數(shù)組,活動數(shù)組用來標記當前時間片未用完并且處于等待調(diào)度狀態(tài)的進程列表,而過期數(shù)組用來標記當前時間片消耗完畢,無法繼續(xù)運行的進 程列表*/

       int best_expired_prio, prev_cpu_load[NR_CPUS];

#ifdef CONFIG_NUMA

       atomic_t *node_nr_running;

       int prev_node_load[MAX_NUMNODES];

#endif

       task_t *migration_thread;

       struct list_head migration_queue;

 

       atomic_t nr_iowait;

}*/

       rq = this_rq();

 

       release_kernel_lock(prev);

       now = sched_clock();

       if (likely(now - prev->timestamp < NS_MAX_SLEEP_AVG))

              run_time = now - prev->timestamp;

       else

              run_time = NS_MAX_SLEEP_AVG;

 

       /*

        * Tasks with interactive credits get charged less run_time

        * at high sleep_avg to delay them losing their interactive

        * status

        */

       if (HIGH_CREDIT(prev))

              run_time /= (CURRENT_BONUS(prev) ? : 1);
       spin_lock_irq(&rq->lock);
       /*

        * if entering off of a kernel preemption go straight

        * to picking the next task.

        */

       switch_count = &prev->nivcsw;

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

              switch_count = &prev->nvcsw;

              if (unlikely((prev->state & TASK_INTERRUPTIBLE) &&

                            unlikely(signal_pending(prev))))

                     prev->state = TASK_RUNNING;

              else

                     deactivate_task(prev, rq);

       }

       /*如果當前就緒隊列中沒有正在運行的任務(wù),則進行進程上下文切換,到空閑進程*/

       if (unlikely(!rq->nr_running)) {

#ifdef CONFIG_SMP

              load_balance(rq, 1, cpu_to_node_mask(smp_processor_id()));

#endif

              if (!rq->nr_running) {

                     next = rq->idle;

                     rq->expired_timestamp = 0;

                     goto switch_tasks;

              }

       }

       /*獲取當前活動數(shù)組指針,判斷處于活動狀態(tài)的進程數(shù)目,如果活動數(shù)組當中已經(jīng)沒有任何活動的進程,則需要將活動數(shù)組與過期數(shù)組進行對換,這樣就可以很快的開始新一輪的調(diào)度,這里array指針總是指向活動數(shù)組。*/

       array = rq->active;

       if (unlikely(!array->nr_active)) {

              /*

               * Switch the active and expired arrays.

               */

              rq->active = rq->expired;

              rq->expired = array;

              array = rq->active;

              rq->expired_timestamp = 0;

              rq->best_expired_prio = MAX_PRIO;

       }

       /*由于調(diào)度器事先將活動數(shù)組的索引存放在了一個64位的bitmap位圖結(jié)構(gòu)當中,排序的依據(jù)是根據(jù)每個進程的優(yōu)先級排列的,所以當我們需要查找當前活 動數(shù)組中優(yōu)先級最高的進程的索引時,僅僅需要從位圖中找到最前面的一個索引值就可以了,而不需要想2.4內(nèi)核那樣每次遍歷就緒隊列,計算每個進程的優(yōu)先級 然后才能確定應(yīng)該選擇哪個進程作為下一個調(diào)度的對象。

查找到當前隊列中優(yōu)先級最高的進程索引,然后在列表中找到相應(yīng)的進程。*/

       idx = sched_find_first_bit(array->bitmap);

       queue = array->queue + idx;

       next = list_entry(queue->next, task_t, run_list);

       /*一個進程的優(yōu)先級是從0到MAX_PRIO-1(MAX_PRIO為140),實時進程優(yōu)先級是從0到MAX_RT_PRIO- 1(MAX_RT_PRIO為100),SCHED_NORMAL任務(wù)的優(yōu)先級在MAX_RT_PRIO到MAX_PRIO-1之間,優(yōu)先級值越小表明優(yōu) 先級越大。

在這里首先判斷當前需要調(diào)度的進程是否為實時進程,如果不是看是不是處于活動狀態(tài),如果是,根據(jù)其消耗的時間片,重新計算優(yōu)先級。*/

       if (!rt_task(next) && next->activated > 0) {

              unsigned long long delta = now - next->timestamp;

 

              if (next->activated == 1)

                     delta = delta * (ON_RUNQUEUE_WEIGHT * 128 / 100) / 128;

 

              array = next->array;

              dequeue_task(next, array);

              recalc_task_prio(next, next->timestamp + delta);

              enqueue_task(next, array);

       }

       next->activated = 0;

       /*開始進行進程切換,關(guān)于內(nèi)核如何進行上下文切換這里就不看了*/

switch_tasks:                   

       reacquire_kernel_lock(current);

       preempt_enable_no_resched();

       if (test_thread_flag(TIF_NEED_RESCHED))

              goto need_resched;

}

3.         2.6版新調(diào)度算法流程圖

 

三、內(nèi)核中斷及定時器實現(xiàn)分析
定時器是Linux提供的一種定時服務(wù)的機制。它在某個特定的時間喚醒某 個進程,來做一些工作。Linux初始化時,init_IRQ()函數(shù)設(shè)定8253的定時周期為10ms(一個tick值)。同樣,在初始化 時,time_init()用setup_irq()設(shè)置時間中斷向量irq0,中斷服務(wù)程序為timer_interrupt。
在2.4版內(nèi)核 及較早的版本當中,定時器的中斷處理采用底半機制,底半處理函數(shù)的注冊在start_kernel()函數(shù)中調(diào)用sechd_init(),在這個函數(shù)中 又調(diào)用init_bh(TIMER_BH, timer_bh)注冊了定時器的底半處理函數(shù)。然后系統(tǒng)才調(diào)用time_init()來注冊定時器的中斷向量和中斷處理函數(shù)。
在中斷處理函數(shù) timer_interrupt()中,主要是調(diào)用do_timer()函數(shù)完成工作。do_timer()函數(shù)的主要功能就是調(diào)用mark_bh()產(chǎn) 生軟中斷,隨后處理器會在合適的時候調(diào)用定時器底半處理函數(shù)timer_bh()。在timer_bh()中,實現(xiàn)了更新定時器的功能。2.4.23版的 do_timer()函數(shù)代碼如下(經(jīng)過簡略):

void do_timer(struct pt_regs *regs)
{
       (*(unsigned long *)&jiffies)++;
       update_process_times(user_mode(regs));
       mark_bh(TIMER_BH);
}

而在內(nèi)核2.6版本以后,定時器中斷處理采用了軟中斷機制而不是底半機制。時鐘中斷處理函數(shù)仍然為timer_interrup()-> do_timer_interrupt()-> do_timer_interrupt_hook()-> do_timer()。不過do_timer()函數(shù)的實現(xiàn)有所不同:

void do_timer(struct pt_regs *regs)
{
       jiffies_64++;
       update_process_times(user_mode(regs));
       update_times();
}

兩者所調(diào)用的函數(shù)基本相同,但是2.4.23版內(nèi)核與2.6.6內(nèi)核中,對于update_process_times()函數(shù)的實現(xiàn)不同:

void update_process_times(int user_tick)
{
       struct task_struct *p = current;
       int cpu = smp_processor_id(), system = user_tick ^ 1;

       update_one_process(p, user_tick, system, cpu);
       run_local_timers();
       scheduler_tick(user_tick, system);
}

update_process_times()調(diào)用run_local_timers()引起TIMER_SOFTIRQ定時器軟中斷,處理器在隨 后的合適的時機運行軟中斷處理函數(shù)run_timer_softirq,這個函數(shù)是在init_timers()函數(shù)中注冊的:

void __init init_timers(void)
{
       timer_cpu_notify(&timers_nb, (unsigned long)CPU_UP_PREPARE,

                            (void *)(long)smp_processor_id());
       register_cpu_notifier(&timers_nb);
       open_softirq(TIMER_SOFTIRQ, run_timer_softirq, NULL);
}
事實上軟中斷處理函數(shù)run_timer_softirq()并沒有做什么工作,主要的任務(wù)還是通過調(diào)用__run_timers()函數(shù)完成的,這個函數(shù)相當于2.4.23內(nèi)核當中的run_timer_list()函數(shù)的功能。
static inline void __run_timers(tvec_base_t *base)
{
       struct timer_list *timer;
       spin_lock_irq(&base->lock);
       /*這里進入定時器處理循環(huán),利用系統(tǒng)全局jiffies與定時器基準jiffies進行對比,如果前者大,則表明有某些定時器需要進行處理了,否則表示所有的定時器都沒有超時*/
       while (time_after_eq(jiffies, base->timer_jiffies)) {
              struct list_head work_list = LIST_HEAD_INIT(work_list);
              struct list_head *head = &work_list;
             int index = base->timer_jiffies & TVR_MASK;
              /*
              在時間列表數(shù)據(jù)結(jié)構(gòu)當中查找是否存在需要進行超時處理的定時器,時間列表的數(shù)據(jù)結(jié)構(gòu)定義如下:
              typedef struct tvec_s {
                     struct list_head vec[TVN_SIZE];
} tvec_t;

typedef struct tvec_root_s {
                     struct list_head vec[TVR_SIZE];
} tvec_root_t;

struct tvec_t_base_s {

                     spinlock_t lock;

                     unsigned long timer_jiffies;

                     struct timer_list *running_timer;

                     tvec_root_t tv1;

                     tvec_t tv2;

                     tvec_t tv3;

                     tvec_t tv4;

                     tvec_t tv5;

} ____cacheline_aligned_in_smp;
*/
              if (!index &&

                     (!cascade(base, &base->tv2, INDEX(0))) &&

                            (!cascade(base, &base->tv3, INDEX(1))) &&

                                   !cascade(base, &base->tv4, INDEX(2)))

                     cascade(base, &base->tv5, INDEX(3));

              ++base->timer_jiffies;

              list_splice_init(base->tv1.vec + index, &work_list);
repeat:
/*如果當前找到的時間數(shù)組對應(yīng)的列表不為空,則表明該列表上串連的所有定時器都已經(jīng)超時,循環(huán)調(diào)用每個定時器的處理函數(shù),并將其從列表中刪除,直到列表為空為止。*/
              if (!list_empty(head)) {
                     void (*fn)(unsigned long);
                     unsigned long data;

                    timer = list_entry(head->next,struct timer_list,entry);
                    fn = timer->function;
                    data = timer->data;

                     list_del(&timer->entry);
                     set_running_timer(base, timer);
                     smp_wmb();
                     timer->base = NULL;
                     spin_unlock_irq(&base->lock);
                     fn(data);
                     spin_lock_irq(&base->lock);
                     goto repeat;
              }
       }
       set_running_timer(base, NULL);
       spin_unlock_irq(&base->lock);

}


四、系統(tǒng)調(diào)用的實現(xiàn)過程

Linux系統(tǒng)調(diào)用的形式與POSIX兼容,也是一套C語言函數(shù)名的集合,入fock(),read()等等共221個。系統(tǒng)調(diào)用是通過INT 0x80軟中斷調(diào)用進入內(nèi)核,然后根據(jù)系統(tǒng)調(diào)用號分門別類的進行服務(wù)。

系統(tǒng)調(diào)用號:文件include/asm-i386/unistd.h為每一個系統(tǒng)調(diào)用規(guī)定了唯一的編號,這個編號與真正的響應(yīng)函數(shù)之間的關(guān)系是利 用系統(tǒng)調(diào)用號為數(shù)組的下標,可以在sys_call_table(系統(tǒng)調(diào)用表數(shù)組)中查找對應(yīng)的響應(yīng)函數(shù)的sys_name的入口地址。

系統(tǒng)調(diào)用表:系統(tǒng)調(diào)用表sys_call_table是一組宏定義,將系統(tǒng)調(diào)用的編號和相應(yīng)的內(nèi)核系統(tǒng)調(diào)用處理函數(shù)的入口地址綁定。

內(nèi)核宏定義syscallN()用于系統(tǒng)調(diào)用的格式轉(zhuǎn)換和參數(shù)的傳遞。其中N取0~6之間的任意整數(shù)。參數(shù)個數(shù)為N的系統(tǒng)調(diào)用由syscallN負 責格式轉(zhuǎn)換和參數(shù)傳遞。對系統(tǒng)調(diào)用的初始化,即是對INT 0x80的初始化。系統(tǒng)啟動時,匯編子程序setup_idt準備了256項的idt表。設(shè)置0x80號軟中斷服務(wù)程序system_call,這個就是 所有系統(tǒng)調(diào)用的總?cè)肟凇?/p>

當進程需要進行系統(tǒng)調(diào)用時,必須以C語言函數(shù)的形式寫一句系統(tǒng)調(diào)用命令。該命令如果已經(jīng)在某個頭文件中由相應(yīng)的syscallN()展開,則用戶程 序必須包含該頭文件。當進程執(zhí)行到系統(tǒng)調(diào)用命令時,實際上執(zhí)行的是syscallN()展開的函數(shù)。系統(tǒng)調(diào)用的參數(shù)由個寄存器傳遞,然后執(zhí)行INT 0x80中斷,以內(nèi)核態(tài)進入入口地址system_call。

從system_call入口的匯編程序的主要功能是:

l         保存寄存器當前值;

l         檢驗是否為合法的系統(tǒng)調(diào)用;

l         根據(jù)系統(tǒng)調(diào)用表sys_call_table和EAX持有的系統(tǒng)調(diào)用號找出并轉(zhuǎn)入系統(tǒng)調(diào)用響應(yīng)函數(shù);

l         從該響應(yīng)函數(shù)返回后,讓EAX寄存器保存函數(shù)返回值,跳轉(zhuǎn)至ret_from_sys_call(arch/i386/kernel/entry.S)。

以ret_from_sys_call入口的匯編程序段在Linux進程管理中起著十分重要的作用。所有系統(tǒng)調(diào)用結(jié)束前,以及大部分中斷服務(wù)程序返 回前,都會跳轉(zhuǎn)至此入口地址。反過來,該段程序也不僅僅為系統(tǒng)調(diào)用服務(wù),它還要處理中斷嵌套、bottom half隊列、CPU調(diào)度、信號等事務(wù)。

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導購買等信息,謹防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多