国产精品电影_久久视频免费_欧美日韩国产激情_成年人视频免费在线播放_日本久久亚洲电影_久久都是精品_66av99_九色精品美女在线_蜜臀a∨国产成人精品_冲田杏梨av在线_欧美精品在线一区二区三区_麻豆mv在线看

深入理解Linux內核之主調度器(下)

系統 Linux
前面選擇了一個合適進程作為下一個進程,接下來做重要的上下文切換動作,來保存上一個進程的“上下文”恢復下一個進程的“上下文”,主要包括進程地址空間切換和處理器狀態切換。

[[409218]]

本文轉載自微信公眾號「Linux內核遠航者」,作者Linux內核遠航者。轉載本文請聯系Linux內核遠航者公眾號。

4.進程上下文切換

前面選擇了一個合適進程作為下一個進程,接下來做重要的上下文切換動作,來保存上一個進程的“上下文”恢復下一個進程的“上下文”,主要包括進程地址空間切換和處理器狀態切換。

注:這里的上下文實際上是指進程運行時最小寄存器的集合。

如果切換的next進程不是同一個進程,才進行切換:

  1. __schedule 
  2.  i  f (likely(prev != next)) {       
  3.         ... 
  4.         context_switch  //進程上下文切換 
  5.     } 

4.1 進程地址空間切換

進程地址空間切換就是切換虛擬地址空間,使得切換之后,當前進程訪問的是屬于自己的虛擬地址空間(包括用戶地址空間和內核地址空間),本質上是切換頁表基地址寄存器。

進程地址空間切換讓進程產生獨占系統內存的錯覺,因為切換完地址空間后,當前進程可以訪問屬于它的海量的虛擬地址空間(內核地址空間各個進程共享,用戶地址空間各個進程私有),而實際上物理地址空間只有一份。

下面給出源代碼分析:

  1. context_switch 
  2. -> 
  3.  /* 
  4.  ¦* kernel -> kernel   lazy + transfer active 
  5.  ¦*   user -> kernel   lazy + mmgrab() active 
  6.  ¦* 
  7.  ¦* kernel ->   user   switch + mmdrop() active 
  8.  ¦*   user ->   user   switch 
  9.  ¦*/ 
  10.  if (!next->mm) {                                // to kernel 
  11.          enter_lazy_tlb(prev->active_mm, next); 
  12.  
  13.          next->active_mm = prev->active_mm; 
  14.          if (prev->mm)                           // from user 
  15.                  mmgrab(prev->active_mm); 
  16.          else 
  17.                  prev->active_mm = NULL
  18.  } else {                                        // to user 
  19.         ... 
  20.          switch_mm_irqs_off(prev->active_mm, next->mm, next); 
  21.  
  22.          if (!prev->mm) {                        // from kernel 
  23.                  /* will mmdrop() in finish_task_switch(). */ 
  24.                  rq->prev_mm = prev->active_mm; 
  25.                  prev->active_mm = NULL
  26.          }             
  27.  }                     
  28.                       

以上代碼是判斷是否next進程是內核線程,如果是則不需要進行地址空間切換(實際上指的是用戶地址空間),因為內核線程總是運行在內核態訪問的是內核地址空間,而內核地址空間是所有的進程共享的。在arm64架構中,內核地址空間是通過ttbr1_el1來訪問,而它的主內核頁表在內核初始化的時候已經填充好了,也就是我們常說的swapper_pg_dir頁表,后面所有對內核地址空間的訪問,無論是內核線程也好還是用戶任務,統統通過swapper_pg_dir頁表來訪問,而在內核初始化期間swapper_pg_dir頁表地址已經加載到ttbr1_el1中。

需要說明一點的是:這里會做“借用” prev->active_mm的處理,借用的目的是為了避免切換屬于同一個進程的地址空間。舉例說明:Ua -> Ka -> Ua ,Ua表示用戶進程, Ka表示內核線程,當進行這樣的切換的時候,Ka 借用Ua地址空間,Ua -> Ka不需要做地址空間切換,而Ka -> Ua按理來說需要做地址空間切換,但是由于切換的還是Ua 地址空間,所以也不需要真正的切換(判斷了Ka->active_mm == Ua->active_mm ),當然還包括切換的是同一個進程的多個線程的情況,這留給大家思考。

下面來看下真正的地址空間切換:

  1. switch_mm_irqs_off(prev->active_mm, next->mm, next); 
  2. ->switch_mm  //arch/arm64/include/asm/mmu_context.h 
  3.    -> if (prev != next)  
  4.         __switch_mm(next); 
  5.           ->check_and_switch_context(next
  6.                -> ...  //asid處理 
  7.               -> cpu_switch_mm(mm->pgd, mm) 
  8.                   ->cpu_do_switch_mm(virt_to_phys(pgd),mm) 
  9.                         -> unsigned long ttbr1 = read_sysreg(ttbr1_el1);   
  10.                             unsigned long asid = ASID(mm);                  
  11.                             unsigned long ttbr0 = phys_to_ttbr(pgd_phys);   
  12.                             ... 
  13.                             write_sysreg(ttbr1, ttbr1_el1);   //設置asid到ttbr1_el1 
  14.                             isb();                             
  15.                             write_sysreg(ttbr0, ttbr0_el1);   //設置mm->pgd 到ttbr0_el1 

上面代碼是做真正的地址空間切換,實際的切換很簡單,并沒有那么復雜和玄乎,僅僅設置頁表基地址寄存器即可,當然這里還涉及到了為了防止頻繁無效tlb的ASID的設置。

主要做的工作就是設置next進程的ASID到ttbr1_el1, 設置mm->pgd 到ttbr0_el1,僅此而已!

需要注意的是:1.寫到ttbr0_el1的值是進程pgd頁表的物理地址。2.雖然做了這樣的切換,但是這個時候并不能訪問到next的用戶地址空間,因為還處在主調度器上下文中,屬于內核態,訪問的是內核空間。

而一旦返回了用戶態,next進程就能正常訪問自己地址空間內容:

  • 訪問一個用戶空間的虛擬地址va,首先通過va和記錄在ttbr1_el1的asid查詢tlb,如果找到相應表項則獲得pa進行訪問。
  • 如果tlb中沒有找到,通過ttbr0_el1來遍歷自己的多級頁表,找到相應表項則獲得pa進行訪問。
  • 如果發生中斷異常等訪問內核地址空間,直接通過ttbr1_el1即可完成訪問。
  • 訪問沒有建立頁表映射的合法va,發生缺頁異常來建立映射關系,填寫屬于進程自己的各級頁表,然后訪問。
  • 訪問無法地址,發生缺頁殺死進程等等。

4.2 處理器狀態切換

來切換下一個進程的執行流,上一個進程執行狀態保存,讓下一個進程恢復執行狀態。

處理器狀態切換而后者讓進程產生獨占系統cpu的錯覺,使得系統中各個任務能夠并發(多個任務在多個cpu上運行)或分時復用(多個任務在一個cpu上運行)cpu資源。

下面給出代碼:

  1. context_switch 
  2. ->(last) = __switch_to((prev), (next)) 
  3.     -> fpsimd_thread_switch(next) //浮點寄存器切換 
  4.         ... 
  5.         last = cpu_switch_to(prev, next);  

處理器狀態切換會做浮點寄存器等切換,最終調用cpu_switch_to做真正切換。

  1. cpu_switch_to  //arch/arm64/kernel/entry.S 
  2. SYM_FUNC_START(cpu_switch_to) 
  3.         mov     x10, #THREAD_CPU_CONTEXT 
  4.         add     x8, x0, x10 
  5.         mov     x9, sp 
  6.         stp     x19, x20, [x8], #16             // store callee-saved registers 
  7.         stp     x21, x22, [x8], #16 
  8.         stp     x23, x24, [x8], #16 
  9.         stp     x25, x26, [x8], #16 
  10.         stp     x27, x28, [x8], #16 
  11.         stp     x29, x9, [x8], #16 
  12.         str     lr, [x8] 
  13.         add     x8, x1, x10 
  14.         ldp     x19, x20, [x8], #16             // restore callee-saved registers 
  15.         ldp     x21, x22, [x8], #16 
  16.         ldp     x23, x24, [x8], #16 
  17.         ldp     x25, x26, [x8], #16 
  18.         ldp     x27, x28, [x8], #16 
  19.         ldp     x29, x9, [x8], #16 
  20.         ldr     lr, [x8] 
  21.         mov     sp, x9 
  22.         msr     sp_el0, x1 
  23.         ptrauth_keys_install_kernel x1, x8, x9, x10 
  24.         scs_save x0, x8 
  25.         scs_load x1, x8 
  26.         ret 
  27. SYM_FUNC_END(cpu_switch_to) 

這里傳遞過來的是x0為prev進程的進程描述符(struct task_struct)地址, x1為next的進程描述符地址。會就將prev進程的 x19-x28,fp,sp,lr保存到prev進程的tsk.thread.cpu_context中,next進程的這些寄存器值從next進程的tsk.thread.cpu_context中恢復到相應寄存器。這里還做了sp_el0設置為next進程描述符的操作,為了通過current宏找到當前的任務。

需要注意的是:

  1. mov sp, x9 做了切換進程內核棧的操作。
  2. ldr lr, [x8] 設置了鏈接寄存器,然后ret的時候會將lr恢復到pc從而真正完成了執行流的切換。

4.3 精美圖示

這里給出了進程切換的圖示(以arm64處理器為例),這里從prev進程切換到next進程。

5.進程再次被調度

當進程重新被調度的時候,從原來的調度現場恢復執行。

5.1 關于lr地址的設置

1)如果切換的next進程是剛fork的進程,它并沒有真正的這些調度上下文的存在,那么lr是什么呢?這是在fork的時候設置的:

  1. do_fork 
  2.     ... 
  3.     copy_thread //arch/arm64/kernel/process.c 
  4.     ->memset(&p->thread.cpu_context, 0, sizeof(struct cpu_context)); 
  5.      p->thread.cpu_context.pc = (unsigned long)ret_from_fork; 
  6.     p->thread.cpu_context.sp = (unsigned long)childregs; 

設置為了ret_from_fork的地址,當然這里也設置了sp等調度上下文(這里將進程切換保存的寄存器稱之為調度上下文)。

  1. SYM_CODE_START(ret_from_fork) 
  2.         bl      schedule_tail 
  3.         cbz     x19, 1f                         // not a kernel thread 
  4.         mov     x0, x20 
  5.         blr     x19 
  6. 1:      get_current_task tsk 
  7.         b       ret_to_user 
  8. SYM_CODE_END(ret_from_fork) 

剛fork的進程,從cpu_switch_to的ret指令執行后返回,lr加載到pc。

于是執行到ret_from_fork:這里首先調用schedule_tail對前一個進程做清理工作,然后判斷是否為內核線程如果是執行內核線程的執行函數,如果是用戶任務通過ret_to_user返回到用戶態。

2)如果是之前已經被切換過的進程,lr為cpu_switch_to調用的下一條指令地址(這里實際上是__schedule函數中調用barrier()的指令地址)。

5.2 關于__switch_to的參數和返回值

  1. switch_to(prev, next, prev) 
  2. >  ((last) = __switch_to((prev), (next))) 

這里做處理器狀態切換時,傳遞了兩個參數,返回了一個參數:

prev和next很好理解就是 就是前一個進程(當前進程)和下一個進程的 task_struct結構指針,那么last是什么呢?

一句話:返回的last是當前重新被調度的進程的上一個進程的 task_struct結構指針。

如:A ->B ->千山萬水->D -> A 上面的切換過程:A切換到B 然后經歷千山萬水再從D -> A,這個時候A重新被調度時,last即為D的 task_struct結構指針。

獲得當前重新被調度進程的前一個進程是為了回收前一個進程資源,見后面分析。

5.3 關于finish_task_switch

進程被重新調度時無論是否為剛fork出的進程都會走到finish_task_switch這個函數,下面我們來看它做了什么事情:

主要工作為:檢查回收前一個進程資源,為當前進程恢復執行做一些準備工作。

  1. finish_task_switch 
  2. ->finish_lock_switch 
  3.     ->raw_spin_unlock_irq   //使能本地中斷 
  4. ->if (mm)  
  5.     mmdrop(mm)  //有借有還  借用的mm現在歸還 
  6.  ->if (unlikely(prev_state == TASK_DEAD)) {        //前一個進程是死亡狀態 
  7.             put_task_stack(prev);    //如果內核棧在task_struct中   釋放內核棧                                       
  8.            put_task_struct_rcu_user(prev);  //釋放前一個進程的task_struct占用內存 
  9.    }                      

可以看到進程被重新調度時首先需要做的主要是:

  • 重新使能本地中斷 ,進程被重新調度時,本地cpu中斷是被重新打開的!!!
  • 如果有借用mm的情況,現在歸還 如果前一個是內核線程,在進程地址空間切換時“借用了”某個進程的mm_struct,現在切換到了下一個進程,理應歸還,歸還做的是遞減借用的mm_struct的引用計數,引用計數為0就會釋放mm_struct占用的內存。
  • 對于上一個死亡的進程現在回收最后的資源, 注意這里是遞減引用計數,當引用計數為0時才會真正釋放。

6. 總結 

主調度器可以說Linux內核進程管理中的核心組件,進程管理的其他部分如搶占、喚醒、睡眠等都是圍繞它來運作。在原子上下文不能發生調度,說的就是調用主調度器,但是可以設置搶占標志以至于在最近的搶占點發生調度,如中斷中喚醒高優先級進程的場景。主調度器所做的工作就是讓出cpu,內核很多場景可以直接或間接調用它,而大體上可以分為兩種情況:即為主動調度和搶占式調度。主調度器做了兩件事情:選擇下一個進程和進程進程上下文切換。選擇下一個進程解決選擇合適高優先級進程的問題。進程進程上下文切換又分為地址空間切換和處理器狀態切換,前者讓進程產生獨自占用系統內存的錯覺,而后者讓進程產生獨自占用系統cpu的錯覺,讓系統各個進程有條不紊的共享內存和cpu等資源。

 

責任編輯:武曉燕 來源: Linux內核遠航者
相關推薦

2021-07-02 06:54:44

Linux內核主調度器

2021-07-26 07:47:36

數據庫

2025-06-16 05:10:00

2021-05-19 07:56:26

Linux內核搶占

2021-12-09 08:09:31

Linux內核臟頁

2017-01-12 19:34:58

2021-07-20 08:02:41

Linux進程睡眠

2020-09-28 08:44:17

Linux內核

2023-02-10 08:11:43

Linux系統調用

2022-11-09 08:12:07

2025-10-28 04:25:00

2021-08-31 10:32:11

LinuxPage Cache命令

2015-09-17 10:51:35

修改hostnameLinux

2013-06-20 10:25:56

2020-07-21 08:26:08

SpringSecurity過濾器

2010-06-01 15:25:27

JavaCLASSPATH

2016-12-08 15:36:59

HashMap數據結構hash函數

2025-04-28 02:00:00

2025-04-22 08:21:10

2016-11-07 21:59:52

threadpython
點贊
收藏

51CTO技術棧公眾號

亚洲成人av电影在线| 欧美亚洲视频在线看网址| 已婚少妇美妙人妻系列| 综合精品久久| 欧美插天视频在线播放| 午夜视频在线免费观看| 99精品欧美一区二区蜜桃免费 | 福利电影导航| 韩国一区二区三区| 91九色极品视频| 亚洲亚洲一区二区三区| 4hu四虎永久在线影院成人| 爱爱永久免费视频| 国产成人福利片| 欧美精品一区二区三区在线看午夜| 天堂网在线最新版www中文网| 欧美小视频在线观看| 少妇性饥渴无码a区免费| 在线综合欧美| 91久久久在线| 日本久久成人网| 亚洲精品一区二区三区蜜桃下载 | 蜜桃久久影院| 国产精品成人av| 911国产网站尤物在线观看| 国产成人精品一区二区三区视频| 欧美性生交大片免网| 毛片视频免费| 中文字幕一区二区三区在线观看| 久久综合九色综合88i| 国产乱一区二区| 一区二区三区我不卡| 免费看的黄色欧美网站 | 在线看片福利| 日韩欧美另类在线| 欧美性猛交xxx乱大交3蜜桃| 91福利视频久久久久| 日本a一级在线免费播放| 中文字幕中文字幕在线一区 | 日本a视频在线观看| 久久国产精品免费| 欧美重口乱码一区二区| 亚洲免费成人| 亚洲精品免费在线视频| 色棕色天天综合网| 日本久久久久久久久| 成人涩涩网站| 日产精品久久久一区二区福利| 精品国产乱码久久久| 国产女人精品视频| 国产精品第十页| 久久久久se| 免费高清在线一区| 久久久久久久免费视频| 懂色一区二区三区免费观看| 中文字幕欧美日韩一区二区三区| 国产精品中文字幕一区二区三区| 天天综合五月天| 粉嫩av一区二区三区在线播放 | 欧美一区二区大胆人体摄影专业网站| 波多野结衣一区二区三区免费视频| 欧美大片在线免费观看| 婷婷亚洲成人| 97人摸人人澡人人人超一碰| 欧美资源在线| 欧洲精品在线播放| 国产精品国产三级国产aⅴ原创 | 秋霞av在线| 日韩一级欧美一级| 亚洲欧美日韩中文播放| 青青草成人影院| 国产99久久久欧美黑人| 日韩精品dvd| 精品一区二区三区国产| 久久亚洲风情| 国产91porn| 97久久超碰国产精品电影| 天天碰免费视频| 夜色激情一区二区| 日韩三级久久| 久久久久国产精品免费网站| 亚洲免费观看高清完整版在线观| 国产精品亚洲欧美导航| 国产精品99久久99久久久| 欧美一级二级三级乱码| 亚洲综合影院| 国产一区二区三区精品在线观看| 国产精品99久久久久久人 | 欧美亚洲国产一区在线观看网站 | 亚洲第一级黄色片| 国产探花一区二区| 成人性生活视频免费看| 欧美日韩国产高清一区二区三区 | 国产乱码精品一区二区三区忘忧草 | 日韩电影第一页| 午夜国产欧美理论在线播放| 精品日韩久久久| 一区二区三区天堂av| 老司机精品导航| 日韩一二三四| 欧美在线免费视频| 久久久99精品免费观看不卡| av有声小说一区二区三区| 欧美日韩一区二区三区在线视频 | 天海翼女教师无删减版电影| 中文字幕日本精品| 日本不卡视频一二三区| 伊人在线视频| aa成人免费视频| 舔着乳尖日韩一区| 中文字幕中文字幕精品| 欧美自拍小视频| 日韩视频一区在线| 国产精品综合网| 都市激情国产精品| 日韩精品久久久免费观看| 91激情五月电影| 女优一区二区三区| 深夜黄色小视频| 久久夜色精品国产亚洲aⅴ| 国产成人超碰人人澡人人澡| 欧美性猛交xxx高清大费中文| 性欧美大战久久久久久久免费观看| 欧美在线观看一区二区| 国产韩日影视精品| 黄网站app在线观看大全免费视频| 青青草99啪国产免费| 综合久久久久久久| 伊人久久大香线蕉综合网站| 国产视频一二三| 国产精品美女免费| 亚洲一二三区视频在线观看| 欧美美女一区| 最近最新mv在线观看免费高清| 国产成+人+综合+亚洲欧美丁香花| 亚洲人成亚洲人成在线观看图片 | 亚洲精品套图| 91久久国产精品| 欧美性生交大片免费| 亚洲另类黄色| 91中文在线| 宅男一区二区三区| 亚洲女人天堂色在线7777| 国产大陆a不卡| 日韩久久一区| 男人透女人免费视频| 91大神福利视频在线| 亚洲成av人综合在线观看| 我不卡手机影院| 大片免费在线看视频| 一本色道婷婷久久欧美 | 91探花福利精品国产自产在线| 欧美性猛交xxxx富婆弯腰| 91久久黄色| 午夜日韩成人影院| 亚洲福利精品视频| 91精品视频网站| 欧美精品一区二区三区蜜桃视频| 成人三级伦理片| 亚洲小说图片| 黄色一级大片在线免费看产| 日韩视频在线观看视频| 欧美黑人xxx| 在线观看日韩电影| 久久精品国产精品亚洲红杏| www.久久热| 久久精品国产亚洲a∨麻豆| 亚洲精品一卡二卡三卡四卡| 久久99精品久久久久久琪琪| 亚洲一区二区高清| 日本成人在线不卡视频| 6080亚洲理论片在线观看| 永久免费在线| 四虎永久免费网站| 日韩av片免费在线观看| 日韩免费高清av| 亚洲国产高清aⅴ视频| 亚洲毛片播放| 国产精品18| 素人av在线| 在线免费视频a| 久久资源av| 欧美亚洲国产视频| 亚洲第一精品夜夜躁人人爽| 亚洲精品国产第一综合99久久 | 精品免费在线观看| 国产综合色视频| 综合干狼人综合首页| 日本小视频在线免费观看| 男人插女人下面免费视频| 91久久嫩草影院一区二区| 一区二区欧美亚洲| 欧美性猛交xxxx免费看| 久久蜜桃av一区精品变态类天堂 | 亚州一区二区三区| 青青草在线视频免费观看| 波多野结衣之无限发射| 激情一区二区三区| 日韩免费在线免费观看| 在线播放国产一区二区三区|