程序是一個非常重要的概念,了解它,你會更清晰的認知計算機中的程式執行。看待計算機的角度都會不一樣。
- 馮諾依曼體系
-
- 通信在體系下的硬體操作
- 外設與CPU的互動
- 作業系統OS
-
- 邏輯圖
- 概括
- 管理的概念
- PCB-程序描述
- 程序概念
-
- 建立程序
- 删除程序
- PCB的成員
-
- 時間片
- 辨別符
- 狀态
- 優先級
- 程式計數器
- 記賬資訊
- 程序切換與排程
- 上下文資料
- 結構體指針
- CPU的操作
- 系統調用建立程序-fork
- 程序狀态
-
- R狀态(運作狀态)
- S狀态-休眠狀态
- D狀态-深度睡眠
- T狀态-暫停狀态
- Z-僵屍狀态
- 孤兒狀态
- 程序間的性質
- 程序優先級
-
- top任務管理器
-
- PRI與NICE值
- 環境變量
-
- 常見的環境變量
-
- HOME
- SHELL
- env
- 三個main函數的參數
-
- argc-指令行參數個數
- argv-指令行參數清單
- envp-環境變量參數清單
- 程序位址空間
-
- 提問:這個程序位址空間是不是記憶體呢?
- 程序位址空間對應的位址不是實體位址
- 虛拟位址
-
- 邏輯關系
- 對父程序也一樣
- 程序位址空間的了解
-
- 為什麼會存在程序位址空間
- 建立程序
馮諾依曼體系
首先,來介紹計算機的結構-馮諾依曼體系
這是現代計算機的邏輯結構。
輸入裝置:鍵盤、滑鼠、搖桿、網卡…
輸出裝置:螢幕、硬碟、音響、網卡…
存儲器:記憶體
也就是說,在不考慮緩存的情況下,CPU隻會與記憶體進行互動,輸入輸出裝置也是隻與記憶體(存儲器)進行直接互動。
控制器是進行決策的,決策的對象是記憶體中的資料與代碼。
運算器是根據控制器的指令進行運算。
比如:鍵盤輸入時,鍵盤中的寄存器會進行按鍵識别,識别後會把寄存器的資料給記憶體中相應的寄存器,CPU中的控制器會控制其寄存器中的資料讀到運算器中,在根據指令進行操作。
各種硬體單元,使用的是線:總線(IO總線,系統總線),進行連接配接。
通信在體系下的硬體操作
使用即時通信軟體,例如QQ,微信。
如果兩個人想要發資訊,需要兩個人的QQ都要打開,則意味着,QQ這個程式變成了程序(後面介紹),正在執行中。
A從鍵盤上輸入發送的資訊,資訊被讀取到記憶體中,再被CPU讀取,根據QQ的加密或者資料打包,再還要符合網絡協定的格式,被CPU處理,再放入記憶體中,被輸出裝置(顯示屏,網卡)讀取,資訊原封不動的顯示在顯示器上,同時,被打包後的資訊會被發送進入網絡,伺服器上,
再發送到B的網卡中,資料包再被讀取到記憶體中,被CPU解碼,發送回記憶體,再被顯示器讀取,顯示。就完成了一整個通信的硬體邏輯操作的過程
外設與CPU的互動
像輸入輸出裝置也是能與CPU互動的,但不是資料互動,而是信号級别的互動,比如:中斷(外部中斷)
當你在鍵盤上輸入資料時,CPU是如何知道輸入好了資料呢?就是靠外設和CPU之間的信号互動。當資料輸入好了,給CPU(控制器)發送一個硬體級别的電脈沖,CPU就會把外設寄存器中的資料讀入到記憶體中。
除此之外,在資料層面上,CPU是不會和外設進行互動的,因為效率太低了。
所有裝置在資料層面上都隻能和記憶體(存儲器)進行互動。
作業系統OS
一個計算機中,僅僅隻有硬體體系是不夠的,還需要管理硬體,然硬體被組織起來。
是以開發了一款軟體,來對硬體資源進行管理,就是作業系統。同時,作業系統還為其它應用程式提供了一個可執行的軟體環境
邏輯圖
其中的驅動,是進行硬體和OS之間處理資訊的,防止不同的硬體導緻OS出現問題,讓OS與硬體進行解耦。
概括
是以說作業系統就是
核心(記憶體管理,檔案管理,程序管理,驅動管理)
其它程式(函數庫,shell程式)
設計OS的目的,是為了
- 與硬體互動,管理所有的軟硬體資源
- 為使用者程式(應用程式)提供一個良好的執行環境
純粹是為了進行管理。
管理的概念
我目前還是個學生,對于這個方面還是舉學校的例子。
學生入學後,就進入了學校的管理系統,就屬于被管理這的身份。而我們學生是看不到那些系統裡的資訊的。
而每個學院每一級都有一個輔導員,他負責處理我們的入學生活之類的。他看上去是個管理者,
但站在整個學校的角度來看,校長才是真正的管理者,而我們學生是被管理者,輔導員之類的是執行者。
而校長靠什麼來管理我們的呢?靠教務系統,系統裡已經全部組織好了全校學生的資訊。比如,要打學校之間的辯論賽了,校長在教務系統中挑10名績點最高的學生去打比賽,校長選好後,告訴輔導員,讓輔導員去組織一下賽前準備。這個過程就是一個被組織的過程。
是以可以得出一個概念,管理,其實就是,先描述,再組織的過程。
PCB-程序描述
作業系統對于程序也是要進行管理的,也是要遵循,先描述再組織的過程。
那一個OS要管理的程序肯定不止一個,那那麼多的程序要麼怎麼一一描述?
使用C語言中的結構體的概念,因為linux核心就是那C語言寫的。
關于程序的所有資訊都被放置在一個程序控制塊中的結構體中,結構體中右程序所有資訊的分類。
task_struct
{
}
task_struct是Linux核心的一種資料結構,它會被裝載到RAM(記憶體)裡并且包含着程序的資訊
而一個PCB程序控制塊來規劃、組織一個程序,那OS要管理多個程序的時候,就是要管理多個PCB。那OS就會用一個資料結構來管理多個PCB。
使用了連結清單來組織的資料結構。
程序概念
當一個可執行檔案(exe檔案),沒有執行時,是被儲存在硬碟中,當被執行後,這個可執行程式的代碼和資料就會被從硬碟中,加載到記憶體中。
且,當電腦開機時,第一個加載到記憶體中的程序就是作業系統。然後加載到記憶體中的程序就會被作業系統管理起來,管理程序的PCB。
建立程序
可執行檔案的代碼和資料會被加載到記憶體中,同時,OS會為這個程序建立一個PCB結構體來描述這個程序,同時,會把新建立的PCB連接配接到執行連結清單中,等待被CPU讀取。
删除程序
當選擇删除時,作業系統會先找到,要被删除程序的PCB,然後根據PCB中的資訊,找到在記憶體中的代碼和資料的位置,将這個記憶體銷毀,再将·PCB從隊列中剔除,并銷毀PCB的所處空間。這就完成了一個程序的删除。
PCB的成員
- 标示符: 描述本程序的唯一标示符,用來差別其他程序。
- 狀态: 任務狀态,退出代碼,退出信号等。
- 優先級: 相對于其他程序的優先級-pid。
- 程式計數器: 程式中即将被執行的下一條指令的位址。
- 記憶體指針: 包括程式代碼和程序相關資料的指針,還有和其他程序共享的記憶體塊的指針
- 上下文資料: 程序執行時處理器的寄存器中的資料[休學例子,要加圖CPU,寄存器。
- I/O狀态資訊: 包括顯示的I/O請求,配置設定給程序的I/O裝置和被程序使用的檔案清單。
- 記賬資訊: 可能包括處理器時間總和,使用的時鐘數總和,時間限制,記賬号等。
- 其他資訊
時間片
時間片:由于在計算機中,一定是程序多,CPU少,但是一個CPU不能隻執行一個程序,其它程序也要執行,是以有了時間片的概念,即一個程序在CPU上執行的時間,通常是比較短的,這個程序在CPU上執行一點時間,立馬換下一個程序,就遮掩給,依次進行。在我們看起來1就像是多個程序在同時進行的。
在正常的程序排程的過程中,要基于時間片來進行排程,也就是基于時間片的輪轉
我先聲明一下,這些圖檔都是我在OneNote上做的筆記,但複制就變成了圖檔了。
辨別符
狀态
優先級
程式計數器
記賬資訊
程序切換與排程
上下文資料
結構體指針
CPU的操作
當程式變成程序時, CPU要對程序進行操作。
CPU隻負責執行三個操作,循環進行。
讀取指令->分析指令->執行指令
這個程序替換就是又作業系統來決定的。
CPU取指令時,要從指令寄存器eip中讀取即将要執行的下一條指令的位址。也就是PCB中程式計數器的概念。
實際上,作業系統對程序的管理,就是對PCB連結清單的增删查改。
再概括一下,作業系統對程序的管理是,先描述,再組織。描述就是建立PCB,組織就是對連結清單進行操作。
系統調用建立程序-fork
如果是子程序,其傳回的就是0
如果是父程序,其傳回的就是子程序的pid
如果程序建立失敗,就會傳回-1。
這個是fork函數的傳回值的資訊。
從某種意義上說,這個函數有兩個傳回值。
因為它不僅會傳回代表子程序的0,還有代表父程序的子程序的pid。
至于為什麼說他又兩個傳回值,看這個。
這是輸出結果。
看到麼?這有兩個傳回值,正好對應了上面的介紹。
這就是可以通過,fork函數系統調用建立子程序,有兩個程序,就可以讓兩個程序執行不同的操作,利用其傳回值。
這是效果
一直執行不同的操作。
程序狀态
程序再記憶體中也是有狀态的。
對于作業系統而言,所有的程序都需要被作業系統識别,區分,而作業系統不能像我們人類能這麼抽像的了解,是以,程序狀态要讓OS了解,就必須符合OS的規則,要将其資料化。
在OS中,程序的所有狀态的都是可以資料化的,
而程序狀态->資料化,所有的資料都被儲存在PCB中,不然PCB怎麼叫程序狀态控制塊。
像
task_struct
中,就有一個指針數組專門存儲程序的所有狀态。
static const char * const task_state_array[] = {
"R (running)", /* 0 */
"S (sleeping)", /* 1 */
"D (disk sleep)", /* 2 */
"T (stopped)", /* 4 */
"t (tracing stop)", /* 8 */
"X (dead)", /* 16 */
"Z (zombie)", /* 32 */
};
R狀态(運作狀态)
舉例
注意:
S狀态-休眠狀态
舉例:
比如說:你在課堂上太困了,然後你跟你同桌說,太困了,要睡一覺,等老師來了,就趕緊叫醒你。這時,觸發你醒的條件就是老師來了。然而,這也可以因其他情況叫醒你,比如,老師叫你上去做題目,這又是另外一種條件。或者你自己睡醒了。你是随時能夠被喚醒的。
休眠狀态的程序也是這樣子。
D狀态-深度睡眠
可以防止程序被作業系統殺掉,而導緻程序執行任務失敗後造成資源丢失,而記憶體洩漏。
T狀态-暫停狀态
經過信号SIGSTOP可以讓程序處于暫停的狀态
這個和休眠狀态很像,按照程式設計需要進行程序暫停或運作。
Z-僵屍狀态
一個程序需要一個退出碼,其OS需要根據退出碼來判斷這個程序為什麼退出。是正常退出還是因為其他原因而導緻程序退出。
這個退出碼就是main函數中return傳回的值。
可以用
echo $?
來顯示退出碼。
且這個程序的退出碼儲存在
task_struct
中,也就是PCB中,OS會在這裡讀取退出資訊。
孤兒狀态
這是父程序托管子程序的狀态。
當我殺掉父程序時,子程序的pid未變動,而父程序已經變成了OS
子程序被1号程序接管了。
程序間的性質
- 獨立性:多程序運作需要獨享各種資源,多程序運作期間互不幹擾
- 并行:多程序在多個CPU下分别、同時的進行(任意時刻)
- 并發:多個程序在一個CPU下采用切換的方式,在同一時間段内,多個程序都得以推進
- 競争:系統程序數目衆多,而CPU資源有限,是以程序之間存在競争性,為了高效完成任務,程序獲得相關資源的先後順序便有了優先級
當程序數目較多時,對某一該程序而言,其切換到周期就變大了,就反映出一種”卡“的感覺
程序優先級
- cpu資源配置設定的先後順序,就是指程序的優先權(priority)。
- 優先權高的程序有優先執行權利。配置程序優先權對多任務環境的linux很有用,可以改善系統性能。
- 還可以把程序運作到指定的CPU上,這樣一來,把不重要的程序安排到某個CPU,可以大大改善系統整體性能。
top任務管理器
PRI與NICE值
- PRI也還是比較好了解的,即程序的優先級,或者通俗點說就是程式被CPU執行的先後順序,此值越小程序的優先級别越高
- 那NI呢?就是我們所要說的nice值了,其表示程序可被執行的優先級的修正數值
- PRI值越小越快被執行,那麼加入nice值後,将會使得PRI變為:PRI(new)=PRI(old)+nice
- 這樣,當nice值為負值的時候,那麼該程式将會優先級值将變小,即其優先級會變高,則其越快被執行
- 是以,調整程序優先級,在Linux下,就是調整程序nice值
- nice其取值範圍是-20至19,一共40個級别
需要強調一點的是:程序的nice值不是程序的優先級,他們不是一個概念,但是程序nice值會影響到進 程的優先級變化。
可以了解nice值是程序優先級的修正資料
通過top指令也能修改一個程序的NICE值,來間接修改一個程序的優先級。
top->按 ‘r’ -> 輸入程序的PID - >輸入NICE值
通過
ps -al
可以檢視程序
我把子程序的優先級修改成了60,也就是說我将NICE值變成了-20,是以子程序的優先級是最高的。
提醒:普通權限是不被允許修改程序優先級的,要sudo提升權限。才會允許。
環境變量
-
環境變量(environment variables)一般是指在作業系統中用來指定作業系統運作環境的一些參數
如:我們在編寫C/C++代碼的時候,在連結的時候,從來不知道我們的所連結的動态靜态庫在哪裡,但是照樣可以連結成功,生成可執行程式,原因就是有相關環境變量幫助編譯器進行查找。
- 環境變量通常具有某些特殊用途,還有在系統當中通常具有全局特性
由系統提供的環境變量,那就有變量名與變量内容。
如,經常使用的
ls
指令,這其實某種程度上說是一個可執行程式。就跟我們讓自己寫的程式運作。
一般是
,
./myproc
是指在目前目錄下查找,找到
.
後,再執行。而
myproc
都是一樣的,要先找到,在執行這個程式。這些查找其路徑,就是靠環境變量來完成。
ls/mkdir/rm/mv....
常見的環境變量
- PATH : 指定指令的搜尋路徑
- HOME : 指定使用者的主工作目錄(即使用者登陸到Linux系統中時,預設的目錄)
- SHELL : 目前Shell,它的值通常是/bin/bash。
像
ls
指令就是PATH去指定指令下搜尋。
就會去這些路徑下搜尋指令。
ls指令是被打包後的。
從理論上說,隻要我們把自己編寫的可執行程式也放入PATH可以找到的路徑下,那麼也能像這些指令一樣運作。
比如:
sudo cp -f myproc /usr/bin
(不推薦,可能會對系統造成影響)
export PATH=$PATH:可執行檔案的路徑
這樣就是添加了PATH查找的路徑
如果你破壞了環境變量,也沒什麼,重新進一下系統,伺服器會重新配置環境變量的。
HOME
可以顯示自己所處的工作目錄。也就是說這個環境變量儲存了目前所處的工作目錄
SHELL
顯示指令行解釋器的版本
env
可以顯示所有的環境變量
三個main函數的參數
argc-指令行參數個數
argv-指令行參數清單
envp-環境變量參數清單
後面的就是指令行參數。
解釋一下:
ls -a/-al...
這些都是指令,而 ls 是指令,而後面的-a -al 都是選項
就有點像,函數傳遞的不同的參數,而執行不同的指令
最後一個元素是NULL代表結尾。
代碼運作後
把環境變量全部給列印出來了。
如果把這個程式加入PATH中,就也能變成一個指令。
程序位址空間
先來回顧一下程式位址空間。
我們曾經認為的程式空間的位址就是這樣,可以直接對應與硬碟的空間。系統給程式配置設定了
2的32次方
(32位機下)的大小,總共4G,當時我還不了解是什麼意思。
現在,在了解了程序後,這個其實是叫程序位址空間更合适一些。
提問:這個程序位址空間是不是記憶體呢?
回答:不是(後面解答)
程序位址空間對應的位址不是實體位址
這是一份程式,還沒跑起來。有一個全局變量
val
,在父程序時,讓該程序休眠了1秒,也就是說讓子程序先跑。
在子程序的分流中執行了修改全局變量的值。
看看輸出結果。
子程序修改後的val的值變成20了,而父程序的值依舊是之前的100,而兩個程序分流後的val的位址依舊是一樣的,沒有改變
也就是說,val的位址沒變,但是子程序與父程序的val值取已經發生了改變。看似不可能,卻是實實在在的發生了改變。
先前提過,父程序建立了子程序,子程序會共享父程序的代碼和資料。
這也就證明了一點,這個程序位址空間,其對應的位址,根本就不是真實的實體位址,如果其對應的是真實的實體位址,那麼兩個位址一樣的空間,是不可能讀取到兩個不同的值
虛拟位址
程序位址空間對應的位址,其實是虛拟位址。也就是說,我們在代碼中操作的指針、位址,也不是真實位址,而也是虛拟位址。
而在Linux作業系統中,OS負責将實體位址轉換為虛拟位址。
再來一個聯想,列印輸出的val的虛拟位址在父子程序中是一樣的,而值是不一樣的也就是說,這個val的真實實體位址是不一樣的,隻不過作業系統将不同的實體位址處理成了相同的虛拟位址。
邏輯關系
頁表是OS處理虛拟與實體位址之間映射關系的方案,頁表使用一種算法,将實體位址處理成虛拟位址,且,讓實體位址不可見。同時對于子程序也是一樣的。
當程式運作時,子程序的分流修改了val的值,由于程序之間是具有獨立性的,子程序的資料被修改了,并不會影響父程序的資料。為了保證這種程序間的獨立性,OS會位這個val在記憶體中申請過一個空間,修改val的值,同時,這個新申請的空間的位址就又會被頁表處理,使虛拟位址不變。
對父程序也一樣
這次,我讓父程序先運作,先對val的值進行修改,子程序後運作。看看結果。
同樣,為了滿足程序之間的獨立性,并不是隻有子程序會讓OS為它在其它地方申請一塊空間來儲存修改後的值,對于任何程序都一樣,無論是父程序還是子程序。
程序位址空間的了解
其本質是記憶體中的一種資料結構
mm_struct
也就是意味着,結構體成員中存在不同的區域,而程序位址空間中存在不同的區域,靜态區,代碼區,資料區,堆區,棧區…
而這些被
mm_struct
這個結構體進行維護,是以說,結構體中就存在
stack_start、stack_end、heap_start、heap_end
差不多是這樣的成員。
而了解上,程序位址空間是實體記憶體的一種度量。實體記憶體本身是不具備任何衡量标準的,隻是代表一個個位址,而程序位址空間,就像是一把尺子上的刻度,将實體記憶體邏輯化、形象化、資料化了。
不僅僅是記憶體被劃分區域了,對于磁盤中的程式,可執行檔案(Linux中是ELF檔案),也同樣被劃分了不同的區域,代碼段,資料段…當這個檔案被執行時,會根據規則,将這些分段全部連結在一起。
為什麼會存在程序位址空間
- 有了程序位址空間提供的虛拟位址、虛拟空間,這樣可以防止代碼中有些操作直接通路實體位址,這樣,哪怕是通路了未申請區域,也僅僅是虛拟空間中的越界,,不會存在系統界别的越界通路,在虛拟空間中,作業系統是可以掌握的,最多就是代碼執行出錯,系統殺程序或者程序崩了,不會影響到真實的實體記憶體。同時,頁表可以對真實的實體位址進行保護,讓真實的實體位址完全不可見,可以保護記憶體。
- 大一統的好處:讓所有的程序都遵循同一個處理規則,所有的程序都有着相同的記憶體處理規則
- 每個程序都認為自己是獨占記憶體的,更好的完成程序的獨立性且合理的配置設定空間,友善作業系統合理的規劃所有控件的排程。
因為一個程序不可能是所有的代碼資料都同時加載到記憶體中,因為記憶體明顯不夠,且把CPU是一條指令一條指令的執行,一次性把所有代碼和資料都加載到記憶體中,大部分的代碼和資料并不會被執行,這樣非常占用資源,是以,排程算法會處理,當需要執行的部分,才會進行加載,配置設定資源空間。這樣作業系統可以很高效的配置設定資源。
将程序排程和記憶體管理進行解耦分離對于部分的程序排程,隻有當執行到的時候才會為其配置設定資源,減少記憶體負荷。