《Linux核心設計與實現》與《Linux核心原始碼情景分析》讀書筆記__Linux

來源:互聯網
上載者:User

第一章:核心簡介

處理器在任何指定時間點上的活動範圍:

a,運行於核心空間,處於進程上下文,代表某個特定的進程執行;

b,運行於核心空間,處於中斷上下文,於任何進程無關,處理某個特定的中斷;

c,運行於使用者空間,執行使用者進程。

當一個進程在執行時,CPU的所有寄存器中的值、進程的狀態以及堆棧中的內容被稱 為該進程的上下文。當核心需要切換到另一個進程時,它需要儲存當前進程的 所有狀態,即儲存當前進程的上下文,以便在再次執行該進程時,能夠必得到切換時的狀態執行下去。在LINUX中,當前進程上下文均儲存在進程的任務資料結 構中。在發生中斷時,核心就在被中斷進程的上下文中,在核心態下執行插斷服務常式。但同時會保留所有需要用到的資源,以便中繼服務結束時能恢複被中斷進程 的執行。

第三章:進程管理

1,核心把進程存放在叫做任務隊列(task list)的雙向鏈表中,鏈表中每一項都是類型為task_struct,稱為進程描述符(process descriptor)的結構,此結構包含一個具體進程的所有資訊。

2,核心通過一個惟一的進程標識值或PID來標誌每個進程,核心把每個進程的PID存放在它們各自的進程描述符中。

3,x86系統寄存器較少只能通過在該進程核心棧的棧頂或棧底建立thread_info結構,通過計算位移間接的尋找task_struct結構。

4,進程狀態:

a,TASK_RUNNING進程正在執行或在運行隊列中等待執行;

b,TASK_INTERRUPTIBLE進程正在睡眠,等到某些條件達成,核心就會把進程狀態設定為運行,或因為接收到訊號而提前被喚醒並投入運行。

c,TASK_UNINTERRUPTIBLE進程正在睡眠,且不響應訊號。

d,TASK_ZOMBIE該進程已經結束,為使父進程獲知它的訊息,子進程的進程描述符仍被保留;

e,TASK_STOPPED進程停止執行,接收到SIGSTOP、SIGTTIN、SIGTTOU、SIGTSTP等訊號時發生。

5,系統調用和例外處理常式是對核心明確定義的介面,進程只有通過這些介面才能陷入核心執行,對核心的所有訪問都必須通過這些介面。

6,進程間的關係存放在進程描述符中,每個task_struct都包含一個指向其父進程task_struct叫做parent的指標,和一個稱為children的子進程鏈表。

7,寫時拷貝:fork()後核心讓父進程和子進程共用同一個拷貝,只有在父子進程需要寫入的時候,資料才會被複製。

8,fork()和vfork()調用clone(),clone()調用do_fork(),do_fork()調用copy_process(),具體見p27,p28。

9,線程在核心中是一個普通的進程,致使該進程和其他進程共用一些資源,每個線程擁有屬於自己的task_struct,線程的建立也是調用clone()。

10,核心線程和普通進程的區別是沒有獨立的地址空間,只在核心空間運行,會將它在建立時得到的函數永遠執行下去,該函數通常有一個迴圈,再需要的時候,該核心線程會被喚醒和執行,完成任務會自行休眠。

11,進程終結,do_exit()系統調用p31,wait()函數通過系統調用wait4()實現的,最終釋放進程描述符時,release_task()會被調用,p32;

12,核心對孤兒進程的處理:給子進程在當前線程組內找一個線程作為父親,如果不行,就讓init做其父進程,遍曆子進程鏈表和prace子進程鏈表;

13,當一個程式執行了系統調用或者觸發了某個異常,它就會陷入核心空間,此時核心代表進程運行,處於進程上下文中。此時,進程可以睡眠和調用發送器。可以通過current宏關聯當前進程。

第四章:進程調度

1,在搶佔式多任務模式下,由發送器來決定什麼時候停止一個進程的運行,以便其他進程能夠得到執行機會。這個強制的掛起動作叫做搶佔。進程在被搶佔之前能夠啟動並執行時間的預先設定好的,叫進程的時間片。

2,策略決定發送器在何時讓什麼進程運行。I/O消耗型進程大部分時間用來提交I/O請求或是等待I/O請求,經常處於可運行狀態,但通常都是運行短短一會兒,這裡說的I/O是指任何類型的可阻塞資源;處理器消耗型進程把時間大部分用在執行代碼上,除非被搶佔否則就一直在執行。

3,調度策略在兩個目標中找平衡:進程響應迅速和最大系統利用率。

4,進程優先順序:根據進程的價值和其對處理器時間需求來對進程分級。Linux兩種優先順序:nice值和即時優先順序,nice值作為權重將調整進程所使用的處理器時間比,nice值越高的進程被賦予低權重,喪失一部分處理器使用比;CFS調度器的搶佔時機取決於新的可執行程式消耗的多少處理器使用比,若比當前進程小,則新進程立刻投入運行,搶佔當前進程。舉例來說,一個文字編輯程式和一個視頻編碼程式是某一時刻僅有的兩個可執行程式,有相同的nice值,因為文字編輯器將更多時間用於等待使用者輸入,所以它的處理器使用比肯定低於50%,低於視頻編碼程式的使用比,所以CFS會在使用者輸入即文字編輯器被喚醒時,將其立即投入運行,搶佔視頻編碼程式,處理完程後,又一次進入睡眠等待使用者的下一次輸入。

5,linux的調度器是以模組方式提供的,允許不同類型的進程選擇不同的調度演算法,這種模組化結構成為調度器類,完全公平調度(CFS)是針對普通進程的調度類,它的做法是允許每個進程運行一段時間、迴圈輪轉、選擇運行最少的進程作為下一個運行進程。

6,調度器實體結構struct sched_entity作為一個名為se的成員嵌入在進程描述符struct task_struct內,se裡面的vruntime變數是進程花在運行上的時間和,CFS調度演算法的核心:選擇具有最小vruntime的任務。

CFS使用紅/黑樹狀結構組織可運行隊列,CFS的進程選擇演算法總結為運行rbtree樹中最左邊葉子節點所代表的那個進程。向樹中加入進程發生在進程變為可運行狀態或者通過fork()調用第一次建立進程時;從樹中刪除動作發生在進程阻塞(變為不可運行狀態)或者終止時。

調度器的入口時schedule()函數,它以優先順序為序,從最高的調度類開始,每個調度類要有自己的可運行隊列,從隊列中擷取下一個可啟動並執行進程。

睡眠:進程把自已標幟成睡眠狀態,從可執行紅黑數中移出,放入等待隊列,然後調用schedule()選擇和執行一個其他進程;

喚醒:進程被設定為可運行狀態,再從等待隊列中移到可執行紅/黑樹狀結構中;

等待隊列是由等待某些事件發生的進程組成的簡單鏈表。P50進程加入等待隊列的詳細步驟;

環境切換:context_switch()函數處理,調用switch_mm()將虛擬記憶體從上一個進程映射切換到新進程中;調用switch_to()將上一個進程的處理器狀態切換到新進程的處理器狀態,包括儲存、恢複棧資訊和寄存器資訊。

7,核心提供一個need_resched標誌來表明是否需要重新執行一次調度。

8,使用者搶佔發生在:從系統調用返回使用者空間時和從中斷處理常式返回使用者空間時;核心搶佔發生在:中斷處理常式正在執行,且返回核心空間之前;核心代碼再一次具有可搶佔性的時候;如果核心中的任務顯示的調用schedule();如果核心中的任務阻塞(這同樣也會調用schedule());

第五章:系統調用

1,在Linux中,系統調用是使用者空間訪問核心的唯一手段,除異常和陷入外,它們是核心唯一的合法入口。Unix的系統調用抽象出了用於完成某種確定的目的的函數,至於這些函數怎麼用完全不需要核心去關心。提供機制而不是策略。

2,在Linux中,每個系統調用被賦予一個系統調用號,核心記錄了系統調用表中的所有登入過的系統調用的列表,儲存在sys_call_table中。

3,使用者空間的程式無法直接執行核心代碼,因為核心駐守在受保護的地址空間上,應用程式通過非強制中斷的機制通知核心,通過引發一個異常促使系統切換到核心態去執行例外處理常式即系統調用處理常式system_call(),系統調用陷入核心要將系統調用號通過eax寄存器傳遞給核心,參數和傳回值都是通過寄存器傳遞。系統調用返回的時候,system_call負責切換到使用者空間,並讓使用者進程繼續執行下去。

第六章:核心資料結構

1,核心鏈表和普通鏈表的區別:普通鏈表的鏈表節點包含業務內容,而核心鏈表將業務內容和鏈表分離,單獨成為一個節點,並且將鏈表節點包含在其中;

i = (int) (&(((struct AdvAdvTeacher *)0)->age ));擷取業務內容的位移量。

2,核心紅/黑樹狀結構:詳見http://blog.csdn.net/yang_yulei/article/details/26066409

第七章:中斷和中斷處理

1,中斷是一種由硬體產生的電訊號,並直接送入中斷控制器,中斷控制器會給處理器發送一個電訊號,處理器通知作業系統已經產生中斷,作業系統再對中斷進行處理。

2,核心隨時可能因為新到的中斷而被打斷。硬體發生中斷是為了引起核心的關注。

3,每個中斷對應一個中斷值稱為IRQ插斷要求線,每個IRQ都關聯一個數值量。

4,異常與中斷的區別:異常要與處理器時鐘同步。異常是由軟體引起的,中斷是由硬體產生的。

5,核心響應中斷的特定函數叫中斷處理函數或插斷服務常式ISR,一個裝置的ISR是它的裝置驅動程式的一部分,裝置驅動程式是用於管理裝置的核心代碼。

6,當執行一個ISR時,核心處於中斷上下文中,中斷上下文與進程無關,無current宏無關,不可以睡眠,因為沒有後備進程所以無法調用發送器。

7,中斷處理常式是上半部,中斷處理常式打斷了其他的代碼(甚至可能打斷了在其他中斷線上的另一中斷處理常式),正是因為這種非同步執行的特性,所以所有的中斷處理常式必須儘可能的迅速簡潔,盡量把工作從中斷處理常式中分離出來,放在下半部來執行,因為下半部可以在更合適的時間運行。

8,中斷處理常式擁有自己的棧,每個處理器一個,大小為一頁。即中斷棧。

第八章:下半部和推後執行的工作

1,要盡量減少中斷處理常式中需要完成的工作量,因為它在啟動並執行時候,當前的中斷線在所有的處理器上都會被屏蔽。縮短中斷被屏蔽的時間對系統的響應能力和效能都至關重要。下半部執行的關鍵在於它們啟動並執行時候,允許響應所有的中斷。

2,非強制中斷:一個非強制中斷不會搶佔另一個非強制中斷,唯一可以搶佔非強制中斷的是中斷處理常式,相同類型的其他非強制中斷可以在其他處理器上同時運行。

3,tasklet

4,工作隊列把工作交由一個核心線程去執行,它總是會在進程上下文中執行,即允許重新調度和睡眠。

第九章:核心同步介紹

1,Linux核心是搶佔式核心,在沒有保護的的情況下,發送器可以在任何時刻搶佔正在啟動並執行核心代碼,重新調度其他的進程執行。

2,各種鎖機制之間的區別主要在於:當鎖已經被其他線程持有,因而不可用時的行為表現——一些鎖被爭用時會簡單地執行忙等待,而另外一些鎖會使當前任務睡眠直到鎖可用為止。

3實際上同步就是調用模組等待一個被調用體返回後,再繼續下一步;而非同步是調用模組發起調用之後,不用等待調用返回就繼續下一步了。

4,核心中造成並發執行的原因:a,中斷;b,非強制中斷和tasklet;c,核心搶佔;d,睡眠及與使用者空間的同步;e,對稱式多處理。

5,大多數核心資料結構都需要加鎖,要給資料而不是代碼加鎖。

6,自死結,如果一個執行線程試圖去獲得一個自己已經擁有的鎖,它將不得不等待鎖被釋放。abba死結每個線程都在等待其他線程持有的鎖,但是沒有一個線程會釋放他們一開始就持有的鎖。避免死結的規則:a按順序加鎖,b防止發生饑餓,c不要重複請求同一個鎖,d設計力求簡單。以獲得所的相反順序釋放鎖。

第十章:核心同步方法

1,自旋鎖最多隻能被一個可執行線程持有,如果一個線程試圖獲得一個被其他線程持有的自旋鎖,那麼該線程就會一直進行忙迴圈——旋轉——等待鎖重新可用。這樣特別浪費處理器時間,所以自旋鎖不應給長時間被持有。

2,自旋鎖可以用在中斷處理常式中,而訊號量不可以,因為訊號量會導致睡眠,在中斷處理常式中使用自旋鎖時,一定要在擷取鎖之前,禁止本地中斷(當前處理器上的插斷要求),否則,中斷處理常式就會打斷正持有鎖的核心代碼,有可能會試圖去爭用這個已經被持有的自旋鎖,這樣,中斷處理常式就會自旋,等待該鎖重新可用,但是鎖的持有人在這個中斷處理常式執行完畢前不可能運行。這就是雙重請求死結。

3,下半部和進程上下文共用資料時,因為下半部可以搶佔進程上下文,所以要對進程上下文中的共用資料進行保護,加鎖的同時還要禁止下半部執行;中斷處理常式和下半部共用資料時,由於中斷處理常式可以搶佔下半部,必須在擷取恰當的鎖的同時還要禁止中斷。同類的tasklet不能同時運行,所以對於同類tasklet中的共用資料不需要保護。當資料被兩個不同種類的taskLet共用時,就需要在訪問下半部中的資料前先獲得自旋鎖,不需要禁止下半部,因為同一個處理器上tasklet不會相互搶佔。資料被非強制中斷共用和tasklet一樣。

4,讀寫自旋鎖:一個或多個讀任務可以並發的持有讀者鎖,用於寫的鎖最多隻能被一個寫任務持有,而且此時不能有並發的讀操作。

5,訊號量:如果一個任務試圖獲得一個停用訊號量時,訊號量會將其推進一個等待隊列,然後讓其睡眠,這是處理器能重獲自由,去執行其他代碼,當持有的訊號量可用後,處於等待隊列中的那個任務將被喚醒,並獲得該訊號量。

6,訊號量適用於鎖被長期持有的情況。只能在進程上下文中擷取訊號量鎖。

7,讀寫訊號量,只要沒有寫者,並發持有讀鎖的讀者數不限,相反,只有唯一的寫者可以在沒有讀者時獲得寫鎖。

8,互斥鎖,使用計數始終為1的互斥訊號量。

9,完成變數、順序鎖、屏障;

第九章:記憶體管理

1,程式碼產生出的是邏輯地址,CPU要將一個邏輯地址轉換為物理地址,需要兩步:首先CPU利用段式記憶體管理單元,將邏輯地址轉換成線性地址,再利用頁式記憶體管理單元,把線性地址最終轉換為物理地址。

2,Linux採用頁式儲存管理機制,由於i386CPU的向下相容,所以Linux核心只不過是在對付本來就毫無必要卻又非得如此的例行公事而已,即每個段都是從0地址開始的整個4GB虛存空間,虛擬位址到線性地址的映射保持原值不變。

3,每個進程都擁有4G位元組的虛存空間,較低的3G位元組為自己的使用者空間,最高的1G位元組則為與所有進程以及核心共用的系統空間。雖然系統空間佔據了每個虛存空間中最高的1G位元組,在物理的記憶體中卻是從最低的地址(0)開始。

對於系統空間來說,其地址映射就是簡單的線性映射,給定一個虛地址x,其物理地址是從x中減去PAGE_OFFSET=0xC0000000,相應的,給定一個物理地址x,其虛擬位址是x+PAGE_OFFSET;不管什麼進程,一旦進入系統空間,都有相同的頁面映射。

對於使用者空間,其地址映射就是頁式管理的精髓了。Linux頁式映射機制分為三層:頁面目錄PGD,中間目錄PMD,頁面表PT,PT中的表項成為PTE。每個進程都有自己的PGD,PMD,PT,這三者均為數組。

一個地址為0000 1000 0000 0100 1000 0101 0110 1000,最高十位是十進位32,所以i386CPU就以32為下標去頁面目錄中找到其目錄項,這個目錄項的高20位指向一個頁面表,CPU在這20位後面添上12個0就得到該頁面表的指標,(每個頁面表佔一個頁面,所以自然就是4K位元組邊界對齊的,其起始地址的低12位一定是0,),找到頁面表以後,CPU再來看線性地址中的中間10位,即72,CPU就以此為下標在已經找到的頁表中找到相應的表項,與目錄項類似,32位的頁面表項中的高20位指向一個實體記憶體頁面,在後面添上12個0就得到這實體記憶體頁面的起始地址,在其起始地址上加上線性地址的最低12位就得到了最終的實體記憶體地址。

4,越界訪問:1,相應的頁面目錄項或頁面表項為空白;2,相應的物理頁面不在記憶體中;3,指令中規定的訪問方式與頁面的許可權不符;此時CPU會產生一個page fault exception頁面出錯異常,進而執行預定的頁面例外處理常式,並向該進程發送SIGSEGV訊號,進程每次從中斷/異常返回之前,都要檢查當前進程是否有懸而未決的訊號需要處理,即輸出Segment Fault,進程結束。

5,使用者堆棧的擴充:

假設進程運行過程中,已經用盡了為本進程分配的堆棧空間,即堆棧指標已經指向了堆棧空間的起始地址esp,假設現在需要調用某個子程式,CPU需將其返回地址壓入堆棧,即要將返回地址寫入esp-4的地方,而那個地方是空洞;因堆棧操作引起的越界是作為特殊情況對待的,需要檢查發生異常的地址是否緊挨著堆棧指標所指的地方標準是esp-32,如果不是,那就是非法越界訪問,如果是那就在空洞的頂端開始分配若干頁面建立映射,並將之併入堆棧空間,使其得到擴充。

6,中斷和異常的區別:當中斷以及自陷發生時,CPU都會將下一條指令,也就是本來應該執行的指令的地址壓入堆棧作為中斷服務程式的返回地址,異常發生時,CPU將因無法完成而夭折的指令本身的地址(不是下一條指令的地址)壓入堆棧,這就可以在從異常處理返回時完成未竟的事業。

7,記憶體配置演算法:http://blog.chinaunix.net/xmlrpc.php?r=blog/article&uid=28820980&id=3848787

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.