精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

Linux 進(jìn)程管理之進(jìn)程調(diào)度與切換

系統(tǒng) Linux
我們知道,進(jìn)程運(yùn)行需要各種各樣的系統(tǒng)資源,如內(nèi)存、文件、打印機(jī)和最寶貴的 CPU 等,所以說,調(diào)度的實(shí)質(zhì)就是資源的分配。系統(tǒng)通過不同的調(diào)度算法(Scheduling Algorithm)來實(shí)現(xiàn)這種資源的分配。通常來說,選擇什么樣的調(diào)度算法取決于資源分配的策略(Scheduling Policy)。

進(jìn)程調(diào)度相關(guān)內(nèi)核結(jié)構(gòu)

我們知道,進(jìn)程運(yùn)行需要各種各樣的系統(tǒng)資源,如內(nèi)存、文件、打印機(jī)和最寶貴的 CPU 等,所以說,調(diào)度的實(shí)質(zhì)就是資源的分配。系統(tǒng)通過不同的調(diào)度算法(Scheduling Algorithm)來實(shí)現(xiàn)這種資源的分配。通常來說,選擇什么樣的調(diào)度算法取決于資源分配的策略(Scheduling Policy)。

有關(guān)調(diào)度相關(guān)的結(jié)構(gòu)保存在 task_struct 中,如下:

struct task_struct {
/*
task_struct 采用了如下3個(gè)成員表示進(jìn)程的優(yōu)先級(jí),prio、normal_prio表示動(dòng)態(tài)優(yōu)先級(jí)
static_prio 為靜態(tài)優(yōu)先級(jí),靜態(tài)優(yōu)先級(jí)是進(jìn)程啟動(dòng)時(shí)分配的優(yōu)先級(jí),它可以使用nice和sched_setscheduler系統(tǒng)調(diào)用修改,
否則在進(jìn)程運(yùn)行期間會(huì)一直保持恒定
*/
int prio, static_prio, normal_prio;
/*
sched_clas表示該進(jìn)程所屬的調(diào)度器類
2.6.24版本中有3中調(diào)度類:
實(shí)時(shí)調(diào)度器 : rt_sched_class
完全公平調(diào)度器:fair_sched_class
空閑調(diào)度器: idle_sched_class
*/
const struct sched_class *sched_clas;
//調(diào)度實(shí)體
struct sched_entity se;
/*
policy 保存了對(duì)該進(jìn)程應(yīng)用的調(diào)度策略。
進(jìn)程的調(diào)度策略有6種
SCHED_NORMAL SCHED_FIFO SCHED_RR SCHED_BATCH SCHED_IDLE
普通進(jìn)程調(diào)度策略: SCHED_NORMAL、SCHED_BATCH、SCHED_IDLE,這些都是通過完全公平調(diào)度器來處理的
實(shí)時(shí)進(jìn)程調(diào)度策略: SCHED_RR、SCHED_FIFO 這些都是通過實(shí)時(shí)調(diào)度器來處理的
*/
unsigned int policy;
/*
除了內(nèi)核線程(Kernel Thread),每個(gè)進(jìn)程都擁有自己的地址空間(也叫虛擬空間),用mm_struct 來描述。active_mm是為內(nèi)核線程而引入的。因?yàn)閮?nèi)核線程沒有自己的地址空間,
為了讓內(nèi)核線程與普通進(jìn)程具有統(tǒng)一的上下文切換方式,當(dāng)內(nèi)核線程進(jìn)行上下文切換時(shí),讓切換進(jìn)來的線程的active_mm
指向剛被調(diào)度出去的進(jìn)程的active_mm(如果進(jìn)程的mm 域不為空,則其active_mm 域與mm 域相同)
*/
struct mm_struct *mm, *active_mm;
//表示實(shí)時(shí)進(jìn)程的優(yōu)先級(jí),最低的優(yōu)先級(jí)為0,最高為99,值越大,優(yōu)先級(jí)越高
unsigned int rt_priority;
...
};

每個(gè)進(jìn)程都擁有自己的地址空間(也叫虛擬空間),用 mm_struct 來描述。

active_mm 是為內(nèi)核線程而引入的,因?yàn)閮?nèi)核線程沒有自己的地址空間,為了讓內(nèi)核線程與普通進(jìn)程具有統(tǒng)一的上下文切換方式,當(dāng)內(nèi)核線程進(jìn)行上下文切換時(shí),讓切換進(jìn)來的線程的 active_mm 指向剛被調(diào)度出去的進(jìn)程的 active_mm(如果進(jìn)程的mm 域不為空,則其 active_mm 域與 mm 域相同)。

在 linux 2.6 中 sched_class 表示該進(jìn)程所屬的調(diào)度器類有3種:

  • 實(shí)時(shí)調(diào)度器 (RT,rt_sched_class) : 為每個(gè)優(yōu)先級(jí)維護(hù)一個(gè)隊(duì)列;
  • 完全公平調(diào)度器 (CFS,fair_sched_class) : 采用完全公平調(diào)度算法,引入虛擬運(yùn)行時(shí)間的概念。
  • 空閑調(diào)度器(idle_sched_class) : 為每個(gè) CPU 都會(huì)有一個(gè) idle 線程,當(dāng)沒有其他進(jìn)程可以調(diào)度時(shí),調(diào)度運(yùn)行idle線程。

進(jìn)程的調(diào)度策略有5種,用戶可以調(diào)用調(diào)度器里不同的調(diào)度策略:

  • SCHED_FIFO :采用了先入先出,不使用時(shí)間片的調(diào)度算法。若處于可執(zhí)行狀態(tài),就會(huì)一直執(zhí)行,沒有更高優(yōu)先級(jí)的情況下,進(jìn)程只能等待其主動(dòng)讓出 cpu;
  • SCHED_RR :時(shí)間片輪轉(zhuǎn),進(jìn)程用完時(shí)間片后加入優(yōu)先級(jí)對(duì)應(yīng)的運(yùn)行隊(duì)列的尾部,把 CPU 讓給同一優(yōu)先級(jí)的其他進(jìn)程;
  • SCHED_NORMAL : 使 task 選擇 CFS 調(diào)度器來調(diào)度運(yùn)行;
  • SCHED_BATCH:批量處理,使 task 選擇 CFS 調(diào)度器來調(diào)度運(yùn)行;
  • SCHED_IDLE: 使 task 選擇 CFS 調(diào)度器來調(diào)度運(yùn)行

在每個(gè) CPU 中都有一個(gè)自身的運(yùn)行隊(duì)列 rq,每個(gè)活動(dòng)進(jìn)程只出現(xiàn)在一個(gè)運(yùn)行隊(duì)列中,在多個(gè) CPU 上同時(shí)運(yùn)行一個(gè)進(jìn)程是不可能的。

運(yùn)行隊(duì)列是使用如下結(jié)構(gòu)實(shí)現(xiàn)的:

struct rq {
//運(yùn)行隊(duì)列中進(jìn)程的數(shù)目
unsigned long nr_running;
//進(jìn)程切換總數(shù)
u64 nr_switches;
//用于完全公平調(diào)度器的就緒隊(duì)列
struct cfs_rq cfs;
//用于實(shí)時(shí)調(diào)度器的就緒隊(duì)列
struct rt_rq rt;
//記錄本cpu尚處于TASK_UNINTERRUPTIBLE狀態(tài)的進(jìn)程數(shù),和負(fù)載信息有關(guān)
unsigned long nr_uninterruptible;
//curr指向當(dāng)前運(yùn)行的進(jìn)程實(shí)例,idle 指向空閑進(jìn)程的實(shí)例
struct task_struct *curr, *idle;
//上一次調(diào)度時(shí)的mm結(jié)構(gòu)
struct mm_struct *prev_mm;
...
};

每個(gè)運(yùn)行隊(duì)列中有2個(gè)調(diào)度隊(duì)列:CFS 調(diào)度隊(duì)列 和 RT 調(diào)度隊(duì)列。

tast 作為調(diào)度實(shí)體加入到 CPU 中的調(diào)度隊(duì)列中。

系統(tǒng)中所有的運(yùn)行隊(duì)列都在 runqueues 數(shù)組中,該數(shù)組的每個(gè)元素分別對(duì)應(yīng)于系統(tǒng)中的一個(gè) CPU。在單處理器系統(tǒng)中,由于只需要一個(gè)就緒隊(duì)列,因此數(shù)組只有一個(gè)元素。

static DEFINE_PER_CPU_SHARED_ALIGNED(struct rq, runqueues);

內(nèi)核也定義了一下便利的宏,其含義很明顯。

#define cpu_rq(cpu) (&per_cpu(runqueues, (cpu)))
#define this_rq() (&__get_cpu_var(runqueues))
#define task_rq(p) cpu_rq(task_cpu(p))
#define cpu_curr(cpu) (cpu_rq(cpu)->curr)

進(jìn)程調(diào)度與切換

在分析調(diào)度流程之前,我們先來看在什么情況下要執(zhí)行調(diào)度程序,我們把這種情況叫做調(diào)度時(shí)機(jī)。

Linux 調(diào)度時(shí)機(jī)主要有。

  1. 進(jìn)程狀態(tài)轉(zhuǎn)換的時(shí)刻:進(jìn)程終止、進(jìn)程睡眠;
  2. 當(dāng)前進(jìn)程的時(shí)間片用完時(shí)(current->counter=0);
  3. 設(shè)備驅(qū)動(dòng)程序;
  4. 進(jìn)程從中斷、異常及系統(tǒng)調(diào)用返回到用戶態(tài)時(shí)。

時(shí)機(jī)1,進(jìn)程要調(diào)用 sleep() 或 exit() 等函數(shù)進(jìn)行狀態(tài)轉(zhuǎn)換,這些函數(shù)會(huì)主動(dòng)調(diào)用調(diào)度程序進(jìn)行進(jìn)程調(diào)度。

時(shí)機(jī)2,由于進(jìn)程的時(shí)間片是由時(shí)鐘中斷來更新的,因此,這種情況和時(shí)機(jī)4 是一樣的。

時(shí)機(jī)3,當(dāng)設(shè)備驅(qū)動(dòng)程序執(zhí)行長(zhǎng)而重復(fù)的任務(wù)時(shí),直接調(diào)用調(diào)度程序。在每次反復(fù)循環(huán)中,驅(qū)動(dòng)程序都檢查 need_resched 的值,如果必要,則調(diào)用調(diào)度程序 schedule() 主動(dòng)放棄 CPU。

時(shí)機(jī)4 , 如前所述, 不管是從中斷、異常還是系統(tǒng)調(diào)用返回, 最終都調(diào)用 ret_from_sys_call(),由這個(gè)函數(shù)進(jìn)行調(diào)度標(biāo)志的檢測(cè),如果必要,則調(diào)用調(diào)用調(diào)度程序。那么,為什么從系統(tǒng)調(diào)用返回時(shí)要調(diào)用調(diào)度程序呢?這當(dāng)然是從效率考慮。從系統(tǒng)調(diào)用返回意味著要離開內(nèi)核態(tài)而返回到用戶態(tài),而狀態(tài)的轉(zhuǎn)換要花費(fèi)一定的時(shí)間,因此,在返回到用戶態(tài)前,系統(tǒng)把在內(nèi)核態(tài)該處理的事全部做完。

Linux 的調(diào)度程序是一個(gè)叫 Schedule() 的函數(shù),這個(gè)函數(shù)來決定是否要進(jìn)行進(jìn)程的切換,如果要切換的話,切換到哪個(gè)進(jìn)程等。

Schedule 的實(shí)現(xiàn)

asmlinkage void __sched schedule(void)
{
/*prev 表示調(diào)度之前的進(jìn)程, next 表示調(diào)度之后的進(jìn)程 */
struct task_struct *prev, *next;
long *switch_count;
struct rq *rq;
int cpu;
need_resched:
preempt_disable(); //關(guān)閉內(nèi)核搶占
cpu = smp_processor_id(); //獲取所在的cpu
rq = cpu_rq(cpu); //獲取cpu對(duì)應(yīng)的運(yùn)行隊(duì)列
rcu_qsctr_inc(cpu);
prev = rq->curr; /*讓prev 成為當(dāng)前進(jìn)程 */
switch_count = &prev->nivcsw;
/釋放全局內(nèi)核鎖,并開this_cpu 的中斷/
release_kernel_lock(prev);
need_resched_nonpreemptible:
__update_rq_clock(rq); //更新運(yùn)行隊(duì)列的時(shí)鐘值
...
if (unlikely(!rq->nr_running))
idle_balance(cpu, rq);
// 對(duì)應(yīng)到CFS,則為 put_prev_task_fair
prev->sched_class->put_prev_task(rq, prev); //通知調(diào)度器類當(dāng)前運(yùn)行進(jìn)程要被另一個(gè)進(jìn)程取代
/pick_next_task以優(yōu)先級(jí)從高到底依次檢查每個(gè)調(diào)度類,從最高優(yōu)先級(jí)的調(diào)度類中選擇最高優(yōu)先級(jí)的進(jìn)程作為
下一個(gè)應(yīng)執(zhí)行進(jìn)程(若其余都睡眠,則只有當(dāng)前進(jìn)程可運(yùn)行,就跳過下面了)/
next = pick_next_task(rq, prev); //選擇需要進(jìn)行切換的task
//進(jìn)程prev和進(jìn)程next切換前更新各自的sched_info
sched_info_switch(prev, next);
if (likely(prev != next)) {
rq->nr_switches++;
rq->curr = next;
++switch_count;
//完成進(jìn)程切換(上下文切換)
context_switch(rq, prev, next); / unlocks the rq */
} else
spin_unlock_irq(&rq->lock);
if (unlikely(reacquire_kernel_lock(current) < 0)) {
cpu = smp_processor_id();
rq = cpu_rq(cpu);
goto need_resched_nonpreemptible;
}
preempt_enable_no_resched();
if (unlikely(test_thread_flag(TIF_NEED_RESCHED)))
goto need_resched;
}

從代碼分析來看,Schedule 主要完成了2個(gè)功能:

  • pick_next_task 以優(yōu)先級(jí)從高到底依次檢查每個(gè)調(diào)度類,從最高優(yōu)先級(jí)的調(diào)度類中選擇最高優(yōu)先級(jí)的進(jìn)程作為下一個(gè)應(yīng)執(zhí)行進(jìn)程。
  • context_switch 完成進(jìn)程的上下文切換。

進(jìn)程上下文切換

static inline void
context_switch(struct rq *rq, struct task_struct *prev, struct task_struct *next)
{
struct mm_struct *mm, *oldmm;
prepare_task_switch(rq, prev, next);
mm = next->mm; //獲取要執(zhí)行進(jìn)程的mm字段
oldmm = prev->active_mm; //被切換出去進(jìn)程的 active_mm 字段
//mm為空,說明是一個(gè)內(nèi)核線程
if (unlikely(!mm)) {
//內(nèi)核線程共享上一個(gè)運(yùn)行進(jìn)程的mm
next->active_mm = oldmm; //借用切換出去進(jìn)程的 mm_struct
//增加引用計(jì)數(shù)
atomic_inc(&oldmm->mm_count);
//惰性TLB,因?yàn)閮?nèi)核線程沒有虛擬地址空間的用戶空間部分,告訴底層體系結(jié)構(gòu)無(wú)須切換
enter_lazy_tlb(oldmm, next);
} else
//進(jìn)程切換包括進(jìn)程的執(zhí)行環(huán)境切換和運(yùn)行空間的切換。運(yùn)行空間的切換是有switch_mm完成的。若是用戶進(jìn)程,則切換運(yùn)行空間
switch_mm(oldmm, mm, next);
//若上一個(gè)運(yùn)行進(jìn)程是內(nèi)核線程
if (unlikely(!prev->mm)) {
prev->active_mm = NULL; //斷開內(nèi)核線程與之前借用的地址空間聯(lián)系
//更新運(yùn)行隊(duì)列的prev_mm成員,為之后歸還借用的mm_struct做準(zhǔn)備
rq->prev_mm = oldmm;
}
/* Here we just switch the register state and the stack. */
//切換進(jìn)程的執(zhí)行環(huán)境
switch_to(prev, next, prev);
barrier();
//進(jìn)程切換之后的處理工作
finish_task_switch(this_rq(), prev);
}

進(jìn)程上下文切換包括進(jìn)程的地址空間的切換和執(zhí)行環(huán)境的切換。

  • switch_mm 完成了進(jìn)程的地址空間的切換:如果新進(jìn)程有自己的用戶空間,也就是說,如果 next->mm 與 next->active_mm 相同,那么,switch_mm() 函數(shù)就把該進(jìn)程從內(nèi)核空間切換到用戶空間,也就是加載next 的頁(yè)目錄。如果新進(jìn)程無(wú)用戶空間(next->mm 為空),也就是說,如果它是一個(gè)內(nèi)核線程,那它就要在內(nèi)核空間運(yùn)行,因此,需要借用前一個(gè)進(jìn)程(prev)的地址空間,因?yàn)樗羞M(jìn)程的內(nèi)核空間都是共享的,因此,這種借用是有效的。
  • switch_to 完成了執(zhí)行環(huán)境的切換,該宏實(shí)現(xiàn)了進(jìn)程之間的真正切換。
//進(jìn)程切換包括進(jìn)程的執(zhí)行環(huán)境切換和運(yùn)行空間的切換。運(yùn)行空間的切換是有switch_mm完成的
static inline void switch_mm(struct mm_struct *prev,
struct mm_struct *next,
struct task_struct tsk)
{
//得到當(dāng)前進(jìn)程運(yùn)行的cpu
int cpu = smp_processor_id();
//若要切換的prev != next,執(zhí)行切換過程
if (likely(prev != next)) {
/ stop flush ipis for the previous mm */
//清除prev的cpu_vm_mask,標(biāo)志prev已經(jīng)棄用了當(dāng)前cpu
cpu_clear(cpu, prev->cpu_vm_mask);
#ifdef CONFIG_SMP
//在smp系統(tǒng)中,更新cpu_tlbstate
per_cpu(cpu_tlbstate, cpu).state = TLBSTATE_OK;
per_cpu(cpu_tlbstate, cpu).active_mm = next;
#endif
//設(shè)置cpu_vm_mask,表示next占用的當(dāng)前的cpu
cpu_set(cpu, next->cpu_vm_mask);
/* Re-load page tables */
//加載CR3
load_cr3(next->pgd);
/*
? load the LDT, if the LDT is different:
*/
//若ldt不相同,還要加載ldt
if (unlikely(prev->context.ldt != next->context.ldt))
load_LDT_nolock(&next->context);
}
#ifdef CONFIG_SMP
else {
per_cpu(cpu_tlbstate, cpu).state = TLBSTATE_OK;
//prev == next 那當(dāng)前cpu中的active_mm就是prev,也即是next
BUG_ON(per_cpu(cpu_tlbstate, cpu).active_mm != next);
/*
在smp系統(tǒng)中,雖然mm時(shí)一樣的,但需要加載CR3
執(zhí)行cpu_test_and_set來判斷next是否正運(yùn)行在此cpu上,這里是判斷在切換next是否運(yùn)行
在當(dāng)前的cpu中,
假設(shè)cpu為1,一個(gè)進(jìn)程在1上執(zhí)行時(shí)候,被調(diào)度出來,再次調(diào)度的時(shí)候,
又發(fā)生在cpu1上
*/
if (!cpu_test_and_set(cpu, next->cpu_vm_mask)) {
/* We were in lazy tlb mode and leave_mm disabled
? tlb flush IPI delivery. We must reload %cr3.
*/
load_cr3(next->pgd);
load_LDT_nolock(&next->context);
}
}
#endif
}

對(duì)于 switch_mm 處理,關(guān)鍵的一步就是它將新進(jìn)程頁(yè)面目錄的起始物理地址裝入到寄存器 CR3 中。CR3 寄存器總是指向當(dāng)前進(jìn)程的頁(yè)面目錄。

#define switch_to(prev,next,last) do { \
unsigned long esi,edi; \
/*分別保存了eflags、ebp、esp,flags和 ebp他們是被保存在prev進(jìn)程(其實(shí)就是要被切換出去的進(jìn)程)的內(nèi)核堆棧中的*/
asm volatile("pushfl\n\t" /* Save flags */ \
"pushl %%ebp\n\t" \

//%0為 prev->thread.esp, %1 為 prev->thread.eip
"movl %%esp,%0\n\t" /* save ESP */ \

//%5為 next->thread.esp,%6 為 next->thread.eip
"movl %5,%%esp\n\t" /* restore ESP */ \

/*將標(biāo)號(hào)為1所在的地址,也即是"popl %%ebp\n\t" 指令所在的地址保存在prev->thread.eip中,作為prev進(jìn)程
下一次被調(diào)度運(yùn)行而切入時(shí)的返回地址,因此可以知道,每個(gè)進(jìn)程調(diào)離時(shí)都要執(zhí)行"movl $1f,%1\n\t",所以
這就決定了每個(gè)進(jìn)程在受到調(diào)度恢復(fù)運(yùn)行時(shí)都會(huì)從標(biāo)號(hào)1處也即是"popl %%ebp\n\t"開始執(zhí)行。
但是有一個(gè)例外,那就是新創(chuàng)建的進(jìn)程,新創(chuàng)建的進(jìn)程并沒有在上一次調(diào)離時(shí)執(zhí)行上面的指令,所以一來要將其
task_struct中的thread.eip事先設(shè)置好,二來所設(shè)置的返回地址也未必是這里的標(biāo)號(hào)1所在的地址,這取決于系統(tǒng)空間
堆棧的設(shè)置。事實(shí)上可以下fork()中可以看到,這個(gè)地址在copy_thread()中設(shè)置為ret_from_fork,其代碼在entry.S中,
也就是對(duì)于新創(chuàng)建的進(jìn)程,在調(diào)用schedule_tail()后直接轉(zhuǎn)到了ret_from_sys_call, 也即是返回到了用戶空間去了
*/
"movl $1f,%1\n\t" /* save EIP */ \

/*將next->thread.eip壓入棧中,這里的next->thread.eip正是next進(jìn)程上一次被調(diào)離在"movl $1f,%1\n\t"保存的,也即是
指向標(biāo)號(hào)為1的地方,也即是"popl %%ebp\n\t"指令所在的地址*/
"pushl %6\n\t" /* restore EIP */ \

/*需要注意的是 __switch_to 是經(jīng)過regparm(3)來修飾的,這個(gè)是gcc的一個(gè)擴(kuò)展語(yǔ)法。
即從eax,ebx,ecx寄存器取函數(shù)的參數(shù)。這樣,__switch_to函數(shù)的參數(shù)就是從寄存器中取的。
并是不向普通函數(shù)那樣從堆棧中取的。在__switch_to之前,將next->thread.eip壓棧了,這樣從函數(shù)返回后
,它的下一條運(yùn)行指令就是 next->thread.eip了。

對(duì)于新創(chuàng)建的進(jìn)程。我們?cè)O(shè)置了p->thread.eip = ret_from_fork.這樣子進(jìn)程被切換進(jìn)來之后,就會(huì)通過
ret_from_fork返回到用戶空間了。
對(duì)于已經(jīng)運(yùn)行的進(jìn)程,我們這里可以看到,在進(jìn)程被切換出去的時(shí)候,prev->thread.eip被設(shè)置了標(biāo)號(hào)1的地址,
即是從標(biāo)號(hào)1的地址開始運(yùn)行的。
標(biāo)號(hào)1的操作:
恢復(fù)ebp(popl %%ebp)
恢復(fù)flags(popf1)
這樣就恢復(fù)了進(jìn)程的執(zhí)行環(huán)境。

從代碼可以看到,在進(jìn)程切換時(shí),只保留了flags esp和ebp寄存器,顯示的用到了eax和edx,
那其他寄存器怎么保存的呢?
實(shí)際上過程切換只是發(fā)生在內(nèi)核態(tài),對(duì)于內(nèi)核態(tài)的寄存器來說,它的段寄存器都是一樣的,所以不需要保存

*/
/*通過jmp指令轉(zhuǎn)入到函數(shù)__switch_to中,由于上一行"pushl %6\n\t"把next->thread.eip,也即是標(biāo)號(hào)為1的地址壓到棧中,所以
跳入__switch_to執(zhí)行完后,執(zhí)行標(biāo)記為1的地方,也即是"popl %%ebp\n\t" 指令*/
"jmp __switch_to\n" \
"1:\t" \
"popl %%ebp\n\t" \
"popfl" \
:"=m" (prev->thread.esp),"=m" (prev->thread.eip), \
"=a" (last),"=S" (esi),"=D" (edi) \
:"m" (next->thread.esp),"m" (next->thread.eip), \
"2" (prev), "d" (next)); \
} while (0)

switch_to 把寄存器中的值比如esp等存放到進(jìn)程thread結(jié)構(gòu)中,保存現(xiàn)場(chǎng)一邊后續(xù)恢復(fù),同時(shí)調(diào)用 __switch_to 完成了堆棧的切換。

在進(jìn)程的 task_struct 結(jié)構(gòu)中有個(gè)重要的成分 thread,它本身是一個(gè)數(shù)據(jù)結(jié)構(gòu) thread_struct, 里面記錄著進(jìn)程在切換時(shí)的(系統(tǒng)空間)堆棧指針,取指令地址(也就是“返回地址”)等關(guān)鍵性的信息。

/*
__switch_to 處理的主要邏輯是TSS,其核心就是load_esp0將TSS中的內(nèi)核空間(0級(jí))堆棧指針換成next->esp0. 這是因?yàn)?/span>
cpu在穿越中斷門或者陷阱門時(shí)要根據(jù)新的運(yùn)行級(jí)別從TSS中取得進(jìn)程在系統(tǒng)空間的堆棧指針,其次,段寄存器fs和gs的內(nèi)容也做了
相應(yīng)的切換。同時(shí)cpu中為debug而設(shè)計(jì)的一些寄存器以及說明進(jìn)程I/O 操作權(quán)限的位圖。
*/
struct task_struct fastcall * __switch_to(struct task_struct *prev_p, struct task_struct *next_p)
{
struct thread_struct *prev = &prev_p->thread,
*next = &next_p->thread;
int cpu = smp_processor_id();
struct tss_struct *tss = &per_cpu(init_tss, cpu);

...

/* 將TSS 中的內(nèi)核級(jí)(0 級(jí))堆棧指針換成next->esp0,這就是next 進(jìn)程在內(nèi)核棧的指針*/
load_esp0(tss, next);


//為prev保存gs
savesegment(gs, prev->gs);


//從next的tls_array緩存中加載線程的Thread-Local Storage描述符
load_TLS(next, cpu);


/*若當(dāng)前特權(quán)級(jí)別是0且prev->iopl != next->iopl,則恢復(fù)IOPL設(shè)置set_iopl_mask
*/
if (get_kernel_rpl() && unlikely(prev->iopl != next->iopl))
set_iopl_mask(next->iopl);

/*根據(jù)thread_info的TIF標(biāo)志_TIF_WORK_CTXSW_PREV和 _TIF_WORK_CTXSW_NEXT 判斷是否需要處理
debug寄存器和IO位圖*/
if (unlikely(task_thread_info(prev_p)->flags & _TIF_WORK_CTXSW_PREV ||
task_thread_info(next_p)->flags & _TIF_WORK_CTXSW_NEXT))
__switch_to_xtra(prev_p, next_p, tss);

//設(shè)置cpu的lazy模式
arch_leave_lazy_cpu_mode();

//若fpu_counter > 5 則恢復(fù)next_p 的FPU寄存器
if (next_p->fpu_counter > 5)
math_state_restore();

//若需要,恢復(fù)gs寄存器
if (prev->gs | next->gs)
loadsegment(gs, next->gs);

x86_write_percpu(current_task, next_p);

return prev_p;
}

關(guān)于__switch_to 的工作就是處理 TSS (任務(wù)狀態(tài)段)。

TSS 全稱task state segment,是指在操作系統(tǒng)進(jìn)程管理的過程中,任務(wù)(進(jìn)程)切換時(shí)的任務(wù)現(xiàn)場(chǎng)信息。

linux 為每一個(gè) CPU 提供一個(gè) TSS 段,并且在 TR 寄存器中保存該段。

linux 中之所以為每一個(gè) CPU 提供一個(gè) TSS 段,而不是為每個(gè)進(jìn)程提供一個(gè)TSS 段,主要原因是 TR 寄存器永遠(yuǎn)指向它,在任務(wù)切換的適合不必切換 TR 寄存器,從而減小開銷。

在從用戶態(tài)切換到內(nèi)核態(tài)時(shí),可以通過獲取 TSS 段中的 esp0 來獲取當(dāng)前進(jìn)程的內(nèi)核棧 棧頂指針,從而可以保存用戶態(tài)的 cs,esp,eip 等上下文。

TSS 在任務(wù)切換過程中起著重要作用,通過它實(shí)現(xiàn)任務(wù)的掛起和恢復(fù)。所謂任務(wù)切換是指,掛起當(dāng)前正在執(zhí)行的任務(wù),恢復(fù)或啟動(dòng)另一任務(wù)的執(zhí)行。

在任務(wù)切換過程中,首先,處理器中各寄存器的當(dāng)前值被自動(dòng)保存到 TR(任務(wù)寄存器)所指定的任務(wù)的 TSS 中;然后,下一任務(wù)的 TSS 被裝入 TR;最后,從 TR 所指定的 TSS 中取出各寄存器的值送到處理器的各寄存器中。由此可見,通過在 TSS 中保存任務(wù)現(xiàn)場(chǎng)各寄存器狀態(tài)的完整映象,實(shí)現(xiàn)任務(wù)的切換。

因此,__switch_to 核心內(nèi)容就是將 TSS 中的內(nèi)核空間(0級(jí))堆棧指針換成 next->esp0。這是因?yàn)?CPU 在穿越中斷門或者陷阱門時(shí)要根據(jù)新的運(yùn)行級(jí)別從TSS中取得進(jìn)程在系統(tǒng)空間的堆棧指針。

thread_struct.esp0 指向進(jìn)程的系統(tǒng)空間堆棧的頂端。當(dāng)一個(gè)進(jìn)程被調(diào)度運(yùn)行時(shí),內(nèi)核會(huì)將這個(gè)變量寫入 TSS 的 esp0 字段,表示這個(gè)進(jìn)程進(jìn)入0級(jí)運(yùn)行時(shí)其堆棧的位置。換句話說,進(jìn)程的 thread_struct 結(jié)構(gòu)中的 esp0 保存著其系統(tǒng)空間堆棧指針。當(dāng)進(jìn)程穿過中斷門、陷阱門或者調(diào)用門進(jìn)入系統(tǒng)空間時(shí),處理器會(huì)從這里恢復(fù)期系統(tǒng)空間棧。

由于棧中變量的訪問依賴的是段、頁(yè)、和 esp、ebp 等這些寄存器,所以當(dāng)段、頁(yè)、寄存器切換完以后,棧中的變量就可以被訪問了。

因此 switch_to 完成了進(jìn)程堆棧的切換,由于被切進(jìn)的進(jìn)程各個(gè)寄存器的信息已完成切換,因此 next 進(jìn)程得以執(zhí)行指令運(yùn)行。

由于 A 進(jìn)程在調(diào)用 switch_to 完成了與 B 進(jìn)程堆棧的切換,也即是寄存器中的值都是 B 的,所以 A 進(jìn)程在 switch_to 執(zhí)行完后,A停止運(yùn)行,B開始運(yùn)行,當(dāng)過一段時(shí)間又把 A 進(jìn)程切進(jìn)去后,A 開始從 switch_to 后面的代碼開始執(zhí)行。

schedule 的調(diào)用流程如下:

責(zé)任編輯:華軒 來源: 今日頭條
相關(guān)推薦

2021-05-12 07:50:02

CFS調(diào)度器Linux

2023-03-05 15:28:39

CFSLinux進(jìn)程

2023-03-05 16:12:41

Linux進(jìn)程線程

2011-01-11 13:47:27

Linux管理進(jìn)程

2023-03-02 23:50:36

Linux進(jìn)程管理

2023-05-08 12:03:14

Linux內(nèi)核進(jìn)程

2009-09-16 08:40:53

linux進(jìn)程調(diào)度linuxlinux操作系統(tǒng)

2021-06-15 08:02:55

Linux 進(jìn)程管理

2021-04-22 07:47:46

Linux進(jìn)程管理

2021-04-15 05:51:25

Linux

2015-09-08 15:13:35

Android進(jìn)程與線程

2010-02-25 10:28:43

Linux進(jìn)程管理

2021-05-17 18:28:36

Linux CFS負(fù)載均衡

2010-03-08 14:40:27

Linux進(jìn)程調(diào)度

2021-12-15 15:03:51

Linux內(nèi)核調(diào)度

2014-08-01 15:38:37

Linux進(jìn)程管理

2009-10-23 17:35:16

linux進(jìn)程管理

2012-05-14 14:09:53

Linux內(nèi)核調(diào)度系統(tǒng)

2009-09-16 08:43:51

linux進(jìn)程線程

2011-04-20 17:00:56

Linux終端進(jìn)程
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

亚洲色图狂野欧美| ...av二区三区久久精品| 97久久国产精品| 在线观看日本中文字幕| av免费在线一区| 亚洲精品成人少妇| 欧美精品免费观看二区| 91中文字幕在线播放| 伊人成人在线视频| 在线看欧美日韩| 免费看黄色片的网站| 456亚洲精品成人影院| 亚洲乱码国产乱码精品精的特点| 精品国产91亚洲一区二区三区www| 波多野结衣电车| 亚洲特色特黄| 国产香蕉97碰碰久久人人| 色姑娘综合天天| 蜜桃成人精品| 精品国产31久久久久久| 亚洲一区精彩视频| 无码国产精品高潮久久99| 日韩 欧美一区二区三区| 久久久久久久999| 最新黄色av网址| 加勒比久久综合| 欧美成人综合网站| gogogo高清免费观看在线视频| 丁香花高清在线观看完整版| 国产欧美日韩在线视频| 精品视频一区二区三区四区| 国产视频手机在线| 蜜桃av一区二区三区| 日本久久精品视频| 日本少妇激情舌吻| 午夜欧美理论片| 久久精品电影网| 国产精品无码无卡无需播放器| 日韩电影不卡一区| 亚洲成人精品视频| 久久艹这里只有精品| 国产欧美自拍| 欧美午夜免费电影| 成人亚洲视频在线观看| 亚洲性色av| 第一福利永久视频精品| 欧美视频在线第一页| caoporn97在线视频| 亚洲欧美在线高清| 一区二区三视频| 一广人看www在线观看免费视频| 国产日韩精品一区二区浪潮av| 欧美精品一区二区三区在线看午夜| 色婷婷av一区二区三区之红樱桃| 成人一区二区视频| 国产富婆一区二区三区| 国产丰满美女做爰| 国产成人超碰人人澡人人澡| 91精品国自产在线观看| 北条麻妃一二三区| 成人亚洲精品久久久久软件| 国产欧美日韩亚洲| 天堂中文字幕在线| 久久日韩粉嫩一区二区三区 | 高清欧美日韩| 欧美日韩另类国产亚洲欧美一级| 五月婷婷激情久久| 色综合久久久| 日韩视频在线你懂得| 色诱av手机版| 欧美黑白配在线| 亚洲免费视频观看| 潮喷失禁大喷水aⅴ无码| 色喇叭免费久久综合网| 欧美成人激情视频免费观看| 欧美丰满艳妇bbwbbw| 红桃视频国产精品| 午夜精品久久久久久久男人的天堂| 国产成人愉拍精品久久| 日韩精品欧美成人高清一区二区| 国产精品男人的天堂| 国产农村妇女毛片精品| 不卡av在线免费观看| 欧美久久电影| 黄色动漫在线观看| 精品二区三区线观看| 国产高潮免费视频| 91成人午夜| 亚洲最新视频在线| 三级影片在线看| 午夜在线视频一区二区区别| 国产精品欧美一区二区| 国内精品久久久久久久久久| 99re在线精品| 在线观看日韩羞羞视频| 999av小视频在线| 欧美午夜精品电影| 野战少妇38p| 日韩国产一区二区| 国外成人在线播放| 这里只有精品9| www.亚洲色图.com| 在线成人av电影| 夜鲁夜鲁夜鲁视频在线播放| 3atv一区二区三区| 黄瓜视频污在线观看| 91精品一区二区三区综合在线爱| 国外成人性视频| 国产片高清在线观看| 91麻豆高清视频| 久久久久亚洲av无码专区喷水| 不卡av播放| 日韩免费电影一区| 香蕉久久久久久久| 国产模特精品视频久久久久| 99久久综合狠狠综合久久止| 第九色区av在线| 五月天亚洲精品| 特黄特黄一级片| 色小子综合网| 国产成人精品综合久久久| 欧美特黄一级视频| 亚洲蜜臀av乱码久久精品| 免费男同深夜夜行网站| 动漫av一区| 色综合久综合久久综合久鬼88| 中文字幕在线2019| 久久久亚洲精品一区二区三区| av日韩在线看| 日韩三级av高清片| 久久夜色精品亚洲噜噜国产mv| 自拍偷拍18p| 91免费在线播放| 秋霞无码一区二区| 6080亚洲理论片在线观看| www.亚洲成人| 亚洲永久精品视频| 国产精品国产精品国产专区不蜜| 欧美伦理视频在线观看| 亚洲最好看的视频| 欧美综合激情网| 日本中文字幕电影在线观看| 午夜国产精品一区| 午夜剧场免费看| 影院欧美亚洲| 国产一区二区三区无遮挡| 国产羞羞视频在线播放| 日韩小视频在线观看专区| 丁香花五月激情| 国产精品99久| 日韩a级在线观看| 国产精品99久久免费观看| 欧美激情视频网站| 男人天堂网在线视频| 亚洲成av人片在www色猫咪| 中文字幕在线国产| 99视频精品免费观看| 九九九九九九精品| 欧美电影网站| 中文字幕免费国产精品| 中文字幕日本视频| 亚洲欧美在线另类| 欧美国产在线一区| 狠狠88综合久久久久综合网| 国产在线精品一区| xxx欧美xxx| www.日韩欧美| 亚洲美女福利视频| 欧美日韩免费网站| 波多野结衣一二三四区| 国产综合色产在线精品| 欧洲金发美女大战黑人| 精品亚洲自拍| 国产成人精品在线| 欧美日韩xx| 精品福利二区三区| 51国产偷自视频区视频| 国产精品网站一区| 永久免费看片在线观看| 国产情侣一区| 亚洲一卡二卡区| silk一区二区三区精品视频 | 日韩精品一区二区视频| 黄色污污网站在线观看| 亚洲日本va在线观看| 亚洲自拍偷拍精品| 美女视频黄久久| 国产精品久久国产| 国产日产精品一区二区三区四区的观看方式 | 精品一区二区久久久| 亚洲av首页在线| 国产精品一区二区av日韩在线| 成人网在线免费观看| a天堂资源在线| 在线观看国产欧美| 可以免费看毛片的网站| 欧美午夜一区二区三区免费大片| 青娱乐国产精品| 亚洲国产精品精华液2区45| 欧美日韩一区二区区别是什么| 久久久久久久高潮| 国产在线视频在线| 日韩精品2区| 看欧美日韩国产| 精品视频在线观看免费观看 | 久久久成人精品视频| 亚洲 小说区 图片区 都市| 欧美浪妇xxxx高跟鞋交| 在线观看亚洲天堂| 自拍视频在线观看一区二区| 香蕉网在线播放| 高清不卡一二三区| 中文字幕国产免费| 午夜在线观看免费一区| 国产精品久久国产| 一精品久久久| 亚洲不卡1区| 婷婷视频一区二区三区| 国产欧美日韩精品在线观看| 国产精品一二三产区| 欧美裸体xxxx极品少妇| 97超碰国产一区二区三区| 亚洲黄色成人网| 亚洲欧美另类视频| 91精品在线一区二区| 亚洲精品一区二区二区| 色综合久久中文综合久久97| 国产精品不卡av| 一区二区三区中文字幕在线观看| 日本免费网站视频| 欧美激情一区二区三区不卡| 三级黄色片网站| 成人av一区二区三区| 无码国产精品一区二区高潮| 久久99精品国产麻豆婷婷 | 国产成人福利片| 午夜影院免费观看视频| 精品亚洲成a人| 亚洲精品性视频| 理论片日本一区| 污版视频在线观看| 日韩avvvv在线播放| 无码日韩人妻精品久久蜜桃| 性欧美videos另类喷潮| koreanbj精品视频一区| 99精品国产在热久久下载| 免费不卡av在线| 亚洲第一毛片| 男女激情无遮挡| 99亚洲精品| 亚洲精品中文字幕无码蜜桃| 久久久国产亚洲精品| 99久久久无码国产精品6| 国产精品尤物| 99视频精品免费| 人人狠狠综合久久亚洲| 男人添女人下面免费视频| 久久成人18免费观看| 日本国产一级片| 国产精品一级二级三级| 在线精品视频播放| 91一区二区在线| 亚洲欧洲久久久| 亚洲图片激情小说| 久久久久成人精品无码| 天天综合色天天综合| 欧美三级韩国三级日本三斤在线观看| 狠狠操狠狠色综合网| 91视频在线视频| 欧美军同video69gay| 性一交一乱一伧老太| 亚洲国产欧美日韩精品| 精品视频二区| 不卡中文字幕av| а√天堂8资源中文在线| 国产97在线播放| 色婷婷成人网| 久99久在线| 欧美电影一区| 欧美视频免费看欧美视频| 麻豆精品网站| 日韩av影视大全| 99re热视频精品| 男女全黄做爰文章| 亚洲一区二区在线观看视频 | 国产一二三四区| 天天影视网天天综合色在线播放| 怡红院av久久久久久久| 日韩一区二区三区四区| 日本又骚又刺激的视频在线观看| 在线播放日韩精品| 操喷在线视频| 国产伦精品一区二区三区精品视频| 亚洲超碰在线观看| 欧美一区二区三区四区在线观看地址| 91精品动漫在线观看| 久久久久久久久久久99| 久久精品国产免费看久久精品| 亚洲美女高潮久久久| 国产日韩欧美亚洲| 久久精品无码人妻| 欧美卡1卡2卡| 蜜桃成人在线视频| 欧美极品欧美精品欧美视频 | 久久久久久久久四区三区| 欧美激情另类| av动漫免费看| 成人午夜视频网站| 青青操在线播放| 狠狠躁夜夜躁久久躁别揉| 99久久夜色精品国产亚洲| 亚洲欧洲黄色网| 2019中文字幕在线电影免费| 国产中文字幕日韩| 国产欧美日韩| 阿v天堂2017| 国产99精品在线观看| 三级黄色录像视频| 91国偷自产一区二区三区观看| 丁香六月色婷婷| 欧美成人免费在线视频| 国产精品蜜月aⅴ在线| 欧美日韩在线观看一区| 99精品免费视频| 91精品国产高清91久久久久久| 国产精品福利av| 成人黄色免费网| 亚洲天堂网在线观看| 激情黄产视频在线免费观看| 成人女人免费毛片| 欧美va天堂在线| 亚洲精品中文字幕乱码无线| 国产精品福利一区二区三区| 亚洲婷婷久久综合| 亚洲三级黄色在线观看| 亚洲插插视频| 久久99久久99精品蜜柚传媒| 亚洲激情欧美| 超碰caoprom| 亚洲成人久久影院| 人人妻人人澡人人爽精品日本| 欧美夫妻性生活视频| 日本在线视频一区二区三区| 欧美a级黄色大片| 国产激情一区二区三区| 欧美黄色一级网站| 欧美大片在线观看| av老司机在线观看| 国模精品一区二区三区| 亚洲精选久久| 欧美bbbbb性bbbbb视频| 精品久久久久久国产91| 欧美日本网站| 国产精品igao视频| 色综合天天爱| 亚洲一二三av| 亚洲资源中文字幕| 人妻中文字幕一区| 欧美一区二区三区图| 国产精品一区二区三区av麻| 亚洲男人天堂色| 中文字幕一区二区三区四区不卡| 一级片一区二区三区| 久久精视频免费在线久久完整在线看| 国产aⅴ精品一区二区四区| 日韩不卡视频一区二区| 不卡的av中国片| 黄色在线免费观看| 上原亚衣av一区二区三区| 4438五月综合| 久艹视频在线免费观看| 久久婷婷国产综合精品青草| 亚洲综合精品在线| 色综合视频网站| 国产伦精品一区二区三区视频| 天堂一区在线观看| 亚洲一区二区影院| 牛牛影视精品影视| 国产精品一区二区久久久| 欧美黄色aaaa| 全黄一级裸体片| 91精品国产aⅴ一区二区| 9999在线视频| 色一情一乱一伦一区二区三区| 韩国三级在线一区| 日韩毛片在线视频| 中日韩美女免费视频网址在线观看 | 欧美日韩国产精品专区| www.中文字幕久久久| 97人人模人人爽人人少妇| 久久xxxx精品视频| 精品无码久久久久成人漫画 | 日本aaa视频| 欧美精品视频www在线观看 | 国产精品丝袜白浆摸在线| 国产精品黄色| 亚洲欧美日韩第一页| 亚洲国产精品专区久久| av久久网站| 女人帮男人橹视频播放|