當(dāng)前位置:首頁 > 公眾號精選 > C語言與CPP編程
[導(dǎo)讀]現(xiàn)代計(jì)算機(jī)之父馮諾伊曼最先提出程序存儲的思想,并成功將其運(yùn)用在計(jì)算機(jī)的設(shè)計(jì)之中,該思想約定了用二進(jìn)制進(jìn)行計(jì)算和存儲,還定義計(jì)算機(jī)基本結(jié)構(gòu)為 5 個部分。


1 馮諾伊曼體系

1.1 馮諾伊曼體系簡介

現(xiàn)代計(jì)算機(jī)之父馮諾伊曼最先提出程序存儲的思想,并成功將其運(yùn)用在計(jì)算機(jī)的設(shè)計(jì)之中,該思想約定了用二進(jìn)制進(jìn)行計(jì)算和存儲,還定義計(jì)算機(jī)基本結(jié)構(gòu)為 5 個部分,分別是中央處理器(CPU)、內(nèi)存、輸入設(shè)備、輸出設(shè)備、總線。

  1. 存儲器:代碼跟數(shù)據(jù)在RAM跟ROM中是線性存儲, 數(shù)據(jù)存儲的單位是一個二進(jìn)制位。最小的存儲單位是字節(jié)。

  2. 總線:總線是用于 CPU 和內(nèi)存以及其他設(shè)備之間的通信,總線主要有三種:

  1. 地址總線:用于指定 CPU 將要操作的內(nèi)存地址。

  2. 數(shù)據(jù)總線:用于讀寫內(nèi)存的數(shù)據(jù)。

  3. 控制總線:用于發(fā)送和接收信號,比如中斷、設(shè)備復(fù)位等信號,CPU 收到信號后響應(yīng),這時也需要控制總線。

  1. 輸入/輸出設(shè)備:輸入設(shè)備向計(jì)算機(jī)輸入數(shù)據(jù),計(jì)算機(jī)經(jīng)過計(jì)算后,把數(shù)據(jù)輸出給輸出設(shè)備。比如鍵盤按鍵時需要和 CPU 進(jìn)行交互,這時就需要用到控制總線。

  2. CPU:中央處理器,類比人腦,作為計(jì)算機(jī)系統(tǒng)的運(yùn)算和控制核心,是信息處理、程序運(yùn)行的最終執(zhí)行單元。CPU用寄存器存儲計(jì)算時所需數(shù)據(jù),寄存器一般有三種:

  1. 通用寄存器:用來存放需要進(jìn)行運(yùn)算的數(shù)據(jù),比如需進(jìn)行加法運(yùn)算的兩個數(shù)據(jù)。

  2. 程序計(jì)數(shù)器:用來存儲 CPU 要執(zhí)行下一條指令所在的內(nèi)存地址。

  3. 指令寄存器:用來存放程序計(jì)數(shù)器指向的指令本身。

在馮諾伊曼體系下電腦指令執(zhí)行的過程

  1. CPU讀取程序計(jì)數(shù)器獲得指令內(nèi)存地址,CPU控制單元操作地址總線從內(nèi)存地址拿到數(shù)據(jù),數(shù)據(jù)通過數(shù)據(jù)總線到達(dá)CPU被存入指令寄存器。

  2. CPU分析指令寄存器中的指令,如果是計(jì)算類型的指令交給邏輯運(yùn)算單元,如果是存儲類型的指令交給控制單元執(zhí)行。

  3. CPU 執(zhí)行完指令后程序計(jì)數(shù)器的值通過自增指向下個指令,比如32位CPU會自增4。

  4. 自增后開始順序執(zhí)行下一條指令,不斷循環(huán)執(zhí)行直到程序結(jié)束。

CPU位寬:32位CPU一次可操作計(jì)算4個字節(jié),64位CPU一次可操作計(jì)算8個字節(jié),這個是硬件級別的。平常我們說的32位或64位操作系統(tǒng)指的是軟件級別的,指的是程序中指令多少位。
線路位寬:CPU操作指令數(shù)據(jù)通過高低電壓變化進(jìn)行數(shù)據(jù)傳輸,傳輸時候可以串行傳輸,也可以并行傳輸,多少個并行等于多少個位寬。

1.2 CPU 簡介

Central Processing Unit 中央處理器,作為計(jì)算機(jī)系統(tǒng)的運(yùn)算和控制核心,是信息處理、程序運(yùn)行的最終執(zhí)行單元。

CPU
  1. CPU核心:一般一個CPU會有多個CPU核心,平常說的多核是指在一枚處理器中集成兩個或多個完整的計(jì)算引擎。核跟CPU的關(guān)系是:核屬于CPU的一部分。

  2. 寄存器:最靠近CPU對存儲單元,32位CPU寄存器可存儲4字節(jié),64位寄存器可存儲8字節(jié)。寄存器訪問速度一般是半個CPU時鐘周期,屬于納秒級別,

  3. L1緩存:每個CPU核心都有,用來緩存數(shù)據(jù)跟指令,訪問空間大小一般在32~256KB,訪問速度一般是2~4個CPU時鐘周期。

    cat /sys/devices/system/cpu/cpu0/cache/index0/size # L1 數(shù)據(jù)緩存 cat /sys/devices/system/cpu/cpu0/cache/index1/size # L1 指令緩存 
  4. L2緩存:每個CPU核心都有,訪問空間大小在128KB~2MB,訪問速度一般是10~20個CPU時鐘周期。

    cat /sys/devices/system/cpu/cpu0/cache/index2/size # L2 緩存容量大小 
  5. L3緩存:多個CPU核心共用,訪問空間大小在2MB~64MB,訪問速度一般是20~60個CPU時鐘周期。

    cat /sys/devices/system/cpu/cpu0/cache/index3/size # L3 緩存容量大小 
  6. 內(nèi)存:多個CPU共用,現(xiàn)在一般是4G~512G,訪問速度一般是200~300個CPU時鐘周期。

  7. 固體硬盤SSD:現(xiàn)在臺式機(jī)主流都會配備,上述的寄存器、緩存、內(nèi)存都是斷電數(shù)據(jù)立馬丟失的,而SSD里不會丟失,大小一般是128G~1T,比內(nèi)存慢10~1000倍。

  8. 機(jī)械盤HDD:很早以前流行的硬盤了,容量可在512G~8T不等,訪問速度比內(nèi)存慢10W倍不等。

  9. 訪問數(shù)據(jù)順序:CPU在拿數(shù)據(jù)處理的時候幾乎也是按照上面說得流程來操縱的,只有上面一層找不到才會找下一層。

  10. Cache Line:  CPU讀取數(shù)據(jù)時會按照 Cache Line 方式把數(shù)據(jù)加載到緩存中,每個Cacheline = 64KB,因?yàn)長1、L2是每個核獨(dú)有到可能會觸發(fā)偽共享,就是 所以可能會將數(shù)據(jù)劃分到不同到CacheLine中來避免偽共享,比如在JDK8 新增加的 LongAdder 就涉及到此知識點(diǎn)。

  1. 偽共享:緩存系統(tǒng)中是以緩存行(cache line)為單位存儲的,當(dāng)多線程修改互相獨(dú)立的變量時,如果這些變量共享同一個緩存行,就會無意中影響彼此的性能,這就是偽共享。

  1. JMM: 數(shù)據(jù)經(jīng)過種種分層會導(dǎo)致訪問速度在不斷提升,同時也帶來了各種問題,多個CPU同時操作相同數(shù)據(jù)可能會造成各種BU個,需要加鎖,這里在JUC并發(fā)已詳細(xì)探討過。

1.3 CPU 訪問方式

CPU訪問方式
內(nèi)存數(shù)據(jù)映射到CPU Cache 時通過公式Block N % CacheLineMax決定內(nèi)存Block數(shù)據(jù)放到那個CPU Cache Line 里。CPU Cache 主要有4部分組成。
  1. Cache Line Index :CPU緩存讀取數(shù)據(jù)時不是按照字節(jié)來讀取的,而是按照CacheLine方式存儲跟讀取數(shù)據(jù)的。

  2. Valid Bit : 有效位標(biāo)志符,值為0時表示無論 CPU Line 中是否有數(shù)據(jù),CPU 都會直接訪問內(nèi)存,重新加載數(shù)據(jù)。

  3. Tag:組標(biāo)記,用來標(biāo)記內(nèi)存中不同BLock映射到相同CacheLine,用Tag來區(qū)分不同的內(nèi)存Block。

  4. Data:真實(shí)到內(nèi)存數(shù)據(jù)信息。

CPU真實(shí)訪問內(nèi)存數(shù)據(jù)時只需要指定三個部分即可。

  1. Cache Line Index :要訪問到Cache Line 位置。

  2. Tag:表示用那個數(shù)據(jù)塊。

  3. Offset:CPU從CPU Cache 讀取數(shù)據(jù)時不是直接讀取Cache Line整個數(shù)據(jù)塊,而是讀取CPU所需的數(shù)據(jù)片段,稱為Word。如何找到Word就需要個偏移量Offset。

1.4 CPU 訪問速度

訪問耗時對比
如上圖所示,CPU訪問速度是逐步變慢,所以CPU訪問數(shù)據(jù)時需盡量在距離CPU近的高速緩存區(qū)訪問,根據(jù)摩爾定律CPU訪問速度每18個月就會翻倍,而內(nèi)存的訪問每18個月也就增長10% 左右,導(dǎo)致的結(jié)果就是CPU跟內(nèi)存訪問性能差距逐步變大,那如何盡可能提高CPU緩存命中率呢?
1.數(shù)據(jù)緩存:遍歷數(shù)據(jù)時候按照內(nèi)存布局順序訪問,因?yàn)镃PU Cache是根據(jù)Cache Line批量操作數(shù)據(jù)的,所以你順序讀取數(shù)據(jù)會提速,道理跟磁盤順序?qū)懸粯印?/span>
  1. 指令緩存:盡可能的提供有規(guī)律的條件分支語句,讓 CPU 的分支預(yù)測器發(fā)揮作用,進(jìn)一步提高執(zhí)行的效率,因?yàn)镃PU是自帶分支預(yù)測器,自動提前將可能需要的指令放到指令緩存區(qū)。

  2. 線程綁定到CPU:一個任務(wù)A在前一個時間片用CPU核心1 運(yùn)行,后一個時間片用CPU核心2 運(yùn)行,這樣緩存L1、L2就浪費(fèi)了。因此操作系統(tǒng)提供了將進(jìn)程或者線程綁定到某一顆 CPU 上運(yùn)行的能力。如 Linux 上提供了 sched_setaffinity 方法實(shí)現(xiàn)這一功能,其他操作系統(tǒng)也有類似功能的 API 可用。當(dāng)多線程同時執(zhí)行密集計(jì)算,且 CPU 緩存命中率很高時,如果將每個線程分別綁定在不同的 CPU 核心上,性能便會獲得非??捎^的提升。

1.5  操作系統(tǒng)

計(jì)算機(jī)結(jié)構(gòu)
有了馮諾伊曼計(jì)算機(jī)體系后,電腦想要為用戶提供便捷的服務(wù)還需要安裝個操作系統(tǒng)Operation System,操作系統(tǒng)是覆蓋在硬件上的一層特殊軟件,它管理計(jì)算機(jī)的硬件和軟件資源,為其他應(yīng)用程序提供大量服務(wù)??梢岳斫鉃椴僮飨到y(tǒng)是日常應(yīng)用程序跟硬件之間的接口。日常你經(jīng)常在用Windows/Linux 系統(tǒng),操作系統(tǒng)給我們提供了超級大的便利,但是你了解操作系統(tǒng)么?操作系統(tǒng)是如何進(jìn)行 內(nèi)存管理進(jìn)程管理、 文件管理、 輸入輸出管理的呢?

2 內(nèi)存管理

你的電腦是32位操作系統(tǒng),那可支持的最大內(nèi)存就是4G,你有沒有好奇為什么可以同時運(yùn)行2個以上的2G內(nèi)存的程序。應(yīng)用程序不是直接使用的物理地址,操作系統(tǒng)為每個運(yùn)行的進(jìn)程分配了一套虛擬地址,每個進(jìn)程都有自己的虛擬內(nèi)存地址,進(jìn)程是無法直接進(jìn)行物理內(nèi)存地址的訪問的。至于虛擬地址跟物理地址的映射,進(jìn)程是感知不到的!操作系統(tǒng)自身會提供一套機(jī)制將不同進(jìn)程的虛擬地址和不同內(nèi)存的物理地址進(jìn)行映射。

虛擬內(nèi)存

2.1  MMU

Memory Management Unit 內(nèi)存管理單元是一種負(fù)責(zé)處理CPU內(nèi)存訪問請求的計(jì)算機(jī)硬件。它的功能包括虛擬地址到物理地址的轉(zhuǎn)換、內(nèi)存保護(hù)、中央處理器高速緩存的控制。現(xiàn)代 CPU 基本上都選擇了使用 MMU。

當(dāng)進(jìn)程持有虛擬內(nèi)存地址的時候,CPU執(zhí)行該進(jìn)程時會操作虛擬內(nèi)存,而MMU會自動的將虛擬內(nèi)存的操作映射到物理內(nèi)存上。

MMU
這里提一下,Java操作的時候你看到的地址是JVM地址,不是真正的物理地址。

2.2  內(nèi)存管理方式

操作系統(tǒng)主要采用內(nèi)存分段和內(nèi)存分頁來管理虛擬地址與物理地址之間的關(guān)系,其中分段是很早前的方法了,現(xiàn)在大部分用的是分頁,不過分頁也不是完全的分頁,是在分段的基礎(chǔ)上再分頁。

2.2.1 內(nèi)存分段
JVM內(nèi)存模型

我們以上圖的JVM內(nèi)存模型舉例,程序員會認(rèn)為我們的代碼是由代碼段、數(shù)據(jù)段、棧段、堆段組成。不同的段是有不同的屬性的,用戶并不關(guān)心這些元素所在內(nèi)存的位置,而分段就是支持這種用戶視圖的內(nèi)存管理方案。邏輯地址空間是由一組段構(gòu)成。每個段都有名稱和長度。地址指定了段名稱和段內(nèi)偏移。因此用戶段編號和段偏移來指定不同屬性的地址。而虛擬內(nèi)存地址跟物理內(nèi)存地址中間是通過段表進(jìn)行映射的,口說無憑,看圖吧。

內(nèi)存分段管理

如上虛擬地址有 5 個段,各段按如圖所示來存儲。每個段都在段表中有一個條目,它包括段在物理內(nèi)存內(nèi)的開始的基地址和該段的界限長度。例如段 2 為 400 字節(jié)長,開始于位置 4300。因此對段 2 字節(jié) 53 的引用映射成位置 4300 + 53 = 4353。對段 3 字節(jié) 852 的引用映射成位置 3200 + 852 = 4052。

分段映射很簡單,但是會導(dǎo)致內(nèi)存碎片跟內(nèi)存交互效率低。這里先普及下在內(nèi)存管理中主要有內(nèi)部內(nèi)存碎片跟外部內(nèi)存碎片。

  1. 內(nèi)部碎片:已經(jīng)被分配出去的的內(nèi)存空間不經(jīng)常使用,并且分配出去的內(nèi)存空間大于請求所需的內(nèi)存空間。

  2. 外部碎片:指可用空間還沒有分配出去,但是可用空間由于大小太小而無法分配給申請空間的新進(jìn)程的內(nèi)存空間空閑塊。

以上圖為例,現(xiàn)在系統(tǒng)空閑是1400 +  800 + 600 = 2800。那如果有個程序想要連續(xù)的使用2000,內(nèi)存分段模式下提供不了啊!上述三個是外部內(nèi)存碎片。當(dāng)然可以使用系統(tǒng)的Swap空間,先把段0寫入到磁盤,然后再重新給段0分配空間。這樣可以實(shí)現(xiàn)最終可用,可是但凡涉及到磁盤讀寫就會導(dǎo)致內(nèi)存交互效率低。

swap空間利用
2.2.2 內(nèi)存分頁

內(nèi)存分頁,整個虛擬內(nèi)存和物理內(nèi)存切成一段段固定尺寸的大小。每個固定大小的尺寸稱之為頁P(yáng)age,在 Linux 系統(tǒng)中Page = 4KB。然后虛擬內(nèi)存跟物理內(nèi)存之間通過頁表來實(shí)現(xiàn)映射。

采用內(nèi)存分頁時內(nèi)存的釋放跟使用都是以頁為單位的,也就不會產(chǎn)生內(nèi)存碎片了。當(dāng)空間還不夠時根據(jù)操作系統(tǒng)調(diào)度算法,可能將最少用的內(nèi)存頁面 swap-out換出到磁盤,用時候再swap-in換入,盡可能的減少磁盤刷寫量,提高內(nèi)存交互效率。

分頁模式下虛擬地址主要有頁號跟頁內(nèi)偏移量兩部分組成。通過頁號查詢頁表找到物理內(nèi)存地址,然后再配合頁內(nèi)偏移量就找到了真正的物理內(nèi)存地址。

分頁內(nèi)存尋址

32位操作系統(tǒng)環(huán)境下進(jìn)程可操作的虛擬地址是4GB,假設(shè)一個虛擬頁大小為4KB,那需要4GB/4KB =2^20個頁信息。一行頁表記錄為4字節(jié),2^20等價于4MB頁表存儲信息。這只是一個進(jìn)程需要的,如果10個、100個、1000個呢?僅僅是頁表存儲都占據(jù)超大內(nèi)存了。

為了解決這個問題就需要用到多級頁表,核心思想就是局部性分配。在32位的操作系統(tǒng)中將將4G空間分為 1024 行頁目錄項(xiàng)目(4KB),每個頁目錄項(xiàng)又對應(yīng)1024行頁表項(xiàng)。如下圖所示:

32位系統(tǒng)二級分頁
控制寄存器cr3中存放了頁目錄的物理地址,通過cr3寄存器可以找到頁目錄,而32位線性地址中的Directory部分決定頁目錄中的目錄項(xiàng),而頁目錄項(xiàng)中存放了要找的頁表的物理基地址,再結(jié)合線性地址中的中間10位頁表項(xiàng),就可以找到頁框的頁表項(xiàng)。線性地址中的Offset部分占12位,因此頁框的物理地址 + 線性地址Offset部分 = 頁框中的任何一個字節(jié)。

分頁后一級頁就等價于4G虛擬地址空間,并且如果一級頁表中那些地址沒有就不需要再創(chuàng)建二級頁表了!核心思想就是按需創(chuàng)建,當(dāng)系統(tǒng)給每個進(jìn)程分配4G空間,進(jìn)程不可能占據(jù)全部內(nèi)存的,如果一級目錄頁只有10%用到了,此時頁表空間 = 一級頁表4KB + 0.1 * 4MB  。這比單獨(dú)的每個進(jìn)程占據(jù)4M好用多了!

多層分頁的弊端就是訪問時間的增加。

  1. 使用頁表時讀取內(nèi)存中一頁內(nèi)容需要2次訪問內(nèi)存,訪問頁表項(xiàng) + 并讀取的一頁數(shù)據(jù)。

  2. 使用二級頁表的話需要三次訪問,訪問頁目錄項(xiàng) + 訪問頁表項(xiàng) + 訪問并讀取的一頁數(shù)據(jù)。訪存次數(shù)的增加也就意味著訪問數(shù)據(jù)所花費(fèi)的總時間增加。

而對于64位系統(tǒng),二級分頁就無法滿足了,Linux 從2.6.11開始采用四級分頁模型。

  1. Page Global Directory 全局頁目錄項(xiàng)

  2. Page Upper Directory 上層頁目錄項(xiàng)

  3. Page Middle Directory 中間頁目錄項(xiàng)

  4. Page Table Entry 頁表項(xiàng)

  5. Offset 偏移量。

64位尋址
2.2.2 TLB

Translation Lookaside Buffer 可翻譯為地址轉(zhuǎn)換后援緩沖器,簡稱為快表,屬于CPU內(nèi)部的一個模塊,TLB是MMU的一部分,實(shí)質(zhì)是cache,它所緩存的是最近使用的數(shù)據(jù)的頁表項(xiàng)(虛擬地址到物理地址的映射)。他的出現(xiàn)是為了加快訪問數(shù)據(jù)(內(nèi)存)的速度,減少重復(fù)的頁表查找。當(dāng)然它不是必須要有的,但有它,速度就更快。

TLB
TLB很小,因此緩存的東西也不多。主要緩存最近使用的數(shù)據(jù)的數(shù)據(jù)映射。TLB結(jié)構(gòu)如下圖: TLB查詢
如果一個需要訪問內(nèi)存中的一個數(shù)據(jù),給定這個數(shù)據(jù)的虛擬地址,查詢TLB,發(fā)現(xiàn)有hit,直接得到物理地址,在內(nèi)存根據(jù)物理地址取數(shù)據(jù)。如果TLB沒有這個虛擬地址miss,那么只能費(fèi)力的通過頁表來查找了。日常CPU讀取一個數(shù)據(jù)的流程如下:
CPU讀取數(shù)據(jù)流程圖
當(dāng)進(jìn)程地址空間進(jìn)行了上下文切換時,比如現(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)的,此時TLB刷新數(shù)據(jù)有兩種辦法。
  1. 全部刷新:很簡單,但花銷大,很多不必刷新的數(shù)據(jù)也進(jìn)行刷新,增加了無畏的花銷。

  2. 部分刷新:根據(jù)標(biāo)志位,刷新需要刷新的數(shù)據(jù),保留不需要刷新的數(shù)據(jù)。

2.2.3 段頁式管理

內(nèi)存分段跟內(nèi)存分頁不是對立的,這倆可以組合起來在同一個系統(tǒng)中使用的,那么組合起來后通常稱為段頁式內(nèi)存管理。段頁式內(nèi)存管理實(shí)現(xiàn)的方式:

  1. 先對數(shù)據(jù)不同劃分出不同的段,也就是前面說的分段機(jī)制。

  2. 然后再把每一個段進(jìn)行分頁操作,也就是前面說的分頁機(jī)制。

  3. 此時 地址結(jié)構(gòu) = 段號 + 段內(nèi)頁號 + 頁內(nèi)位移。

每一個進(jìn)程有一張段表,每個段又建立一張頁表,段表中的地址是頁表的起始地址,而頁表中的地址則為某頁的物理頁號。

段頁式管理
同時我們經(jīng)??吹絻蓚€專業(yè)詞邏輯地址跟線性地址。
  1. 邏輯地址:指的是沒被段式內(nèi)存管理映射的地址。

  2. 線性地址:通過段式內(nèi)存管理映射且頁式內(nèi)存管理轉(zhuǎn)換前的地址,俗稱虛擬地址。

目前 Intel X86 CPU 采用的是內(nèi)存分段 +  內(nèi)存分頁的管理方式,其中分頁的意思是在由段式內(nèi)存管理所映射而成的的地址上再加上一層地址映射。

X86內(nèi)存管理方式
2.2.4 Linux 內(nèi)存管理

先說結(jié)論:Linux系統(tǒng)基于X86 CPU 而做的操作系統(tǒng),所以也是用的段頁式內(nèi)存管理方式。


我們知道32位的操作系統(tǒng)可尋址范圍是4G,操作系統(tǒng)會將4G的可訪問內(nèi)存空間分為 用戶空間內(nèi)核空間。
  1. 內(nèi)核空間:操作系統(tǒng)內(nèi)核訪問的區(qū)域,獨(dú)立于普通的應(yīng)用程序,是受保護(hù)的內(nèi)存空間。內(nèi)核態(tài)下CPU可執(zhí)行任何指令,可自由訪問任何有效地址。

  2. 用戶空間:普通應(yīng)用程序可訪問的內(nèi)存區(qū)域。被執(zhí)行代碼會受到CPU眾多限制,進(jìn)程只能訪問映射其地址空間的頁表項(xiàng)中規(guī)定的在用戶態(tài)下可訪問頁面的虛擬地址。

那為啥要搞倆空間呢?現(xiàn)在嵌入式環(huán)境跟以前的WIN98系統(tǒng)是沒有區(qū)分倆空間的,須知倆空間是CPU分的,而操作系統(tǒng)是在上面運(yùn)行的,單一用戶、單一任務(wù)服務(wù)的操作系統(tǒng),是沒有分所謂用戶態(tài)和內(nèi)核態(tài)的必要。用戶態(tài)和內(nèi)核態(tài)是因?yàn)橛卸嘤脩簦嗳蝿?wù)的需求,然后在CPU硬件廠商配合之后,產(chǎn)生的一個操作系統(tǒng)解決多用戶多任務(wù)需求的方案。方案就是限制,通過硬件手段(也只能硬件手段才能做到),限制某些代碼,使其無法控制整個物理硬件,進(jìn)而使各個不同用戶,不同任務(wù)的代碼,無權(quán)修改整個物理硬件,再進(jìn)而保護(hù)操作系統(tǒng)的核心底層代碼和其他用戶的數(shù)據(jù)不被無意或者有意地破壞和盜取。


后來研究者根據(jù) CPU的運(yùn)行級別,分成了Ring0~Ring3四個級別。Ring0是最高級別,Ring1次之,Rng2更次之,拿Linux+x86來說,  操作系統(tǒng)內(nèi)核的代碼運(yùn)行在最高運(yùn)行級別Ring0上,可以使用特權(quán)指令,控制中斷、修改頁表、訪問設(shè)備等。 應(yīng)用程序的代碼運(yùn)行在最低運(yùn)行級別上Ring3上,不能做受控操作,只能訪問用戶被分配的空間。如果要做訪問磁盤跟寫文件等操作,那就要通過執(zhí)行系統(tǒng)調(diào)用函數(shù),執(zhí)行系統(tǒng)調(diào)用的時候,CPU的運(yùn)行級別會發(fā)生從Ring3到Ring0的切換,并跳轉(zhuǎn)到系統(tǒng)調(diào)用對應(yīng)的內(nèi)核代碼位置執(zhí)行,這樣內(nèi)核就為你完成了設(shè)備訪問,完成之后再從Ring0返回Ring3。這個過程也稱作 用戶態(tài)和內(nèi)核態(tài)的切換

用戶態(tài)想要使用計(jì)算機(jī)設(shè)備或IO需通過系統(tǒng)調(diào)用完成sys call,系統(tǒng)調(diào)用就是讓內(nèi)核來做這些操作。而系統(tǒng)調(diào)用是影響整個當(dāng)前進(jìn)程上下文的,CPU提供了個軟中斷來是實(shí)現(xiàn)保護(hù)線程,獲取系統(tǒng)調(diào)用號跟參數(shù),交給內(nèi)核對應(yīng)系統(tǒng)調(diào)用函數(shù)執(zhí)行。

Linux系統(tǒng)結(jié)構(gòu)
可以看到每個應(yīng)用程序都各自有獨(dú)立的虛擬內(nèi)存地址,但每個虛擬內(nèi)存中對應(yīng)的內(nèi)核地址其實(shí)是相同的一大塊,這樣當(dāng)進(jìn)程切換到內(nèi)核態(tài)后可以很方便地訪問內(nèi)核空間內(nèi)存。比如Java代碼創(chuàng)建線程new Thread調(diào)用start方法后跟蹤JVM源碼你會發(fā)現(xiàn)是調(diào)用pthread_create來創(chuàng)建線程的,這就涉及到了用戶態(tài)到內(nèi)核態(tài)的切換。

3 進(jìn)程管理

3.1 進(jìn)程基礎(chǔ)知識

進(jìn)程是程序的一次執(zhí)行,是一個程序及其數(shù)據(jù)在機(jī)器上順序執(zhí)行時所發(fā)生的活動,是具有獨(dú)立功能的程序在一個數(shù)據(jù)集合上的一次運(yùn)行過程,是系統(tǒng)進(jìn)行資源分配和調(diào)度的一個基本單位。進(jìn)程的調(diào)度狀態(tài)如下:

狀態(tài)變化圖
重點(diǎn)說下掛起跟阻塞:

  1. 阻塞一般是當(dāng)系統(tǒng)執(zhí)行IO操作時,此時進(jìn)程進(jìn)入阻塞狀態(tài),等待某個事件的返回。

  2. 掛起是指進(jìn)程沒有占有物理內(nèi)存,被寫到磁盤上了。這時進(jìn)程狀態(tài)是掛起狀態(tài)。

  1. 阻塞掛起:進(jìn)程被寫入硬盤并等待某個事件的出現(xiàn)。

  2. 就緒掛起:進(jìn)程被寫入硬盤,進(jìn)入內(nèi)存可直接進(jìn)入就緒狀態(tài)。

3.2 PCB

為了描述跟控制進(jìn)程的運(yùn)行,系統(tǒng)為每個進(jìn)程定義了一個數(shù)據(jù)結(jié)構(gòu)——進(jìn)程控制塊 Process Control Block,它是進(jìn)程實(shí)體的一部分,是操作系統(tǒng)中最重要的記錄型數(shù)據(jù)結(jié)構(gòu)。

PCB 的作用是使一個在多道程序環(huán)境下不能獨(dú)立運(yùn)行的程序,成為一個能獨(dú)立運(yùn)行的基本單位,一個能與其它進(jìn)程并發(fā)執(zhí)行的進(jìn)程 :

  1. 作為獨(dú)立運(yùn)行基本單位的標(biāo)志

  2. 實(shí)現(xiàn)間斷性的運(yùn)行方式

  3. 提供進(jìn)程管理所需要的信息

  4. 提供進(jìn)程調(diào)度所需要的信息

  5. 實(shí)現(xiàn)與其他進(jìn)程的同步與通信

3.2.1 PCB 信息

PCB為實(shí)現(xiàn)上述功能,內(nèi)部包含眾多信息:

  1. 進(jìn)程標(biāo)識符:用于唯一地標(biāo)識一個進(jìn)程,一個進(jìn)程通常有兩種標(biāo)識符:

  1. 內(nèi)部進(jìn)程標(biāo)識符:標(biāo)識各個進(jìn)程,每個進(jìn)程都有一個并且唯一的標(biāo)識符,設(shè)置內(nèi)部標(biāo)識符主要是為了方便系統(tǒng)使用。

  2. 外部進(jìn)程標(biāo)識符:它由創(chuàng)建者提供,可設(shè)置用戶標(biāo)識,以指示擁有該進(jìn)程的用戶。往往是由用戶進(jìn)程在訪問該進(jìn)程時使用。一般為了描述進(jìn)程的家族關(guān)系,還應(yīng)設(shè)置父進(jìn)程標(biāo)識及子進(jìn)程標(biāo)識。

  1. 處理機(jī)狀態(tài):由各種寄存器組成。包含許多信息都放在寄存器中,方便程序restart。

  1. 通用寄存器、指令計(jì)數(shù)器、程序狀態(tài)字PSW、用戶棧指針等信息。

  1. 進(jìn)程調(diào)度信息

  1. 進(jìn)程狀態(tài):指明進(jìn)程的當(dāng)前狀態(tài),作為進(jìn)程調(diào)度和對換時的依據(jù)。

  2. 進(jìn)程優(yōu)先級:用于描述進(jìn)程使用處理機(jī)的優(yōu)先級別的一個整數(shù),優(yōu)先級高的進(jìn)程應(yīng)優(yōu)先獲得處理機(jī)

  3. 進(jìn)程調(diào)度所需的其它信息:與所采用的進(jìn)程調(diào)度算法有關(guān),如進(jìn)程已等待CPU的時間總和、進(jìn)程已執(zhí)行的時間總和等。

  4. 事件:指進(jìn)程由執(zhí)行狀態(tài)轉(zhuǎn)變?yōu)樽枞麪顟B(tài)所等待發(fā)生的事件,即阻塞原因。

  1. 資源清單

有關(guān)內(nèi)存地址空間或虛擬地址空間的信息,所打開文件的列表和所使用的 I/O 設(shè)備信息。

3.2.2 PCB 組織方式

操作系統(tǒng)中有太多 PCB,如何管理是個問題,一般有如下方式。

線下數(shù)組
  1. 線性方式

  1. 將系統(tǒng)所有PCB都組織在一張線性表中,將該表首地址存在內(nèi)存的一個專用區(qū)域

  2. 實(shí)現(xiàn)簡單,開銷小,但是每次都需要掃描整張表,適合進(jìn)程數(shù)目不多的系統(tǒng)

索引方式
  1. 索引方式

  1. 將同一狀態(tài)的進(jìn)程組織在一個索引表中,索引表項(xiàng)指向相應(yīng)的 PCB,不同狀態(tài)對應(yīng)不同的索引表。

鏈表方式
  1. 鏈接方式

  1. 把同一狀態(tài)的PCB鏈接成一個隊(duì)列,形成就緒隊(duì)列、阻塞隊(duì)列、空白隊(duì)列等。對其中的就緒隊(duì)列常按進(jìn)程優(yōu)先級的高低排列,優(yōu)先級高排在隊(duì)前。

  2. 因?yàn)檫M(jìn)程創(chuàng)建、銷毀、調(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)程也會被終止。子進(jìn)程可繼承父進(jìn)程所有資源,子進(jìn)程終止需將自己所繼承的資源歸還父進(jìn)程。接下來看下創(chuàng)建的大致流程。

  1. 為新進(jìn)程分配唯一進(jìn)件標(biāo)識號,然后創(chuàng)建一個空白PCB,需注意PCB數(shù)量是有限的,所以可能會創(chuàng)建失敗。

  2. 嘗試為新進(jìn)程分配所需資源,如果資源不足進(jìn)程會進(jìn)入等待狀態(tài)。

  3. 初始化PCB,有如下幾個操作。

  1. 標(biāo)識信息:將系統(tǒng)分配的標(biāo)識符和父進(jìn)程標(biāo)識符填入新PCB

  2. 處理機(jī)狀態(tài)信息:使程序計(jì)數(shù)器指向程序入口地址,使棧指針指向棧頂

  3. 處理機(jī)控制信息:將進(jìn)程設(shè)為就緒/靜止?fàn)顟B(tài),通常設(shè)為最低優(yōu)先級

  1. 如果進(jìn)程調(diào)度隊(duì)列能接納新進(jìn)程,就將進(jìn)程插入到就緒隊(duì)列,等待被調(diào)度運(yùn)行。

3.3.2 進(jìn)程終止

進(jìn)程終止情況一般分為正常結(jié)束、異常結(jié)束、外界干預(yù)三種。

  1. 正常結(jié)束

  2. 異常結(jié)束

  1. 越界錯:訪問的存儲區(qū)越出該進(jìn)程的區(qū)域

  2. 保護(hù)錯:試圖訪問不允許訪問的資源,或以不適當(dāng)?shù)姆绞皆L問(寫只讀)

  3. 非法指令:試圖執(zhí)行不存在的指令(可能是程序錯誤地轉(zhuǎn)移到數(shù)據(jù)區(qū),數(shù)據(jù)當(dāng)成了指令)

  4. 特權(quán)指令出錯:用戶進(jìn)程試圖執(zhí)行一條只允許OS執(zhí)行的指令

  5. 運(yùn)行超時:執(zhí)行時間超過指定的最大值

  6. 等待超時:進(jìn)程等待某件事超過指定的最大值

  7. 算數(shù)運(yùn)算錯:試圖執(zhí)行被禁止的運(yùn)算(被0除)

  8. I/O故障

  1. 外界干預(yù)

  1. 操作員或OS干預(yù)(死鎖)

  2. 父進(jìn)程請求,子進(jìn)程完成父進(jìn)程指定的任務(wù)時

  3. 父進(jìn)程終止,所有子進(jìn)程都應(yīng)該結(jié)束

終止過程

  1. 根據(jù)被終止進(jìn)程的標(biāo)識符,從PCB集合中檢索出該P(yáng)CB,讀取進(jìn)程狀態(tài)

  2. 若處于執(zhí)行狀態(tài)則立即終止執(zhí)行,將CPU資源分配給其他進(jìn)程。

  3. 若進(jìn)程有子孫進(jìn)程則將其所有子孫進(jìn)程終止。

  4. 全部資源還給父進(jìn)程或操作系統(tǒng)。

  5. 該進(jìn)程的PCB從所在隊(duì)列/鏈表中移出。

3.3.3 進(jìn)程阻塞

意思是該進(jìn)程執(zhí)行半路被阻塞,必須由某個事件進(jìn)程喚醒該進(jìn)程。常見的就是IO讀取操作。常見阻塞時機(jī)/事件如下:

  1. 請求共享資源失敗,系統(tǒng)無足夠資源分配

  2. 等待某種操作完成

  3. 新數(shù)據(jù)尚未到達(dá)(相互合作的進(jìn)程)

  4. 等待新任務(wù)

阻塞流程

  1. 找到要被阻塞進(jìn)程標(biāo)識號對應(yīng)的 PCB。

  2. 將該進(jìn)程由運(yùn)行狀態(tài)轉(zhuǎn)換為阻塞狀態(tài)。

  3. 將該 進(jìn)程PCB 插入的阻塞隊(duì)列中去。

3.3.4 進(jìn)程喚醒

喚醒 原語 wake up,一般和阻塞成對使用。喚醒過程如下:

  1. 從阻塞隊(duì)列找到所需PCB。

  2. PCB從阻塞隊(duì)列溢出,然后變?yōu)榫途w狀態(tài)。

  3. 從阻塞隊(duì)列溢出該P(yáng)CB然后插入到就緒狀態(tài)隊(duì)列等待被分配CPU資源。

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

進(jìn)程數(shù)一般會大于CPU個數(shù),進(jìn)程狀態(tài)切換主要由調(diào)度程序進(jìn)行調(diào)度。一般情況下CPU調(diào)度時主要分為搶占式調(diào)度跟非搶占式調(diào)度。

  1. 非搶占式:讓進(jìn)程運(yùn)行直到結(jié)束或阻塞的調(diào)度方式, 容易實(shí)現(xiàn),適合專用系統(tǒng)。

  2. 搶占式:每個進(jìn)程獲得時間片才可以被CPU調(diào)度運(yùn)行, 可防止單一進(jìn)程長時間獨(dú)占CPU 系統(tǒng)開銷大。

3.4.1 進(jìn)程調(diào)度原則
  1. CPU 利用率

  1. CPU利用率 = 忙碌時間 / 總時間。

  2. 調(diào)度程序應(yīng)該盡量讓 CPU 始終處于忙碌的狀態(tài),這可提高 CPU 的利用率。比如當(dāng)發(fā)生IO讀取時候,不要傻傻等待,去執(zhí)行下別的進(jìn)程。

  1. 系統(tǒng)吞吐量

  1. 系統(tǒng)吞吐量 = 總共完成多少個作業(yè) / 總共花費(fèi)時間。

  2. 長作業(yè)的進(jìn)程會占用較長的 CPU 資源導(dǎo)致降低吞吐量,相反短作業(yè)的進(jìn)程會提升系統(tǒng)吞吐量。

  1. 周轉(zhuǎn)時間

  1. 周轉(zhuǎn)時間 = 作業(yè)完成時間 - 作業(yè)提交時間。

  2. 平均周轉(zhuǎn)時間 = 各作業(yè)周轉(zhuǎn)時間和 / 作業(yè)數(shù)

  3. 帶權(quán)周轉(zhuǎn)時間 = 作業(yè)周轉(zhuǎn)時間 / 作業(yè)實(shí)際運(yùn)行時間

  4. 平均帶權(quán)周轉(zhuǎn)時間 = 各作業(yè)帶權(quán)周轉(zhuǎn)時間之和 / 作業(yè)數(shù)

  5. 盡可能使周轉(zhuǎn)時間降低。

  1. 等待時間

  1. 指的是進(jìn)程在等待隊(duì)列中等待的時間,一般也需要盡可能短。

  2. 響應(yīng)時間
    響應(yīng)時間 = 系統(tǒng)第一次響應(yīng)時間 - 用戶提交時間,在交互式系統(tǒng)中響應(yīng)時間是衡量調(diào)度算法好壞的主要標(biāo)準(zhǔn)。

3.4.2 調(diào)度算法

FCFS 算法

  1. First Come First Severd 先來先服務(wù)算法,遵循先來后端原則,每次從就緒隊(duì)列拿等待時間最久的,運(yùn)行完畢后再拿下一個。

  2. 該模式對長作業(yè)有利,適用 CPU 繁忙型作業(yè)的系統(tǒng),不適用 I/O 型作業(yè),因?yàn)闀?dǎo)致進(jìn)程CPU利用率很低。

SJF 算法

  1. Shortest Job First 最短作業(yè)優(yōu)先算法,該算法會優(yōu)先選擇運(yùn)行所需時間最短的進(jìn)程執(zhí)行,可提高吞吐量。

  2. 跟FCFS正好相反,對長作業(yè)很不利。

SRTN 算法

  1. Shortest Remaining Time Next 最短剩余時間優(yōu)先算法,可以認(rèn)為是SJF的搶占式版本,當(dāng)一個新就緒的進(jìn)程比當(dāng)前運(yùn)行進(jìn)程具有更短完成時間時,系統(tǒng)搶占當(dāng)前進(jìn)程,選擇新就緒的進(jìn)程執(zhí)行。

  2. 有最短的平均周轉(zhuǎn)時間,但不公平,源源不斷的短任務(wù)到來,可能使長的任務(wù)長時間得不到運(yùn)行。

HRRN 算法

  1. Highest Response Ratio Next 最高響應(yīng)比優(yōu)先算法,為了平衡前面?zhèn)z而生,按照響應(yīng)優(yōu)先權(quán)從高到低依次執(zhí)行。屬于前面?zhèn)z的折中權(quán)衡。

  2. 優(yōu)先權(quán) = (等待時間 + 要求服務(wù)時間) / 要求服務(wù)時間

RR 算法

  1. Round Robin 時間片輪轉(zhuǎn)算法,操作系統(tǒng)設(shè)定了個時間片Quantum,時間片導(dǎo)致每個進(jìn)程只有在該時間片內(nèi)才可以運(yùn)行,這種方式導(dǎo)致每個進(jìn)程都會均勻的獲得執(zhí)行權(quán)。

  2. 時間片一般20ms~50ms,如果太小會導(dǎo)致系統(tǒng)頻繁進(jìn)行上下文切換,太大又可能引起對短的交互請求的響應(yīng)變差。

HPF 算法

  1. Highest Priority First 最高優(yōu)先級調(diào)度算法,從就緒隊(duì)列中選擇最高優(yōu)先級的進(jìn)程先執(zhí)行。

  2. 優(yōu)先級的設(shè)置有初始化固定死的那種,也有在代碼運(yùn)轉(zhuǎn)過程中根據(jù)等待時間或性能動態(tài)調(diào)整 這兩種思路。

  3. 缺點(diǎn)是可能導(dǎo)致低優(yōu)先級的一直無法被執(zhí)行。

MFQ 算法

  1. Multilevel Feedback Queue 多級反饋隊(duì)列調(diào)度算法 ,可以認(rèn)為是 RR 算法 跟 HPF 算法 的綜合體。

  2. 系統(tǒng)會同時存在多個就緒隊(duì)列,每個隊(duì)列優(yōu)先級從高到低排列,同時優(yōu)先級越高獲得是時間片越短。

  3. 新進(jìn)程會先加入到最高優(yōu)先級隊(duì)列,如果新進(jìn)程優(yōu)先級高于當(dāng)前在執(zhí)行的進(jìn)程,會停止當(dāng)前進(jìn)程轉(zhuǎn)而去執(zhí)行新進(jìn)程。新進(jìn)程如果在時間片內(nèi)沒執(zhí)行完畢需下移到次優(yōu)先級隊(duì)列。

    多級反饋隊(duì)列調(diào)度算法

3.5 線程

3.5.1 線程定義

早期操作系統(tǒng)是沒有線程概念的,線程是后來加進(jìn)來的。為啥會有線程呢?那是因?yàn)橐郧霸诙噙M(jìn)程階段,經(jīng)常會涉及到進(jìn)程之間如何通訊,如何共享數(shù)據(jù)的問題。并且進(jìn)程關(guān)聯(lián)到PCB的生命周期,管理起來開銷較大。為了解決這個問題引入了線程。

線程是進(jìn)程當(dāng)中的一個執(zhí)行流程。同一個進(jìn)程內(nèi)的多個線程之間可以共享進(jìn)程的代碼段、數(shù)據(jù)段、打開的文件等資源。同時每個線程又都有一套獨(dú)立的寄存器和棧來確保線程的控制流是獨(dú)立的。

進(jìn)程有個PCB來管理,同理操作系統(tǒng)通過Thread Control Block線程控制塊來實(shí)現(xiàn)線程的管控。

3.5.2 線程優(yōu)缺點(diǎn)

優(yōu)點(diǎn)

  1. 一個進(jìn)程中可以同時存在1~N個線程,這些線程可以并發(fā)的執(zhí)行。

  2. 各個線程之間可以共享地址空間和文件等資源。

缺點(diǎn)

  1. 當(dāng)進(jìn)程中的一個線程奔潰時,會導(dǎo)致其所屬進(jìn)程的所有線程奔潰。

  2. 多線程編程,讓人頭大的東西。

  3. 線程執(zhí)行開銷小,但不利于資源的隔離管理和保護(hù),而進(jìn)程正相反。

3.5.3 進(jìn)程跟線程關(guān)聯(lián)

進(jìn)程

  1. 是系統(tǒng)進(jìn)行資源分配和調(diào)度的一個獨(dú)立單位.

  2. 是程序的一次執(zhí)行,每個進(jìn)程都有自己的地址空間、內(nèi)存、數(shù)據(jù)棧及其他輔助記錄運(yùn)行軌跡的數(shù)據(jù)

線程

  1. 是進(jìn)程的一個實(shí)體,是CPU調(diào)度和分派的基本單位,它是比進(jìn)程更小的能獨(dú)立運(yùn)行的基本單位

  2. 所有的線程運(yùn)行在同一個進(jìn)程中,共享相同的運(yùn)行資源和環(huán)境

  3. 線程一般是并發(fā)執(zhí)行的,使得實(shí)現(xiàn)了多任務(wù)的并行和數(shù)據(jù)共享。

進(jìn)程線程區(qū)別

  1. 一個線程只能屬于一個進(jìn)程,而一個進(jìn)程可以有多個線程,但至少有一個線程。

  2. 線程的劃分尺度小于進(jìn)程(資源比進(jìn)程少),使得多線程程序的并發(fā)性高。

  3. 進(jìn)程在執(zhí)行過程中擁有獨(dú)立的內(nèi)存單元,而多個線程共享內(nèi)存,從而極大地提高了程序的運(yùn)行效率。

  4. 資源分配給進(jìn)程,同一進(jìn)程的所有線程共享該進(jìn)程的所有資源。

  5. CPU分配資源給進(jìn)程,但真正在CPU上運(yùn)行的是線程。

  6. 線程不能夠獨(dú)立執(zhí)行,必須依存在進(jìn)程中。

線程快在哪兒?

  1. 線程創(chuàng)建的時有些資源不需要自己管理,直接從進(jìn)程拿即可,線程管理寄存器跟棧的生命周期即可。

  2. 同進(jìn)程內(nèi)多線程共享數(shù)據(jù),所以進(jìn)程數(shù)據(jù)傳輸可以用zero copy技術(shù),不需要經(jīng)過內(nèi)核了。

  3. 進(jìn)程使用一個虛擬內(nèi)存跟頁表,然后多線程共用這些虛擬內(nèi)存,如果同進(jìn)程內(nèi)兩個線程進(jìn)行上下文切換比進(jìn)程提速很多。

3.5.4 線程實(shí)現(xiàn)

在前面的內(nèi)存管理中說到了內(nèi)核態(tài)跟用戶態(tài)。相對應(yīng)的線程的創(chuàng)建也分為用戶態(tài)線程跟內(nèi)核態(tài)線程。

3.5.4.1 用戶態(tài)線程

在用戶空間實(shí)現(xiàn)的線程,由用戶態(tài)的線程庫來完成線程的管理。操作系統(tǒng)按進(jìn)程維度進(jìn)行調(diào)度,當(dāng)線程在用戶態(tài)創(chuàng)建時應(yīng)用程序在用戶空間內(nèi)要實(shí)現(xiàn)線程的創(chuàng)建、維護(hù)和調(diào)度。操作系統(tǒng)對線程的存在一無所知!操作系統(tǒng)只能看到進(jìn)程看不到線程。所有的線程都是在用戶空間實(shí)現(xiàn)。在操作系統(tǒng)看來,每一個進(jìn)程只有一個線程。

用戶態(tài)線程

好處

  1. 及時操作系統(tǒng)不支持線程模式也可以通過用戶層庫函數(shù)來支持線程模式,TCB 由用戶級線程庫函數(shù)來維護(hù)。

  2. 使用庫函數(shù)模式實(shí)現(xiàn)線程可以避免用戶態(tài)到內(nèi)核態(tài)的切換。

壞處

  1. CPU不知道線程存在,CPU的時間片切換是以進(jìn)程為維度的,某個線程因?yàn)镮O等操作導(dǎo)致線程阻塞,操作系統(tǒng)會阻塞整個進(jìn)程,即使這個進(jìn)程中其它線程還在工作。

  2. 用戶態(tài)線程沒法打斷正在運(yùn)行中的線程,除非線程主動交出CPU使用權(quán)。

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

在內(nèi)核中實(shí)現(xiàn)的線程,是由內(nèi)核管理的線程,線程對應(yīng)的 TCB 在操作系統(tǒng)里,這樣線程的創(chuàng)建、終止和管理都是由操作系統(tǒng)負(fù)責(zé)。內(nèi)線程模式下一個用戶線程對應(yīng)一個內(nèi)核線程。

內(nèi)核態(tài)線程
注意:Linux中的JVM從1.2版以后是基于pthread實(shí)現(xiàn)的,所以現(xiàn)在Java中線程的本質(zhì)就是操作系統(tǒng)中的線程。

優(yōu)點(diǎn)

  1. 一個進(jìn)程中某個線程阻塞不會影響其他內(nèi)核線程運(yùn)行。

  2. 用戶態(tài)模式一個時間片分給多個線程,內(nèi)核態(tài)模式直接分配給線程的時間片增加。

缺點(diǎn)

  1. 內(nèi)核級線程調(diào)度開銷較大。調(diào)度內(nèi)核線程的代價可能和調(diào)度進(jìn)程差不多昂貴,代價要比用戶級線程大很多。一個線程默認(rèn)棧=1M,線程多了會導(dǎo)致內(nèi)存消耗很大。

  2. 線程表是存放在操作系統(tǒng)固定的表格空間或者堆??臻g里,所以內(nèi)核級線程的數(shù)量是有限的。

3.4.4.3 輕量級進(jìn)程

最初的進(jìn)程定義都包含程序、資源及其執(zhí)行三部分,其中程序通常指代碼,資源在操作系統(tǒng)層面上通常包括內(nèi)存資源、IO資源、信號處理等部分,而程序的執(zhí)行通常理解為執(zhí)行上下文,包括對CPU的占用,后來發(fā)展為線程。在線程概念出現(xiàn)以前,為了減小進(jìn)程切換的開銷,操作系統(tǒng)設(shè)計(jì)者逐漸修正進(jìn)程的概念,逐漸允許將進(jìn)程所占有的資源從其主體剝離出來,允許某些進(jìn)程共享一部分資源,例如文件、信號,數(shù)據(jù)內(nèi)存,甚至代碼,這就發(fā)展出輕量進(jìn)程的概念。

Light-weight process 輕量級進(jìn)程是內(nèi)核支持的用戶線程,它是基于內(nèi)核線程的高級抽象,系統(tǒng)只有先支持內(nèi)核線程才能有 LWP。一個進(jìn)程可有1~N個LWP,每個 LWP 是跟內(nèi)核線程一對一映射的,也就是 LWP 都是由一個內(nèi)核線程支持。

LWP模式

輕量級進(jìn)程本質(zhì)還是進(jìn)程,只是跟普通進(jìn)程相比LWP跟其他進(jìn)程共享大部分邏輯地址空間跟系統(tǒng)資源,LWP輕量體現(xiàn)在它只有一個最小的執(zhí)行上下文和調(diào)度程序所需的統(tǒng)計(jì)信息。他是進(jìn)程的執(zhí)行部分,只帶有執(zhí)行相關(guān)的信息。

Linux特性

  1. Linux中沒有真正的線程,因?yàn)長inux并沒有為線程準(zhǔn)備特定的數(shù)據(jù)結(jié)構(gòu)。在內(nèi)核看來只有進(jìn)程而沒有線程,在調(diào)度時也是當(dāng)做進(jìn)程來調(diào)度。Linux所謂的線程其實(shí)是與其他進(jìn)程共享資源的進(jìn)程。但windows中確實(shí)有線程。

  2. Linux中沒有的線程,線程是由進(jìn)程來模擬實(shí)現(xiàn)的。

  3. 所以在Linux中在CPU角度看,進(jìn)程被稱作輕量級進(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ù)讀寫。以前有兩種解決方案:

  1. 多進(jìn)程:存在頻繁調(diào)度切換問題,同時還會存在每個進(jìn)程資源不共享的問題,需要額外引入進(jìn)程間通信機(jī)制來解決。

  2. 多線程:高并發(fā)場景的大量 IO 等待會導(dǎo)致多線程被頻繁掛起和切換,非常消耗系統(tǒng)資源,同時多線程訪問共享資源存在競爭問題。

此時協(xié)程出現(xiàn)了,協(xié)程 Coroutines 是一種比線程更加輕量級的微線程。類比一個進(jìn)程可以擁有多個線程,一個線程也可以擁有多個協(xié)程??梢院唵蔚陌褏f(xié)程理解成子程序調(diào)用,每個子程序都可以在一個單獨(dú)的協(xié)程內(nèi)執(zhí)行。

協(xié)程
協(xié)程運(yùn)行在線程之上,當(dāng)一個協(xié)程執(zhí)行完成后,可以選擇主動讓出,讓另一個協(xié)程運(yùn)行在當(dāng)前線程之上。 協(xié)程并沒有增加線程數(shù)量,只是在線程的基礎(chǔ)之上通過分時復(fù)用的方式運(yùn)行多個協(xié)程,而且協(xié)程的切換在用戶態(tài)完成,切換的代價比線程從用戶態(tài)到內(nèi)核態(tài)的代價小很多,一般在Python、Go中會涉及到協(xié)程的知識,尤其是現(xiàn)在高性能的腳本Go。
3.5.5.2 協(xié)程注意事項(xiàng)

協(xié)程運(yùn)行在線程之上,并且協(xié)程調(diào)用了一個阻塞IO操作,此時操作系統(tǒng)并不知道協(xié)程的存在,它只知道線程,因此在協(xié)程調(diào)用阻塞IO操作時,操作系統(tǒng)會讓線程進(jìn)入阻塞狀態(tài),當(dāng)前的協(xié)程和其它綁定在該線程之上的協(xié)程都會陷入阻塞而得不到調(diào)度。

因此在協(xié)程中不能調(diào)用導(dǎo)致線程阻塞的操作,比如打印、讀取文件、Socket接口等。協(xié)程只有和異步IO結(jié)合起來才能發(fā)揮最大的威力。并且協(xié)程只有在IO密集型的任務(wù)中才會發(fā)揮作用

3.6 進(jìn)程通信

進(jìn)程的用戶地址空間是相互獨(dú)立的,不可以互相訪問,但內(nèi)核空間是進(jìn)程都共享的,所以進(jìn)程之間要通信必須通過內(nèi)核。進(jìn)程間通信主要通過管道、消息隊(duì)列、共享內(nèi)存、信號量、信號、Socket編程。

3.6.1 管道

管道主要分為匿名管道跟命名管道兩種,可以實(shí)現(xiàn)數(shù)據(jù)的單向流動性。使用起來很簡單,但是管道這種通信方式效率低,不適合進(jìn)程間頻繁地交換數(shù)據(jù)。

匿名管道

  1. 日常Linux系統(tǒng)中的|就是匿名管道。指令的前一個輸入是后一個指令的輸出。

命名管道

  1. 一般通過mkfifo SoWhatPipe創(chuàng)建管道。通過echo "sw" > SoWhatPipe跟cat < SoWhatPipe實(shí)現(xiàn)輸入跟輸出。

匿名管道的實(shí)現(xiàn)依賴int pipe(int fd[2])函數(shù),其中fd[0]是讀取斷描述符,fd[1]是管道寫入端描述符。它的本質(zhì)就是在內(nèi)核中創(chuàng)建個屬于內(nèi)存的緩存,從一端輸入無格式數(shù)據(jù)一端輸出無格式數(shù)據(jù),需注意管道傳輸大小是有限的。

管道通信底層
匿名管道的通信范圍是存在父子關(guān)系的進(jìn)程。由于管道沒有實(shí)體,也就是沒有管道文件,不會涉及到文件系統(tǒng)。只能通過fork子進(jìn)程來復(fù)制父進(jìn)程 fd 文件描述符,父子進(jìn)程通過共用特殊的管道文件實(shí)現(xiàn)跨進(jìn)程通信,并且因?yàn)楣艿乐荒芤欢藢懭?,另一端讀出,所以通常父子進(jìn)程遵從如下要求:
  1. 父進(jìn)程關(guān)閉讀取的 fd[0],只保留寫入的 fd[1]。

  2. 子進(jìn)程關(guān)閉寫入的 fd[1],只保留讀取的 fd[0]。

shell管道通信
需注意Shell執(zhí)行匿名管道 a | b其實(shí)是通過Shell父進(jìn)程fork出了兩個子進(jìn)程來實(shí)現(xiàn)通信的,而ab之間是不存在父子進(jìn)程關(guān)系的。而命名管道是可以直接在不想關(guān)進(jìn)程間通信的,因?yàn)橛泄艿牢募?/span>
3.6.2 消息隊(duì)列
消息隊(duì)列消息隊(duì)列是保存在 內(nèi)核中的消息鏈表, 會涉及到用戶態(tài)跟內(nèi)核態(tài)到來回切換,雙方約定好消息體到數(shù)據(jù)結(jié)構(gòu),然后發(fā)送數(shù)據(jù)時將數(shù)據(jù)分成一個個獨(dú)立的數(shù)據(jù)單元消息體,需注意消息隊(duì)列及單個消息都有上限,日常我們到RabbitMQ、Redis 都涉及到消息隊(duì)列。
3.6.3 共享內(nèi)存
共享空間
現(xiàn)代操作系統(tǒng)對內(nèi)存管理采用的是虛擬內(nèi)存技術(shù),也就是每個進(jìn)程都有自己獨(dú)立的虛擬內(nèi)存空間,不同進(jìn)程的虛擬內(nèi)存映射到不同的物理內(nèi)存中。所以,即使進(jìn)程A和進(jìn)程B虛擬地址是一樣的,真正訪問的也是不同的物理內(nèi)存地址,該模式不涉及到用戶態(tài)跟內(nèi)核態(tài)來回切換,JVM 就是用的共享內(nèi)存模式。并且并發(fā)編程也是個難點(diǎn)。
3.6.4 信號量

既然共享內(nèi)存容易造成數(shù)據(jù)紊亂,那為了簡單的實(shí)現(xiàn)共享數(shù)據(jù)在任意時刻只能被一個進(jìn)程訪問,此時需要信號量。

信號量其實(shí)是一個整型的計(jì)數(shù)器,主要用于實(shí)現(xiàn)進(jìn)程間的互斥與同步,而不是用于緩存進(jìn)程間通信的數(shù)據(jù)。

信號量表示資源的數(shù)量,核心點(diǎn)在于原子性的控制一個數(shù)據(jù)的值,控制信號量的方式有PV兩種原子操作

  1. P 操作會把信號量減去 -1,相減后如果信號量 < 0,則表明資源已被占用,進(jìn)程需阻塞等待。相減后如果信號量 >= 0,則表明還有資源可使用,進(jìn)程可正常繼續(xù)執(zhí)行。

  2. V 操作會把信號量加上 1,相加后如果信號量 <= 0,則表明當(dāng)前有阻塞中的進(jìn)程,于是會將該進(jìn)程喚醒運(yùn)行。相加后如果信號量 > 0,則表明當(dāng)前沒有阻塞中的進(jìn)程。

3.6.5 信號

對于異常狀態(tài)下進(jìn)程工作模式需要用到信號工作方式來通知進(jìn)程。比如Linux系統(tǒng)為了響應(yīng)各種事件提供了很多異常信號kill -l,信號是進(jìn)程間通信機(jī)制中唯一的異步通信機(jī)制,可以在任何時候發(fā)送信號給某一進(jìn)程。比如:

  1. kill -9 1412 ,表示給 PID 為 1412 的進(jìn)程發(fā)送 SIGKILL 信號,用來立即結(jié)束該進(jìn)程。

  2. 鍵盤 Ctrl+C 產(chǎn)生 SIGINT 信號,表示終止該進(jìn)程。

  3. 鍵盤 Ctrl+Z 產(chǎn)生 SIGTSTP 信號,表示停止該進(jìn)程,但還未結(jié)束。

有信號發(fā)生時,進(jìn)程一般有三種方式響應(yīng)信號:

  1. 執(zhí)行默認(rèn)操作:Linux操作系統(tǒng)為眾多信號配備了專門的處理操作。

  2. 捕捉信號:給捕捉到的信號配備專門的信號處理函數(shù)。

  3. 忽略信號:專門用來忽略某些信號,但 SIGKILL 和 SEGSTOP是無法被忽略的,為了能在任何時候結(jié)束或停止某個進(jìn)程而存在。

3.6.6 Socket編程

前面提到的管道、消息隊(duì)列、共享內(nè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類型,TCP或UDP類型。比如TCP協(xié)議通信的 socket 編程模型如下:

Socket編程
  1. 服務(wù)端和客戶端初始化socket,得到文件描述符。

  2. 服務(wù)端調(diào)用bind,將綁定在 IP 地址和端口。

  3. 服務(wù)端調(diào)用listen,進(jìn)行監(jiān)聽。

  4. 服務(wù)端調(diào)用accept,等待客戶端連接。

  5. 客戶端調(diào)用connect,向服務(wù)器端的地址和端口發(fā)起連接請求。

  6. 服務(wù)端accept返回用于傳輸?shù)膕ocket的文件描述符。

  7. 客戶端調(diào)用write寫入數(shù)據(jù),服務(wù)端調(diào)用read讀取數(shù)據(jù)。

  8. 客戶端斷開連接時,會調(diào)用close,那么服務(wù)端read讀取數(shù)據(jù)的時候,就會讀取到了EOF,待處理完數(shù)據(jù)后,服務(wù)端調(diào)用 close,表示連接關(guān)閉。

  9. 服務(wù)端調(diào)用accept時,連接成功會返回一個已完成連接的socket,后續(xù)用來傳輸數(shù)據(jù)。服務(wù)端有倆socket,一個叫作監(jiān)聽socket,一個叫作已完成連接socket。

  10. 成功連接建立之后雙方開始通過 read 和 write 函數(shù)來讀寫數(shù)據(jù)。

UDP傳輸
UDP比較簡單,屬于類似廣播性質(zhì)的傳輸,不需要維護(hù)連接。但也需要 bind,每次通信時調(diào)用 sendto 和 recvfrom 都要傳入目標(biāo)主機(jī)的 IP 地址和端口。

3.7 多線程編程

既然多進(jìn)程開銷過大,那平常我們經(jīng)常使用到的就是多線程編程了。期間可能涉及到內(nèi)存模型、JMM、Volatile、臨界區(qū)等等。這些在Java并發(fā)編程專欄有講。

4 文件管理

4.1 VFS 虛擬文件系統(tǒng)

文件系統(tǒng)在操作系統(tǒng)中主要負(fù)責(zé)將文件數(shù)據(jù)信息存儲到磁盤中,起到持久化文件的作用。文件系統(tǒng)的基本組成單元就是文件,文件組成方式不同就會形成不同的文件系統(tǒng)。

文件系統(tǒng)有很多種而不同的文件系統(tǒng)應(yīng)用到操作系統(tǒng)后需要提供統(tǒng)一的對外接口,此時用到了一個設(shè)計(jì)理念沒有什么是加一層解決不了的,在用戶層跟不同的文件系統(tǒng)之間加入一個虛擬文件系統(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)一般有如下三種:
  1. 磁盤文件系統(tǒng):就是我們常見的EXT 2/3/4系列。

  2. 內(nèi)存文件系統(tǒng):數(shù)據(jù)沒存儲到磁盤,占用內(nèi)存數(shù)據(jù),比如/sys、/proc。進(jìn)程中的一些數(shù)據(jù)映射到/proc中了。

  3. 網(wǎng)絡(luò)文件系統(tǒng):常見的網(wǎng)盤掛載NFS等,通過訪問其他主機(jī)數(shù)據(jù)實(shí)現(xiàn)。

4.2 文件組成

以Linux系統(tǒng)為例,在Linux系統(tǒng)中一切皆文件,Linux文件系統(tǒng)會為每個文件分配索引節(jié)點(diǎn) inode跟目錄項(xiàng)directory entry來記錄文件內(nèi)容跟目錄層次結(jié)構(gòu)。

4.2.1 inode

要理解inode要從文件儲存說起。文件存儲在硬盤上,硬盤的最小存儲單位叫做扇區(qū)。每個扇區(qū)儲存512字節(jié)。操作系統(tǒng)讀取硬盤的時候,不會一個個扇區(qū)的讀取,這樣效率太低,一般一次性連續(xù)讀取8個扇區(qū)(4KB)來當(dāng)做一塊,這種由多個扇區(qū)組成的,是文件存取的最小單位。

文件數(shù)據(jù)都儲存在塊中,我們還必須找到一個地方儲存文件的元信息,比如inode編號、文件大小、創(chuàng)建時間、修改時間、磁盤位置、訪問權(quán)限等。幾乎除了文件名以為的所有文件元數(shù)據(jù)信息都存儲在一個叫叫索引節(jié)點(diǎn)inode的地方??赏ㄟ^stat 文件名查看 inode 信息

每個inode都有一個號碼,操作系統(tǒng)用inode號碼來識別不同的文件。Unix/Linux系統(tǒng)內(nèi)部不使用文件名,而使用inode號碼來識別文件,用戶可通過ls -i查看每個文件對應(yīng)編號。對于系統(tǒng)來說文件名只是inode號碼便于識別的別稱或者綽號。特殊名字的文件不好刪除時可以嘗試用inode號刪除,移動跟重命名不會導(dǎo)致文件inode變化,當(dāng)用戶嘗試根據(jù)文件名打開文件時,實(shí)際上系統(tǒng)內(nèi)部將這個過程分成三步:

  1. 系統(tǒng)找到這個文件名對應(yīng)的inode號碼。

  2. 通過inode號碼,獲取inode信息,進(jìn)行權(quán)限驗(yàn)證等操作。

  3. 根據(jù)inode信息,找到文件數(shù)據(jù)所在的block,讀出數(shù)據(jù)。

需注意 inode也會消耗硬盤空間,硬盤格式化后會被分成超級塊、索引節(jié)點(diǎn)區(qū)數(shù)據(jù)塊區(qū)三個區(qū)域:

  1. 超級塊區(qū):用來存儲文件系統(tǒng)的詳細(xì)信息,比如塊大小,塊個數(shù)等信息。一般文件系統(tǒng)掛載后就會將數(shù)據(jù)信息同步到內(nèi)存。

  2. 索引節(jié)點(diǎn)區(qū):用來存儲索引節(jié)點(diǎn) inode  table。每個inode一般為128字節(jié)或256字節(jié),一般每1KB或2KB數(shù)據(jù)就需設(shè)置一個inode。一般為了加速查詢會把索引數(shù)據(jù)緩存到內(nèi)存。

  3. 數(shù)據(jù)塊區(qū):真正存儲磁盤數(shù)據(jù)的地方。

    df -i # 查看每個硬盤分區(qū)的inode總數(shù)和已經(jīng)使用的數(shù)量 sudo dumpe2fs -h /dev/hda | grep "Inode size" # 查看每個inode節(jié)點(diǎn)的大小 
4.2.2 目錄

Unix/Linux系統(tǒng)中目錄directory也是一種文件,打開目錄實(shí)際上就是打開目錄文件。目錄文件內(nèi)容就是一系列目錄項(xiàng)的列,目錄項(xiàng)的內(nèi)容包含文件的名字、文件類型、索引節(jié)點(diǎn)指針以及與其他目錄項(xiàng)的層級關(guān)系。

為避免頻繁讀取磁盤里的目錄文件,內(nèi)核會把已經(jīng)讀過的目錄文件用目錄項(xiàng)這個數(shù)據(jù)結(jié)構(gòu)緩存在內(nèi)存,方便用戶下次讀取目錄信息,目錄項(xiàng)可包含目錄或文件,不要驚訝于可以保存目錄,目錄格式的目錄項(xiàng)里面保存的是目錄里面一項(xiàng)一項(xiàng)的文件信息。

4.2.3 軟連接跟硬鏈接
軟連接跟硬鏈接
硬鏈接:老文件A被創(chuàng)建若干個硬鏈接B、C后。A、B、C三個文件的inode是相同的,所以不能跨文件系統(tǒng)。同時只有ABC全部刪除,系統(tǒng)才會刪除源文件。
軟鏈接:相當(dāng)于基于老文件A新建了個文件B,該文件B有新的inode,不過文件B內(nèi)容是老文件A的路徑。所以軟鏈接可以跨文件系統(tǒng)。當(dāng)老文件A刪除后,文件B仍然存在,不過找不到指定文件了。
[sowhat@localhost ~]$ ln [選項(xiàng)] 源文件 目標(biāo)文件
選項(xiàng):
-s:建立軟鏈接文件。如果不加 "-s" 選項(xiàng),則建立硬鏈接文件;
-f:強(qiáng)制。如果目標(biāo)文件已經(jīng)存在,則刪除目標(biāo)文件后再建立鏈接文件;

4.3 文件存儲

說文件存儲前需了解文件系統(tǒng)操作基本單位是數(shù)據(jù)塊,而平常用戶操作字節(jié)到數(shù)據(jù)塊之間是需要轉(zhuǎn)換的,當(dāng)然這些文件系統(tǒng)都幫我們對接好了。接下來看文件系統(tǒng)是如何按照數(shù)據(jù)塊, 文件在磁盤中存儲時候主要分為連續(xù)空間存儲跟非連續(xù)空間存儲

4.3.1 連續(xù)空間存儲
  1. 實(shí)現(xiàn):連續(xù)空間存儲的意思就跟數(shù)組存儲一樣,找個連續(xù)的空間一次性把數(shù)據(jù)存儲進(jìn)去,文件頭存儲起始位置跟數(shù)據(jù)長度即可。

  2. 優(yōu)勢:讀寫效率高,磁盤尋址一次即可。

  3. 劣勢:容易產(chǎn)生空間碎片,并且文件擴(kuò)容不方便。

    連續(xù)存儲
4.3.2 非連續(xù)空間存儲之鏈表

隱式鏈表

  1. 實(shí)現(xiàn):文件頭包含StartBlock、EndBlock。每個BLock有隱藏的next指針,跟單向鏈表一樣。

  2. 缺點(diǎn):只能通過鏈?zhǔn)讲粩嗤虏檎覕?shù)據(jù),不支持快速直接訪問。

隱式鏈表

顯式鏈表

  1. 實(shí)現(xiàn):把每個Block中的next指針存儲到內(nèi)存文件分配表中,通過遍歷數(shù)組方式實(shí)現(xiàn)拿到全部數(shù)據(jù)。

  2. 缺點(diǎn):前面說1KB就有個inode指針,如果磁盤數(shù)據(jù)很大那就需要很大的文件分配表來存儲映射關(guān)系了,

    顯示鏈表
4.3.3 非連續(xù)空間存儲之索引
  1. 實(shí)現(xiàn):整個文件類型一本新華字典,真實(shí)的數(shù)據(jù)塊在詞典實(shí)際位置存儲著,但文件所需數(shù)據(jù)塊的索引位置會被匯總起來形成目錄索引放在字典前頭。

  2. 優(yōu)勢:不會產(chǎn)生碎片,文件可動態(tài)擴(kuò)容,并且支持順序跟隨機(jī)讀寫。

  3. 劣勢:可能一個小文件都要占用一個目錄索引,文件過大導(dǎo)致索引指針一個容不下,可能還需要有多級索引或索引+鏈表模式。

索引存儲
這些存儲方式各有利弊,所以操作系統(tǒng)才存儲的時候一般是根據(jù)文件的大小進(jìn)行動態(tài)的變化存儲方式的,跟STL中的快排底層 = 快排 + 插入排序 + 堆排 一樣的道理。
4.3.4 空閑空間管理

為了避免用戶存儲數(shù)據(jù)時候遍歷全部磁盤空間來尋找可以數(shù)據(jù)塊,一般有如下幾種記錄方法。

  1. 空閑表:動態(tài)的維護(hù)一個空閑數(shù)據(jù)塊列表,每行存儲空閑塊的開始位置跟空閑長度。適合少量有少量空閑數(shù)據(jù)塊時。

    空閑表
  2. 空閑鏈表:將空閑的數(shù)據(jù)庫用next指針串聯(lián)起來,缺點(diǎn)是不能隨機(jī)訪問。

    空閑鏈表
  3. 位圖法:利用Bit的 01 表示數(shù)據(jù)塊可用跟不可用,簡單方便,inode跟空閑數(shù)據(jù)庫都用的此方法

    位圖法

5  輸入輸出管理

5.1 設(shè)備控制器跟驅(qū)動程序

5.1.1 設(shè)備控制器
設(shè)備控制器
操作系統(tǒng)為統(tǒng)一管理眾多的設(shè)備并且屏蔽設(shè)備之間的差異,給每個設(shè)備都安裝了個小CPU叫 設(shè)備控制器。每個設(shè)備控制器都知道自己對應(yīng)外設(shè)的功能跟用法,并且每個 設(shè)備控制器都有獨(dú)有的寄存器用來跟CPU通信。
  1. 讀設(shè)備寄存器值了解設(shè)備狀態(tài),是否可以接收新指令。

  2. 操作系統(tǒng)給設(shè)備寄存器寫入一些指令可以實(shí)現(xiàn)發(fā)送數(shù)據(jù)、接收數(shù)據(jù)等等操作。

控制器一般分為數(shù)據(jù)寄存器、命令寄存器跟狀態(tài)寄存器,CPU 通過讀、寫設(shè)備控制器中的寄存器來便捷的控制設(shè)備:

  1. 數(shù)據(jù)寄存器:CPU 向 I/O 設(shè)備寫入需要傳輸?shù)臄?shù)據(jù),比如打印what,CPU 就要先發(fā)送一個w字符給到對應(yīng)的 I/O 設(shè)備。

  2. 命令寄存器:CPU 發(fā)送命令來告訴 I/O 設(shè)備要進(jìn)行輸入/輸出操作,于是就會交給 I/O 設(shè)備去工作,任務(wù)完成后,會把狀態(tài)寄存器里面的狀態(tài)標(biāo)記為完成。

  3. 狀態(tài)寄存器:用來告訴 CPU 現(xiàn)在已經(jīng)在工作或工作已經(jīng)完成,只有狀態(tài)寄存標(biāo)記成已完成,CPU 才能發(fā)送下一個字符和命令。

同時輸入輸出設(shè)備可分為塊設(shè)備跟字符設(shè)備。

  1. 塊設(shè)備:用來把數(shù)據(jù)存儲在固定大小的塊中,每個塊有自己的地址,硬盤、U盤等是常見的塊設(shè)備。塊設(shè)備一般數(shù)據(jù)傳輸較大為避免頻繁IO,控制器中有個可讀寫等數(shù)據(jù)緩沖區(qū)。Linux操作系統(tǒng)為屏蔽不同塊設(shè)備帶來的差異引入了通用塊層通用塊層是處于文件系統(tǒng)和磁盤驅(qū)動中間的一個塊設(shè)備抽象層,主要提供如下倆功能:

  1. 向上為文件系統(tǒng)和應(yīng)用程序,提供訪問塊設(shè)備的標(biāo)準(zhǔn)接口,向下把各種不同的磁盤設(shè)備抽象為統(tǒng)一的塊設(shè)備,并在內(nèi)核層面提供一個框架來管理這些設(shè)備的驅(qū)動程序。

  2. 通用層還會給文件系統(tǒng)和應(yīng)用程序發(fā)來的 I/O進(jìn)行調(diào)度,主要目的是為了提高磁盤讀寫的效率。

  1. 字符設(shè)備:以字符為單位發(fā)送或接收一個字符流,字符設(shè)備是不可尋址的,也沒有任何尋道操作,鼠標(biāo)是常見的字符設(shè)備。

CPU一般通過IO端口內(nèi)存映射IO來跟設(shè)備的控制寄存器和數(shù)據(jù)緩沖區(qū)進(jìn)行通信

  1. IO端口:每個控制寄存器被分配一個 I/O 端口,可以通過特殊的匯編指令操作這些寄存器,比如 in/out 類似的指令。

  2. 內(nèi)存映射IO:將所有控制寄存器映射到內(nèi)存空間中,這樣就可以像讀寫內(nèi)存一樣讀寫數(shù)據(jù)緩沖區(qū)。

5.1.2 驅(qū)動接口
驅(qū)動程序

設(shè)備控制器屏蔽了設(shè)備細(xì)節(jié),但每種設(shè)備的控制器的寄存器、緩沖區(qū)等使用模式都是不同的,它屬于硬件。在操作系統(tǒng)圖范疇內(nèi)為了屏蔽設(shè)備控制器的差異,引入了設(shè)備驅(qū)動程序,不同設(shè)備到驅(qū)動程序會提供統(tǒng)一接口給操作系統(tǒng)來調(diào)用,這樣操作系統(tǒng)內(nèi)核會像調(diào)用本地代碼一樣使用設(shè)備驅(qū)動程序接口。

設(shè)備發(fā)出IO請求就是在設(shè)備驅(qū)動程序中來響應(yīng)到,它會根據(jù)中斷類型調(diào)用響應(yīng)到中斷處理程序進(jìn)行處理。

中斷請求流程

5.2 IO 控制

CPU發(fā)送指令讓那個設(shè)備控制器去讀寫數(shù)據(jù),完畢后如何通知CPU呢?

5.2.1 輪詢模式

控制器中有個狀態(tài)寄存器,CPU不斷詢查看寄存器狀態(tài),該模式會傻瓜式的一直占用CPU。

輪詢模式
5.2.2 IO 中斷請求
中斷模式
控制器有個中斷控制器,當(dāng)設(shè)備完成任務(wù)后觸發(fā)中斷到中斷控制器,中斷控制器就通知 CPU來處理中斷請求。中斷有兩種,一種是 軟中斷,比如代碼調(diào)用 INT 指令觸發(fā)。一種是 硬件中斷,硬件通過中斷控制器觸發(fā)的。但中斷方式對于頻繁讀寫磁盤數(shù)據(jù)的操作就不太友好了,會頻繁打斷CPU。

這里說下磁盤高速緩存 PageCache,它是用來緩存最近被CPU訪問的數(shù)據(jù)到內(nèi)存中,并且還具有預(yù)讀功能,可能你讀前16KB數(shù)據(jù),已經(jīng)把后16KB數(shù)據(jù)給你緩存好了。

  1. pagecache : 頁緩存,當(dāng)進(jìn)程需讀取磁盤文件時,linux先分配一些內(nèi)存,將數(shù)據(jù)從磁盤讀區(qū)到內(nèi)存中,然后再將數(shù)據(jù)傳給進(jìn)程。當(dāng)進(jìn)程需寫數(shù)據(jù)到磁盤時,linux先分配內(nèi)存接收用戶數(shù)據(jù),然后再將數(shù)據(jù)從內(nèi)存寫到磁盤。同時pagecache由于大小受限,所以一般只緩存最近被訪問的數(shù)據(jù),數(shù)據(jù)不足時還需訪問磁盤。

5.2.3 DMA 模式

Direct Memory Access直接內(nèi)存訪問,在硬件DMA控制器的支持下,在進(jìn)行 I/O 設(shè)備和內(nèi)存的數(shù)據(jù)傳輸?shù)臅r候,數(shù)據(jù)搬運(yùn)的工作全部交給 DMA 控制器,而 CPU 不再參與任何與數(shù)據(jù)搬運(yùn)相關(guān)的事情,讓CPU 去處理別的事。

DMA模式可以發(fā)現(xiàn)整個數(shù)據(jù)傳輸過程中CPU是不會直接參與數(shù)據(jù)搬運(yùn)工作,由DMA來直接負(fù)責(zé)數(shù)據(jù)讀取工作,現(xiàn)如今每個IO設(shè)備一般都自帶DMA控制器。讀數(shù)據(jù)時候僅僅在傳送開始跟結(jié)束時需要CPU干預(yù)。
5.2.4 Zero Copy

Zero Copy 全程不會通過 CPU 來搬運(yùn)數(shù)據(jù),所有的數(shù)據(jù)都是通過 DMA 來進(jìn)行傳輸?shù)?,中間只需要經(jīng)過2次上下文切換跟2次DMA數(shù)據(jù)拷貝,相比最原始讀寫方式至少速度翻倍。其實(shí)在Kafka中已經(jīng)講過Zero Copy了。

5.2.4.1 老版本讀寫

老版本的簡單讀寫操作中間不對數(shù)據(jù)做任何操作。期間會發(fā)生4次用戶態(tài)跟內(nèi)核態(tài)的切換。2次DMA數(shù)據(jù)拷貝,2次CPU數(shù)據(jù)拷貝

老式讀寫
提速方法就是需減少用戶態(tài)與內(nèi)核態(tài)的上下文切換和內(nèi)存拷貝的次數(shù)。數(shù)據(jù)傳輸時從內(nèi)核的讀緩沖區(qū)拷貝到用戶的緩沖區(qū),再從用戶緩沖區(qū)拷貝到 socket 緩沖區(qū)的這個過程是沒有必要的。接下來

接下來按照三個版本說下Zero Copy 發(fā)展史。

5.2.4.2  mmap 跟 write
mmap + write
思路就是用 mmap替代read函數(shù),mmap調(diào)用時會 直接把內(nèi)核緩沖區(qū)里的數(shù)據(jù)映射到用戶空間,此時減少了一次數(shù)據(jù)拷貝,但仍然需要通過 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ù)長度 ssize_t:實(shí)際上復(fù)制數(shù)據(jù)的長度

可以發(fā)現(xiàn)一個 sendfile = read + write,避免了2次用戶態(tài)跟內(nèi)核態(tà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ù),可以減少通過 CPU 把內(nèi)核緩沖區(qū)里的數(shù)據(jù)拷貝到 socket 緩沖區(qū)的過程。

$ ethtool -k eth0 | grep scatter-gather
scatter-gather: on

SG-DMA 技術(shù)可以直接將內(nèi)核緩存中的數(shù)據(jù)拷貝到網(wǎng)卡的緩沖區(qū)里,此過程不需要將數(shù)據(jù)從操作系統(tǒng)內(nèi)核緩沖區(qū)拷貝到 socket 緩沖區(qū)中,這樣就減少了一次數(shù)據(jù)拷貝。

ZeroCopy
5.2.4.5 文件傳輸規(guī)則

不要以為會了Zero Copy后,無論大小文件都用Zero Copy。實(shí)際工作中一般小文件采用Zero Copy技術(shù),而大文件會用異步IO。至于為啥,且看如下分析:

前面說的數(shù)據(jù)從磁盤讀到內(nèi)核緩沖區(qū)就是讀到PageCache中,PageCache具有緩存跟預(yù)讀功能。但當(dāng)傳輸超大文件時PageCache會不失效,因?yàn)榇笪募焖僬紳MPageCache區(qū),但這些文件又只是一次訪問,會造成其他熱點(diǎn)小文件無法使用PageCache,所以索性不用PageCache,使用異步IO的了。至于異步IO是啥呢?下文在說。

5.3 IO分層

IO分層
Linux 存儲系統(tǒng)的 I/O 由上到下可以分為 文件系統(tǒng)層通用塊層、 設(shè)備層
  1. 文件系統(tǒng)層向上為應(yīng)用程序統(tǒng)一提供了標(biāo)準(zhǔn)的文件訪問接口,向下會通過通用塊層來存儲和管理磁盤數(shù)據(jù)。

  2. 通用塊層包括塊設(shè)備的 I/O 隊(duì)列和 I/O 調(diào)度器,通過IO調(diào)度器處理IO請求。

  3. 設(shè)備層包括硬件設(shè)備、設(shè)備控制器和驅(qū)動程序,負(fù)責(zé)最終物理設(shè)備的 I/O 操作。

Linux系統(tǒng)中的IO讀取提速

  1. 為提高文件訪問效率會使用頁緩存、索引節(jié)點(diǎn)緩存、目錄項(xiàng)緩存等多種緩存機(jī)制,目的是為了減少對塊設(shè)備的直接調(diào)用。

  2. 為了提高塊設(shè)備的訪問效率, 會使用緩沖區(qū),來緩存塊設(shè)備的數(shù)據(jù)。

6 End

小3W字,終于吹逼完了。希望讀完可以讓你對操作系統(tǒng)有個大概的印象,你在用Window,卻不知經(jīng)過30年的基礎(chǔ)沉淀,Windows 的完整源代碼樹的大小超過 0.5 TB,涉及超過56萬個文件夾,400 多萬個文件,總規(guī)模超十億行。再加上各個功能之間需要兼容性,可維護(hù)性,可管理性等這些隨著代碼的越來越多可推敲,最終產(chǎn)生了這樣的一個藝術(shù)品!


免責(zé)聲明:本文內(nèi)容由21ic獲得授權(quán)后發(fā)布,版權(quán)歸原作者所有,本平臺僅提供信息存儲服務(wù)。文章僅代表作者個人觀點(diǎn),不代表本平臺立場,如有問題,請聯(lián)系我們,謝謝!

本站聲明: 本文章由作者或相關(guān)機(jī)構(gòu)授權(quán)發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點(diǎn),本站亦不保證或承諾內(nèi)容真實(shí)性等。需要轉(zhuǎn)載請聯(lián)系該專欄作者,如若文章內(nèi)容侵犯您的權(quán)益,請及時聯(lián)系本站刪除。
換一批
延伸閱讀

9月2日消息,不造車的華為或?qū)⒋呱龈蟮莫?dú)角獸公司,隨著阿維塔和賽力斯的入局,華為引望愈發(fā)顯得引人矚目。

關(guān)鍵字: 阿維塔 塞力斯 華為

加利福尼亞州圣克拉拉縣2024年8月30日 /美通社/ -- 數(shù)字化轉(zhuǎn)型技術(shù)解決方案公司Trianz今天宣布,該公司與Amazon Web Services (AWS)簽訂了...

關(guān)鍵字: AWS AN BSP 數(shù)字化

倫敦2024年8月29日 /美通社/ -- 英國汽車技術(shù)公司SODA.Auto推出其旗艦產(chǎn)品SODA V,這是全球首款涵蓋汽車工程師從創(chuàng)意到認(rèn)證的所有需求的工具,可用于創(chuàng)建軟件定義汽車。 SODA V工具的開發(fā)耗時1.5...

關(guān)鍵字: 汽車 人工智能 智能驅(qū)動 BSP

北京2024年8月28日 /美通社/ -- 越來越多用戶希望企業(yè)業(yè)務(wù)能7×24不間斷運(yùn)行,同時企業(yè)卻面臨越來越多業(yè)務(wù)中斷的風(fēng)險,如企業(yè)系統(tǒng)復(fù)雜性的增加,頻繁的功能更新和發(fā)布等。如何確保業(yè)務(wù)連續(xù)性,提升韌性,成...

關(guān)鍵字: 亞馬遜 解密 控制平面 BSP

8月30日消息,據(jù)媒體報(bào)道,騰訊和網(wǎng)易近期正在縮減他們對日本游戲市場的投資。

關(guān)鍵字: 騰訊 編碼器 CPU

8月28日消息,今天上午,2024中國國際大數(shù)據(jù)產(chǎn)業(yè)博覽會開幕式在貴陽舉行,華為董事、質(zhì)量流程IT總裁陶景文發(fā)表了演講。

關(guān)鍵字: 華為 12nm EDA 半導(dǎo)體

8月28日消息,在2024中國國際大數(shù)據(jù)產(chǎn)業(yè)博覽會上,華為常務(wù)董事、華為云CEO張平安發(fā)表演講稱,數(shù)字世界的話語權(quán)最終是由生態(tài)的繁榮決定的。

關(guān)鍵字: 華為 12nm 手機(jī) 衛(wèi)星通信

要點(diǎn): 有效應(yīng)對環(huán)境變化,經(jīng)營業(yè)績穩(wěn)中有升 落實(shí)提質(zhì)增效舉措,毛利潤率延續(xù)升勢 戰(zhàn)略布局成效顯著,戰(zhàn)新業(yè)務(wù)引領(lǐng)增長 以科技創(chuàng)新為引領(lǐng),提升企業(yè)核心競爭力 堅(jiān)持高質(zhì)量發(fā)展策略,塑強(qiáng)核心競爭優(yōu)勢...

關(guān)鍵字: 通信 BSP 電信運(yùn)營商 數(shù)字經(jīng)濟(jì)

北京2024年8月27日 /美通社/ -- 8月21日,由中央廣播電視總臺與中國電影電視技術(shù)學(xué)會聯(lián)合牽頭組建的NVI技術(shù)創(chuàng)新聯(lián)盟在BIRTV2024超高清全產(chǎn)業(yè)鏈發(fā)展研討會上宣布正式成立。 活動現(xiàn)場 NVI技術(shù)創(chuàng)新聯(lián)...

關(guān)鍵字: VI 傳輸協(xié)議 音頻 BSP

北京2024年8月27日 /美通社/ -- 在8月23日舉辦的2024年長三角生態(tài)綠色一體化發(fā)展示范區(qū)聯(lián)合招商會上,軟通動力信息技術(shù)(集團(tuán))股份有限公司(以下簡稱"軟通動力")與長三角投資(上海)有限...

關(guān)鍵字: BSP 信息技術(shù)
關(guān)閉
關(guān)閉