亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 學院 > 開發設計 > 正文

進程切換(context_switch)代碼分析:基本邏輯

2019-11-14 09:44:26
字體:
來源:轉載
供稿:網友

http://www.wowotech.net/PRocess_management/context-switch-arch.html

一、前言

本文主要是以context_switch為起點,分析了整個進程切換過程中的基本操作和基本的代碼框架,很多細節,例如tlb的操作,cache的操作,鎖的操作等等會在其他專門的文檔中描述。進程切換包括體系結構相關的代碼和系統結構無關的代碼。第二、三、四分別描述了context_switch的代碼脈絡,后面的章節是以ARM64為例子,講述了具體進程地址空間的切換過程和硬件上下文的切換過程。

 

二、context_switch代碼分析

在kernel/sched/core.c中有一個context_switch函數,該函數用來完成具體的進程切換,代碼如下(本文主要描述進程切換的基本邏輯,因此部分代碼會有刪節):

static inline struct rq * context_switch(struct rq *rq, struct task_struct *prev,            struct task_struct *next)------------------(1) {     struct mm_struct *mm, *oldmm;

    mm = next->mm;     oldmm = prev->active_mm;-------------------(2)

    if (!mm) {---------------------------(3)         next->active_mm = oldmm;         atomic_inc(&oldmm->mm_count);         enter_lazy_tlb(oldmm, next);-----------------(4)     } else         switch_mm(oldmm, mm, next); ---------------(5)

    if (!prev->mm) {------------------------(6)         prev->active_mm = NULL;         rq->prev_mm = oldmm;     }

    switch_to(prev, next, prev);------------------(7)     barrier();

    return finish_task_switch(prev); }

(1)一旦調度器算法確定了pre task和next task,那么就可以調用context_switch函數實際執行進行切換的工作了,這里我們先看看參數傳遞情況:

  rq:在多核系統中,進程切換總是發生在各個cpu core上,參數rq指向本次切換發生的那個cpu對應的run queue   prev:將要被剝奪執行權利的那個進程   next:被選擇在該cpu上執行的那個進程

(2)next是馬上就要被切入的進程(后面簡稱B進程),prev是馬上就要被剝奪執行權利的進程(后面簡稱A進程)。mm變量指向B進程的地址空間描述符,oldmm變量指向A進程的當前正在使用的地址空間描述符(active_mm)。對于normal進程,其任務描述符(task_struct)的mm和active_mm相同,都是指向其進程地址空間。對于內核線程而言,其task_struct的mm成員為NULL(內核線程沒有進程地址空間),但是,內核線程被調度執行的時候,總是需要一個進程地址空間,而active_mm就是指向它借用的那個進程地址空間。

(3)mm為空的話,說明B進程是內核線程,這時候,只能借用A進程當前正在使用的那個地址空間(prev->active_mm)。注意:這里不能借用A進程的地址空間(prev->mm),因為A進程也可能是一個內核線程,不擁有自己的地址空間描述符。

(4)如果要切入的B進程是內核線程,那么調用體系結構相關的代碼enter_lazy_tlb,標識該cpu進入lazy tlb mode。那么什么是lazy tlb mode呢?如果要切入的進程實際上是內核線程,那么我們也暫時不需要flush TLB,因為內核線程不會訪問usersapce,所以那些無效的TLB entry也不會影響內核線程的執行。在這種情況下,為了性能,我們會進入lazy tlb mode。進程切換中和TLB相關的內容我們會單獨在一篇文章中描述,這里就不再贅述了。

(5)如果要切入的B進程是內核線程,那么由于是借用當前正在使用的地址空間,因此沒有必要調用switch_mm進行地址空間切換,只有要切入的B進程是一個普通進程的情況下(有自己的地址空間)才會調用switch_mm,真正執行地址空間切換。

如果切入的是普通進程,那么這時候進程的地址空間已經切換了,也就是說在A--->B進程的過程中,進程本身尚未切換,而進程的地址空間已經切換到了B進程了。這樣會不會造成問題呢?還好,呵呵,這時候代碼執行在kernel space,A和B進程的kernel space都是一樣一樣的啊,即便是切了進程地址空間,不過內核空間實際上保持不變的。

(6)如果切出的A進程是內核線程,那么其借用的那個地址空間(active_mm)已經不需要繼續使用了(內核線程A被掛起了,根本不需要地址空間了)。除此之外,我們這里還設定了run queue上一次使用的mm struct(rq->prev_mm)為oldmm。為何要這么做?先等一等,下面我們會統一描述。

(7)一次進程切換,表面上看起來涉及兩個進程,實際上涉及到了三個進程。switch_to是一個有魔力的符號,和一般的調用函數不同,當A進程在CPUa調用它切換到B進程的時候,switch_to一去不回,直到在某個cpu上(我們稱之CPUx)完成從X進程(就是last進程)到A進程切換的時候,switch_to返回到A進程的現場。這一點我們會在下一節詳細描述。switch_to完成了具體prev到next進程的切換,當switch_to返回的時候,說明A進程再次被調度執行了。

 

三、switch_to為什么需要三個參數呢?

switch_to定義如下:

#define switch_to(prev, next, last)                    /     do {                                /         ((last) = __switch_to((prev), (next)));            /     } while (0)

一個switch_to將代碼分成兩段:

AAA

switch_to(prev, next, prev);

BBB

一次進程切換,涉及到了三個進程,prev和next是大家都熟悉的參數了,對于進程A(下圖中的右半圖片),如果它想要切換到B進程,那么:     prev=A     next=B switch-to

這時候,在A進程中調用 switch_to 完成A到B進程的切換。但是,當經歷萬水千山,A進程又被重新調度的時候,我們又來到了switch_to返回的這一點(下圖中的左半圖片),這時候,我們是從哪一個進程切換到A呢?誰知道呢(在A進程調用switch_to 的時候是不知道的)?在A進程調用switch_to之后,cpu就執行B進程了,后續B進程切到哪個進程呢?隨后又經歷了怎樣的進程切換過程呢?當然,這一切對于A進程來說它并不關心,它唯一關心的是當切換回A進程的時候,該cpu上(也不一定是A調用switch_to切換到B進程的那個CPU)執行的上一個task是誰?這就是第三個參數的含義(實際上這個參數的名字就是last,也基本說明了其含義)。也就是說,在AAA點上,prev是A進程,對應的run queue是CPUa的run queue,而在BBB點上,A進程恢復執行,last是X進程,對應的run queue是CPUx的run queue。

 

四、在內核線程切換過程中,內存描述符的處理

我們上面已經說過:如果切入內核線程,那么其實進程地址空間實際上并沒有切換,該內核線程只是借用了切出進程使用的那個地址空間(active_mm)。對于內核中的實體,我們都會使用引用計數來根據一個數據對象,從而確保在沒有任何引用的情況下釋放該數據對象實體,對于內存描述符亦然。因此,在context_switch中有代碼如下:

if (!mm) {     next->active_mm = oldmm;     atomic_inc(&oldmm->mm_count);-----增加引用計數     enter_lazy_tlb(oldmm, next); }

既然是借用別人的內存描述符(地址空間),那么調用atomic_inc是合理的,反正馬上就切入B進程了,在A進程中提前增加引用計數也OK的。話說有借有還,那么在內核線程被切出的時候,就是歸還內存描述符的時候了。

這里還有一個悖論,對于內核線程而言,在運行的時候,它會借用其他進程的地址空間,因此,在整個內核線程運行過程中,需要使用該地址空間(內存描述符),因此對內存描述符的增加和減少引用計數的操作只能在在內核線程之外完成。假如一次切換是這樣的:…A--->B(內核線程)--->C…,增加引用計數比較簡單,上面已經說了,在A進程調用context_switch的時候完成。現在問題來了,如何在C中完成減少引用計數的操作呢?我們還是從代碼中尋找答案,如下(context_switch函數中,去掉了不相關的代碼):

if (!prev->mm) {     prev->active_mm = NULL;     rq->prev_mm = oldmm;---在rq->prev_mm上保存了上一次使用的mm struct }

借助其他進程內存描述符的東風,內核線程B歡快的運行,然而,快樂總是短暫的,也許是B自愿的,也許是強迫的,調度器最終會剝奪B的執行,切入C進程。也就是說,B內核線程調用switch_to(執行了AAA段代碼),自己掛起,C粉墨登場,執行BBB段的代碼。具體的代碼在finish_task_switch,如下:

static struct rq *finish_task_switch(struct task_struct *prev) {     struct rq *rq = this_rq();     struct mm_struct *mm = rq->prev_mm;――――――――――――――――(1)

    rq->prev_mm = NULL;

    if (mm)         mmdrop(mm);――――――――――――――――――――――――(2) }

(1)我們假設B是內核線程,在進程A調用context_switch切換到B線程的時候,借用的地址空間被保存在CPU對應的run queue中。在B切換到C之后,通過rq->prev_mm就可以得到借用的內存描述符。

(2)已經完成B到C的切換后,借用的地址空間可以返還了。因此在C進程中調用mmdrop來完成這一動作。很神奇,在A進程中為內核線程B借用地址空間,但卻在C進程中釋放它。

 

五、ARM64的進程地址空間切換

對于ARM64這個cpu arch,每一個cpu core都有兩個寄存器來指示當前運行在該CPU core上的進程(線程)實體的地址空間。這兩個寄存器分別是ttbr0_el1(用戶地址空間)和ttbr1_el1(內核地址空間)。由于所有的進程共享內核地址空間,因此所謂地址空間切換也就是切換ttbr0_el1而已。地址空間聽起來很抽象,實際上就是內存中的若干Translation table而已,每一個進程都有自己獨立的一組用于翻譯用戶空間虛擬地址的Translation table,這些信息保存在內存描述符中,具體位于struct mm_struct中的pgd成員中。以pgd為起點,可以遍歷該內存描述符的所有用戶地址空間的Translation table。具體代碼如下:

static inline void switch_mm(struct mm_struct *prev, struct mm_struct *next,       struct task_struct *tsk)----------------(1) {     unsigned int cpu = smp_processor_id();

    if (prev == next)--------------------(2)         return;

    if (next == &init_mm) {-----------------(3)         cpu_set_reserved_ttbr0();         return;     }

    check_and_switch_context(next, cpu); }

(1)prev是要切出的地址空間,next是要切入的地址空間,tsk是將要切入的進程。

(2)要切出的地址空間和要切入的地址空間是一個地址空間的話,那么切換地址空間也就沒有什么意義了。

(3)在ARM64中,地址空間的切換主要是切換ttbr0_el1,對于swapper進程的地址空間,其用戶空間沒有任何的mapping,而如果要切入的地址空間就是swapper進程的地址空間的時候,將(設定ttbr0_el1指向empty_zero_page)。

(4)check_and_switch_context中有很多TLB、ASID相關的操作,我們將會在另外的文檔中給出細致描述,這里就簡單略過,實際上,最終該函數會調用arch/arm64/mm/proc.S文件中的cpu_do_switch_mm將要切入進程的L0 Translation table物理地址(保存在內存描述符的pgd成員)寫入ttbr0_el1。

 

六、ARM64的的進程切換

由于存在MMU,內存中可以有多個task,并且由調度器依次調度到cpu core上實際執行。系統有多少個cpu core就可以有多少個進程(線程)同時執行。即便是對于一個特定的cpu core,調度器可以可以不斷的將控制權從一個task切換到另外一個task上。實際的context switch的動作也不復雜:就是將當前的上下文保存在內存中,然后從內存中恢復另外一個task的上下文。對于ARM64而言,context包括:

(1)通用寄存器

(2)浮點寄存器

(3)地址空間寄存器(ttbr0_el1和ttbr1_el1),上一節已經描述

(4)其他寄存器(ASID、thread process ID register等)

__switch_to代碼(位于arch/arm64/kernel/process.c)如下:

struct task_struct *__switch_to(struct task_struct *prev,                 struct task_struct *next) {     struct task_struct *last;

    fpsimd_thread_switch(next);--------------(1)     tls_thread_switch(next);----------------(2)     hw_breakpoint_thread_switch(next);--和硬件跟蹤相關     contextidr_thread_switch(next); --和硬件跟蹤相關

    dsb(ish);      last = cpu_switch_to(prev, next); ------------(3)

    return last; }

(1)fp是float-point的意思,和浮點運算相關。simd是Single Instruction Multiple Data的意思,和多媒體以及信號處理相關。fpsimd_thread_switch其實就是把當前FPSIMD的狀態保存到了內存中(task.thread.fpsimd_state),從要切入的next進程描述符中獲取FPSIMD狀態,并加載到CPU上。

(2)概念同上,不過是處理tls(thread local storage)的切換。這里硬件寄存器涉及tpidr_el0和tpidrro_el0,涉及的內存是task.thread.tp_value。具體的應用場景是和線程庫相關,具體大家可以自行學習了。

(3)具體的切換發生在arch/arm64/kernel/entry.S文件中的cpu_switch_to,代碼如下:

ENTRY(cpu_switch_to) -------------------(1)     mov    x10, #THREAD_CPU_CONTEXT ----------(2)     add    x8, x0, x10 --------------------(3)     mov    x9, sp     stp    x19, x20, [x8], #16----------------(4)     stp    x21, x22, [x8], #16     stp    x23, x24, [x8], #16     stp    x25, x26, [x8], #16     stp    x27, x28, [x8], #16     stp    x29, x9, [x8], #16     str    lr, [x8] ---------A     add    x8, x1, x10 -------------------(5)     ldp    x19, x20, [x8], #16----------------(6)     ldp    x21, x22, [x8], #16     ldp    x23, x24, [x8], #16     ldp    x25, x26, [x8], #16     ldp    x27, x28, [x8], #16     ldp    x29, x9, [x8], #16     ldr    lr, [x8] -------B     mov    sp, x9 -------C     ret -------------------------(7) ENDPROC(cpu_switch_to)

(1)進入cpu_switch_to函數之前,x0,x1用做參數傳遞,x0是prev task,就是那個要掛起的task,x1是next task,就是馬上要切入的task。cpu_switch_to和其他的普通函數沒有什么不同,盡管會走遍萬水千山,但是最終還是會返回調用者函數__switch_to。

在進入細節之前,先想一想這個問題:cpu_switch_to要如何保存現場?要保存那些通用寄存器呢?其實上一小段描述已經做了鋪陳:盡管有點怪異,本質上cpu_switch_to仍然是一個普通函數,需要符合ARM64標準過程調用文檔。在該文檔中規定,x19~x28是屬于callee-saved registers,也就是說,在__switch_to函數調用cpu_switch_to函數這個過程中,cpu_switch_to函數要保證x19~x28這些寄存器值是和調用cpu_switch_to函數之前一模一樣的。除此之外,pc、sp、fp當然也是必須是屬于現場的一部分的。

(2)得到THREAD_CPU_CONTEXT的偏移,保存在x10中

(3)x0是pre task的進程描述符,加上偏移之后就獲取了訪問cpu context內存的指針(x8寄存器)。所有context的切換的原理都是一樣的,就是把當前cpu寄存器保存在內存中,這里的內存是在進程描述符中的 thread.cpu_context中。

(4)一旦定位到保存cpu context(各種通用寄存器)的內存,那么使用stp保存硬件現場。這里x29就是fp(frame pointer),x9保存了stack pointer,lr是返回的PC值。到A代碼處,完成了pre task cpu context的保存動作。

(5)和步驟(3)類似,只不過是針對next task而言的。這時候x8指向了next task的cpu context。

(6)和步驟(4)類似,不同的是這里的操作是恢復next task的cpu context。執行到代碼B處,所有的寄存器都已經恢復,除了PC和SP,其中PC保存在了lr(x30)中,而sp保存在了x9中。在代碼C出恢復了sp值,這時候萬事俱備,只等PC操作了。

(7)ret指令其實就是把x30(lr)寄存器的值加載到PC,至此現場完全恢復到調用cpu_switch_to那一點上了。

 

參考文獻:

1、ARM標準過程調用文檔(IHI0056C_beta_aaelf64.pdf)

2、linux 4.4.6內核源代碼


發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
日韩精品免费综合视频在线播放| 日韩成人在线免费观看| 亚洲女成人图区| 国产精品旅馆在线| 精品香蕉在线观看视频一| 日本精品性网站在线观看| 国产精品久久久久久久久久ktv| 亚洲欧美综合精品久久成人| 欧美在线视频导航| 日本久久久久久久久久久| 久久99视频精品| 欧美黑人性视频| 中文字幕精品久久久久| 久久久成人精品| 亚洲第一区第一页| 日韩电影中文 亚洲精品乱码| 91香蕉电影院| 久久男人的天堂| 欧洲亚洲免费视频| 久久久99久久精品女同性| 日韩av在线最新| 中文字幕日韩电影| 久热99视频在线观看| 亚洲一区二区在线播放| 91在线观看欧美日韩| 91夜夜未满十八勿入爽爽影院| 日韩中文字幕在线观看| 久久6免费高清热精品| 欧美一区二区三区图| 国产精品久久久久影院日本| 欧美日韩免费观看中文| 美乳少妇欧美精品| 久久久久久久久久久免费| 欧美黄网免费在线观看| 在线成人免费网站| 成人黄色av免费在线观看| 亚洲人成电影网站色| 91精品国产91久久久| 精品中文字幕在线2019| 国产欧美va欧美va香蕉在线| 午夜精品一区二区三区视频免费看| 久久久久久久久久久免费精品| 国产精品自拍视频| 国产精品aaaa| 亚洲福利视频在线| 亚洲一区精品电影| 国产精品久久久久久搜索| 亚洲国产97在线精品一区| 久久97久久97精品免视看| 欧美巨大黑人极品精男| 国产精品视频不卡| 久久6精品影院| 国产在线精品成人一区二区三区| 亚洲一区国产精品| 亚洲xxxxx| 亚洲天堂第二页| 91免费精品国偷自产在线| 国产精品久久久久久久久影视| 国产精品直播网红| 在线观看日韩www视频免费| 亚洲女人天堂网| 黄色成人av在线| 欧美激情亚洲视频| 欧美成人久久久| 中文字幕久热精品视频在线| 日韩欧美极品在线观看| 国产91ⅴ在线精品免费观看| 色哟哟网站入口亚洲精品| 国产成人a亚洲精品| 久久久电影免费观看完整版| 欧美第一黄色网| 一本色道久久综合狠狠躁篇怎么玩| 欧美限制级电影在线观看| 久久精品久久久久久国产 免费| 欧美成人免费va影院高清| 亚洲视频电影图片偷拍一区| 亚洲国产一区二区三区四区| 亚洲福利视频在线| 欧美一级免费看| 久久国产精品久久久| 亚洲精品久久久久久久久久久久久| 国产精品爽爽ⅴa在线观看| 91视频88av| 亚洲性夜色噜噜噜7777| 亚洲精品久久久久久久久| 国产精品一区二区性色av| 亚洲国产精品一区二区三区| 国产香蕉一区二区三区在线视频| 精品国产自在精品国产浪潮| 91黑丝在线观看| 国产精品免费一区二区三区都可以| 欧美最近摘花xxxx摘花| 欧美怡春院一区二区三区| 欧美日韩一区二区三区| 亚洲xxxx做受欧美| 欧美午夜精品久久久久久人妖| 亚洲色图校园春色| 日韩在线观看视频免费| 秋霞成人午夜鲁丝一区二区三区| 精品久久久久久国产91| 国产精品户外野外| 久久久免费高清电视剧观看| 欧美日韩午夜视频在线观看| 92国产精品视频| 国产精品久久久久久av福利| 欧美刺激性大交免费视频| 久久夜色精品国产| 久久精品青青大伊人av| 一区二区av在线| 两个人的视频www国产精品| 亚洲性线免费观看视频成熟| 91啪国产在线| 91精品国产91久久久久久久久| 中文字幕在线视频日韩| 国产精品一区二区三区久久久| 久久精品影视伊人网| 国产在线98福利播放视频| 国产精品美女av| 久久成年人视频| 国产精品福利无圣光在线一区| 亚洲福利视频久久| 少妇av一区二区三区| 欧美黑人xxxⅹ高潮交| 55夜色66夜色国产精品视频| 欧美福利小视频| 久久综合电影一区| 国产精品高精视频免费| 欧美美女操人视频| 亚洲欧美在线磁力| 欧美极品少妇xxxxⅹ喷水| 久久久电影免费观看完整版| 色综合色综合网色综合| 美女国内精品自产拍在线播放| 久久久久久久一区二区三区| 国产一区二区视频在线观看| 亚洲精品久久久久中文字幕欢迎你| 亚洲国产精品美女| 久久久久久久影院| 亚洲国产精品yw在线观看| 亚洲成人av在线播放| 欧美在线一级va免费观看| 欧美日韩国产影院| 日韩一区二区福利| 国产精品亚洲片夜色在线| 中文字幕久热精品在线视频| 精品久久久久久| 欧美中文字幕精品| 成人羞羞国产免费| 久久久这里只有精品视频| 国产亚洲精品高潮| 精品国产一区二区三区久久狼5月| 欧美一区二区.| 亚洲福利在线播放| 欧美—级a级欧美特级ar全黄| 国产精品美女免费视频| 久久777国产线看观看精品| 美女啪啪无遮挡免费久久网站| 欧美成人精品三级在线观看| 日韩成人在线视频| 精品久久久香蕉免费精品视频| 久久久噜噜噜久久久| 最近2019年好看中文字幕视频| 亚洲欧美国产va在线影院| 亚洲精品电影网站|