(轉(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)境也叫上下文。它包括各個進程"公用"的東西,比如寄存器。 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); * 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)分析 void do_timer(struct pt_regs *regs) 而在內(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) 兩者所調(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) update_one_process(p, user_tick, system, cpu); update_process_times()調(diào)用run_local_timers()引起TIMER_SOFTIRQ定時器軟中斷,處理器在隨 后的合適的時機運行軟中斷處理函數(shù)run_timer_softirq,這個函數(shù)是在init_timers()函數(shù)中注冊的: void __init init_timers(void) (void *)(long)smp_processor_id()); typedef struct tvec_root_s { 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; (!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); timer = list_entry(head->next,struct timer_list,entry); list_del(&timer->entry); }
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ù)。 |
|