操作系統
操作系統(Operating System,OS)是指控制和管理整個計算機系統的硬件和軟件資源,並合理地組織調度計算機的工作和資源的分配,以提供給用戶和其他軟件方便的接口和環境,它是計算機系統中最基本的系統軟件。
操作系統特征
-
並發
指兩個或多個事件在同一時間間隔內發生。這些享件宏觀上是同時發生的,但微觀上是交替發生的。
-
共享
互斥共享:系統中的某些資源,雖然可以提供給多個進程使用,但一個時間段內只允許一個進程訪問該資源。
例子:使用QQ和微信視頻。同一時間段攝像頭只能分配給其中一個。
同時共享:系統中的某些資源,允許一個時間段內由多個進程“同時”對它們進行訪問(這個同時是宏觀上的)。
例子:例子:使用QQ發送文件A,同時使用微信發送文件B。宏觀上看兩邊同時都在讀取並發送數據。微觀上,兩個進程是交替着訪問硬盤的。
-
虛擬
概念:虛擬是指把一個物理上的實體變為若干個邏輯上的對應物。物理實體(前者)是實際存在的,而邏輯上對應物((后者)是用戶感受到的。
虛擬技術:
空分復用技術 如:虛擬存儲器技術):如:我們在電腦上打開多個軟件,超過了電腦的內存(16GB),但這些軟件仍可以在電腦上同時運行。
時分復用技術 (如:虛擬處理器):如一個單核CPU電腦可以打開多個程序
-
異步
概念:在多道程序環境下,允許多個程序並發執行,但由於資源有限,進程的執行不是一管到底的,而是走走停停,以不可預知的速度向前推進,這就是進程的異步性。
例如:點外賣,點完外賣之后去做其他事情,等外賣到了再去取外賣。在A事件的空閑期間,我們可以做B事件。當空閑期間結束后,我們再去執行A事件的后續操作。
操作系統的運行機制和體系結構
運行機制
兩種指令:
- 特權指令:如:如內存清零指令
- 非特權指令:如:普通的運算指令
兩種處理器狀態:
- 核心態(管態):特權指令、非特權指令都可以執行
- 用戶態(目態):此時CPU只能執行非特權指令
兩種程序
- 內核程序:操作系統的內核程序是系統的管理者,既可以執行特權指令,也可以執行非特權指令,運行在核心態。
- 應用程序:為了保證系統能安全運行,普通應用程序只能執行非特權指令,運行在用戶態
操作系統內核
- 時鍾管理:操作系統的時鍾管理是依靠
硬件定時器
的(具體硬件怎么實現我也不太清楚,好像是靠硬件周期性的產生一個脈沖信號實現的)。時鍾管理相當重要,比如我們獲取時間信息
,進程切換
等等都是要依靠時鍾管理。 - 中斷處理
- 原語:可以簡單理解為用來實現某個特定功能,在執行過程中
不可被中斷
的指令集合。原語有一個非常重要的特性,就是原子性(其運行一氣呵成,不可中斷
)。 - 對系統資源進行管理的功能:進程管理、存儲器管理、設備管理等。
中斷
-
在程序運行過程中,系統出現了一個必須由CPU立即處理的情況,此時,CPU
暫時中止程序的執行
轉而處理這個新的情況
的過程就叫做中斷
。 -
操作系統發現
中斷的信號
是第一個程序的時間片(每個程序不能一直執行,CPU會給每個程序一定的執行時間,這段時間就是時間片)用完了,應該換第二個應用程序執行了 -
切換到
第2個進程
后,操作系統會將CPU
的使用權
交換給第二個應用程序,接着第二個應用程序就在用戶態
下開始執行。 -
進程
2需要調用打印機資源
,這時會執行一個系統調用
(后面會講系統調用,這里簡單理解為需要操作系統進入核心態處理的函數),讓操作系統進入核心態,去調用打印機資源 -
打印機開始工作,
此時進程2
因為要等待打印機啟動,操作系統就不等待了(等到打印機准備好了,再回來執行程序2),直接切換到第三個應用程序
執行 -
等到打印機准備好了,此時打印機通過I/O控制器會給操作系統發出一
個中斷信號
,操作系統又進入到核心態,發現這個中斷是因為程序2
等待打印機資源,現在打印機准備好了,就切換到程序2
,切換到用戶態
,把CPU給程序2繼續執行。
好了,現在可以給出一個結論,就是用戶態、核心態之間的切換是怎么實現的?
- "用戶態 ---> 核心態"是通過中斷實現的。
並且中斷時唯一途徑
。 - "核心態 ---> 用戶態"的切換時通過執行一個特權指令,將程序狀態的標志位設為用戶態。
中斷的分類
舉個例子,什么是內中斷和外中斷:
假如你對象上課的時候突然異想天開,回過神來已經過好好長一段時間,這是內部中斷
。想着想着老師走過來,給了你對象一嘴巴,這是外部中斷
。
官方解釋如下:
- 內中斷常見的情況如
程序非法操作
(比如你要拿的的數據的內存地址不是內存地址,是系統無法識別的地址),地址越界
(比如系統給你的程序分配了一些內存,但是你訪問的時候超出了你應該訪問的內存范圍)、浮點溢出
(比如系統只能表示1.1到5.1的范圍,你輸入一個100, 超出了計算機能處理的范圍),或者異常
,陷入trap
(是指應用程序請求系統調用造成的,什么是系統調用,后面小節會舉例講)。 - 外中斷常見的情況如
I/O中斷
(由I/O控制器產生,用於發送信號通知操作完成等信號,比如進程需要請求打印機資源,打印機有一個啟動准備的過程,准備好了就會給CPU一個I/O中斷,告訴它已經准備好了)、時鍾中斷
(由處理器內部的計時器產生,允許操作系統以一定規程執行函數,操作系統每過大約15ms會進行一次線程調度,就是利用時鍾中斷來實現的)。
系統調用
為什么需要系統調用?
- 比如你的程序需要
讀取文件信息
,可讀取文件屬於讀取硬盤里的數
據,這個操作應該是CPU在內核態
去完成的,我們的應用程序怎么讓CPU去幫助我們切換到內核態完成這個工作呢,這里就需要系統調用了
。 - 這里就引出系統調用的概念和作用。
- 應用程序
通過系統調用請求操作系統的服務
。系統中的各種共享資源都由操作系統統一管理,因此在用戶程序中,凡是與資源有關的操作
(如存儲分配、I/O操作、文件管理等),都必須
通過系統調用的方式向操作系統提出服務請求,由操作系統代為完成。
系統調用的分類:
需要注意的是,庫函數
和系統調用
容易混淆。
- 庫是可重用的模塊
處於用戶態
- 進程通過系統調用從用戶態進入
內核態
, 庫函數中有很大部分是對系統調用的封裝
舉個例子:比如windows
和linux
中,創建進程的系統調用方法是不一樣的。 但在node中的只需要調用相同函數方法就可以創建一個進程。例如
// 引入創建子進程的模塊
const childProcess = require('child_process')
// 獲取cpu的數量
const cpuNum = require('os').cpus().length
// 創建與cpu數量一樣的子進程
for (let i = 0; i < cpuNum; ++i) {
childProcess.fork('./worker.js')
}
進程的定義、組成、組織方式、狀態與轉換
為什么要引入進程的概念呢?
-
早期的計算機只支持
單道程序
(是指所有進程一個一個排隊執行,A進程執行時,CPU、內存、I/O設備全是A進程控制的,等A進程執行完了,才換B進程,然后對應的資源比如CPU、內存這些才能換B用)。 -
現代計算機是
多道程序
執行,就是同時看起來有多個程序在一起執行,那每個程序執行都需要系統分配給它資源來執行,比如CPU
、內存
。 -
拿內存來說,操作系統要知道給A程序分配的內存有哪些,給B程序分配的內存有哪些,這些都要有小本本記錄下來,這個小本本就是進程的一部分,進程的一大職責就是
記錄目前程序運行的狀態
。 -
系統為每個運行的程序配置一個數據結構,稱為
進程控制塊
(PCB),用來描述進程的各種信息(比如代碼段放在哪)。
進程的定義
簡要的說,進程就是具有獨立功能的程序
在數據集合上運行的過程
。(強調動態性)
比如啟動QQ,這個程序運行過程的整體就是一個進程。
PCB有哪些組成
如下圖
進程標識符PID
:相當於身份證。是在進程被創建時,操作系統會為該進程分配一個唯一的、不重復的ID,用於區分不同的進程
。- 用戶標識符
UID
:用來表示這個進程所屬的用戶
是誰。 - 進程當前狀態和優先級下一小節會詳細介紹
- 程序段指針:指當前進程的程序在
內存的什么地方
。 - 數據段指針:指當前進程的數據在
內存的什么地方
。 - 鍵盤和鼠標:指進程被
分配得到的I/O設備
。 - 各種寄存器值:指比如把程序計數器的值,比如有些計算的結果算到一半,進程切換時需要把這些值保存下來。
進程的狀態
進程是程序的一次執行。
在這個執行過程中,有時進程正在被CPU處理
,有時又需要等待CPU服務
,可見,進程的 狀態是會有各種變化。為了方便對各個進程的管理,操作系統需要將進程合理地划分為幾種狀態。
進程的三種基本狀態:
進程的另外兩種狀態:
進程狀態的轉換
進程的狀態並不是一成不變的,在一定情況下會動態轉換。
以上的這些進程狀態的轉換是如何實現的呢,這就要引出下一個角色了,叫原語
。
- 原語是
不可被中斷
的原子操作。我們舉一個例子看看原語是怎么保證不可中斷的。
原語采用關中斷指令
和開中斷指令
實現。
- 首先執行關中斷指令
- 然后外部來了中斷信號,不予以處理
- 等到開中斷指令執行后,其他中斷信號才有機會處理。
進程的通信
為什么需要進程間通信呢?
因為進程是
分配系統資源的單位
(包括內存地址空間),因此各進程擁有的內存地址空間相互獨立。
進程通信3種方法
共享存儲
因為兩個進程的存儲空間不能相互訪問
,所以操作系統就提供的一個內存空間讓彼此都能訪問,這就是共享存儲的原理。
注:同一時刻只能有一個進程訪問共享空間
其中,介紹一下基於存儲區的共享。
- 在內存中畫出一塊
共享存儲區
,以數據的形式、存放位置都是由進程控制,而不是操作系統。
管道
- 管道數據是以
字符流
(注意不是字節流)的形式寫入管道,當管道寫滿時,寫進程的write()
系統調用將被阻塞,等待讀進程將數據取走。當讀進程將數據全部取走后,管道變空,此時讀進程的read()
系統調用將被阻塞。 - 如果沒寫滿就不允許讀。如果都沒空就不允許寫。
- 數據一旦被讀出,就從管道中被丟棄,這就意味着
讀進程
最多只能有一個。
消息傳遞
進程間的數據交換以格式化的消息
為單位。進程通過操作系統提供的"發送消息/接收消息"
兩個原語進行數據交換。
其中消息是什么意思呢?就好像你發QQ消息,消息頭的來源是你,消息體是你發的內容。如下圖:
直接通信:消息直接掛到接受進程的消息緩沖隊列上
間接通信:消息要先發送到中間實體中,因此也稱“信箱通信方式”。
線程
為什么要引入線程呢?
- 比如你在玩QQ的時候,QQ是一個進程,如果QQ的進程里沒有多線程並發,那么QQ進程就只能
同一時間做一件事情
(比如QQ打字聊天)- 但是我們真實的場景是QQ聊天的同時,還可以發文件,還可以視頻聊天,這說明如果QQ
沒有多線程並發能力
,QQ能夠的實用性就大大降低了。所以我們需要線程
,也就是需要進程擁有能夠並發
多個事件的能力。
引入線程后帶來的變化
進程的同步和互斥
同步:是指多個進程中發生的事件存在某種先后順序。即某些進程的執行必須先於另一些進程。
例:
進程B
需要從緩沖區讀取進程A
產生的信息,當緩沖區為空時,進程B
因為讀取不到信息而被阻塞。而當進程A
產生信息放入緩沖區時,進程B
才會被喚醒。
互斥:是指多個進程不允許同時使用同一資源。當某個進程使用某種資源的時候,其他進程必須等待。
例:
進程B
需要訪問打印機,但此時進程A
占有了打印機,進程B
會被阻塞,直到進程A
釋放了打印機資源,進程B才可以繼續執行。
信號量
信號量
主要是來解決進程的同步
和互斥
的。
在操作系統中,常用P、V信號量
來實現進程間的同步
和互斥
,我們簡單了解一下一種常用的信號量,記錄型信號量
來簡單了解一下信號量本質是怎樣的。
/*記錄型信號量的定義*/
typedef struct {
int value; // 剩余資源
Struct process *L // 等待隊列
} semaphore
意思是信號量的結構有兩部分組成,一部分是剩余資源value
,比如目前有兩台打印機空閑,那么剩余資源就是2,誰正在使用打印機,剩余資源就減1。
Struct process *L
意思是,比如2台打印機都有人在用,這時候你要用打印機,此時會把這個打印機資源的請求放入阻塞隊列,L就是阻塞隊列的地址。
/*P 操作,也就是記錄型信號量的請求資源操作*/
void wait (semaphore S) {
S.value--;
if (S.value < 0){
block (S.L);
}
}
需要注意的是,如果剩余資源數不夠,使用block原語使進程從運行態進入阻塞態,並掛到信號量S的等待隊列中。
/*V 操作,也就是記錄型信號量的釋放資源操作*/
void singal (semaphore S) {
S.value++;
if (S.value <= 0){
wakeup (S.L);
}
}
釋放資源后,若還有別的進程在等待這個資源,比如打印機資源,則使用wakeup原語喚醒等待隊列中的一個進程,該進程從阻塞態變為繼續態。
生產者消費者問題
為什么要講這個呢,主要是node流的機制,本質就是生產者消費者問題,可以簡單的看看這個問題如何解決。
如上圖,
生產者
的主要作用是生成一定量的數據放到緩沖區中
,然后重復此過程
。與此同時,消費者也在緩沖區消耗這些數據
。該問題的關鍵就是要保證生產者不會在緩沖區滿時加入數據,消費者也不會在緩沖區中空時消耗數據。
內存的基礎知識和概念
為什么需要內存
內存是計算機其它硬件設備
與CPU溝通
的橋梁、中轉站。程序執行前需要先放到內存中才能被CPU處理。
cpu如何區分執行程序的數據在內存的什么地方
- 是通過給
內存的存儲單元編址
實現的。(存儲單元一般是以字節為單位) - 如下圖,內存的存儲單元,就像一個酒店的房間,都有編號,比如程序一的數據都在1樓,1樓1號存儲着程序里
let a = 1
這段代碼。
內存管理-內存空間的分配與回收
- 內存分配分為
連續分配
和非連續分配
,連續分配是指用戶進程分配的必須是一個連續的內存空間
。 - 這里我們只講連續分配中的
動態分區分配
。 - 什么是動態分區分配呢,這種分配方式
不會預先划分內存分區
,而是在進程裝入內存時,根據進程的大小動態地
建立分區,並使分區的大小正好適合
進程的需要。(比如,某計算機內存大小64MB,系統區8MB,用戶區56MB...,現在我們有幾個進程要裝入內存,如下圖)
- 隨之而來的問題就是,如果此時進程1使用完了,相應在內存上的數據也被刪除了,那么
空閑的區域
,后面該怎么分配(也就是說隨着進程退出,會有很多空閑的內存區域出現)
我們講一種較為簡單的處理方法叫空閑分區表
法來解決這個問題。如下圖,右側的表格就是一個空閑分區表。
當很多個空閑分區都能滿足需求時,應該選擇哪個分區進行分配呢,例如下圖,分別有20MB
,10MB
,4MB
三個空閑分區塊,現在進程5
需要4MB
空閑分區,改怎么分配呢?
我們需要按照一定的動態分區分配算法,比如有首次適應算法
,指的是每次都從低地址開始查找,找到第一個能滿足大小的空閑分區。還有比如最佳適應算法
,指的是從空閑分區表中找到最小的適合分配的分區塊來滿足需求。
連續分配缺點很明顯
,大多數情況,需要分配的進程大小,不能跟空閑分區剩下的大小完全一樣,這樣就產生很多很難利用的內存碎片
。
這里我們介紹一種更好的空閑分區的分配方法,基本分頁存儲
。如下圖
將內存空間分為一個個大小相等
的分區(比如:每個分區4KB
).每個分區就是一個“頁框”
。頁框號從0
開始。
將用戶進程的地址空間分為與頁框大小相等的一個個區域,稱為“頁”
。每個頁也是從0
開始。
死鎖
什么是僵屍進程
僵屍進程是已完成且處於終止狀態,但在進程表中卻仍然存在的進程。僵屍進程通常發生在父子關系的進程中,由於父進程仍需要讀取其子進程的退出狀態所造成的。
死鎖產生的原因
死鎖產生的原因大致有兩個:資源競爭和程序執行順序不當
死鎖產生的必要條件
資源死鎖可能出現的情況主要有
- 互斥條件:每個資源都被分配給了一個進程或者資源是可用的
- 保持和等待條件:已經獲取資源的進程被認為能夠獲取新的資源
- 不可搶占條件:分配給一個進程的資源不能強制的從其他進程搶占資源,它只能由占有它的進程顯示釋放
- 循環等待:死鎖發生時,系統中一定有兩個或者兩個以上的進程組成一個循環,循環中的每個進程都在等待下一個進程釋放的資源。
死鎖類型
- 兩階段加鎖
- 通信死鎖
- 活鎖
- 飢餓鎖
死鎖的恢復方式
- 通過搶占進行恢復
- 通過回滾進行恢復
- 殺死進程恢復
破壞死鎖
- 破壞互斥條件
- 破壞保持等待的條件
- 破壞不可搶占條件
- 破壞循環等待條件
文件管理
文件是什么?
文件就是一組有意義的信息/數據
集合。
文件的屬性
文件名、標識符、類型、位置、大小、保護信息。
文件內部數據如何組織在一起
如下圖,文件主要分為有結構文件
和無結構文件
。
文件之間如何組織起來
通過
樹狀結構
組織的。
文件的邏輯結構
邏輯結構是指,在用戶看來,文件內部的數據是如何組織起來的,而“物理結構”
是在操作系統看來,文件是如何保存在外存,比如硬盤
中的。
1.順序文件
什么是順序文件
指的是文件中的記錄一個接一個地在邏輯上是順序排列
,記錄可以是定長
或變長
,各個記錄在物理上可以順序存儲
或鏈式存儲
2.索引文件
3. 索引順序文件
索引順序文件是索引文件
和順序文件
思想的結合。索引順序文件中,同樣會為文件建立一張索引表,但不同的是,並不是每個記錄對應一個索引表項
,而是一組記錄對應一個索引表項。
如上圖,學生記錄按照學生姓名的開頭字母進行分組。每個分組就是一個順序文件,分組內的記錄不需要按關鍵字排序
文件目錄
一個文件對應一個FCB,一個FCB就是一個目錄項,多個FCB組成文件目錄
文件目錄的結構通常是樹狀的
- 需要注意的是,樹狀目錄
不容易實現文件共享
,所以在樹形目錄結構的基礎上,增加了一些指向同一節點的有向邊(可以簡單理解為引用關系,就跟js里的對象一樣) - 也就是說需要為
每個共享節點
設置一個共享計數器
,用於記錄此時有多少個地方在共享該結點。只有共享計數器減為0
,才刪除該節點。
文件共享
文件共享分為兩種
- 基於索引結點的共享方式(硬鏈接)
- 基於符號鏈的共享方式(軟鏈接)
- 軟連接可以理解為
windows
里的快捷方式
。 - 硬鏈接可以理解為js里的
引用計數
,只有引用為0
的時候,才會真正刪除這個文件。
- 軟連接可以理解為
文件保護
操作系統需要保護文件的安全,一般有如下3種方式:
- 口令保護。是指為文件設置一個
“口令”
(比如123),用戶請求訪問該文件時必須提供對應的口令。口令一般放在文件對應的FCB或者索引結點
上。 - 加密保護。使用某個
"密碼"
對文件進行加密,在訪問文件時需要提供正確的“密碼”
才能對文件進行正確的解密。 - 訪問控制。在每個文件的FCB或者索引節點種增加一個
訪問控制列表
,該表中記錄了各個用戶可以對該文件執行哪些操作。
I/O設備
什么是I/O設備
I/O就是
輸入輸出
(Input/Output)的意思,計算機的外部設備,屬於計算機中的硬件部件。
I/O設備分類--按使用特性
-
人機交互類設備,這類設備傳輸數據的速度慢。例:鼠標、鍵盤、打印機。
-
存儲設備,這類設備傳輸數據的速度較快。例:移動硬盤、光盤等。
-
網絡通信設備,這類設備的傳輸速度介於人機交互設備和存儲設備之間
I/O控制器
CPU無法直接控制
I/O設備的機械部件
,因此I/O設備還要有一個電子部件作為CPU
和I/O設備
機械部件之間的“中介”
,用於實現CPU對設備的控制。這個電子部件就是I/O控制器
。
主要功能:
- 接收和識別CPU發出的指令是指,比如CPU發來讀取文件的命令,I/O控制器中會有相應的
控制寄存器
來存放命令和參數- 向cpu報告設備的狀態是指,I/O控制器會有相應的
狀態寄存器
,用來記錄I/O設備是否空閑
或者忙碌
- 數據交換是指I/O控制器會設置相應的
數據寄存器
。輸出時,數據寄存器用於暫存CPU發來的數據
,之后再由控制器傳送給設備。- 地址識別是指,為了區分設備控制器中的各個寄存器中的各個寄存器,也需要給各個寄存器設置一個特性的
“地址”
。I/O控制器通過CPU提供的“地址”來判斷CPU要讀寫的是哪個寄存器
I/O控制方式
- 這里我們只講一下目前比較先進的方式,通道控制方式。
- 通道可以理解為一種
“弱雞版CPU”
。通道可以識別並執行一系列通道指令。
通道最大的優點是極大的減少了CPU的干預頻率
,I/O設備
完成任務,通道會向CPU發出中斷
,不需要輪詢來問I/O設備是否完成CPU下達的任務。