溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

如何理解硬核操作系統(tǒng)

發(fā)布時(shí)間:2021-10-18 14:37:09 來源:億速云 閱讀:155 作者:iii 欄目:編程語言

這篇文章主要講解了“如何理解硬核操作系統(tǒng)”,文中的講解內(nèi)容簡單清晰,易于學(xué)習(xí)與理解,下面請(qǐng)大家跟著小編的思路慢慢深入,一起來研究和學(xué)習(xí)“如何理解硬核操作系統(tǒng)”吧!

1 馮諾伊曼體系

1.1 馮諾伊曼體系簡介

現(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è)備、總線

如何理解硬核操作系統(tǒng)

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

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

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

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

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

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

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

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

  2. 程序計(jì)數(shù)器:用來存儲(chǔ) 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)算單元,如果是存儲(chǔ)類型的指令交給控制單元執(zhí)行。

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

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

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

1.2 CPU 簡介

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

如何理解硬核操作系統(tǒng)

CPU

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

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

  3. L1緩存:每個(gè)CPU核心都有,用來緩存數(shù)據(jù)跟指令,訪問空間大小一般在32~256KB,訪問速度一般是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 指令緩存


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

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


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

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


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

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

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

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

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

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

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

1.3 CPU 訪問方式

如何理解硬核操作系統(tǒng)

CPU訪問方式


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

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

  2. Valid Bit : 有效位標(biāo)志符,值為0時(shí)表示無論 CPU Line 中是否有數(shù)據(jù),CPU 都會(huì)直接訪問內(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ù)時(shí)只需要指定三個(gè)部分即可。

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

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

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

1.4 CPU 訪問速度

如何理解硬核操作系統(tǒng)

訪問耗時(shí)對(duì)比


如上圖所示,CPU訪問速度是逐步變慢,所以CPU訪問數(shù)據(jù)時(shí)需盡量在距離CPU近的高速緩存區(qū)訪問,根據(jù)摩爾定律CPU訪問速度每18個(gè)月就會(huì)翻倍,而內(nèi)存的訪問每18個(gè)月也就增長10% 左右,導(dǎo)致的結(jié)果就是CPU跟內(nèi)存訪問性能差距逐步變大,那如何盡可能提高CPU緩存命中率呢?
1.  數(shù)據(jù)緩存:遍歷數(shù)據(jù)時(shí)候按照內(nèi)存布局順序訪問,因?yàn)镃PU Cache是根據(jù)Cache Line批量操作數(shù)據(jù)的,所以你順序讀取數(shù)據(jù)會(huì)提速,道理跟磁盤順序?qū)懸粯印?/p>

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

  2. 線程綁定到CPU:一個(gè)任務(wù)A在前一個(gè)時(shí)間片用CPU核心1 運(yùn)行,后一個(gè)時(shí)間片用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)多線程同時(shí)執(zhí)行密集計(jì)算,且 CPU 緩存命中率很高時(shí),如果將每個(gè)線程分別綁定在不同的 CPU 核心上,性能便會(huì)獲得非??捎^的提升。

1.5  操作系統(tǒng)

如何理解硬核操作系統(tǒng)

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


有了馮諾伊曼計(jì)算機(jī)體系后,電腦想要為用戶提供便捷的服務(wù)還需要安裝個(gè)操作系統(tǒng) Operation System,操作系統(tǒng)是覆蓋在硬件上的一層特殊軟件,它管理計(jì)算機(jī)的硬件和軟件資源,為其他應(yīng)用程序提供大量服務(wù)。可以理解為操作系統(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,你有沒有好奇為什么可以同時(shí)運(yùn)行2個(gè)以上的2G內(nèi)存的程序。應(yīng)用程序不是直接使用的物理地址,操作系統(tǒng)為每個(gè)運(yùn)行的進(jìn)程分配了一套虛擬地址,每個(gè)進(jìn)程都有自己的虛擬內(nèi)存地址,進(jìn)程是無法直接進(jìn)行物理內(nèi)存地址的訪問的。至于虛擬地址跟物理地址的映射,進(jìn)程是感知不到的!操作系統(tǒng)自身會(huì)提供一套機(jī)制將不同進(jìn)程的虛擬地址和不同內(nèi)存的物理地址進(jìn)行映射。

如何理解硬核操作系統(tǒng)

虛擬內(nèi)存

2.1  MMU

Memory Management Unit 內(nèi)存管理單元是一種負(fù)責(zé)處理CPU內(nèi)存訪問請(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)存上。

如何理解硬核操作系統(tǒng)

MMU


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

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

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

2.2.1 內(nèi)存分段

如何理解硬核操作系統(tǒng)

JVM內(nèi)存模型

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

如何理解硬核操作系統(tǒng)

內(nèi)存分段管理

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

分段映射很簡單,但是會(huì)導(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)存空間大于請(qǐng)求所需的內(nèi)存空間。

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

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

如何理解硬核操作系統(tǒng)

swap空間利用

2.2.2 內(nèi)存分頁

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

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

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

如何理解硬核操作系統(tǒng)

分頁內(nèi)存尋址

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

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

如何理解硬核操作系統(tǒng)

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


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

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

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

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

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

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

  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 偏移量。

如何理解硬核操作系統(tǒng)

64位尋址

2.2.2 TLB

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

如何理解硬核操作系統(tǒng)

TLB


TLB很小,因此緩存的東西也不多。主要緩存最近使用的數(shù)據(jù)的數(shù)據(jù)映射。TLB結(jié)構(gòu)如下圖: 如何理解硬核操作系統(tǒng)

TLB查詢


如果一個(gè)需要訪問內(nèi)存中的一個(gè)數(shù)據(jù),給定這個(gè)數(shù)據(jù)的虛擬地址,查詢TLB,發(fā)現(xiàn)有hit,直接得到物理地址,在內(nèi)存根據(jù)物理地址取數(shù)據(jù)。如果TLB沒有這個(gè)虛擬地址miss,那么只能費(fèi)力的通過頁表來查找了。日常CPU讀取一個(gè)數(shù)據(jù)的流程如下:
如何理解硬核操作系統(tǒng)

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ù)有兩種辦法。

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

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

2.2.3 段頁式管理

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

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

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

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

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

如何理解硬核操作系統(tǒng)

段頁式管理


同時(shí)我們經(jīng)??吹絻蓚€(gè)專業(yè)詞 邏輯地址線性地址。

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

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

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

如何理解硬核操作系統(tǒng)

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

2.2.4 Linux 內(nèi)存管理

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

如何理解硬核操作系統(tǒng)


我們知道32位的操作系統(tǒng)可尋址范圍是4G,操作系統(tǒng)會(huì)將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í)行代碼會(huì)受到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)生的一個(gè)操作系統(tǒng)解決多用戶多任務(wù)需求的方案。方案就是限制,通過硬件手段(也只能硬件手段才能做到),限制某些代碼,使其無法控制整個(gè)物理硬件,進(jìn)而使各個(gè)不同用戶,不同任務(wù)的代碼,無權(quán)修改整個(gè)物理硬件,再進(jìn)而保護(hù)操作系統(tǒng)的核心底層代碼和其他用戶的數(shù)據(jù)不被無意或者有意地破壞和盜取。

如何理解硬核操作系統(tǒng)


后來研究者根據(jù) CPU的運(yùn)行級(jí)別,分成了Ring0~Ring3四個(gè)級(jí)別。Ring0是最高級(jí)別,Ring1次之,Rng2更次之,拿Linux+x86來說,  操作系統(tǒng)內(nèi)核的代碼運(yùn)行在最高運(yùn)行級(jí)別Ring0上,可以使用特權(quán)指令,控制中斷、修改頁表、訪問設(shè)備等。 應(yīng)用程序的代碼運(yùn)行在最低運(yùn)行級(jí)別上Ring3上,不能做受控操作,只能訪問用戶被分配的空間。如果要做訪問磁盤跟寫文件等操作,那就要通過執(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è)備訪問,完成之后再從Ring0返回Ring3。這個(gè)過程也稱作 用戶態(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)用是影響整個(gè)當(dāng)前進(jìn)程上下文的,CPU提供了個(gè)軟中斷來是實(shí)現(xiàn)保護(hù)線程,獲取系統(tǒng)調(diào)用號(hào)跟參數(shù),交給內(nèi)核對(duì)應(yīng)系統(tǒng)調(diào)用函數(shù)執(zhí)行。

如何理解硬核操作系統(tǒng)

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)后可以很方便地訪問內(nèi)核空間內(nèi)存。比如Java代碼創(chuàng)建線程 new Thread調(diào)用 start方法后跟蹤 JVM源碼你會(huì)發(fā)現(xiàn)是調(diào)用 pthread_create來創(chuàng)建線程的,這就涉及到了用戶態(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)行過程,是系統(tǒng)進(jìn)行資源分配和調(diào)度的一個(gè)基本單位。進(jìn)程的調(diào)度狀態(tài)如下:

如何理解硬核操作系統(tǒng)

狀態(tài)變化圖


重點(diǎn)說下 掛起阻塞
 

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

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

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

  2. 就緒掛起:進(jì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)程 :

  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)識(shí)符:用于唯一地標(biāo)識(shí)一個(gè)進(jìn)程,一個(gè)進(jìn)程通常有兩種標(biāo)識(shí)符:

  1. 內(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)使用。

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

  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)度和對(duì)換時(shí)的依據(jù)。

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

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

  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,如何管理是個(gè)問題,一般有如下方式。

如何理解硬核操作系統(tǒng)

線下數(shù)組

  1. 線性方式

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

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

如何理解硬核操作系統(tǒng)

索引方式

  1. 索引方式

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

如何理解硬核操作系統(tǒng)

鏈表方式

  1. 鏈接方式

  1. 把同一狀態(tài)的PCB鏈接成一個(gè)隊(duì)列,形成就緒隊(duì)列、阻塞隊(duì)列、空白隊(duì)列等。對(duì)其中的就緒隊(duì)列常按進(jìn)程優(yōu)先級(jí)的高低排列,優(yōu)先級(jí)高排在隊(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)程也會(huì)被終止。子進(jìn)程可繼承父進(jìn)程所有資源,子進(jìn)程終止需將自己所繼承的資源歸還父進(jìn)程。接下來看下創(chuàng)建的大致流程。

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

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

  3. 初始化PCB,有如下幾個(gè)操作。

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

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

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

  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. 越界錯(cuò):訪問的存儲(chǔ)區(qū)越出該進(jìn)程的區(qū)域

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

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

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

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

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

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

  8. I/O故障

  1. 外界干預(yù)

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

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

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

終止過程

  1. 根據(jù)被終止進(jìn)程的標(biāo)識(shí)符,從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í)行半路被阻塞,必須由某個(gè)事件進(jìn)程喚醒該進(jìn)程。常見的就是IO讀取操作。常見阻塞時(shí)機(jī)/事件如下:

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

  2. 等待某種操作完成

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

  4. 等待新任務(wù)

阻塞流程

  1. 找到要被阻塞進(jìn)程標(biāo)識(shí)號(hào)對(duì)應(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,一般和阻塞成對(duì)使用。喚醒過程如下:

  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ù)一般會(huì)大于CPU個(gè)數(shù),進(jìn)程狀態(tài)切換主要由調(diào)度程序進(jìn)行調(diào)度。一般情況下CPU調(diào)度時(shí)主要分為搶占式調(diào)度非搶占式調(diào)度。

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

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

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

  1. CPU 利用率

  1. CPU利用率 = 忙碌時(shí)間 / 總時(shí)間。

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

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

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

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

  1. 周轉(zhuǎn)時(shí)間

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

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

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

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

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

  1. 等待時(shí)間

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

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

3.4.2 調(diào)度算法

FCFS 算法

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

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

SJF 算法

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

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

SRTN 算法

  1. 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í)行。

  2. 有最短的平均周轉(zhuǎn)時(shí)間,但不公平,源源不斷的短任務(wù)到來,可能使長的任務(wù)長時(shí)間得不到運(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) = (等待時(shí)間 + 要求服務(wù)時(shí)間) / 要求服務(wù)時(shí)間

RR 算法

  1. 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)。

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

HPF 算法

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

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

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

MFQ 算法

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

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

  3. 新進(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)沒執(zhí)行完畢需下移到次優(yōu)先級(jí)隊(duì)列。

    如何理解硬核操作系統(tǒng)

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

3.5 線程

3.5.1 線程定義

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

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

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

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

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

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

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

缺點(diǎn)

  1. 當(dāng)進(jìn)程中的一個(gè)線程奔潰時(shí),會(huì)導(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)度的一個(gè)獨(dú)立單位.

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

線程

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

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

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

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

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

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

  3. 進(jìn)程在執(zhí)行過程中擁有獨(dú)立的內(nèi)存單元,而多個(gè)線程共享內(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)建的時(shí)有些資源不需要自己管理,直接從進(jìn)程拿即可,線程管理寄存器跟棧的生命周期即可。

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

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

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

在前面的內(nèi)存管理中說到了內(nèi)核態(tài)跟用戶態(tài)。相對(duì)應(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)建時(shí)應(yīng)用程序在用戶空間內(nèi)要實(shí)現(xiàn)線程的創(chuàng)建、維護(hù)和調(diào)度。操作系統(tǒng)對(duì)線程的存在一無所知!操作系統(tǒng)只能看到進(jìn)程看不到線程。所有的線程都是在用戶空間實(shí)現(xiàn)。在操作系統(tǒng)看來,每一個(gè)進(jìn)程只有一個(gè)線程。

如何理解硬核操作系統(tǒng)

用戶態(tài)線程

好處

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

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

壞處

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

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

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

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

如何理解硬核操作系統(tǒng)

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


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

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

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

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

缺點(diǎn)

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

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

3.4.4.3 輕量級(jí)進(jìn)程

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

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

如何理解硬核操作系統(tǒng)

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特性

  1. Linux中沒有真正的線程,因?yàn)長inux并沒有為線程準(zhǔn)備特定的數(shù)據(jù)結(jié)構(gòu)。在內(nèi)核看來只有進(jìn)程而沒有線程,在調(diào)度時(shí)也是當(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í)進(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)度切換問題,同時(shí)還會(huì)存在每個(gè)進(jìn)程資源不共享的問題,需要額外引入進(jìn)程間通信機(jī)制來解決。

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

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

如何理解硬核操作系統(tǒng)

協(xié)程


協(xié)程運(yùn)行在線程之上,當(dāng)一個(gè)協(xié)程執(zhí)行完成后,可以選擇主動(dòng)讓出,讓另一個(gè)協(xié)程運(yùn)行在當(dāng)前線程之上。 協(xié)程并沒有增加線程數(shù)量,只是在線程的基礎(chǔ)之上通過分時(shí)復(fù)用的方式運(yùn)行多個(gè)協(xié)程,而且協(xié)程的切換在用戶態(tài)完成,切換的代價(jià)比線程從用戶態(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é)程調(diào)用了一個(gè)阻塞IO操作,此時(shí)操作系統(tǒng)并不知道協(xié)程的存在,它只知道線程,因此在協(xié)程調(diào)用阻塞IO操作時(shí),操作系統(tǒng)會(huì)讓線程進(jìn)入阻塞狀態(tài),當(dāng)前的協(xié)程和其它綁定在該線程之上的協(xié)程都會(huì)陷入阻塞而得不到調(diào)度。

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

3.6 進(jìn)程通信

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

3.6.1 管道

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

匿名管道

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

命名管道

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

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

如何理解硬核操作系統(tǒng)

管道通信底層


匿名管道的通信范圍是存在父子關(guān)系的進(jìn)程。由于管道沒有實(shí)體,也就是沒有管道文件,不會(huì)涉及到文件系統(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]。

如何理解硬核操作系統(tǒng)

shell管道通信


需注意Shell執(zhí)行匿名管道 a | b其實(shí)是通過Shell父進(jìn)程fork出了兩個(gè)子進(jìn)程來實(shí)現(xiàn)通信的,而ab之間是不存在父子進(jìn)程關(guān)系的。而命名管道是可以直接在不想關(guān)進(jìn)程間通信的,因?yàn)橛泄艿牢募?/p>

3.6.2 消息隊(duì)列

如何理解硬核操作系統(tǒng)

消息隊(duì)列

消息隊(duì)列是保存在 內(nèi)核中的消息鏈表, 會(huì)涉及到用戶態(tài)跟內(nèi)核態(tà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)存

如何理解硬核操作系統(tǒng)

共享空間


現(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虛擬地址是一樣的,真正訪問的也是不同的物理內(nèi)存地址,該模式不涉及到用戶態(tài)跟內(nèi)核態(tài)來回切換,JVM 就是用的共享內(nèi)存模式。并且并發(fā)編程也是個(gè)難點(diǎn)。

3.6.4 信號(hào)量

既然共享內(nèi)存容易造成數(shù)據(jù)紊亂,那為了簡單的實(shí)現(xiàn)共享數(shù)據(jù)在任意時(shí)刻只能被一個(gè)進(jì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兩種原子操作

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

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

3.6.5 信號(hào)

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

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

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

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

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

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

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

  3. 忽略信號(hào):專門用來忽略某些信號(hào),但 SIGKILL 和 SEGSTOP是無法被忽略的,為了能在任何時(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類型,TCP或UDP類型。比如TCP協(xié)議通信的 socket 編程模型如下:

如何理解硬核操作系統(tǒng)

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ā)起連接請(qǐng)求。

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

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

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

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

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

如何理解硬核操作系統(tǒng)

UDP傳輸


UDP比較簡單,屬于類似廣播性質(zhì)的傳輸,不需要維護(hù)連接。但也需要 bind,每次通信時(shí)調(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ù)信息存儲(chǔ)到磁盤中,起到持久化文件的作用。文件系統(tǒng)的基本組成單元就是文件,文件組成方式不同就會(huì)形成不同的文件系統(tǒng)。

文件系統(tǒng)有很多種而不同的文件系統(tǒng)應(yīng)用到操作系統(tǒng)后需要提供統(tǒng)一的對(duì)外接口,此時(shí)用到了一個(gè)設(shè)計(jì)理念沒有什么是加一層解決不了的,在用戶層跟不同的文件系統(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)


日常的文件系統(tǒng)一般有如下三種:

  1. 磁盤文件系統(tǒng):就是我們常見的EXT 2/3/4系列。

  2. 內(nèi)存文件系統(tǒng):數(shù)據(jù)沒存儲(chǔ)到磁盤,占用內(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)會(huì)為每個(gè)文件分配索引節(jié)點(diǎn) inode目錄項(xiàng)directory entry來記錄文件內(nèi)容跟目錄層次結(jié)構(gòu)。

4.2.1 inode

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

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

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

  1. 系統(tǒng)找到這個(gè)文件名對(duì)應(yīng)的inode號(hào)碼。

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

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

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

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

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

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

    df -i # 查看每個(gè)硬盤分區(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也是一種文件,打開目錄實(shí)際上就是打開目錄文件。目錄文件內(nèi)容就是一系列目錄項(xiàng)的列,目錄項(xiàng)的內(nèi)容包含文件的名字、文件類型、索引節(jié)點(diǎn)指針以及與其他目錄項(xiàng)的層級(jí)關(guān)系。

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

4.2.3 軟連接跟硬鏈接

如何理解硬核操作系統(tǒng)

軟連接跟硬鏈接


硬鏈接:老文件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,不過文件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 文件存儲(chǔ)

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

4.3.1 連續(xù)空間存儲(chǔ)

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

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

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

    如何理解硬核操作系統(tǒng)

    連續(xù)存儲(chǔ)

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

隱式鏈表

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

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

如何理解硬核操作系統(tǒng)

隱式鏈表

顯式鏈表

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

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

    如何理解硬核操作系統(tǒng)

    顯示鏈表

4.3.3 非連續(xù)空間存儲(chǔ)之索引

  1. 實(shí)現(xiàn):整個(gè)文件類型一本新華字典,真實(shí)的數(shù)據(jù)塊在詞典實(shí)際位置存儲(chǔ)著,但文件所需數(shù)據(jù)塊的索引位置會(huì)被匯總起來形成目錄索引放在字典前頭。

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

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

如何理解硬核操作系統(tǒng)

索引存儲(chǔ)


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

4.3.4 空閑空間管理

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

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

    如何理解硬核操作系統(tǒng)

    空閑表

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

    如何理解硬核操作系統(tǒng)

    空閑鏈表

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

    如何理解硬核操作系統(tǒng)

    位圖法

5  輸入輸出管理

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

5.1.1 設(shè)備控制器

如何理解硬核操作系統(tǒng)

設(shè)備控制器


操作系統(tǒng)為統(tǒng)一管理眾多的設(shè)備并且屏蔽設(shè)備之間的差異,給每個(gè)設(shè)備都安裝了個(gè)小CPU叫 設(shè)備控制器。每個(gè)設(shè)備控制器都知道自己對(duì)應(yīng)外設(shè)的功能跟用法,并且每個(gè) 設(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ā)送一個(gè)w字符給到對(duì)應(yīng)的 I/O 設(shè)備。

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

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

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

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

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

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

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

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

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

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

5.1.2 驅(qū)動(dòng)接口

如何理解硬核操作系統(tǒ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)來調(diào)用,這樣操作系統(tǒng)內(nèi)核會(huì)像調(diào)用本地代碼一樣使用設(shè)備驅(qū)動(dòng)程序接口。

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

如何理解硬核操作系統(tǒng)

中斷請(qǐng)求流程

5.2 IO 控制

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

5.2.1 輪詢模式

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

如何理解硬核操作系統(tǒng)

輪詢模式

5.2.2 IO 中斷請(qǐng)求

如何理解硬核操作系統(tǒng)

中斷模式


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

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

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

5.2.3 DMA 模式

Direct Memory Access 直接內(nèi)存訪問,在硬件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 去處理別的事。

如何理解硬核操作系統(tǒng)

DMA模式

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

5.2.4 Zero Copy

Zero Copy 全程不會(huì)通過 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 老版本讀寫

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

如何理解硬核操作系統(tǒng)

老式讀寫


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

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

5.2.4.2  mmap 跟 write

如何理解硬核操作系統(tǒng)

mmap + write


思路就是用 mmap替代read函數(shù),mmap調(diào)用時(shí)會(huì) 直接把內(nèi)核緩沖區(qū)里的數(shù)據(jù)映射到用戶空間,此時(shí)減少了一次數(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)一個(gè) sendfile = read + write,避免了2次用戶態(tài)跟內(nèi)核態(tài)來回切換,并且可以直接把內(nèi)核緩沖區(qū)里的數(shù)據(jù)拷貝到 socket 緩沖區(qū)里,這樣就只有 2 次上下文切換,和 3 次數(shù)據(jù)拷貝。

如何理解硬核操作系統(tǒng)

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ù)拷貝。

如何理解硬核操作系統(tǒng)

ZeroCopy

5.2.4.5 文件傳輸規(guī)則

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

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

5.3 IO分層

如何理解硬核操作系統(tǒng)

IO分層


Linux 存儲(chǔ)系統(tǒng)的 I/O 由上到下可以分為 文件系統(tǒng)層、 通用塊層、 設(shè)備層。

  1. 文件系統(tǒng)層向上為應(yīng)用程序統(tǒng)一提供了標(biāo)準(zhǔn)的文件訪問接口,向下會(huì)通過通用塊層來存儲(chǔ)和管理磁盤數(shù)據(jù)。

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

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

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

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

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

6 End

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

感謝各位的閱讀,以上就是“如何理解硬核操作系統(tǒng)”的內(nèi)容了,經(jīng)過本文的學(xué)習(xí)后,相信大家對(duì)如何理解硬核操作系統(tǒng)這一問題有了更深刻的體會(huì),具體使用情況還需要大家實(shí)踐驗(yàn)證。這里是億速云,小編將為大家推送更多相關(guān)知識(shí)點(diǎn)的文章,歡迎關(guān)注!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請(qǐng)聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI