姓名:王紫圣? ? 學(xué)號(hào):16130140355
轉(zhuǎn)載自: 手游實(shí)驗(yàn)室
【嵌牛導(dǎo)讀】本章主要介紹操作系統(tǒng)挑围、文件系統(tǒng)相關(guān)的知識(shí)要點(diǎn)。大部分互聯(lián)網(wǎng)公司的軟件開(kāi)發(fā)職位面試可能不會(huì)直接涉及這一層面的知識(shí)蛋哭,但并不意味著這部分知識(shí)不重要求厕。對(duì)于計(jì)算機(jī)底層實(shí)現(xiàn)的深入理解趣苏,能幫助你了解計(jì)算機(jī)的運(yùn)行原理,能夠更好地設(shè)計(jì)高效的架構(gòu)凿歼,并且有助于調(diào)試褪迟、判斷錯(cuò)誤。特別地答憔,對(duì)于多線程的理解尤為重要:現(xiàn)今的程序架構(gòu)都需要并發(fā)處理味赃,如何協(xié)調(diào)不同線程之間的分工協(xié)作,避免死鎖虐拓、同步出錯(cuò)等問(wèn)題心俗,是程序員應(yīng)當(dāng)具備的技能。對(duì)于后端工程師而言蓉驹,良好的操作系統(tǒng)知識(shí)基礎(chǔ)更是深刻理解并實(shí)現(xiàn)復(fù)雜分布式系統(tǒng)的前提條件城榛。
【嵌牛鼻子】系統(tǒng)底層
【嵌牛提問(wèn)】編代碼的你到底知不知道系統(tǒng)底層是什么
【嵌牛正文】進(jìn)程vs.線程
進(jìn)程(process)與線程(thread)最大的區(qū)別是:進(jìn)程擁有自己的地址空間,某進(jìn)程內(nèi)的線程對(duì)于其他進(jìn)程不可見(jiàn)态兴,即進(jìn)程A不能通過(guò)傳地址的方式直接讀寫進(jìn)程B的存儲(chǔ)區(qū)域狠持。進(jìn)程之間的通信需要通過(guò)進(jìn)程間通信(Inter-Process Communication,IPC)瞻润。與之相對(duì)的喘垂,同一進(jìn)程的各線程間之間可以直接通過(guò)傳遞地址或全局變量的方式傳遞信息甜刻。
此外,進(jìn)程作為操作系統(tǒng)中擁有資源和獨(dú)立調(diào)度的基本單位正勒,可以擁有多個(gè)線程得院。通常操作系統(tǒng)中運(yùn)行的一個(gè)程序就對(duì)應(yīng)一個(gè)進(jìn)程。在同一進(jìn)程中章贞,線程的切換不會(huì)引起進(jìn)程切換祥绞。在不同進(jìn)程中進(jìn)行線程切換,如從一個(gè)進(jìn)程內(nèi)的線程切換到另一個(gè)進(jìn)程中的線程時(shí)鸭限,會(huì)引起進(jìn)程切換蜕径。相比進(jìn)程切換,線程切換的開(kāi)銷要小很多里覆。線程于進(jìn)程相互結(jié)合能夠提高系統(tǒng)的運(yùn)行效率。
線程可以分為兩類:
一類是用戶級(jí)線程(user level thread)缆瓣。對(duì)于這類線程喧枷,有關(guān)線程管理的所有工作都由應(yīng)用程序完成,內(nèi)核意識(shí)不到線程的存在弓坞。在應(yīng)用程序啟動(dòng)后隧甚,操作系統(tǒng)分配給該程序一個(gè)進(jìn)程號(hào),以及其對(duì)應(yīng)的內(nèi)存空間等資源渡冻。應(yīng)用程序通常先在一個(gè)線程中運(yùn)行戚扳,該線程稱為主線程。在其運(yùn)行的某個(gè)時(shí)刻族吻,可以通過(guò)調(diào)用線程庫(kù)中的函數(shù)創(chuàng)建一個(gè)在相同進(jìn)程中運(yùn)行的新線程帽借。 用戶級(jí)線程的好處是非常高效,不需要進(jìn)入內(nèi)核空間超歌,但并發(fā)效率不高砍艾。
另一類是內(nèi)核級(jí)線程(kernel level thread)。對(duì)于這類線程巍举,有關(guān)線程管理的所有工作由內(nèi)核完成脆荷,應(yīng)用程序沒(méi)有進(jìn)行線程管理的代碼,只能調(diào)用內(nèi)核線程的接口懊悯。內(nèi)核維護(hù)進(jìn)程及其內(nèi)部的每個(gè)線程蜓谋,調(diào)度也由內(nèi)核基于線程架構(gòu)完成。內(nèi)核級(jí)線程的好處是炭分,內(nèi)核可以將不同線程更好地分配到不同的CPU桃焕,以實(shí)現(xiàn)真正的并行計(jì)算。
事實(shí)上捧毛,在現(xiàn)代操作系統(tǒng)中覆旭,往往使用組合方式實(shí)現(xiàn)多線程退子,即線程創(chuàng)建完全在用戶空間中完成,并且一個(gè)應(yīng)用程序中的多個(gè)用戶級(jí)線程被映射到一些內(nèi)核級(jí)線程上型将,相當(dāng)于是一種折中方案寂祥。
上下文切換
對(duì)于單核單線程CPU而言,在某一時(shí)刻只能執(zhí)行一條CPU指令七兜。上下文切換(Context Switch)是一種將CPU資源從一個(gè)進(jìn)程分配給另一個(gè)進(jìn)程的機(jī)制丸凭。從用戶角度看,計(jì)算機(jī)能夠并行運(yùn)行多個(gè)進(jìn)程腕铸,這恰恰是操作系統(tǒng)通過(guò)快速上下文切換造成的結(jié)果惜犀。在切換的過(guò)程中,操作系統(tǒng)需要先存儲(chǔ)當(dāng)前進(jìn)程的狀態(tài)(包括內(nèi)存空間的指針狠裹,當(dāng)前執(zhí)行完的指令等等)虽界,再讀入下一個(gè)進(jìn)程的狀態(tài),然后執(zhí)行此進(jìn)程涛菠。
系統(tǒng)調(diào)用
系統(tǒng)調(diào)用(System Call)是程序向系統(tǒng)內(nèi)核請(qǐng)求服務(wù)的方式莉御。可以包括硬件相關(guān)的服務(wù)(例如俗冻,訪問(wèn)硬盤等)礁叔,或者創(chuàng)建新進(jìn)程,調(diào)度其他進(jìn)程等迄薄。系統(tǒng)調(diào)用是程序和操作系統(tǒng)之間的重要接口琅关。
Semaphore/Mutex
當(dāng)用戶創(chuàng)立多個(gè)線程/進(jìn)程時(shí)党瓮,如果不同線程/進(jìn)程同時(shí)讀寫相同的內(nèi)容最岗,則可能造成讀寫錯(cuò)誤蕉陋,或者數(shù)據(jù)不一致李破。此時(shí)聚凹,需要通過(guò)加鎖的方式摩钙,控制核心區(qū)域(critical section)的訪問(wèn)權(quán)限臂外。對(duì)于semaphore而言伴澄,在初始化變量的時(shí)候可以控制允許多少個(gè)線程/進(jìn)程同時(shí)訪問(wèn)一個(gè)核心區(qū)域碰缔,其他的線程/進(jìn)程會(huì)被堵塞账劲,直到有人解鎖。Mutex相當(dāng)于只允許一個(gè)線程/進(jìn)程訪問(wèn)的semaphore金抡。此外瀑焦,根據(jù)實(shí)際需要,人們還實(shí)現(xiàn)了一種讀寫鎖(read-write lock)梗肝,它允許同時(shí)存在多個(gè)讀取者(reader)榛瓮,但任何時(shí)候至多只有一個(gè)寫入者(writer),且不能與讀取者共存巫击。
死鎖
在引入鎖的同時(shí)禀晓,我們遇到了一個(gè)新的問(wèn)題:死鎖(Deadlock)精续。死鎖是指兩個(gè)或多個(gè)線程/進(jìn)程之間相互阻塞,以至于任何一個(gè)都不能繼續(xù)運(yùn)行粹懒,因此也不能解鎖其他線程/進(jìn)程重付。例如,線程A占有l(wèi)ock A凫乖,并且嘗試獲取lock B确垫;而線程2占有l(wèi)ock B,嘗試獲取lock A帽芽。此時(shí)删掀,兩者相互阻塞,都無(wú)法繼續(xù)運(yùn)行导街。
產(chǎn)生死鎖的4個(gè)條件概括如下(只有當(dāng)4個(gè)條件同時(shí)滿足時(shí)才會(huì)產(chǎn)生死鎖):
(1)Mutual Exclusion – Only one process may use a resource at a time
(2)Hold-and-Wait – Process holds resource while waiting for another
(3)No Preemption – Can’t take a resource away from a process
(4)Circular Wait – The waiting processes form a cycle
生產(chǎn)者消費(fèi)者
生產(chǎn)者消費(fèi)者模型是一種常見(jiàn)的通信模型:生產(chǎn)者和消費(fèi)者共享一個(gè)數(shù)據(jù)管道披泪,生產(chǎn)者將數(shù)據(jù)寫入buffer,消費(fèi)者從另一頭讀取數(shù)據(jù)搬瑰。對(duì)于數(shù)據(jù)管道款票,需要考慮為空和溢出的情況。同時(shí)跌捆,通常還需要將這部分共享內(nèi)存用mutex加鎖徽职。在只有一個(gè)生產(chǎn)者一個(gè)消費(fèi)者的情況下象颖,可以設(shè)計(jì)無(wú)鎖隊(duì)列(lockless queue)佩厚,線程安全地直接讀寫數(shù)據(jù)。
進(jìn)程間通信
在介紹進(jìn)程的時(shí)候说订,我們提起過(guò)一個(gè)進(jìn)程不能直接讀寫另一個(gè)進(jìn)程的數(shù)據(jù)抄瓦,兩者之間的通信需要通過(guò)進(jìn)程間通信進(jìn)行。進(jìn)程通信的方式通常遵從生產(chǎn)者消費(fèi)者模型陶冷,需要實(shí)現(xiàn)數(shù)據(jù)交換和同步兩大功能钙姊。
(1)共享內(nèi)存(Shared-memory)+ semaphore
不同進(jìn)程通過(guò)讀寫操作系統(tǒng)中特殊的共享內(nèi)存進(jìn)行數(shù)據(jù)交換,進(jìn)程之間用semaphore實(shí)現(xiàn)同步埂伦。
(2)信息傳遞(Message passing)
進(jìn)程在操作系統(tǒng)內(nèi)部注冊(cè)一個(gè)端口煞额,并且監(jiān)測(cè)有沒(méi)有數(shù)據(jù),其他進(jìn)程直接寫數(shù)據(jù)到該端口沾谜。該通信方式更加接近于網(wǎng)絡(luò)通信方式膊毁。事實(shí)上,網(wǎng)絡(luò)通信也是一種IPC基跑,只是進(jìn)程分布在不同機(jī)器上而已婚温。
邏輯地址/物理地址/虛擬內(nèi)存
所謂的邏輯地址,是指計(jì)算機(jī)用戶(例如程序開(kāi)發(fā)者)看到的地址媳否。例如栅螟,當(dāng)創(chuàng)建一個(gè)長(zhǎng)度為100的整型數(shù)組時(shí)荆秦,操作系統(tǒng)返回一個(gè)邏輯上的連續(xù)空間:指針指向數(shù)組第一個(gè)元素的內(nèi)存地址。由于整型元素的大小為4個(gè)字節(jié)力图,故第二個(gè)元素的地址時(shí)起始地址加4步绸,以此類推。事實(shí)上搪哪,邏輯地址并不一定是元素存儲(chǔ)的真實(shí)地址靡努,即數(shù)組元素的物理地址(在內(nèi)存條中所處的位置),物理地址并不是連續(xù)的晓折,只不過(guò)操作系統(tǒng)通過(guò)地址映射惑朦,將邏輯地址映射成連續(xù)的,這樣更符合人們的直觀思維漓概。
另一個(gè)重要概念是虛擬內(nèi)存漾月。操作系統(tǒng)讀寫內(nèi)存的速度可以比讀寫磁盤的速度快幾個(gè)量級(jí)。但是胃珍,內(nèi)存價(jià)格也相對(duì)較高梁肿,不能大規(guī)模擴(kuò)展。于是觅彰,操作系統(tǒng)可以將部分不太常用的數(shù)據(jù)移出內(nèi)存吩蔑,存放到價(jià)格相對(duì)較低的磁盤緩存,以實(shí)現(xiàn)內(nèi)存擴(kuò)展填抬。操作系統(tǒng)還可以通過(guò)算法預(yù)測(cè)哪部分存儲(chǔ)到磁盤緩存的數(shù)據(jù)需要進(jìn)行讀寫烛芬,提前把這部分?jǐn)?shù)據(jù)讀回內(nèi)存。虛擬內(nèi)存空間相對(duì)磁盤而言要小很多飒责,因此赘娄,即使搜索虛擬內(nèi)存空間也比直接搜索磁盤要快。唯一慢于磁盤的可能是宏蛉,內(nèi)存遣臼、虛擬內(nèi)存中都沒(méi)有所需要的數(shù)據(jù),最終還需要從硬盤中直接讀取拾并。這就是為什么內(nèi)存和虛擬內(nèi)存中需要存儲(chǔ)會(huì)被重復(fù)讀寫的數(shù)據(jù)揍堰,否則就失去了緩存的意義。
現(xiàn)代計(jì)算機(jī)中有一個(gè)專門的轉(zhuǎn)譯緩沖區(qū)(Translation Lookaside Buffer嗅义,TLB)屏歹,用來(lái)實(shí)現(xiàn)虛擬地址到物理地址的快速轉(zhuǎn)換。
與內(nèi)存/虛擬內(nèi)存相關(guān)的還有以下兩個(gè)概念:
(1)Resident Set
當(dāng)一個(gè)進(jìn)程在運(yùn)行的時(shí)候芥喇,操作系統(tǒng)不會(huì)一次性加載進(jìn)程的所有數(shù)據(jù)到內(nèi)存西采,只會(huì)加載一部分正在用,以及預(yù)期要用的數(shù)據(jù)继控。其他數(shù)據(jù)可能存儲(chǔ)在虛擬內(nèi)存械馆,交換區(qū)和硬盤文件系統(tǒng)上胖眷。被加載到內(nèi)存的部分就是resident set。
(2)Thrashing
由于resident set包含預(yù)期要用的數(shù)據(jù)霹崎,理想情況下珊搀,進(jìn)程運(yùn)行過(guò)程中用到的數(shù)據(jù)都會(huì)逐步加載進(jìn)resident set。但事實(shí)往往并非如此:每當(dāng)需要的內(nèi)存頁(yè)面(page)不在resident set中時(shí)尾菇,操作系統(tǒng)必須從虛擬內(nèi)存或硬盤中讀數(shù)據(jù)境析,這個(gè)過(guò)程被稱為內(nèi)存頁(yè)面錯(cuò)誤(page faults)。當(dāng)操作系統(tǒng)需要花費(fèi)大量時(shí)間去處理頁(yè)面錯(cuò)誤的情況就是thrashing派诬。
文件系統(tǒng)
UNIX風(fēng)格的文件系統(tǒng)利用樹(shù)形結(jié)構(gòu)管理文件劳淆。每個(gè)節(jié)點(diǎn)有多個(gè)指針,指向下一層節(jié)點(diǎn)或者文件的磁盤存儲(chǔ)位置默赂。文件節(jié)點(diǎn)還附有文件的操作信息(metadata)沛鸵,包括修改時(shí)間、訪問(wèn)權(quán)限等缆八。
用戶的訪問(wèn)權(quán)限通過(guò)訪問(wèn)控制表(Access Control List)和能力表(Capability List)實(shí)現(xiàn)曲掰。前者從文件角度出發(fā),標(biāo)注了每個(gè)用戶可以對(duì)該文件進(jìn)行何種操作奈辰。后者從用戶角度出發(fā)栏妖,標(biāo)注了某用戶可以以什么權(quán)限操作哪些文件。
UNIX的文件權(quán)限分為讀奖恰、寫和執(zhí)行吊趾,用戶組分為文件擁有者、組和所有用戶房官≈夯眨可以通過(guò)命令對(duì)三組用戶分別設(shè)置權(quán)限续滋。
實(shí)時(shí)vs.分時(shí)操作系統(tǒng)
操作系統(tǒng)可以分為實(shí)時(shí)操作系統(tǒng)(Real-Time System)翰守,和分時(shí)操作系統(tǒng)(Sharing Time System)。通常計(jì)算機(jī)采用的是分時(shí)疲酌,即多個(gè)進(jìn)程/用戶之間共享CPU蜡峰,從形勢(shì)上實(shí)現(xiàn)多任務(wù)。各個(gè)用戶/進(jìn)程之間的調(diào)度并非精準(zhǔn)度特別高朗恳,如果一個(gè)進(jìn)程被鎖住湿颅,可以給它分配更多的時(shí)間。而實(shí)時(shí)操作系統(tǒng)則不同粥诫,軟件和硬件必須遵從嚴(yán)格的deadline油航,超過(guò)時(shí)限的進(jìn)程可能直接被終止。在這樣的操作系統(tǒng)中怀浆,每次加鎖都需要仔細(xì)考慮谊囚。
編譯器
對(duì)于高級(jí)語(yǔ)言來(lái)說(shuō)怕享,代碼需要通過(guò)編譯才能夠運(yùn)行。編譯通過(guò)編譯器(Compiler)實(shí)現(xiàn)镰踏,是一個(gè)將程序源代碼轉(zhuǎn)換成二進(jìn)制機(jī)器碼的過(guò)程函筋。計(jì)算機(jī)可以直接執(zhí)行二進(jìn)制代碼。在編譯的過(guò)程中奠伪,編譯器需要進(jìn)行詞法分析(Lexical Analysis)跌帐、解析(Parsing)和過(guò)渡代碼生成(Intermediate Code Generation)。編譯器的好壞可以直接影響最終代碼的執(zhí)行效率绊率。