久综合色-久综合网-玖草影视-玖草资源在线-亚洲黄色片子-亚洲黄色片在线观看

Hello! 歡迎來到小浪云!


Linux 進(jìn)程調(diào)度之schdule主調(diào)度器


考慮到文章篇幅,在這里我只討論普通進(jìn)程,其調(diào)度算法采用的是cfs(完全公平)調(diào)度算法。 至于cfs調(diào)度算法的實現(xiàn)后面后專門寫一篇文章,這里只要記住調(diào)度時選擇一個優(yōu)先級最高的任務(wù)執(zhí)行

一、調(diào)度單位簡介1.1 task_struct 結(jié)構(gòu)體簡介

對于Linux內(nèi)核來說,調(diào)度的基本單位是任務(wù),用 Struct task_struct 表示,定義在include/linux/sched.h文件中,這個結(jié)構(gòu)體包含一個任務(wù)的所有信息,結(jié)構(gòu)體成員很多,在這里我只列出與本章內(nèi)容有關(guān)的成員:

代碼語言:JavaScript代碼運行次數(shù):0運行復(fù)制

struct task_struct {......(1)volatile long state;(2)const struct sched_class *sched_class;(3)void *stack;struct Thread_struct thread;struct mm_struct *mm, *active_mm;......}

(1)state :表示任務(wù)當(dāng)前的狀態(tài),當(dāng)state為TASK_RUNNING時,表示任務(wù)處于可運行的狀態(tài),并不一定表示目前正在占有CPU,也許在等待調(diào)度,調(diào)度器只會選擇在該狀態(tài)下的任務(wù)進(jìn)行調(diào)度。該狀態(tài)確保任務(wù)可以立即運行,而不需要等待外部事件

簡單來說就是:任務(wù)調(diào)度的對象是處于TASK_RUNNING狀態(tài)的任務(wù)。

處于TASK_RUNNING狀態(tài)的任務(wù),可能正在執(zhí)行用戶態(tài)代碼,也可能正在執(zhí)行內(nèi)核態(tài)的代碼。

(2)sched_class :表示任務(wù)所屬的調(diào)度器類,我們這里只講CFS調(diào)度類。

代碼語言:javascript代碼運行次數(shù):0運行復(fù)制

//  kernel/sched/sched.hstruct sched_class {......//將任務(wù)加入可運行的隊列中void (*enqueue_task) (struct rq *rq, struct task_struct *p, int flags);//將任務(wù)移除可運行的隊列中void (*dequeue_task) (struct rq *rq, struct task_struct *p, int flags);//選擇一下將要運行的任務(wù)struct task_struct * (*pick_next_task) (struct rq *rq,struct task_struct *prev,struct pin_cookie cookie);......}extern const struct sched_class fair_sched_class;

代碼語言:javascript代碼運行次數(shù):0運行復(fù)制

//  kernel/sched/fair.cconst struct sched_class fair_sched_class;/* * All the scheduling class methods: */const struct sched_class fair_sched_class = {.......enqueue_task= enqueue_task_fair,    //CFS 的 enqueue_task 實例.dequeue_task= dequeue_task_fair,//CFS 的 dequeue_task 實例.pick_next_task= pick_next_task_fair,//CFS 的 pick_next_task 實例......}

(3)任務(wù)上下文:表示任務(wù)調(diào)度時要切換的任務(wù)上下文(任務(wù)切換只發(fā)生在內(nèi)核態(tài))。 stack:當(dāng)前任務(wù)的內(nèi)核態(tài)(用戶態(tài)的sp,用戶態(tài)的ip,在內(nèi)核頂部的 pt_regs 結(jié)構(gòu)里面) thread :也叫任務(wù)的硬件上下文,主要包含了大部分CPU寄存器(如:內(nèi)核棧sp)。 struct mm_struct *mm :切換每個任務(wù)用戶態(tài)的虛擬地址空間(每個任務(wù)的用戶棧都是獨立的,都在內(nèi)存空間里面,切換任務(wù)的虛擬地址空間,也就切換了任務(wù)的用戶棧)。

1.2 task_struct 結(jié)構(gòu)體的產(chǎn)生

任務(wù)(task_struct) 的來源有三處: (1) fork():該函數(shù)是一個系統(tǒng)調(diào)用,可以復(fù)制一個現(xiàn)有的進(jìn)程來創(chuàng)建一個全新的進(jìn)程,產(chǎn)生一個 task_struct,然后調(diào)用wake_up_new_task()喚醒新的進(jìn)程,使其進(jìn)入TASK_RUNNING狀態(tài)。 (2) pthread_create():該函數(shù)是Glibc中的函數(shù),然后調(diào)用clone()系統(tǒng)調(diào)用創(chuàng)建一個線程(又叫輕量級進(jìn)程),產(chǎn)生一個 task_struct,然后調(diào)用wake_up_new_task()喚醒新的線程,使其進(jìn)入TASK_RUNNING狀態(tài)。 (3)kthread_create():創(chuàng)建一個新的內(nèi)核線程,產(chǎn)生一個 task_struct,然后wake_up_new_task(),喚醒新的內(nèi)核線程,使其進(jìn)入TASK_RUNNING狀態(tài)。

其實這三個API最后都會調(diào)用 _do_fork(),不同之處是傳入給 _do_fork() 的參數(shù)不同(clone_flags),最終結(jié)果就是進(jìn)程有獨立的地址空間和棧,而用戶線程可以自己指定用戶棧,地址空間和父進(jìn)程共享,內(nèi)核線程則只有和內(nèi)核共享的同一個棧,同一個地址空間。因此上述三個API最終都會創(chuàng)建一個task_struct結(jié)構(gòu)。

備注:這里沒有討論vfok()。

總結(jié):上述三個方式都產(chǎn)生了一個任務(wù) task_struct,然后喚醒該任務(wù)使其處于TASK_RUNNING狀態(tài),然后這樣調(diào)度器就可以調(diào)度 任務(wù)(task_struct)了。

還有一個來源就是0號進(jìn)程(又叫 idle 進(jìn)程),每個邏輯處理器上都有一個,屬于內(nèi)核態(tài)線程,只有在沒有其他的任務(wù)處于TASK_RUNNING狀態(tài)時(系統(tǒng)此時處于空閑狀態(tài)),任務(wù)調(diào)度器才會選擇0號進(jìn)程,然后重復(fù)執(zhí)行 HLT 指令。 HLT 指令 :停止指令執(zhí)行,并將處理器置于HALT狀態(tài)。簡單來說讓該CPU進(jìn)入休眠狀態(tài),低功耗狀態(tài)。 (該指令只能在 privilege level 0執(zhí)行,且CPU指的是邏輯CPU而不是物理CPU,每個邏輯CPU都有一個idle進(jìn)程)。

Linux 進(jìn)程調(diào)度之schdule主調(diào)度器

1.3 struct rq 結(jié)構(gòu)體

目前的x86_64都有多個處理器,那么對于所有處于TASK_RUNNING狀態(tài)的任務(wù)是應(yīng)該位于一個隊列還有每個處理器都有自己的隊列? Linux采用的是每個CPU都有自己的運行隊列,這樣做的好處: (1)每個CPU在自己的運行隊列上選擇任務(wù)降低了競爭 (2)某個任務(wù)位于一個CPU的運行隊列上,經(jīng)過多次調(diào)度后,內(nèi)核趨于選擇相同的CPU執(zhí)行該任務(wù),那么上次任務(wù)運行的變量很可能仍然在這個CPU緩存上,提高運行效率。

在這里我只討論普通任務(wù)的調(diào)度,因為Linux大部分情況下都是在運行普通任務(wù),普通任務(wù)選擇的調(diào)度器是CFS完全調(diào)度。

在調(diào)度時,調(diào)度器去 CFS 運行隊列找是否有任務(wù)需要運行。

代碼語言:javascript代碼運行次數(shù):0運行復(fù)制

DEFINE_PER_CPU_SHAred_ALIGNED(struct rq, runqueues);

代碼語言:javascript代碼運行次數(shù):0運行復(fù)制

struct rq {    .......unsigned int nr_running;   //運行隊列上可運行任務(wù)的個數(shù)struct cfs_rq cfs;         // CFS 運行隊列struct task_struct *curr,  //當(dāng)前正在運行任務(wù)的 task_struct 實例struct task_struct *idle,  //指向idle任務(wù)的實例,在沒有其它可運行任務(wù)的時候執(zhí)行......}

二、schedule函數(shù)詳解2.1 schedule函數(shù)簡介

上文說到任務(wù)調(diào)度器是對于可運行狀態(tài)(TASK_RUNNING)的任務(wù)進(jìn)行調(diào)度,如果任務(wù)的狀態(tài)不是TASK_RUNNING,那么該任務(wù)是不會被調(diào)度的。

調(diào)度的入口點是schedule()函數(shù),定義在 kernel/sched/core.c 文件中,這里刪掉了很多代碼,只留了重要的代碼:

代碼語言:javascript代碼運行次數(shù):0運行復(fù)制

asmlinkage __visible void __sched schedule(void){......preempt_disable();//禁止內(nèi)核搶占__schedule(false);//任務(wù)開始調(diào)度,調(diào)度的過程中禁止其它任務(wù)搶占sched_preempt_enable_no_resched();//開啟內(nèi)核搶占......}

這里注意schedule調(diào)用__schedule函數(shù)是傳入的是false參數(shù),表示schedule函數(shù)主調(diào)度器。 調(diào)度分為主動調(diào)度和搶占調(diào)度。

__schedule的參數(shù)preempt是bool類型,表示本次調(diào)度是否為搶占調(diào)度:

__schedule的參數(shù)preempt等于0表示不是搶占調(diào)度,即主動調(diào)度,代表此次調(diào)度是該進(jìn)程主動請求調(diào)度,主動調(diào)用了schedule函數(shù)(任務(wù)主動讓出處理器),比如該進(jìn)程進(jìn)入了阻塞態(tài)。 而schedule函數(shù)參數(shù)固定傳入的參數(shù)是false,也就是0,就是調(diào)用schedule函數(shù)就是主動發(fā)起調(diào)度,不是搶占調(diào)度,因此schedule函數(shù)稱為主調(diào)度器。

直接調(diào)用主調(diào)度器schdule函數(shù)的場景有3種: (1)當(dāng)前進(jìn)程需要等待某個條件滿足才能繼續(xù)運行時,調(diào)用一個wait_event()類函數(shù)將自己的狀態(tài)設(shè)為TASK_INTERRUPTIBLE或者TASK_UNINTERRUPTIBLE,掛接到某個等待隊列,然后根據(jù)情況設(shè)置一個合適的喚醒定時器,最后調(diào)用schedule()發(fā)起調(diào)度;

代碼語言:javascript代碼運行次數(shù):0運行復(fù)制

/* * The below macro ___wait_event() has an explicit shadow of the __ret * variable when used from the wait_event_*() macros. * * This is so that both can use the ___wait_cond_timeout() construct * to wrap the condition. * * The type inconsistency of the wait_event_*() __ret variable is also * on purpose; we use long where we can return timeout values and int * otherwise. */#define ___wait_event(wq, condition, state, exclusive, ret, cmd)({__label__ __out;wait_queue_t __wait;long __ret = ret;/* explicit shadow */init_wait_entry(&__wait, exclusive ? WQ_FLAG_EXCLUSIVE : 0);or (;;) {long __int = prepare_to_wait_event(&wq, &__wait, state);if (condition)reak;if (___wait_is_interruptible(state) && __int) {__ret = __int;goto __out;}cmd;}inish_wait(&wq, &__wait);__out:__ret;})#define __wait_event(wq, condition)(void)___wait_event(wq, condition, TASK_UNINTERRUPTIBLE, 0, 0,    schedule())/** * wait_event - sleep until a condition gets true * @wq: the waitqueue to wait on * @condition: a C expression for the event to wait for * * The process is put to sleep (TASK_UNINTERRUPTIBLE) until the * @condition evaluates to true. The @condition is checked each time * the waitqueue @wq is woken up. * * wake_up() has to be called after changing any variable that could * change the result of the wait condition. */#define wait_event(wq, condition)do {might_sleep();if (condition)reak;__wait_event(wq, condition);} while (0)

(2)當(dāng)前進(jìn)程需要睡眠一段特定的時間(不等待任何事件)時,調(diào)用一個sleep()類函數(shù)將自己的狀態(tài)設(shè)為TASK_INTERRUPTIBLE或者TASK_UNINTERRUPTIBLE但不進(jìn)入任何等待隊列,然后設(shè)置一個合適的喚醒定時器,最后調(diào)用schedule()發(fā)起調(diào)度;

(3)當(dāng)前進(jìn)程單純地想要讓出CPU控制權(quán)時,調(diào)用yield()函數(shù)將自己的狀態(tài)設(shè)為TASK_RUNNING并依舊處于運行隊列,然后執(zhí)行特定調(diào)度類的yield_task()操作,最后調(diào)用schedule()發(fā)起自愿調(diào)度。

代碼語言:javascript代碼運行次數(shù):0運行復(fù)制

/** * sys_sched_yield - yield the current processor to other threads. * * This function yields the current CPU to other tasks. If there are no * other threads running on this CPU then this function will return. * * Return: 0. */SYSCALL_DEFINE0(sched_yield){struct rq *rq = this_rq_lock();schedstat_inc(rq->yld_count);current->sched_class->yield_task(rq);/* * Since we are going to call schedule() anyway, there's * no need to preempt or enable interrupts: */__release(rq->lock);spin_release(&rq->lock.dep_map, 1, _THIS_IP_);do_raw_spin_unlock(&rq->lock);sched_preempt_enable_no_resched();schedule();return 0;}

__schedule的參數(shù)preempt等于1表示是搶占調(diào)度,有處于運行態(tài)的任務(wù)發(fā)起的搶占調(diào)度。 例舉幾處發(fā)起搶占調(diào)度的地方:

代碼語言:javascript代碼運行次數(shù):0運行復(fù)制

static void __sched notrace preempt_schedule_common(void){do {/* * Because the function tracer can trace preempt_count_sub() * and it also uses preempt_enable/disable_notrace(), if * NEED_RESCHED is set, the preempt_enable_notrace() called * by the function tracer will call this function again and * cause infinite recursion. * * Preemption must be disabled here before the function * tracer can trace. Break up preempt_disable() into two * calls. One to disable preemption without fear of being * traced. The other to still record the preemption latency, * which can also be traced by the function tracer. */preempt_disable_notrace();preempt_latency_start(1);//搶占調(diào)度__schedule(true);preempt_latency_stop(1);preempt_enable_no_resched_notrace();/* * Check again in case we missed a preemption opportunity * between schedule and now. */} while (need_resched());}#ifdef CONFIG_PREEMPT/* * this is the entry point to schedule() from in-kernel preemption * off of preempt_enable. Kernel preemptions off return from interrupt * occur there and call schedule directly. */asmlinkage __visible void __sched notrace preempt_schedule(void){/* * If there is a non-zero preempt_count or interrupts are disabled, * we do not want to preempt the current task. Just return.. */if (likely(!preemptible()))return;preempt_schedule_common();}NOKPROBE_SYMBOL(preempt_schedule);EXPORT_SYMBOL(preempt_schedule);/** * preempt_schedule_notrace - preempt_schedule called by tracing * * The tracing infrastructure uses preempt_enable_notrace to prevent * recursion and tracing preempt enabling caused by the tracing * infrastructure itself. But as tracing can happen in areas coming * from userspace or just about to enter userspace, a preempt enable * can occur before user_exit() is called. This will cause the scheduler * to be called when the system is still in usermode. * * To prevent this, the preempt_enable_notrace will use this function * instead of preempt_schedule() to exit user context if needed before * calling the scheduler. */asmlinkage __visible void __sched notrace preempt_schedule_notrace(void){enum ctx_state prev_ctx;if (likely(!preemptible()))return;do {/* * Because the function tracer can trace preempt_count_sub() * and it also uses preempt_enable/disable_notrace(), if * NEED_RESCHED is set, the preempt_enable_notrace() called * by the function tracer will call this function again and * cause infinite recursion. * * Preemption must be disabled here before the function * tracer can trace. Break up preempt_disable() into two * calls. One to disable preemption without fear of being * traced. The other to still record the preemption latency, * which can also be traced by the function tracer. */preempt_disable_notrace();preempt_latency_start(1);/* * Needs preempt disabled in case user_exit() is traced * and the tracer calls preempt_enable_notrace() causing * an infinite recursion. */prev_ctx = exception_enter();//搶占調(diào)度__schedule(true);exception_exit(prev_ctx);preempt_latency_stop(1);preempt_enable_no_resched_notrace();} while (need_resched());}EXPORT_SYMBOL_GPL(preempt_schedule_notrace);#endif /* CONFIG_PREEMPT *//* * this is the entry point to schedule() from kernel preemption * off of irq context. * Note, that this is called and return with irqs disabled. This will * protect us against recursive calling from irq. */asmlinkage __visible void __sched preempt_schedule_irq(void){enum ctx_state prev_state;/* Catch callers which need to be fixed */BUG_ON(preempt_count() || !irqs_disabled());prev_state = exception_enter();do {preempt_disable();local_irq_enable();//搶占調(diào)度__schedule(true);local_irq_disable();sched_preempt_enable_no_resched();} while (need_resched());exception_exit(prev_state);}

__schedule()是主調(diào)度器的主要函數(shù),__schedule在內(nèi)核源碼中有很多注釋,如下所示: 驅(qū)使調(diào)度器并因此進(jìn)入此函數(shù)的主要方法有: 1.顯式阻塞:互斥、信號量、等待隊列等。 2.中斷和用戶空間返回路徑上檢查TIF_NEED_RESCHED標(biāo)志。例如,請參考arch/x86/entry_64.S。 為了驅(qū)動任務(wù)之間的搶占,調(diào)度程序在定時器中斷處理程序scheduler_tick()中設(shè)置標(biāo)志。 3.喚醒不會真正馬上調(diào)用schedule(),只是將一個任務(wù)添加到運行隊列中,設(shè)置任務(wù)標(biāo)志位為TIF_NED_RESCHED,也就是將喚醒的進(jìn)程加入的CFS就緒隊列中(將喚醒的進(jìn)程調(diào)度實體加入到紅黑樹中),僅此而已。

現(xiàn)在,如果添加到運行隊列的新任務(wù)搶占了當(dāng)前任務(wù),則設(shè)置TIF_NED_RESCHED,并在以下的可能情況下調(diào)用schedule(),也就是喚醒的進(jìn)程什么時候調(diào)用schedule()函數(shù),分為以下兩種情況:

(1)如果內(nèi)核可搶占(CONFIG_PREMPT=y): 在系統(tǒng)調(diào)用或異常上下文中,在下一次調(diào)用preempt_enable()時檢查是否需要搶占調(diào)度。 在IRQ上下文中,從中斷處理程序返回到可搶占上下文。硬件中斷處理返回前會檢查是否要搶占當(dāng)前進(jìn)程。

(2)如果內(nèi)核不可搶占(未設(shè)置CONFIG_PREMPT) 調(diào)用cond_resched()。 顯式調(diào)用schedule()。 從syscall或異常返回到用戶空間。 從中斷處理程序返回到用戶空間。

2.2 __schedule 代碼解圖代碼語言:javascript代碼運行次數(shù):0運行復(fù)制

//__schedule() is the main scheduler function.static void __sched notrace __schedule(bool preempt){    (1)struct task_struct *prev, *next;unsigned long *switch_count;struct pin_cookie cookie;struct rq *rq;int cpu;    (2)cpu = smp_processor_id();rq = cpu_rq(cpu);prev = rq->curr;(3)if (!preempt && prev->state) {if (unlikely(signal_pending_state(prev->state, prev))) {prev->state = TASK_RUNNING;} else {deactivate_task(rq, prev, DEQUEUE_SLEEP);prev->on_rq = 0;}}(4)next = pick_next_task(rq, prev, cookie);clear_tsk_need_resched(prev);clear_preempt_need_resched();(5)if (likely(prev != next)) {rq->nr_switches++;rq->curr = next;++*switch_count;rq = context_switch(rq, prev, next, cookie); /* unlocks the rq */} else {lockdep_unpin_lock(&rq->lock, cookie);raw_spin_unlock_irq(&rq->lock);}}

(1)prev局部變量表示要切換出去的任務(wù),next局部變量表示要切換進(jìn)來的任務(wù)。

代碼語言:javascript代碼運行次數(shù):0運行復(fù)制

struct task_struct *prev, *next;

(2)找到當(dāng)前CPU的運行隊列 struct rq,把當(dāng)前正在運行的任務(wù)curr 賦值給 prev。

代碼語言:javascript代碼運行次數(shù):0運行復(fù)制

int cpu = smp_processor_id();struct rq = cpu_rq(cpu);struct task_struct *prev = rq->curr;

(3) preempt 用于判斷本次調(diào)度是否為搶占調(diào)度,如果發(fā)生了調(diào)度搶占(preempt =1),那么直接跳過,不參與判斷,直接調(diào)用pick_next_task。搶占調(diào)度通常都是處于運行態(tài)的任務(wù)發(fā)起的搶占調(diào)度。

如果本次調(diào)度不是搶占調(diào)度(preempt = 0),并且該進(jìn)程的state不等于 TASK_RUNNING (0),也就是不是運行態(tài),處于其他狀態(tài)。代表此次調(diào)度是該進(jìn)程主動請求調(diào)度,主動調(diào)用了schedule函數(shù),比如該進(jìn)程進(jìn)入了阻塞態(tài)。

進(jìn)程在操作外部設(shè)備的時候(網(wǎng)絡(luò)和存儲則多是和外部設(shè)備的合作),往往需要讓出 CPU,發(fā)起主動調(diào)度。

由于進(jìn)程不是運行態(tài):TASK_RUNNING了,那么就不能在CFS就緒隊列中了,那么就調(diào)用 deactivate_task 將陷入阻塞態(tài)的進(jìn)程移除CFS就緒隊列,并將進(jìn)程調(diào)度實體的 on_rq 成員置0,表示不在CFS就緒隊列中了。 通常主動請求調(diào)用之前會提前設(shè)置當(dāng)前進(jìn)程的運行狀態(tài)為 TASK_INTERRUPTIBLE 或者 TASK_UNINTERRUPTIBLE。

代碼語言:javascript代碼運行次數(shù):0運行復(fù)制

#define TASK_RUNNING0#define TASK_INTERRUPTIBLE1#define TASK_UNINTERRUPTIBLE2......

代碼語言:javascript代碼運行次數(shù):0運行復(fù)制

if (!preempt && prev->state) {if (unlikely(signal_pending_state(prev->state, prev))) {prev->state = TASK_RUNNING;} else {deactivate_task(rq, prev, DEQUEUE_SLEEP);prev->on_rq = 0;}}

(4)選擇下一個將要執(zhí)行的任務(wù),通過CFS調(diào)度算法選擇優(yōu)先級最高的一個任務(wù)。 clear_tsk_need_resched將被搶占的任務(wù)prev(也就是當(dāng)前的任務(wù))需要被調(diào)度的標(biāo)志位(TIF_NEED_RESCHED)給清除掉,表示 prev 接下來不會被調(diào)度。 clear_preempt_need_resched 被搶占的任務(wù)prev(也就是當(dāng)前的任務(wù))的 PREEMPT_NEED_RESCHED 標(biāo)志位給清除掉。

代碼語言:javascript代碼運行次數(shù):0運行復(fù)制

if (likely(prev != next)) {        //大概率事件,進(jìn)行任務(wù)切換rq->nr_switches++;  //可運行隊列切換次數(shù)更新rq->curr = next;    //將當(dāng)前任務(wù)curr設(shè)置為將要運行的下一個任務(wù) next ++*switch_count;    //任務(wù)切換次數(shù)更新//任務(wù)上下文切換rq = context_switch(rq, prev, next, cookie); /* unlocks the rq */} else {     //小概率事件,不進(jìn)行任務(wù)切換lockdep_unpin_lock(&rq->lock, cookie);raw_spin_unlock_irq(&rq->lock);}

(5)如果選擇的任務(wù)next和 原任務(wù)prev不是同一個任務(wù),則進(jìn)行任務(wù)上下文切換 如果是同一個任務(wù),則不進(jìn)行上下文切換。 注意這里是 用 likely()修飾(這是gcc內(nèi)建的一條指令用于優(yōu)化,編譯器可以根據(jù)這條指令對分支選擇進(jìn)行優(yōu)化),表示有很大的概率 選擇的任務(wù)next 和 原任務(wù)prev不是同一個任務(wù)。 由我們程序員來指明指令最可能的分支走向,以達(dá)到優(yōu)化性能的效果。

代碼語言:javascript代碼運行次數(shù):0運行復(fù)制

if (likely(prev != next)) {        //大概率事件,進(jìn)行任務(wù)切換rq->nr_switches++;  //可運行隊列切換次數(shù)更新rq->curr = next;    //將當(dāng)前任務(wù)curr設(shè)置為將要運行的下一個任務(wù) next ++*switch_count;    //任務(wù)切換次數(shù)更新//任務(wù)上下文切換rq = context_switch(rq, prev, next, cookie); /* unlocks the rq */} else {     //小概率事件,不進(jìn)行任務(wù)切換lockdep_unpin_lock(&rq->lock, cookie);raw_spin_unlock_irq(&rq->lock);}

2.3 context_switch 代碼解讀

任務(wù)切換主要是任務(wù)空間即虛擬內(nèi)存(用戶態(tài)的虛擬地址空間,包括了用戶態(tài)的棧)、CPU寄存器、內(nèi)核態(tài)堆棧。

后面context_switch 還會專門一篇進(jìn)行描述,這里限于篇幅,只是簡單描述一下。

用偽代碼表示:

代碼語言:javascript代碼運行次數(shù):0運行復(fù)制

switch_mm();switch_to(){switch_register();switch_stack();}

代碼語言:javascript代碼運行次數(shù):0運行復(fù)制

/* * context_switch - switch to the new MM and the new thread's register state. */static __always_inline struct rq * context_switch(struct rq *rq, struct task_struct *prev,       struct task_struct *next, struct pin_cookie cookie){struct mm_struct *mm, *oldmm;(1)prepare_task_switch(rq, prev, next);(2)mm = next->mm;oldmm = prev->active_mm;if (!mm) {next->active_mm = oldmm;atomic_inc(&oldmm->mm_count);enter_lazy_tlb(oldmm, next);} elseswitch_mm_irqs_off(oldmm, mm, next);    (3)/* Here we just switch the register state and the stack. */switch_to(prev, next, prev);(4)return finish_task_switch(prev);}

(1)開始任務(wù)切換前,需要做的準(zhǔn)備工作,這里主要是提供了2個接口給我們內(nèi)核開發(fā)者,當(dāng)任務(wù)切換時我們可以自己添加一些操作進(jìn)去,任務(wù)被重新調(diào)度時我們也可以自己添加一些操作進(jìn)去。 同時通知我們?nèi)蝿?wù)被搶占(sched_out)。

代碼語言:javascript代碼運行次數(shù):0運行復(fù)制

prepare_task_switch(rq, prev, next);-->fire_sched_out_preempt_notifiers(prev, next);   -->__fire_sched_out_preempt_notifiers(curr, next);      -->hlist_for_each_entry(notifier, &curr->preempt_notifiers, link)notifier->ops->sched_out(notifier, next);

這里的 sched_in()函數(shù)和 sched_out函數(shù)是內(nèi)核提供給我們開發(fā)者的接口。我們可以通過在這兩個接口里面添加一些操作。 sched_in :任務(wù)重新調(diào)度時會通知我們。 sched_out:任務(wù)被搶占時會通知我們。

備注:調(diào)度器運行調(diào)度相關(guān)的代碼,但其自身并不作為一個單獨的 process 存在,在進(jìn)程切換時,執(zhí)行 switch out 的代碼就是在被換出的 process 中,執(zhí)行 switch in 的代碼就是在被換入的 process 中,因此 scheduler 沒有一個對應(yīng)的 PID。

具體請參考:Linux 進(jìn)程調(diào)度通知機(jī)制

代碼語言:javascript代碼運行次數(shù):0運行復(fù)制

struct preempt_ops {void (*sched_in)(struct preempt_notifier *notifier, int cpu);void (*sched_out)(struct preempt_notifier *notifier,  struct task_struct *next);};struct preempt_notifier {struct hlist_node link;struct preempt_ops *ops;};

(2) 切換任務(wù)的用戶虛擬態(tài)地址(不切換內(nèi)核態(tài)的虛擬地址),也包括了用戶態(tài)的棧,主要就是切換了任務(wù)的CR3, CR3寄存器放的是 頁目錄表物理內(nèi)存基地址。

代碼語言:javascript代碼運行次數(shù):0運行復(fù)制

mm = next->mm;oldmm = prev->active_mm;if (!mm) {   //mm == NULL,代表任務(wù)是內(nèi)核線程//  直接用 被切換進(jìn)程prev的active_mmnext->active_mm = oldmm;atomic_inc(&oldmm->mm_count);//通知處理器不需要切換虛擬地址空間的用戶空間部分,用來加速上下文切換enter_lazy_tlb(oldmm, next);} else//不是內(nèi)核線程,那么就要切換用戶態(tài)的虛擬地址空間,也就是切換任務(wù)的CR3switch_mm_irqs_off(oldmm, mm, next);-->load_cr3(next->pgd); //加載下一個任務(wù)的CR3
Linux 進(jìn)程調(diào)度之schdule主調(diào)度器

(3)切換任務(wù)的寄存器和內(nèi)核態(tài)堆棧,保存原任務(wù)(prev)的所有寄存器信息,恢復(fù)新任務(wù)(next)的所有寄存器信息,當(dāng)切換完之后,并執(zhí)行新的任務(wù)。

代碼語言:javascript代碼運行次數(shù):0運行復(fù)制

switch_to(prev, next, prev);-->__switch_to_asm((prev), (next)))-->ENTRY(__switch_to_asm)/* switch stack */movq%rsp, TASK_threadsp(%rdi)movqTASK_threadsp(%rsi), %rspjmp__switch_toEND(__switch_to_asm)-->__switch_to()

(4) 完成一些清理工作,使其能夠正確的釋放鎖。這個清理工作的完成是第三個任務(wù),系統(tǒng)中隨機(jī)的某個其它任務(wù)。同時通知我們?nèi)蝿?wù)被重新調(diào)度(sched_in)。 這里其實也有點復(fù)雜,我們后面在 context_switch 篇重點描述。

代碼語言:javascript代碼運行次數(shù):0運行復(fù)制

finish_task_switch(prev)-->fire_sched_in_preempt_notifiers(current);-->__fire_sched_in_preempt_notifiers(curr)-->hlist_for_each_entry(notifier, &curr->preempt_notifiers, link)notifier->ops->sched_in(notifier, raw_smp_processor_id());

總結(jié) 這篇文章主要講了schdule主調(diào)度器的工作流程,即一個運行中的進(jìn)程主動調(diào)用 _schedule 讓出 CPU,總結(jié)來說就是: (1)通過調(diào)度算法選擇一個優(yōu)先級最高的任務(wù)。 (2)進(jìn)行任務(wù)上下文切換,上下文切換又分用戶態(tài)進(jìn)程空間的切換和內(nèi)核態(tài)的切換。 保存原任務(wù)的所有寄存器信息,恢復(fù)新任務(wù)的所有寄存器信息,并執(zhí)行新的任務(wù)。 (3)上下文切換完后,新的任務(wù)投入運行。 如下圖所示:

Linux 進(jìn)程調(diào)度之schdule主調(diào)度器

相關(guān)閱讀

主站蜘蛛池模板: 一本本久综合久久爱 | 高清一级片 | 日本老熟妇激情毛片 | 免费一区二区三区四区五区 | 亚洲欧美极品 | 九草在线| 在线观看日韩www视频免费 | 欧美一区二区精品系列在线观看 | 成人午夜视频一区二区国语 | 日本黄页网站在线观看 | 国产精品国三级国产aⅴ | 亚洲自偷| 久草免费在线 | 成人免费观看网欧美片 | 国产三级日本三级日产三 | 欧美三级日韩三级 | 久久综合婷婷香五月 | 成人偷拍视频 | 一区二区三区视频在线 | 玖草在线视频 | 免费人成观看在线网 | 一级毛片成人免费看免费不卡 | 一级特黄aaa大片在线观看 | 高清视频 一区二区三区四区 | 99免费精品 | 久久久久依人综合影院 | 成人精品一区二区久久久 | 日韩日韩日韩手机看片自拍 | 国产手机在线视频 | 久草亚洲视频 | 欧美大尺度xxxxx视频 | 国产成人综合视频 | 国产精品欧美一区二区 | 精品成人在线观看 | aaa在线| 免费一级毛片不卡在线播放 | 97影院在线午夜 | 深夜福利视频网站 | 国产一毛片 | 国产亚洲欧美久久精品 | 美女张开双腿让男人桶视频免费 |