課程目錄

1 馮諾伊曼體系

1.1 馮諾伊曼體系簡介

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

CPU位寬:32位CPU一次可操作計算4個字節(jié),64位CPU一次可操作計算8個字節(jié),這個是硬件級別的。平常我們說的32位或64位操作系統(tǒng)指的是軟件級別的,指的是程序中指令多少位。

線路位寬:CPU操作指令數(shù)據(jù)通過高低電壓變化進行數(shù)據(jù)傳輸,傳輸時候可以串行傳輸,也可以并行傳輸,多少個并行等于多少個位寬。

1.2 CPU 簡介

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

CPU

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

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

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 指令緩存

L2緩存:每個CPU核心都有,訪問空間大小在128KB~2MB,訪問速度一般是10~20個CPU時鐘周期。

cat /sys/devices/system/cpu/cpu0/cache/index2/size # L2 緩存容量大小

L3緩存:多個CPU核心共用,訪問空間大小在2MB~64MB,訪問速度一般是20~60個CPU時鐘周期。

cat /sys/devices/system/cpu/cpu0/cache/index3/size # L3 緩存容量大小

內(nèi)存:多個CPU共用,現(xiàn)在一般是4G~512G,訪問速度一般是200~300個CPU時鐘周期。

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

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

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

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

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

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

1.3 CPU 訪問方式

CPU訪問方式

內(nèi)存數(shù)據(jù)映射到CPU Cache 時通過公式Block N % CacheLineMax決定內(nèi)存Block數(shù)據(jù)放到那個CPU Cache Line 里。CPU Cache 主要有4部分組成。

Cache Line Index :CPU緩存讀取數(shù)據(jù)時不是按照字節(jié)來讀取的,而是按照CacheLine方式存儲跟讀取數(shù)據(jù)的。

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

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

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

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

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

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

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% 左右,導致的結(jié)果就是CPU跟內(nèi)存訪問性能差距逐步變大,那如何盡可能提高CPU緩存命中率呢?

1. 數(shù)據(jù)緩存:遍歷數(shù)據(jù)時候按照內(nèi)存布局順序訪問,因為CPU Cache是根據(jù)Cache Line批量操作數(shù)據(jù)的,所以你順序讀取數(shù)據(jù)會提速,道理跟磁盤順序?qū)懸粯印?/p>

指令緩存:盡可能的提供有規(guī)律的條件分支語句,讓 CPU 的分支預測器發(fā)揮作用,進一步提高執(zhí)行的效率,因為CPU是自帶分支預測器,自動提前將可能需要的指令放到指令緩存區(qū)。

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

1.5  操作系統(tǒng)

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

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

2 內(nèi)存管理

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

虛擬內(nèi)存

2.1  MMU

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

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

內(nèi)存分段管理

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

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

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

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

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

swap空間利用

2.2.2 內(nèi)存分頁

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

采用內(nèi)存分頁時內(nèi)存的釋放跟使用都是以頁為單位的,也就不會產(chǎn)生內(nèi)存碎片了。當空間還不夠時根據(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)境下進程可操作的虛擬地址是4GB,假設(shè)一個虛擬頁大小為4KB,那需要4GB/4KB = 2^20 個頁信息。一行頁表記錄為4字節(jié),2^20等價于4MB頁表存儲信息。這只是一個進程需要的,如果10個、100個、1000個呢?僅僅是頁表存儲都占據(jù)超大內(nèi)存了。

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

32位系統(tǒng)二級分頁

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

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

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

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

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

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

Page Global Directory 全局頁目錄項

Page Upper Directory 上層頁目錄項

Page Middle Directory 中間頁目錄項

Page Table Entry 頁表項

Offset 偏移量。

64位尋址

2.2.2 TLB

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

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,那么只能費力的通過頁表來查找了。日常CPU讀取一個數(shù)據(jù)的流程如下:

CPU讀取數(shù)據(jù)流程圖

當進程地址空間進行了上下文切換時,比如現(xiàn)在是進程1運行,TLB中放的是進程1的相關(guān)數(shù)據(jù)的地址,突然切換到進程2,TLB中原有的數(shù)據(jù)不是進程2相關(guān)的,此時TLB刷新數(shù)據(jù)有兩種辦法。

全部刷新:很簡單,但花銷大,很多不必刷新的數(shù)據(jù)也進行刷新,增加了無畏的花銷。

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

2.2.3 段頁式管理

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

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

然后再把每一個段進行分頁操作,也就是前面說的分頁機制。

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

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

段頁式管理

同時我們經(jīng)常看到兩個專業(yè)詞邏輯地址跟線性地址。

邏輯地址:指的是沒被段式內(nèi)存管理映射的地址。

線性地址:通過段式內(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)核空間。

內(nèi)核空間:操作系統(tǒng)內(nèi)核訪問的區(qū)域,獨立于普通的應用程序,是受保護的內(nèi)存空間。內(nèi)核態(tài)下CPU可執(zhí)行任何指令,可自由訪問任何有效地址。

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

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

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

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

Linux系統(tǒng)結(jié)構(gòu)

可以看到每個應用程序都各自有獨立的虛擬內(nèi)存地址,但每個虛擬內(nèi)存中對應的內(nèi)核地址其實是相同的一大塊,這樣當進程切換到內(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 進程管理

3.1 進程基礎(chǔ)知識

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

狀態(tài)變化圖

重點說下掛起跟阻塞:

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

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

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

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

3.2 PCB

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

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

作為獨立運行基本單位的標志

實現(xiàn)間斷性的運行方式

提供進程管理所需要的信息

提供進程調(diào)度所需要的信息

實現(xiàn)與其他進程的同步與通信

3.2.1 PCB 信息

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

進程標識符:用于唯一地標識一個進程,一個進程通常有兩種標識符:

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

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

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

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

進程調(diào)度信息

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

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

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

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

資源清單

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

3.2.2 PCB 組織方式

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

線下數(shù)組

線性方式:

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

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

索引方式

索引方式:

將同一狀態(tài)的進程組織在一個索引表中,索引表項指向相應的 PCB,不同狀態(tài)對應不同的索引表。

鏈表方式

鏈接方式:

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

因為進程創(chuàng)建、銷毀、調(diào)度頻繁,所以一般采用此模式。

3.3 進程控制

進程控制是進程管理最基本的功能,主要包括創(chuàng)建新進程,終止已完成的進程,將發(fā)生異常的進程置于阻塞狀態(tài),將進程喚醒等。

3.3.1 進程創(chuàng)建

父進程可創(chuàng)建子進程,父進程終止后子進程也會被終止。子進程可繼承父進程所有資源,子進程終止需將自己所繼承的資源歸還父進程。接下來看下創(chuàng)建的大致流程。

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

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

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

標識信息:將系統(tǒng)分配的標識符和父進程標識符填入新PCB

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

處理機控制信息:將進程設(shè)為就緒/靜止狀態(tài),通常設(shè)為最低優(yōu)先級

如果進程調(diào)度隊列能接納新進程,就將進程插入到就緒隊列,等待被調(diào)度運行。

3.3.2 進程終止

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

正常結(jié)束

異常結(jié)束

越界錯:訪問的存儲區(qū)越出該進程的區(qū)域

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

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

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

運行超時:執(zhí)行時間超過指定的最大值

等待超時:進程等待某件事超過指定的最大值

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

I/O故障

外界干預

操作員或OS干預(死鎖)

父進程請求,子進程完成父進程指定的任務(wù)時

父進程終止,所有子進程都應該結(jié)束

終止過程:

根據(jù)被終止進程的標識符,從PCB集合中檢索出該PCB,讀取進程狀態(tài)

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

若進程有子孫進程則將其所有子孫進程終止。

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

該進程的PCB從所在隊列/鏈表中移出。

3.3.3 進程阻塞

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

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

等待某種操作完成

新數(shù)據(jù)尚未到達(相互合作的進程)

等待新任務(wù)

阻塞流程:

找到要被阻塞進程標識號對應的 PCB。

將該進程由運行狀態(tài)轉(zhuǎn)換為阻塞狀態(tài)。

將該 進程PCB 插入的阻塞隊列中去。

3.3.4 進程喚醒

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

從阻塞隊列找到所需PCB。

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

從阻塞隊列溢出該PCB然后插入到就緒狀態(tài)隊列等待被分配CPU資源。

3.4 進程調(diào)度

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

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

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

3.4.1 進程調(diào)度原則

CPU 利用率

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

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

系統(tǒng)吞吐量

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

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

周轉(zhuǎn)時間

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

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

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

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

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

等待時間

指的是進程在等待隊列中等待的時間,一般也需要盡可能短。

響應時間

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

3.4.2 調(diào)度算法

FCFS 算法

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

該模式對長作業(yè)有利,適用 CPU 繁忙型作業(yè)的系統(tǒng),不適用 I/O 型作業(yè),因為會導致進程CPU利用率很低。

SJF 算法

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

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

SRTN 算法

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

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

HRRN 算法

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

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

RR 算法

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

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

HPF 算法

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

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

缺點是可能導致低優(yōu)先級的一直無法被執(zhí)行。

MFQ 算法

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

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

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

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

3.5 線程

3.5.1 線程定義

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

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

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

3.5.2 線程優(yōu)缺點

優(yōu)點

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

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

缺點

當進程中的一個線程奔潰時,會導致其所屬進程的所有線程奔潰。

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

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

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

進程:

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

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

線程:

是進程的一個實體,是CPU調(diào)度和分派的基本單位,它是比進程更小的能獨立運行的基本單位

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

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

進程線程區(qū)別:

一個線程只能屬于一個進程,而一個進程可以有多個線程,但至少有一個線程。

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

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

資源分配給進程,同一進程的所有線程共享該進程的所有資源。

CPU分配資源給進程,但真正在CPU上運行的是線程。

線程不能夠獨立執(zhí)行,必須依存在進程中。

線程快在哪兒?

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

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

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

3.5.4 線程實現(xiàn)

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

3.5.4.1 用戶態(tài)線程

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


課程聯(lián)系1:
大學資源網(wǎng)客服

課程聯(lián)系2:
大學資源網(wǎng)客服

課程聯(lián)系3:
大學資源網(wǎng)客服

服務(wù)時間:
8:00-21:00(工作日)

承德市| 密云县| 宁晋县| 大方县| 洞口县| 丰顺县| 玉山县| 庄浪县| 杭锦旗| 翼城县| 宁都县| 通州市| 桦川县| 兰州市| 怀远县| 浑源县| 巴马| 汾西县| 安平县| 武乡县| 南投县| 商丘市| 尤溪县| 镶黄旗| 汝南县| 德州市| 德化县| 堆龙德庆县| 瓦房店市| 平安县| 托克托县| 普兰县| 甘谷县| 济源市| 左云县| 南澳县| 右玉县| 沙洋县| 深泽县| 小金县| 正蓝旗|