硬核操作系統(tǒng)講解
掃描二維碼
隨時(shí)隨地手機(jī)看文章
1 馮諾伊曼體系
1.1 馮諾伊曼體系簡(jiǎn)介
現(xiàn)代計(jì)算機(jī)之父馮諾伊曼最先提出程序存儲(chǔ)的思想,并成功將其運(yùn)用在計(jì)算機(jī)的設(shè)計(jì)之中,該思想約定了用二進(jìn)制進(jìn)行計(jì)算和存儲(chǔ),還定義計(jì)算機(jī)基本結(jié)構(gòu)為 5 個(gè)部分,分別是中央處理器(CPU)、內(nèi)存、輸入設(shè)備、輸出設(shè)備、總線(xiàn)。
-
存儲(chǔ)器:代碼跟數(shù)據(jù)在RAM跟ROM中是線(xiàn)性存儲(chǔ), 數(shù)據(jù)存儲(chǔ)的單位是一個(gè)二進(jìn)制位。最小的存儲(chǔ)單位是字節(jié)。
-
總線(xiàn):總線(xiàn)是用于 CPU 和內(nèi)存以及其他設(shè)備之間的通信,總線(xiàn)主要有三種:
地址總線(xiàn):用于指定 CPU 將要操作的內(nèi)存地址。
數(shù)據(jù)總線(xiàn):用于讀寫(xiě)內(nèi)存的數(shù)據(jù)。
控制總線(xiàn):用于發(fā)送和接收信號(hào),比如中斷、設(shè)備復(fù)位等信號(hào),CPU 收到信號(hào)后響應(yīng),這時(shí)也需要控制總線(xiàn)。
-
輸入/輸出設(shè)備:輸入設(shè)備向計(jì)算機(jī)輸入數(shù)據(jù),計(jì)算機(jī)經(jīng)過(guò)計(jì)算后,把數(shù)據(jù)輸出給輸出設(shè)備。比如鍵盤(pán)按鍵時(shí)需要和 CPU 進(jìn)行交互,這時(shí)就需要用到控制總線(xiàn)。
-
CPU:中央處理器,類(lèi)比人腦,作為計(jì)算機(jī)系統(tǒng)的運(yùn)算和控制核心,是信息處理、程序運(yùn)行的最終執(zhí)行單元。CPU用寄存器存儲(chǔ)計(jì)算時(shí)所需數(shù)據(jù),寄存器一般有三種:
通用寄存器:用來(lái)存放需要進(jìn)行運(yùn)算的數(shù)據(jù),比如需進(jìn)行加法運(yùn)算的兩個(gè)數(shù)據(jù)。
程序計(jì)數(shù)器:用來(lái)存儲(chǔ) CPU 要執(zhí)行下一條指令所在的內(nèi)存地址。
指令寄存器:用來(lái)存放程序計(jì)數(shù)器指向的指令本身。
在馮諾伊曼體系下電腦指令執(zhí)行的過(guò)程:
-
CPU讀取程序計(jì)數(shù)器獲得指令內(nèi)存地址,CPU控制單元操作地址總線(xiàn)從內(nèi)存地址拿到數(shù)據(jù),數(shù)據(jù)通過(guò)數(shù)據(jù)總線(xiàn)到達(dá)CPU被存入指令寄存器。
-
CPU分析指令寄存器中的指令,如果是計(jì)算類(lèi)型的指令交給邏輯運(yùn)算單元,如果是存儲(chǔ)類(lèi)型的指令交給控制單元執(zhí)行。
-
CPU 執(zhí)行完指令后程序計(jì)數(shù)器的值通過(guò)自增指向下個(gè)指令,比如32位CPU會(huì)自增4。
-
自增后開(kāi)始順序執(zhí)行下一條指令,不斷循環(huán)執(zhí)行直到程序結(jié)束。
CPU位寬:32位CPU一次可操作計(jì)算4個(gè)字節(jié),64位CPU一次可操作計(jì)算8個(gè)字節(jié),這個(gè)是硬件級(jí)別的。平常我們說(shuō)的32位或64位操作系統(tǒng)指的是軟件級(jí)別的,指的是程序中指令多少位。
線(xiàn)路位寬:CPU操作指令數(shù)據(jù)通過(guò)高低電壓變化進(jìn)行數(shù)據(jù)傳輸,傳輸時(shí)候可以串行傳輸,也可以并行傳輸,多少個(gè)并行等于多少個(gè)位寬。
1.2 CPU 簡(jiǎn)介
Central Processing Unit 中央處理器,作為計(jì)算機(jī)系統(tǒng)的運(yùn)算和控制核心,是信息處理、程序運(yùn)行的最終執(zhí)行單元。
CPU
-
CPU核心:一般一個(gè)CPU會(huì)有多個(gè)CPU核心,平常說(shuō)的多核是指在一枚處理器中集成兩個(gè)或多個(gè)完整的計(jì)算引擎。核跟CPU的關(guān)系是:核屬于CPU的一部分。
-
寄存器:最靠近CPU對(duì)存儲(chǔ)單元,32位CPU寄存器可存儲(chǔ)4字節(jié),64位寄存器可存儲(chǔ)8字節(jié)。寄存器訪(fǎng)問(wèn)速度一般是半個(gè)CPU時(shí)鐘周期,屬于納秒級(jí)別,
-
L1緩存:每個(gè)CPU核心都有,用來(lái)緩存數(shù)據(jù)跟指令,訪(fǎng)問(wèn)空間大小一般在32~256KB,訪(fǎng)問(wèn)速度一般是2~4個(gè)CPU時(shí)鐘周期。
cat /sys/devices/system/cpu/cpu0/cache/index0/size # L1 數(shù)據(jù)緩存 cat /sys/devices/system/cpu/cpu0/cache/index1/size # L1 指令緩存
-
L2緩存:每個(gè)CPU核心都有,訪(fǎng)問(wèn)空間大小在128KB~2MB,訪(fǎng)問(wèn)速度一般是10~20個(gè)CPU時(shí)鐘周期。
cat /sys/devices/system/cpu/cpu0/cache/index2/size # L2 緩存容量大小 -
L3緩存:多個(gè)CPU核心共用,訪(fǎng)問(wèn)空間大小在2MB~64MB,訪(fǎng)問(wèn)速度一般是20~60個(gè)CPU時(shí)鐘周期。
cat /sys/devices/system/cpu/cpu0/cache/index3/size # L3 緩存容量大小 -
內(nèi)存:多個(gè)CPU共用,現(xiàn)在一般是4G~512G,訪(fǎng)問(wèn)速度一般是200~300個(gè)CPU時(shí)鐘周期。
-
固體硬盤(pán)SSD:現(xiàn)在臺(tái)式機(jī)主流都會(huì)配備,上述的寄存器、緩存、內(nèi)存都是斷電數(shù)據(jù)立馬丟失的,而SSD里不會(huì)丟失,大小一般是128G~1T,比內(nèi)存慢10~1000倍。
-
機(jī)械盤(pán)HDD:很早以前流行的硬盤(pán)了,容量可在512G~8T不等,訪(fǎng)問(wèn)速度比內(nèi)存慢10W倍不等。
-
訪(fǎng)問(wèn)數(shù)據(jù)順序:CPU在拿數(shù)據(jù)處理的時(shí)候幾乎也是按照上面說(shuō)得流程來(lái)操縱的,只有上面一層找不到才會(huì)找下一層。
-
Cache Line: CPU讀取數(shù)據(jù)時(shí)會(huì)按照 Cache Line 方式把數(shù)據(jù)加載到緩存中,每個(gè)Cacheline = 64KB,因?yàn)長(zhǎng)1、L2是每個(gè)核獨(dú)有到可能會(huì)觸發(fā)偽共享,就是 所以可能會(huì)將數(shù)據(jù)劃分到不同到CacheLine中來(lái)避免偽共享,比如在JDK8 新增加的 LongAdder 就涉及到此知識(shí)點(diǎn)。
偽共享:緩存系統(tǒng)中是以緩存行(cache line)為單位存儲(chǔ)的,當(dāng)多線(xiàn)程修改互相獨(dú)立的變量時(shí),如果這些變量共享同一個(gè)緩存行,就會(huì)無(wú)意中影響彼此的性能,這就是偽共享。
-
JMM: 數(shù)據(jù)經(jīng)過(guò)種種分層會(huì)導(dǎo)致訪(fǎng)問(wèn)速度在不斷提升,同時(shí)也帶來(lái)了各種問(wèn)題,多個(gè)CPU同時(shí)操作相同數(shù)據(jù)可能會(huì)造成各種BU個(gè),需要加鎖,這里在JUC并發(fā)已詳細(xì)探討過(guò)。
1.3 CPU 訪(fǎng)問(wèn)方式
CPU訪(fǎng)問(wèn)方式內(nèi)存數(shù)據(jù)映射到CPU Cache 時(shí)通過(guò)公式Block N % CacheLineMax決定內(nèi)存Block數(shù)據(jù)放到那個(gè)CPU Cache Line 里。CPU Cache 主要有4部分組成。
-
Cache Line Index :CPU緩存讀取數(shù)據(jù)時(shí)不是按照字節(jié)來(lái)讀取的,而是按照CacheLine方式存儲(chǔ)跟讀取數(shù)據(jù)的。
-
Valid Bit : 有效位標(biāo)志符,值為0時(shí)表示無(wú)論 CPU Line 中是否有數(shù)據(jù),CPU 都會(huì)直接訪(fǎng)問(wèn)內(nèi)存,重新加載數(shù)據(jù)。
-
Tag:組標(biāo)記,用來(lái)標(biāo)記內(nèi)存中不同BLock映射到相同CacheLine,用Tag來(lái)區(qū)分不同的內(nèi)存Block。
-
Data:真實(shí)到內(nèi)存數(shù)據(jù)信息。
CPU真實(shí)訪(fǎng)問(wèn)內(nèi)存數(shù)據(jù)時(shí)只需要指定三個(gè)部分即可。
-
Cache Line Index :要訪(fǎng)問(wèn)到Cache Line 位置。
-
Tag:表示用那個(gè)數(shù)據(jù)塊。
-
Offset:CPU從CPU Cache 讀取數(shù)據(jù)時(shí)不是直接讀取Cache Line整個(gè)數(shù)據(jù)塊,而是讀取CPU所需的數(shù)據(jù)片段,稱(chēng)為Word。如何找到Word就需要個(gè)偏移量Offset。
1.4 CPU 訪(fǎng)問(wèn)速度
訪(fǎng)問(wèn)耗時(shí)對(duì)比如上圖所示,CPU訪(fǎng)問(wèn)速度是逐步變慢,所以CPU訪(fǎng)問(wèn)數(shù)據(jù)時(shí)需盡量在距離CPU近的高速緩存區(qū)訪(fǎng)問(wèn),根據(jù)摩爾定律CPU訪(fǎng)問(wèn)速度每18個(gè)月就會(huì)翻倍,而內(nèi)存的訪(fǎng)問(wèn)每18個(gè)月也就增長(zhǎng)10% 左右,導(dǎo)致的結(jié)果就是CPU跟內(nèi)存訪(fǎng)問(wèn)性能差距逐步變大,那如何盡可能提高CPU緩存命中率呢?
1.數(shù)據(jù)緩存:遍歷數(shù)據(jù)時(shí)候按照內(nèi)存布局順序訪(fǎng)問(wèn),因?yàn)镃PU Cache是根據(jù)Cache Line批量操作數(shù)據(jù)的,所以你順序讀取數(shù)據(jù)會(huì)提速,道理跟磁盤(pán)順序?qū)懸粯印?
-
指令緩存:盡可能的提供有規(guī)律的條件分支語(yǔ)句,讓 CPU 的分支預(yù)測(cè)器發(fā)揮作用,進(jìn)一步提高執(zhí)行的效率,因?yàn)镃PU是自帶分支預(yù)測(cè)器,自動(dòng)提前將可能需要的指令放到指令緩存區(qū)。
-
線(xiàn)程綁定到CPU:一個(gè)任務(wù)A在前一個(gè)時(shí)間片用CPU核心1 運(yùn)行,后一個(gè)時(shí)間片用CPU核心2 運(yùn)行,這樣緩存L1、L2就浪費(fèi)了。因此操作系統(tǒng)提供了將進(jìn)程或者線(xiàn)程綁定到某一顆 CPU 上運(yùn)行的能力。如 Linux 上提供了 sched_setaffinity 方法實(shí)現(xiàn)這一功能,其他操作系統(tǒng)也有類(lèi)似功能的 API 可用。當(dāng)多線(xiàn)程同時(shí)執(zhí)行密集計(jì)算,且 CPU 緩存命中率很高時(shí),如果將每個(gè)線(xiàn)程分別綁定在不同的 CPU 核心上,性能便會(huì)獲得非常可觀(guān)的提升。
1.5 操作系統(tǒng)
計(jì)算機(jī)結(jié)構(gòu)有了馮諾伊曼計(jì)算機(jī)體系后,電腦想要為用戶(hù)提供便捷的服務(wù)還需要安裝個(gè)操作系統(tǒng)Operation System,操作系統(tǒng)是覆蓋在硬件上的一層特殊軟件,它管理計(jì)算機(jī)的硬件和軟件資源,為其他應(yīng)用程序提供大量服務(wù)??梢岳斫鉃椴僮飨到y(tǒng)是日常應(yīng)用程序跟硬件之間的接口。日常你經(jīng)常在用Windows/Linux 系統(tǒng),操作系統(tǒng)給我們提供了超級(jí)大的便利,但是你了解操作系統(tǒng)么?操作系統(tǒng)是如何進(jìn)行內(nèi)存管理、進(jìn)程管理、文件管理、輸入輸出管理的呢?
2 內(nèi)存管理
你的電腦是32位操作系統(tǒng),那可支持的最大內(nèi)存就是4G,你有沒(méi)有好奇為什么可以同時(shí)運(yùn)行2個(gè)以上的2G內(nèi)存的程序。應(yīng)用程序不是直接使用的物理地址,操作系統(tǒng)為每個(gè)運(yùn)行的進(jìn)程分配了一套虛擬地址,每個(gè)進(jìn)程都有自己的虛擬內(nèi)存地址,進(jìn)程是無(wú)法直接進(jìn)行物理內(nèi)存地址的訪(fǎng)問(wèn)的。至于虛擬地址跟物理地址的映射,進(jìn)程是感知不到的!操作系統(tǒng)自身會(huì)提供一套機(jī)制將不同進(jìn)程的虛擬地址和不同內(nèi)存的物理地址進(jìn)行映射。
虛擬內(nèi)存
2.1 MMU
Memory Management Unit 內(nèi)存管理單元是一種負(fù)責(zé)處理CPU內(nèi)存訪(fǎng)問(wèn)請(qǐng)求的計(jì)算機(jī)硬件。它的功能包括虛擬地址到物理地址的轉(zhuǎn)換、內(nèi)存保護(hù)、中央處理器高速緩存的控制?,F(xiàn)代 CPU 基本上都選擇了使用 MMU。
當(dāng)進(jìn)程持有虛擬內(nèi)存地址的時(shí)候,CPU執(zhí)行該進(jìn)程時(shí)會(huì)操作虛擬內(nèi)存,而MMU會(huì)自動(dòng)的將虛擬內(nèi)存的操作映射到物理內(nèi)存上。
MMU這里提一下,Java操作的時(shí)候你看到的地址是JVM地址,不是真正的物理地址。
2.2 內(nèi)存管理方式
操作系統(tǒng)主要采用內(nèi)存分段和內(nèi)存分頁(yè)來(lái)管理虛擬地址與物理地址之間的關(guān)系,其中分段是很早前的方法了,現(xiàn)在大部分用的是分頁(yè),不過(guò)分頁(yè)也不是完全的分頁(yè),是在分段的基礎(chǔ)上再分頁(yè)。
2.2.1 內(nèi)存分段
JVM內(nèi)存模型
我們以上圖的JVM內(nèi)存模型舉例,程序員會(huì)認(rèn)為我們的代碼是由代碼段、數(shù)據(jù)段、棧段、堆段組成。不同的段是有不同的屬性的,用戶(hù)并不關(guān)心這些元素所在內(nèi)存的位置,而分段就是支持這種用戶(hù)視圖的內(nèi)存管理方案。邏輯地址空間是由一組段構(gòu)成。每個(gè)段都有名稱(chēng)和長(zhǎng)度。地址指定了段名稱(chēng)和段內(nèi)偏移。因此用戶(hù)段編號(hào)和段偏移來(lái)指定不同屬性的地址。而虛擬內(nèi)存地址跟物理內(nèi)存地址中間是通過(guò)段表進(jìn)行映射的,口說(shuō)無(wú)憑,看圖吧。
內(nèi)存分段管理
如上虛擬地址有 5 個(gè)段,各段按如圖所示來(lái)存儲(chǔ)。每個(gè)段都在段表中有一個(gè)條目,它包括段在物理內(nèi)存內(nèi)的開(kāi)始的基地址和該段的界限長(zhǎng)度。例如段 2 為 400 字節(jié)長(zhǎng),開(kāi)始于位置 4300。因此對(duì)段 2 字節(jié) 53 的引用映射成位置 4300 + 53 = 4353。對(duì)段 3 字節(jié) 852 的引用映射成位置 3200 + 852 = 4052。
分段映射很簡(jiǎn)單,但是會(huì)導(dǎo)致內(nèi)存碎片跟內(nèi)存交互效率低。這里先普及下在內(nèi)存管理中主要有內(nèi)部?jī)?nèi)存碎片跟外部?jī)?nèi)存碎片。
-
內(nèi)部碎片:已經(jīng)被分配出去的的內(nèi)存空間不經(jīng)常使用,并且分配出去的內(nèi)存空間大于請(qǐng)求所需的內(nèi)存空間。
-
外部碎片:指可用空間還沒(méi)有分配出去,但是可用空間由于大小太小而無(wú)法分配給申請(qǐng)空間的新進(jìn)程的內(nèi)存空間空閑塊。
以上圖為例,現(xiàn)在系統(tǒng)空閑是1400 + 800 + 600 = 2800。那如果有個(gè)程序想要連續(xù)的使用2000,內(nèi)存分段模式下提供不了啊!上述三個(gè)是外部?jī)?nèi)存碎片。當(dāng)然可以使用系統(tǒng)的Swap空間,先把段0寫(xiě)入到磁盤(pán),然后再重新給段0分配空間。這樣可以實(shí)現(xiàn)最終可用,可是但凡涉及到磁盤(pán)讀寫(xiě)就會(huì)導(dǎo)致內(nèi)存交互效率低。
swap空間利用
2.2.2 內(nèi)存分頁(yè)
內(nèi)存分頁(yè),整個(gè)虛擬內(nèi)存和物理內(nèi)存切成一段段固定尺寸的大小。每個(gè)固定大小的尺寸稱(chēng)之為頁(yè)P(yáng)age,在 Linux 系統(tǒng)中Page = 4KB。然后虛擬內(nèi)存跟物理內(nèi)存之間通過(guò)頁(yè)表來(lái)實(shí)現(xiàn)映射。
采用內(nèi)存分頁(yè)時(shí)內(nèi)存的釋放跟使用都是以頁(yè)為單位的,也就不會(huì)產(chǎn)生內(nèi)存碎片了。當(dāng)空間還不夠時(shí)根據(jù)操作系統(tǒng)調(diào)度算法,可能將最少用的內(nèi)存頁(yè)面 swap-out換出到磁盤(pán),用時(shí)候再swap-in換入,盡可能的減少磁盤(pán)刷寫(xiě)量,提高內(nèi)存交互效率。
分頁(yè)模式下虛擬地址主要有頁(yè)號(hào)跟頁(yè)內(nèi)偏移量?jī)刹糠纸M成。通過(guò)頁(yè)號(hào)查詢(xún)頁(yè)表找到物理內(nèi)存地址,然后再配合頁(yè)內(nèi)偏移量就找到了真正的物理內(nèi)存地址。
分頁(yè)內(nèi)存尋址
32位操作系統(tǒng)環(huán)境下進(jìn)程可操作的虛擬地址是4GB,假設(shè)一個(gè)虛擬頁(yè)大小為4KB,那需要4GB/4KB =2^20個(gè)頁(yè)信息。一行頁(yè)表記錄為4字節(jié),2^20等價(jià)于4MB頁(yè)表存儲(chǔ)信息。這只是一個(gè)進(jìn)程需要的,如果10個(gè)、100個(gè)、1000個(gè)呢??jī)H僅是頁(yè)表存儲(chǔ)都占據(jù)超大內(nèi)存了。
為了解決這個(gè)問(wèn)題就需要用到多級(jí)頁(yè)表,核心思想就是局部性分配。在32位的操作系統(tǒng)中將將4G空間分為 1024 行頁(yè)目錄項(xiàng)目(4KB),每個(gè)頁(yè)目錄項(xiàng)又對(duì)應(yīng)1024行頁(yè)表項(xiàng)。如下圖所示:
32位系統(tǒng)二級(jí)分頁(yè)控制寄存器cr3中存放了頁(yè)目錄的物理地址,通過(guò)cr3寄存器可以找到頁(yè)目錄,而32位線(xiàn)性地址中的Directory部分決定頁(yè)目錄中的目錄項(xiàng),而頁(yè)目錄項(xiàng)中存放了要找的頁(yè)表的物理基地址,再結(jié)合線(xiàn)性地址中的中間10位頁(yè)表項(xiàng),就可以找到頁(yè)框的頁(yè)表項(xiàng)。線(xiàn)性地址中的Offset部分占12位,因此頁(yè)框的物理地址 + 線(xiàn)性地址Offset部分 = 頁(yè)框中的任何一個(gè)字節(jié)。
分頁(yè)后一級(jí)頁(yè)就等價(jià)于4G虛擬地址空間,并且如果一級(jí)頁(yè)表中那些地址沒(méi)有就不需要再創(chuàng)建二級(jí)頁(yè)表了!核心思想就是按需創(chuàng)建,當(dāng)系統(tǒng)給每個(gè)進(jìn)程分配4G空間,進(jìn)程不可能占據(jù)全部?jī)?nèi)存的,如果一級(jí)目錄頁(yè)只有10%用到了,此時(shí)頁(yè)表空間 = 一級(jí)頁(yè)表4KB + 0.1 * 4MB 。這比單獨(dú)的每個(gè)進(jìn)程占據(jù)4M好用多了!
多層分頁(yè)的弊端就是訪(fǎng)問(wèn)時(shí)間的增加。
-
使用頁(yè)表時(shí)讀取內(nèi)存中一頁(yè)內(nèi)容需要2次訪(fǎng)問(wèn)內(nèi)存,訪(fǎng)問(wèn)頁(yè)表項(xiàng) + 并讀取的一頁(yè)數(shù)據(jù)。
-
使用二級(jí)頁(yè)表的話(huà)需要三次訪(fǎng)問(wèn),訪(fǎng)問(wèn)頁(yè)目錄項(xiàng) + 訪(fǎng)問(wèn)頁(yè)表項(xiàng) + 訪(fǎng)問(wèn)并讀取的一頁(yè)數(shù)據(jù)。訪(fǎng)存次數(shù)的增加也就意味著訪(fǎng)問(wèn)數(shù)據(jù)所花費(fèi)的總時(shí)間增加。
而對(duì)于64位系統(tǒng),二級(jí)分頁(yè)就無(wú)法滿(mǎn)足了,Linux 從2.6.11開(kāi)始采用四級(jí)分頁(yè)模型。
-
Page Global Directory 全局頁(yè)目錄項(xiàng)
-
Page Upper Directory 上層頁(yè)目錄項(xiàng)
-
Page Middle Directory 中間頁(yè)目錄項(xiàng)
-
Page Table Entry 頁(yè)表項(xiàng)
-
Offset 偏移量。
64位尋址
2.2.2 TLB
Translation Lookaside Buffer 可翻譯為地址轉(zhuǎn)換后援緩沖器,簡(jiǎn)稱(chēng)為快表,屬于CPU內(nèi)部的一個(gè)模塊,TLB是MMU的一部分,實(shí)質(zhì)是cache,它所緩存的是最近使用的數(shù)據(jù)的頁(yè)表項(xiàng)(虛擬地址到物理地址的映射)。他的出現(xiàn)是為了加快訪(fǎng)問(wèn)數(shù)據(jù)(內(nèi)存)的速度,減少重復(fù)的頁(yè)表查找。當(dāng)然它不是必須要有的,但有它,速度就更快。
TLBTLB很小,因此緩存的東西也不多。主要緩存最近使用的數(shù)據(jù)的數(shù)據(jù)映射。TLB結(jié)構(gòu)如下圖:
TLB查詢(xún)如果一個(gè)需要訪(fǎng)問(wèn)內(nèi)存中的一個(gè)數(shù)據(jù),給定這個(gè)數(shù)據(jù)的虛擬地址,查詢(xún)TLB,發(fā)現(xiàn)有hit,直接得到物理地址,在內(nèi)存根據(jù)物理地址取數(shù)據(jù)。如果TLB沒(méi)有這個(gè)虛擬地址miss,那么只能費(fèi)力的通過(guò)頁(yè)表來(lái)查找了。日常CPU讀取一個(gè)數(shù)據(jù)的流程如下:
CPU讀取數(shù)據(jù)流程圖當(dāng)進(jìn)程地址空間進(jìn)行了上下文切換時(shí),比如現(xiàn)在是進(jìn)程1運(yùn)行,TLB中放的是進(jìn)程1的相關(guān)數(shù)據(jù)的地址,突然切換到進(jìn)程2,TLB中原有的數(shù)據(jù)不是進(jìn)程2相關(guān)的,此時(shí)TLB刷新數(shù)據(jù)有兩種辦法。
-
全部刷新:很簡(jiǎn)單,但花銷(xiāo)大,很多不必刷新的數(shù)據(jù)也進(jìn)行刷新,增加了無(wú)畏的花銷(xiāo)。
-
部分刷新:根據(jù)標(biāo)志位,刷新需要刷新的數(shù)據(jù),保留不需要刷新的數(shù)據(jù)。
2.2.3 段頁(yè)式管理
內(nèi)存分段跟內(nèi)存分頁(yè)不是對(duì)立的,這倆可以組合起來(lái)在同一個(gè)系統(tǒng)中使用的,那么組合起來(lái)后通常稱(chēng)為段頁(yè)式內(nèi)存管理。段頁(yè)式內(nèi)存管理實(shí)現(xiàn)的方式:
-
先對(duì)數(shù)據(jù)不同劃分出不同的段,也就是前面說(shuō)的分段機(jī)制。
-
然后再把每一個(gè)段進(jìn)行分頁(yè)操作,也就是前面說(shuō)的分頁(yè)機(jī)制。
-
此時(shí) 地址結(jié)構(gòu) = 段號(hào) + 段內(nèi)頁(yè)號(hào) + 頁(yè)內(nèi)位移。
每一個(gè)進(jìn)程有一張段表,每個(gè)段又建立一張頁(yè)表,段表中的地址是頁(yè)表的起始地址,而頁(yè)表中的地址則為某頁(yè)的物理頁(yè)號(hào)。
段頁(yè)式管理同時(shí)我們經(jīng)??吹絻蓚€(gè)專(zhuān)業(yè)詞邏輯地址跟線(xiàn)性地址。
-
邏輯地址:指的是沒(méi)被段式內(nèi)存管理映射的地址。
-
線(xiàn)性地址:通過(guò)段式內(nèi)存管理映射且頁(yè)式內(nèi)存管理轉(zhuǎn)換前的地址,俗稱(chēng)虛擬地址。
目前 Intel X86 CPU 采用的是內(nèi)存分段 + 內(nèi)存分頁(yè)的管理方式,其中分頁(yè)的意思是在由段式內(nèi)存管理所映射而成的的地址上再加上一層地址映射。
X86內(nèi)存管理方式
2.2.4 Linux 內(nèi)存管理
先說(shuō)結(jié)論:Linux系統(tǒng)基于X86 CPU 而做的操作系統(tǒng),所以也是用的段頁(yè)式內(nèi)存管理方式。

我們知道32位的操作系統(tǒng)可尋址范圍是4G,操作系統(tǒng)會(huì)將4G的可訪(fǎng)問(wèn)內(nèi)存空間分為用戶(hù)空間跟內(nèi)核空間。
-
內(nèi)核空間:操作系統(tǒng)內(nèi)核訪(fǎng)問(wèn)的區(qū)域,獨(dú)立于普通的應(yīng)用程序,是受保護(hù)的內(nèi)存空間。內(nèi)核態(tài)下CPU可執(zhí)行任何指令,可自由訪(fǎng)問(wèn)任何有效地址。
-
用戶(hù)空間:普通應(yīng)用程序可訪(fǎng)問(wèn)的內(nèi)存區(qū)域。被執(zhí)行代碼會(huì)受到CPU眾多限制,進(jìn)程只能訪(fǎng)問(wèn)映射其地址空間的頁(yè)表項(xiàng)中規(guī)定的在用戶(hù)態(tài)下可訪(fǎng)問(wèn)頁(yè)面的虛擬地址。
那為啥要搞倆空間呢?現(xiàn)在嵌入式環(huán)境跟以前的WIN98系統(tǒng)是沒(méi)有區(qū)分倆空間的,須知倆空間是CPU分的,而操作系統(tǒng)是在上面運(yùn)行的,單一用戶(hù)、單一任務(wù)服務(wù)的操作系統(tǒng),是沒(méi)有分所謂用戶(hù)態(tài)和內(nèi)核態(tài)的必要。用戶(hù)態(tài)和內(nèi)核態(tài)是因?yàn)橛卸嘤脩?hù),多任務(wù)的需求,然后在CPU硬件廠(chǎng)商配合之后,產(chǎn)生的一個(gè)操作系統(tǒng)解決多用戶(hù)多任務(wù)需求的方案。方案就是限制,通過(guò)硬件手段(也只能硬件手段才能做到),限制某些代碼,使其無(wú)法控制整個(gè)物理硬件,進(jìn)而使各個(gè)不同用戶(hù),不同任務(wù)的代碼,無(wú)權(quán)修改整個(gè)物理硬件,再進(jìn)而保護(hù)操作系統(tǒng)的核心底層代碼和其他用戶(hù)的數(shù)據(jù)不被無(wú)意或者有意地破壞和盜取。

后來(lái)研究者根據(jù)CPU的運(yùn)行級(jí)別,分成了Ring0~Ring3四個(gè)級(jí)別。Ring0是最高級(jí)別,Ring1次之,Rng2更次之,拿Linux+x86來(lái)說(shuō), 操作系統(tǒng)內(nèi)核的代碼運(yùn)行在最高運(yùn)行級(jí)別Ring0上,可以使用特權(quán)指令,控制中斷、修改頁(yè)表、訪(fǎng)問(wèn)設(shè)備等。 應(yīng)用程序的代碼運(yùn)行在最低運(yùn)行級(jí)別上Ring3上,不能做受控操作,只能訪(fǎng)問(wèn)用戶(hù)被分配的空間。如果要做訪(fǎng)問(wèn)磁盤(pán)跟寫(xiě)文件等操作,那就要通過(guò)執(zhí)行系統(tǒng)調(diào)用函數(shù),執(zhí)行系統(tǒng)調(diào)用的時(shí)候,CPU的運(yùn)行級(jí)別會(huì)發(fā)生從Ring3到Ring0的切換,并跳轉(zhuǎn)到系統(tǒng)調(diào)用對(duì)應(yīng)的內(nèi)核代碼位置執(zhí)行,這樣內(nèi)核就為你完成了設(shè)備訪(fǎng)問(wèn),完成之后再?gòu)腞ing0返回Ring3。這個(gè)過(guò)程也稱(chēng)作用戶(hù)態(tài)和內(nèi)核態(tài)的切換。
用戶(hù)態(tài)想要使用計(jì)算機(jī)設(shè)備或IO需通過(guò)系統(tǒng)調(diào)用完成sys call,系統(tǒng)調(diào)用就是讓內(nèi)核來(lái)做這些操作。而系統(tǒng)調(diào)用是影響整個(gè)當(dāng)前進(jìn)程上下文的,CPU提供了個(gè)軟中斷來(lái)是實(shí)現(xiàn)保護(hù)線(xiàn)程,獲取系統(tǒng)調(diào)用號(hào)跟參數(shù),交給內(nèi)核對(duì)應(yīng)系統(tǒng)調(diào)用函數(shù)執(zhí)行。
Linux系統(tǒng)結(jié)構(gòu)可以看到每個(gè)應(yīng)用程序都各自有獨(dú)立的虛擬內(nèi)存地址,但每個(gè)虛擬內(nèi)存中對(duì)應(yīng)的內(nèi)核地址其實(shí)是相同的一大塊,這樣當(dāng)進(jìn)程切換到內(nèi)核態(tài)后可以很方便地訪(fǎng)問(wèn)內(nèi)核空間內(nèi)存。比如Java代碼創(chuàng)建線(xiàn)程new Thread調(diào)用start方法后跟蹤JVM源碼你會(huì)發(fā)現(xiàn)是調(diào)用pthread_create來(lái)創(chuàng)建線(xiàn)程的,這就涉及到了用戶(hù)態(tài)到內(nèi)核態(tài)的切換。
3 進(jìn)程管理
3.1 進(jìn)程基礎(chǔ)知識(shí)
進(jìn)程是程序的一次執(zhí)行,是一個(gè)程序及其數(shù)據(jù)在機(jī)器上順序執(zhí)行時(shí)所發(fā)生的活動(dòng),是具有獨(dú)立功能的程序在一個(gè)數(shù)據(jù)集合上的一次運(yùn)行過(guò)程,是系統(tǒng)進(jìn)行資源分配和調(diào)度的一個(gè)基本單位。進(jìn)程的調(diào)度狀態(tài)如下:
狀態(tài)變化圖重點(diǎn)說(shuō)下掛起跟阻塞:
-
阻塞一般是當(dāng)系統(tǒng)執(zhí)行IO操作時(shí),此時(shí)進(jìn)程進(jìn)入阻塞狀態(tài),等待某個(gè)事件的返回。
-
掛起是指進(jìn)程沒(méi)有占有物理內(nèi)存,被寫(xiě)到磁盤(pán)上了。這時(shí)進(jìn)程狀態(tài)是掛起狀態(tài)。
阻塞掛起:進(jìn)程被寫(xiě)入硬盤(pán)并等待某個(gè)事件的出現(xiàn)。
就緒掛起:進(jìn)程被寫(xiě)入硬盤(pán),進(jìn)入內(nèi)存可直接進(jìn)入就緒狀態(tài)。
3.2 PCB
為了描述跟控制進(jìn)程的運(yùn)行,系統(tǒng)為每個(gè)進(jìn)程定義了一個(gè)數(shù)據(jù)結(jié)構(gòu)——進(jìn)程控制塊 Process Control Block,它是進(jìn)程實(shí)體的一部分,是操作系統(tǒng)中最重要的記錄型數(shù)據(jù)結(jié)構(gòu)。
PCB 的作用是使一個(gè)在多道程序環(huán)境下不能獨(dú)立運(yùn)行的程序,成為一個(gè)能獨(dú)立運(yùn)行的基本單位,一個(gè)能與其它進(jìn)程并發(fā)執(zhí)行的進(jìn)程 :
-
作為獨(dú)立運(yùn)行基本單位的標(biāo)志
-
實(shí)現(xiàn)間斷性的運(yùn)行方式
-
提供進(jìn)程管理所需要的信息
-
提供進(jìn)程調(diào)度所需要的信息
-
實(shí)現(xiàn)與其他進(jìn)程的同步與通信
3.2.1 PCB 信息
PCB為實(shí)現(xiàn)上述功能,內(nèi)部包含眾多信息:
-
進(jìn)程標(biāo)識(shí)符:用于唯一地標(biāo)識(shí)一個(gè)進(jìn)程,一個(gè)進(jìn)程通常有兩種標(biāo)識(shí)符:
內(nèi)部進(jìn)程標(biāo)識(shí)符:標(biāo)識(shí)各個(gè)進(jìn)程,每個(gè)進(jìn)程都有一個(gè)并且唯一的標(biāo)識(shí)符,設(shè)置內(nèi)部標(biāo)識(shí)符主要是為了方便系統(tǒng)使用。
外部進(jìn)程標(biāo)識(shí)符:它由創(chuàng)建者提供,可設(shè)置用戶(hù)標(biāo)識(shí),以指示擁有該進(jìn)程的用戶(hù)。往往是由用戶(hù)進(jìn)程在訪(fǎng)問(wèn)該進(jìn)程時(shí)使用。一般為了描述進(jìn)程的家族關(guān)系,還應(yīng)設(shè)置父進(jìn)程標(biāo)識(shí)及子進(jìn)程標(biāo)識(shí)。
-
處理機(jī)狀態(tài):由各種寄存器組成。包含許多信息都放在寄存器中,方便程序restart。
通用寄存器、指令計(jì)數(shù)器、程序狀態(tài)字PSW、用戶(hù)棧指針等信息。
-
進(jìn)程調(diào)度信息
進(jìn)程狀態(tài):指明進(jìn)程的當(dāng)前狀態(tài),作為進(jìn)程調(diào)度和對(duì)換時(shí)的依據(jù)。
進(jìn)程優(yōu)先級(jí):用于描述進(jìn)程使用處理機(jī)的優(yōu)先級(jí)別的一個(gè)整數(shù),優(yōu)先級(jí)高的進(jìn)程應(yīng)優(yōu)先獲得處理機(jī)
進(jìn)程調(diào)度所需的其它信息:與所采用的進(jìn)程調(diào)度算法有關(guān),如進(jìn)程已等待CPU的時(shí)間總和、進(jìn)程已執(zhí)行的時(shí)間總和等。
事件:指進(jìn)程由執(zhí)行狀態(tài)轉(zhuǎn)變?yōu)樽枞麪顟B(tài)所等待發(fā)生的事件,即阻塞原因。
-
資源清單
有關(guān)內(nèi)存地址空間或虛擬地址空間的信息,所打開(kāi)文件的列表和所使用的 I/O 設(shè)備信息。
3.2.2 PCB 組織方式
操作系統(tǒng)中有太多 PCB,如何管理是個(gè)問(wèn)題,一般有如下方式。
線(xiàn)下數(shù)組
-
線(xiàn)性方式:
將系統(tǒng)所有PCB都組織在一張線(xiàn)性表中,將該表首地址存在內(nèi)存的一個(gè)專(zhuān)用區(qū)域
實(shí)現(xiàn)簡(jiǎn)單,開(kāi)銷(xiāo)小,但是每次都需要掃描整張表,適合進(jìn)程數(shù)目不多的系統(tǒng)
索引方式
-
索引方式:
將同一狀態(tài)的進(jìn)程組織在一個(gè)索引表中,索引表項(xiàng)指向相應(yīng)的 PCB,不同狀態(tài)對(duì)應(yīng)不同的索引表。
鏈表方式
-
鏈接方式:
把同一狀態(tài)的PCB鏈接成一個(gè)隊(duì)列,形成就緒隊(duì)列、阻塞隊(duì)列、空白隊(duì)列等。對(duì)其中的就緒隊(duì)列常按進(jìn)程優(yōu)先級(jí)的高低排列,優(yōu)先級(jí)高排在隊(duì)前。
因?yàn)檫M(jìn)程創(chuàng)建、銷(xiāo)毀、調(diào)度頻繁,所以一般采用此模式。
3.3 進(jìn)程控制
進(jìn)程控制是進(jìn)程管理最基本的功能,主要包括創(chuàng)建新進(jìn)程,終止已完成的進(jìn)程,將發(fā)生異常的進(jìn)程置于阻塞狀態(tài),將進(jìn)程喚醒等。
3.3.1 進(jìn)程創(chuàng)建
父進(jìn)程可創(chuàng)建子進(jìn)程,父進(jìn)程終止后子進(jìn)程也會(huì)被終止。子進(jìn)程可繼承父進(jìn)程所有資源,子進(jìn)程終止需將自己所繼承的資源歸還父進(jìn)程。接下來(lái)看下創(chuàng)建的大致流程。
-
為新進(jìn)程分配唯一進(jìn)件標(biāo)識(shí)號(hào),然后創(chuàng)建一個(gè)空白PCB,需注意PCB數(shù)量是有限的,所以可能會(huì)創(chuàng)建失敗。
-
嘗試為新進(jìn)程分配所需資源,如果資源不足進(jìn)程會(huì)進(jìn)入等待狀態(tài)。
-
初始化PCB,有如下幾個(gè)操作。
標(biāo)識(shí)信息:將系統(tǒng)分配的標(biāo)識(shí)符和父進(jìn)程標(biāo)識(shí)符填入新PCB
處理機(jī)狀態(tài)信息:使程序計(jì)數(shù)器指向程序入口地址,使棧指針指向棧頂
處理機(jī)控制信息:將進(jìn)程設(shè)為就緒/靜止?fàn)顟B(tài),通常設(shè)為最低優(yōu)先級(jí)
-
如果進(jìn)程調(diào)度隊(duì)列能接納新進(jìn)程,就將進(jìn)程插入到就緒隊(duì)列,等待被調(diào)度運(yùn)行。
3.3.2 進(jìn)程終止
進(jìn)程終止情況一般分為正常結(jié)束、異常結(jié)束、外界干預(yù)三種。
-
正常結(jié)束
-
異常結(jié)束
越界錯(cuò):訪(fǎng)問(wèn)的存儲(chǔ)區(qū)越出該進(jìn)程的區(qū)域
保護(hù)錯(cuò):試圖訪(fǎng)問(wèn)不允許訪(fǎng)問(wèn)的資源,或以不適當(dāng)?shù)姆绞皆L(fǎng)問(wèn)(寫(xiě)只讀)
非法指令:試圖執(zhí)行不存在的指令(可能是程序錯(cuò)誤地轉(zhuǎn)移到數(shù)據(jù)區(qū),數(shù)據(jù)當(dāng)成了指令)
特權(quán)指令出錯(cuò):用戶(hù)進(jìn)程試圖執(zhí)行一條只允許OS執(zhí)行的指令
運(yùn)行超時(shí):執(zhí)行時(shí)間超過(guò)指定的最大值
等待超時(shí):進(jìn)程等待某件事超過(guò)指定的最大值
算數(shù)運(yùn)算錯(cuò):試圖執(zhí)行被禁止的運(yùn)算(被0除)
I/O故障
-
外界干預(yù)
操作員或OS干預(yù)(死鎖)
父進(jìn)程請(qǐng)求,子進(jìn)程完成父進(jìn)程指定的任務(wù)時(shí)
父進(jìn)程終止,所有子進(jìn)程都應(yīng)該結(jié)束
終止過(guò)程:
-
根據(jù)被終止進(jìn)程的標(biāo)識(shí)符,從PCB集合中檢索出該P(yáng)CB,讀取進(jìn)程狀態(tài)
-
若處于執(zhí)行狀態(tài)則立即終止執(zhí)行,將CPU資源分配給其他進(jìn)程。
-
若進(jìn)程有子孫進(jìn)程則將其所有子孫進(jìn)程終止。
-
全部資源還給父進(jìn)程或操作系統(tǒng)。
-
該進(jìn)程的PCB從所在隊(duì)列/鏈表中移出。
3.3.3 進(jìn)程阻塞
意思是該進(jìn)程執(zhí)行半路被阻塞,必須由某個(gè)事件進(jìn)程喚醒該進(jìn)程。常見(jiàn)的就是IO讀取操作。常見(jiàn)阻塞時(shí)機(jī)/事件如下:
-
請(qǐng)求共享資源失敗,系統(tǒng)無(wú)足夠資源分配
-
等待某種操作完成
-
新數(shù)據(jù)尚未到達(dá)(相互合作的進(jìn)程)
-
等待新任務(wù)
阻塞流程:
-
找到要被阻塞進(jìn)程標(biāo)識(shí)號(hào)對(duì)應(yīng)的 PCB。
-
將該進(jìn)程由運(yùn)行狀態(tài)轉(zhuǎn)換為阻塞狀態(tài)。
-
將該 進(jìn)程PCB 插入的阻塞隊(duì)列中去。
3.3.4 進(jìn)程喚醒
喚醒 原語(yǔ) wake up,一般和阻塞成對(duì)使用。喚醒過(guò)程如下:
-
從阻塞隊(duì)列找到所需PCB。
-
PCB從阻塞隊(duì)列溢出,然后變?yōu)榫途w狀態(tài)。
-
從阻塞隊(duì)列溢出該P(yáng)CB然后插入到就緒狀態(tài)隊(duì)列等待被分配CPU資源。
3.4 進(jìn)程調(diào)度
進(jìn)程數(shù)一般會(huì)大于CPU個(gè)數(shù),進(jìn)程狀態(tài)切換主要由調(diào)度程序進(jìn)行調(diào)度。一般情況下CPU調(diào)度時(shí)主要分為搶占式調(diào)度跟非搶占式調(diào)度。
-
非搶占式:讓進(jìn)程運(yùn)行直到結(jié)束或阻塞的調(diào)度方式, 容易實(shí)現(xiàn),適合專(zhuān)用系統(tǒng)。
-
搶占式:每個(gè)進(jìn)程獲得時(shí)間片才可以被CPU調(diào)度運(yùn)行, 可防止單一進(jìn)程長(zhǎng)時(shí)間獨(dú)占CPU 系統(tǒng)開(kāi)銷(xiāo)大。
3.4.1 進(jìn)程調(diào)度原則
-
CPU 利用率
CPU利用率 = 忙碌時(shí)間 / 總時(shí)間。
調(diào)度程序應(yīng)該盡量讓 CPU 始終處于忙碌的狀態(tài),這可提高 CPU 的利用率。比如當(dāng)發(fā)生IO讀取時(shí)候,不要傻傻等待,去執(zhí)行下別的進(jìn)程。
-
系統(tǒng)吞吐量
系統(tǒng)吞吐量 = 總共完成多少個(gè)作業(yè) / 總共花費(fèi)時(shí)間。
長(zhǎng)作業(yè)的進(jìn)程會(huì)占用較長(zhǎng)的 CPU 資源導(dǎo)致降低吞吐量,相反短作業(yè)的進(jìn)程會(huì)提升系統(tǒng)吞吐量。
-
周轉(zhuǎn)時(shí)間
周轉(zhuǎn)時(shí)間 = 作業(yè)完成時(shí)間 - 作業(yè)提交時(shí)間。
平均周轉(zhuǎn)時(shí)間 = 各作業(yè)周轉(zhuǎn)時(shí)間和 / 作業(yè)數(shù)
帶權(quán)周轉(zhuǎn)時(shí)間 = 作業(yè)周轉(zhuǎn)時(shí)間 / 作業(yè)實(shí)際運(yùn)行時(shí)間
平均帶權(quán)周轉(zhuǎn)時(shí)間 = 各作業(yè)帶權(quán)周轉(zhuǎn)時(shí)間之和 / 作業(yè)數(shù)
盡可能使周轉(zhuǎn)時(shí)間降低。
-
等待時(shí)間
指的是進(jìn)程在等待隊(duì)列中等待的時(shí)間,一般也需要盡可能短。
響應(yīng)時(shí)間
響應(yīng)時(shí)間 = 系統(tǒng)第一次響應(yīng)時(shí)間 - 用戶(hù)提交時(shí)間,在交互式系統(tǒng)中響應(yīng)時(shí)間是衡量調(diào)度算法好壞的主要標(biāo)準(zhǔn)。
3.4.2 調(diào)度算法
FCFS 算法
-
First Come First Severd 先來(lái)先服務(wù)算法,遵循先來(lái)后端原則,每次從就緒隊(duì)列拿等待時(shí)間最久的,運(yùn)行完畢后再拿下一個(gè)。
-
該模式對(duì)長(zhǎng)作業(yè)有利,適用 CPU 繁忙型作業(yè)的系統(tǒng),不適用 I/O 型作業(yè),因?yàn)闀?huì)導(dǎo)致進(jìn)程CPU利用率很低。
SJF 算法
-
Shortest Job First 最短作業(yè)優(yōu)先算法,該算法會(huì)優(yōu)先選擇運(yùn)行所需時(shí)間最短的進(jìn)程執(zhí)行,可提高吞吐量。
-
跟FCFS正好相反,對(duì)長(zhǎng)作業(yè)很不利。
SRTN 算法
-
Shortest Remaining Time Next 最短剩余時(shí)間優(yōu)先算法,可以認(rèn)為是SJF的搶占式版本,當(dāng)一個(gè)新就緒的進(jìn)程比當(dāng)前運(yùn)行進(jìn)程具有更短完成時(shí)間時(shí),系統(tǒng)搶占當(dāng)前進(jìn)程,選擇新就緒的進(jìn)程執(zhí)行。
-
有最短的平均周轉(zhuǎn)時(shí)間,但不公平,源源不斷的短任務(wù)到來(lái),可能使長(zhǎng)的任務(wù)長(zhǎng)時(shí)間得不到運(yùn)行。
HRRN 算法
-
Highest Response Ratio Next 最高響應(yīng)比優(yōu)先算法,為了平衡前面?zhèn)z而生,按照響應(yīng)優(yōu)先權(quán)從高到低依次執(zhí)行。屬于前面?zhèn)z的折中權(quán)衡。
-
優(yōu)先權(quán) = (等待時(shí)間 + 要求服務(wù)時(shí)間) / 要求服務(wù)時(shí)間
RR 算法
-
Round Robin 時(shí)間片輪轉(zhuǎn)算法,操作系統(tǒng)設(shè)定了個(gè)時(shí)間片Quantum,時(shí)間片導(dǎo)致每個(gè)進(jìn)程只有在該時(shí)間片內(nèi)才可以運(yùn)行,這種方式導(dǎo)致每個(gè)進(jìn)程都會(huì)均勻的獲得執(zhí)行權(quán)。
-
時(shí)間片一般20ms~50ms,如果太小會(huì)導(dǎo)致系統(tǒng)頻繁進(jìn)行上下文切換,太大又可能引起對(duì)短的交互請(qǐng)求的響應(yīng)變差。
HPF 算法
-
Highest Priority First 最高優(yōu)先級(jí)調(diào)度算法,從就緒隊(duì)列中選擇最高優(yōu)先級(jí)的進(jìn)程先執(zhí)行。
-
優(yōu)先級(jí)的設(shè)置有初始化固定死的那種,也有在代碼運(yùn)轉(zhuǎn)過(guò)程中根據(jù)等待時(shí)間或性能動(dòng)態(tài)調(diào)整 這兩種思路。
-
缺點(diǎn)是可能導(dǎo)致低優(yōu)先級(jí)的一直無(wú)法被執(zhí)行。
MFQ 算法
-
Multilevel Feedback Queue 多級(jí)反饋隊(duì)列調(diào)度算法 ,可以認(rèn)為是 RR 算法 跟 HPF 算法 的綜合體。
-
系統(tǒng)會(huì)同時(shí)存在多個(gè)就緒隊(duì)列,每個(gè)隊(duì)列優(yōu)先級(jí)從高到低排列,同時(shí)優(yōu)先級(jí)越高獲得是時(shí)間片越短。
-
新進(jìn)程會(huì)先加入到最高優(yōu)先級(jí)隊(duì)列,如果新進(jìn)程優(yōu)先級(jí)高于當(dāng)前在執(zhí)行的進(jìn)程,會(huì)停止當(dāng)前進(jìn)程轉(zhuǎn)而去執(zhí)行新進(jìn)程。新進(jìn)程如果在時(shí)間片內(nèi)沒(méi)執(zhí)行完畢需下移到次優(yōu)先級(jí)隊(duì)列。
多級(jí)反饋隊(duì)列調(diào)度算法
3.5 線(xiàn)程
3.5.1 線(xiàn)程定義
早期操作系統(tǒng)是沒(méi)有線(xiàn)程概念的,線(xiàn)程是后來(lái)加進(jìn)來(lái)的。為啥會(huì)有線(xiàn)程呢?那是因?yàn)橐郧霸诙噙M(jìn)程階段,經(jīng)常會(huì)涉及到進(jìn)程之間如何通訊,如何共享數(shù)據(jù)的問(wèn)題。并且進(jìn)程關(guān)聯(lián)到PCB的生命周期,管理起來(lái)開(kāi)銷(xiāo)較大。為了解決這個(gè)問(wèn)題引入了線(xiàn)程。
線(xiàn)程是進(jìn)程當(dāng)中的一個(gè)執(zhí)行流程。同一個(gè)進(jìn)程內(nèi)的多個(gè)線(xiàn)程之間可以共享進(jìn)程的代碼段、數(shù)據(jù)段、打開(kāi)的文件等資源。同時(shí)每個(gè)線(xiàn)程又都有一套獨(dú)立的寄存器和棧來(lái)確保線(xiàn)程的控制流是獨(dú)立的。
進(jìn)程有個(gè)PCB來(lái)管理,同理操作系統(tǒng)通過(guò)Thread Control Block線(xiàn)程控制塊來(lái)實(shí)現(xiàn)線(xiàn)程的管控。
3.5.2 線(xiàn)程優(yōu)缺點(diǎn)
優(yōu)點(diǎn)
-
一個(gè)進(jìn)程中可以同時(shí)存在1~N個(gè)線(xiàn)程,這些線(xiàn)程可以并發(fā)的執(zhí)行。
-
各個(gè)線(xiàn)程之間可以共享地址空間和文件等資源。
缺點(diǎn)
-
當(dāng)進(jìn)程中的一個(gè)線(xiàn)程奔潰時(shí),會(huì)導(dǎo)致其所屬進(jìn)程的所有線(xiàn)程奔潰。
-
多線(xiàn)程編程,讓人頭大的東西。
-
線(xiàn)程執(zhí)行開(kāi)銷(xiāo)小,但不利于資源的隔離管理和保護(hù),而進(jìn)程正相反。
3.5.3 進(jìn)程跟線(xiàn)程關(guān)聯(lián)
進(jìn)程:
-
是系統(tǒng)進(jìn)行資源分配和調(diào)度的一個(gè)獨(dú)立單位.
-
是程序的一次執(zhí)行,每個(gè)進(jìn)程都有自己的地址空間、內(nèi)存、數(shù)據(jù)棧及其他輔助記錄運(yùn)行軌跡的數(shù)據(jù)
線(xiàn)程:
-
是進(jìn)程的一個(gè)實(shí)體,是CPU調(diào)度和分派的基本單位,它是比進(jìn)程更小的能獨(dú)立運(yùn)行的基本單位
-
所有的線(xiàn)程運(yùn)行在同一個(gè)進(jìn)程中,共享相同的運(yùn)行資源和環(huán)境
-
線(xiàn)程一般是并發(fā)執(zhí)行的,使得實(shí)現(xiàn)了多任務(wù)的并行和數(shù)據(jù)共享。
進(jìn)程線(xiàn)程區(qū)別:
-
一個(gè)線(xiàn)程只能屬于一個(gè)進(jìn)程,而一個(gè)進(jìn)程可以有多個(gè)線(xiàn)程,但至少有一個(gè)線(xiàn)程。
-
線(xiàn)程的劃分尺度小于進(jìn)程(資源比進(jìn)程少),使得多線(xiàn)程程序的并發(fā)性高。
-
進(jìn)程在執(zhí)行過(guò)程中擁有獨(dú)立的內(nèi)存單元,而多個(gè)線(xiàn)程共享內(nèi)存,從而極大地提高了程序的運(yùn)行效率。
-
資源分配給進(jìn)程,同一進(jìn)程的所有線(xiàn)程共享該進(jìn)程的所有資源。
-
CPU分配資源給進(jìn)程,但真正在CPU上運(yùn)行的是線(xiàn)程。
-
線(xiàn)程不能夠獨(dú)立執(zhí)行,必須依存在進(jìn)程中。
線(xiàn)程快在哪兒?
-
線(xiàn)程創(chuàng)建的時(shí)有些資源不需要自己管理,直接從進(jìn)程拿即可,線(xiàn)程管理寄存器跟棧的生命周期即可。
-
同進(jìn)程內(nèi)多線(xiàn)程共享數(shù)據(jù),所以進(jìn)程數(shù)據(jù)傳輸可以用zero copy技術(shù),不需要經(jīng)過(guò)內(nèi)核了。
-
進(jìn)程使用一個(gè)虛擬內(nèi)存跟頁(yè)表,然后多線(xiàn)程共用這些虛擬內(nèi)存,如果同進(jìn)程內(nèi)兩個(gè)線(xiàn)程進(jìn)行上下文切換比進(jìn)程提速很多。
3.5.4 線(xiàn)程實(shí)現(xiàn)
在前面的內(nèi)存管理中說(shuō)到了內(nèi)核態(tài)跟用戶(hù)態(tài)。相對(duì)應(yīng)的線(xiàn)程的創(chuàng)建也分為用戶(hù)態(tài)線(xiàn)程跟內(nèi)核態(tài)線(xiàn)程。
3.5.4.1 用戶(hù)態(tài)線(xiàn)程
在用戶(hù)空間實(shí)現(xiàn)的線(xiàn)程,由用戶(hù)態(tài)的線(xiàn)程庫(kù)來(lái)完成線(xiàn)程的管理。操作系統(tǒng)按進(jìn)程維度進(jìn)行調(diào)度,當(dāng)線(xiàn)程在用戶(hù)態(tài)創(chuàng)建時(shí)應(yīng)用程序在用戶(hù)空間內(nèi)要實(shí)現(xiàn)線(xiàn)程的創(chuàng)建、維護(hù)和調(diào)度。操作系統(tǒng)對(duì)線(xiàn)程的存在一無(wú)所知!操作系統(tǒng)只能看到進(jìn)程看不到線(xiàn)程。所有的線(xiàn)程都是在用戶(hù)空間實(shí)現(xiàn)。在操作系統(tǒng)看來(lái),每一個(gè)進(jìn)程只有一個(gè)線(xiàn)程。
用戶(hù)態(tài)線(xiàn)程
好處:
-
及時(shí)操作系統(tǒng)不支持線(xiàn)程模式也可以通過(guò)用戶(hù)層庫(kù)函數(shù)來(lái)支持線(xiàn)程模式,TCB 由用戶(hù)級(jí)線(xiàn)程庫(kù)函數(shù)來(lái)維護(hù)。
-
使用庫(kù)函數(shù)模式實(shí)現(xiàn)線(xiàn)程可以避免用戶(hù)態(tài)到內(nèi)核態(tài)的切換。
壞處:
-
CPU不知道線(xiàn)程存在,CPU的時(shí)間片切換是以進(jìn)程為維度的,某個(gè)線(xiàn)程因?yàn)镮O等操作導(dǎo)致線(xiàn)程阻塞,操作系統(tǒng)會(huì)阻塞整個(gè)進(jìn)程,即使這個(gè)進(jìn)程中其它線(xiàn)程還在工作。
-
用戶(hù)態(tài)線(xiàn)程沒(méi)法打斷正在運(yùn)行中的線(xiàn)程,除非線(xiàn)程主動(dòng)交出CPU使用權(quán)。
3.5.4.2 內(nèi)核態(tài)線(xiàn)程
在內(nèi)核中實(shí)現(xiàn)的線(xiàn)程,是由內(nèi)核管理的線(xiàn)程,線(xiàn)程對(duì)應(yīng)的 TCB 在操作系統(tǒng)里,這樣線(xiàn)程的創(chuàng)建、終止和管理都是由操作系統(tǒng)負(fù)責(zé)。內(nèi)線(xiàn)程模式下一個(gè)用戶(hù)線(xiàn)程對(duì)應(yīng)一個(gè)內(nèi)核線(xiàn)程。
內(nèi)核態(tài)線(xiàn)程注意:Linux中的JVM從1.2版以后是基于pthread實(shí)現(xiàn)的,所以現(xiàn)在Java中線(xiàn)程的本質(zhì)就是操作系統(tǒng)中的線(xiàn)程。
優(yōu)點(diǎn):
-
一個(gè)進(jìn)程中某個(gè)線(xiàn)程阻塞不會(huì)影響其他內(nèi)核線(xiàn)程運(yùn)行。
-
用戶(hù)態(tài)模式一個(gè)時(shí)間片分給多個(gè)線(xiàn)程,內(nèi)核態(tài)模式直接分配給線(xiàn)程的時(shí)間片增加。
缺點(diǎn):
-
內(nèi)核級(jí)線(xiàn)程調(diào)度開(kāi)銷(xiāo)較大。調(diào)度內(nèi)核線(xiàn)程的代價(jià)可能和調(diào)度進(jìn)程差不多昂貴,代價(jià)要比用戶(hù)級(jí)線(xiàn)程大很多。一個(gè)線(xiàn)程默認(rèn)棧=1M,線(xiàn)程多了會(huì)導(dǎo)致內(nèi)存消耗很大。
-
線(xiàn)程表是存放在操作系統(tǒng)固定的表格空間或者堆棧空間里,所以?xún)?nèi)核級(jí)線(xiàn)程的數(shù)量是有限的。
3.4.4.3 輕量級(jí)進(jìn)程
最初的進(jìn)程定義都包含程序、資源及其執(zhí)行三部分,其中程序通常指代碼,資源在操作系統(tǒng)層面上通常包括內(nèi)存資源、IO資源、信號(hào)處理等部分,而程序的執(zhí)行通常理解為執(zhí)行上下文,包括對(duì)CPU的占用,后來(lái)發(fā)展為線(xiàn)程。在線(xiàn)程概念出現(xiàn)以前,為了減小進(jìn)程切換的開(kāi)銷(xiāo),操作系統(tǒng)設(shè)計(jì)者逐漸修正進(jìn)程的概念,逐漸允許將進(jìn)程所占有的資源從其主體剝離出來(lái),允許某些進(jìn)程共享一部分資源,例如文件、信號(hào),數(shù)據(jù)內(nèi)存,甚至代碼,這就發(fā)展出輕量進(jìn)程的概念。
Light-weight process 輕量級(jí)進(jìn)程是內(nèi)核支持的用戶(hù)線(xiàn)程,它是基于內(nèi)核線(xiàn)程的高級(jí)抽象,系統(tǒng)只有先支持內(nèi)核線(xiàn)程才能有 LWP。一個(gè)進(jìn)程可有1~N個(gè)LWP,每個(gè) LWP 是跟內(nèi)核線(xiàn)程一對(duì)一映射的,也就是 LWP 都是由一個(gè)內(nèi)核線(xiàn)程支持。
LWP模式
輕量級(jí)進(jìn)程本質(zhì)還是進(jìn)程,只是跟普通進(jìn)程相比LWP跟其他進(jìn)程共享大部分邏輯地址空間跟系統(tǒng)資源,LWP輕量體現(xiàn)在它只有一個(gè)最小的執(zhí)行上下文和調(diào)度程序所需的統(tǒng)計(jì)信息。他是進(jìn)程的執(zhí)行部分,只帶有執(zhí)行相關(guān)的信息。
Linux特性:
-
Linux中沒(méi)有真正的線(xiàn)程,因?yàn)長(zhǎng)inux并沒(méi)有為線(xiàn)程準(zhǔn)備特定的數(shù)據(jù)結(jié)構(gòu)。在內(nèi)核看來(lái)只有進(jìn)程而沒(méi)有線(xiàn)程,在調(diào)度時(shí)也是當(dāng)做進(jìn)程來(lái)調(diào)度。Linux所謂的線(xiàn)程其實(shí)是與其他進(jìn)程共享資源的進(jìn)程。但windows中確實(shí)有線(xiàn)程。
-
Linux中沒(méi)有的線(xiàn)程,線(xiàn)程是由進(jìn)程來(lái)模擬實(shí)現(xiàn)的。
-
所以在Linux中在CPU角度看,進(jìn)程被稱(chēng)作輕量級(jí)進(jìn)程LWP。
3.5.5 協(xié)程
3.5.5.1 協(xié)程定義
大多數(shù)web服務(wù)跟互聯(lián)網(wǎng)服務(wù)本質(zhì)上大部分都是 IO 密集型服務(wù),IO 密集型服務(wù)的瓶頸不在CPU處理速度,而在于盡可能快速的完成高并發(fā)、多連接下的數(shù)據(jù)讀寫(xiě)。以前有兩種解決方案:
-
多進(jìn)程:存在頻繁調(diào)度切換問(wèn)題,同時(shí)還會(huì)存在每個(gè)進(jìn)程資源不共享的問(wèn)題,需要額外引入進(jìn)程間通信機(jī)制來(lái)解決。
-
多線(xiàn)程:高并發(fā)場(chǎng)景的大量 IO 等待會(huì)導(dǎo)致多線(xiàn)程被頻繁掛起和切換,非常消耗系統(tǒng)資源,同時(shí)多線(xiàn)程訪(fǎng)問(wèn)共享資源存在競(jìng)爭(zhēng)問(wèn)題。
此時(shí)協(xié)程出現(xiàn)了,協(xié)程 Coroutines 是一種比線(xiàn)程更加輕量級(jí)的微線(xiàn)程。類(lèi)比一個(gè)進(jìn)程可以擁有多個(gè)線(xiàn)程,一個(gè)線(xiàn)程也可以擁有多個(gè)協(xié)程??梢院?jiǎn)單的把協(xié)程理解成子程序調(diào)用,每個(gè)子程序都可以在一個(gè)單獨(dú)的協(xié)程內(nèi)執(zhí)行。
協(xié)程協(xié)程運(yùn)行在線(xiàn)程之上,當(dāng)一個(gè)協(xié)程執(zhí)行完成后,可以選擇主動(dòng)讓出,讓另一個(gè)協(xié)程運(yùn)行在當(dāng)前線(xiàn)程之上。協(xié)程并沒(méi)有增加線(xiàn)程數(shù)量,只是在線(xiàn)程的基礎(chǔ)之上通過(guò)分時(shí)復(fù)用的方式運(yùn)行多個(gè)協(xié)程,而且協(xié)程的切換在用戶(hù)態(tài)完成,切換的代價(jià)比線(xiàn)程從用戶(hù)態(tài)到內(nèi)核態(tài)的代價(jià)小很多,一般在Python、Go中會(huì)涉及到協(xié)程的知識(shí),尤其是現(xiàn)在高性能的腳本Go。
3.5.5.2 協(xié)程注意事項(xiàng)
協(xié)程運(yùn)行在線(xiàn)程之上,并且協(xié)程調(diào)用了一個(gè)阻塞IO操作,此時(shí)操作系統(tǒng)并不知道協(xié)程的存在,它只知道線(xiàn)程,因此在協(xié)程調(diào)用阻塞IO操作時(shí),操作系統(tǒng)會(huì)讓線(xiàn)程進(jìn)入阻塞狀態(tài),當(dāng)前的協(xié)程和其它綁定在該線(xiàn)程之上的協(xié)程都會(huì)陷入阻塞而得不到調(diào)度。
因此在協(xié)程中不能調(diào)用導(dǎo)致線(xiàn)程阻塞的操作,比如打印、讀取文件、Socket接口等。協(xié)程只有和異步IO結(jié)合起來(lái)才能發(fā)揮最大的威力。并且協(xié)程只有在IO密集型的任務(wù)中才會(huì)發(fā)揮作用。
3.6 進(jìn)程通信
進(jìn)程的用戶(hù)地址空間是相互獨(dú)立的,不可以互相訪(fǎng)問(wèn),但內(nèi)核空間是進(jìn)程都共享的,所以進(jìn)程之間要通信必須通過(guò)內(nèi)核。進(jìn)程間通信主要通過(guò)管道、消息隊(duì)列、共享內(nèi)存、信號(hào)量、信號(hào)、Socket編程。
3.6.1 管道
管道主要分為匿名管道跟命名管道兩種,可以實(shí)現(xiàn)數(shù)據(jù)的單向流動(dòng)性。使用起來(lái)很簡(jiǎn)單,但是管道這種通信方式效率低,不適合進(jìn)程間頻繁地交換數(shù)據(jù)。
匿名管道:
-
日常Linux系統(tǒng)中的|就是匿名管道。指令的前一個(gè)輸入是后一個(gè)指令的輸出。
命名管道:
-
一般通過(guò)mkfifo SoWhatPipe創(chuàng)建管道。通過(guò)echo "sw" > SoWhatPipe跟cat < SoWhatPipe實(shí)現(xiàn)輸入跟輸出。
匿名管道的實(shí)現(xiàn)依賴(lài)int pipe(int fd[2])函數(shù),其中fd[0]是讀取斷描述符,fd[1]是管道寫(xiě)入端描述符。它的本質(zhì)就是在內(nèi)核中創(chuàng)建個(gè)屬于內(nèi)存的緩存,從一端輸入無(wú)格式數(shù)據(jù)一端輸出無(wú)格式數(shù)據(jù),需注意管道傳輸大小是有限的。
管道通信底層匿名管道的通信范圍是存在父子關(guān)系的進(jìn)程。由于管道沒(méi)有實(shí)體,也就是沒(méi)有管道文件,不會(huì)涉及到文件系統(tǒng)。只能通過(guò)fork子進(jìn)程來(lái)復(fù)制父進(jìn)程 fd 文件描述符,父子進(jìn)程通過(guò)共用特殊的管道文件實(shí)現(xiàn)跨進(jìn)程通信,并且因?yàn)楣艿乐荒芤欢藢?xiě)入,另一端讀出,所以通常父子進(jìn)程遵從如下要求:
-
父進(jìn)程關(guān)閉讀取的 fd[0],只保留寫(xiě)入的 fd[1]。
-
子進(jìn)程關(guān)閉寫(xiě)入的 fd[1],只保留讀取的 fd[0]。
shell管道通信需注意Shell執(zhí)行匿名管道 a | b其實(shí)是通過(guò)Shell父進(jìn)程fork出了兩個(gè)子進(jìn)程來(lái)實(shí)現(xiàn)通信的,而ab之間是不存在父子進(jìn)程關(guān)系的。而命名管道是可以直接在不想關(guān)進(jìn)程間通信的,因?yàn)橛泄艿牢募?
3.6.2 消息隊(duì)列
消息隊(duì)列消息隊(duì)列是保存在內(nèi)核中的消息鏈表,會(huì)涉及到用戶(hù)態(tài)跟內(nèi)核態(tài)到來(lái)回切換,雙方約定好消息體到數(shù)據(jù)結(jié)構(gòu),然后發(fā)送數(shù)據(jù)時(shí)將數(shù)據(jù)分成一個(gè)個(gè)獨(dú)立的數(shù)據(jù)單元消息體,需注意消息隊(duì)列及單個(gè)消息都有上限,日常我們到RabbitMQ、Redis 都涉及到消息隊(duì)列。
3.6.3 共享內(nèi)存
共享空間現(xiàn)代操作系統(tǒng)對(duì)內(nèi)存管理采用的是虛擬內(nèi)存技術(shù),也就是每個(gè)進(jìn)程都有自己獨(dú)立的虛擬內(nèi)存空間,不同進(jìn)程的虛擬內(nèi)存映射到不同的物理內(nèi)存中。所以,即使進(jìn)程A和進(jìn)程B虛擬地址是一樣的,真正訪(fǎng)問(wèn)的也是不同的物理內(nèi)存地址,該模式不涉及到用戶(hù)態(tài)跟內(nèi)核態(tài)來(lái)回切換,JVM 就是用的共享內(nèi)存模式。并且并發(fā)編程也是個(gè)難點(diǎn)。
3.6.4 信號(hào)量
既然共享內(nèi)存容易造成數(shù)據(jù)紊亂,那為了簡(jiǎn)單的實(shí)現(xiàn)共享數(shù)據(jù)在任意時(shí)刻只能被一個(gè)進(jìn)程訪(fǎng)問(wèn),此時(shí)需要信號(hào)量。
信號(hào)量其實(shí)是一個(gè)整型的計(jì)數(shù)器,主要用于實(shí)現(xiàn)進(jìn)程間的互斥與同步,而不是用于緩存進(jìn)程間通信的數(shù)據(jù)。
信號(hào)量表示資源的數(shù)量,核心點(diǎn)在于原子性的控制一個(gè)數(shù)據(jù)的值,控制信號(hào)量的方式有PV兩種原子操作:
-
P 操作會(huì)把信號(hào)量減去 -1,相減后如果信號(hào)量 < 0,則表明資源已被占用,進(jìn)程需阻塞等待。相減后如果信號(hào)量 >= 0,則表明還有資源可使用,進(jìn)程可正常繼續(xù)執(zhí)行。
-
V 操作會(huì)把信號(hào)量加上 1,相加后如果信號(hào)量 <= 0,則表明當(dāng)前有阻塞中的進(jìn)程,于是會(huì)將該進(jìn)程喚醒運(yùn)行。相加后如果信號(hào)量 > 0,則表明當(dāng)前沒(méi)有阻塞中的進(jìn)程。
3.6.5 信號(hào)
對(duì)于異常狀態(tài)下進(jìn)程工作模式需要用到信號(hào)工作方式來(lái)通知進(jìn)程。比如Linux系統(tǒng)為了響應(yīng)各種事件提供了很多異常信號(hào)kill -l,信號(hào)是進(jìn)程間通信機(jī)制中唯一的異步通信機(jī)制,可以在任何時(shí)候發(fā)送信號(hào)給某一進(jìn)程。比如:
-
kill -9 1412 ,表示給 PID 為 1412 的進(jìn)程發(fā)送 SIGKILL 信號(hào),用來(lái)立即結(jié)束該進(jìn)程。
-
鍵盤(pán) Ctrl+C 產(chǎn)生 SIGINT 信號(hào),表示終止該進(jìn)程。
-
鍵盤(pán) Ctrl+Z 產(chǎn)生 SIGTSTP 信號(hào),表示停止該進(jìn)程,但還未結(jié)束。
有信號(hào)發(fā)生時(shí),進(jìn)程一般有三種方式響應(yīng)信號(hào):
-
執(zhí)行默認(rèn)操作:Linux操作系統(tǒng)為眾多信號(hào)配備了專(zhuān)門(mén)的處理操作。
-
捕捉信號(hào):給捕捉到的信號(hào)配備專(zhuān)門(mén)的信號(hào)處理函數(shù)。
-
忽略信號(hào):專(zhuān)門(mén)用來(lái)忽略某些信號(hào),但 SIGKILL 和 SEGSTOP是無(wú)法被忽略的,為了能在任何時(shí)候結(jié)束或停止某個(gè)進(jìn)程而存在。
3.6.6 Socket編程
前面提到的管道、消息隊(duì)列、共享內(nèi)存、信號(hào)量和信號(hào)都是在同一臺(tái)主機(jī)上進(jìn)行進(jìn)程間通信,那要想跨網(wǎng)絡(luò)與不同主機(jī)上的進(jìn)程之間通信,就需要 Socket 通信。
int socket(int domain, int type, int protocal)
上面是socket編程的核心函數(shù),可以指定IPV4或IPV6類(lèi)型,TCP或UDP類(lèi)型。比如TCP協(xié)議通信的 socket 編程模型如下:
Socket編程
-
服務(wù)端和客戶(hù)端初始化socket,得到文件描述符。
-
服務(wù)端調(diào)用bind,將綁定在 IP 地址和端口。
-
服務(wù)端調(diào)用listen,進(jìn)行監(jiān)聽(tīng)。
-
服務(wù)端調(diào)用accept,等待客戶(hù)端連接。
-
客戶(hù)端調(diào)用connect,向服務(wù)器端的地址和端口發(fā)起連接請(qǐng)求。
-
服務(wù)端accept返回用于傳輸?shù)膕ocket的文件描述符。
-
客戶(hù)端調(diào)用write寫(xiě)入數(shù)據(jù),服務(wù)端調(diào)用read讀取數(shù)據(jù)。
-
客戶(hù)端斷開(kāi)連接時(shí),會(huì)調(diào)用close,那么服務(wù)端read讀取數(shù)據(jù)的時(shí)候,就會(huì)讀取到了EOF,待處理完數(shù)據(jù)后,服務(wù)端調(diào)用 close,表示連接關(guān)閉。
-
服務(wù)端調(diào)用accept時(shí),連接成功會(huì)返回一個(gè)已完成連接的socket,后續(xù)用來(lái)傳輸數(shù)據(jù)。服務(wù)端有倆socket,一個(gè)叫作監(jiān)聽(tīng)socket,一個(gè)叫作已完成連接socket。
-
成功連接建立之后雙方開(kāi)始通過(guò) read 和 write 函數(shù)來(lái)讀寫(xiě)數(shù)據(jù)。
UDP傳輸UDP比較簡(jiǎn)單,屬于類(lèi)似廣播性質(zhì)的傳輸,不需要維護(hù)連接。但也需要 bind,每次通信時(shí)調(diào)用 sendto 和 recvfrom 都要傳入目標(biāo)主機(jī)的 IP 地址和端口。
3.7 多線(xiàn)程編程
既然多進(jìn)程開(kāi)銷(xiāo)過(guò)大,那平常我們經(jīng)常使用到的就是多線(xiàn)程編程了。期間可能涉及到內(nèi)存模型、JMM、Volatile、臨界區(qū)等等。這些在Java并發(fā)編程專(zhuān)欄有講。
4 文件管理
4.1 VFS 虛擬文件系統(tǒng)
文件系統(tǒng)在操作系統(tǒng)中主要負(fù)責(zé)將文件數(shù)據(jù)信息存儲(chǔ)到磁盤(pán)中,起到持久化文件的作用。文件系統(tǒng)的基本組成單元就是文件,文件組成方式不同就會(huì)形成不同的文件系統(tǒng)。
文件系統(tǒng)有很多種而不同的文件系統(tǒng)應(yīng)用到操作系統(tǒng)后需要提供統(tǒng)一的對(duì)外接口,此時(shí)用到了一個(gè)設(shè)計(jì)理念沒(méi)有什么是加一層解決不了的,在用戶(hù)層跟不同的文件系統(tǒng)之間加入一個(gè)虛擬文件系統(tǒng)層Virtual File System。
虛擬文件系統(tǒng)層定義了一組所有文件系統(tǒng)都支持的數(shù)據(jù)結(jié)構(gòu)和標(biāo)準(zhǔn)接口,這樣程序員不需要了解文件系統(tǒng)的工作原理,只需要了解 VFS 提供的統(tǒng)一接口即可。
虛擬文件系統(tǒng)日常的文件系統(tǒng)一般有如下三種:
-
磁盤(pán)文件系統(tǒng):就是我們常見(jiàn)的EXT 2/3/4系列。
-
內(nèi)存文件系統(tǒng):數(shù)據(jù)沒(méi)存儲(chǔ)到磁盤(pán),占用內(nèi)存數(shù)據(jù),比如/sys、/proc。進(jìn)程中的一些數(shù)據(jù)映射到/proc中了。
-
網(wǎng)絡(luò)文件系統(tǒng):常見(jiàn)的網(wǎng)盤(pán)掛載NFS等,通過(guò)訪(fǎng)問(wèn)其他主機(jī)數(shù)據(jù)實(shí)現(xiàn)。
4.2 文件組成
以L(fǎng)inux系統(tǒng)為例,在Linux系統(tǒng)中一切皆文件,Linux文件系統(tǒng)會(huì)為每個(gè)文件分配索引節(jié)點(diǎn) inode跟目錄項(xiàng)directory entry來(lái)記錄文件內(nèi)容跟目錄層次結(jié)構(gòu)。
4.2.1 inode
要理解inode要從文件儲(chǔ)存說(shuō)起。文件存儲(chǔ)在硬盤(pán)上,硬盤(pán)的最小存儲(chǔ)單位叫做扇區(qū)。每個(gè)扇區(qū)儲(chǔ)存512字節(jié)。操作系統(tǒng)讀取硬盤(pán)的時(shí)候,不會(huì)一個(gè)個(gè)扇區(qū)的讀取,這樣效率太低,一般一次性連續(xù)讀取8個(gè)扇區(qū)(4KB)來(lái)當(dāng)做一塊,這種由多個(gè)扇區(qū)組成的塊,是文件存取的最小單位。
文件數(shù)據(jù)都儲(chǔ)存在塊中,我們還必須找到一個(gè)地方儲(chǔ)存文件的元信息,比如inode編號(hào)、文件大小、創(chuàng)建時(shí)間、修改時(shí)間、磁盤(pán)位置、訪(fǎng)問(wèn)權(quán)限等。幾乎除了文件名以為的所有文件元數(shù)據(jù)信息都存儲(chǔ)在一個(gè)叫叫索引節(jié)點(diǎn)inode的地方??赏ㄟ^(guò)stat 文件名查看 inode 信息
每個(gè)inode都有一個(gè)號(hào)碼,操作系統(tǒng)用inode號(hào)碼來(lái)識(shí)別不同的文件。Unix/Linux系統(tǒng)內(nèi)部不使用文件名,而使用inode號(hào)碼來(lái)識(shí)別文件,用戶(hù)可通過(guò)ls -i查看每個(gè)文件對(duì)應(yīng)編號(hào)。對(duì)于系統(tǒng)來(lái)說(shuō)文件名只是inode號(hào)碼便于識(shí)別的別稱(chēng)或者綽號(hào)。特殊名字的文件不好刪除時(shí)可以嘗試用inode號(hào)刪除,移動(dòng)跟重命名不會(huì)導(dǎo)致文件inode變化,當(dāng)用戶(hù)嘗試根據(jù)文件名打開(kāi)文件時(shí),實(shí)際上系統(tǒng)內(nèi)部將這個(gè)過(guò)程分成三步:
-
系統(tǒng)找到這個(gè)文件名對(duì)應(yīng)的inode號(hào)碼。
-
通過(guò)inode號(hào)碼,獲取inode信息,進(jìn)行權(quán)限驗(yàn)證等操作。
-
根據(jù)inode信息,找到文件數(shù)據(jù)所在的block,讀出數(shù)據(jù)。
需注意 inode也會(huì)消耗硬盤(pán)空間,硬盤(pán)格式化后會(huì)被分成超級(jí)塊、索引節(jié)點(diǎn)區(qū)和數(shù)據(jù)塊區(qū)三個(gè)區(qū)域:
-
超級(jí)塊區(qū):用來(lái)存儲(chǔ)文件系統(tǒng)的詳細(xì)信息,比如塊大小,塊個(gè)數(shù)等信息。一般文件系統(tǒng)掛載后就會(huì)將數(shù)據(jù)信息同步到內(nèi)存。
-
索引節(jié)點(diǎn)區(qū):用來(lái)存儲(chǔ)索引節(jié)點(diǎn) inode table。每個(gè)inode一般為128字節(jié)或256字節(jié),一般每1KB或2KB數(shù)據(jù)就需設(shè)置一個(gè)inode。一般為了加速查詢(xún)會(huì)把索引數(shù)據(jù)緩存到內(nèi)存。
-
數(shù)據(jù)塊區(qū):真正存儲(chǔ)磁盤(pán)數(shù)據(jù)的地方。
df -i # 查看每個(gè)硬盤(pán)分區(qū)的inode總數(shù)和已經(jīng)使用的數(shù)量 sudo dumpe2fs -h /dev/hda | grep "Inode size" # 查看每個(gè)inode節(jié)點(diǎn)的大小
4.2.2 目錄
Unix/Linux系統(tǒng)中目錄directory也是一種文件,打開(kāi)目錄實(shí)際上就是打開(kāi)目錄文件。目錄文件內(nèi)容就是一系列目錄項(xiàng)的列,目錄項(xiàng)的內(nèi)容包含文件的名字、文件類(lèi)型、索引節(jié)點(diǎn)指針以及與其他目錄項(xiàng)的層級(jí)關(guān)系。
為避免頻繁讀取磁盤(pán)里的目錄文件,內(nèi)核會(huì)把已經(jīng)讀過(guò)的目錄文件用目錄項(xiàng)這個(gè)數(shù)據(jù)結(jié)構(gòu)緩存在內(nèi)存,方便用戶(hù)下次讀取目錄信息,目錄項(xiàng)可包含目錄或文件,不要驚訝于可以保存目錄,目錄格式的目錄項(xiàng)里面保存的是目錄里面一項(xiàng)一項(xiàng)的文件信息。
4.2.3 軟連接跟硬鏈接
軟連接跟硬鏈接硬鏈接:老文件A被創(chuàng)建若干個(gè)硬鏈接B、C后。A、B、C三個(gè)文件的inode是相同的,所以不能跨文件系統(tǒng)。同時(shí)只有ABC全部刪除,系統(tǒng)才會(huì)刪除源文件。
軟鏈接:相當(dāng)于基于老文件A新建了個(gè)文件B,該文件B有新的inode,不過(guò)文件B內(nèi)容是老文件A的路徑。所以軟鏈接可以跨文件系統(tǒng)。當(dāng)老文件A刪除后,文件B仍然存在,不過(guò)找不到指定文件了。
[sowhat@localhost ~]$ ln [選項(xiàng)] 源文件 目標(biāo)文件
選項(xiàng):
-s:建立軟鏈接文件。如果不加 "-s" 選項(xiàng),則建立硬鏈接文件;
-f:強(qiáng)制。如果目標(biāo)文件已經(jīng)存在,則刪除目標(biāo)文件后再建立鏈接文件;
4.3 文件存儲(chǔ)
說(shuō)文件存儲(chǔ)前需了解文件系統(tǒng)操作基本單位是數(shù)據(jù)塊,而平常用戶(hù)操作字節(jié)到數(shù)據(jù)塊之間是需要轉(zhuǎn)換的,當(dāng)然這些文件系統(tǒng)都幫我們對(duì)接好了。接下來(lái)看文件系統(tǒng)是如何按照數(shù)據(jù)塊, 文件在磁盤(pán)中存儲(chǔ)時(shí)候主要分為連續(xù)空間存儲(chǔ)跟非連續(xù)空間存儲(chǔ)
4.3.1 連續(xù)空間存儲(chǔ)
-
實(shí)現(xiàn):連續(xù)空間存儲(chǔ)的意思就跟數(shù)組存儲(chǔ)一樣,找個(gè)連續(xù)的空間一次性把數(shù)據(jù)存儲(chǔ)進(jìn)去,文件頭存儲(chǔ)起始位置跟數(shù)據(jù)長(zhǎng)度即可。
-
優(yōu)勢(shì):讀寫(xiě)效率高,磁盤(pán)尋址一次即可。
-
劣勢(shì):容易產(chǎn)生空間碎片,并且文件擴(kuò)容不方便。
連續(xù)存儲(chǔ)
4.3.2 非連續(xù)空間存儲(chǔ)之鏈表
隱式鏈表
-
實(shí)現(xiàn):文件頭包含StartBlock、EndBlock。每個(gè)BLock有隱藏的next指針,跟單向鏈表一樣。
-
缺點(diǎn):只能通過(guò)鏈?zhǔn)讲粩嗤虏檎覕?shù)據(jù),不支持快速直接訪(fǎng)問(wèn)。
隱式鏈表
顯式鏈表
-
實(shí)現(xiàn):把每個(gè)Block中的next指針存儲(chǔ)到內(nèi)存文件分配表中,通過(guò)遍歷數(shù)組方式實(shí)現(xiàn)拿到全部數(shù)據(jù)。
-
缺點(diǎn):前面說(shuō)1KB就有個(gè)inode指針,如果磁盤(pán)數(shù)據(jù)很大那就需要很大的文件分配表來(lái)存儲(chǔ)映射關(guān)系了,
顯示鏈表
4.3.3 非連續(xù)空間存儲(chǔ)之索引
-
實(shí)現(xiàn):整個(gè)文件類(lèi)型一本新華字典,真實(shí)的數(shù)據(jù)塊在詞典實(shí)際位置存儲(chǔ)著,但文件所需數(shù)據(jù)塊的索引位置會(huì)被匯總起來(lái)形成目錄索引放在字典前頭。
-
優(yōu)勢(shì):不會(huì)產(chǎn)生碎片,文件可動(dòng)態(tài)擴(kuò)容,并且支持順序跟隨機(jī)讀寫(xiě)。
-
劣勢(shì):可能一個(gè)小文件都要占用一個(gè)目錄索引,文件過(guò)大導(dǎo)致索引指針一個(gè)容不下,可能還需要有多級(jí)索引或索引+鏈表模式。
索引存儲(chǔ)這些存儲(chǔ)方式各有利弊,所以操作系統(tǒng)才存儲(chǔ)的時(shí)候一般是根據(jù)文件的大小進(jìn)行動(dòng)態(tài)的變化存儲(chǔ)方式的,跟STL中的快排底層 = 快排 + 插入排序 + 堆排 一樣的道理。
4.3.4 空閑空間管理
為了避免用戶(hù)存儲(chǔ)數(shù)據(jù)時(shí)候遍歷全部磁盤(pán)空間來(lái)尋找可以數(shù)據(jù)塊,一般有如下幾種記錄方法。
-
空閑表:動(dòng)態(tài)的維護(hù)一個(gè)空閑數(shù)據(jù)塊列表,每行存儲(chǔ)空閑塊的開(kāi)始位置跟空閑長(zhǎng)度。適合少量有少量空閑數(shù)據(jù)塊時(shí)。
空閑表
-
空閑鏈表:將空閑的數(shù)據(jù)庫(kù)用next指針串聯(lián)起來(lái),缺點(diǎn)是不能隨機(jī)訪(fǎng)問(wèn)。
空閑鏈表
-
位圖法:利用Bit的 01 表示數(shù)據(jù)塊可用跟不可用,簡(jiǎn)單方便,inode跟空閑數(shù)據(jù)庫(kù)都用的此方法。
位圖法
5 輸入輸出管理
5.1 設(shè)備控制器跟驅(qū)動(dòng)程序
5.1.1 設(shè)備控制器
設(shè)備控制器操作系統(tǒng)為統(tǒng)一管理眾多的設(shè)備并且屏蔽設(shè)備之間的差異,給每個(gè)設(shè)備都安裝了個(gè)小CPU叫設(shè)備控制器。每個(gè)設(shè)備控制器都知道自己對(duì)應(yīng)外設(shè)的功能跟用法,并且每個(gè)設(shè)備控制器都有獨(dú)有的寄存器用來(lái)跟CPU通信。
-
讀設(shè)備寄存器值了解設(shè)備狀態(tài),是否可以接收新指令。
-
操作系統(tǒng)給設(shè)備寄存器寫(xiě)入一些指令可以實(shí)現(xiàn)發(fā)送數(shù)據(jù)、接收數(shù)據(jù)等等操作。
控制器一般分為數(shù)據(jù)寄存器、命令寄存器跟狀態(tài)寄存器,CPU 通過(guò)讀、寫(xiě)設(shè)備控制器中的寄存器來(lái)便捷的控制設(shè)備:
-
數(shù)據(jù)寄存器:CPU 向 I/O 設(shè)備寫(xiě)入需要傳輸?shù)臄?shù)據(jù),比如打印what,CPU 就要先發(fā)送一個(gè)w字符給到對(duì)應(yīng)的 I/O 設(shè)備。
-
命令寄存器:CPU 發(fā)送命令來(lái)告訴 I/O 設(shè)備要進(jìn)行輸入/輸出操作,于是就會(huì)交給 I/O 設(shè)備去工作,任務(wù)完成后,會(huì)把狀態(tài)寄存器里面的狀態(tài)標(biāo)記為完成。
-
狀態(tài)寄存器:用來(lái)告訴 CPU 現(xiàn)在已經(jīng)在工作或工作已經(jīng)完成,只有狀態(tài)寄存標(biāo)記成已完成,CPU 才能發(fā)送下一個(gè)字符和命令。
同時(shí)輸入輸出設(shè)備可分為塊設(shè)備跟字符設(shè)備。
-
塊設(shè)備:用來(lái)把數(shù)據(jù)存儲(chǔ)在固定大小的塊中,每個(gè)塊有自己的地址,硬盤(pán)、U盤(pán)等是常見(jiàn)的塊設(shè)備。塊設(shè)備一般數(shù)據(jù)傳輸較大為避免頻繁IO,控制器中有個(gè)可讀寫(xiě)等數(shù)據(jù)緩沖區(qū)。Linux操作系統(tǒng)為屏蔽不同塊設(shè)備帶來(lái)的差異引入了通用塊層,通用塊層是處于文件系統(tǒng)和磁盤(pán)驅(qū)動(dòng)中間的一個(gè)塊設(shè)備抽象層,主要提供如下倆功能:
向上為文件系統(tǒng)和應(yīng)用程序,提供訪(fǎng)問(wèn)塊設(shè)備的標(biāo)準(zhǔn)接口,向下把各種不同的磁盤(pán)設(shè)備抽象為統(tǒng)一的塊設(shè)備,并在內(nèi)核層面提供一個(gè)框架來(lái)管理這些設(shè)備的驅(qū)動(dòng)程序。
通用層還會(huì)給文件系統(tǒng)和應(yīng)用程序發(fā)來(lái)的 I/O進(jìn)行調(diào)度,主要目的是為了提高磁盤(pán)讀寫(xiě)的效率。
-
字符設(shè)備:以字符為單位發(fā)送或接收一個(gè)字符流,字符設(shè)備是不可尋址的,也沒(méi)有任何尋道操作,鼠標(biāo)是常見(jiàn)的字符設(shè)備。
CPU一般通過(guò)IO端口跟內(nèi)存映射IO來(lái)跟設(shè)備的控制寄存器和數(shù)據(jù)緩沖區(qū)進(jìn)行通信
-
IO端口:每個(gè)控制寄存器被分配一個(gè) I/O 端口,可以通過(guò)特殊的匯編指令操作這些寄存器,比如 in/out 類(lèi)似的指令。
-
內(nèi)存映射IO:將所有控制寄存器映射到內(nèi)存空間中,這樣就可以像讀寫(xiě)內(nèi)存一樣讀寫(xiě)數(shù)據(jù)緩沖區(qū)。
5.1.2 驅(qū)動(dòng)接口
驅(qū)動(dòng)程序
設(shè)備控制器屏蔽了設(shè)備細(xì)節(jié),但每種設(shè)備的控制器的寄存器、緩沖區(qū)等使用模式都是不同的,它屬于硬件。在操作系統(tǒng)圖范疇內(nèi)為了屏蔽設(shè)備控制器的差異,引入了設(shè)備驅(qū)動(dòng)程序,不同設(shè)備到驅(qū)動(dòng)程序會(huì)提供統(tǒng)一接口給操作系統(tǒng)來(lái)調(diào)用,這樣操作系統(tǒng)內(nèi)核會(huì)像調(diào)用本地代碼一樣使用設(shè)備驅(qū)動(dòng)程序接口。
設(shè)備發(fā)出IO請(qǐng)求就是在設(shè)備驅(qū)動(dòng)程序中來(lái)響應(yīng)到,它會(huì)根據(jù)中斷類(lèi)型調(diào)用響應(yīng)到中斷處理程序進(jìn)行處理。
中斷請(qǐng)求流程
5.2 IO 控制
CPU發(fā)送指令讓那個(gè)設(shè)備控制器去讀寫(xiě)數(shù)據(jù),完畢后如何通知CPU呢?
5.2.1 輪詢(xún)模式
控制器中有個(gè)狀態(tài)寄存器,CPU不斷輪詢(xún)查看寄存器狀態(tài),該模式會(huì)傻瓜式的一直占用CPU。
輪詢(xún)模式
5.2.2 IO 中斷請(qǐng)求
中斷模式控制器有個(gè)中斷控制器,當(dāng)設(shè)備完成任務(wù)后觸發(fā)中斷到中斷控制器,中斷控制器就通知 CPU來(lái)處理中斷請(qǐng)求。中斷有兩種,一種是軟中斷,比如代碼調(diào)用 INT 指令觸發(fā)。一種是硬件中斷,硬件通過(guò)中斷控制器觸發(fā)的。但中斷方式對(duì)于頻繁讀寫(xiě)磁盤(pán)數(shù)據(jù)的操作就不太友好了,會(huì)頻繁打斷CPU。
這里說(shuō)下磁盤(pán)高速緩存 PageCache,它是用來(lái)緩存最近被CPU訪(fǎng)問(wèn)的數(shù)據(jù)到內(nèi)存中,并且還具有預(yù)讀功能,可能你讀前16KB數(shù)據(jù),已經(jīng)把后16KB數(shù)據(jù)給你緩存好了。
pagecache : 頁(yè)緩存,當(dāng)進(jìn)程需讀取磁盤(pán)文件時(shí),linux先分配一些內(nèi)存,將數(shù)據(jù)從磁盤(pán)讀區(qū)到內(nèi)存中,然后再將數(shù)據(jù)傳給進(jìn)程。當(dāng)進(jìn)程需寫(xiě)數(shù)據(jù)到磁盤(pán)時(shí),linux先分配內(nèi)存接收用戶(hù)數(shù)據(jù),然后再將數(shù)據(jù)從內(nèi)存寫(xiě)到磁盤(pán)。同時(shí)pagecache由于大小受限,所以一般只緩存最近被訪(fǎng)問(wèn)的數(shù)據(jù),數(shù)據(jù)不足時(shí)還需訪(fǎng)問(wèn)磁盤(pán)。
5.2.3 DMA 模式
Direct Memory Access直接內(nèi)存訪(fǎng)問(wèn),在硬件DMA控制器的支持下,在進(jìn)行 I/O 設(shè)備和內(nèi)存的數(shù)據(jù)傳輸?shù)臅r(shí)候,數(shù)據(jù)搬運(yùn)的工作全部交給 DMA 控制器,而 CPU 不再參與任何與數(shù)據(jù)搬運(yùn)相關(guān)的事情,讓CPU 去處理別的事。
DMA模式可以發(fā)現(xiàn)整個(gè)數(shù)據(jù)傳輸過(guò)程中CPU是不會(huì)直接參與數(shù)據(jù)搬運(yùn)工作,由DMA來(lái)直接負(fù)責(zé)數(shù)據(jù)讀取工作,現(xiàn)如今每個(gè)IO設(shè)備一般都自帶DMA控制器。讀數(shù)據(jù)時(shí)候僅僅在傳送開(kāi)始跟結(jié)束時(shí)需要CPU干預(yù)。
5.2.4 Zero Copy
Zero Copy 全程不會(huì)通過(guò) CPU 來(lái)搬運(yùn)數(shù)據(jù),所有的數(shù)據(jù)都是通過(guò) DMA 來(lái)進(jìn)行傳輸?shù)?,中間只需要經(jīng)過(guò)2次上下文切換跟2次DMA數(shù)據(jù)拷貝,相比最原始讀寫(xiě)方式至少速度翻倍。其實(shí)在Kafka中已經(jīng)講過(guò)Zero Copy了。
5.2.4.1 老版本讀寫(xiě)
老版本的簡(jiǎn)單讀寫(xiě)操作中間不對(duì)數(shù)據(jù)做任何操作。期間會(huì)發(fā)生4次用戶(hù)態(tài)跟內(nèi)核態(tài)的切換。2次DMA數(shù)據(jù)拷貝,2次CPU數(shù)據(jù)拷貝。
老式讀寫(xiě)提速方法就是需減少用戶(hù)態(tài)與內(nèi)核態(tài)的上下文切換和內(nèi)存拷貝的次數(shù)。數(shù)據(jù)傳輸時(shí)從內(nèi)核的讀緩沖區(qū)拷貝到用戶(hù)的緩沖區(qū),再?gòu)挠脩?hù)緩沖區(qū)拷貝到 socket 緩沖區(qū)的這個(gè)過(guò)程是沒(méi)有必要的。接下來(lái)
接下來(lái)按照三個(gè)版本說(shuō)下Zero Copy 發(fā)展史。
5.2.4.2 mmap 跟 write
mmap + write思路就是用mmap替代read函數(shù),mmap調(diào)用時(shí)會(huì)直接把內(nèi)核緩沖區(qū)里的數(shù)據(jù)映射到用戶(hù)空間,此時(shí)減少了一次數(shù)據(jù)拷貝,但仍然需要通過(guò) CPU 把內(nèi)核緩沖區(qū)的數(shù)據(jù)拷貝到 socket 緩沖區(qū)里,而且仍然需要 4 次上下文切換,因?yàn)橄到y(tǒng)調(diào)用還是 2 次。
buf = mmap(file, len); write(sockfd, buf, len);
5.2.4.3 sendfile
Linux 內(nèi)核版本 2.1版本提供了函數(shù) sendfile()。
ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count); out_fd : 目的文件描述符 in_fd:源文件描述符 offset:源文件內(nèi)偏移量 count:打算復(fù)制數(shù)據(jù)長(zhǎng)度 ssize_t:實(shí)際上復(fù)制數(shù)據(jù)的長(zhǎng)度
可以發(fā)現(xiàn)一個(gè) sendfile = read + write,避免了2次用戶(hù)態(tài)跟內(nèi)核態(tài)來(lái)回切換,并且可以直接把內(nèi)核緩沖區(qū)里的數(shù)據(jù)拷貝到 socket 緩沖區(qū)里,這樣就只有 2 次上下文切換,和 3 次數(shù)據(jù)拷貝。
sendfile模式
5.2.4.4 真正的零拷貝
Linux 內(nèi)核 2.4如果網(wǎng)卡支持SG-DMA 技術(shù),可以減少通過(guò) CPU 把內(nèi)核緩沖區(qū)里的數(shù)據(jù)拷貝到 socket 緩沖區(qū)的過(guò)程。
$ ethtool -k eth0 | grep scatter-gather scatter-gather: on
SG-DMA 技術(shù)可以直接將內(nèi)核緩存中的數(shù)據(jù)拷貝到網(wǎng)卡的緩沖區(qū)里,此過(guò)程不需要將數(shù)據(jù)從操作系統(tǒng)內(nèi)核緩沖區(qū)拷貝到 socket 緩沖區(qū)中,這樣就減少了一次數(shù)據(jù)拷貝。
ZeroCopy
5.2.4.5 文件傳輸規(guī)則
不要以為會(huì)了Zero Copy后,無(wú)論大小文件都用Zero Copy。實(shí)際工作中一般小文件采用Zero Copy技術(shù),而大文件會(huì)用異步IO。至于為啥,且看如下分析:
前面說(shuō)的數(shù)據(jù)從磁盤(pán)讀到內(nèi)核緩沖區(qū)就是讀到PageCache中,PageCache具有緩存跟預(yù)讀功能。但當(dāng)傳輸超大文件時(shí)PageCache會(huì)不失效,因?yàn)榇笪募?huì)快速占滿(mǎn)PageCache區(qū),但這些文件又只是一次訪(fǎng)問(wèn),會(huì)造成其他熱點(diǎn)小文件無(wú)法使用PageCache,所以索性不用PageCache,使用異步IO的了。至于異步IO是啥呢?下文在說(shuō)。
5.3 IO分層
IO分層Linux 存儲(chǔ)系統(tǒng)的 I/O 由上到下可以分為文件系統(tǒng)層、通用塊層、設(shè)備層。
-
文件系統(tǒng)層向上為應(yīng)用程序統(tǒng)一提供了標(biāo)準(zhǔn)的文件訪(fǎng)問(wèn)接口,向下會(huì)通過(guò)通用塊層來(lái)存儲(chǔ)和管理磁盤(pán)數(shù)據(jù)。
-
通用塊層包括塊設(shè)備的 I/O 隊(duì)列和 I/O 調(diào)度器,通過(guò)IO調(diào)度器處理IO請(qǐng)求。
-
設(shè)備層包括硬件設(shè)備、設(shè)備控制器和驅(qū)動(dòng)程序,負(fù)責(zé)最終物理設(shè)備的 I/O 操作。
Linux系統(tǒng)中的IO讀取提速:
-
為提高文件訪(fǎng)問(wèn)效率會(huì)使用頁(yè)緩存、索引節(jié)點(diǎn)緩存、目錄項(xiàng)緩存等多種緩存機(jī)制,目的是為了減少對(duì)塊設(shè)備的直接調(diào)用。
-
為了提高塊設(shè)備的訪(fǎng)問(wèn)效率, 會(huì)使用緩沖區(qū),來(lái)緩存塊設(shè)備的數(shù)據(jù)。
6 End
小3W字,終于吹逼完了。希望讀完可以讓你對(duì)操作系統(tǒng)有個(gè)大概的印象,你在用Window,卻不知經(jīng)過(guò)30年的基礎(chǔ)沉淀,Windows 的完整源代碼樹(shù)的大小超過(guò) 0.5 TB,涉及超過(guò)56萬(wàn)個(gè)文件夾,400 多萬(wàn)個(gè)文件,總規(guī)模超十億行。再加上各個(gè)功能之間需要兼容性,可維護(hù)性,可管理性等這些隨著代碼的越來(lái)越多可推敲,最終產(chǎn)生了這樣的一個(gè)藝術(shù)品!





