<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          操作系統(tǒng)常用知識總結(jié)!

          共 27254字,需瀏覽 55分鐘

           ·

          2021-08-24 00:45

          前言

          文章已經(jīng)同步到個人網(wǎng)站:http://xiaoflyfish.cn/!

          下一篇分享網(wǎng)站基礎(chǔ)!

          「文章較長,可以點贊在看」

          計算機(jī)結(jié)構(gòu)

          現(xiàn)代計算機(jī)模型是基于-「馮諾依曼計算機(jī)模型」

          計算機(jī)在運行時,先從內(nèi)存中取出第一條指令,通過控制器的譯碼,按指令的要求,從存儲器中取出數(shù)據(jù)進(jìn)行指定的運算和邏輯操作等加工,然后再按地址把結(jié)果送到內(nèi)存中去,接下來,再取出第二條指令,在控制器的指揮下完成規(guī)定操作,依此進(jìn)行下去。直至遇到停止指令

          程序與數(shù)據(jù)一樣存貯,按程序編排的順序,一步一步地取出指令,自動地完成指令規(guī)定的操作是計算機(jī)最基本的工作模型

          「計算機(jī)五大核心組成部分」

          控制器:是整個計算機(jī)的中樞神經(jīng),其功能是對程序規(guī)定的控制信息進(jìn)行解釋,根據(jù)其要求進(jìn)行控制,調(diào)度程序、數(shù)據(jù)、地址,協(xié)調(diào)計算機(jī)各部分工作及內(nèi)存與外設(shè)的訪問等。

          運算器:運算器的功能是對數(shù)據(jù)進(jìn)行各種算術(shù)運算和邏輯運算,即對數(shù)據(jù)進(jìn)行加工處理。

          存儲器:存儲器的功能是存儲程序、數(shù)據(jù)和各種信號、命令等信息,并在需要時提供這些信息。

          輸入:輸入設(shè)備是計算機(jī)的重要組成部分,輸入設(shè)備與輸出設(shè)備合你為外部設(shè)備,簡稱外設(shè),輸入設(shè)備的作用是將程序、原始數(shù)據(jù)、文字、字符、控制命令或現(xiàn)場采集的數(shù)據(jù)等信息輸入到計算機(jī)。

          ?

          常見的輸入設(shè)備有鍵盤、鼠標(biāo)器、光電輸入機(jī)、磁帶機(jī)、磁盤機(jī)、光盤機(jī)等。

          ?

          輸出:輸出設(shè)備與輸入設(shè)備同樣是計算機(jī)的重要組成部分,它把外算機(jī)的中間結(jié)果或最后結(jié)果、機(jī)內(nèi)的各種數(shù)據(jù)符號及文字或各種控制信號等信息輸出出來,微機(jī)常用的輸出設(shè)備有顯示終端CRT、打印機(jī)、激光印字機(jī)、繪圖儀及磁帶、光盤機(jī)等。

          「計算機(jī)結(jié)構(gòu)分成以下 5 個部分:」

          輸入設(shè)備;輸出設(shè)備;內(nèi)存;中央處理器;總線。

          內(nèi)存

          在馮諾依曼模型中,程序和數(shù)據(jù)被存儲在一個被稱作內(nèi)存的線性排列存儲區(qū)域。

          存儲的數(shù)據(jù)單位是一個二進(jìn)制位,英文是 bit,最小的存儲單位叫作字節(jié),也就是 8 位,英文是 byte,每一個字節(jié)都對應(yīng)一個內(nèi)存地址。

          內(nèi)存地址由 0 開始編號,比如第 1 個地址是 0,第 2 個地址是 1, 然后自增排列,最后一個地址是內(nèi)存中的字節(jié)數(shù)減 1。

          我們通常說的內(nèi)存都是隨機(jī)存取器,也就是讀取任何一個地址數(shù)據(jù)的速度是一樣的,寫入任何一個地址數(shù)據(jù)的速度也是一樣的。

          CPU

          馮諾依曼模型中 CPU 負(fù)責(zé)控制和計算,為了方便計算較大的數(shù)值,CPU 每次可以計算多個字節(jié)的數(shù)據(jù)。

          • 如果 CPU 每次可以計算 4 個 byte,那么我們稱作 32 位 CPU;

          • 如果 CPU 每次可以計算 8 個 byte,那么我們稱作 64 位 CPU。

          這里的 32 和 64,稱作 CPU 的位寬。

          「為什么 CPU 要這樣設(shè)計呢?」

          因為一個 byte 最大的表示范圍就是 0~255。

          比如要計算 20000*50,就超出了byte 最大的表示范圍了。

          因此,CPU 需要支持多個 byte 一起計算,當(dāng)然,CPU 位數(shù)越大,可以計算的數(shù)值就越大,但是在現(xiàn)實生活中不一定需要計算這么大的數(shù)值,比如說 32 位 CPU 能計算的最大整數(shù)是 4294967295,這已經(jīng)非常大了。

          「控制單元和邏輯運算單元」

          CPU 中有一個控制單元專門負(fù)責(zé)控制 CPU 工作;還有邏輯運算單元專門負(fù)責(zé)計算。

          「寄存器」

          CPU 要進(jìn)行計算,比如最簡單的加和兩個數(shù)字時,因為 CPU 離內(nèi)存太遠(yuǎn),所以需要一種離自己近的存儲來存儲將要被計算的數(shù)字。

          這種存儲就是寄存器,寄存器就在 CPU 里,控制單元和邏輯運算單元非常近,因此速度很快。

          常見的寄存器種類:

          • 通用寄存器,用來存放需要進(jìn)行運算的數(shù)據(jù),比如需要進(jìn)行加和運算的兩個數(shù)據(jù)。
          • 程序計數(shù)器,用來存儲 CPU 要執(zhí)行下一條指令所在的內(nèi)存地址,注意不是存儲了下一條要執(zhí)行的指令,此時指令還在內(nèi)存中,程序計數(shù)器只是存儲了下一條指令的地址。
          • 指令寄存器,用來存放程序計數(shù)器指向的指令,也就是指令本身,指令被執(zhí)行完成之前,指令都存儲在這里。

          多級緩存

          現(xiàn)代CPU為了提升執(zhí)行效率,減少CPU與內(nèi)存的交互(交互影響CPU效率),一般在CPU上集成了多級緩存架構(gòu)

          「CPU緩存」即高速緩沖存儲器,是位于CPU與主內(nèi)存間的一種容量較小但速度很高的存儲器

          由于CPU的速度遠(yuǎn)高于主內(nèi)存,CPU直接從內(nèi)存中存取數(shù)據(jù)要等待一定時間周期,Cache中保存著CPU剛用過或循環(huán)使用的一部分?jǐn)?shù)據(jù),當(dāng)CPU再次使用該部分?jǐn)?shù)據(jù)時可從Cache中直接調(diào)用,減少CPU的等待時間,提高了系統(tǒng)的效率,具體包括以下幾種:

          「L1-Cache」

          L1- 緩存在 CPU 中,相比寄存器,雖然它的位置距離 CPU 核心更遠(yuǎn),但造價更低,通常 L1-Cache 大小在幾十 Kb 到幾百 Kb 不等,讀寫速度在 2~4 個 CPU 時鐘周期。

          「L2-Cache」

          L2- 緩存也在 CPU 中,位置比 L1- 緩存距離 CPU 核心更遠(yuǎn),它的大小比 L1-Cache 更大,具體大小要看 CPU 型號,有 2M 的,也有更小或者更大的,速度在 10~20 個 CPU 周期。

          「L3-Cache」

          L3- 緩存同樣在 CPU 中,位置比 L2- 緩存距離 CPU 核心更遠(yuǎn),大小通常比 L2-Cache 更大,讀寫速度在 20~60 個 CPU 周期。

          L3 緩存大小也是看型號的,比如 i9 CPU 有 512KB L1 Cache;有 2MB L2 Cache; 有16MB L3 Cache。

          當(dāng) CPU 需要內(nèi)存中某個數(shù)據(jù)的時候,如果寄存器中有這個數(shù)據(jù),我們可以直接使用;如果寄存器中沒有這個數(shù)據(jù),我們就要先查詢 L1 緩存;L1 中沒有,再查詢 L2 緩存;L2 中沒有再查詢 L3 緩存;L3 中沒有,再去內(nèi)存中拿。

          「總結(jié):」

          存儲器存儲空間大小:內(nèi)存>L3>L2>L1>寄存器;

          存儲器速度快慢排序:寄存器>L1>L2>L3>內(nèi)存;

          安全等級

          「CPU運行安全等級」

          CPU有4個運行級別,分別為:

          • ring0,ring1,ring2,ring3

          ring0只給操作系統(tǒng)用,ring3誰都能用。

          ring0是指CPU的運行級別,是最高級別,ring1次之,ring2更次之……

          系統(tǒng)(內(nèi)核)的代碼運行在最高運行級別ring0上,可以使用特權(quán)指令,控制中斷、修改頁表、訪問設(shè)備等等。

          應(yīng)用程序的代碼運行在最低運行級別上ring3上,不能做受控操作。

          如果要做,比如要訪問磁盤,寫文件,那就要通過執(zhí)行系統(tǒng)調(diào)用(函數(shù)),執(zhí)行系統(tǒng)調(diào)用的時候,CPU的運行級別會發(fā)生從ring3到ring0的切換,并跳轉(zhuǎn)到系統(tǒng)調(diào)用對應(yīng)的內(nèi)核代碼位置執(zhí)行,這樣內(nèi)核就為你完成了設(shè)備訪問,完成之后再從ring0返回ring3。

          這個過程也稱作用戶態(tài)和內(nèi)核態(tài)的切換。

          局部性原理

          在CPU訪問存儲設(shè)備時,無論是存取數(shù)據(jù)抑或存取指令,都趨于聚集在一片連續(xù)的區(qū)域中,這就被稱為局部性原理

          「時間局部性(Temporal Locality):」

          如果一個信息項正在被訪問,那么在近期它很可能還會被再次訪問。

          比如循環(huán)、遞歸、方法的反復(fù)調(diào)用等。

          「空間局部性(Spatial Locality):」

          如果一個存儲器的位置被引用,那么將來他附近的位置也會被引用。

          比如順序執(zhí)行的代碼、連續(xù)創(chuàng)建的兩個對象、數(shù)組等。

          程序的執(zhí)行過程

          程序?qū)嶋H上是一條一條指令,所以程序的運行過程就是把每一條指令一步一步的執(zhí)行起來,負(fù)責(zé)執(zhí)行指令的就是 CPU 了。

          「那 CPU 執(zhí)行程序的過程如下:」

          • 第一步,CPU 讀取程序計數(shù)器的值,這個值是指令的內(nèi)存地址,然后 CPU 的控制單元操作地址總線指定需要訪問的內(nèi)存地址,接著通知內(nèi)存設(shè)備準(zhǔn)備數(shù)據(jù),數(shù)據(jù)準(zhǔn)備好后通過數(shù)據(jù)總線將指令數(shù)據(jù)傳給 CPU,CPU 收到內(nèi)存?zhèn)鱽淼臄?shù)據(jù)后,將這個指令數(shù)據(jù)存入到指令寄存器。
          • 第二步,CPU 分析指令寄存器中的指令,確定指令的類型和參數(shù),如果是計算類型的指令,就把指令交給邏輯運算單元運算;如果是存儲類型的指令,則交由控制單元執(zhí)行;
          • 第三步,CPU 執(zhí)行完指令后,程序計數(shù)器的值自增,表示指向下一條指令。這個自增的大小,由 CPU 的位寬決定,比如 32 位的 CPU,指令是 4 個字節(jié),需要 4 個內(nèi)存地址存放,因此程序計數(shù)器的值會自增 4;

          簡單總結(jié)一下就是,一個程序執(zhí)行的時候,CPU 會根據(jù)程序計數(shù)器里的內(nèi)存地址,從內(nèi)存里面把需要執(zhí)行的指令讀取到指令寄存器里面執(zhí)行,然后根據(jù)指令長度自增,開始順序讀取下一條指令。

          CPU 從程序計數(shù)器讀取指令、到執(zhí)行、再到下一條指令,這個過程會不斷循環(huán),直到程序執(zhí)行結(jié)束,這個不斷循環(huán)的過程被稱為 「CPU 的指令周期」

          總線

          CPU 和內(nèi)存以及其他設(shè)備之間,也需要通信,因此我們用一種特殊的設(shè)備進(jìn)行控制,就是總線。

          • 地址總線,用于指定 CPU 將要操作的內(nèi)存地址;
          • 數(shù)據(jù)總線,用于讀寫內(nèi)存的數(shù)據(jù);
          • 控制總線,用于發(fā)送和接收信號,比如中斷、設(shè)備復(fù)位等信號,CPU 收到信號后自然進(jìn)行響應(yīng),這時也需要控制總線;

          當(dāng) CPU 要讀寫內(nèi)存數(shù)據(jù)的時候,一般需要通過兩個總線:

          • 首先要通過地址總線來指定內(nèi)存的地址;
          • 再通過數(shù)據(jù)總線來傳輸數(shù)據(jù);

          輸入、輸出設(shè)備

          輸入設(shè)備向計算機(jī)輸入數(shù)據(jù),計算機(jī)經(jīng)過計算,將結(jié)果通過輸出設(shè)備向外界傳達(dá)。

          如果輸入設(shè)備、輸出設(shè)備想要和 CPU 進(jìn)行交互,比如說用戶按鍵需要 CPU 響應(yīng),這時候就需要用到控制總線。

          基礎(chǔ)知識

          中斷

          「中斷的類型」

          • 按照中斷的觸發(fā)方分成同步中斷和異步中斷;

          • 根據(jù)中斷是否強(qiáng)制觸發(fā)分成可屏蔽中斷和不可屏蔽中斷。

          中斷可以由 CPU 指令直接觸發(fā),這種主動觸發(fā)的中斷,叫作同步中斷。

          ?

          同步中斷有幾種情況。

          ?
          • 比如系統(tǒng)調(diào)用,需要從用戶態(tài)切換內(nèi)核態(tài),這種情況需要程序觸發(fā)一個中斷,叫作陷阱(Trap),中斷觸發(fā)后需要繼續(xù)執(zhí)行系統(tǒng)調(diào)用。

          • 還有一種同步中斷情況是錯誤(Fault),通常是因為檢測到某種錯誤,需要觸發(fā)一個中斷,中斷響應(yīng)結(jié)束后,會重新執(zhí)行觸發(fā)錯誤的地方,比如后面我們要學(xué)習(xí)的缺頁中斷。

          • 最后還有一種情況是程序的異常,這種情況和 Trap 類似,用于實現(xiàn)程序拋出的異常。

          另一部分中斷不是由 CPU 直接觸發(fā),是因為需要響應(yīng)外部的通知,比如響應(yīng)鍵盤、鼠標(biāo)等設(shè)備而觸發(fā)的中斷,這種中斷我們稱為異步中斷。

          CPU 通常都支持設(shè)置一個中斷屏蔽位(一個寄存器),設(shè)置為 1 之后 CPU 暫時就不再響應(yīng)中斷。

          對于鍵盤鼠標(biāo)輸入,比如陷阱、錯誤、異常等情況,會被臨時屏蔽。

          但是對于一些特別重要的中斷,比如 CPU 故障導(dǎo)致的掉電中斷,還是會正常觸發(fā)。

          「可以被屏蔽的中斷我們稱為可屏蔽中斷,多數(shù)中斷都是可屏蔽中斷。」

          內(nèi)核態(tài)和用戶態(tài)

          「什么是用戶態(tài)和內(nèi)核態(tài)」

          Kernel 運行在超級權(quán)限模式下,所以擁有很高的權(quán)限。

          按照權(quán)限管理的原則,多數(shù)應(yīng)用程序應(yīng)該運行在最小權(quán)限下。

          因此,很多操作系統(tǒng),將內(nèi)存分成了兩個區(qū)域:

          • 內(nèi)核空間(Kernal Space),這個空間只有內(nèi)核程序可以訪問;

          • 用戶空間(User Space),這部分內(nèi)存專門給應(yīng)用程序使用。

          用戶空間中的代碼被限制了只能使用一個局部的內(nèi)存空間,我們說這些程序在用戶態(tài) 執(zhí)行。

          內(nèi)核空間中的代碼可以訪問所有內(nèi)存,我們稱這些程序在內(nèi)核態(tài) 執(zhí)行。

          ?

          按照級別分:

          ?

          當(dāng)程序運行在0級特權(quán)級上時,就可以稱之為運行在內(nèi)核態(tài)

          當(dāng)程序運行在3級特權(quán)級上時,就可以稱之為運行在用戶態(tài)

          運行在用戶態(tài)下的程序不能直接訪問操作系統(tǒng)內(nèi)核數(shù)據(jù)結(jié)構(gòu)和程序。

          當(dāng)我們在系統(tǒng)中執(zhí)行一個程序時,大部分時間是運行在用戶態(tài)下的,在其需要操作系統(tǒng)幫助完成某些它沒有權(quán)力和能力完成的工作時就會切換到內(nèi)核態(tài)(比如操作硬件)

          「這兩種狀態(tài)的主要差別」

          處于用戶態(tài)執(zhí)行時,進(jìn)程所能訪問的內(nèi)存空間和對象受到限制,其所處于占有的處理器是可被搶占的

          處于內(nèi)核態(tài)執(zhí)行時,則能訪問所有的內(nèi)存空間和對象,且所占有的處理器是不允許被搶占的。

          「為什么要有用戶態(tài)和內(nèi)核態(tài)」

          由于需要限制不同的程序之間的訪問能力,防止他們獲取別的程序的內(nèi)存數(shù)據(jù),或者獲取外圍設(shè)備的數(shù)據(jù),并發(fā)送到網(wǎng)絡(luò)

          「用戶態(tài)與內(nèi)核態(tài)的切換」

          所有用戶程序都是運行在用戶態(tài)的,但是有時候程序確實需要做一些內(nèi)核態(tài)的事情, 例如從硬盤讀取數(shù)據(jù),或者從鍵盤獲取輸入等,而唯一可以做這些事情的就是操作系統(tǒng),所以此時程序就需要先操作系統(tǒng)請求以程序的名義來執(zhí)行這些操作

          「用戶態(tài)和內(nèi)核態(tài)的轉(zhuǎn)換」

          ?

          系統(tǒng)調(diào)用

          ?

          用戶態(tài)進(jìn)程通過系統(tǒng)調(diào)用申請使用操作系統(tǒng)提供的服務(wù)程序完成工作,比如fork()實際上就是執(zhí)行了一個創(chuàng)建新進(jìn)程的系統(tǒng)調(diào)用

          而系統(tǒng)調(diào)用的機(jī)制其核心還是使用了操作系統(tǒng)為用戶特別開放的一個中斷來實現(xiàn),例如Linux的int 80h中斷

          「舉例:」

          如上圖所示:內(nèi)核程序執(zhí)行在內(nèi)核態(tài)(Kernal Mode),用戶程序執(zhí)行在用戶態(tài)(User Mode)。

          當(dāng)發(fā)生系統(tǒng)調(diào)用時,用戶態(tài)的程序發(fā)起系統(tǒng)調(diào)用,因為系統(tǒng)調(diào)用中牽扯特權(quán)指令,用戶態(tài)程序權(quán)限不足,因此會中斷執(zhí)行,也就是 Trap(Trap 是一種中斷)。

          發(fā)生中斷后,當(dāng)前 CPU 執(zhí)行的程序會中斷,跳轉(zhuǎn)到中斷處理程序,內(nèi)核程序開始執(zhí)行,也就是開始處理系統(tǒng)調(diào)用。

          內(nèi)核處理完成后,主動觸發(fā) Trap,這樣會再次發(fā)生中斷,切換回用戶態(tài)工作。

          ?

          異常

          ?

          當(dāng)CPU在執(zhí)行運行在用戶態(tài)下的程序時,發(fā)生了某些事先不可知的異常,這時會觸發(fā)由當(dāng)前運行進(jìn)程切換到處理此異常的內(nèi)核相關(guān)程序中,也就轉(zhuǎn)到了內(nèi)核態(tài),比如缺頁異常

          ?

          外圍設(shè)備的中斷

          ?

          當(dāng)外圍設(shè)備完成用戶請求的操作后,會向CPU發(fā)出相應(yīng)的中斷信號,這時CPU會暫停執(zhí)行下一條即將要執(zhí)行的指令轉(zhuǎn)而去執(zhí)行與中斷信號對應(yīng)的處理程序,如果先前執(zhí)行的指令是用戶態(tài)下的程序,那么這個轉(zhuǎn)換的過程自然也就發(fā)生了由用戶態(tài)到內(nèi)核態(tài)的切換

          比如硬盤讀寫操作完成,系統(tǒng)會切換到硬盤讀寫的中斷處理程序中執(zhí)行后續(xù)操作等

          線程

          線程:系統(tǒng)分配處理器時間資源的基本單元,是程序執(zhí)行的最小單位

          線程可以看做輕量級的進(jìn)程,共享內(nèi)存空間,每個線程都有自己獨立的運行棧和程序計數(shù)器,線程之間切換的開銷小。

          在同一個進(jìn)程(程序)中有多個線程同時執(zhí)行(通過CPU調(diào)度,在每個時間片中只有一個線程執(zhí)行)

          進(jìn)程可以通過 API 創(chuàng)建用戶態(tài)的線程,也可以通過系統(tǒng)調(diào)用創(chuàng)建內(nèi)核態(tài)的線程。

          用戶態(tài)線程

          用戶態(tài)線程也稱作用戶級線程,操作系統(tǒng)內(nèi)核并不知道它的存在,它完全是在用戶空間中創(chuàng)建。

          用戶級線程有很多優(yōu)勢,比如:

          • 管理開銷小:創(chuàng)建、銷毀不需要系統(tǒng)調(diào)用。

          • 切換成本低:用戶空間程序可以自己維護(hù),不需要走操作系統(tǒng)調(diào)度。

          但是這種線程也有很多的缺點:

          • 與內(nèi)核協(xié)作成本高:比如這種線程完全是用戶空間程序在管理,當(dāng)它進(jìn)行 I/O 的時候,無法利用到內(nèi)核的優(yōu)勢,需要頻繁進(jìn)行用戶態(tài)到內(nèi)核態(tài)的切換。

          • 線程間協(xié)作成本高:設(shè)想兩個線程需要通信,通信需要 I/O,I/O 需要系統(tǒng)調(diào)用,因此用戶態(tài)線程需要額外的系統(tǒng)調(diào)用成本。

          • 無法利用多核優(yōu)勢:比如操作系統(tǒng)調(diào)度的仍然是這個線程所屬的進(jìn)程,所以無論每次一個進(jìn)程有多少用戶態(tài)的線程,都只能并發(fā)執(zhí)行一個線程,因此一個進(jìn)程的多個線程無法利用多核的優(yōu)勢。

          操作系統(tǒng)無法針對線程調(diào)度進(jìn)行優(yōu)化:當(dāng)一個進(jìn)程的一個用戶態(tài)線程阻塞(Block)了,操作系統(tǒng)無法及時發(fā)現(xiàn)和處理阻塞問題,它不會更換執(zhí)行其他線程,從而造成資源浪費。

          內(nèi)核態(tài)線程

          內(nèi)核態(tài)線程也稱作內(nèi)核級線程(Kernel Level Thread),這種線程執(zhí)行在內(nèi)核態(tài),可以通過系統(tǒng)調(diào)用創(chuàng)造一個內(nèi)核級線程。

          內(nèi)核級線程有很多優(yōu)勢:

          • 可以利用多核 CPU 優(yōu)勢:內(nèi)核擁有較高權(quán)限,因此可以在多個 CPU 核心上執(zhí)行內(nèi)核線程。

          • 操作系統(tǒng)級優(yōu)化:內(nèi)核中的線程操作 I/O 不需要進(jìn)行系統(tǒng)調(diào)用;一個內(nèi)核線程阻塞了,可以立即讓另一個執(zhí)行。

          當(dāng)然內(nèi)核線程也有一些缺點:

          • 創(chuàng)建成本高:創(chuàng)建的時候需要系統(tǒng)調(diào)用,也就是切換到內(nèi)核態(tài)。

          • 擴(kuò)展性差:由一個內(nèi)核程序管理,不可能數(shù)量太多。

          • 切換成本較高:切換的時候,也同樣存在需要內(nèi)核操作,需要切換內(nèi)核態(tài)。

          「用戶態(tài)線程和內(nèi)核態(tài)線程之間的映射關(guān)系」

          如果有一個用戶態(tài)的進(jìn)程,它下面有多個線程,如果這個進(jìn)程想要執(zhí)行下面的某一個線程,應(yīng)該如何做呢?

          ?

          這時,比較常見的一種方式,就是將需要執(zhí)行的程序,讓一個內(nèi)核線程去執(zhí)行。

          ?

          畢竟,內(nèi)核線程是真正的線程,因為它會分配到 CPU 的執(zhí)行資源。

          如果一個進(jìn)程所有的線程都要自己調(diào)度,相當(dāng)于在進(jìn)程的主線程中實現(xiàn)分時算法調(diào)度每一個線程,也就是所有線程都用操作系統(tǒng)分配給主線程的時間片段執(zhí)行。

          ?

          這種做法,相當(dāng)于操作系統(tǒng)調(diào)度進(jìn)程的主線程;進(jìn)程的主線程進(jìn)行二級調(diào)度,調(diào)度自己內(nèi)部的線程。

          ?

          這樣操作劣勢非常明顯,比如無法利用多核優(yōu)勢,每個線程調(diào)度分配到的時間較少,而且這種線程在阻塞場景下會直接交出整個進(jìn)程的執(zhí)行權(quán)限。

          由此可見,用戶態(tài)線程創(chuàng)建成本低,問題明顯,不可以利用多核。

          內(nèi)核態(tài)線程,創(chuàng)建成本高,可以利用多核,切換速度慢。

          因此通常我們會在內(nèi)核中預(yù)先創(chuàng)建一些線程,并反復(fù)利用這些線程。

          協(xié)程

          協(xié)程,是一種比線程更加輕量級的存在,協(xié)程不是被操作系統(tǒng)內(nèi)核所管理,而完全是由程序所控制(也就是在用戶態(tài)執(zhí)行)。

          這樣帶來的好處就是性能得到了很大的提升,不會像線程切換那樣消耗資源。

          「子程序」

          或者稱為函數(shù),在所有語言中都是層級調(diào)用,比如A調(diào)用B,B在執(zhí)行過程中又調(diào)用了C,C執(zhí)行完畢返回,B執(zhí)行完畢返回,最后是A執(zhí)行完畢。

          所以子程序調(diào)用是通過棧實現(xiàn)的,一個線程就是執(zhí)行一個子程序。

          子程序調(diào)用總是一個入口,一次返回,調(diào)用順序是明確的。

          「協(xié)程的特點在于是一個線程執(zhí)行,那和多線程比,協(xié)程有何優(yōu)勢?」

          • 極高的執(zhí)行效率:因為子程序切換不是線程切換,而是由程序自身控制,因此,沒有線程切換的開銷,和多線程比,線程數(shù)量越多,協(xié)程的性能優(yōu)勢就越明顯;
          • 不需要多線程的鎖機(jī)制:因為只有一個線程,也不存在同時寫變量沖突,在協(xié)程中控制共享資源不加鎖,只需要判斷狀態(tài)就好了,所以執(zhí)行效率比多線程高很多。

          線程安全

          如果你的代碼所在的進(jìn)程中有多個線程在同時運行,而這些線程可能會同時運行這段代碼。

          如果每次運行結(jié)果和單線程運行的結(jié)果是一樣的,而且其他的變量的值也和預(yù)期的是一樣的,就是線程安全的。

          進(jìn)程

          在系統(tǒng)中正在運行的一個應(yīng)用程序;程序一旦運行就是進(jìn)程;是資源分配的最小單位。

          在操作系統(tǒng)中能同時運行多個進(jìn)程;

          開機(jī)的時候,磁盤的內(nèi)核鏡像被導(dǎo)入內(nèi)存作為一個執(zhí)行副本,成為內(nèi)核進(jìn)程。

          進(jìn)程可以分成「用戶態(tài)進(jìn)程和內(nèi)核態(tài)進(jìn)程」兩類,用戶態(tài)進(jìn)程通常是應(yīng)用程序的副本,內(nèi)核態(tài)進(jìn)程就是內(nèi)核本身的進(jìn)程。

          如果用戶態(tài)進(jìn)程需要申請資源,比如內(nèi)存,可以通過系統(tǒng)調(diào)用向內(nèi)核申請。

          每個進(jìn)程都有獨立的內(nèi)存空間,存放代碼和數(shù)據(jù)段等,程序之間的切換會有較大的開銷;

          「分時和調(diào)度」

          每個進(jìn)程在執(zhí)行時都會獲得操作系統(tǒng)分配的一個時間片段,如果超出這個時間,就會輪到下一個進(jìn)程(線程)執(zhí)行。

          ?

          注意,現(xiàn)代操作系統(tǒng)都是直接調(diào)度線程,不會調(diào)度進(jìn)程。

          ?

          「分配時間片段」

          如下圖所示,進(jìn)程 1 需要 2 個時間片段,進(jìn)程 2 只有 1 個時間片段,進(jìn)程 3 需要 3 個時間片段。

          因此當(dāng)進(jìn)程 1 執(zhí)行到一半時,會先掛起,然后進(jìn)程 2 開始執(zhí)行;進(jìn)程 2 一次可以執(zhí)行完,然后進(jìn)程 3 開始執(zhí)行,不過進(jìn)程 3 一次執(zhí)行不完,在執(zhí)行了 1 個時間片段后,進(jìn)程 1 開始執(zhí)行;就這樣如此周而復(fù)始,這個就是分時技術(shù)。

          創(chuàng)建進(jìn)程

          用戶想要創(chuàng)建一個進(jìn)程,最直接的方法就是從命令行執(zhí)行一個程序,或者雙擊打開一個應(yīng)用,但對于程序員而言,顯然需要更好的設(shè)計。

          首先,應(yīng)該有 API 打開應(yīng)用,比如可以通過函數(shù)打開某個應(yīng)用;

          另一方面,如果程序員希望執(zhí)行完一段代價昂貴的初始化過程后,將當(dāng)前程序的狀態(tài)復(fù)制好幾份,變成一個個單獨執(zhí)行的進(jìn)程,那么操作系統(tǒng)提供了 fork 指令。

          也就是說,每次 fork 會多創(chuàng)造一個克隆的進(jìn)程,這個克隆的進(jìn)程,所有狀態(tài)都和原來的進(jìn)程一樣,但是會有自己的地址空間。

          如果要創(chuàng)造 2 個克隆進(jìn)程,就要 fork 兩次。

          ?

          那如果我就是想啟動一個新的程序呢?

          ?

          操作系統(tǒng)提供了啟動新程序的 API。

          如果我就是想用一個新進(jìn)程執(zhí)行一小段程序,比如說每次服務(wù)端收到客戶端的請求時,我都想用一個進(jìn)程去處理這個請求。

          如果是這種情況,建議你不要單獨啟動進(jìn)程,而是使用線程。

          因為進(jìn)程的創(chuàng)建成本實在太高了,因此不建議用來做這樣的事情:要創(chuàng)建條目、要分配內(nèi)存,特別是還要在內(nèi)存中形成一個個段,分成不同的區(qū)域。所以通常,我們更傾向于多創(chuàng)建線程。

          不同程序語言會自己提供創(chuàng)建線程的 API,比如 Java 有 Thread 類;go 有 go-routine(注意不是協(xié)程,是線程)。

          進(jìn)程狀態(tài)

          「創(chuàng)建狀態(tài)」

          進(jìn)程由創(chuàng)建而產(chǎn)生,創(chuàng)建進(jìn)程是一個非常復(fù)雜的過程,一般需要通過多個步驟才能完成:如首先由進(jìn)程申請一個空白的進(jìn)程控制塊(PCB),并向PCB中填寫用于控制和管理進(jìn)程的信息;然后為該進(jìn)程分配運行時所必須的資源;最后,把該進(jìn)程轉(zhuǎn)入就緒狀態(tài)并插入到就緒隊列中

          「就緒狀態(tài)」

          這是指進(jìn)程已經(jīng)準(zhǔn)備好運行的狀態(tài),即進(jìn)程已分配到除CPU以外所有的必要資源后,只要再獲得CPU,便可立即執(zhí)行,如果系統(tǒng)中有許多處于就緒狀態(tài)的進(jìn)程,通常將它們按照一定的策略排成一個隊列,該隊列稱為就緒隊列,有執(zhí)行資格,沒有執(zhí)行權(quán)的進(jìn)程

          「運行狀態(tài)」

          這里指進(jìn)程已經(jīng)獲取CPU,其進(jìn)程處于正在執(zhí)行的狀態(tài)。對任何一個時刻而言,在單處理機(jī)的系統(tǒng)中,只有一個進(jìn)程處于執(zhí)行狀態(tài)而在多處理機(jī)系統(tǒng)中,有多個進(jìn)程處于執(zhí)行狀態(tài),既有執(zhí)行資格,又有執(zhí)行權(quán)的進(jìn)程

          「阻塞狀態(tài)」

          這里是指正在執(zhí)行的進(jìn)程由于發(fā)生某事件(如I/O請求、申請緩沖區(qū)失敗等)暫時無法繼續(xù)執(zhí)行的狀態(tài),即進(jìn)程執(zhí)行受到阻塞,此時引起進(jìn)程調(diào)度,操作系統(tǒng)把處理機(jī)分配給另外一個就緒的進(jìn)程,而讓受阻的進(jìn)程處于暫停的狀態(tài),一般將這個暫停狀態(tài)稱為阻塞狀態(tài)

          「終止?fàn)顟B(tài)」

          進(jìn)程間通信IPC

          每個進(jìn)程各自有不同的用戶地址空間,任何一個進(jìn)程的全局變量在另一個進(jìn)程中都看不到,所以進(jìn)程之間要交換數(shù)據(jù)必須通過內(nèi)核,在內(nèi)核中開辟一塊緩沖區(qū),進(jìn)程1把數(shù)據(jù)從用戶空間拷到內(nèi)核緩沖區(qū),進(jìn)程2再從內(nèi)核緩沖區(qū)把數(shù)據(jù)讀走,內(nèi)核提供的這種機(jī)制稱為進(jìn)程間通信

          「管道/匿名管道」

          管道是半雙工的,數(shù)據(jù)只能向一個方向流動;需要雙方通信時,需要建立起兩個管道。

          • 只能用于父子進(jìn)程或者兄弟進(jìn)程之間(具有親緣關(guān)系的進(jìn)程);

          • 單獨構(gòu)成一種獨立的文件系統(tǒng):管道對于管道兩端的進(jìn)程而言,就是一個文件,但它不是普通的文件,它不屬于某種文件系統(tǒng),而是自立門戶,單獨構(gòu)成一種文件系統(tǒng),并且只存在與內(nèi)存中。

          • 數(shù)據(jù)的讀出和寫入:一個進(jìn)程向管道中寫的內(nèi)容被管道另一端的進(jìn)程讀出,寫入的內(nèi)容每次都添加在管道緩沖區(qū)的末尾,并且每次都是從緩沖區(qū)的頭部讀出數(shù)據(jù)。

          「有名管道(FIFO)」

          匿名管道,由于沒有名字,只能用于親緣關(guān)系的進(jìn)程間通信。

          為了克服這個缺點,提出了有名管道(FIFO)。

          有名管道不同于匿名管道之處在于它提供了一個路徑名與之關(guān)聯(lián),以有名管道的文件形式存在于文件系統(tǒng)中,這樣,即使與有名管道的創(chuàng)建進(jìn)程不存在親緣關(guān)系的進(jìn)程,只要可以訪問該路徑,就能夠彼此通過有名管道相互通信,因此,通過有名管道不相關(guān)的進(jìn)程也能交換數(shù)據(jù)。

          「信號」

          信號是Linux系統(tǒng)中用于進(jìn)程間互相通信或者操作的一種機(jī)制,信號可以在任何時候發(fā)給某一進(jìn)程,而無需知道該進(jìn)程的狀態(tài)。

          如果該進(jìn)程當(dāng)前并未處于執(zhí)行狀態(tài),則該信號就有內(nèi)核保存起來,知道該進(jìn)程回復(fù)執(zhí)行并傳遞給它為止。

          如果一個信號被進(jìn)程設(shè)置為阻塞,則該信號的傳遞被延遲,直到其阻塞被取消是才被傳遞給進(jìn)程。

          「消息隊列」

          消息隊列是存放在內(nèi)核中的消息鏈表,每個消息隊列由消息隊列標(biāo)識符表示。

          與管道(無名管道:只存在于內(nèi)存中的文件;命名管道:存在于實際的磁盤介質(zhì)或者文件系統(tǒng))不同的是消息隊列存放在內(nèi)核中,只有在內(nèi)核重啟(即操作系統(tǒng)重啟)或者顯示地刪除一個消息隊列時,該消息隊列才會被真正的刪除。

          另外與管道不同的是,消息隊列在某個進(jìn)程往一個隊列寫入消息之前,并不需要另外某個進(jìn)程在該隊列上等待消息的到達(dá)

          「共享內(nèi)存」

          使得多個進(jìn)程可以直接讀寫同一塊內(nèi)存空間,是最快的可用IPC形式,是針對其他通信機(jī)制運行效率較低而設(shè)計的。

          為了在多個進(jìn)程間交換信息,內(nèi)核專門留出了一塊內(nèi)存區(qū),可以由需要訪問的進(jìn)程將其映射到自己的私有地址空間,進(jìn)程就可以直接讀寫這一塊內(nèi)存而不需要進(jìn)行數(shù)據(jù)的拷貝,從而大大提高效率。

          由于多個進(jìn)程共享一段內(nèi)存,因此需要依靠某種同步機(jī)制(如信號量)來達(dá)到進(jìn)程間的同步及互斥。

          共享內(nèi)存示意圖:

          一旦這樣的內(nèi)存映射到共享它的進(jìn)程的地址空間,這些進(jìn)程間數(shù)據(jù)傳遞不再涉及到內(nèi)核,換句話說是進(jìn)程不再通過執(zhí)行進(jìn)入內(nèi)核的系統(tǒng)調(diào)用來傳遞彼此的數(shù)據(jù)。

          「信號量」

          信號量是一個計數(shù)器,用于多進(jìn)程對共享數(shù)據(jù)的訪問,信號量的意圖在于進(jìn)程間同步。

          為了獲得共享資源,進(jìn)程需要執(zhí)行下列操作:

          1. 創(chuàng)建一個信號量:這要求調(diào)用者指定初始值,對于二值信號量來說,它通常是1,也可是0。

          2. 等待一個信號量:該操作會測試這個信號量的值,如果小于0,就阻塞,也稱為P操作。

          3. 掛出一個信號量:該操作將信號量的值加1,也稱為V操作。

          「套接字(Socket)」

          套接字是一種通信機(jī)制,憑借這種機(jī)制,客戶/服務(wù)器(即要進(jìn)行通信的進(jìn)程)系統(tǒng)的開發(fā)工作既可以在本地單機(jī)上進(jìn)行,也可以跨網(wǎng)絡(luò)進(jìn)行。也就是說它可以讓不在同一臺計算機(jī)但通過網(wǎng)絡(luò)連接計算機(jī)上的進(jìn)程進(jìn)行通信。

          信號

          信號是進(jìn)程間通信機(jī)制中唯一的異步通信機(jī)制,可以看作是異步通知,通知接收信號的進(jìn)程有哪些事情發(fā)生了。

          也可以簡單理解為信號是某種形式上的軟中斷

          可運行kill -l查看Linux支持的信號列表:

          kill -l
           1) SIGHUP  2) SIGINT  3) SIGQUIT  4) SIGILL  5) SIGTRAP
           6) SIGABRT  7) SIGBUS  8) SIGFPE  9) SIGKILL 10) SIGUSR1
          11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
          16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
          21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
          26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR
          31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3
          38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8
          43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
          48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
          53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7
          58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2
          63) SIGRTMAX-1 64) SIGRTMAX

          「幾個常用的信號:」

          信號描述
          SIGHUP當(dāng)用戶退出終端時,由該終端開啟的所有進(jìn)程都會接收到這個信號,默認(rèn)動作為終止進(jìn)程。
          SIGINT程序終止(interrupt)信號, 在用戶鍵入INTR字符(通常是Ctrl+C)時發(fā)出,用于通知前臺進(jìn)程組終止進(jìn)程。
          SIGQUITSIGINT類似, 但由QUIT字符(通常是Ctrl+\)來控制,進(jìn)程在因收到SIGQUIT退出時會產(chǎn)生core文件, 在這個意義上類似于一個程序錯誤信號。
          SIGKILL用來立即結(jié)束程序的運行,本信號不能被阻塞、處理和忽略。
          SIGTERM程序結(jié)束(terminate)信號, 與SIGKILL不同的是該信號可以被阻塞和處理。通常用來要求程序自己正常退出。
          SIGSTOP停止(stopped)進(jìn)程的執(zhí)行. 注意它和terminate以及interrupt的區(qū)別:該進(jìn)程還未結(jié)束, 只是暫停執(zhí)行,本信號不能被阻塞, 處理或忽略

          進(jìn)程同步

          「臨界區(qū)」

          通過對多線程的串行化來訪問公共資源或一段代碼,速度快,適合控制數(shù)據(jù)訪問

          優(yōu)點:保證在某一時刻只有一個線程能訪問數(shù)據(jù)的簡便辦法

          缺點:雖然臨界區(qū)同步速度很快,但卻只能用來同步本進(jìn)程內(nèi)的線程,而不可用來同步多個進(jìn)程中的線程

          「互斥量」

          為協(xié)調(diào)共同對一個共享資源的單獨訪問而設(shè)計的

          互斥量跟臨界區(qū)很相似,比臨界區(qū)復(fù)雜,互斥對象只有一個,只有擁有互斥對象的線程才具有訪問資源的權(quán)限

          優(yōu)點:使用互斥不僅僅能夠在同一應(yīng)用程序不同線程中實現(xiàn)資源的安全共享,而且可以在不同應(yīng)用程序的線程之間實現(xiàn)對資源的安全共享

          「信號量」

          為控制一個具有有限數(shù)量用戶資源而設(shè)計,它允許多個線程在同一時刻訪問同一資源,但是需要限制在同一時刻訪問此資源的最大線程數(shù)目,互斥量是信號量的一種特殊情況,當(dāng)信號量的最大資源數(shù)=1就是互斥量了

          信號量(Semaphore)是一個整型變量,可以對其執(zhí)行 down 和 up 操作,也就是常見的 P 和 V 操作

          • 「down」 : 如果信號量大于 0 ,執(zhí)行 -1 操作;如果信號量等于 0,進(jìn)程睡眠,等待信號量大于 0;
          • 「up」 :對信號量執(zhí)行 +1 操作,喚醒睡眠的進(jìn)程讓其完成 down 操作。

          down 和 up 操作需要被設(shè)計成原語,不可分割,通常的做法是在執(zhí)行這些操作的時候屏蔽中斷。

          如果信號量的取值只能為 0 或者 1,那么就成為了 「互斥量(Mutex)」 ,0 表示臨界區(qū)已經(jīng)加鎖,1 表示臨界區(qū)解鎖。

          「事件」

          用來通知線程有一些事件已發(fā)生,從而啟動后繼任務(wù)的開始

          優(yōu)點:事件對象通過通知操作的方式來保持線程的同步,并且可以實現(xiàn)不同進(jìn)程中的線程同步操作

          「管程」

          管程有一個重要特性:在一個時刻只能有一個進(jìn)程使用管程。

          進(jìn)程在無法繼續(xù)執(zhí)行的時候不能一直占用管程,否則其它進(jìn)程永遠(yuǎn)不能使用管程。

          管程引入了 「條件變量」 以及相關(guān)的操作:「wait()」「signal()」 來實現(xiàn)同步操作。

          對條件變量執(zhí)行 wait() 操作會導(dǎo)致調(diào)用進(jìn)程阻塞,把管程讓出來給另一個進(jìn)程持有。

          signal() 操作用于喚醒被阻塞的進(jìn)程。

          使用信號量機(jī)制實現(xiàn)的生產(chǎn)者消費者問題需要客戶端代碼做很多控制,而管程把控制的代碼獨立出來,不僅不容易出錯,也使得客戶端代碼調(diào)用更容易。

          上下文切換

          對于單核單線程CPU而言,在某一時刻只能執(zhí)行一條CPU指令。

          上下文切換(Context Switch)是一種將CPU資源從一個進(jìn)程分配給另一個進(jìn)程的機(jī)制。

          從用戶角度看,計算機(jī)能夠并行運行多個進(jìn)程,這恰恰是操作系統(tǒng)通過快速上下文切換造成的結(jié)果。

          「在切換的過程中,操作系統(tǒng)需要先存儲當(dāng)前進(jìn)程的狀態(tài)(包括內(nèi)存空間的指針,當(dāng)前執(zhí)行完的指令等等),再讀入下一個進(jìn)程的狀態(tài),然后執(zhí)行此進(jìn)程。」

          進(jìn)程調(diào)度算法

          「先來先服務(wù)調(diào)度算法」

          該算法既可用于作業(yè)調(diào)度,也可用于進(jìn)程調(diào)度,當(dāng)在作業(yè)調(diào)度中采用該算法時,每次調(diào)度都是從后備作業(yè)隊列中選擇一個或多個最先進(jìn)入該隊列的作業(yè),將它們調(diào)入內(nèi)存,為它們分配資源、創(chuàng)建進(jìn)程,然后放入就緒隊列

          「短作業(yè)優(yōu)先調(diào)度算法」

          從后備隊列中選擇一個或若干個估計運行時間最短的作業(yè),將它們調(diào)入內(nèi)存運行

          「時間片輪轉(zhuǎn)法」

          每次調(diào)度時,把CPU分配給隊首進(jìn)程,并令其執(zhí)行一個時間片,時間片的大小從幾ms到幾百ms,當(dāng)執(zhí)行的時間片用完時,由一個計時器發(fā)出時鐘中斷請求,調(diào)度程序便據(jù)此信號來停止該進(jìn)程的執(zhí)行,并將它送往就緒隊列的末尾

          然后,再把處理機(jī)分配給就緒隊列中新的隊首進(jìn)程,同時也讓它執(zhí)行一個時間片,這樣就可以保證就緒隊列中的所有進(jìn)程在一給定的時間內(nèi)均能獲得一時間片的處理機(jī)執(zhí)行時間

          「最短剩余時間優(yōu)先」

          最短作業(yè)優(yōu)先的搶占式版本,按剩余運行時間的順序進(jìn)行調(diào)度,當(dāng)一個新的作業(yè)到達(dá)時,其整個運行時間與當(dāng)前進(jìn)程的剩余時間作比較。

          如果新的進(jìn)程需要的時間更少,則掛起當(dāng)前進(jìn)程,運行新的進(jìn)程。否則新的進(jìn)程等待。

          「多級反饋隊列調(diào)度算法」

          前面介紹的幾種進(jìn)程調(diào)度的算法都有一定的局限性,如「短進(jìn)程優(yōu)先的調(diào)度算法,僅照顧了短進(jìn)程而忽略了長進(jìn)程」,多級反饋隊列調(diào)度算法既能使高優(yōu)先級的作業(yè)得到響應(yīng)又能使短作業(yè)迅速完成,因而它是目前「被公認(rèn)的一種較好的進(jìn)程調(diào)度算法」,UNIX 操作系統(tǒng)采取的便是這種調(diào)度算法。

          ?

          舉例:

          ?

          多級隊列,就是多個隊列執(zhí)行調(diào)度,先考慮最簡單的兩級模型

          上圖中設(shè)計了兩個優(yōu)先級不同的隊列,從下到上優(yōu)先級上升,上層隊列調(diào)度緊急任務(wù),下層隊列調(diào)度普通任務(wù)。

          只要上層隊列有任務(wù),下層隊列就會讓出執(zhí)行權(quán)限。

          低優(yōu)先級隊列可以考慮搶占 + 優(yōu)先級隊列的方式實現(xiàn),這樣每次執(zhí)行一個時間片段就可以判斷一下高優(yōu)先級的隊列中是否有任務(wù)。

          高優(yōu)先級隊列可以考慮用非搶占(每個任務(wù)執(zhí)行完才執(zhí)行下一個)+ 優(yōu)先級隊列實現(xiàn),這樣緊急任務(wù)優(yōu)先級有個區(qū)分,如果遇到十萬火急的情況,就可以優(yōu)先處理這個任務(wù)。

          上面這個模型雖然解決了任務(wù)間的優(yōu)先級問題,但是還是沒有解決短任務(wù)先行的問題,可以考慮再增加一些隊列,讓級別更多。

          ?

          比如下圖這個模型:

          ?

          緊急任務(wù)仍然走高優(yōu)隊列,非搶占執(zhí)行。

          普通任務(wù)先放到優(yōu)先級僅次于高優(yōu)任務(wù)的隊列中,并且只分配很小的時間片;如果沒有執(zhí)行完成,說明任務(wù)不是很短,就將任務(wù)下調(diào)一層。

          下面一層,最低優(yōu)先級的隊列中時間片很大,長任務(wù)就有更大的時間片可以用。

          通過這種方式,短任務(wù)會在更高優(yōu)先級的隊列中執(zhí)行完成,長任務(wù)優(yōu)先級會下調(diào),也就類似實現(xiàn)了最短作業(yè)優(yōu)先的問題。

          實際操作中,可以有 n 層,一層層把大任務(wù)篩選出來,最長的任務(wù),放到最閑的時間去執(zhí)行,要知道,大部分時間 CPU 不是滿負(fù)荷的。

          「優(yōu)先級調(diào)度」

          為每個流程分配優(yōu)先級,首先執(zhí)行具有最高優(yōu)先級的進(jìn)程,依此類推,具有相同優(yōu)先級的進(jìn)程以 FCFS 方式執(zhí)行,可以根據(jù)內(nèi)存要求,時間要求或任何其他資源要求來確定優(yōu)先級。

          守護(hù)進(jìn)程

          守護(hù)進(jìn)程是脫離于終端并且在后臺運行的進(jìn)程,脫離終端是為了避免在執(zhí)行的過程中的信息在終端上顯示,并且進(jìn)程也不會被任何終端所產(chǎn)生的終端信息所打斷。

          守護(hù)進(jìn)程一般的生命周期是系統(tǒng)啟動到系統(tǒng)停止運行。

          Linux系統(tǒng)中有很多的守護(hù)進(jìn)程,最典型的就是我們經(jīng)常看到的服務(wù)進(jìn)程。

          當(dāng)然,我們也經(jīng)常會利用守護(hù)進(jìn)程來完成很多的系統(tǒng)或者自動化任務(wù)。

          孤兒進(jìn)程

          父進(jìn)程早于子進(jìn)程退出時候子進(jìn)程還在運行,子進(jìn)程會成為孤兒進(jìn)程,Linux會對孤兒進(jìn)程的處理,把孤兒進(jìn)程的父進(jìn)程設(shè)為進(jìn)程號為1的進(jìn)程,也就是由init進(jìn)程來托管,init進(jìn)程負(fù)責(zé)子進(jìn)程退出后的善后清理工作

          僵尸進(jìn)程

          子進(jìn)程執(zhí)行完畢時發(fā)現(xiàn)父進(jìn)程未退出,會向父進(jìn)程發(fā)送 SIGCHLD 信號,但父進(jìn)程沒有使用 wait/waitpid 或其他方式處理 SIGCHLD 信號來回收子進(jìn)程,子進(jìn)程變成為了對系統(tǒng)有害的僵尸進(jìn)程

          子進(jìn)程退出后留下的進(jìn)程信息沒有被收集,會導(dǎo)致占用的進(jìn)程控制塊PCB不被釋放,形成僵尸進(jìn)程,進(jìn)程已經(jīng)死去,但是進(jìn)程資源沒有被釋放掉

          「問題及危害」

          如果系統(tǒng)中存在大量的僵尸進(jìn)程,他們的進(jìn)程號就會一直被占用,但是系統(tǒng)所能使用的進(jìn)程號是有限的,系統(tǒng)將因為沒有可用的進(jìn)程號而導(dǎo)致系統(tǒng)不能產(chǎn)生新的進(jìn)程

          任何一個子進(jìn)程(init除外)在exit()之后,并非馬上就消失掉,而是留下一個稱為僵尸進(jìn)程(Zombie)的數(shù)據(jù)結(jié)構(gòu),等待父進(jìn)程處理,這是每個子進(jìn)程在結(jié)束時都要經(jīng)過的階段,如果子進(jìn)程在exit()之后,父進(jìn)程沒有來得及處理,這時用ps命令就能看到子進(jìn)程的狀態(tài)是Z。

          如果父進(jìn)程能及時處理,可能用ps命令就來不及看到子進(jìn)程的僵尸狀態(tài),但這并不等于子進(jìn)程不經(jīng)過僵尸狀態(tài)

          產(chǎn)生僵尸進(jìn)程的元兇其實是他們的父進(jìn)程,殺掉父進(jìn)程,僵尸進(jìn)程就變?yōu)榱斯聝哼M(jìn)程,便可以轉(zhuǎn)交給 init 進(jìn)程回收處理

          死鎖

          「產(chǎn)生原因」

          系統(tǒng)資源的競爭:系統(tǒng)資源的競爭導(dǎo)致系統(tǒng)資源不足,以及資源分配不當(dāng),導(dǎo)致死鎖。

          進(jìn)程運行推進(jìn)順序不合適:進(jìn)程在運行過程中,請求和釋放資源的順序不當(dāng),會導(dǎo)致死鎖。

          「發(fā)生死鎖的四個必要條件」

          互斥條件:一個資源每次只能被一個進(jìn)程使用,即在一段時間內(nèi)某資源僅為一個進(jìn)程所占有,此時若有其他進(jìn)程請求該資源,則請求進(jìn)程只能等待

          請求與保持條件:進(jìn)程已經(jīng)保持了至少一個資源,但又提出了新的資源請求時,該資源已被其他進(jìn)程占有,此時請求進(jìn)程被阻塞,但對自己已獲得的資源保持不放

          不可剝奪條件:進(jìn)程所獲得的資源在未使用完畢之前,不能被其他進(jìn)程強(qiáng)行奪走,即只能由獲得該資源的進(jìn)程自己來釋放(只能是主動釋放)

          循環(huán)等待條件: 若干進(jìn)程間形成首尾相接循環(huán)等待資源的關(guān)系

          這四個條件是死鎖的必要條件,只要系統(tǒng)發(fā)生死鎖,這些條件必然成立,而只要上述條件之一不滿足,就不會發(fā)生死鎖

          「只要我們破壞其中一個,就可以成功避免死鎖的發(fā)生」

          其中,互斥這個條件我們沒有辦法破壞,因為我們用鎖為的就是互斥

          1. 對于占用且等待這個條件,我們可以一次性申請所有的資源,這樣就不存在等待了。
          2. 對于不可搶占這個條件,占用部分資源的線程進(jìn)一步申請其他資源時,如果申請不到,可以主動釋放它占有的資源,這樣不可搶占這個條件就破壞掉了。
          3. 對于循環(huán)等待這個條件,可以靠按序申請資源來預(yù)防,所謂按序申請,是指資源是有線性順序的,申請的時候可以先申請資源序號小的,再申請資源序號大的,這樣線性化后自然就不存在循環(huán)了。

          「處理方法」

          主要有以下四種方法:

          • 鴕鳥策略
          • 死鎖檢測與死鎖恢復(fù)
          • 死鎖預(yù)防,破壞4個必要條件
          • 死鎖避免,銀行家算法

          「鴕鳥策略」

          把頭埋在沙子里,假裝根本沒發(fā)生問題。

          因為解決死鎖問題的代價很高,因此鴕鳥策略這種不采取任務(wù)措施的方案會獲得更高的性能。

          當(dāng)發(fā)生死鎖時不會對用戶造成多大影響,或發(fā)生死鎖的概率很低,可以采用鴕鳥策略。

          「死鎖檢測」

          不試圖阻止死鎖,而是當(dāng)檢測到死鎖發(fā)生時,采取措施進(jìn)行恢復(fù)。

          1. 每種類型一個資源的死鎖檢測

          2. 每種類型多個資源的死鎖檢測

          「死鎖恢復(fù)」

          • 利用搶占恢復(fù)
          • 利用回滾恢復(fù)
          • 通過殺死進(jìn)程恢復(fù)

          哲學(xué)家進(jìn)餐問題

          五個哲學(xué)家圍著一張圓桌,每個哲學(xué)家面前放著食物。

          哲學(xué)家的生活有兩種交替活動:吃飯以及思考。

          當(dāng)一個哲學(xué)家吃飯時,需要先拿起自己左右兩邊的兩根筷子,并且一次只能拿起一根筷子。

          如果所有哲學(xué)家同時拿起左手邊的筷子,那么所有哲學(xué)家都在等待其它哲學(xué)家吃完并釋放自己手中的筷子,導(dǎo)致死鎖。

          哲學(xué)家進(jìn)餐問題可看作是并發(fā)進(jìn)程并發(fā)執(zhí)行時處理共享資源的一個有代表性的問題。

          「為了防止死鎖的發(fā)生,可以設(shè)置兩個條件:」

          • 必須同時拿起左右兩根筷子;
          • 只有在兩個鄰居都沒有進(jìn)餐的情況下才允許進(jìn)餐。

          銀行家算法

          銀行家算法的命名是它可以用了銀行系統(tǒng),當(dāng)不能滿足所有客戶的需求時,銀行絕不會分配其資金。

          當(dāng)新進(jìn)程進(jìn)入系統(tǒng)時,它必須說明其可能需要的每種類型資源實例的最大數(shù)量這一數(shù)量不可以超過系統(tǒng)資源的總和。

          當(dāng)用戶申請一組資源時,系統(tǒng)必須確定這些資源的分配是否處于安全狀態(tài),如何安全,則分配,如果不安全,那么進(jìn)程必須等待指導(dǎo)某個其他進(jìn)程釋放足夠資源為止。

          「安全狀態(tài)」

          在避免死鎖的方法中,允許進(jìn)程動態(tài)地申請資源,但系統(tǒng)在進(jìn)行資源分配之前,應(yīng)先計算此次資源分配的安全性,若此次分配不會導(dǎo)致系統(tǒng)進(jìn)入不安全狀態(tài),則將資源分配給進(jìn)程;否則,令進(jìn)程等待

          因此,避免死鎖的實質(zhì)在于:系統(tǒng)在進(jìn)行資源分配時,如何使系統(tǒng)不進(jìn)入不安全狀態(tài)

          Fork函數(shù)

          fork函數(shù)用于創(chuàng)建一個與當(dāng)前進(jìn)程一樣的子進(jìn)程,所創(chuàng)建的子進(jìn)程將復(fù)制父進(jìn)程的代碼段、數(shù)據(jù)段、BSS段、堆、棧等所有用戶空間信息,在內(nèi)核中操作系統(tǒng)會重新為其申請一個子進(jìn)程執(zhí)行的位置。

          fork系統(tǒng)調(diào)用會通過復(fù)制一個現(xiàn)有進(jìn)程來創(chuàng)建一個全新的進(jìn)程,新進(jìn)程被存放在一個叫做任務(wù)隊列的雙向循環(huán)鏈表中,鏈表中的每一項都是類型為task_struct的進(jìn)程控制塊PCB的結(jié)構(gòu)。

          每個進(jìn)程都由獨特?fù)Q不相同的進(jìn)程標(biāo)識符(PID),通過getpid()函數(shù)可獲取當(dāng)前進(jìn)程的進(jìn)程標(biāo)識符,通過getppid()函數(shù)可獲得父進(jìn)程的進(jìn)程標(biāo)識符。

          一個現(xiàn)有的進(jìn)程可通過調(diào)用fork函數(shù)創(chuàng)建一個新進(jìn)程,由fork創(chuàng)建的新進(jìn)程稱為子進(jìn)程child processfork函數(shù)被調(diào)用一次但返回兩次,兩次返回的唯一區(qū)別是子進(jìn)程中返回0而父進(jìn)程中返回子進(jìn)程ID。

          「為什么fork會返回兩次呢?」

          因為復(fù)制時會復(fù)制父進(jìn)程的堆棧段,所以兩個進(jìn)程都停留在fork函數(shù)中等待返回,因此會返回兩次,一個是在父進(jìn)程中返回,一次是在子進(jìn)程中返回,兩次返回值是不一樣的。

          • 在父進(jìn)程中將返回新建子進(jìn)程的進(jìn)程ID
          • 在子進(jìn)程中將返回0
          • 若出現(xiàn)錯誤則返回一個負(fù)數(shù)

          因此可以通過fork的返回值來判斷當(dāng)前進(jìn)程是子進(jìn)程還是父進(jìn)程。

          「fork執(zhí)行執(zhí)行流程」

          當(dāng)進(jìn)程調(diào)用fork后控制轉(zhuǎn)入內(nèi)核,內(nèi)核將會做4件事兒:

          1. 分配新的內(nèi)存塊和內(nèi)核數(shù)據(jù)結(jié)構(gòu)給子進(jìn)程
          2. 將父進(jìn)程部分?jǐn)?shù)據(jù)結(jié)構(gòu)內(nèi)容(數(shù)據(jù)空間、堆棧等)拷貝到子進(jìn)程
          3. 添加子進(jìn)程到系統(tǒng)進(jìn)程列表中
          4. fork返回開始調(diào)度器調(diào)度

          「為什么pid在父子進(jìn)程中不同呢?」

          其實就相當(dāng)于鏈表,進(jìn)程形成了鏈表,父進(jìn)程的pid指向子進(jìn)程的進(jìn)程ID,因此子進(jìn)程沒有子進(jìn)程,所以PID為0,這里的pid相當(dāng)于鏈表中的指針。

          設(shè)備管理

          磁盤調(diào)度算法

          讀寫一個磁盤塊的時間的影響因素有:

          • 旋轉(zhuǎn)時間
          • 尋道時間實際的數(shù)據(jù)傳輸時間

          其中,尋道時間最長,因此磁盤調(diào)度的主要目標(biāo)是使磁盤的平均尋道時間最短。

          ?

          先來先服務(wù) FCFS, First Come First Served

          ?

          按照磁盤請求的順序進(jìn)行調(diào)度,優(yōu)點是公平和簡單,缺點也很明顯,因為未對尋道做任何優(yōu)化,使平均尋道時間可能較長。

          ?

          最短尋道時間優(yōu)先,SSTF, Shortest Seek Time First

          ?

          優(yōu)先調(diào)度與當(dāng)前磁頭所在磁道距離最近的磁道, 雖然平均尋道時間比較低,但是不夠公平,如果新到達(dá)的磁道請求總是比一個在等待的磁道請求近,那么在等待的 磁道請求會一直等待下去,也就是出現(xiàn)饑餓現(xiàn)象,具體來說,兩邊的磁道請求更容易出現(xiàn)饑餓現(xiàn)象。

          ?

          電梯算法,SCAN

          ?

          電梯總是保持一個方向運行,直到該方向沒有請求為止,然后改變運行方向, 電梯算法(掃描算法)和電梯的運行過程類似,總是按一個方向來進(jìn)行磁盤調(diào)度,直到該方向上沒有未完成的磁盤 請求,然后改變方向,因為考慮了移動方向,因此所有的磁盤請求都會被滿足,解決了 SSTF 的饑餓問題

          內(nèi)存管理

          「邏輯地址和物理地址」

          我們編程一般只有可能和邏輯地址打交道,比如在 C 語言中,指針里面存儲的數(shù)值就可以理解成為內(nèi)存里的一個地址,這個地址也就是我們說的邏輯地址,邏輯地址由操作系統(tǒng)決定。

          物理地址指的是真實物理內(nèi)存中地址,更具體一點來說就是內(nèi)存地址寄存器中的地址,物理地址是內(nèi)存單元真正的地址。

          編譯時只需確定變量x存放的相對地址是100 ( 也就是說相對于進(jìn)程在內(nèi)存中的起始地址而言的地址)。

          CPU想要找到x在內(nèi)存中的實際存放位置,只需要用進(jìn)程的起始地址+100即可。

          相對地址又稱邏輯地址,絕對地址又稱物理地址。

          「內(nèi)存管理有哪幾種方式」

          1. 「塊式管理」:將內(nèi)存分為幾個固定大小的塊,每個塊中只包含一個進(jìn)程,如果程序運行需要內(nèi)存的話,操作系統(tǒng)就分配給它一塊,如果程序運行只需要很小的空間的話,分配的這塊內(nèi)存很大一部分幾乎被浪費了,這些在每個塊中未被利用的空間,我們稱之為碎片。
          2. 「頁式管理」:把主存分為大小相等且固定的一頁一頁的形式,頁較小,相對相比于塊式管理的劃分力度更大,提高了內(nèi)存利用率,減少了碎片,頁式管理通過頁表對應(yīng)邏輯地址和物理地址。
          1. 「段式管理」: 頁式管理雖然提高了內(nèi)存利用率,但是頁式管理其中的頁實際并無任何實際意義, 段式管理把主存分為一段段的,每一段的空間又要比一頁的空間小很多 ,段式管理通過段表對應(yīng)邏輯地址和物理地址。
          2. 「段頁式管理機(jī)制:「段頁式管理機(jī)制結(jié)合了段式管理和頁式管理的優(yōu)點,簡單來說段頁式管理機(jī)制就是把主存先分成若干段,每個段又分成若干頁,也就是說」段頁式管理機(jī)制」中段與段之間以及段的內(nèi)部的都是離散的。

          虛擬地址

          現(xiàn)代處理器使用的是一種稱為**虛擬尋址(Virtual Addressing)**的尋址方式

          「使用虛擬尋址,CPU 需要將虛擬地址翻譯成物理地址,這樣才能訪問到真實的物理內(nèi)存。」

          實際上完成虛擬地址轉(zhuǎn)換為物理地址轉(zhuǎn)換的硬件是 CPU 中含有一個被稱為**內(nèi)存管理單元(Memory Management Unit, MMU)**的硬件

          「為什么要有虛擬地址空間」

          沒有虛擬地址空間的時候,「程序都是直接訪問和操作的都是物理內(nèi)存」

          但是這樣有什么問題?

          1. 用戶程序可以訪問任意內(nèi)存,尋址內(nèi)存的每個字節(jié),這樣就很容易破壞操作系統(tǒng),造成操作系統(tǒng)崩潰。
          2. 想要同時運行多個程序特別困難,比如你想同時運行一個微信和一個 QQ 音樂都不行,為什么呢?舉個簡單的例子:微信在運行的時候給內(nèi)存地址 1xxx 賦值后,QQ 音樂也同樣給內(nèi)存地址 1xxx 賦值,那么 QQ 音樂對內(nèi)存的賦值就會覆蓋微信之前所賦的值,這就造成了微信這個程序就會崩潰。

          「通過虛擬地址訪問內(nèi)存有以下優(yōu)勢:」

          • 程序可以使用一系列相鄰的虛擬地址來訪問物理內(nèi)存中不相鄰的大內(nèi)存緩沖區(qū)。
          • 程序可以使用一系列虛擬地址來訪問大于可用物理內(nèi)存的內(nèi)存緩沖區(qū)。
          • 不同進(jìn)程使用的虛擬地址彼此隔離,一個進(jìn)程中的代碼無法更改正在由另一進(jìn)程或操作系統(tǒng)使用的物理內(nèi)存。

          「MMU如何把虛擬地址翻譯成物理地址的」

          對于每個程序,內(nèi)存管理單元MMU都為其保存一個頁表,該頁表中存放的是虛擬頁面到物理頁面的映射。

          每當(dāng)為一個虛擬頁面尋找到一個物理頁面之后,就在頁表里增加一條記錄來保留該映射關(guān)系,當(dāng)然,隨著虛擬頁面進(jìn)出物理內(nèi)存,頁表的內(nèi)容也會不斷更新變化。

          虛擬內(nèi)存

          很多時候我們使用點開了很多占內(nèi)存的軟件,這些軟件占用的內(nèi)存可能已經(jīng)遠(yuǎn)遠(yuǎn)超出了我們電腦本身具有的物理內(nèi)存

          通過 「虛擬內(nèi)存」 可以讓程序可以擁有超過系統(tǒng)物理內(nèi)存大小的可用內(nèi)存空間。

          另外,虛擬內(nèi)存為每個進(jìn)程提供了一個一致的、私有的地址空間,它讓每個進(jìn)程產(chǎn)生了一種自己在獨享主存的錯覺(每個進(jìn)程擁有一片連續(xù)完整的內(nèi)存空間),這樣會更加有效地管理內(nèi)存并減少出錯。

          「虛擬內(nèi)存」是計算機(jī)系統(tǒng)內(nèi)存管理的一種技術(shù),我們可以手動設(shè)置自己電腦的虛擬內(nèi)存

          「虛擬內(nèi)存的重要意義是它定義了一個連續(xù)的虛擬地址空間」,并且 「把內(nèi)存擴(kuò)展到硬盤空間」

          「虛擬內(nèi)存的實現(xiàn)有以下三種方式:」

          1. 「請求分頁存儲管理」 :請求分頁是目前最常用的一種實現(xiàn)虛擬存儲器的方法,請求分頁存儲管理系統(tǒng)中,在作業(yè)開始運行之前,僅裝入當(dāng)前要執(zhí)行的部分段即可運行,假如在作業(yè)運行的過程中發(fā)現(xiàn)要訪問的頁面不在內(nèi)存,則由處理器通知操作系統(tǒng)按照對應(yīng)的頁面置換算法將相應(yīng)的頁面調(diào)入到主存,同時操作系統(tǒng)也可以將暫時不用的頁面置換到外存中。
          2. 「請求分段存儲管理」 :請求分段儲存管理方式就如同請求分頁儲存管理方式一樣,在作業(yè)開始運行之前,僅裝入當(dāng)前要執(zhí)行的部分段即可運行;在執(zhí)行過程中,可使用請求調(diào)入中斷動態(tài)裝入要訪問但又不在內(nèi)存的程序段;當(dāng)內(nèi)存空間已滿,而又需要裝入新的段時,根據(jù)置換功能適當(dāng)調(diào)出某個段,以便騰出空間而裝入新的段。
          3. 「請求段頁式存儲管理」

          不管是上面那種實現(xiàn)方式,我們一般都需要:

          ?

          一定容量的內(nèi)存和外存:在載入程序的時候,只需要將程序的一部分裝入內(nèi)存,而將其他部分留在外存,然后程序就可以執(zhí)行了;

          ?

          缺頁中斷

          如果「需執(zhí)行的指令或訪問的數(shù)據(jù)尚未在內(nèi)存」(稱為缺頁或缺段),則由處理器通知操作系統(tǒng)將相應(yīng)的頁面或段「調(diào)入到內(nèi)存」,然后繼續(xù)執(zhí)行程序;

          在分頁系統(tǒng)中,一個虛擬頁面既有可能在物理內(nèi)存,也有可能保存在磁盤上。

          如果CPU發(fā)出的虛擬地址對應(yīng)的頁面不在物理內(nèi)存,就將產(chǎn)生一個缺頁中斷,而缺頁中斷服務(wù)程序負(fù)責(zé)將需要的虛擬頁面找到并加載到內(nèi)存。

          缺頁中斷的處理步驟如下,省略了中間很多的步驟,只保留最核心的幾個步驟:

          頁面置換算法

          當(dāng)發(fā)生缺頁中斷時,如果當(dāng)前內(nèi)存中并沒有空閑的頁面,操作系統(tǒng)就必須在內(nèi)存選擇一個頁面將其移出內(nèi)存,以便為即將調(diào)入的頁面讓出空間。

          用來選擇淘汰哪一頁的規(guī)則叫做頁面置換算法,我們可以把頁面置換算法看成是淘汰頁面的規(guī)則

          • 「OPT 頁面置換算法(最佳頁面置換算法)」 :該置換算法所選擇的被淘汰頁面將是以后永不使用的,或者是在最長時間內(nèi)不再被訪問的頁面,這樣可以保證獲得最低的缺頁率,但由于人們目前無法預(yù)知進(jìn)程在內(nèi)存下的若千頁面中哪個是未來最長時間內(nèi)不再被訪問的,因而該算法無法實現(xiàn),一般作為衡量其他置換算法的方法。

          • 「FIFO(First In First Out) 頁面置換算法(先進(jìn)先出頁面置換算法)」 : 總是淘汰最先進(jìn)入內(nèi)存的頁面,即選擇在內(nèi)存中駐留時間最久的頁面進(jìn)行淘汰。

          • 「LRU (Least Currently Used)頁面置換算法(最近最久未使用頁面置換算法)」 :LRU算法賦予每個頁面一個訪問字段,用來記錄一個頁面自上次被訪問以來所經(jīng)歷的時間 T,當(dāng)須淘汰一個頁面時,選擇現(xiàn)有頁面中其 T 值最大的,即最近最久未使用的頁面予以淘汰。

          • 「LFU (Least Frequently Used)頁面置換算法(最少使用頁面置換算法)」 : 該置換算法選擇在之前時期使用最少的頁面作為淘汰頁。

          局部性原理

          局部性原理是虛擬內(nèi)存技術(shù)的基礎(chǔ),正是因為程序運行具有局部性原理,才可以只裝入部分程序到內(nèi)存就開始運行。

          局部性原理表現(xiàn)在以下兩個方面:

          1. 「時間局部性」 :如果程序中的某條指令一旦執(zhí)行,不久以后該指令可能再次執(zhí)行;如果某數(shù)據(jù)被訪問過,不久以后該數(shù)據(jù)可能再次被訪問,產(chǎn)生時間局部性的典型原因,是由于在程序中存在著大量的循環(huán)操作。
          2. 「空間局部性」 :一旦程序訪問了某個存儲單元,在不久之后,其附近的存儲單元也將被訪問,即程序在一段時間內(nèi)所訪問的地址,可能集中在一定的范圍之內(nèi),這是因為指令通常是順序存放、順序執(zhí)行的,數(shù)據(jù)也一般是以向量、數(shù)組、表等形式簇聚存儲的。

          時間局部性是通過將近來使用的指令和數(shù)據(jù)保存到「高速緩存存儲器」中,并使用高速緩存的層次結(jié)構(gòu)實現(xiàn)。

          空間局部性通常是使用較大的高速緩存,并將預(yù)取機(jī)制集成到高速緩存控制邏輯中實現(xiàn)。

          頁表

          操作系統(tǒng)將虛擬內(nèi)存分塊,每個小塊稱為一個頁(Page);真實內(nèi)存也需要分塊,每個小塊我們稱為一個 Frame。

          Page 到 Frame 的映射,需要一種叫作頁表的結(jié)構(gòu)。

          上圖展示了 Page、Frame 和頁表 (PageTable)三者之間的關(guān)系。

          Page 大小和 Frame 大小通常相等,頁表中記錄的某個 Page 對應(yīng)的 Frame 編號。

          頁表也需要存儲空間,比如虛擬內(nèi)存大小為 10G, Page 大小是 4K,那么需要 10G/4K = 2621440 個條目。

          如果每個條目是 64bit,那么一共需要 20480K = 20M 頁表,操作系統(tǒng)在內(nèi)存中劃分出小塊區(qū)域給頁表,并負(fù)責(zé)維護(hù)頁表。

          「頁表維護(hù)了虛擬地址到真實地址的映射。」

          每次程序使用內(nèi)存時,需要把虛擬內(nèi)存地址換算成物理內(nèi)存地址,換算過程分為以下 3 個步驟:

          • 通過虛擬地址計算 Page 編號;

          • 查頁表,根據(jù) Page 編號,找到 Frame 編號;

          • 將虛擬地址換算成物理地址。

          多級頁表

          引入多級頁表的主要目的是為了避免把全部頁表一直放在內(nèi)存中占用過多空間,特別是那些根本就不需要的頁表就不需要保留在內(nèi)存中

          「一級頁表:」

          假如物理內(nèi)存中一共有1048576個頁,那么頁表就需要總共就是1048576 * 4B = 4M

          也就是說我需要4M連續(xù)的內(nèi)存來存放這個頁表,也就是一級頁表。

          隨著虛擬地址空間的增大,存放頁表所需要的連續(xù)空間也會增大,在操作系統(tǒng)內(nèi)存緊張或者內(nèi)存碎片較多時,這無疑會帶來額外的開銷。

          頁表尋址是用寄存器來確定一級頁表地址的,所以一級頁表的地址必須指向確定的物理頁,否則就會出現(xiàn)錯誤,所以如果用一級頁表的話,就必須把全部的頁表都加載進(jìn)去。

          「二級頁表:」

          而使用二級頁表的話,只需要加載一個頁目錄表(一級頁表),大小為4K,可以管理1024個二級頁表。

          可能你會有疑問,這1024個二級頁表也是需要內(nèi)存空間的,這下反而需要4MB+4KB的內(nèi)存,反而更多了。

          其實二級頁表并不是一定要存在內(nèi)存中的,內(nèi)存中只需要一個一級頁表地址存在存器即可,二級頁表可以使用缺頁中斷從外存移入內(nèi)存。

          「多級頁表屬于時間換空間的典型場景」

          快表

          為了解決虛擬地址到物理地址的轉(zhuǎn)換速度,操作系統(tǒng)在「頁表方案」基礎(chǔ)之上引入了「快表」來加速虛擬地址到物理地址的轉(zhuǎn)換

          我們可以把快表理解為一種特殊的「高速緩沖存儲器(Cache)」,其中的內(nèi)容是頁表的一部分或者全部內(nèi)容,作為頁表的 Cache,它的作用與頁表相似,但是提高了訪問速率,由于采用頁表做地址轉(zhuǎn)換,讀寫內(nèi)存數(shù)據(jù)時 CPU 要訪問兩次主存,有了快表,有時只要訪問一次高速緩沖存儲器,一次主存,這樣可加速查找并提高指令執(zhí)行速度。

          「使用快表之后的地址轉(zhuǎn)換流程是這樣的:」

          1. 根據(jù)虛擬地址中的頁號查快表;
          2. 如果該頁在快表中,直接從快表中讀取相應(yīng)的物理地址;
          3. 如果該頁不在快表中,就訪問內(nèi)存中的頁表,再從頁表中得到物理地址,同時將頁表中的該映射表項添加到快表中;
          4. 當(dāng)快表填滿后,又要登記新頁時,就按照一定的淘汰策略淘汰掉快表中的一個頁。

          內(nèi)存管理單元

          在 CPU 中一個小型的設(shè)備——內(nèi)存管理單元(MMU)

          當(dāng) CPU 需要執(zhí)行一條指令時,如果指令中涉及內(nèi)存讀寫操作,CPU 會把虛擬地址給 MMU,MMU 自動完成虛擬地址到真實地址的計算;然后,MMU 連接了地址總線,幫助 CPU 操作真實地址。

          在不同 CPU 的 MMU 可能是不同的,因此這里會遇到很多跨平臺的問題。

          解決跨平臺問題不但有繁重的工作量,更需要高超的編程技巧。

          動態(tài)分區(qū)分配算法

          內(nèi)存分配算法,大體來說分為:「連續(xù)式分配 與 非連續(xù)式分配」

          連續(xù)式分配就是把所以要執(zhí)行的程序 「完整的,有序的」 存入內(nèi)存,連續(xù)式分配又可以分為「固定分區(qū)分配 和 動態(tài)分區(qū)分配」

          非連續(xù)式分配就是把要執(zhí)行的程序按照一定規(guī)則進(jìn)行拆分,顯然這樣更有效率,現(xiàn)在的操作系統(tǒng)通常也都是采用這種方式分配內(nèi)存

          所謂動態(tài)分區(qū)分配,就是指「內(nèi)存在初始時不會劃分區(qū)域,而是會在進(jìn)程裝入時,根據(jù)所要裝入的進(jìn)程大小動態(tài)地對內(nèi)存空間進(jìn)行劃分,以提高內(nèi)存空間利用率,降低碎片的大小」

          動態(tài)分區(qū)分配算法有以下四種:

          ?

          首次適應(yīng)算法(First Fit)

          ?

          空閑分區(qū)以地址遞增的次序鏈接,分配內(nèi)存時順序查找,找到大小滿足要求的第一個空閑分區(qū)就進(jìn)行分配

          ?

          鄰近適應(yīng)算法(Next Fit)

          ?

          又稱循環(huán)首次適應(yīng)法,由首次適應(yīng)法演變而成,不同之處是分配內(nèi)存時從上一次查找結(jié)束的位置開始繼續(xù)查找

          ?

          最佳適應(yīng)算法(Best Fit)

          ?

          空閑分區(qū)按容量遞增形成分區(qū)鏈,找到第一個能滿足要求的空閑分區(qū)就進(jìn)行分配

          ?

          最壞適應(yīng)算法(Next Fit)

          ?

          又稱最大適應(yīng)算法,空閑分區(qū)以容量遞減的次序鏈接,找到第一個能滿足要求的空閑分區(qū)(也就是最大的分區(qū))就進(jìn)行分配

          「總結(jié)」

          首次適應(yīng)不僅最簡單,通常也是最好最快,不過首次適應(yīng)算法會使得內(nèi)存低地址部分出現(xiàn)很多小的空閑分區(qū),而每次查找都要經(jīng)過這些分區(qū),因此也增加了查找的開銷。

          鄰近算法試圖解決這個問題,但實際上,它常常會導(dǎo)致在內(nèi)存的末尾分配空間分裂成小的碎片,它通常比首次適應(yīng)算法結(jié)果要差。

          最佳適應(yīng)算法導(dǎo)致大量碎片,最壞適應(yīng)算法導(dǎo)致沒有大的空間。

          內(nèi)存覆蓋

          覆蓋與交換技術(shù)是在程序用來擴(kuò)充內(nèi)存的兩種方法。

          早期的計算機(jī)系統(tǒng)中,主存容量很小,雖然主存中僅存放一道用戶程序,但是存儲空間放不下用戶進(jìn)程的現(xiàn)象也經(jīng)常發(fā)生,這一矛盾可以用覆蓋技術(shù)來解決。

          「覆蓋的基本思想是:」

          由于程序運行時并非任何時候都要訪問程序及數(shù)據(jù)的各個部分(尤其是大程序),因此可以把用戶空間分成一個固定區(qū)和若干個覆蓋區(qū)。

          將經(jīng)常活躍的部分放在固定區(qū),其余部分按調(diào)用關(guān)系分段。

          首先將那些即將要訪問的段放入覆蓋區(qū),其他段放在外存中,在需要調(diào)用前,系統(tǒng)再將其調(diào)入覆蓋區(qū),替換覆蓋區(qū)中原有的段。

          覆蓋技術(shù)的特點是打破了必須將一個進(jìn)程的全部信息裝入主存后才能運行的限制,但當(dāng)同時運行程序的代碼量大于主存時仍不能運行。

          內(nèi)存交換

          「交換的基本思想」

          把處于等待狀態(tài)(或在CPU調(diào)度原則下被剝奪運行權(quán)利)的程序從內(nèi)存移到輔存,把內(nèi)存空間騰出來,這一過程又叫換出;

          把準(zhǔn)備好競爭CPU運行的程序從輔存移到內(nèi)存,這一過程又稱為換入。

          ?

          例如,有一個CPU釆用時間片輪轉(zhuǎn)調(diào)度算法的多道程序環(huán)境。

          ?

          時間片到,內(nèi)存管理器將剛剛執(zhí)行過的進(jìn)程換出,將另一進(jìn)程換入到剛剛釋放的內(nèi)存空間中。

          同時,CPU調(diào)度器可以將時間片分配給其他已在內(nèi)存中的進(jìn)程。

          每個進(jìn)程用完時間片都與另一進(jìn)程交換。

          理想情況下,內(nèi)存管理器的交換過程速度足夠快,總有進(jìn)程在內(nèi)存中可以執(zhí)行。

          ?

          交換技術(shù)主要是在不同進(jìn)程(或作業(yè))之間進(jìn)行,而覆蓋則用于同一個程序或進(jìn)程中。

          ?

          由于覆蓋技術(shù)要求給出程序段之間的覆蓋結(jié)構(gòu),使得其對用戶和程序員不透明,所以對于主存無法存放用戶程序的矛盾

          現(xiàn)代操作系統(tǒng)是通過虛擬內(nèi)存技術(shù)來解決的,覆蓋技術(shù)則已成為歷史;而交換技術(shù)在現(xiàn)代操作系統(tǒng)中仍具有較強(qiáng)的生命力。

          常見面試題

          「進(jìn)程、線程的區(qū)別」

          操作系統(tǒng)會以進(jìn)程為單位,分配系統(tǒng)資源(CPU時間片、內(nèi)存等資源),進(jìn)程是資源分配的最小單位。

          調(diào)度:線程作為CPU調(diào)度和分配的基本單位,進(jìn)程作為擁有資源的基本單位;

          并發(fā)性:不僅進(jìn)程之間可以并發(fā)執(zhí)行,同一個進(jìn)程的多個線程之間也可并發(fā)執(zhí)行;

          ?

          擁有資源:

          ?

          進(jìn)程是擁有資源的一個獨立單位,線程不擁有系統(tǒng)資源,但可以訪問隸屬于進(jìn)程的資源。

          進(jìn)程所維護(hù)的是程序所包含的資源(靜態(tài)資源), 如:地址空間,打開的文件句柄集,文件系統(tǒng)狀態(tài),信號處理handler等;

          線程所維護(hù)的運行相關(guān)的資源(動態(tài)資源),如:運行棧,調(diào)度相關(guān)的控制信息,待處理的信號集等;

          ?

          系統(tǒng)開銷:

          ?

          在創(chuàng)建或撤消進(jìn)程時,由于系統(tǒng)都要為之分配和回收資源,導(dǎo)致系統(tǒng)的開銷明顯大于創(chuàng)建或撤消線程時的開銷。

          但是進(jìn)程有獨立的地址空間,一個進(jìn)程崩潰后,在保護(hù)模式下不會對其它進(jìn)程產(chǎn)生影響,而線程只是一個進(jìn)程中的不同執(zhí)行路徑。

          線程有自己的堆棧和局部變量,但線程之間沒有單獨的地址空間,一個進(jìn)程死掉就等于所有的線程死掉,所以多進(jìn)程的程序要比多線程的程序健壯,但在進(jìn)程切換時,耗費資源較大,效率要差一些。

          「一個進(jìn)程可以創(chuàng)建多少線程」

          理論上,一個進(jìn)程可用虛擬空間是2G,默認(rèn)情況下,線程的棧的大小是1MB,所以理論上最多只能創(chuàng)建2048個線程。

          如果要創(chuàng)建多于2048的話,必須修改編譯器的設(shè)置。

          在一般情況下,你不需要那么多的線程,過多的線程將會導(dǎo)致大量的時間浪費在線程切換上,給程序運行效率帶來負(fù)面影響。

          「外中斷和異常有什么區(qū)別」

          外中斷是指由 CPU 執(zhí)行指令以外的事件引起,如 I/O 完成中斷,表示設(shè)備輸入/輸出處理已經(jīng)完成,處理器能夠發(fā)送下一個輸入/輸出請求,此外還有時鐘中斷、控制臺中斷等。

          而異常時由 CPU 執(zhí)行指令的內(nèi)部事件引起,如非法操作碼、地址越界、算術(shù)溢出等。

          「解決Hash沖突四種方法」

          開放定址法

          • 開放定址法就是一旦發(fā)生了沖突,就去尋找下一個空的散列地址,只要散列表足夠大,空的散列地址總能找到,并將記錄存入。

          鏈地址法

          • 將哈希表的每個單元作為鏈表的頭結(jié)點,所有哈希地址為i的元素構(gòu)成一個同義詞鏈表。即發(fā)生沖突時就把該關(guān)鍵字鏈在以該單元為頭結(jié)點的鏈表的尾部。

          再哈希法

          • 當(dāng)哈希地址發(fā)生沖突用其他的函數(shù)計算另一個哈希函數(shù)地址,直到?jīng)_突不再產(chǎn)生為止。

          建立公共溢出區(qū)

          • 將哈希表分為基本表和溢出表兩部分,發(fā)生沖突的元素都放入溢出表中。

          「分頁機(jī)制和分段機(jī)制有哪些共同點和區(qū)別」

          共同點

          • 分頁機(jī)制和分段機(jī)制都是為了提高內(nèi)存利用率,較少內(nèi)存碎片。
          • 頁和段都是離散存儲的,所以兩者都是離散分配內(nèi)存的方式。但是,每個頁和段中的內(nèi)存是連續(xù)的。

          區(qū)別

          • 頁的大小是固定的,由操作系統(tǒng)決定;而段的大小不固定,取決于我們當(dāng)前運行的程序。
          • 分頁僅僅是為了滿足操作系統(tǒng)內(nèi)存管理的需求,而段是邏輯信息的單位,在程序中可以體現(xiàn)為代碼段,數(shù)據(jù)段,能夠更好滿足用戶的需要。
          • 分頁是一維地址空間,分段是二維的。

          「介紹一下幾種典型的鎖」

          ?

          讀寫鎖

          ?
          • 可以同時進(jìn)行多個讀
          • 寫者必須互斥(只允許一個寫者寫,也不能讀者寫者同時進(jìn)行)
          • 寫者優(yōu)先于讀者(一旦有寫者,則后續(xù)讀者必須等待,喚醒時優(yōu)先考慮寫者)
          ?

          互斥鎖

          ?

          一次只能一個線程擁有互斥鎖,其他線程只有等待

          互斥鎖是在搶鎖失敗的情況下主動放棄CPU進(jìn)入睡眠狀態(tài)直到鎖的狀態(tài)改變時再喚醒,而操作系統(tǒng)負(fù)責(zé)線程調(diào)度,為了實現(xiàn)鎖的狀態(tài)發(fā)生改變時喚醒阻塞的線程或者進(jìn)程,需要把鎖交給操作系統(tǒng)管理,所以互斥鎖在加鎖操作時涉及上下文的切換。

          互斥鎖實際的效率還是可以讓人接受的,加鎖的時間大概100ns左右,而實際上互斥鎖的一種可能的實現(xiàn)是先自旋一段時間,當(dāng)自旋的時間超過閥值之后再將線程投入睡眠中,因此在并發(fā)運算中使用互斥鎖(每次占用鎖的時間很短)的效果可能不亞于使用自旋鎖

          ?

          條件變量

          ?

          互斥鎖一個明顯的缺點是他只有兩種狀態(tài):鎖定和非鎖定。

          而條件變量通過允許線程阻塞和等待另一個線程發(fā)送信號的方法彌補了互斥鎖的不足,他常和互斥鎖一起使用,以免出現(xiàn)競態(tài)條件。

          當(dāng)條件不滿足時,線程往往解開相應(yīng)的互斥鎖并阻塞線程然后等待條件發(fā)生變化。

          一旦其他的某個線程改變了條件變量,他將通知相應(yīng)的條件變量喚醒一個或多個正被此條件變量阻塞的線程。

          總的來說「互斥鎖是線程間互斥的機(jī)制,條件變量則是同步機(jī)制。」

          ?

          自旋鎖

          ?

          如果進(jìn)線程無法取得鎖,進(jìn)線程不會立刻放棄CPU時間片,而是一直循環(huán)嘗試獲取鎖,直到獲取為止。

          如果別的線程長時期占有鎖,那么自旋就是在浪費CPU做無用功,但是自旋鎖一般應(yīng)用于加鎖時間很短的場景,這個時候效率比較高。

          雖然它的效率比互斥鎖高,但是它也有些不足之處:

          • 自旋鎖一直占用CPU,在未獲得鎖的情況下,一直進(jìn)行自旋,所以占用著CPU,如果不能在很短的時間內(nèi)獲得鎖,無疑會使CPU效率降低。
          • 在用自旋鎖時有可能造成死鎖,當(dāng)遞歸調(diào)用時有可能造成死鎖。

          「如何讓進(jìn)程后臺運行」

          1.命令后面加上&即可,實際上,這樣是將命令放入到一個作業(yè)隊列中了

          2.ctrl + z 掛起進(jìn)程,使用jobs查看序號,在使用bg %序號后臺運行進(jìn)程

          3.nohup + &,將標(biāo)準(zhǔn)輸出和標(biāo)準(zhǔn)錯誤缺省會被重定向到 nohup.out 文件中,忽略所有掛斷(SIGHUP)信號

          nohup ping www.ibm.com &

          4.運行指令前面 + setsid,使其父進(jìn)程變成init進(jìn)程,不受SIGHUP信號的影響

          [root@pvcent107 ~]# setsid ping www.ibm.com
          [root@pvcent107 ~]# ps -ef |grep www.ibm.com
          root     31094     1  0 07:28 ?        00:00:00 ping www.ibm.com
          root     31102 29217  0 07:29 pts/4    00:00:00 grep www.ibm.com

          上例中我們的進(jìn)程 ID(PID)為31094,而它的父 ID(PPID)為1(即為 init 進(jìn)程 ID),并不是當(dāng)前終端的進(jìn)程 ID。

          ?

          5.將命令+ &放在()括號中,也可以是進(jìn)程不受HUP信號的影響

          ?
          [root@pvcent107 ~]# (ping www.ibm.com &)

          「異常和中斷的區(qū)別」

          ?

          中斷

          ?

          當(dāng)我們在敲擊鍵盤的同時就會產(chǎn)生中斷,當(dāng)硬盤讀寫完數(shù)據(jù)之后也會產(chǎn)生中斷,所以,我們需要知道,中斷是由硬件設(shè)備產(chǎn)生的,而它們從物理上說就是電信號,之后,它們通過中斷控制器發(fā)送給CPU,接著CPU判斷收到的中斷來自于哪個硬件設(shè)備(這定義在內(nèi)核中),最后,由CPU發(fā)送給內(nèi)核,有內(nèi)核處理中斷。

          下面這張圖顯示了中斷處理的流程:

          ?

          異常

          ?

          CPU處理程序的時候一旦程序不在內(nèi)存中,會產(chǎn)生缺頁異常;當(dāng)運行除法程序時,當(dāng)除數(shù)為0時,又會產(chǎn)生除0異常。

          「異常是由CPU產(chǎn)生的,同時,它會發(fā)送給內(nèi)核,要求內(nèi)核處理這些異常」

          下面這張圖顯示了異常處理的流程:

          ?

          相同點

          ?
          • 最后都是由CPU發(fā)送給內(nèi)核,由內(nèi)核去處理
          • 處理程序的流程設(shè)計上是相似的
          ?

          不同點

          ?
          • 產(chǎn)生源不相同,異常是由CPU產(chǎn)生的,而中斷是由硬件設(shè)備產(chǎn)生的
          • 內(nèi)核需要根據(jù)是異常還是中斷調(diào)用不同的處理程序
          • 中斷不是時鐘同步的,這意味著中斷可能隨時到來;異常由于是CPU產(chǎn)生的,所以它是時鐘同步的
          • 當(dāng)處理中斷時,處于中斷上下文中;處理異常時,處于進(jìn)程上下文中


          瀏覽 40
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  欧美一区二区三色欲区AA大片 | 亚洲天堂三级片 | 四位少妇按摩欧美三级 | B日烂了日B | 小泽玛利亚大战黑人 |