操作系統(tǒng)基礎(chǔ)知識(shí)大全
計(jì)算機(jī)系統(tǒng)中的一個(gè)系統(tǒng)軟件,它是這樣一些程序模塊的集合——它們管理和控制計(jì)算機(jī)系統(tǒng)中的軟硬件資源,合理地組織計(jì)算機(jī)的工作流程,以便有效地利用這些資源為用戶提供一個(gè)功能強(qiáng)大、使用方便和可擴(kuò)展的工作環(huán)境,從而在計(jì)算機(jī)與其用戶之間起到接口的作用。下面就讓小編帶你去看看操作系統(tǒng)基礎(chǔ)知識(shí)大全吧,希望能幫助到大家!
操作系統(tǒng)基礎(chǔ)知識(shí)筆記
一、操作系統(tǒng)相關(guān)概念
計(jì)算機(jī)軟件:系統(tǒng)軟件和應(yīng)用軟件。
計(jì)算機(jī)系統(tǒng)資源:硬件資源、軟件資源。
硬件資源:中央處理器、存儲(chǔ)器、輸入、輸出等物理設(shè)備。
軟件資源:以文件形式保存到存儲(chǔ)器上的程序和數(shù)據(jù)信息。
定義:有效地組織和管理系統(tǒng)的各種軟/硬件資源,合理組織計(jì)算機(jī)系統(tǒng)工作流程,控制程序的執(zhí)行,并給用戶提供一個(gè)良好的環(huán)境和友好的接口。
操作系統(tǒng)作用:通過(guò)資源管理提高計(jì)算機(jī)系統(tǒng)的效率、改善人家界面提高良好的工作環(huán)境。
吞吐量:計(jì)算機(jī)在單位時(shí)間內(nèi)處理工作的能力。
二、操作系統(tǒng)的特征與功能
操作系統(tǒng)的特征:并發(fā)性、共享性、虛擬性、隨機(jī)性。
2.1、 操作系統(tǒng)的功能
1、進(jìn)程管理:實(shí)際上是對(duì)處理機(jī)的執(zhí)行時(shí)間進(jìn)行管理,采用多道程序等技術(shù)將CPU的時(shí)間合理分配給每個(gè)任務(wù)。比如:進(jìn)程控制、進(jìn)程同步、進(jìn)程通信、進(jìn)程調(diào)度。
2、文件管理:主要有存儲(chǔ)空間管理、目錄管理、文件讀寫。
3、存儲(chǔ)管理:對(duì)主存儲(chǔ)器空間進(jìn)行管理,主要包括存儲(chǔ)空間分配回收、存儲(chǔ)保護(hù)、地址映射、主存擴(kuò)充等。
4、設(shè)備管理:對(duì)硬件設(shè)備的管理。包括分配、啟動(dòng)、完成、回收。
5、作業(yè)管理:包括任務(wù)、界面管理、人機(jī)交互、語(yǔ)音控制、虛擬現(xiàn)實(shí)等。
三、操作系統(tǒng)分類
1、批處理操作系統(tǒng)
分為單道批處理、多道批處理。
單道批處理:早期的操作系統(tǒng),一次只有一個(gè)作業(yè)裝入內(nèi)存執(zhí)行。作業(yè)由用戶程序、數(shù)據(jù)和作業(yè)說(shuō)明書組成。一個(gè)作業(yè)運(yùn)行結(jié)束后,自動(dòng)調(diào)入同批的下一個(gè)作業(yè)。
多道批處理:允許多個(gè)作業(yè)裝入內(nèi)存執(zhí)行,在任意時(shí)刻,作業(yè)都處于開始和結(jié)束點(diǎn)之間。
多道批處理系統(tǒng)特點(diǎn):多道、宏觀上并行運(yùn)行、微觀上串行運(yùn)行。
2、分時(shí)操作系統(tǒng)
分時(shí)操作系統(tǒng)是將CPU的工作劃分為很短的時(shí)間片。輪流為各個(gè)終端的用戶服務(wù)。
分時(shí)操作系統(tǒng)特點(diǎn):多路性、獨(dú)立性、交互性、及時(shí)性。
3、實(shí)時(shí)操作系統(tǒng)
實(shí)時(shí)操作系統(tǒng)對(duì)交互能力要求不高,要能對(duì)外來(lái)信息足夠快的速度響應(yīng)和處理。分為實(shí)時(shí)控制系統(tǒng)和實(shí)時(shí)信息處理系統(tǒng)。
實(shí)時(shí)控制系統(tǒng):主要用于生產(chǎn)過(guò)程的自動(dòng)控制,比如自動(dòng)采集、飛機(jī)的自動(dòng)駕駛等。
實(shí)時(shí)信息處理系統(tǒng):主要是實(shí)時(shí)信息處理,比如飛機(jī)訂票系統(tǒng)、情報(bào)檢索系統(tǒng)等。
4、網(wǎng)絡(luò)操作系統(tǒng)
網(wǎng)絡(luò)操作系統(tǒng)使互聯(lián)網(wǎng)能方便有效的共享網(wǎng)絡(luò)資源,為網(wǎng)絡(luò)用戶提供各種服務(wù)軟件和有關(guān)協(xié)議的幾何。比如電子郵件、文件傳輸、共享硬盤等。
網(wǎng)絡(luò)操作系統(tǒng)分為如下三類:
1、集中式:系統(tǒng)的基本單元由一臺(tái)主機(jī)和若干臺(tái)主機(jī)相連的終端構(gòu)成,將多臺(tái)主機(jī)連接處理形成網(wǎng)絡(luò)。比如UNI__。
2、客戶端/服務(wù)器模式:該模式分為客戶端和服務(wù)器。服務(wù)器是網(wǎng)絡(luò)控制的中心,向客戶端提供多種服務(wù),客戶端主要是訪問(wèn)服務(wù)端的資源。
3、對(duì)等模式(P2P):相當(dāng)于每一臺(tái)客戶端都可以給其他客戶端提供資源服務(wù)。
5、分布式操作系統(tǒng)
分布式操作系統(tǒng)是由多個(gè)分散的計(jì)算機(jī)經(jīng)連接而成的計(jì)算機(jī)系統(tǒng),系統(tǒng)中的計(jì)算機(jī)無(wú)主次之分,任意兩臺(tái)計(jì)算機(jī)都可以交換信息。分布式操作系統(tǒng)能直接對(duì)各類資源進(jìn)行動(dòng)態(tài)分配和調(diào)度、任務(wù)劃分、信息傳輸協(xié)調(diào)工作,為用戶提供一個(gè)統(tǒng)一的界面、標(biāo)準(zhǔn)的接口,用戶通過(guò)這一界面實(shí)現(xiàn)所需要的操作和使用系統(tǒng)資源。
6、微機(jī)操作系統(tǒng)
目前主流的操作系統(tǒng)有Linu__、MacOS、Windows。
7、嵌入式操作系統(tǒng)
嵌入式操作系統(tǒng)運(yùn)行在嵌入式智能芯片環(huán)境中,對(duì)整個(gè)智能芯片以及操作、控制、部件裝置等資源進(jìn)行統(tǒng)一協(xié)調(diào)、處理、指揮、控制。
嵌入式操作系統(tǒng)特點(diǎn):微型化、可定制、實(shí)時(shí)性、可靠性、易移植性。
操作系統(tǒng)基礎(chǔ)知識(shí)
一、概述
1. 操作系統(tǒng)基本特征
1. 并發(fā)
并發(fā)是指宏觀上在一段時(shí)間內(nèi)能同時(shí)運(yùn)行多個(gè)程序,而并行則指同一時(shí)刻能運(yùn)行多個(gè)指令。
并行需要硬件支持,如多流水線或者多處理器。
操作系統(tǒng)通過(guò)引入進(jìn)程和線程,使得程序能夠并發(fā)運(yùn)行。
2. 共享
共享是指系統(tǒng)中的資源可以被多個(gè)并發(fā)進(jìn)程共同使用。
有兩種共享方式:互斥共享和同時(shí)共享。
互斥共享的資源稱為臨界資源,例如打印機(jī)等,在同一時(shí)間只允許一個(gè)進(jìn)程訪問(wèn),需要用同步機(jī)制來(lái)實(shí)現(xiàn)對(duì)臨界資源的訪問(wèn)。
3. 虛擬
虛擬技術(shù)把一個(gè)物理實(shí)體轉(zhuǎn)換為多個(gè)邏輯實(shí)體。
利用多道程序設(shè)計(jì)技術(shù),讓每個(gè)用戶都覺(jué)得有一個(gè)計(jì)算機(jī)專門為他服務(wù)。
主要有兩種虛擬技術(shù):時(shí)分復(fù)用技術(shù)和空分復(fù)用技術(shù)。例如多個(gè)進(jìn)程能在同一個(gè)處理器上并發(fā)執(zhí)行使用了時(shí)分復(fù)用技術(shù),讓每個(gè)進(jìn)程輪流占有處理器,每次只執(zhí)行一小個(gè)時(shí)間片并快速切換。
4. 異步
異步指進(jìn)程不是一次性執(zhí)行完畢,而是走走停停,以不可知的速度向前推進(jìn)。
但只要運(yùn)行環(huán)境相同,OS需要保證程序運(yùn)行的結(jié)果也要相同。
2. 操作系統(tǒng)基本功能
1. 進(jìn)程管理
進(jìn)程控制、進(jìn)程同步、進(jìn)程通信、死鎖處理、處理機(jī)調(diào)度等。
2. 內(nèi)存管理
內(nèi)存分配、地址映射、內(nèi)存保護(hù)與共享、虛擬內(nèi)存等。
3. 文件管理
文件存儲(chǔ)空間的管理、目錄管理、文件讀寫管理和保護(hù)等。
4. 設(shè)備管理
完成用戶的 I/O 請(qǐng)求,方便用戶使用各種設(shè)備,并提高設(shè)備的利用率。
主要包括緩沖管理、設(shè)備分配、設(shè)備處理、虛擬設(shè)備等。
3. 系統(tǒng)調(diào)用
如果一個(gè)進(jìn)程在用戶態(tài)需要使用內(nèi)核態(tài)的功能,就進(jìn)行系統(tǒng)調(diào)用從而陷入內(nèi)核,由操作系統(tǒng)代為完成。
4. 大內(nèi)核和微內(nèi)核
1. 大內(nèi)核
大內(nèi)核是將操作系統(tǒng)功能作為一個(gè)緊密結(jié)合的整體放到內(nèi)核。
由于各模塊共享信息,因此有很高的性能。
2. 微內(nèi)核
由于操作系統(tǒng)不斷復(fù)雜,因此將一部分操作系統(tǒng)功能移出內(nèi)核,從而降低內(nèi)核的復(fù)雜性。移出的部分根據(jù)分層的原則劃分成若干服務(wù),相互獨(dú)立。
在微內(nèi)核結(jié)構(gòu)下,操作系統(tǒng)被劃分成小的、定義良好的模塊,只有微內(nèi)核這一個(gè)模塊運(yùn)行在內(nèi)核態(tài),其余模塊運(yùn)行在用戶態(tài)。
因?yàn)樾枰l繁地在用戶態(tài)和核心態(tài)之間進(jìn)行切換,所以會(huì)有一定的性能損失。
5. 中斷分類
1. 外中斷
由 CPU 執(zhí)行指令以外的事件引起,如 I/O 完成中斷,表示設(shè)備輸入/輸出處理已經(jīng)完成,處理器能夠發(fā)送下一個(gè)輸入/輸出請(qǐng)求。此外還有時(shí)鐘中斷、控制臺(tái)中斷等。
2. 異常
由 CPU 執(zhí)行指令的內(nèi)部事件引起,如非法操作碼、地址越界、算術(shù)溢出等。
6. 什么是堆和棧?說(shuō)一下堆棧都存儲(chǔ)哪些數(shù)據(jù)?
棧區(qū)(stack)— 由編譯器自動(dòng)分配釋放 ,存放函數(shù)的參數(shù)值,局部變量的值等。其操作方式類似于數(shù)據(jù)結(jié)構(gòu)中的棧。
堆區(qū)(heap) — 一般由程序員分配釋放, 若程序員不釋放,程序結(jié)束時(shí)可能由OS回收 。
數(shù)據(jù)結(jié)構(gòu)中這兩個(gè)完全就不放一塊來(lái)講,數(shù)據(jù)結(jié)構(gòu)中棧和隊(duì)列才是好基友,我想新手也很容易區(qū)分。
我想需要區(qū)分的情況肯定不是在數(shù)據(jù)結(jié)構(gòu)話題下,而大多是在 OS 關(guān)于不同對(duì)象的內(nèi)存分配這塊上。
簡(jiǎn)單講的話,在 C 語(yǔ)言中:
int a[N]; // go on a stackint__ a = (int __)malloc(sizeof(int) __ N); // go on a heap
7. 如何理解分布式鎖?
分布式鎖,是控制分布式系統(tǒng)之間同步訪問(wèn)共享資源的一種方式。在分布式系統(tǒng)中,常常需要協(xié)調(diào)他們的動(dòng)作。如果不同的系統(tǒng)或是同一個(gè)系統(tǒng)的不同主機(jī)之間共享了一個(gè)或一組資源,那么訪問(wèn)這些資源的時(shí)候,往往需要互斥來(lái)防止彼此干擾來(lái)保證一致性,在這種情況下,便需要使用到分布式鎖。
二、進(jìn)程管理
1. 進(jìn)程與線程
1. 進(jìn)程
進(jìn)程是資源分配的基本單位,用來(lái)管理資源(例如:內(nèi)存,文件,網(wǎng)絡(luò)等資源)
進(jìn)程控制塊 (Process Control Block, PCB) 描述進(jìn)程的基本信息和運(yùn)行狀態(tài),所謂的創(chuàng)建進(jìn)程和撤銷進(jìn)程,都是指對(duì) PCB 的操作。(PCB是描述進(jìn)程的數(shù)據(jù)結(jié)構(gòu))
下圖顯示了 4 個(gè)程序創(chuàng)建了 4 個(gè)進(jìn)程,這 4 個(gè)進(jìn)程可以并發(fā)地執(zhí)行。
2. 線程
線程是獨(dú)立調(diào)度的基本單位。
一個(gè)進(jìn)程中可以有多個(gè)線程,它們共享進(jìn)程資源。
QQ 和瀏覽器是兩個(gè)進(jìn)程,瀏覽器進(jìn)程里面有很多線程,例如 HTTP 請(qǐng)求線程、事件響應(yīng)線程、渲染線程等等,線程的并發(fā)執(zhí)行使得在瀏覽器中點(diǎn)擊一個(gè)新鏈接從而發(fā)起 HTTP 請(qǐng)求時(shí),瀏覽器還可以響應(yīng)用戶的其它事件。
3. 區(qū)別
(一)擁有資源
進(jìn)程是資源分配的基本單位,但是線程不擁有資源,線程可以訪問(wèn)隸屬進(jìn)程的資源。
(二)調(diào)度
線程是獨(dú)立調(diào)度的基本單位,在同一進(jìn)程中,線程的切換不會(huì)引起進(jìn)程切換,從一個(gè)進(jìn)程內(nèi)的線程切換到另一個(gè)進(jìn)程中的線程時(shí),會(huì)引起進(jìn)程切換。
(三)系統(tǒng)開銷
由于創(chuàng)建或撤銷進(jìn)程時(shí),系統(tǒng)都要為之分配或回收資源,如內(nèi)存空間、I/O 設(shè)備等,所付出的開銷遠(yuǎn)大于創(chuàng)建或撤銷線程時(shí)的開銷。類似地,在進(jìn)行進(jìn)程切換時(shí),涉及當(dāng)前執(zhí)行進(jìn)程 CPU 環(huán)境的保存及新調(diào)度進(jìn)程 CPU 環(huán)境的設(shè)置,而線程切換時(shí)只需保存和設(shè)置少量寄存器內(nèi)容,開銷很小。
(四)通信方面
進(jìn)程間通信 (IPC) 需要進(jìn)程同步和互斥手段的輔助,以保證數(shù)據(jù)的一致性。而線程間可以通過(guò)直接讀/寫同一進(jìn)程中的數(shù)據(jù)段(如全局變量)來(lái)進(jìn)行通信。
2. 進(jìn)程狀態(tài)的切換(生命周期)
就緒狀態(tài)(ready):等待被調(diào)度
運(yùn)行狀態(tài)(running)
阻塞狀態(tài)(waiting):等待資源
應(yīng)該注意以下內(nèi)容:
只有就緒態(tài)和運(yùn)行態(tài)可以相互轉(zhuǎn)換,其它的都是單向轉(zhuǎn)換。就緒狀態(tài)的進(jìn)程通過(guò)調(diào)度算法從而獲得 CPU 時(shí)間,轉(zhuǎn)為運(yùn)行狀態(tài);而運(yùn)行狀態(tài)的進(jìn)程,在分配給它的 CPU 時(shí)間片用完之后就會(huì)轉(zhuǎn)為就緒狀態(tài),等待下一次調(diào)度。
阻塞狀態(tài)是缺少需要的資源從而由運(yùn)行狀態(tài)轉(zhuǎn)換而來(lái),但是該資源不包括 CPU 時(shí)間,缺少 CPU 時(shí)間會(huì)從運(yùn)行態(tài)轉(zhuǎn)換為就緒態(tài)。
進(jìn)程只能自己阻塞自己,因?yàn)橹挥羞M(jìn)程自身才知道何時(shí)需要等待某種事件的發(fā)生
3. 進(jìn)程調(diào)度算法
不同環(huán)境的調(diào)度算法目標(biāo)不同,因此需要針對(duì)不同環(huán)境來(lái)討論調(diào)度算法。
1. 批處理系統(tǒng)
批處理系統(tǒng)沒(méi)有太多的用戶操作,在該系統(tǒng)中,調(diào)度算法目標(biāo)是保證吞吐量和周轉(zhuǎn)時(shí)間(從提交到終止的時(shí)間)。
1.1 先來(lái)先服務(wù)
先來(lái)先服務(wù) first-come first-serverd(FCFS)
按照請(qǐng)求的順序進(jìn)行調(diào)度。
有利于長(zhǎng)作業(yè),但不利于短作業(yè),因?yàn)槎套鳂I(yè)必須一直等待前面的長(zhǎng)作業(yè)執(zhí)行完畢才能執(zhí)行,而長(zhǎng)作業(yè)又需要執(zhí)行很長(zhǎng)時(shí)間,造成了短作業(yè)等待時(shí)間過(guò)長(zhǎng)。
1.2 短作業(yè)優(yōu)先
短作業(yè)優(yōu)先 shortest job first(SJF)
按估計(jì)運(yùn)行時(shí)間最短的順序進(jìn)行調(diào)度。
長(zhǎng)作業(yè)有可能會(huì)餓死,處于一直等待短作業(yè)執(zhí)行完畢的狀態(tài)。因?yàn)槿绻恢庇卸套鳂I(yè)到來(lái),那么長(zhǎng)作業(yè)永遠(yuǎn)得不到調(diào)度。
1.3 最短剩余時(shí)間優(yōu)先
最短剩余時(shí)間優(yōu)先 shortest remaining time ne__t(SRTN)
按估計(jì)剩余時(shí)間最短的順序進(jìn)行調(diào)度。
2. 交互式系統(tǒng)
交互式系統(tǒng)有大量的用戶交互操作,在該系統(tǒng)中調(diào)度算法的目標(biāo)是快速地進(jìn)行響應(yīng)。
2.1 時(shí)間片輪轉(zhuǎn)
將所有就緒進(jìn)程按 FCFS (先來(lái)先服務(wù)) 的原則排成一個(gè)隊(duì)列,每次調(diào)度時(shí),把 CPU 時(shí)間分配給隊(duì)首進(jìn)程,該進(jìn)程可以執(zhí)行一個(gè)時(shí)間片。當(dāng)時(shí)間片用完時(shí),由計(jì)時(shí)器發(fā)出時(shí)鐘中斷,調(diào)度程序便停止該進(jìn)程的執(zhí)行,并將它送往就緒隊(duì)列的末尾,同時(shí)繼續(xù)把 CPU 時(shí)間分配給隊(duì)首的進(jìn)程。
時(shí)間片輪轉(zhuǎn)算法的效率和時(shí)間片的大小有很大關(guān)系。因?yàn)檫M(jìn)程切換都要保存進(jìn)程的信息并且載入新進(jìn)程的信息,如果時(shí)間片太小,會(huì)導(dǎo)致進(jìn)程切換得太頻繁,在進(jìn)程切換上就會(huì)花過(guò)多時(shí)間。
2.2 優(yōu)先級(jí)調(diào)度
為每個(gè)進(jìn)程分配一個(gè)優(yōu)先級(jí),按優(yōu)先級(jí)進(jìn)行調(diào)度。
為了防止低優(yōu)先級(jí)的進(jìn)程永遠(yuǎn)等不到調(diào)度,可以隨著時(shí)間的推移增加等待進(jìn)程的優(yōu)先級(jí)。
2.3 多級(jí)反饋隊(duì)列
如果一個(gè)進(jìn)程需要執(zhí)行 100 個(gè)時(shí)間片,如果采用時(shí)間片輪轉(zhuǎn)調(diào)度算法,那么需要交換 100 次。
多級(jí)隊(duì)列是為這種需要連續(xù)執(zhí)行多個(gè)時(shí)間片的進(jìn)程考慮,它設(shè)置了多個(gè)隊(duì)列,每個(gè)隊(duì)列時(shí)間片大小都不同,例如 1,2,4,8,..。進(jìn)程在第一個(gè)隊(duì)列沒(méi)執(zhí)行完,就會(huì)被移到下一個(gè)隊(duì)列。這種方式下,之前的進(jìn)程只需要交換 7 次。
每個(gè)隊(duì)列優(yōu)先權(quán)也不同,最上面的優(yōu)先權(quán)最高。因此只有上一個(gè)隊(duì)列沒(méi)有進(jìn)程在排隊(duì),才能調(diào)度當(dāng)前隊(duì)列上的進(jìn)程。
可以將這種調(diào)度算法看成是時(shí)間片輪轉(zhuǎn)調(diào)度算法和優(yōu)先級(jí)調(diào)度算法的結(jié)合。
3. 實(shí)時(shí)系統(tǒng)
實(shí)時(shí)系統(tǒng)要求一個(gè)請(qǐng)求在一個(gè)確定時(shí)間內(nèi)得到響應(yīng)。
分為硬實(shí)時(shí)和軟實(shí)時(shí),前者必須滿足絕對(duì)的截止時(shí)間,后者可以容忍一定的超時(shí)。
參考資料:
操作系統(tǒng)典型調(diào)度算法_C語(yǔ)言中文網(wǎng)
4. 進(jìn)程同步
1. 臨界區(qū)
對(duì)臨界資源進(jìn)行訪問(wèn)的那段代碼稱為臨界區(qū)。
為了互斥訪問(wèn)臨界資源,每個(gè)進(jìn)程在進(jìn)入臨界區(qū)之前,需要先進(jìn)行檢查。
// entry section// critical section;// e__it section
2. 同步與互斥
同步:多個(gè)進(jìn)程按一定順序執(zhí)行;
互斥:多個(gè)進(jìn)程在同一時(shí)刻只有一個(gè)進(jìn)程能進(jìn)入臨界區(qū)。
3. 信號(hào)量
P 和 V 是來(lái)源于兩個(gè)荷蘭語(yǔ)詞匯,P() ---prolaag (荷蘭語(yǔ),嘗試減少的意思),V() ---verhoog(荷蘭語(yǔ),增加的意思)
信號(hào)量(Semaphore)是一個(gè)整型變量,可以對(duì)其執(zhí)行 down 和 up 操作,也就是常見(jiàn)的 P 和 V 操作。
down : 如果信號(hào)量大于 0 ,執(zhí)行 -1 操作;如果信號(hào)量等于 0,進(jìn)程睡眠,等待信號(hào)量大于 0;(阻塞)
up :對(duì)信號(hào)量執(zhí)行 +1 操作,喚醒睡眠的進(jìn)程讓其完成 down 操作。(喚醒)
down 和 up 操作需要被設(shè)計(jì)成原語(yǔ),不可分割,通常的做法是在執(zhí)行這些操作的時(shí)候屏蔽中斷。
如果信號(hào)量的取值只能為 0 或者 1,那么就成為了 互斥量(Mute__) ,0 表示臨界區(qū)已經(jīng)加鎖,1 表示臨界區(qū)解鎖。
typedef int semaphore;semaphore mute__ = 1;void P1() { down(&mute__); // 臨界區(qū) up(&mute__);}void P2() { down(&mute__); // 臨界區(qū) up(&mute__);}
使用信號(hào)量實(shí)現(xiàn)生產(chǎn)者-消費(fèi)者問(wèn)題
問(wèn)題描述:使用一個(gè)緩沖區(qū)來(lái)保存物品,只有緩沖區(qū)沒(méi)有滿,生產(chǎn)者才可以放入物品;只有緩沖區(qū)不為空,消費(fèi)者才可以拿走物品。
因?yàn)榫彌_區(qū)屬于臨界資源,因此需要使用一個(gè)互斥量 mute__ 來(lái)控制對(duì)緩沖區(qū)的互斥訪問(wèn)。
為了同步生產(chǎn)者和消費(fèi)者的行為,需要記錄緩沖區(qū)中物品的數(shù)量。數(shù)量可以使用信號(hào)量來(lái)進(jìn)行統(tǒng)計(jì),這里需要使用兩個(gè)信號(hào)量:empty 記錄空緩沖區(qū)的數(shù)量,full 記錄滿緩沖區(qū)的數(shù)量。其中,empty 信號(hào)量是在生產(chǎn)者進(jìn)程中使用,當(dāng) empty 不為 0 時(shí),生產(chǎn)者才可以放入物品;full 信號(hào)量是在消費(fèi)者進(jìn)程中使用,當(dāng) full 信號(hào)量不為 0 時(shí),消費(fèi)者才可以取走物品。
注意,不能先對(duì)緩沖區(qū)進(jìn)行加鎖,再測(cè)試信號(hào)量。也就是說(shuō),不能先執(zhí)行 down(mute__) 再執(zhí)行 down(empty)。如果這么做了,那么可能會(huì)出現(xiàn)這種情況:生產(chǎn)者對(duì)緩沖區(qū)加鎖后,執(zhí)行 down(empty) 操作,發(fā)現(xiàn) empty = 0,此時(shí)生產(chǎn)者睡眠。消費(fèi)者不能進(jìn)入臨界區(qū),因?yàn)樯a(chǎn)者對(duì)緩沖區(qū)加鎖了,也就無(wú)法執(zhí)行 up(empty) 操作,empty 永遠(yuǎn)都為 0,那么生產(chǎn)者和消費(fèi)者就會(huì)一直等待下去,造成死鎖。
#define N 100typedef int semaphore;semaphore mute__ = 1;semaphore empty = N;semaphore full = 0;void producer() { while(TRUE){ int item = produce_item(); // 生產(chǎn)一個(gè)產(chǎn)品 // down(&empty) 和 down(&mute__) 不能交換位置,否則造成死鎖 down(&empty); // 記錄空緩沖區(qū)的數(shù)量,這里減少一個(gè)產(chǎn)品空間 down(&mute__); // 互斥鎖 insert_item(item); up(&mute__); // 互斥鎖 up(&full); // 記錄滿緩沖區(qū)的數(shù)量,這里增加一個(gè)產(chǎn)品 }}void consumer() { while(TRUE){ down(&full); // 記錄滿緩沖區(qū)的數(shù)量,減少一個(gè)產(chǎn)品 down(&mute__); // 互斥鎖 int item = remove_item(); up(&mute__); // 互斥鎖 up(&empty); // 記錄空緩沖區(qū)的數(shù)量,這里增加一個(gè)產(chǎn)品空間 consume_item(item); }}
4. 管程
管程 (英語(yǔ):Monitors,也稱為監(jiān)視器) 是一種程序結(jié)構(gòu),結(jié)構(gòu)內(nèi)的多個(gè)子程序(對(duì)象或模塊)形成的多個(gè)工作線程互斥訪問(wèn)共享資源。
使用信號(hào)量機(jī)制實(shí)現(xiàn)的生產(chǎn)者消費(fèi)者問(wèn)題需要客戶端代碼做很多控制,而管程把控制的代碼獨(dú)立出來(lái),不僅不容易出錯(cuò),也使得客戶端代碼調(diào)用更容易。
管程是為了解決信號(hào)量在臨界區(qū)的 PV 操作上的配對(duì)的麻煩,把配對(duì)的 PV 操作集中在一起,生成的一種并發(fā)編程方法。其中使用了條件變量這種同步機(jī)制。
c 語(yǔ)言不支持管程,下面的示例代碼使用了類 Pascal 語(yǔ)言來(lái)描述管程。示例代碼的管程提供了 insert() 和 remove() 方法,客戶端代碼通過(guò)調(diào)用這兩個(gè)方法來(lái)解決生產(chǎn)者-消費(fèi)者問(wèn)題。
monitor ProducerConsumer integer i; condition c; procedure insert(); begin // ... end; procedure remove(); begin // ... end;end monitor;
管程有一個(gè)重要特性:在一個(gè)時(shí)刻只能有一個(gè)進(jìn)程使用管程。進(jìn)程在無(wú)法繼續(xù)執(zhí)行的時(shí)候不能一直占用管程,否者其它進(jìn)程永遠(yuǎn)不能使用管程。
管程引入了 條件變量 以及相關(guān)的操作:wait() 和 signal() 來(lái)實(shí)現(xiàn)同步操作。對(duì)條件變量執(zhí)行 wait() 操作會(huì)導(dǎo)致調(diào)用進(jìn)程阻塞,把管程讓出來(lái)給另一個(gè)進(jìn)程持有。signal() 操作用于喚醒被阻塞的進(jìn)程。
使用管程實(shí)現(xiàn)生產(chǎn)者-消費(fèi)者問(wèn)題
// 管程monitor ProducerConsumer condition full, empty; integer count := 0; condition c; procedure insert(item: integer); begin if count = N then wait(full); insert_item(item); count := count + 1; if count = 1 then signal(empty); end; function remove: integer; begin if count = 0 then wait(empty); remove = remove_item; count := count - 1; if count = N -1 then signal(full); end;end monitor;// 生產(chǎn)者客戶端procedure producerbegin while true do begin item = produce_item; ProducerConsumer.insert(item); endend;// 消費(fèi)者客戶端procedure consumerbegin while true do begin item = ProducerConsumer.remove; consume_item(item); endend;
5. 經(jīng)典同步問(wèn)題
生產(chǎn)者和消費(fèi)者問(wèn)題前面已經(jīng)討論過(guò)了。
1. 讀者-寫者問(wèn)題
允許多個(gè)進(jìn)程同時(shí)對(duì)數(shù)據(jù)進(jìn)行讀操作,但是不允許讀和寫以及寫和寫操作同時(shí)發(fā)生。讀者優(yōu)先策略
Rcount:讀操作的進(jìn)程數(shù)量(Rcount=0)
CountMute__:對(duì)于Rcount進(jìn)行加鎖(CountMute__=1)
WriteMute__:互斥量對(duì)于寫操作的加鎖(WriteMute__=1)
Rcount = 0;semaphore CountMute__ = 1;semaphore WriteMute__ = 1;void writer(){ while(true){ sem_wait(WriteMute__); // TO DO write(); sem_post(WriteMute__); }}// 讀者優(yōu)先策略void reader(){ while(true){ sem_wait(CountMute__); if(Rcount == 0) sem_wait(WriteMute__); Rcount++; sem_post(CountMute__); // TO DO read(); sem_wait(CountMute__); Rcount--; if(Rcount == 0) sem_post(WriteMute__); sem_post(CountMute__); }}
2. 哲學(xué)家進(jìn)餐問(wèn)題
五個(gè)哲學(xué)家圍著一張圓桌,每個(gè)哲學(xué)家面前放著食物。哲學(xué)家的生活有兩種交替活動(dòng):吃飯以及思考。當(dāng)一個(gè)哲學(xué)家吃飯時(shí),需要先拿起自己左右兩邊的兩根筷子,并且一次只能拿起一根筷子。
____方案一:____下面是一種錯(cuò)誤的解法,考慮到如果所有哲學(xué)家同時(shí)拿起左手邊的筷子,那么就無(wú)法拿起右手邊的筷子,造成死鎖。
#define N 5 // 哲學(xué)家個(gè)數(shù)void philosopher(int i) // 哲學(xué)家編號(hào):0 - 4{ while(TRUE) { think(); // 哲學(xué)家在思考 take_fork(i); // 去拿左邊的叉子 take_fork((i + 1) % N); // 去拿右邊的叉子 eat(); // 吃面條中…. put_fork(i); // 放下左邊的叉子 put_fork((i + 1) % N); // 放下右邊的叉子 }}
方案二:對(duì)拿叉子的過(guò)程進(jìn)行了改進(jìn),但仍不正確
#define N 5 // 哲學(xué)家個(gè)數(shù)while(1) // 去拿兩把叉子{ take_fork(i); // 去拿左邊的叉子 if(fork((i+1)%N)) { // 右邊叉子還在嗎 take_fork((i + 1) % N);// 去拿右邊的叉子 break; // 兩把叉子均到手 } else { // 右邊叉子已不在 put_fork(i); // 放下左邊的叉子 wait_some_time(); // 等待一會(huì)兒 }}
方案三:等待時(shí)間隨機(jī)變化??尚?,但非萬(wàn)全之策
#define N 5 // 哲學(xué)家個(gè)數(shù)while(1) // 去拿兩把叉子{ take_fork(i); // 去拿左邊的叉子 if(fork((i+1)%N)) { // 右邊叉子還在嗎 take_fork((i + 1) % N);// 去拿右邊的叉子 break; // 兩把叉子均到手 } else { // 右邊叉子已不在 put_fork(i); // 放下左邊的叉子 wait_random_time( ); // 等待隨機(jī)長(zhǎng)時(shí)間 }}
方案四:互斥訪問(wèn)。正確,但每次只允許一人進(jìn)餐
semaphore mute__ // 互斥信號(hào)量,初值1void philosopher(int i) // 哲學(xué)家編號(hào)i:0-4 { while(TRUE){ think(); // 哲學(xué)家在思考 P(mute__); // 進(jìn)入臨界區(qū) take_fork(i); // 去拿左邊的叉子 take_fork((i + 1) % N); // 去拿右邊的叉子 eat(); // 吃面條中…. put_fork(i); // 放下左邊的叉子 put_fork((i + 1) % N); // 放下右邊的叉子 V(mute__); // 退出臨界區(qū) }}
正確方案如下:
為了防止死鎖的發(fā)生,可以設(shè)置兩個(gè)條件(臨界資源):
必須同時(shí)拿起左右兩根筷子;
只有在兩個(gè)鄰居都沒(méi)有進(jìn)餐的情況下才允許進(jìn)餐。
//1. 必須由一個(gè)數(shù)據(jù)結(jié)構(gòu),來(lái)描述每個(gè)哲學(xué)家當(dāng)前的狀態(tài)#define N 5#define LEFT i // 左鄰居#define RIGHT (i + 1) % N // 右鄰居#define THINKING 0#define HUNGRY 1#define EATING 2typedef int semaphore;int state[N]; // 跟蹤每個(gè)哲學(xué)家的狀態(tài)//2. 該狀態(tài)是一個(gè)臨界資源,對(duì)它的訪問(wèn)應(yīng)該互斥地進(jìn)行semaphore mute__ = 1; // 臨界區(qū)的互斥//3. 一個(gè)哲學(xué)家吃飽后,可能要喚醒鄰居,存在著同步關(guān)系semaphore s[N]; // 每個(gè)哲學(xué)家一個(gè)信號(hào)量void philosopher(int i) { while(TRUE) { think(); take_two(i); eat(); put_tow(i); }}void take_two(int i) { P(&mute__); // 進(jìn)入臨界區(qū) state[i] = HUNGRY; // 我餓了 test(i); // 試圖拿兩把叉子 V(&mute__); // 退出臨界區(qū) P(&s[i]); // 沒(méi)有叉子便阻塞}void put_tow(i) { P(&mute__); state[i] = THINKING; test(LEFT); test(RIGHT); V(&mute__);}void test(i) { // 嘗試拿起兩把筷子 if(state[i] == HUNGRY && state[LEFT] != EATING && state[RIGHT] !=EATING) { state[i] = EATING; V(&s[i]); // 通知第i個(gè)人可以吃飯了 }}
6. 進(jìn)程通信
進(jìn)程同步與進(jìn)程通信很容易混淆,它們的區(qū)別在于:
進(jìn)程同步:控制多個(gè)進(jìn)程按一定順序執(zhí)行
進(jìn)程通信:進(jìn)程間傳輸信息
進(jìn)程通信是一種手段,而進(jìn)程同步是一種目的。也可以說(shuō),為了能夠達(dá)到進(jìn)程同步的目的,需要讓進(jìn)程進(jìn)行通信,傳輸一些進(jìn)程同步所需要的信息。
__ 進(jìn)程通信方式
直接通信
發(fā)送進(jìn)程直接把消息發(fā)送給接收進(jìn)程,并將它掛在接收進(jìn)程的消息緩沖隊(duì)列上,接收進(jìn)程從消息緩沖隊(duì)列中取得消息。
Send 和 Receive 原語(yǔ)的使用格式如下:
Send(Receiver,message);//發(fā)送一個(gè)消息message給接收進(jìn)程ReceiverReceive(Sender,message);//接收Sender進(jìn)程發(fā)送的消息message
間接通信
間接通信方式是指進(jìn)程之間的通信需要通過(guò)作為共享數(shù)據(jù)結(jié)構(gòu)的實(shí)體。該實(shí)體用來(lái)暫存發(fā)送進(jìn)程發(fā)給目標(biāo)進(jìn)程的消息。
發(fā)送進(jìn)程把消息發(fā)送到某個(gè)中間實(shí)體中,接收進(jìn)程從中間實(shí)體中取得消息。這種中間實(shí)體一般稱為信箱,這種通信方式又稱為信箱通信方式。該通信方式廣泛應(yīng)用于計(jì)算機(jī)網(wǎng)絡(luò)中,相應(yīng)的通信系統(tǒng)稱為電子郵件系統(tǒng)。
1. 管道
管道是通過(guò)調(diào)用 pipe 函數(shù)創(chuàng)建的,fd[0] 用于讀,fd[1] 用于寫。
#include int pipe(int fd[2]);
它具有以下限制:
只支持半雙工通信(單向傳輸);
只能在父子進(jìn)程中使用。
2. 命名管道
也稱為命名管道,去除了管道只能在父子進(jìn)程中使用的限制。
#include int mkfifo(const char __path, mode_t mode);int mkfifoat(int fd, const char __path, mode_t mode);
FIFO 常用于客戶-服務(wù)器應(yīng)用程序中,F(xiàn)IFO 用作匯聚點(diǎn),在客戶進(jìn)程和服務(wù)器進(jìn)程之間傳遞數(shù)據(jù)。
3. 消息隊(duì)列
間接(內(nèi)核)
相比于 FIFO,消息隊(duì)列具有以下優(yōu)點(diǎn):
消息隊(duì)列可以獨(dú)立于讀寫進(jìn)程存在,從而避免了 FIFO 中同步管道的打開和關(guān)閉時(shí)可能產(chǎn)生的困難;
避免了 FIFO 的同步阻塞問(wèn)題,不需要進(jìn)程自己提供同步方法;
讀進(jìn)程可以根據(jù)消息類型有選擇地接收消息,而不像 FIFO 那樣只能默認(rèn)地接收。
4. 信號(hào)量
它是一個(gè)計(jì)數(shù)器,用于為多個(gè)進(jìn)程提供對(duì)共享數(shù)據(jù)對(duì)象的訪問(wèn)。
5. 共享內(nèi)存
允許多個(gè)進(jìn)程共享一個(gè)給定的存儲(chǔ)區(qū)。因?yàn)閿?shù)據(jù)不需要在進(jìn)程之間復(fù)制,所以這是最快的一種 IPC。
需要使用信號(hào)量用來(lái)同步對(duì)共享存儲(chǔ)的訪問(wèn)。
多個(gè)進(jìn)程可以將同一個(gè)文件映射到它們的地址空間從而實(shí)現(xiàn)共享內(nèi)存。另外 __SI 共享內(nèi)存不是使用文件,而是使用使用內(nèi)存的匿名段。
6. 套接字
與其它通信機(jī)制不同的是,它可用于不同機(jī)器間的進(jìn)程通信。
7. 線程間通信和進(jìn)程間通信
線程間通信
synchronized同步
這種方式,本質(zhì)上就是 “共享內(nèi)存” 式的通信。多個(gè)線程需要訪問(wèn)同一個(gè)共享變量,誰(shuí)拿到了鎖(獲得了訪問(wèn)權(quán)限),誰(shuí)就可以執(zhí)行。
while輪詢的方式
在這種方式下,ThreadA 不斷地改變條件,ThreadB 不停地通過(guò) while 語(yǔ)句檢測(cè)這個(gè)條件 (list.size()==5) 是否成立 ,從而實(shí)現(xiàn)了線程間的通信。但是這種方式會(huì)浪費(fèi) CPU 資源。
之所以說(shuō)它浪費(fèi)資源,是因?yàn)?JVM 調(diào)度器將 CPU 交給 ThreadB 執(zhí)行時(shí),它沒(méi)做啥 “有用” 的工作,只是在不斷地測(cè)試某個(gè)條件是否成立。
就類似于現(xiàn)實(shí)生活中,某個(gè)人一直看著手機(jī)屏幕是否有電話來(lái)了,而不是:在干別的事情,當(dāng)有電話來(lái)時(shí),響鈴?fù)ㄖ猅A電話來(lái)了。
wait/notify機(jī)制
當(dāng)條件未滿足時(shí),ThreadA 調(diào)用 wait() 放棄 CPU,并進(jìn)入阻塞狀態(tài)。(不像 while 輪詢那樣占用 CPU)
當(dāng)條件滿足時(shí),ThreadB 調(diào)用 notify() 通知線程 A,所謂通知線程 A,就是喚醒線程 A,并讓它進(jìn)入可運(yùn)行狀態(tài)。
管道通信
java.io.PipedInputStream 和 java.io.PipedOutputStream進(jìn)行通信
進(jìn)程間通信
管道(Pipe) :管道可用于具有親緣關(guān)系進(jìn)程間的通信,允許一個(gè)進(jìn)程和另一個(gè)與它有共同祖先的進(jìn)程之間進(jìn)行通信。
命名管道(named pipe) :命名管道克服了管道沒(méi)有名字的限制,因此,除具有管道所具有的功能外,它還允許無(wú)親緣關(guān) 系 進(jìn)程間的通信。命名管道在文件系統(tǒng)中有對(duì)應(yīng)的文件名。命名管道通過(guò)命令mkfifo或系統(tǒng)調(diào)用mkfifo來(lái)創(chuàng)建。
信號(hào)(Signal) :信號(hào)是比較復(fù)雜的通信方式,用于通知接受進(jìn)程有某種事件發(fā)生,除了用于進(jìn)程間通信外,進(jìn)程還可以發(fā)送 信號(hào)給進(jìn)程本身;Linu__除了支持Uni__早期信號(hào)語(yǔ)義函數(shù)sigal外,還支持語(yǔ)義符合Posi__.1標(biāo)準(zhǔn)的信號(hào)函數(shù)sigaction(實(shí)際上,該函數(shù)是基于BSD的,BSD為了實(shí)現(xiàn)可靠信號(hào)機(jī)制,又能夠統(tǒng)一對(duì)外接口,用sigaction函數(shù)重新實(shí)現(xiàn)了signal函數(shù))。
消息(Message)隊(duì)列 :消息隊(duì)列是消息的鏈接表,包括Posi__消息隊(duì)列system V消息隊(duì)列。有足夠權(quán)限的進(jìn)程可以向隊(duì)列中添加消息,被賦予讀權(quán)限的進(jìn)程則可以讀走隊(duì)列中的消息。消息隊(duì)列克服了信號(hào)承載信息量少,管道只能承載無(wú)格式字節(jié)流以及緩沖區(qū)大小受限等缺
共享內(nèi)存 :使得多個(gè)進(jìn)程可以訪問(wèn)同一塊內(nèi)存空間,是最快的可用IPC形式。是針對(duì)其他通信機(jī)制運(yùn)行效率較低而設(shè)計(jì)的。往往與其它通信機(jī)制,如信號(hào)量結(jié)合使用,來(lái)達(dá)到進(jìn)程間的同步及互斥。
內(nèi)存映射(mapped memory) :內(nèi)存映射允許任何多個(gè)進(jìn)程間通信,每一個(gè)使用該機(jī)制的進(jìn)程通過(guò)把一個(gè)共享的文件映射到自己的進(jìn)程地址空間來(lái)實(shí)現(xiàn)它。
信號(hào)量(semaphore) :主要作為進(jìn)程間以及同一進(jìn)程不同線程之間的同步手段。
套接口(Socket) :更為一般的進(jìn)程間通信機(jī)制,可用于不同機(jī)器之間的進(jìn)程間通信。起初是由Uni__系統(tǒng)的BSD分支開發(fā)出來(lái)的,但現(xiàn)在一般可以移植到其它類Uni__系統(tǒng)上:linu__和System V的變種都支持套接字。
8. 進(jìn)程操作
Linu__進(jìn)程結(jié)構(gòu)可由三部分組成:
代碼段(程序)
數(shù)據(jù)段(數(shù)據(jù))
堆棧段(控制塊PCB)
進(jìn)程控制塊是進(jìn)程存在的惟一標(biāo)識(shí),系統(tǒng)通過(guò)PCB的存在而感知進(jìn)程的存在。系統(tǒng)通過(guò) PCB 對(duì)進(jìn)程進(jìn)行管理和調(diào)度。PCB 包括創(chuàng)建進(jìn)程、執(zhí)行進(jìn)程、退出進(jìn)程以及改變進(jìn)程的優(yōu)先級(jí)等。
一般程序轉(zhuǎn)換為進(jìn)程分以下幾個(gè)步驟:
內(nèi)核將程序讀入內(nèi)存,為程序分配內(nèi)存空間
內(nèi)核為該進(jìn)程分配進(jìn)程標(biāo)識(shí)符 PID 和其他所需資源
內(nèi)核為進(jìn)程保存 PID 及相應(yīng)的狀態(tài)信息,把進(jìn)程放到運(yùn)行隊(duì)列中等待執(zhí)行,程序轉(zhuǎn)化為進(jìn)程后可以被操作系統(tǒng)的調(diào)度程序調(diào)度執(zhí)行了
在 UNI__ 里,除了進(jìn)程 0(即 PID=0 的交換進(jìn)程,Swapper Process)以外的所有進(jìn)程都是由其他進(jìn)程使用系統(tǒng)調(diào)用 fork 創(chuàng)建的,這里調(diào)用 fork 創(chuàng)建新進(jìn)程的進(jìn)程即為父進(jìn)程,而相對(duì)應(yīng)的為其創(chuàng)建出的進(jìn)程則為子進(jìn)程,因而除了進(jìn)程 0 以外的進(jìn)程都只有一個(gè)父進(jìn)程,但一個(gè)進(jìn)程可以有多個(gè)子進(jìn)程。操作系統(tǒng)內(nèi)核以進(jìn)程標(biāo)識(shí)符(Process Identifier,即 PID )來(lái)識(shí)別進(jìn)程。進(jìn)程 0 是系統(tǒng)引導(dǎo)時(shí)創(chuàng)建的一個(gè)特殊進(jìn)程,在其調(diào)用 fork 創(chuàng)建出一個(gè)子進(jìn)程(即 PID=1 的進(jìn)程 1,又稱 init)后,進(jìn)程 0 就轉(zhuǎn)為交換進(jìn)程(有時(shí)也被稱為空閑進(jìn)程),而進(jìn)程1(init進(jìn)程)就是系統(tǒng)里其他所有進(jìn)程的祖先。
進(jìn)程0:Linu__引導(dǎo)中創(chuàng)建的第一個(gè)進(jìn)程,完成加載系統(tǒng)后,演變?yōu)檫M(jìn)程調(diào)度、交換及存儲(chǔ)管理進(jìn)程?! ∵M(jìn)程1:init 進(jìn)程,由0進(jìn)程創(chuàng)建,完成系統(tǒng)的初始化. 是系統(tǒng)中所有其它用戶進(jìn)程的祖先進(jìn)程。
Linu__中 1 號(hào)進(jìn)程是由 0 號(hào)進(jìn)程來(lái)創(chuàng)建的,因此必須要知道的是如何創(chuàng)建 0 號(hào)進(jìn)程,由于在創(chuàng)建進(jìn)程時(shí),程序一直運(yùn)行在內(nèi)核態(tài),而進(jìn)程運(yùn)行在用戶態(tài),因此創(chuàng)建 0 號(hào)進(jìn)程涉及到特權(quán)級(jí)的變化,即從特權(quán)級(jí) 0 變到特權(quán)級(jí) 3,Linu__ 是通過(guò)模擬中斷返回來(lái)實(shí)現(xiàn)特權(quán)級(jí)的變化以及創(chuàng)建 0 號(hào)進(jìn)程,通過(guò)將 0 號(hào)進(jìn)程的代碼段選擇子以及程序計(jì)數(shù)器EIP直接壓入內(nèi)核態(tài)堆棧,然后利用 iret 匯編指令中斷返回跳轉(zhuǎn)到 0 號(hào)進(jìn)程運(yùn)行。
創(chuàng)建一個(gè)進(jìn)程
進(jìn)程是系統(tǒng)中基本的執(zhí)行單位。Linu__ 系統(tǒng)允許任何一個(gè)用戶進(jìn)程創(chuàng)建一個(gè)子進(jìn)程,創(chuàng)建成功后,子進(jìn)程存在于系統(tǒng)之中,并且獨(dú)立于父進(jìn)程。該子進(jìn)程可以接受系統(tǒng)調(diào)度,可以得到分配的系統(tǒng)資源。系統(tǒng)也可以檢測(cè)到子進(jìn)程的存在,并且賦予它與父進(jìn)程同樣的權(quán)利。
Linu__系統(tǒng)下使用 fork() 函數(shù)創(chuàng)建一個(gè)子進(jìn)程,其函數(shù)原型如下:
#include pid_t fork(void);
在討論 fork() 函數(shù)之前,有必要先明確父進(jìn)程和子進(jìn)程兩個(gè)概念。除了 0 號(hào)進(jìn)程(該進(jìn)程是系統(tǒng)自舉時(shí)由系統(tǒng)創(chuàng)建的)以外,Linu__ 系統(tǒng)中的任何一個(gè)進(jìn)程都是由其他進(jìn)程創(chuàng)建的。創(chuàng)建新進(jìn)程的進(jìn)程,即調(diào)用 fork() 函數(shù)的進(jìn)程就是父進(jìn)程,而新創(chuàng)建的進(jìn)程就是子進(jìn)程。
fork() 函數(shù)不需要參數(shù),返回值是一個(gè)進(jìn)程標(biāo)識(shí)符 (PID)。對(duì)于返回值,有以下 3 種情況:
對(duì)于父進(jìn)程,fork() 函數(shù)返回新創(chuàng)建的子進(jìn)程的 ID。
對(duì)于子進(jìn)程,fork() 函數(shù)返回 0。由于系統(tǒng)的 0 號(hào)進(jìn)程是內(nèi)核進(jìn)程,所以子進(jìn)程的進(jìn)程標(biāo)識(shí)符不會(huì)是0,由此可以用來(lái)區(qū)別父進(jìn)程和子進(jìn)程。
如果創(chuàng)建出錯(cuò),則 fork() 函數(shù)返回 -1。
fork() 函數(shù)會(huì)創(chuàng)建一個(gè)新的進(jìn)程,并從內(nèi)核中為此進(jìn)程分配一個(gè)新的可用的進(jìn)程標(biāo)識(shí)符 (PID),之后,為這個(gè)新進(jìn)程分配進(jìn)程空間,并將父進(jìn)程的進(jìn)程空間中的內(nèi)容復(fù)制到子進(jìn)程的進(jìn)程空間中,包括父進(jìn)程的數(shù)據(jù)段和堆棧段,并且和父進(jìn)程共享代碼段(寫時(shí)復(fù)制)。這時(shí)候,系統(tǒng)中又多了一個(gè)進(jìn)程,這個(gè)進(jìn)程和父進(jìn)程一模一樣,兩個(gè)進(jìn)程都要接受系統(tǒng)的調(diào)度。
注意:由于在復(fù)制時(shí)復(fù)制了父進(jìn)程的堆棧段,所以兩個(gè)進(jìn)程都停留在了 fork() 函數(shù)中,等待返回。因此,fork() 函數(shù)會(huì)返回兩次,一次是在父進(jìn)程中返回,另一次是在子進(jìn)程中返回,這兩次的返回值是不一樣的。
下面給出的示例程序用來(lái)創(chuàng)建一個(gè)子進(jìn)程,該程序在父進(jìn)程和子進(jìn)程中分別輸出不同的內(nèi)容。
#include #include #include int main(void){ pid_t pid; // 保存進(jìn)程ID pid = fork(); // 創(chuàng)建一個(gè)新進(jìn)程 if(pid < 0){ // fork出錯(cuò) printf("fail to fork\n"); e__it(1); } else if(pid == 0){ // 子進(jìn)程 // 打印子進(jìn)程的進(jìn)程ID printf("this is child, pid is : %u\n", getpid()); } else{ // 打印父進(jìn)程和其子進(jìn)程的進(jìn)程ID printf("this is parent, pid is : %u, child-pid is : %u\n", getpid(), pid); } return 0;}
程序運(yùn)行結(jié)果如下:
$ ./forkParent, PID: 2598, Sub-process PID: 2599Sub-process, PID: 2599, PPID: 2598
由于創(chuàng)建的新進(jìn)程和父進(jìn)程在系統(tǒng)看來(lái)是地位平等的兩個(gè)進(jìn)程,所以運(yùn)行機(jī)會(huì)也是一樣的,我們不能夠?qū)ζ鋱?zhí)行先后順序進(jìn)行假設(shè),先執(zhí)行哪一個(gè)進(jìn)程取決于系統(tǒng)的調(diào)度算法。如果想要指定運(yùn)行的順序,則需要執(zhí)行額外的操作。正因?yàn)槿绱?,程序在運(yùn)行時(shí)并不能保證輸出順序和上面所描述的一致。
getpid() 是獲得當(dāng)前進(jìn)程的pid,而 getppid() 則是獲得父進(jìn)程的 id。
父子進(jìn)程的共享資源
子進(jìn)程完全復(fù)制了父進(jìn)程的地址空間的內(nèi)容,包括堆棧段和數(shù)據(jù)段的內(nèi)容。子進(jìn)程并沒(méi)有復(fù)制代碼段,而是和父進(jìn)程共用代碼段。這樣做是存在其合理依據(jù)的,因?yàn)樽舆M(jìn)程可能執(zhí)行不同的流程,那么就會(huì)改變數(shù)據(jù)段和堆棧段,因此需要分開存儲(chǔ)父子進(jìn)程各自的數(shù)據(jù)段和堆棧段。但是代碼段是只讀的,不存在被修改的問(wèn)題,因此這一個(gè)段可以讓父子進(jìn)程共享,以節(jié)省存儲(chǔ)空間,如下圖所示。
下面給出一個(gè)示例來(lái)說(shuō)明這個(gè)問(wèn)題。該程序定義了一個(gè)全局變量 global、一個(gè)局部變量 stack 和一個(gè)指針 heap。該指針用來(lái)指向一塊動(dòng)態(tài)分配的內(nèi)存區(qū)域。之后,該程序創(chuàng)建一個(gè)子進(jìn)程,在子進(jìn)程中修改 global、stack 和動(dòng)態(tài)分配的內(nèi)存中變量的值。然后在父子進(jìn)程中分別打印出這些變量的值。由于父子進(jìn)程的運(yùn)行順序是不確定的,因此我們先讓父進(jìn)程額外休眠2秒,以保證子進(jìn)程先運(yùn)行。
#include #include #include // 全局變量,在數(shù)據(jù)段中int global;int main(){ pid_t pid; int stack = 1; // 局部變量,在棧中 int __ heap; heap = (int __)malloc(sizeof(int)); // 動(dòng)態(tài)分配的內(nèi)存,在堆中 __heap = 2; pid = fork(); // 創(chuàng)建一個(gè)子進(jìn)程 if(pid < 0){ // 創(chuàng)建子進(jìn)程失敗 printf("fail to fork\n"); e__it(1); } else if(pid == 0){ // 子進(jìn)程,改變各變量的值 global++; // 修改棧、堆和數(shù)據(jù)段 stack++; (__heap)++; printf("the child, data : %d, stack : %d, heap : %d\n", global, stack, __heap); e__it(0); // 子進(jìn)程運(yùn)行結(jié)束 } // 父進(jìn)程休眠2秒鐘,保證子進(jìn)程先運(yùn)行 sleep(2); // 輸出結(jié)果 printf("the parent, data : %d, stack : %d, heap : %d\n", global, stack, __heap); return 0;}
程序運(yùn)行效果如下:
$ ./forkIn sub-process, global: 2, stack: 2, heap: 3In parent-process, global: 1, stack: 1, heap: 2
由于父進(jìn)程休眠了2秒鐘,子進(jìn)程先于父進(jìn)程運(yùn)行,因此會(huì)先在子進(jìn)程中修改數(shù)據(jù)段和堆棧段中的內(nèi)容。因此不難看出,子進(jìn)程對(duì)這些數(shù)據(jù)段和堆棧段中內(nèi)容的修改并不會(huì)影響到父進(jìn)程的進(jìn)程環(huán)境。
fork()函數(shù)的出錯(cuò)情況
有兩種情況可能會(huì)導(dǎo)致fork()函數(shù)出錯(cuò):
系統(tǒng)中已經(jīng)有太多的進(jìn)程存在了
調(diào)用fork()函數(shù)的用戶進(jìn)程太多了
一般情況下,系統(tǒng)都會(huì)對(duì)一個(gè)用戶所創(chuàng)建的進(jìn)程數(shù)加以限制。如果操作系統(tǒng)不對(duì)其加限制,那么惡意用戶可以利用這一缺陷攻擊系統(tǒng)。下面是一個(gè)利用進(jìn)程的特性編寫的一個(gè)病毒程序,該程序是一個(gè)死循環(huán),在循環(huán)中不斷調(diào)用fork()函數(shù)來(lái)創(chuàng)建子進(jìn)程,直到系統(tǒng)中不能容納如此多的進(jìn)程而崩潰為止。下圖展示了這種情況:
#include int main(){ while(1) fork(); /__ 不斷地創(chuàng)建子進(jìn)程,使系統(tǒng)中進(jìn)程溢滿 __/ return 0;}
創(chuàng)建共享空間的子進(jìn)程
進(jìn)程在創(chuàng)建一個(gè)新的子進(jìn)程之后,子進(jìn)程的地址空間完全和父進(jìn)程分開。父子進(jìn)程是兩個(gè)獨(dú)立的進(jìn)程,接受系統(tǒng)調(diào)度和分配系統(tǒng)資源的機(jī)會(huì)均等,因此父進(jìn)程和子進(jìn)程更像是一對(duì)兄弟。如果父子進(jìn)程共用父進(jìn)程的地址空間,則子進(jìn)程就不是獨(dú)立于父進(jìn)程的。
Linu__環(huán)境下提供了一個(gè)與 fork() 函數(shù)類似的函數(shù),也可以用來(lái)創(chuàng)建一個(gè)子進(jìn)程,只不過(guò)新進(jìn)程與父進(jìn)程共用父進(jìn)程的地址空間,其函數(shù)原型如下:
#include pid_t vfork(void);
vfork() 和 fork() 函數(shù)的區(qū)別有以下兩點(diǎn):
vfork() 函數(shù)產(chǎn)生的子進(jìn)程和父進(jìn)程完全共享地址空間,包括代碼段、數(shù)據(jù)段和堆棧段,子進(jìn)程對(duì)這些共享資源所做的修改,可以影響到父進(jìn)程。由此可知,vfork() 函數(shù)與其說(shuō)是產(chǎn)生了一個(gè)進(jìn)程,還不如說(shuō)是產(chǎn)生了一個(gè)線程。
vfork() 函數(shù)產(chǎn)生的子進(jìn)程一定比父進(jìn)程先運(yùn)行,也就是說(shuō)父進(jìn)程調(diào)用了 vfork() 函數(shù)后會(huì)等待子進(jìn)程運(yùn)行后再運(yùn)行。
下面的示例程序用來(lái)驗(yàn)證以上兩點(diǎn)。在子進(jìn)程中,我們先讓其休眠 2 秒以釋放 CPU 控制權(quán),在前面的 fork() 示例代碼中我們已經(jīng)知道這樣會(huì)導(dǎo)致其他線程先運(yùn)行,也就是說(shuō)如果休眠后父進(jìn)程先運(yùn)行的話,則第 1 點(diǎn)則為假;否則為真。第 2 點(diǎn)為真,則會(huì)先執(zhí)行子進(jìn)程,那么全局變量便會(huì)被修改,如果第 1 點(diǎn)為真,那么后執(zhí)行的父進(jìn)程也會(huì)輸出與子進(jìn)程相同的內(nèi)容。代碼如下:
//@file vfork.c//@brief vfork() usage#include #include #include int global = 1;int main(void){ pid_t pid; int stack = 1; int __heap; heap = (int __)malloc(sizeof(int)); ____eap = 1; pid = vfork(); if (pid < 0) { perror("fail to vfork"); ex___t(-1); } else if (pid == 0) { //sub-process, change values sleep(2);//release cpu controlling global = 999; stack = 888; ____eap = 777; //print all values printf("In sub-process, global: %d, stack: %d, heap: %d\n",global,stack,____eap); ex___t(0); } else { //parent-process printf("In parent-process, global: %d, stack: %d, heap: %d\n",global,stack,____eap); } return 0;}
程序運(yùn)行效果如下:
$ ./vforkIn sub-process, global: 999, stack: 888, heap: 777In parent-process, global: 999, stack: 888, heap: 777
在函數(shù)內(nèi)部調(diào)用vfork
在使用 vfork() 函數(shù)時(shí)應(yīng)該注意不要在任何函數(shù)中調(diào)用 vfork() 函數(shù)。下面的示例是在一個(gè)非 main 函數(shù)中調(diào)用了 vfork() 函數(shù)。該程序定義了一個(gè)函數(shù) f1(),該函數(shù)內(nèi)部調(diào)用了 vfork() 函數(shù)。之后,又定義了一個(gè)函數(shù) f2(),這個(gè)函數(shù)沒(méi)有實(shí)際的意義,只是用來(lái)覆蓋函數(shù) f1() 調(diào)用時(shí)的棧幀。main 函數(shù)中先調(diào)用 f1() 函數(shù),接著調(diào)用 f2() 函數(shù)。
#include #include #include int f1(void){ vfork(); return 0;}int f2(int a, int b){ return a+b;}int main(void){ int c; f1(); c = f2(1,2); printf("%d\n",c); return 0;}
程序運(yùn)行效果如下:
$ ./vfork3Segmentation fault (core dumped)
通過(guò)上面的程序運(yùn)行結(jié)果可以看出,一個(gè)進(jìn)程運(yùn)行正常,打印出了預(yù)期結(jié)果,而另一個(gè)進(jìn)程似乎出了問(wèn)題,發(fā)生了段錯(cuò)誤。出現(xiàn)這種情況的原因可以用下圖來(lái)分析一下:
左邊這張圖說(shuō)明調(diào)用 vfork() 之后產(chǎn)生了一個(gè)子進(jìn)程,并且和父進(jìn)程共享堆棧段,兩個(gè)進(jìn)程都要從 f1() 函數(shù)返回。由于子進(jìn)程先于父進(jìn)程運(yùn)行,所以子進(jìn)程先從 f1() 函數(shù)中返回,并且調(diào)用 f2() 函數(shù),其棧幀覆蓋了原來(lái) f1() 函數(shù)的棧幀。當(dāng)子進(jìn)程運(yùn)行結(jié)束,父進(jìn)程開始運(yùn)行時(shí),就出現(xiàn)了右圖的情景,父進(jìn)程需要從 f1() 函數(shù)返回,但是 f1() 函數(shù)的棧幀已經(jīng)被 f2() 函數(shù)的所替代,因此就會(huì)出現(xiàn)父進(jìn)程返回出錯(cuò),發(fā)生段錯(cuò)誤的情況。
由此可知,使用 vfork() 函數(shù)之后,子進(jìn)程對(duì)父進(jìn)程的影響是巨大的,其同步措施勢(shì)在必行。
退出進(jìn)程
當(dāng)一個(gè)進(jìn)程需要退出時(shí),需要調(diào)用退出函數(shù)。Linu__ 環(huán)境下使用 e__it() 函數(shù)退出進(jìn)程,其函數(shù)原型如下:
#include void e__it(int status);
e__it() 函數(shù)的參數(shù)表示進(jìn)程的退出狀態(tài),這個(gè)狀態(tài)的值是一個(gè)整型,保存在全局變量 $ ? 中,在 shell 中可以通過(guò) echo $? 來(lái)檢查退出狀態(tài)值。
注意:這個(gè)退出函數(shù)會(huì)深入內(nèi)核注銷掉進(jìn)程的內(nèi)核數(shù)據(jù)結(jié)構(gòu),并且釋放掉進(jìn)程的資源。
e__it函數(shù)與內(nèi)核函數(shù)的關(guān)系
e__it 函數(shù)是一個(gè)標(biāo)準(zhǔn)的庫(kù)函數(shù),其內(nèi)部封裝了 Linu__ 系統(tǒng)調(diào)用 e__it() 函數(shù)。兩者的主要區(qū)別在于 e__it() 函數(shù)會(huì)在用戶空間做一些善后工作,例如清理用戶的 I/O 緩沖區(qū),將其內(nèi)容寫入 磁盤文件等,之后才進(jìn)入內(nèi)核釋放用戶進(jìn)程的地址空間;而 e__it() 函數(shù)直接進(jìn)入內(nèi)核釋放用戶進(jìn)程的地址空間,所有用戶空間的緩沖區(qū)內(nèi)容都將丟失。
設(shè)置進(jìn)程所有者
每個(gè)進(jìn)程都有兩個(gè)用戶 ID,實(shí)際用戶 ID 和有效用戶 ID。通常這兩個(gè) ID 的值是相等的,其取值為進(jìn)程所有者的用戶 ID。但是,在有些場(chǎng)合需要改變進(jìn)程的有效用戶 ID。Linu__ 環(huán)境下使用 setuid() 函數(shù)改變一個(gè)進(jìn)程的實(shí)際用戶ID和有效用戶ID,其函數(shù)原型如下:
#include int setuid(uid_t uid);
setuid() 函數(shù)的參數(shù)表示改變后的新用戶 ID,如果成功修改當(dāng)前進(jìn)程的實(shí)際用戶 ID 和有效用戶 ID,函數(shù)返回值為 0;如果失敗,則返回 -1。只有兩種用戶可以修改進(jìn)程的實(shí)際用戶 ID 和有效用戶 ID:
根用戶:根用戶可以將進(jìn)程的實(shí)際用戶 ID 和有效用戶 ID 更換。
其他用戶:其該用戶的用戶 ID 等于進(jìn)程的實(shí)際用戶 ID 或者保存的用戶 ID。
也就是說(shuō),用戶可以將自己的有效用戶 ID 改回去。這種情況多出現(xiàn)于下面的情況:一個(gè)進(jìn)程需要具有某種權(quán)限,所以將其有效用戶 ID 設(shè)置為具有這種權(quán)限的用戶 ID,當(dāng)進(jìn)程不需要這種權(quán)限時(shí),進(jìn)程還原自己之前的有效用戶 ID,使自己的權(quán)限復(fù)原。下面給出一個(gè)修改的示例:
#include #include #include int main(void){ FILE __fp; uid_t uid; uid_t euid; uid = getuid(); /__ 得到進(jìn)程的實(shí)際用戶ID __/ euid = geteuid(); /__ 得到進(jìn)程的有效用戶ID __/ printf("the uid is : %d\n", uid); printf("the euid is : %d\n", euid); if(setuid(8000) == -1){ /__ 改變進(jìn)程的實(shí)際用戶ID和有效用戶ID __/ perror("fail to set uid"); e__it(1); } printf("after changing\n"); uid = getuid(); /__ 再次得到進(jìn)程的實(shí)際用戶ID __/ euid = geteuid(); /__ 再次得到進(jìn)程的有效用戶ID __/ printf("the uid is : %d\n", uid); printf("the euid is : %d\n", euid); return 0;}
程序運(yùn)行效果如下:
$./setuidthe uid is : 0the euid is : 0after changingthe uid is : 8000the euid is : 8000
本節(jié)參考:
《后臺(tái)開發(fā):核心技術(shù)與應(yīng)用實(shí)踐》
《Linu__+C程序設(shè)計(jì)大全》十一章:進(jìn)程控制
9. 孤兒進(jìn)程和僵尸進(jìn)程
基本概念
我們知道在 Uni__/Linu__ 中,正常情況下,子進(jìn)程是通過(guò)父進(jìn)程創(chuàng)建的,子進(jìn)程在創(chuàng)建新的進(jìn)程。子進(jìn)程的結(jié)束和父進(jìn)程的運(yùn)行是一個(gè)異步過(guò)程,即父進(jìn)程永遠(yuǎn)無(wú)法預(yù)測(cè)子進(jìn)程 到底什么時(shí)候結(jié)束。當(dāng)一個(gè)進(jìn)程完成它的工作終止之后,它的父進(jìn)程需要調(diào)用 wait() 或者 waitpid() 系統(tǒng)調(diào)用取得子進(jìn)程的終止?fàn)顟B(tài)。
孤兒進(jìn)程:一個(gè)父進(jìn)程退出,而它的一個(gè)或多個(gè)子進(jìn)程還在運(yùn)行,那么那些子進(jìn)程將成為孤兒進(jìn)程。孤兒進(jìn)程將被 init 進(jìn)程(進(jìn)程號(hào)為1)所收養(yǎng),并由 init 進(jìn)程對(duì)它們完成狀態(tài)收集工作____。____
僵尸進(jìn)程:一個(gè)進(jìn)程使用 fork 創(chuàng)建子進(jìn)程,如果子進(jìn)程退出,而父進(jìn)程并沒(méi)有調(diào)用 wait 或 waitpid 獲取子進(jìn)程的狀態(tài)信息,那么子進(jìn)程的進(jìn)程描述符仍然保存在系統(tǒng)中。這種進(jìn)程稱之為僵尸進(jìn)程。
問(wèn)題及危害
Uni__ 提供了一種機(jī)制可以保證只要父進(jìn)程想知道子進(jìn)程結(jié)束時(shí)的狀態(tài)信息,就可以得到。這種機(jī)制就是:在每個(gè)進(jìn)程退出的時(shí)候,內(nèi)核釋放該進(jìn)程所有的資源,包括打開的文件,占用的內(nèi)存等。但是仍然為其保留一定的信息(包括進(jìn)程號(hào) the process ID,退出狀態(tài) the termination status of the process,運(yùn)行時(shí)間 the amount of CPU time taken by the process 等)。直到父進(jìn)程通過(guò) wait / waitpid 來(lái)取時(shí)才釋放。但這樣就導(dǎo)致了問(wèn)題,如果進(jìn)程不調(diào)用 wait / waitpid 的話, 那么保留的那段信息就不會(huì)釋放,其進(jìn)程號(hào)就會(huì)一直被占用,但是系統(tǒng)所能使用的進(jìn)程號(hào)是有限的,如果大量的產(chǎn)生僵死進(jìn)程,將因?yàn)闆](méi)有可用的進(jìn)程號(hào)而導(dǎo)致系統(tǒng)不能產(chǎn)生新的進(jìn)程。此即為僵尸進(jìn)程的危害,應(yīng)當(dāng)避免。
孤兒進(jìn)程是沒(méi)有父進(jìn)程的進(jìn)程,孤兒進(jìn)程這個(gè)重任就落到了 init 進(jìn)程身上,init 進(jìn)程就好像是一個(gè)民政局,專門負(fù)責(zé)處理孤兒進(jìn)程的善后工作。每當(dāng)出現(xiàn)一個(gè)孤兒進(jìn)程的時(shí)候,內(nèi)核就把孤 兒進(jìn)程的父進(jìn)程設(shè)置為 init,而 init 進(jìn)程會(huì)循環(huán)地 wait() 它的已經(jīng)退出的子進(jìn)程。這樣,當(dāng)一個(gè)孤兒進(jìn)程凄涼地結(jié)束了其生命周期的時(shí)候,init 進(jìn)程就會(huì)代表黨和政府出面處理它的一切善后工作。因此孤兒進(jìn)程并不會(huì)有什么危害。
任何一個(gè)子進(jìn)程(init除外)在e__it() 之后,并非馬上就消失掉,而是留下一個(gè)稱為僵尸進(jìn)程 (Zombie) 的數(shù)據(jù)結(jié)構(gòu),等待父進(jìn)程處理。這是每個(gè)子進(jìn)程在結(jié)束時(shí)都要經(jīng)過(guò)的階段。如果子進(jìn)程在e__it()之后,父進(jìn)程沒(méi)有來(lái)得及處理,這時(shí)用 ps 命令就能看到子進(jìn)程的狀態(tài)是 Z。如果父進(jìn)程能及時(shí)處理,可能用 ps 命令就來(lái)不及看到子進(jìn)程的僵尸狀態(tài),但這并不等于子進(jìn)程不經(jīng)過(guò)僵尸狀態(tài)。如果父進(jìn)程在子進(jìn)程結(jié)束之前退出,則子進(jìn)程將由 init 接管。init 將會(huì)以父進(jìn)程的身份對(duì)僵尸狀態(tài)的子進(jìn)程進(jìn)行處理。
僵尸進(jìn)程危害場(chǎng)景:
例如有個(gè)進(jìn)程,它定期的產(chǎn)生一個(gè)子進(jìn)程,這個(gè)子進(jìn)程需要做的事情很少,做完它該做的事情之后就退出了,因此這個(gè)子進(jìn)程的生命周期很短,但是,父進(jìn)程只管生成新的子進(jìn)程,至于子進(jìn)程退出之后的事情,則一概不聞不問(wèn),這樣,系統(tǒng)運(yùn)行上一段時(shí)間之后,系統(tǒng)中就會(huì)存在很多的僵死進(jìn)程,倘若用 ps 命令查看的話,就會(huì)看到很多狀態(tài)為 Z 的進(jìn)程。嚴(yán)格地來(lái)說(shuō),僵死進(jìn)程并不是問(wèn)題的根源,罪魁禍?zhǔn)资钱a(chǎn)生出大量僵死進(jìn)程的那個(gè)父進(jìn)程。因此,當(dāng)我們尋求如何消滅系統(tǒng)中大量的僵死進(jìn)程時(shí),答案就是把產(chǎn)生大 量僵死進(jìn)程的那個(gè)元兇槍斃掉(也就是通過(guò) kill 發(fā)送 SIGTERM 或者 SIGKILL 信號(hào)啦)。槍斃了元兇進(jìn)程之后,它產(chǎn)生的僵死進(jìn)程就變成了孤兒進(jìn)程,這些孤兒進(jìn)程會(huì)被 init 進(jìn)程接管,init 進(jìn)程會(huì) wait() 這些孤兒進(jìn)程,釋放它們占用的系統(tǒng)進(jìn)程表中的資源,這樣,這些已經(jīng)僵死的孤兒進(jìn)程就能瞑目而去了。
測(cè)試代碼
孤兒進(jìn)程測(cè)試程序如下所示:
#include #include #include #include int main(){ pid_t pid; //創(chuàng)建一個(gè)進(jìn)程 pid = fork(); //創(chuàng)建失敗 if (pid < 0) { perror("fork error:"); e__it(1); } //子進(jìn)程 if (pid == 0) { printf("I am the child process.\n"); //輸出進(jìn)程ID和父進(jìn)程ID printf("pid: %d\tppid:%d\n",getpid(),getppid()); printf("I will sleep five seconds.\n"); //睡眠5s,保證父進(jìn)程先退出 sleep(5); printf("pid: %d\tppid:%d\n",getpid(),getppid()); printf("child process is e__ited.\n"); } //父進(jìn)程 else { printf("I am father process.\n"); //父進(jìn)程睡眠1s,保證子進(jìn)程輸出進(jìn)程id sleep(1); printf("father process is e__ited."); } return 0;}
僵尸進(jìn)程測(cè)試程序如下所示:
#include #include #include #include int main(){ pid_t pid; pid = fork(); if (pid < 0) { perror("fork error:"); e__it(1); } else if (pid == 0) { printf("I am child process.I am e__iting.\n"); e__it(0); } printf("I am father process.I will sleep two seconds\n"); //等待子進(jìn)程先退出 sleep(2); //輸出進(jìn)程信息 system("ps -o pid,ppid,state,tty,command"); printf("father process is e__iting.\n"); return 0;}
測(cè)試結(jié)果如下所示:
僵尸進(jìn)程解決辦法
通過(guò)信號(hào)機(jī)制
子進(jìn)程退出時(shí)向父進(jìn)程發(fā)送SIGCHILD信號(hào),父進(jìn)程處理SIGCHILD信號(hào)。在信號(hào)處理函數(shù)中調(diào)用wait進(jìn)行處理僵尸進(jìn)程
fork兩次
將子進(jìn)程成為孤兒進(jìn)程,從而其的父進(jìn)程變?yōu)?init 進(jìn)程,通過(guò) init 進(jìn)程可以處理僵尸進(jìn)程
10. 守護(hù)進(jìn)程
Linu__ Daemon(守護(hù)進(jìn)程)是運(yùn)行在后臺(tái)的一種特殊進(jìn)程。它獨(dú)立于控制終端并且周期性地執(zhí)行某種任務(wù)或等待處理某些發(fā)生的事件。它不需要用戶輸入就能運(yùn)行而且提供某種服務(wù),不是對(duì)整個(gè)系統(tǒng)就是對(duì)某個(gè)用戶程序提供服務(wù)。Linu__系統(tǒng)的大多數(shù)服務(wù)器就是通過(guò)守護(hù)進(jìn)程實(shí)現(xiàn)的。常見(jiàn)的守護(hù)進(jìn)程包括系統(tǒng)日志進(jìn)程syslogd、 web服務(wù)器httpd、郵件服務(wù)器sendmail和數(shù)據(jù)庫(kù)服務(wù)器mysqld等。
守護(hù)進(jìn)程一般在系統(tǒng)啟動(dòng)時(shí)開始運(yùn)行,除非強(qiáng)行終止,否則直到系統(tǒng)關(guān)機(jī)都保持運(yùn)行。守護(hù)進(jìn)程經(jīng)常以超級(jí)用戶(root)權(quán)限運(yùn)行,因?yàn)樗鼈円褂锰厥獾亩丝?1-1024)或訪問(wèn)某些特殊的資源。
一個(gè)守護(hù)進(jìn)程的父進(jìn)程是init進(jìn)程,因?yàn)樗嬲母高M(jìn)程在fork出子進(jìn)程后就先于子進(jìn)程e__it退出了,所以它是一個(gè)由init繼承的孤兒進(jìn)程。守護(hù)進(jìn)程是非交互式程序,沒(méi)有控制終端,所以任何輸出,無(wú)論是向標(biāo)準(zhǔn)輸出設(shè)備stdout還是標(biāo)準(zhǔn)出錯(cuò)設(shè)備stderr的輸出都需要特殊處理。
守護(hù)進(jìn)程的名稱通常以d結(jié)尾,比如sshd、__inetd、crond等
編寫守護(hù)進(jìn)程的一般步驟步驟:
在父進(jìn)程中執(zhí)行 fork 并 e__it 推出;
在子進(jìn)程中調(diào)用 setsid 函數(shù)創(chuàng)建新的會(huì)話;
在子進(jìn)程中調(diào)用 chdir 函數(shù),讓根目錄 / 成為子進(jìn)程的工作目錄;
在子進(jìn)程中調(diào)用umask函數(shù),設(shè)置進(jìn)程的umask 為 0;
在子進(jìn)程中關(guān)閉任何不需要的文件描述符。
11. 上下文切換
上下文切換,有時(shí)也稱做進(jìn)程切換或任務(wù)切換,是指CPU從一個(gè)進(jìn)程或線程切換到另一個(gè)進(jìn)程或線程。在操作系統(tǒng)中,CPU 切換到另一個(gè)進(jìn)程需要保存當(dāng)前進(jìn)程的狀態(tài)并恢復(fù)另一個(gè)進(jìn)程的狀態(tài):當(dāng)前運(yùn)行任務(wù)轉(zhuǎn)為就緒(或者掛起、刪除)狀態(tài),另一個(gè)被選定的就緒任務(wù)成為當(dāng)前任務(wù)
三、死鎖
資源分類:(1)可重用資源;(2)消耗資源
1. 什么是死鎖
造成死鎖的原因就是多個(gè)線程或進(jìn)程對(duì)同一個(gè)資源的爭(zhēng)搶或相互依賴。一個(gè)最簡(jiǎn)單的解釋就是你去面試,面試官問(wèn)你告訴我什么是死鎖,我就錄用你,你回答面試官你錄用我,我告訴你。
如果一個(gè)進(jìn)程集合里面的每個(gè)進(jìn)程都在等待只能由這個(gè)集合中的其他一個(gè)進(jìn)程(包括他自身)才能引發(fā)的事件,這種情況就是死鎖。
這個(gè)定義可能有點(diǎn)拗口,下面用一個(gè)簡(jiǎn)單例子說(shuō)明。
資源 A、B,進(jìn)程 C、D 描述如下:
資源 A 和資源 B,都是不可剝奪資源,現(xiàn)在進(jìn)程 C 已經(jīng)申請(qǐng)了資源 A,進(jìn)程 D 也申請(qǐng)了資源 B,進(jìn)程 C 接下來(lái)的操作需要用到資源 B,而進(jìn)程 D 恰好也在申請(qǐng)資源A,進(jìn)程 C、D 都得不到接下來(lái)的資源,那么就引發(fā)了死鎖。
然后套用回去定義:如果一個(gè)進(jìn)程集合里面(進(jìn)程 C 和進(jìn)程 D)的每個(gè)進(jìn)程(進(jìn)程 C 和進(jìn)程 D)都在等待只能由這個(gè)集合中的其他一個(gè)進(jìn)程(對(duì)于進(jìn)程 C,他在等進(jìn)程 D;對(duì)于進(jìn)程 D,他在等進(jìn)程 C)才能引發(fā)的事件(釋放相應(yīng)資源)。
這里的資源包括了軟的資源(代碼塊)和硬的資源(例如掃描儀)。資源一般可以分兩種:可剝奪資源(Preemptable)和不可剝奪資源 (Nonpreemptable)。一般來(lái)說(shuō)對(duì)于由可剝奪資源引起的死鎖可以由系統(tǒng)的重新分配資源來(lái)解決,所以一般來(lái)說(shuō)大家說(shuō)的死鎖都是由于不可剝奪資源所引起的。
2. 死鎖的必要條件
互斥:每個(gè)資源要么已經(jīng)分配給了一個(gè)進(jìn)程,要么就是可用的。
占有和等待:已經(jīng)得到了某個(gè)資源的進(jìn)程可以再請(qǐng)求新的資源。
不可搶占:已經(jīng)分配給一個(gè)進(jìn)程的資源不能強(qiáng)制性地被搶占,它只能被占有它的進(jìn)程顯式地釋放。
循環(huán)等待:有兩個(gè)或者兩個(gè)以上的進(jìn)程組成一條環(huán)路,該環(huán)路中的每個(gè)進(jìn)程都在等待下一個(gè)進(jìn)程所占有的資源。
3. 死鎖的處理方法
1. 處理死鎖的策略
鴕鳥策略
把頭埋在沙子里,假裝根本沒(méi)發(fā)生問(wèn)題。
因?yàn)榻鉀Q死鎖問(wèn)題的代價(jià)很高,因此鴕鳥策略這種不采取任務(wù)措施的方案會(huì)獲得更高的性能。當(dāng)發(fā)生死鎖時(shí)不會(huì)對(duì)用戶造成多大影響,或發(fā)生死鎖的概率很低,可以采用鴕鳥策略。
大多數(shù)操作系統(tǒng),包括 Uni__,Linu__ 和 Windows,處理死鎖問(wèn)題的辦法僅僅是忽略它。
檢測(cè)死鎖并且恢復(fù)。
仔細(xì)地對(duì)資源進(jìn)行動(dòng)態(tài)分配,以避免死鎖。
通過(guò)破除死鎖四個(gè)必要條件之一,來(lái)防止死鎖產(chǎn)生。
2. 死鎖檢測(cè)與死鎖恢復(fù)
不試圖阻止死鎖,而是當(dāng)檢測(cè)到死鎖發(fā)生時(shí),采取措施進(jìn)行恢復(fù)。
(一)每種類型一個(gè)資源的死鎖檢測(cè)
上圖為資源分配圖,其中方框表示資源,圓圈表示進(jìn)程。資源指向進(jìn)程表示該資源已經(jīng)分配給該進(jìn)程,進(jìn)程指向資源表示進(jìn)程請(qǐng)求獲取該資源。
圖 a 可以抽取出環(huán),如圖 b,它滿足了環(huán)路等待條件,因此會(huì)發(fā)生死鎖。
每種類型一個(gè)資源的死鎖檢測(cè)算法是通過(guò)檢測(cè)有向圖是否存在環(huán)來(lái)實(shí)現(xiàn),從一個(gè)節(jié)點(diǎn)出發(fā)進(jìn)行深度優(yōu)先搜索,對(duì)訪問(wèn)過(guò)的節(jié)點(diǎn)進(jìn)行標(biāo)記,如果訪問(wèn)了已經(jīng)標(biāo)記的節(jié)點(diǎn),就表示有向圖存在環(huán),也就是檢測(cè)到死鎖的發(fā)生。
(二)每種類型多個(gè)資源的死鎖檢測(cè)
上圖中,有三個(gè)進(jìn)程四個(gè)資源,每個(gè)數(shù)據(jù)代表的含義如下:
E 向量:資源總量
A 向量:資源剩余量
C 矩陣:每個(gè)進(jìn)程所擁有的資源數(shù)量,每一行都代表一個(gè)進(jìn)程擁有資源的數(shù)量
R 矩陣:每個(gè)進(jìn)程請(qǐng)求的資源數(shù)量
進(jìn)程 P1 和 P2 所請(qǐng)求的資源都得不到滿足,只有進(jìn)程 P3 可以,讓 P3 執(zhí)行,之后釋放 P3 擁有的資源,此時(shí) A = (2 2 2 0)。P2 可以執(zhí)行,執(zhí)行后釋放 P2 擁有的資源,A = (4 2 2 1) 。P1 也可以執(zhí)行。所有進(jìn)程都可以順利執(zhí)行,沒(méi)有死鎖。
算法總結(jié)如下:
每個(gè)進(jìn)程最開始時(shí)都不被標(biāo)記,執(zhí)行過(guò)程有可能被標(biāo)記。當(dāng)算法結(jié)束時(shí),任何沒(méi)有被標(biāo)記的進(jìn)程都是死鎖進(jìn)程。
尋找一個(gè)沒(méi)有標(biāo)記的進(jìn)程 Pi,它所請(qǐng)求的資源小于等于 A。
如果找到了這樣一個(gè)進(jìn)程,那么將 C 矩陣的第 i 行向量加到 A 中,標(biāo)記該進(jìn)程,并轉(zhuǎn)回 1。
如果沒(méi)有這樣一個(gè)進(jìn)程,算法終止。
(三)死鎖恢復(fù)
利用搶占恢復(fù)
利用回滾恢復(fù)
通過(guò)殺死進(jìn)程恢復(fù)
3. 死鎖預(yù)防
在程序運(yùn)行之前預(yù)防發(fā)生死鎖,確保系統(tǒng)永遠(yuǎn)不會(huì)進(jìn)入死鎖狀態(tài)。
(一)破壞互斥條件
例如假脫機(jī)打印機(jī)技術(shù)允許若干個(gè)進(jìn)程同時(shí)輸出,唯一真正請(qǐng)求物理打印機(jī)的進(jìn)程是打印機(jī)守護(hù)進(jìn)程。(把互斥地封裝成可以同時(shí)訪問(wèn)的,例如:打印機(jī)的緩存)
(二)破壞占有和等待條件
一種實(shí)現(xiàn)方式是規(guī)定所有進(jìn)程在開始執(zhí)行前請(qǐng)求所需要的全部資源。
但是,這種策略也有如下缺點(diǎn):
在許多情況下,一個(gè)進(jìn)程在執(zhí)行之前不可能知道它所需要的全部資源。這是由于進(jìn)程在執(zhí)行時(shí)是動(dòng)態(tài)的,不可預(yù)測(cè)的;
資源利用率低。無(wú)論所分資源何時(shí)用到,一個(gè)進(jìn)程只有在占有所需的全部資源后才能執(zhí)行。即使有些資源最后才被該進(jìn)程用到一次,但該進(jìn)程在生存期間卻一直占有它們,造成長(zhǎng)期占著不用的狀況。這顯然是一種極大的資源浪費(fèi);
降低了進(jìn)程的并發(fā)性。因?yàn)橘Y源有限,又加上存在浪費(fèi),能分配到所需全部資源的進(jìn)程個(gè)數(shù)就必然少了。
(三)破壞不可搶占條件
允許進(jìn)程強(qiáng)行從占有者那里奪取某些資源。就是說(shuō),當(dāng)一個(gè)進(jìn)程已占有了某些資源,它又申請(qǐng)新的資源,但不能立即被滿足時(shí),它必須釋放所占有的全部資源,以后再重新申請(qǐng)。它所釋放的資源可以分配給其它進(jìn)程。這就相當(dāng)于該進(jìn)程占有的資源被隱蔽地強(qiáng)占了。這種預(yù)防死鎖的方法實(shí)現(xiàn)起來(lái)困難,會(huì)降低系統(tǒng)性能。
(四)破壞循環(huán)等待
實(shí)行資源有序分配策略。采用這種策略,即把資源事先分類編號(hào),按號(hào)分配,使進(jìn)程在申請(qǐng),占用資源時(shí)不會(huì)形成環(huán)路。所有進(jìn)程對(duì)資源的請(qǐng)求必須嚴(yán)格按資源序號(hào)遞增的順序提出。進(jìn)程占用了小號(hào)資源,才能申請(qǐng)大號(hào)資源,就不會(huì)產(chǎn)生環(huán)路,從而預(yù)防了死鎖。這種策略與前面的策略相比,資源的利用率和系統(tǒng)吞吐量都有很大提高,但是也存在以下缺點(diǎn):
限制了進(jìn)程對(duì)資源的請(qǐng)求,同時(shí)給系統(tǒng)中所有資源合理編號(hào)也是件困難事,并增加了系統(tǒng)開銷;
為了遵循按編號(hào)申請(qǐng)的次序,暫不使用的資源也需要提前申請(qǐng),從而增加了進(jìn)程對(duì)資源的占用時(shí)間。
4. 死鎖避免
在程序運(yùn)行時(shí)避免發(fā)生死鎖,在使用前進(jìn)行判斷,只允許不會(huì)出現(xiàn)死鎖的進(jìn)程請(qǐng)求資源。
(一)安全狀態(tài)
圖 a 的第二列 Has 表示已擁有的資源數(shù),第三列 Ma__ 表示總共需要的資源數(shù),F(xiàn)ree 表示還有可以使用的資源數(shù)。從圖 a 開始出發(fā),先讓 B 擁有所需的所有資源(圖 b),運(yùn)行結(jié)束后釋放 B,此時(shí) Free 變?yōu)?5(圖 c);接著以同樣的方式運(yùn)行 C 和 A,使得所有進(jìn)程都能成功運(yùn)行,因此可以稱圖 a 所示的狀態(tài)時(shí)安全的。
定義:如果沒(méi)有死鎖發(fā)生,并且即使所有進(jìn)程突然請(qǐng)求對(duì)資源的最大需求,也仍然存在某種調(diào)度次序能夠使得每一個(gè)進(jìn)程運(yùn)行完畢,則稱該狀態(tài)是安全的。
安全狀態(tài)的檢測(cè)與死鎖的檢測(cè)類似,因?yàn)榘踩珷顟B(tài)必須要求不能發(fā)生死鎖。下面的銀行家算法與死鎖檢測(cè)算法非常類似,可以結(jié)合著做參考對(duì)比。
(二)單個(gè)資源的銀行家算法
一個(gè)小城鎮(zhèn)的銀行家,他向一群客戶分別承諾了一定的貸款額度,算法要做的是判斷對(duì)請(qǐng)求的滿足是否會(huì)進(jìn)入不安全狀態(tài),如果是,就拒絕請(qǐng)求;否則予以分配。
不安全狀態(tài),因此算法會(huì)拒絕之前的請(qǐng)求,從而避免進(jìn)入圖 c 中的狀態(tài)。
(三)多個(gè)資源的銀行家算法
有五個(gè)進(jìn)程,四個(gè)資源。左邊的圖表示已經(jīng)分配的資源,右邊的圖表示還需要分配的資源。最右邊的 E、P 以及 A 分別表示:總資源、已分配資源以及可用資源,注意這三個(gè)為向量,而不是具體數(shù)值,例如 A=(1020),表示 4 個(gè)資源分別還剩下 1/0/2/0。
檢查一個(gè)狀態(tài)是否安全的算法如下:
查找右邊的矩陣是否存在一行小于等于向量 A。如果不存在這樣的行,那么系統(tǒng)將會(huì)發(fā)生死鎖,狀態(tài)是不安全的。
假若找到這樣一行,將該進(jìn)程標(biāo)記為終止,并將其已分配資源加到 A 中。
重復(fù)以上兩步,直到所有進(jìn)程都標(biāo)記為終止,則狀態(tài)時(shí)安全的。
如果一個(gè)狀態(tài)不是安全的,需要拒絕進(jìn)入這個(gè)狀態(tài)。
4. 如何在寫程序的時(shí)候就避免死鎖
所謂的死鎖呢,發(fā)生的主要原因在于了有多個(gè)進(jìn)程去競(jìng)爭(zhēng)資源,也就是同時(shí)去搶占。
可以自己寫一個(gè)支持多線程的消息管理類,單開一個(gè)線程訪問(wèn)獨(dú)占資源,其它線程用消息交互實(shí)現(xiàn)間接訪問(wèn)。這種機(jī)制適應(yīng)性強(qiáng)、效率高,更適合多核環(huán)境。
四、內(nèi)存管理
1. 虛擬內(nèi)存
虛擬內(nèi)存的目的是為了讓物理內(nèi)存擴(kuò)充成更大的邏輯內(nèi)存,從而讓程序獲得更多的可用內(nèi)存。
為了更好的管理內(nèi)存,操作系統(tǒng)將內(nèi)存抽象成地址空間。每個(gè)程序擁有自己的地址空間,這個(gè)地址空間被分割成多個(gè)塊,每一塊稱為一頁(yè)。這些頁(yè)被映射到物理內(nèi)存,但不需要映射到連續(xù)的物理內(nèi)存,也不需要所有頁(yè)都必須在物理內(nèi)存中。當(dāng)程序引用到一部分不在物理內(nèi)存中的地址空間時(shí),由硬件執(zhí)行必要的映射,將缺失的部分裝入物理內(nèi)存并重新執(zhí)行失敗的指令。
從上面的描述中可以看出,虛擬內(nèi)存允許程序不用將地址空間中的每一頁(yè)都映射到物理內(nèi)存,也就是說(shuō)一個(gè)程序不需要全部調(diào)入內(nèi)存就可以運(yùn)行,這使得有限的內(nèi)存運(yùn)行大程序稱為可能。例如有一臺(tái)計(jì)算機(jī)可以產(chǎn)生 16 位地址,那么一個(gè)程序的地址空間范圍是 0~64K。該計(jì)算機(jī)只有 32KB 的物理內(nèi)存,虛擬內(nèi)存技術(shù)允許該計(jì)算機(jī)運(yùn)行一個(gè) 64K 大小的程序。
2. 分頁(yè)系統(tǒng)地址映射
內(nèi)存管理單元(MMU):管理著地址空間和物理內(nèi)存的轉(zhuǎn)換。
頁(yè)表(Page table):頁(yè)(地址空間)和頁(yè)框(物理內(nèi)存空間)的映射表。例如下圖中,頁(yè)表的第 0 個(gè)表項(xiàng)為 010,表示第 0 個(gè)頁(yè)映射到第 2 個(gè)頁(yè)框。頁(yè)表項(xiàng)的最后一位用來(lái)標(biāo)記頁(yè)是否在內(nèi)存中。
下圖的頁(yè)表存放著 16 個(gè)頁(yè),這 16 個(gè)頁(yè)需要用 4 個(gè)比特位來(lái)進(jìn)行索引定位。因此對(duì)于虛擬地址(0010 000000000100),前 4 位是用來(lái)存儲(chǔ)頁(yè)面號(hào),而后 12 位存儲(chǔ)在頁(yè)中的偏移量。
(0010 000000000100)根據(jù)前 4 位得到頁(yè)號(hào)為 2,讀取表項(xiàng)內(nèi)容為(110 1),它的前 3 為為頁(yè)框號(hào),最后 1 位表示該頁(yè)在內(nèi)存中。最后映射得到物理內(nèi)存地址為(110 000000000100)。
3. 頁(yè)面置換算法
在程序運(yùn)行過(guò)程中,如果要訪問(wèn)的頁(yè)面不在內(nèi)存中,就發(fā)生缺頁(yè)中斷從而將該頁(yè)調(diào)入內(nèi)存中。此時(shí)如果內(nèi)存已無(wú)空閑空間,系統(tǒng)必須從內(nèi)存中調(diào)出一個(gè)頁(yè)面到磁盤對(duì)換區(qū)中來(lái)騰出空間。
頁(yè)面置換算法和緩存淘汰策略類似,可以將內(nèi)存看成磁盤的緩存。在緩存系統(tǒng)中,緩存的大小有限,當(dāng)有新的緩存到達(dá)時(shí),需要淘汰一部分已經(jīng)存在的緩存,這樣才有空間存放新的緩存數(shù)據(jù)。
頁(yè)面置換算法的主要目標(biāo)是使頁(yè)面置換頻率最低(也可以說(shuō)缺頁(yè)率最低)。
1. 最佳
Optimal
所選擇的被換出的頁(yè)面將是最長(zhǎng)時(shí)間內(nèi)不再被訪問(wèn),通??梢员WC獲得最低的缺頁(yè)率。
是一種理論上的算法,因?yàn)闊o(wú)法知道一個(gè)頁(yè)面多長(zhǎng)時(shí)間不再被訪問(wèn)。
舉例:一個(gè)系統(tǒng)為某進(jìn)程分配了三個(gè)物理塊,并有如下頁(yè)面引用序列:
開始運(yùn)行時(shí),先將 7, 0, 1 三個(gè)頁(yè)面裝入內(nèi)存。當(dāng)進(jìn)程要訪問(wèn)頁(yè)面 2 時(shí),產(chǎn)生缺頁(yè)中斷,會(huì)將頁(yè)面 7 換出,因?yàn)轫?yè)面 7 再次被訪問(wèn)的時(shí)間最長(zhǎng)。
2. 最近最久未使用
LRU, Least Recently Used
雖然無(wú)法知道將來(lái)要使用的頁(yè)面情況,但是可以知道過(guò)去使用頁(yè)面的情況。LRU 將最近最久未使用的頁(yè)面換出。
為了實(shí)現(xiàn) LRU,需要在內(nèi)存中維護(hù)一個(gè)所有頁(yè)面的鏈表。當(dāng)一個(gè)頁(yè)面被訪問(wèn)時(shí),將這個(gè)頁(yè)面移到鏈表表頭。這樣就能保證鏈表表尾的頁(yè)面時(shí)最近最久未訪問(wèn)的。
因?yàn)槊看卧L問(wèn)都需要更新鏈表,因此這種方式實(shí)現(xiàn)的 LRU 代價(jià)很高。
3. 最近未使用
NRU, Not Recently Used
每個(gè)頁(yè)面都有兩個(gè)狀態(tài)位:R 與 M,當(dāng)頁(yè)面被訪問(wèn)時(shí)設(shè)置頁(yè)面的 R=1,當(dāng)頁(yè)面被修改時(shí)設(shè)置 M=1。其中 R 位會(huì)定時(shí)被清零。可以將頁(yè)面分成以下四類:
R=0,M=0
R=0,M=1
R=1,M=0
R=1,M=1
當(dāng)發(fā)生缺頁(yè)中斷時(shí),NRU 算法隨機(jī)地從類編號(hào)最小的非空類中挑選一個(gè)頁(yè)面將它換出。
NRU 優(yōu)先換出已經(jīng)被修改的臟頁(yè)面(R=0,M=1),而不是被頻繁使用的干凈頁(yè)面(R=1,M=0)。
4. 先進(jìn)先出
FIFO, First In First Out
選擇換出的頁(yè)面是最先進(jìn)入的頁(yè)面。
該算法會(huì)將那些經(jīng)常被訪問(wèn)的頁(yè)面也被換出,從而使缺頁(yè)率升高。
5. 第二次機(jī)會(huì)算法
FIFO 算法可能會(huì)把經(jīng)常使用的頁(yè)面置換出去,為了避免這一問(wèn)題,對(duì)該算法做一個(gè)簡(jiǎn)單的修改:
當(dāng)頁(yè)面被訪問(wèn) (讀或?qū)? 時(shí)設(shè)置該頁(yè)面的 R 位為 1。需要替換的時(shí)候,檢查最老頁(yè)面的 R 位。如果 R 位是 0,那么這個(gè)頁(yè)面既老又沒(méi)有被使用,可以立刻置換掉;如果是 1,就將 R 位清 0,并把該頁(yè)面放到鏈表的尾端,修改它的裝入時(shí)間使它就像剛裝入的一樣,然后繼續(xù)從鏈表的頭部開始搜索。
6. 時(shí)鐘
Clock
第二次機(jī)會(huì)算法需要在鏈表中移動(dòng)頁(yè)面,降低了效率。時(shí)鐘算法使用環(huán)形鏈表將頁(yè)面鏈接起來(lái),再使用一個(gè)指針指向最老的頁(yè)面。
4. 分段
虛擬內(nèi)存采用的是分頁(yè)技術(shù),也就是將地址空間劃分成固定大小的頁(yè),每一頁(yè)再與內(nèi)存進(jìn)行映射。
下圖為一個(gè)編譯器在編譯過(guò)程中建立的多個(gè)表,有 4 個(gè)表是動(dòng)態(tài)增長(zhǎng)的,如果使用分頁(yè)系統(tǒng)的一維地址空間,動(dòng)態(tài)增長(zhǎng)的特點(diǎn)會(huì)導(dǎo)致覆蓋問(wèn)題的出現(xiàn)。
分段的做法是把每個(gè)表分成段,一個(gè)段構(gòu)成一個(gè)獨(dú)立的地址空間。每個(gè)段的長(zhǎng)度可以不同,并且可以動(dòng)態(tài)增長(zhǎng)。
5. 段頁(yè)式
程序的地址空間劃分成多個(gè)擁有獨(dú)立地址空間的段,每個(gè)段上的地址空間劃分成大小相同的頁(yè)。這樣既擁有分段系統(tǒng)的共享和保護(hù),又擁有分頁(yè)系統(tǒng)的虛擬內(nèi)存功能。
6. 分頁(yè)與分段的比較
對(duì)程序員的透明性:分頁(yè)透明,但是分段需要程序員顯示劃分每個(gè)段。
地址空間的維度:分頁(yè)是一維地址空間,分段是二維的。
大小是否可以改變:頁(yè)的大小不可變,段的大小可以動(dòng)態(tài)改變。
出現(xiàn)的原因:分頁(yè)主要用于實(shí)現(xiàn)虛擬內(nèi)存,從而獲得更大的地址空間;分段主要是為了使程序和數(shù)據(jù)可以被劃分為邏輯上獨(dú)立的地址空間并且有助于共享和保護(hù)。
五、設(shè)備管理
1. 磁盤結(jié)構(gòu)
盤面(Platter):一個(gè)磁盤有多個(gè)盤面;
磁道(Track):盤面上的圓形帶狀區(qū)域,一個(gè)盤面可以有多個(gè)磁道;
扇區(qū)(Track Sector):磁道上的一個(gè)弧段,一個(gè)磁道可以有多個(gè)扇區(qū),它是最小的物理儲(chǔ)存單位,目前主要有 512 bytes 與 4 K 兩種大小;
磁頭(Head):與盤面非常接近,能夠?qū)⒈P面上的磁場(chǎng)轉(zhuǎn)換為電信號(hào)(讀),或者將電信號(hào)轉(zhuǎn)換為盤面的磁場(chǎng)(寫);
制動(dòng)手臂(Actuator arm):用于在磁道之間移動(dòng)磁頭;
主軸(Spindle):使整個(gè)盤面轉(zhuǎn)動(dòng)。
2. 磁盤調(diào)度算法
讀寫一個(gè)磁盤塊的時(shí)間的影響因素有:
旋轉(zhuǎn)時(shí)間(主軸旋轉(zhuǎn)磁盤,使得磁頭移動(dòng)到適當(dāng)?shù)纳葏^(qū)上)
尋道時(shí)間(制動(dòng)手臂移動(dòng),使得磁頭移動(dòng)到適當(dāng)?shù)拇诺郎?
實(shí)際的數(shù)據(jù)傳輸時(shí)間
其中,尋道時(shí)間最長(zhǎng),因此磁盤調(diào)度的主要目標(biāo)是使磁盤的平均尋道時(shí)間最短。
1. 先來(lái)先服務(wù)
FCFS, First Come First Served
按照磁盤請(qǐng)求的順序進(jìn)行調(diào)度
公平對(duì)待所有進(jìn)程
在有很多進(jìn)程的情況下,接近隨機(jī)調(diào)度的性能
優(yōu)點(diǎn)是公平和簡(jiǎn)單。缺點(diǎn)也很明顯,因?yàn)槲磳?duì)尋道做任何優(yōu)化,使平均尋道時(shí)間可能較長(zhǎng)。
2. 最短尋道時(shí)間優(yōu)先
SSTF, Shortest Seek Time First
優(yōu)先調(diào)度與當(dāng)前磁頭所在磁道距離最近的磁道。
雖然平均尋道時(shí)間比較低,但是不夠公平。如果新到達(dá)的磁道請(qǐng)求總是比一個(gè)在等待的磁道請(qǐng)求近,那么在等待的磁道請(qǐng)求會(huì)一直等待下去,也就是出現(xiàn)饑餓現(xiàn)象。具體來(lái)說(shuō),兩邊的磁道請(qǐng)求更容易出現(xiàn)饑餓現(xiàn)象。
3. 電梯算法
SCAN
電梯總是保持一個(gè)方向運(yùn)行,直到該方向沒(méi)有請(qǐng)求為止,然后改變運(yùn)行方向。
電梯算法(掃描算法)和電梯的運(yùn)行過(guò)程類似,總是按一個(gè)方向來(lái)進(jìn)行磁盤調(diào)度,直到該方向上沒(méi)有未完成的磁盤請(qǐng)求,然后改變方向。
因?yàn)榭紤]了移動(dòng)方向,因此所有的磁盤請(qǐng)求都會(huì)被滿足,解決了 SSTF 的饑餓問(wèn)題。
六、鏈接
1. 編譯系統(tǒng)
以下是一個(gè) hello.c 程序:
#include int main(){ printf("hello, world\n"); return 0;}
在 Uni__ 系統(tǒng)上,由編譯器把源文件轉(zhuǎn)換為目標(biāo)文件。
gcc -o hello hello.c
這個(gè)過(guò)程大致如下:
1. 預(yù)處理階段 (Preprocessing phase)
預(yù)處理(cpp)根據(jù)以字符 # 開頭的命令,修改原始的 C 程序,生成擴(kuò)展名為 .i 的文件。
$ gcc -E hello.c -o hello.i
2. 編譯階段 (Compilation phase)
編譯器(cc1)將文本文件 hello.i 翻譯成文本文件 hello.s,它包含一個(gè)匯編語(yǔ)言程序。
$ gcc -S hello.i -o hello.s
3. 匯編階段 (Assembly phase)
編譯器(as)將 hello.s 翻譯成機(jī)器語(yǔ)言指令,把這些指令打包成一種叫做可重定位目標(biāo)程序(relocatable object program)的格式,并將結(jié)果保存在目標(biāo)文件 hello.o 中。
$ as hello.s -o hello.o
4. 鏈接階段 (Linking phase)
printf 函數(shù)是標(biāo)準(zhǔn) C 庫(kù)中的一個(gè)函數(shù),在 printf.o 這個(gè)單獨(dú)預(yù)編譯好的目標(biāo)文件中。連接器(ld)將 printf.o 和 hello.o 合并,結(jié)果得到 hello 可執(zhí)行目標(biāo)文件。
$ gcc hello.o -o hello
2. 靜態(tài)鏈接
靜態(tài)連接器以一組可重定向目標(biāo)文件為輸入,生成一個(gè)完全鏈接的可執(zhí)行目標(biāo)文件作為輸出。鏈接器主要完成以下兩個(gè)任務(wù):
符號(hào)解析:每個(gè)符號(hào)對(duì)應(yīng)于一個(gè)函數(shù)、一個(gè)全局變量或一個(gè)靜態(tài)變量,符號(hào)解析的目的是將每個(gè)符號(hào)引用與一個(gè)符號(hào)定義關(guān)聯(lián)起來(lái)。
重定位:鏈接器通過(guò)把每個(gè)符號(hào)定義與一個(gè)內(nèi)存位置關(guān)聯(lián)起來(lái),然后修改所有對(duì)這些符號(hào)的引用,使得它們指向這個(gè)內(nèi)存位置。
3. 目標(biāo)文件
可執(zhí)行目標(biāo)文件:可以直接在內(nèi)存中執(zhí)行;
可重定向目標(biāo)文件:可與其它可重定向目標(biāo)文件在鏈接階段合并,創(chuàng)建一個(gè)可執(zhí)行目標(biāo)文件;
共享目標(biāo)文件:這是一種特殊的可重定向目標(biāo)文件,可以在運(yùn)行時(shí)被動(dòng)態(tài)加載進(jìn)內(nèi)存并鏈接;
4. 動(dòng)態(tài)鏈接
靜態(tài)庫(kù)有以下兩個(gè)問(wèn)題:
當(dāng)靜態(tài)庫(kù)更新時(shí)那么整個(gè)程序都要重新進(jìn)行鏈接;
對(duì)于 printf 這種標(biāo)準(zhǔn)函數(shù)庫(kù),如果每個(gè)程序都要有代碼,這會(huì)極大浪費(fèi)資源。
共享庫(kù)是為了解決靜態(tài)庫(kù)的這兩個(gè)問(wèn)題而設(shè)計(jì)的,在 Linu__ 系統(tǒng)中通常用 .so 后綴來(lái)表示,Windows 系統(tǒng)上它們被稱為 DLL。它具有以下特點(diǎn):
在給定的文件系統(tǒng)中一個(gè)庫(kù)只有一個(gè)文件,所有引用該庫(kù)的可執(zhí)行目標(biāo)文件都共享這個(gè)文件,它不會(huì)被復(fù)制到引用它的可執(zhí)行文件中;
在內(nèi)存中,一個(gè)共享庫(kù)的 .te__t 節(jié)(已編譯程序的機(jī)器代碼)的一個(gè)副本可以被不同的正在運(yùn)行的進(jìn)程共享。
學(xué)完這份計(jì)算機(jī)基礎(chǔ)知識(shí)與操作系統(tǒng)后,帥地飄了
1、操作系統(tǒng)的四個(gè)特征
有以下四個(gè)特征:
并發(fā)
共享
虛擬
異步
接下來(lái),我們分別來(lái)搞定每一個(gè)特征。
1.1 并發(fā)是什么?和并行有啥區(qū)別?
舉個(gè)例子,假如你在語(yǔ)音跟同學(xué)玩英雄聯(lián)盟:
你一邊用鼠標(biāo)移動(dòng)打游戲,同時(shí)語(yǔ)音嘴里說(shuō)"隊(duì)友掛機(jī),真坑!", 這叫并行(邊移動(dòng)鼠標(biāo)邊語(yǔ)音BB)
你一邊用鼠標(biāo)移動(dòng)打游戲,然后離開鼠標(biāo),去砸鍵盤, 這叫并發(fā)(先離開鼠標(biāo)然后砸鍵盤)
并發(fā)只是把時(shí)間分成若干段,使多個(gè)任務(wù)交替的執(zhí)行。
并行的關(guān)鍵是你有同時(shí)處理多個(gè)任務(wù)的能力。
所以我認(rèn)為它們最關(guān)鍵的點(diǎn)就是:是否是『同時(shí)』
那么對(duì)于操作系統(tǒng)而言,操作系統(tǒng)的并發(fā)性指計(jì)算機(jī)系統(tǒng)中同時(shí)存在多個(gè)運(yùn)行著的程序。
比如說(shuō)以前的計(jì)算機(jī)是單核CPU,那么如何在操作系統(tǒng)上同時(shí)運(yùn)行QQ、瀏覽器,記事本、ppt等多個(gè)程序呢,這就需要操作系統(tǒng)具有并發(fā)性
CPU時(shí)間片(操作系統(tǒng)分配給每個(gè)正在運(yùn)行的進(jìn)程微觀上的一段CPU時(shí)間)輪著給進(jìn)程執(zhí)行的時(shí)間,因?yàn)閳?zhí)行速度很快,看起來(lái)就像瀏覽器能同時(shí)執(zhí)行任務(wù)一樣。
有人會(huì)說(shuō),現(xiàn)在都多核CPU了,還需要并發(fā)嗎,答案肯定是需要的,比如你有8核CPU,但是桌面要執(zhí)行的任務(wù)很可能超過(guò)8個(gè)。
1.2 共享是什么?共享和并發(fā)有什么關(guān)系?
舉一個(gè)例子:
你同時(shí)用QQ和微信發(fā)"年終述職.ppt"文件給領(lǐng)導(dǎo),這時(shí)候QQ和微信都在讀取這個(gè)ppt文件
兩個(gè)進(jìn)程正在并發(fā)執(zhí)行(并發(fā)性)
需要共享地訪問(wèn)硬盤資源(共享性)
如果沒(méi)有并發(fā),也就是只有一個(gè)進(jìn)程在運(yùn)行,那就沒(méi)有共享了。如果沒(méi)有共享,QQ和微信就不能同時(shí)發(fā)文件,無(wú)法同時(shí)訪問(wèn)硬盤資源,也就無(wú)法并發(fā)。
其中共享分為兩種情況:
上面的例子,QQ和微信都要訪問(wèn)同一個(gè)文件,屬于同時(shí)共享。
對(duì)于互斥共享,比如打印機(jī),只能同一時(shí)刻被一個(gè)進(jìn)程控制,如打印機(jī),雖然他可以提供多個(gè)進(jìn)程使用,但是試想,同時(shí)打印多個(gè)東西,會(huì)造成打印結(jié)果的混亂,因此規(guī)定,某些資源在進(jìn)行使用的時(shí)候,必須要先讓某進(jìn)程先使用,等使用完之后,再同一其他進(jìn)程進(jìn)行訪問(wèn)。
我們把一段時(shí)間內(nèi)只允許一個(gè)進(jìn)程訪問(wèn)的資源稱為獨(dú)占資源,或臨界資源。
1.3 虛擬是啥?
先舉例,再說(shuō)定義。
假如一個(gè)叫范桶的貨車司機(jī)在玩英雄聯(lián)盟,平時(shí)因?yàn)榫岂{太多,自己裝了很多次別人的車,住院也花了不少錢,所以家里沒(méi)錢,只能買個(gè)1G內(nèi)存的二手電腦玩游戲??捎⑿勐?lián)盟至少需要2G內(nèi)存,這就奇怪了,老司機(jī)雖然一到團(tuán)戰(zhàn)就卡死,但是還是能運(yùn)行英雄聯(lián)盟。為什么需要2G內(nèi)存的游戲,1G電腦還能運(yùn)行呢?
這就是虛擬存儲(chǔ)器技術(shù)。實(shí)際上只有1G內(nèi)存,在用戶看來(lái)遠(yuǎn)遠(yuǎn)大于1G。
還有,范桶的電腦還是單核的,但范桶居然能一邊迅雷下著愛(ài)情動(dòng)作片,一邊聽(tīng)著網(wǎng)易云音樂(lè),還在QQ上撩妹子,既然一個(gè)程序要被分配CPU才能正常執(zhí)行,按道理來(lái)說(shuō)同一時(shí)間只有1個(gè)程序在運(yùn)行,為啥電腦能同時(shí)運(yùn)行這么多程序呢?
這就是虛擬處理器技術(shù)。實(shí)際上只有一個(gè)CPU,在用戶看來(lái)有3個(gè)CPU在同時(shí)服務(wù)。(因?yàn)镃PU來(lái)回切換進(jìn)程的速度特別塊,感覺(jué)就像很多CPU在為我們服務(wù))
虛擬這塊的總結(jié)如下:
1.4 異步性是啥?
異步在JS里是很常見(jiàn)的,比如aja__請(qǐng)求,我們發(fā)出請(qǐng)求后并不是立馬得到信息,也不會(huì)去等待aja__結(jié)果返回,而是繼續(xù)執(zhí)行下面的代碼,等aja__結(jié)果回來(lái),通知JS線程。這就跟CPU處理進(jìn)程很類似。
比如,CPU正在執(zhí)行一個(gè)進(jìn)程,進(jìn)程需要讀取文件,讀取文件可能要1個(gè)小時(shí),那CPU不可能一直等一個(gè)小時(shí),CPU會(huì)繼續(xù)把時(shí)間片分給別的進(jìn)程,等文件讀取完成了(類似aja__返回結(jié)果了),CPU再繼續(xù)執(zhí)行之前被中斷的進(jìn)程。
所以異步性就是描述進(jìn)程這種以不可預(yù)知的速度走走停停、何時(shí)開始何時(shí)暫停何時(shí)結(jié)束不可預(yù)知的性質(zhì)。
2、操作系統(tǒng)運(yùn)行機(jī)制和體系結(jié)構(gòu)
預(yù)備知識(shí):什么是指令
比如說(shuō),如下圖(簡(jiǎn)單掃一下即可):
a+b是一段程序代碼,a+b在CPU看來(lái)并不能一步完成,可以翻譯成如下:
// 意思是將內(nèi)存的16號(hào)單元數(shù)據(jù),放到A寄存器,LOAD A, 16// 意思是將內(nèi)存的16號(hào)單元數(shù)據(jù),放到B寄存器LOAD B, 17// 存器里的A,B數(shù)據(jù)相加,得到CADD C, A, B
這里就可以看得出來(lái),指令是CPU能識(shí)別和執(zhí)行的最基本命令。
2.1 兩種指令、兩種處理器狀態(tài)、兩種程序
假如說(shuō)一個(gè)用戶可以隨意把服務(wù)器上的所有文件刪光,這是很危險(xiǎn)的。所以有些指令普通用戶是不能使用的,只能是權(quán)限較高的用戶能使用。此時(shí)指令就分為了兩種,如下圖:
這就引出一個(gè)問(wèn)題:CPU如何判斷當(dāng)前是否可以執(zhí)行特權(quán)指令?
如下圖:
CPU通常有兩種工作模式即:內(nèi)核態(tài)和用戶態(tài),而在PSW(這個(gè)不用管,就知道有一個(gè)寄存器的標(biāo)志位0表示用戶態(tài),1表示核心態(tài))中有一個(gè)二進(jìn)制位控制這兩種模式。
對(duì)于應(yīng)用程序而言,有的程序能執(zhí)行特權(quán)指令,有的程序只能執(zhí)行非特權(quán)指令。所以操作系統(tǒng)里的程序又分為兩種:
2.2 操作系統(tǒng)內(nèi)核簡(jiǎn)單介紹
從下圖,我們先看看操作系統(tǒng)內(nèi)核包含哪些
操作系統(tǒng)內(nèi)核中跟硬件緊密相關(guān)的部分有:
時(shí)鐘管理。操作系統(tǒng)的時(shí)鐘管理是依靠硬件定時(shí)器的(具體硬件怎么實(shí)現(xiàn)我也不太清楚,好像是靠硬件周期性的產(chǎn)生一個(gè)脈沖信號(hào)實(shí)現(xiàn)的)。時(shí)鐘管理相當(dāng)重要,比如我們獲取時(shí)間信息,進(jìn)程切換等等都是要依靠時(shí)鐘管理。
中斷處理(下一小節(jié)會(huì)詳細(xì)介紹)。
原語(yǔ)(后面會(huì)有案例提到)?,F(xiàn)在可以簡(jiǎn)單理解為用來(lái)實(shí)現(xiàn)某個(gè)特定功能,在執(zhí)行過(guò)程中不可被中斷的指令集合。原語(yǔ)有一個(gè)非常重要的特性,就是原子性(其運(yùn)行一氣呵成,不可中斷)。
2.3 中斷
在程序運(yùn)行過(guò)程中,系統(tǒng)出現(xiàn)了一個(gè)必須由CPU立即處理的情況,此時(shí),CPU暫時(shí)中止程序的執(zhí)行轉(zhuǎn)而處理這個(gè)新的情況的過(guò)程就叫做中斷。
下面舉一個(gè)例子:
第一個(gè)應(yīng)用程序在用戶態(tài)執(zhí)行了一段時(shí)間后
接著操作系統(tǒng)切換到核心態(tài),處理中斷信號(hào)
操作系統(tǒng)發(fā)現(xiàn)中斷的信號(hào)是第一個(gè)程序的時(shí)間片(每個(gè)程序不能一直執(zhí)行,CPU會(huì)給每個(gè)程序一定的執(zhí)行時(shí)間,這段時(shí)間就是時(shí)間片)用完了,應(yīng)該換第二個(gè)應(yīng)用程序執(zhí)行了
切換到第2個(gè)進(jìn)程后,操作系統(tǒng)會(huì)將CPU的使用權(quán)交換給第二個(gè)應(yīng)用程序,接著第二個(gè)應(yīng)用程序就在用戶態(tài)下開始執(zhí)行。
進(jìn)程2需要調(diào)用打印機(jī)資源,這時(shí)會(huì)執(zhí)行一個(gè)系統(tǒng)調(diào)用(后面會(huì)講系統(tǒng)調(diào)用,這里簡(jiǎn)單理解為需要操作系統(tǒng)進(jìn)入核心態(tài)處理的函數(shù)),讓操作系統(tǒng)進(jìn)入核心態(tài),去調(diào)用打印機(jī)資源
打印機(jī)開始工作,此時(shí)進(jìn)程2因?yàn)橐却蛴C(jī)啟動(dòng),操作系統(tǒng)就不等待了(等到打印機(jī)準(zhǔn)備好了,再回來(lái)執(zhí)行程序2),直接切換到第三個(gè)應(yīng)用程序執(zhí)行
等到打印機(jī)準(zhǔn)備好了,此時(shí)打印機(jī)通過(guò)I/O控制器會(huì)給操作系統(tǒng)發(fā)出一個(gè)中斷信號(hào),操作系統(tǒng)又進(jìn)入到核心態(tài),發(fā)現(xiàn)這個(gè)中斷是因?yàn)槌绦?等待打印機(jī)資源,現(xiàn)在打印機(jī)準(zhǔn)備好了,就切換到程序2,切換到用戶態(tài),把CPU給程序2繼續(xù)執(zhí)行。
好了,現(xiàn)在可以給出一個(gè)結(jié)論,就是用戶態(tài)、核心態(tài)之間的切換是怎么實(shí)現(xiàn)的?
"用戶態(tài) ---> 核心態(tài)"是通過(guò)中斷實(shí)現(xiàn)的。并且中斷時(shí)唯一途徑。
"核心態(tài) ---> 用戶態(tài)"的切換時(shí)通過(guò)執(zhí)行一個(gè)特權(quán)指令,將程序狀態(tài)的標(biāo)志位設(shè)為用戶態(tài)。
2.4 中斷的分類
舉一個(gè)例子,什么是內(nèi)中斷和外中斷:
接著說(shuō)之前的范桶同學(xué),小時(shí)候不愛(ài)學(xué)習(xí),每次學(xué)習(xí)著學(xué)習(xí)著突然異想天開,回過(guò)神來(lái)已經(jīng)過(guò)好好長(zhǎng)一段時(shí)間,這是內(nèi)部中斷。想著想著老師走過(guò)來(lái),給了范捅一嘴巴,這是外部中斷。
官方解釋如下:
內(nèi)中斷常見(jiàn)的情況如程序非法操作(比如你要拿的的數(shù)據(jù)的內(nèi)存地址不是內(nèi)存地址,是系統(tǒng)無(wú)法識(shí)別的地址),地址越界(比如系統(tǒng)給你的程序分配了一些內(nèi)存,但是你訪問(wèn)的時(shí)候超出了你應(yīng)該訪問(wèn)的內(nèi)存范圍)、浮點(diǎn)溢出(比如系統(tǒng)只能表示1.1到5.1的范圍,你輸入一個(gè)100, 超出了計(jì)算機(jī)能處理的范圍),或者異常,陷入trap(是指應(yīng)用程序請(qǐng)求系統(tǒng)調(diào)用造成的,什么是系統(tǒng)調(diào)用,后面小節(jié)會(huì)舉例講)。
外中斷常見(jiàn)的情況如I/O中斷(由I/O控制器產(chǎn)生,用于發(fā)送信號(hào)通知操作完成等信號(hào),比如進(jìn)程需要請(qǐng)求打印機(jī)資源,打印機(jī)有一個(gè)啟動(dòng)準(zhǔn)備的過(guò)程,準(zhǔn)備好了就會(huì)給CPU一個(gè)I/O中斷,告訴它已經(jīng)準(zhǔn)備好了)、時(shí)鐘中斷(由處理器內(nèi)部的計(jì)時(shí)器產(chǎn)生,允許操作系統(tǒng)以一定規(guī)程執(zhí)行函數(shù),操作系統(tǒng)每過(guò)大約15ms會(huì)進(jìn)行一次線程調(diào)度,就是利用時(shí)鐘中斷來(lái)實(shí)現(xiàn)的)。
2.5 系統(tǒng)調(diào)用
為什么需要系統(tǒng)調(diào)用?
比如你的程序需要讀取文件信息,可讀取文件屬于讀取硬盤里的數(shù)據(jù),這個(gè)操作應(yīng)該時(shí)CPU在內(nèi)核態(tài)去完成的,我們的應(yīng)用程序怎么讓CPU去幫助我們切換到內(nèi)核態(tài)完成這個(gè)工作呢,這里就需要系統(tǒng)調(diào)用了。
這里就引出系統(tǒng)調(diào)用的概念和作用。
應(yīng)用程序通過(guò)系統(tǒng)調(diào)用請(qǐng)求操作系統(tǒng)的服務(wù)。系統(tǒng)中的各種共享資源都由操作系統(tǒng)統(tǒng)一管理,因此在用戶程序中,凡是與資源有關(guān)的操作(如存儲(chǔ)分配、I/O操作、文件管理等),都必須通過(guò)系統(tǒng)調(diào)用的方式向操作系統(tǒng)提出服務(wù)請(qǐng)求,由操作系統(tǒng)代為完成。
以下內(nèi)容簡(jiǎn)單看一下即可,系統(tǒng)調(diào)用的分類:
需要注意的是,庫(kù)函數(shù)和系統(tǒng)調(diào)用容易混淆。
庫(kù)是可重用的模塊 處于用戶態(tài)
進(jìn)程通過(guò)系統(tǒng)調(diào)用從用戶態(tài)進(jìn)入內(nèi)核態(tài), 庫(kù)函數(shù)中有很大部分是對(duì)系統(tǒng)調(diào)用的封裝
舉個(gè)例子:比如windows和linu__中,創(chuàng)建進(jìn)程的系統(tǒng)調(diào)用方法是不一樣的。但在node中的只需要調(diào)用相同函數(shù)方法就可以創(chuàng)建一個(gè)進(jìn)程。例如
// 引入創(chuàng)建子進(jìn)程的模塊const childProcess = require('child_process')// 獲取cpu的數(shù)量const cpuNum = require('os').cpus().length// 創(chuàng)建與cpu數(shù)量一樣的子進(jìn)程for (let i = 0; i < cpuNum; ++i) { childProcess.fork('./worker.js')}
2.6 進(jìn)程的定義、組成、組織方式、狀態(tài)與轉(zhuǎn)換
2.6.1 為什么要引入進(jìn)程的概念呢?
早期的計(jì)算機(jī)只支持單道程序(是指所有進(jìn)程一個(gè)一個(gè)排隊(duì)執(zhí)行,A進(jìn)程執(zhí)行時(shí),CPU、內(nèi)存、I/O設(shè)備全是A進(jìn)程控制的,等A進(jìn)程執(zhí)行完了,才換B進(jìn)程,然后對(duì)應(yīng)的資源比如CPU、內(nèi)存這些才能換B用)。
現(xiàn)代計(jì)算機(jī)是多道程序執(zhí)行,就是同時(shí)看起來(lái)有多個(gè)程序在一起執(zhí)行,那每個(gè)程序執(zhí)行都需要系統(tǒng)分配給它資源來(lái)執(zhí)行,比如CPU、內(nèi)存。
拿內(nèi)存來(lái)說(shuō),操作系統(tǒng)要知道給A程序分配的內(nèi)存有哪些,給B程序分配的內(nèi)存有哪些,這些都要有小本本記錄下來(lái),這個(gè)小本本就是進(jìn)程的一部分,進(jìn)程的一大職責(zé)就是記錄目前程序運(yùn)行的狀態(tài)。
系統(tǒng)為每個(gè)運(yùn)行的程序配置一個(gè)數(shù)據(jù)結(jié)構(gòu),稱為進(jìn)程控制塊(PCB),用來(lái)描述進(jìn)程的各種信息(比如代碼段放在哪)。
2.6.2 進(jìn)程的定義?
簡(jiǎn)要的說(shuō),進(jìn)程就是具有獨(dú)立功能的程序在數(shù)據(jù)集合上運(yùn)行的過(guò)程。(強(qiáng)調(diào)動(dòng)態(tài)性)
2.6.3 PCB有哪些組成
如下圖,分別說(shuō)明一下
進(jìn)程標(biāo)識(shí)符PID相當(dāng)于身份證。是在進(jìn)程被創(chuàng)建時(shí),操作系統(tǒng)會(huì)為該進(jìn)程分配一個(gè)唯一的、不重復(fù)的ID,用于區(qū)分不同的進(jìn)程。
用戶標(biāo)識(shí)符UID用來(lái)表示這個(gè)進(jìn)程所屬的用戶是誰(shuí)。
進(jìn)程當(dāng)前狀態(tài)和優(yōu)先級(jí)下一小節(jié)會(huì)詳細(xì)介紹
程序段指針是指當(dāng)前進(jìn)程的程序在內(nèi)存的什么地方。
數(shù)據(jù)段指針是指當(dāng)前進(jìn)程的數(shù)據(jù)在內(nèi)存的什么地方。
鍵盤和鼠標(biāo)是指進(jìn)程被分配得到的I/O設(shè)備。
各種寄存器值是指比如把程序計(jì)數(shù)器的值,比如有些計(jì)算的結(jié)果算到一半,進(jìn)程切換時(shí)需要把這些值保存下來(lái)。
2.6.4 進(jìn)程的組織
在一個(gè)系統(tǒng)中,通常由數(shù)十、數(shù)百乃至數(shù)千個(gè)PCB。為了對(duì)他們加以有效的管理,應(yīng)該用適當(dāng)?shù)姆绞桨堰@些PCB組織起來(lái)。這里介紹一種組織方式,類似數(shù)據(jù)結(jié)構(gòu)里的鏈表。
2.6.5 進(jìn)程的狀態(tài)
進(jìn)程是程序的一次執(zhí)行。在這個(gè)執(zhí)行過(guò)程中,有時(shí)進(jìn)程正在被CPU處理,有時(shí)又需要等待CPU服務(wù),可見(jiàn),進(jìn)程的 狀態(tài)是會(huì)有各種變化。為了方便對(duì)各個(gè)進(jìn)程的管理,操作系統(tǒng)需要將進(jìn)程合理地劃分為幾種狀態(tài)。
進(jìn)程的三種基本狀態(tài):
進(jìn)程的另外兩種狀態(tài):
2.6.6 進(jìn)程狀態(tài)的轉(zhuǎn)換
進(jìn)程的狀態(tài)并不是一成不變的,在一定情況下會(huì)動(dòng)態(tài)轉(zhuǎn)換。
以上的這些進(jìn)程狀態(tài)的轉(zhuǎn)換是如何實(shí)現(xiàn)的呢,這就要引出下一個(gè)角色了,叫原語(yǔ)。
原語(yǔ)是不可被中斷的原子操作。我們舉一個(gè)例子看看原語(yǔ)是怎么保證不可中斷的。
原語(yǔ)采用關(guān)中斷指令和開中斷指令實(shí)現(xiàn)。
首先執(zhí)行關(guān)中斷指令
然后外部來(lái)了中斷信號(hào),不予以處理
等到開中斷指令執(zhí)行后,其他中斷信號(hào)才有機(jī)會(huì)處理。
¨K46K ¨K11K 因?yàn)檫M(jìn)程是分配系統(tǒng)資源的單位(包括內(nèi)存地址空間),因此各進(jìn)程擁有的內(nèi)存地址空間相互獨(dú)立。!w=1679&h=683&f=png&s=326726) ¨K47K 因?yàn)閮蓚€(gè)進(jìn)程的存儲(chǔ)空間不能相互訪問(wèn),所以操作系統(tǒng)就提供的一個(gè)內(nèi)存空間讓彼此都能訪問(wèn),這就是共享存儲(chǔ)的原理。其中,介紹一下基于存儲(chǔ)區(qū)的共享。
在內(nèi)存中畫出一塊共享存儲(chǔ)區(qū),數(shù)據(jù)的形式、存放位置都是由進(jìn)程控制,而不是操作系統(tǒng)。
管道數(shù)據(jù)是以字符流(注意不是字節(jié)流)的形式寫入管道,當(dāng)管道寫滿時(shí),寫進(jìn)程的write()系統(tǒng)調(diào)用將被阻塞,等待讀進(jìn)程將數(shù)據(jù)取走。當(dāng)讀進(jìn)程將數(shù)據(jù)全部取走后,管道變空,此時(shí)讀進(jìn)程的read()系統(tǒng)調(diào)用將被阻塞。
如果沒(méi)寫滿就不允許讀。如果都沒(méi)空就不允許寫。
數(shù)據(jù)一旦被讀出,就從管道中被丟棄,這就意味著讀進(jìn)程最多只能有一個(gè)。
¨K49K 進(jìn)程間的數(shù)據(jù)交換以格式化的消息為單位。進(jìn)程通過(guò)操作系統(tǒng)提供的"發(fā)送消息/接收消息"兩個(gè)原語(yǔ)進(jìn)行數(shù)據(jù)交換。其中消息是什么意思呢?就好像你發(fā)QQ消息,消息頭的來(lái)源是你,消息體是你發(fā)的內(nèi)容。
比如你在玩QQ的時(shí)候,QQ是一個(gè)進(jìn)程,如果QQ的進(jìn)程里沒(méi)有多線程并發(fā),那么QQ進(jìn)程就只能同一時(shí)間做一件事情(比如QQ打字聊天)
但是我們真實(shí)的場(chǎng)景是QQ聊天的同時(shí),還可以發(fā)文件,還可以視頻聊天,這說(shuō)明如果QQ沒(méi)有多線程并發(fā)能力,QQ能夠的實(shí)用性就大大降低了。所以我們需要線程,也就是需要進(jìn)程擁有能夠并發(fā)多個(gè)事件的能力。
信號(hào)量主要是來(lái)解決進(jìn)程的同步和互斥的,我們前端需要了解,如果涉及到同步和互斥的關(guān)系(我們編程大多數(shù)關(guān)于流程的邏輯問(wèn)題,本質(zhì)不就是同步和互斥嗎?) 在操作系統(tǒng)中,常用P、V信號(hào)量來(lái)實(shí)現(xiàn)進(jìn)程間的同步和互斥,我們簡(jiǎn)單了解一下一種常用的信號(hào)量,記錄型信號(hào)量來(lái)簡(jiǎn)單了解一下信號(hào)量本質(zhì)是怎樣的。(c語(yǔ)言來(lái)表示,會(huì)有備注) ¨G2G 意思是信號(hào)量的結(jié)構(gòu)有兩部分組成,一部分是剩余資源value,比如目前有兩臺(tái)打印機(jī)空閑,那么剩余資源就是2,誰(shuí)正在使用打印機(jī),剩余資源就減1。 Struct process __L 意思是,比如2臺(tái)打印機(jī)都有人在用,這時(shí)候你的要用打印機(jī),此時(shí)會(huì)把這個(gè)打印機(jī)資源的請(qǐng)求放入阻塞隊(duì)列,L就是阻塞隊(duì)列的地址。¨G3G 需要注意的是,如果剩余資源數(shù)不夠,使用block原語(yǔ)使進(jìn)程從運(yùn)行態(tài)進(jìn)入阻塞態(tài),并把掛到信號(hào)量S的等待隊(duì)列中?!4G 釋放資源后,若還有別的進(jìn)程在等待這個(gè)資源,比如打印機(jī)資源,則使用wakeup原語(yǔ)喚醒等待隊(duì)列中的一個(gè)進(jìn)程,該進(jìn)程從阻塞態(tài)變?yōu)槔^續(xù)態(tài)?!53K 為什么要講這個(gè)呢,主要是node的流的機(jī)制,本質(zhì)就是生產(chǎn)者消費(fèi)者問(wèn)題,可以簡(jiǎn)單的看看這個(gè)問(wèn)題如何解決。!生產(chǎn)者的主要作用是生成一定量的數(shù)據(jù)放到緩沖區(qū)中,然后重復(fù)此過(guò)程。與此同時(shí),消費(fèi)者也在緩沖區(qū)消耗這些數(shù)據(jù)。該問(wèn)題的關(guān)鍵就是要保證生產(chǎn)者不會(huì)在緩沖區(qū)滿時(shí)加入數(shù)據(jù),消費(fèi)者也不會(huì)在緩沖區(qū)中空時(shí)消耗數(shù)據(jù)。這里我們需要兩個(gè)同步信號(hào)量和一個(gè)互斥信號(hào)量 ¨G5G 生產(chǎn)者代碼如下 ¨G6G 消費(fèi)者代碼如下 ¨G7G ¨K54K ¨K19K 內(nèi)存是計(jì)算機(jī)其它硬件設(shè)備與``CPU溝通的橋梁、中轉(zhuǎn)站。程序執(zhí)行前需要先放到內(nèi)存中才能被CPU處理。
4.1 cpu如何區(qū)分執(zhí)行程序的數(shù)據(jù)在內(nèi)存的什么地方
是通過(guò)給內(nèi)存的存儲(chǔ)單元編址實(shí)現(xiàn)的。(存儲(chǔ)單元一般是以字節(jié)為單位)
如下圖,內(nèi)存的存儲(chǔ)單元,就像一個(gè)酒店的房間,都有編號(hào),比如程序一的數(shù)據(jù)都在1樓,1樓1號(hào)存儲(chǔ)著程序里let a = 1這段代碼。
4.2 內(nèi)存管理-內(nèi)存空間的分配與回收
內(nèi)存分配分為連續(xù)分配和非連續(xù)分配,連續(xù)分配是指用戶進(jìn)程分配的必須是一個(gè)連續(xù)的內(nèi)存空間。
這里我們只講連續(xù)分配中的動(dòng)態(tài)分區(qū)分配。
什么是動(dòng)態(tài)分區(qū)分配呢,這種分配方式不會(huì)預(yù)先劃分內(nèi)存分區(qū),而是在進(jìn)程裝入內(nèi)存時(shí),根據(jù)進(jìn)程的大小動(dòng)態(tài)地建立分區(qū),并使分區(qū)的大小正好適合進(jìn)程的需要。(比如,某計(jì)算機(jī)內(nèi)存大小64MB,系統(tǒng)區(qū)8MB,用戶區(qū)56MB…,現(xiàn)在我們有幾個(gè)進(jìn)程要裝入內(nèi)存,如下圖)
隨之而來(lái)的問(wèn)題就是,如果此時(shí)進(jìn)程1使用完了,相應(yīng)在內(nèi)存上的數(shù)據(jù)也被刪除了,那么空閑的區(qū)域,后面該怎么分配(也就是說(shuō)隨著進(jìn)程退出,會(huì)有很多空閑的內(nèi)存區(qū)域出現(xiàn))
我們講一種較為簡(jiǎn)單的處理方法叫空閑分區(qū)表法來(lái)解決這個(gè)問(wèn)題。如下圖,右側(cè)的表格就是一個(gè)空閑分區(qū)表。
當(dāng)很多個(gè)空閑分區(qū)都能滿足需求時(shí),應(yīng)該選擇哪個(gè)分區(qū)進(jìn)行分配呢,例如下圖,分別有20MB,10MB,4MB三個(gè)空閑分區(qū)塊,現(xiàn)在進(jìn)程5需要4MB空閑分區(qū),改怎么分配呢?
我們需要按照一定的動(dòng)態(tài)分區(qū)分配算法,比如有首次適應(yīng)算法,指的是每次都從低地址開始查找,找到第一個(gè)能滿足大小的空閑分區(qū)。還有比如最佳適應(yīng)算法,指的是從空閑分區(qū)表中找到最小的適合分配的分區(qū)塊來(lái)滿足需求。
連續(xù)分配缺點(diǎn)很明顯,大多數(shù)情況,需要分配的進(jìn)程大小,不能跟空閑分區(qū)剩下的大小完全一樣,這樣就產(chǎn)生很多很難利用的內(nèi)存碎片。
這里我們介紹一種更好的空閑分區(qū)的分配方法,基本分頁(yè)存儲(chǔ)。如下圖
將內(nèi)存空間分為一個(gè)個(gè)大小相等的分區(qū)(比如:每個(gè)分區(qū)4KB).每個(gè)分區(qū)就是一個(gè)“頁(yè)框”。頁(yè)框號(hào)從0開始。
將用戶進(jìn)程的地址空間分為與頁(yè)框大小相等的一個(gè)個(gè)區(qū)域,稱為“頁(yè)”。每個(gè)頁(yè)也是從0開始。
進(jìn)程的頁(yè)與內(nèi)存的頁(yè)框有著一一對(duì)應(yīng)的關(guān)系。各個(gè)頁(yè)不必連續(xù)存放,也不必按先后順序來(lái),可以放到不相鄰的各個(gè)頁(yè)框中。
5 文件管理
文件是什么?
文件就是一組有意義的信息/數(shù)據(jù)集合。
計(jì)算機(jī)中存放了各種各樣的文件,一個(gè)文件有哪些屬性呢?文件內(nèi)部的數(shù)據(jù)應(yīng)該怎樣組織起來(lái)?文件之間又該怎么組織起來(lái)?
5.1 文件的屬性
文件名。即文件的名字,需要注意的是,同一目錄下不允許有重名的文件。
標(biāo)識(shí)符。操作系統(tǒng)用于區(qū)分各個(gè)文件的一種內(nèi)部的名稱。
類型。文件的類型。
位置。文件存放的路徑,同時(shí)也是在硬盤里的位置(需要轉(zhuǎn)換成物理硬盤上的地址)
創(chuàng)建時(shí)間、上次修改時(shí)間、文件所有者就是字面意思。
保護(hù)信息。比如對(duì)這個(gè)文件的執(zhí)行權(quán)限,是否有刪除文件權(quán)限,修改文件權(quán)限等等。
5.2 文件內(nèi)部數(shù)據(jù)如何組織在一起
如下圖,文件主要分為有結(jié)構(gòu)文件和無(wú)結(jié)構(gòu)文件。
5.3 文件之間如何組織起來(lái)
通過(guò)樹狀結(jié)構(gòu)組織的。例如windows的文件間的組織關(guān)系如下:
接下來(lái)我們?cè)敿?xì)的了解一下文件的邏輯結(jié)構(gòu)
5.4 文件的邏輯結(jié)構(gòu)
邏輯結(jié)構(gòu)是指,在用戶看來(lái),文件內(nèi)部的數(shù)據(jù)是如何組織起來(lái)的,而“物理結(jié)構(gòu)”是在操作系統(tǒng)看來(lái),文件是如何保存在外存,比如硬盤中的。
比如,“線性表”就是一種邏輯結(jié)構(gòu),在用戶看來(lái),線性表就是一組有先后關(guān)系的元素序列,如:a,b,c,d,e....
“線性表”這種邏輯結(jié)構(gòu)可以用不同的物理結(jié)構(gòu)實(shí)現(xiàn),比如:順序表/鏈表。順序表的各個(gè)元素在邏輯上相鄰,在物理上也相鄰:而鏈表的各個(gè)元素在物理上可以是不相鄰的。
因此,順序表可以實(shí)現(xiàn)“隨機(jī)訪問(wèn)”,而“鏈表”無(wú)法實(shí)現(xiàn)隨機(jī)訪問(wèn)。
接下來(lái)我了解一下有結(jié)構(gòu)文件的三種邏輯結(jié)構(gòu)
5.4.1 順序文件
什么是順序文件
指的是文件中的記錄一個(gè)接一個(gè)地在邏輯上是順序排列,記錄可以是定長(zhǎng)或變長(zhǎng),各個(gè)記錄在物理上可以順序存儲(chǔ)或鏈?zhǔn)酱鎯?chǔ)
順序文件按結(jié)構(gòu)來(lái)劃分,可以分為串結(jié)構(gòu)和順序結(jié)構(gòu)。
串結(jié)構(gòu)是指記錄之間的順序與關(guān)鍵字無(wú)關(guān),通常都是按照記錄的時(shí)間決定記錄的順序。
順序結(jié)構(gòu)就必須保證記錄之間的先后順序按關(guān)鍵字排列。
這里需要注意的知識(shí)點(diǎn)是,順序文件的存儲(chǔ)方式和是否按關(guān)鍵字排列,會(huì)影響數(shù)據(jù)是否支持隨機(jī)存取和是否可以快速按關(guān)鍵字找到對(duì)應(yīng)記錄的功能。
5.4.2 索引文件
對(duì)于可變長(zhǎng)記錄文件,要找到第i個(gè)記錄,必須先順序查找前i-1個(gè)記錄,但是很多場(chǎng)景中又必須使用可變長(zhǎng)記錄,如何解決這個(gè)問(wèn)題呢?這就引出來(lái)馬上講的索引文件
給這些變長(zhǎng)的記錄都用一張索引表來(lái)記錄,一個(gè)索引表項(xiàng)包括了索引號(hào),長(zhǎng)度和指針。
其中,可以將關(guān)鍵字作為索引號(hào)的內(nèi)容,如果關(guān)鍵字本身的排列是有序的,那么還可以按照關(guān)鍵字進(jìn)行折半查找。
但是建立索引表的問(wèn)題也很明顯,首先若要?jiǎng)h除/增加一個(gè)記錄,同時(shí)也要對(duì)索引表操作,其次,如果增加一條記錄才1KB,但是索引表增加i一條記錄可能有8KB,以至于索引表的體積大大多于記錄。存儲(chǔ)空間的利用率就比較低。
5.4.3 索引順序文件
索引順序文件是索引文件和順序文件思想的結(jié)合。索引順序文件中,同樣會(huì)為文件建立一張索引表,但不同的是,并不是每個(gè)記錄對(duì)應(yīng)一個(gè)索引表項(xiàng),而是一組記錄對(duì)應(yīng)一個(gè)索引表項(xiàng)。
5.5 文件目錄
首先,我們需要了解一下文件控制塊是什么。我們假設(shè)目前在windows的D盤,如下圖
可以看到,目錄本身就是一種有結(jié)構(gòu)的文件,記錄了目錄里的文件和目錄的信息,比如名稱和類型。而這些一條條的記錄就是一個(gè)個(gè)的“文件控制塊”(FCB)。
文件目錄的結(jié)構(gòu)通常是樹狀的,例如linu__里/是指根路徑,/home是根路徑下的二級(jí)目錄
需要注意的是,樹狀目錄不容易實(shí)現(xiàn)文件共享,所以在樹形目錄結(jié)構(gòu)的基礎(chǔ)上,增加了一些指向同一節(jié)點(diǎn)的有向邊(可以簡(jiǎn)單理解為引用關(guān)系,就跟js里的對(duì)象一樣)
也就是說(shuō)需要為每個(gè)共享節(jié)點(diǎn)設(shè)置一個(gè)共享計(jì)數(shù)器,用于記錄此時(shí)有多少個(gè)地方在共享該結(jié)點(diǎn)。只有共享計(jì)數(shù)器減為0,才刪除該節(jié)點(diǎn)。
5.6 文件存儲(chǔ)空間管理
首先,我們了解一下磁盤分為目錄區(qū)和文件區(qū)。
接著,我們了解一下常見(jiàn)的兩種文件存儲(chǔ)空間的管理算法,如下圖,假如硬盤上空閑的數(shù)據(jù)塊是藍(lán)色,非空閑的數(shù)據(jù)塊是橙色。
對(duì)分配連續(xù)的存儲(chǔ)空間,可以采用空閑表法(只講這種較簡(jiǎn)單的方法)來(lái)分配和回收磁盤塊。對(duì)于分配,可以采用首次適應(yīng),最佳適應(yīng)等算法來(lái)決定要為文件分配哪個(gè)區(qū)間。(空閑表表示如下)
首次適應(yīng)是指當(dāng)要插入數(shù)據(jù)的時(shí)候,空閑表會(huì)依次檢查空閑表中的表項(xiàng),然后找到第一個(gè)滿足條件的空閑區(qū)間。
最佳適應(yīng)算法是指當(dāng)要插入數(shù)據(jù)的時(shí)候,空閑表會(huì)依次檢查空閑表中的表項(xiàng),然后找到滿足條件而且空閑塊最小的空閑區(qū)間。
如何回收磁盤塊呢,主要分為以下4中情況
回收區(qū)的前后沒(méi)有相鄰空閑區(qū)
回收區(qū)前后都是空閑區(qū)
回收區(qū)前面是空前去
回收區(qū)后面是空閑區(qū)
最重要的是要注意表項(xiàng)合并的問(wèn)題。(比如說(shuō)回收區(qū)前后都有空閑區(qū)就將其一起合并為一個(gè)空閑區(qū))
5.7 文件共享
文件共享分為兩種
注意,多個(gè)用戶共享同一個(gè)文件,意味著系統(tǒng)只有“一份”文件數(shù)據(jù)。并且只要某個(gè)用戶修改了該文件的數(shù)據(jù),其他用戶也可以看到文件的變化。
軟連接可以理解為windows里的快捷方式。
硬鏈接可以理解為js里的引用計(jì)數(shù),只有引用為0的時(shí)候,才會(huì)真正刪除這個(gè)文件。
5.8 文件保護(hù)
操作系統(tǒng)需要保護(hù)文件的安全,一般有如下3種方式:
口令保護(hù)。是指為文件設(shè)置一個(gè)“口令”(比如123),用戶請(qǐng)求訪問(wèn)該文件時(shí)必須提供對(duì)應(yīng)的口令??诹钜话惴旁谖募?duì)應(yīng)的FCB或者索引結(jié)點(diǎn)上。
加密保護(hù)。使用某個(gè)"密碼"對(duì)文件進(jìn)行加密,在訪問(wèn)文件時(shí)需要提供正確的“密碼”才能對(duì)文件進(jìn)行正確的解密。
訪問(wèn)控制。在每個(gè)文件的FCB或者索引節(jié)點(diǎn)種增加一個(gè)訪問(wèn)控制列表,該表中記錄了各個(gè)用戶可以對(duì)該文件執(zhí)行哪些操作。
6 I/O設(shè)備
什么是I/O設(shè)備
I/O就是輸入輸出(Input/Output)的意思,I/O設(shè)備就是可以將數(shù)據(jù)輸入到計(jì)算機(jī),或者可以接收計(jì)算機(jī)輸出數(shù)據(jù)的外部設(shè)備,屬于計(jì)算機(jī)中的硬件部件。
6.1 I/O設(shè)備分類--按使用特性
人機(jī)交互類設(shè)備,這類設(shè)備傳輸數(shù)據(jù)的速度慢
存儲(chǔ)設(shè)備,這類設(shè)備傳輸數(shù)據(jù)的速度較快
網(wǎng)絡(luò)通信設(shè)備,這類設(shè)備的傳輸速度介于人機(jī)交互設(shè)備和存儲(chǔ)設(shè)備之間
6.2 I/O控制器
CPU無(wú)法直接控制I/O設(shè)備的機(jī)械部件,因此I/O設(shè)備還要有一個(gè)電子部件作為CPU和I/O設(shè)備機(jī)械部件之間的“中介”,用于實(shí)現(xiàn)CPU對(duì)設(shè)備的控制。這個(gè)電子部件就是I/O控制器。
接收和識(shí)別CPU發(fā)出的指令是指,比如CPU發(fā)來(lái)讀取文件的命令,I/O控制器中會(huì)有相應(yīng)的控制寄存器來(lái)存放命令和參數(shù)
向cpu報(bào)告設(shè)備的狀態(tài)是指,I/O控制器會(huì)有相應(yīng)的狀態(tài)寄存器,用來(lái)記錄I/O設(shè)備是否空閑或者忙碌
數(shù)據(jù)交換是指I/O控制器會(huì)設(shè)置相應(yīng)的數(shù)據(jù)寄存器。輸出時(shí),數(shù)據(jù)寄存器用于暫存CPU發(fā)來(lái)的數(shù)據(jù),之后再由控制器傳送給設(shè)備。
地址識(shí)別是指,為了區(qū)分設(shè)備控制器中的各個(gè)寄存器中的各個(gè)寄存器,也需要給各個(gè)寄存器設(shè)置一個(gè)特性的“地址”。I/O控制器通過(guò)CPU提供的“地址”來(lái)判斷CPU要讀寫的是哪個(gè)寄存器
6.3 I/O控制方式
這里我們指講一下目前比較先進(jìn)的方式,通道控制方式。
通道可以理解為一種“弱雞版CPU”。通道可以識(shí)別并執(zhí)行一系列通道指令。
通道最大的優(yōu)點(diǎn)是極大的減少了CPU的干預(yù)頻率,I/O設(shè)備完成任務(wù),通道會(huì)向CPU發(fā)出中斷,不需要輪詢來(lái)問(wèn)I/O設(shè)備是否完成CPU下達(dá)的任務(wù)。
操作系統(tǒng)基礎(chǔ)知識(shí)大全相關(guān)文章:
★ Win10系統(tǒng)的基礎(chǔ)知識(shí)大全有哪些
★ 簡(jiǎn)述操作系統(tǒng)的發(fā)展與分類
★ 電腦系統(tǒng)安全基礎(chǔ)知識(shí)大全有哪些
操作系統(tǒng)基礎(chǔ)知識(shí)大全相關(guān)文章:
★ Win10系統(tǒng)的基礎(chǔ)知識(shí)大全有哪些
★ 簡(jiǎn)述操作系統(tǒng)的發(fā)展與分類