2017-2018-1 20179205《Linux內核原理與設計》第九周作業
《Linux內核原理與設計》第九周作業
視頻學習及代碼分析
一、進程調度時機與進程的切換
不同類型的進程有不同的調度需求,第一種分類:I/O-bound
會頻繁的進程I/O,通常會花費很多時間等待I/O操作的完成;CPU-bound
是計算密集型,需要大量的CPU時間進行運算,使得其他交互式進程反應遲鈍,因此需要不同的算法來使系統的運行更高效,以及CPU的資源最大限度的得到使用。第二種分類包括批處理進程(batch process);實時進程(real-time process)以及交互式進程(interactive process)。不同的分類需要不同的調度策略,即決定什麽時候以怎樣的方式選擇一個新進程運行。Linux的調度基於分時和優先級。根據優先級排隊,且優先級是動態的。
進程調度的時機
中斷處理過程(包括時鐘中斷、I/O中斷、系統調用和異常)中,直接調用schedule(),目的是在運行隊列中找到一個進程,把CPU分配給它,或者返回用戶態時根據need_resched標記調用schedule(),用戶態進程只能被動調度;
內核線程是只有內核態沒有用戶態的特殊進程,它可以直接調用schedule()進行進程切換,也可以在中斷處理過程中進行調度,也就是說內核線程作為一類的特殊的進程可以主動調度,也可以被動調度。內核線程在執行過程中可能中斷可以發生時鐘中斷,I/O中斷,但不會發生系統調用,因為它可以直接訪問內核的函數;
用戶態進程無法實現主動調度,僅能通過陷入內核態後的某個時機點進行調度,即在中斷處理過程中進行調度。
進程的切換及相關代碼分析
為了控制進程的執行,內核必須有能力掛起正在CPU上執行的進程,並恢復以前掛起的某個進程的執行,這叫做進程切換、任務切換、上下文切換;
??
schedule()
函數選擇一個新的進程來運行,並調用context_switch進行上下文的切換,這個宏調用switch_to
來進行關鍵上下文切換next = pick_next_task(rq, prev);
//包裝了某種進程調度策略context_switch(rq, prev, next);
switch_to(pre,next,prev)
//切換寄存器的狀態和堆棧。利用了prev和next兩個參數:prev指向當前進程,next指向被調度的進程
相關代碼分析:
#define switch_to(prev, next, last) //prev指向當前進程,next指向被調度的進程
do {
unsigned long ebx, ecx, edx, esi, edi;
asm volatile("pushfl\n\t" //保存當前近程的flags
"pushl %%ebp\n\t" //把當前進程的基址壓棧
"movl %%esp,%[prev_sp]\n\t" //把當前進程的棧頂esp保存到thread.sp中
"movl %[next_sp],%%esp\n\t" //把[next_sp]放到esp,從而這兩步完成了內核堆棧的切換
"movl $1f,%[prev_ip]\n\t" //把1f放到[prev_ip]裏,保存當前進程的EIP,當恢復prev進程時可從這裏恢復
"pushl %[next_ip]\n\t" //把next進程的起點,即ip的位置壓到堆棧中,next_ip一般是$1f
__switch_canary
"jmp __switch_to\n" //執行__switch_to()函數,通過寄存器[prev][next],eax和edx傳遞參數
"1:\t"
"popl %%ebp\n\t"
"popfl\n"
/* output parameters */
: [prev_sp] "=m"(prev->thread.sp), //為了可讀性更好,用字符串[prev_sp]標記參數
[prev_ip] "=m"(prev->thread.ip),
"=a" (last),
/* clobbered output registers: */
"=b" (ebx), "=c"(ecx), "=d" (edx),
"=S" (esi), "=D"(edi)
__switch_canary_oparam
/* input parameters: */
: [next_sp] "m" (next->thread.sp),
[next_ip] "m" (next->thread.ip),
/* regparm parameters for __switch_to():*/
[prev] "a" (prev),
[next] "d" (next)
__switch_canary_iparam
: /* reloaded segment registers */
"memory");
} while (0)
二、Linux系統的一般執行過程分析
最一般的情況:正在運行的用戶態進程x切換到用戶態進程y的過程
1.正在運行的用戶態進程X
2.發生中斷——save cs:eip/esp/eflags(current) to kernel stack,then load cs:eip(entry of a specific ISR ) and ss:esp(point to kernel stack). //CPU自動完成保存和加載
3.SAVE_ALL
//保存現場
4. 中斷處理過程中或中斷返回前調用了schedule(),其中的switch_to
做了關鍵的進程上下文切換 //將x進程的內核堆棧切換到next進程的內核堆棧,再切換eip
5.標號1之後開始運行用戶態進程Y(這裏Y曾經通過以上步驟被切換出去過因此可以從標號1繼續執行)
6.restore_all //恢復現場
7.iret - pop cs:eip/ss:esp/eflags from kernel stack //把y進程在發生中斷時保存在內核堆棧裏面的cs:eip/ss:esp/eflags pop出來
8.繼續運行用戶態進程Y
幾種特殊情況
通過中斷處理過程中的調度時機,用戶態進程與內核線程之間互相切換和內核線程之間互相切換,與最一般的情況非常類似,只是內核線程運行過程中發生中斷沒有進程用戶態和內核態的轉換;
用戶態進程不能主動調度schedule(),但內核線程可以主動調用schedule(),只有進程上下文的切換,沒有發生中斷上下文的切換,與最一般的情況略簡略 ;
創建子進程的系統調用在子進程中的執行起點及返回用戶態,那麽不從標號1開始執行,從用戶態開始執行,next ip=ret from work,如fork ;
加載一個新的可執行程序後返回到用戶態的情況,如execve; //修改了中斷上下文
內核與舞女
0到3G用戶可以訪問,3G以上只有內核態可以訪問。實際上所有的進程3G以上都是完全共享的,比如進程X切換到進程Y,但是地址空間還是3G以上的部分,只是把進程描述符和其他的進程上下文切換了,等到返回到用戶態了才會有不同,在內核態裏不管哪個進程代碼段和堆棧段都是完全相同的,因此在內核中切換時比較容易的。視頻中有一個比喻,就是內核和舞女。內核就好比是出租車,進程是舞女,哪個進程招手都可以進入內核,走一段可以返回到用戶態。內核沒有進程時就進入0號進程idle空轉,有進程時發生中斷進入內核態。
Linux操作系統架構概覽
任何計算機系統都包含一個基本的程序集合,稱為操作系統。
- 內核(進程管理,進程調度,進程間通訊機制,內存管理,中斷異常處理,文件系統,I/O系統,網絡部分)
- 其他程序(例如函數庫、shell程序、系統程序等等)
操作系統的目的
- 與硬件交互,管理所有的硬件資源
- 為用戶程序(應用程序)提供一個良好的執行環境
最簡單也是最復雜的操作-執行ls命令
從CPU角度看Linux系統的執行
從內存角度看Linux系統
參考:圖片鏈接
教材15、16章學習
1、進程地址空間由進程可尋址的虛擬地址組成,而且內核允許進程使用這種虛擬內存中的地址。通常情況下,每個進程都有唯一的這種平坦地址空間。進程地址空間中的任何有效地址都只能位於唯一的區域,這些內存區域不能相互覆蓋。
2、一個進程的地址空間與另一個進程的地址空間即使有相同的內存地址,實際上也彼此互不相幹,稱這樣的進程為線程。其父進程希望和其子進程共享空間,可以在調用clone()時,設置CLONE_VM
標誌;
3、進程的內核區域包含各種內存對象,比如:可執行文件代碼可以包含各種內存映射,稱為代碼段(text section);可執行文件的已初始化全局變量的內存映射,稱為數據段(data section);包含未初始化全局變量,也就是bss段的零頁(頁面中的信息全部為0值,所以可以用於映射bss段等目的)的內存映射;用於進程用戶空間棧的內存映射;
4、內核使用內存描述符結構體表示進程的地址空間,該結構體包含了和進程地址空間有關的全部信息。內存描述符由mm_struct
結構體表示。分配內存描述符時,fork()函數利用copy_mm()
函數復制父進程的內存描述符。撤銷內存描述符,內核會調用定義在kernel/exit.c中的exit_mm()
函數;內核線程沒有進程地址空間,也沒有相關的內存描述符,所有內核線程對應的進程描述符中的mm域為空。
5、內存區域由vm_area_struct
結構體描述,定義在文件<linux/mm_types.h>中。內存區域在Linux中也經常稱作虛擬地址空間。VMA標誌是一種位標誌,其定義見<linux/mm.h>中,它包含在mm_flags
域內,標誌了內存區域所包含的頁面的行為和信息。
6、可以通過內存描述符中的mmap和mm_rb
域之一訪問內存區域。這兩個域各自獨立地指向與內存描述符相關的全體內存區域對象。其實,它們包含完全相同的am_area_struct
結構體的指針,僅僅組織方法不同。mmap域使用單獨鏈表鏈接所有的內存區域對象;mm_rb
域使用紅-黑數鏈接所有的內存區域對象。
7、為了找到一個給定的內存地址屬於哪一個內存區域,內核提供了find_vma()
函數。
8、do_mmap()
函數會將一個地址空間加入到進程的地址空間中——無論是擴展已存在的內存區域還是創建一個新的區域; do_munmap()
函數從特定的進程空間中刪除地址空間。
9、當程序訪問一個虛擬地址時,首先必須將虛擬地址轉換為物理地址,然後處理器才能解析地址訪問請求。地址的轉換需要通過查詢頁表才能完成,概括的講,地址轉換需要將虛擬地址分段,使每段虛擬地址都作為一個索引指向頁表,而頁表則指向下一級別的頁表或者指向最終的物理頁面。Linux中使用三級頁表完成轉換。
10、頁高速緩存(cache)是Linux內核實現磁盤緩存。寫緩存一般被實現成下面三種策略之一:第一種策略稱為不緩存(nowrite);第二種策略,寫操作將自動更新內存緩存,同時也更新磁盤文件。第三種策略,也是Linux所采用的,稱為“回寫"。在這種策略下,程序寫操作直接寫到緩存中,並且被加入到臟頁鏈表中。然後由一個進程(回寫進程)周期性將臟頁鏈表中的頁寫回到磁盤,從而讓磁盤中的數據和內存中的最終一致。
11、頁高速緩存通過兩個參數address_space
兌現加上一個偏移量進行搜索。每個address_space
對象都有惟一的基數,它保存在address_space
結構體中。基數是一個二叉樹,只要指定了文件偏移量,就可以在基數中迅速檢索到希望的頁。
2017-2018-1 20179205《Linux內核原理與設計》第九周作業