LINUX核心分析。8

2puT發表於2016-07-30

一、程式排程與程式切換

1.不同的程式有不同的排程需求

第一種分類:

I/O密集型(I/O-bound)
頻繁的進行I/O
通常會花費很多時間等待I/O操作的完成
CPU密集型(CPU-bound)
計算密集型
需要大量的CPU時間進行運算

第二種分類:

批處理程式
不必與使用者互動,通常在後臺執行
不必很快響應
典型:編譯程式,科學計算
實時程式
有實時需求,不應被低優先順序的程式阻塞
響應時間要短要穩定
典型:視訊、音配、機械控制
互動式程式
需要經常與使用者互動,所以要花很多時間等待使用者輸入操作
響應時間要快,平均延遲低於50~150ms
典型:shell,文字編輯程式,圖形應用程式
2.不同的程式要採取不同的程式排程策略

排程策略:
是一組規則,它們決定什麼時候以怎樣的方式選擇一個新程式執行

Linux的排程基於分時和優先順序。

Linux的程式根據優先順序排隊
根據特定的演算法計算出程式的優先順序,用一個值表示
這個值表示把程式如何適當的分配給CPU
Linux程式中的優先順序是動態的
排程程式會根據程式的行為週期性地調整程式的優先順序
例如:
較長時間為被分配到cpu——↑
已經在cpu上執行了較長時間——↓
常見的一些函式:

nice
getpriority/setpriority 設定優先順序
sched_getschedduler/sched_setscheduler
sched_getparam/sched_setparam
sched_yield
sched_get_priority_min/sched_get_priority_max
sched_rr_get_interval
排程演算法與其他部分解耦合。

3.程式的排程時機

(1)schedule函式實現排程

目的:在執行佇列中找到一個程式,把cpu分配給它
方法:
直接呼叫schedule()
鬆散呼叫,根據need_resched標記
(2)程式排程的時機 【重要】

中斷處理過程(包括時鐘中斷、I/O中斷、系統呼叫和異常)中,直接呼叫schedule(),或者返回使用者態時根據need_resched標記呼叫schedule();主動排程。
使用者態程式無法實現主動排程,僅能通過陷入核心態後的某個時機點進行排程,即在中斷處理過程中進行排程。使用者態程式只能被動排程。
核心執行緒可以直接呼叫schedule()進行程式切換,也可以在中斷處理過程中進行排程,也就是說核心執行緒既可以主動排程,也可以被動排程;

核心執行緒是隻有核心態沒有使用者態的特殊程式

4.程式的切換

為了控制程式的執行,核心必須有能力掛起正在CPU上執行的程式,並恢復以前掛起的某個程式的執行,這叫做程式切換、任務切換、上下文切換;
掛起正在CPU上執行的程式,與中斷時儲存現場是不同的,中斷前後是在同一個程式上下文中,只是由使用者態轉向核心態執行,但是是同一個程式,而程式上下文的切換是兩個程式在切換。
程式上下文包含了程式執行需要的所有資訊
使用者地址空間:包括程式程式碼,資料,使用者堆疊等
控制資訊:程式描述符,核心堆疊等
硬體上下文(注意中斷也要儲存硬體上下文只是儲存的方法不同)
schedule()函式選擇一個新的程式來執行,並呼叫context_switch進行上下文的切換,這個巨集呼叫switch_to來進行關鍵上下文切換
enter description here
next = pick_next_task(rq, prev); //程式排程演算法都封裝這個函式內部
enter description here
context_switch(rq, prev, next); //程式上下文切換
switch_to切換堆疊和暫存器的狀態,利用了prev和next兩個引數:prev指向當前程式,next指向被排程的程式
enter description here
switch_to程式碼及分析如下:

31 #define switch_to(prev, next, last)
32 do {
33 /*
34 * Context-switching clobbers all registers, so we clobber
35 * them explicitly, via unused output variables.
36 * (EAX and EBP is not listed because EBP is saved/restored
37 * explicitly for wchan access and EAX is the return value of \
38 * __switch_to())
39 /
40 unsigned long ebx, ecx, edx, esi, edi;
41
42 asm volatile("pushfl\n\t" /
 儲存當前程式的標誌位 /
43 "pushl %%ebp\n\t" /
 儲存當前程式的堆疊基址EBP /
44 "movl %%esp,%[prev_sp]\n\t" /
 儲存當前棧頂ESP /
45 "movl %[next_sp],%%esp\n\t" /
 把下一個程式的棧頂放到esp暫存器中,完成了核心堆疊的切換,從此往下壓棧都是在next程式的核心堆疊中。 */

46 "movl $1f,%[prev_ip]\n\t" /* 儲存當前程式的EIP /
47 "pushl %[next_ip]\n\t" /
 把下一個程式的起點EIP壓入堆疊 */
48 __switch_canary
49 "jmp __switch_to\n" /* 因為是函式所以是jmp,通過暫存器傳遞引數,暫存器是prev-a,next-d,當函式執行結束ret時因為沒有壓棧當前eip,所以需要使用之前壓棧的eip,就是pop出next_ip。 */

以上四行程式碼實際是使用next程式的程式堆疊,但是還算成prev的程式執行,核心堆疊的切換和程式切換完成並不同時間。

50 "1:\t" /* 認為next程式開始執行。 /
51 "popl %%ebp\n\t" /
 restore EBP /
52 "popfl\n" /
 restore flags /
53
54 /
 output parameters 因為處於中斷上下文,在核心中
prev_sp是核心堆疊棧頂
prev_ip是當前程式的eip /
55 : [prev_sp] "=m" (prev->thread.sp),
56 [prev_ip] "=m" (prev->thread.ip), //[prev_ip]是標號
57 "=a" (last),
58
59 /
 clobbered output registers: */
60 "=b" (ebx), "=c" (ecx), "=d" (edx),
61 "=S" (esi), "=D" (edi)
62
63 __switch_canary_oparam
64
65 /* input parameters:
next_sp下一個程式的核心堆疊的棧頂
next_ip下一個程式執行的起點,一般是$1f,對於新建立的子程式是ret_from_fork/
66 : [next_sp] "m" (next->thread.sp),
67 [next_ip] "m" (next->thread.ip),
68
69 /
 regparm parameters for __switch_to(): */
70 [prev] "a" (prev),
71 [next] "d" (next)
72
73 __switch_canary_iparam
74
75 : /* reloaded segment registers */
76 "memory");
77 } while (0)、

二、Linux系統的一般執行過程

1.最一般的情況:正在執行的使用者態程式X切換到執行使用者態程式Y的過程

正在執行的使用者態程式X
發生中斷——

save cs:eip/esp/eflags(current) to kernel stack
壓入核心堆疊
load cs:eip(entry of a specific ISR) and ss:esp(point to kernel stack).
把當前程式的核心堆疊的資訊儲存,和當前中斷例程的起點載入
SAVE_ALL //儲存現場
中斷處理過程中或中斷返回前呼叫了schedule()【程式排程時機,可能進行排程】,其中的switch_to做了關鍵的程式上下文切換【具體見上一節】
標號1之後開始執行使用者態程式Y【這裡Y曾經通過以上步驟被切換出去過因此可以從標號1繼續執行】
restore_all //恢復現場
iret - pop cs:eip/ss:esp/eflags from kernel stack從核心堆疊中彈出y的相關資訊
繼續執行使用者態程式Y

※ 關鍵點:

中斷上下文的切換
程式上下文的切換
2.幾種特殊情況

比如核心執行緒。

通過中斷處理過程中的排程時機,使用者態程式與核心執行緒之間互相切換和核心執行緒之間互相切換,與最一般的情況非常類似,只是核心執行緒執行過程中發生中斷沒有程式使用者態和核心態的轉換;
使用者態程式不能主動排程,但是核心執行緒可以主動呼叫schedule(),只有程式上下文的切換,沒有發生中斷上下文的切換,即沒有中斷,與最一般的情況相比更簡單;
建立子程式的系統呼叫,如fork,在子程式中的執行起點是ret_from_fork,而不是標號1,返回使用者態;
載入一個新的可執行程式後返回到使用者態的情況,如execve,內部修改了中斷上下文,不是iret返回的那個預設中斷儲存資訊;
0-3G核心態和使用者態都可以訪問,3G以上只能核心態訪問。
核心是所有程式共享的。
核心是各種中斷處理過程和核心執行緒的集合。

三、Linux系統架構和執行過程概覽

1.Linux作業系統架構概覽

作業系統分為:

核心
程式管理,程式排程,程式間通訊機制,記憶體管理,中斷異常處理,檔案系統,I/O系統,網路部分
其他程式
函式庫,shell程式,系統程式……
最關鍵:CPU和記憶體
作業系統的目的:

與硬體互動,管理所有的硬體資源
為使用者程式(應用程式)提供一個良好的執行環境
典型的Linux作業系統的結構
enter description here

2.ls命令——最簡單與最複雜的操作

enter description here

3.從CPU和記憶體的角度來看Linux系統的執行

從在CPU執行指令的角度看:
enter description here
程式x下面是0-3G的部分,是程式的地址空間
在main函式中有一個gets,從控制檯獲得字串
需要gets是一個系統呼叫,陷入核心態,從使用者態的堆疊進入到核心堆疊,esp等壓棧;
一個程式排程:
等待鍵盤輸入的時候,cpu會切換到其他程式,同時在進行等待:因為輸入鍵盤會產生I/O中斷,再排程回來。
例如:
在鍵盤上敲擊ls,I/O中斷,中斷處理程式,找到等待鍵盤輸入的程式,排程,把它設為就緒態。
gets的系統呼叫就獲得了從鍵盤上得到的資料,返回到使用者態,變成使用者態堆疊,繼續執行。

從記憶體的角度來看:
512m記憶體的虛擬地址空間的對映:
enter description here
整個實體記憶體會對映到3G以上的部分。

三、linux系統架構和執行過程概覽

  1.架構概覽

  2.執行ls命令→確定命令→fork生成一個shell本身的拷貝→exce將ls的可執行檔案裝入記憶體→從系統呼叫返回
  3.從CPU和記憶體的角度看linux的執行過程

四、實驗

2,開啟shell終端,執行以下命令:cd LinuxKernel

              rm -rf menu

              git clone https://github.com/mengning/menu.git

              cd menu

              mv test_exec.c test.c

              make rootfs

2,可以通過增加-s -S啟動引數開啟除錯模式

              qemu -kernel ../linux-3.18.6/arch/x86/boot/bzImage -initrd ../rootfs.img -s -S

開啟gdb進行遠端除錯

              gdb

              file ../linux-3.18.6/vmlinux

              target remote:1234

設定斷點

              b schedule

              b context_switch

              b switch_to

              b pick_next_task

五,總結

通過本次的課程學習,我瞭解到了程式的切換和系統的一般執行過程。

相關文章