文章目錄
- 一. 認識作業系統
- 1. 什么是作業系統
- 2. 為什么要有作業系統
- 3. 計算機的體系結構
- 二. 行程
- 1. 行程概念
- 2. 描述行程的物體 --- PCB
- 2.1 行程標示符
- 2.2 狀態
- 2.3 優先級
- 2.4 程式計數器
- 2.5 記憶體指標
- 2.6 背景關系資料
- 3. 行程的組織方式
- 三. 行程的虛擬地址空間
- 1. 什么是虛擬地址空間
- 2. 為什么使用虛擬地址空間
- 3. 虛擬地址空間的使用
- 4. 從結構上理解行程
- 四. 環境變數
- 1. 什么是環境變數
- 2. 環境變數的作用
- 3. 環境變數的組織方式
- 4. 常見的環境變數介紹
- 4.1 PATH
- 4.2 HOME
- 4.3 SHELL
一. 認識作業系統
1. 什么是作業系統
作業系統是做管理軟硬體作業的軟體,
2. 為什么要有作業系統
作業系統在計算機系統里處于承上啟下的低位:向下通過驅動程式與硬體間接互動,充分發揮硬體的功能;向上為用戶提供介面,給用戶提供穩定、高效、簡易的使用環境,
3. 計算機的體系結構
自下而上:硬體部分 -> 系統軟體部分 -> 用戶部分

驅動程式
什么是驅動程式?
驅動程式全稱:設備驅動程式(Device driver),是一種特殊的程式,說特殊點就是一段代碼,其中包含有關硬體設備的資訊在,Linux內核中 '驅動程式的代碼 ',會占到 ”OS作業系統的內核原始碼“的 70%,
并不是說所有硬體設備都需要驅動程式才能使用,像CPU、記憶體等就不需要,因為這些硬體對于一臺電腦而言過于重要,他們都是BIOS所直接支持的硬體,
為什么要有驅動程式?
驅動程式完成硬體 ‘設備的電子信號’ 和 ‘計算機系統的代碼指令’ 之間的翻譯,是硬體和作業系統之間的"橋梁“,
我們的計算機使用的是代碼指令,而外部硬體設備識別的是電子信號,這是兩個完全不同的東西,所以我們的計算機與外設要通過’驅動程式’ 進行互動通信,
驅動程式如何作業?
比如我們要播放音樂:
- OS會發送指令給 ‘聲卡–驅動程式’,
- '聲卡–驅動程式’收到指令后,將其’翻譯’成 聲卡能聽懂的 ‘電子信號’,
- 然后把這個’電子信號’,給到聲卡,讓聲卡播放音樂,
系統呼叫
作業系統對外會表現為一個整體,但是會暴露自己的部分介面,供上層開發使用,這部分由作業系統提供的介面,叫做系統呼叫,系統呼叫是通向作業系統本身的介面,是面向底層硬體的,通過系統呼叫,可以使得用戶態運行的行程與硬體設備(如CPU、磁盤、列印機等)進行互動,
例如 read: 從打開的檔案或設備中讀取資料 ,內核將呼叫內核相關函式sys_read()來實作,用戶程式不能直接呼叫這些函式,這些函式運行在內核態,CPU 通過軟中斷切換到內核態開始執行內核系統呼叫函式,
庫函式
系統呼叫在使用上,功能比較基礎,對用戶的要求相對也比較高,所以,有心的開發者可以對部分系統呼叫進行適度封裝,從而形成庫,方法是把一些常用到的函式編完放到一個檔案里,供不同的人進行呼叫,一般放在.lib檔案中,有了庫,就很有利于上層用戶或者開發者進行二次開發,
例如 read()函式根據傳入引數,直接就能讀檔案,而背后隱藏的比如檔案在硬碟的哪個磁道,哪個扇區,加載到記憶體的哪個位置等等這些操作,我們是不必關心的,這些操作里面自然也包含了系統呼叫,
系統呼叫和庫函式的關系
系統呼叫和庫函式是上下級的關系,一般涉及到硬體的庫函式都要經過系統呼叫,再比如演算法的庫函式就不用經過系統呼叫,
二. 行程
1. 行程概念
行程是程式在一個資料集合上運行的程序,是加載到記憶體當中的程式,通俗理解就是正在進行中的程式,
行程 = 代碼 + 相關的資料 + PCB (行程控制塊Process Control Block)
比如我們運行了一個程式proc,利用ps指令查看他的行程資訊

| USER | PID | %CPU | %MEM | VSZ | RSS | TTY | STAT | START | TIME | COMMAND |
|---|---|---|---|---|---|---|---|---|---|---|
| 行程的所有者 | 行程ID號 | 運算器占用率 | 記憶體占用率 | 虛擬記憶體使用量(單位是KB) | 占用的固定記憶體量(單位是KB) | 所在終端 | 行程狀態 | 被啟動的時間 | 實際使用CPU的時間 | 命令名稱與引數 |
| ljj | 21615 | 97.4 | 0.0 | 13224 | 1288 | pts/0 | R+ | 18:27 | 8:17 | ./proc |
行程和程式的關系
行程是動態的,程式是靜態的,行程有創建,執行,消亡,所以行程物體是有生命周期的,而程式只是一組有序指令的集合,
2. 描述行程的物體 — PCB
為了描述和控制行程的運行,系統為每個行程定義了一個結構體——行程控制塊PCB(Process Control Block),他包含了很多與行程相關的資訊,PCB是行程存在的唯一標志, Linux 系統中的PCB叫做task_struct ,而在 Windows 作業系統中則使用一個執行體行程塊EPROCESS(全稱Execute Process)來表示行程物件的基本屬性,

2.1 行程標示符
PID:全稱Procss ID,描述本行程的唯一標示符,用來區別其他行程,
PPID:全稱Parent Procss ID,父行程的識別符號,

2.2 狀態
標識行程當前所處的任務狀態,

①:R運行狀態(running)
該狀態不意味著行程一定在運行中,它表明行程要么是正在CPU里運行,要么在運行佇列里等待運行,

前臺運行與后臺運行
./ proc,運行可執行程式proc,我們看到它的運行狀態是R+,說明該行程在前臺運行,前臺運行的行程只能有一個,

./proc &,在最后位置加上取地址符號來運行程式proc,可以看到它的狀態是R,說明該行程在后臺運行,后臺運行的程式可以有無數個,

②:S睡眠狀態(sleep)
也叫作可中斷睡眠狀態(interruptible sleep),行程在等待事件發生而被放入對應事件的等待佇列中,當這些事件發生時(由外部中斷觸發、或由其他行程觸發),對應的等待佇列中的一個或多個行程將被喚醒,
通過top命令我們會看到,大多數行程都處于這個狀態,因為行程很多,而cpu一次只能執行一個(這里指單核的處理器),如果不是絕大多數行程都在睡眠,CPU又怎么回應得過來,

③:D磁盤休眠狀態(Disk sleep)
也叫作不可中斷睡眠狀態(uninterruptible sleep),行程不回應異步信號,即不受任何信號影響,通常會等待IO的結束才喚醒,看這個名字就知道與硬體有關,比如行程呼叫read系統呼叫對某個設備檔案進行讀操作,而read系統呼叫最終執行到對應設備驅動的代碼,并與對應的物理設備進行互動,這個時候通常進入這個狀態以對行程進行保護,以避免行程與設備互動的程序被打斷,造成設備陷入不可控的狀態,
④:T停止狀態(stopped)
向行程發送一個SIGSTOP信號,它就會因回應該信號而進T狀態(除非該行程本身處于D狀態而不回應信號),此時向行程發送一個SIGCONT信號,可以讓其從T狀態恢復到R狀態,
當行程正在被跟蹤時,它處于T這個特殊的狀態,“正在被跟蹤”指的是行程暫停下來,等待跟蹤它的行程對它進行進一步的操作,比如我們在除錯到斷點處時行程就處于T狀態,
⑤:X死亡狀態(dead)
行程即將被銷毀,這個狀態只是一個回傳狀態,銷毀程序是非常短暫的,幾乎不可能通過ps命令捕捉到,
⑥:Z(zombie)-僵尸行程
概念
子行程退出,父行程運行,但父行程沒有呼叫 wait 或者 waitpid 函式完成對子行程資源的最后清理,即退出信號的讀取,這時子行程就處于僵尸狀態(Z),
成因
子行程在被銷毀時作業系統會來清理該行程的資源,除了他的task_struct,因為他要告訴關心它的父行程,你交給我的任務,我辦的結果如何?子行程在退出的程序中,內核會給其父行程發送一個信號,通知父行程來“收尸”,這個信號默認是SIGCHLD,父行程收到后可以通過wait系列的系統呼叫(如wait、waitid)來等待某個或某些子行程的退出并獲取他的退出碼和一些統計資訊,順帶銷毀子行程最后的task_struct結構,
結果
只要父行程不退出,這個僵尸狀態的子行程就一直存在,導致記憶體泄漏,那么如果父行程退出了呢,誰又來給子行程“收尸”?這時它就更慘了,變成僵尸不說還沒人認領,它就又屬于孤兒行程(子行程沒完全退出,父行程先退出)哪一類,
這些孤兒行程會被托管給退出行程所在行程組的下一個行程(如果存在的話),或者是1號行程,
1號行程,即pid為1的行程,又稱init行程,linux系統啟動后,第一個被創建的用戶態行程就是init行程,它有三項使命:
- 執行系統初始化腳本,創建一系列的行程(其后創建的所有行程都是init行程的子孫);
- 在一個死回圈中等待其子行程的退出事件,并呼叫waitid函式來完成子行程的“收尸”作業;
- 收養孤兒行程,如果是僵尸的孤兒行程,會通過wait系列的系統呼叫從而讓子行程徹底退出;不是僵尸行程的話沒什么危害,因為該行程只是父行程換成了 init ,依然可以正常運行,
2.3 優先級
優先級決定的是在已經可執行的前提下誰先執行的問題,權限決定能否被執行,PRI越小,優先級越高,優先執行,就好像考試排名的數字一樣,越小越靠,
在linux或者unix系統中,用ps –l命令可以看到行程相關的優先級資訊:

- PRI(全稱priority)=PRI_old + NI(全稱NICE)
- PRI_old是常量80,而NI的取值范圍是-20至19,一共40個級別
- PRI值越小其優先級會變高,則其越快被執行
- 一般是通過修改NI來改變一個行程的優先級的
用top命令更改已存在行程的nice
首先輸入top,進入到實時行程頁面

輸入r,提示你輸入要修改行程的PID,

最后輸入你要修改的NI的值,輸入如果超出NI的范圍,就按邊界的值算,

2.4 程式計數器
程式中即將被執行的下一條指令的地址,當運行中的行程被其他優先級更高的行程占用或該行程執行的時間片結束時,task_struct里的程式計數器會保存下一條執行指令的地址,下一次運行時可以從中斷讀取,
2.5 記憶體指標
包括程式代碼和行程相關資料的指標,還有和其他行程共享的記憶體塊的指標,行程運行時可以讀取代碼和相關資料,
2.6 背景關系資料
行程執行時放在處理器的暫存器中的還沒來得及寫入目標區域的資料,這樣下次行程在CPU運行時,可以從暫存器中讀取到這些資料,

行程退出有兩種情況:
①時間片到了,行程的時間片到了就必須退出,等待下一次運行,
②搶占,當行程在運行時并且時間片還沒到,來了個優先級更高的就會搶占這個運行中的行程,
行程的代碼還沒有全部跑完就退出,退出去它會把還沒有來得及寫入目標區域的資料就會放到處理器的暫存器中,下次運行時可以讀取這些資料,
3. 行程的組織方式
每個task_struct中都有一個tasks的域來連接到行程鏈表上去,把一個個行程以雙向鏈表的形參式組織起來,

三. 行程的虛擬地址空間
1. 什么是虛擬地址空間
在回答這個問題之前我們,我們先來感受一下

我們發現,輸出出來的變數值和地址是一模一樣的,很好理解呀,因為子行程復刻了父行程,父子并沒有對全域變數進行任何修改,可是將代碼稍加改動:在父行程的判斷條件里修改全域變數g_val=200

我們發現,父子行程中輸出全域變數地址還是一致的,但是變數值不一樣!我們可以得到如下結論:
- 變數內容不一樣,所以父子行程輸出的變數絕對不是同一個變數
- 但地址值是一樣的,說明該地址不是物理地址!
- 在Linux地址下,這種地址叫做虛擬地址,其實他也是一種結構體叫做mm_struct,其實我們在用C/C++語言所看到的地址,全部都是虛擬地址!物理地址,用戶一概看不到,由OS統一管理,
在學習C語言時,老師給我們畫過這樣的記憶體空間布局圖:

試想一個地址空間就占4G,如果每個行程都有一個他自己的地址空間的話那就太消耗記憶體了,其實我們看到的這個地址空間是虛擬的,目的是為了讓行程以為自己擁有了一塊連續的完整的空間,實際這些連續的虛擬的地址都是通過頁表映射到真實物理空間的,也就是說每個行程都有屬于自己的一套虛擬地址空間,但其實他們共同映射到同一塊真實的物理空間,
2. 為什么使用虛擬地址空間
①雨露均沾
對于開發者:展示給我們連續的地址,有利于我們學習和開發、管理,而且我不用操心資料放在記憶體的哪里?能不能放得下?這些都由作業系統幫我們完成;對于物理記憶體:資料離散式存盤,充分利用了物理空間,提高記憶體利用率,
②保護記憶體
如果大家都可以直接在同一塊記憶體上修改,這樣操作不安全,缺乏對記憶體的訪問控制,而通過虛擬地址空間和頁表的映射,我們只能間接操作記憶體,一旦間接了,作業系統就可以插一把手(從虛擬到物理的轉化由作業系統來完成),可以更好的保護記憶體,
③行程獨立
頁表可以映射物理記憶體,每一個行程都只能訪問自己虛擬地址映射的物理記憶體,這樣多行程運行,獨享各種資源,在運行期間不會相互干擾,
- 頁表中有標志物理記憶體上資料的讀寫權限,若發現你要修改只讀資料,那么就報記憶體訪問錯誤,
- 當對空指標訪問時,即訪問0號虛擬地址,程式發生段錯誤,虛擬記憶體的0號位置實際上是有空間的,不過不能讀寫,這是虛擬地址空間規定,
3. 虛擬地址空間的使用
子行程的創建
fork后子行程擁有了自己的pcb、虛擬地址空間和它自己的頁表,但是起初父子行程共用代碼(因為代碼不會被修改)和相關物理記憶體資料,為了節省空間所以共用,不單獨在為子行程另外開辟一樣的物理記憶體資料和代碼,對于物理空間,只有當子行程或父行程單獨對代碼里的物件進行寫操作時,才會在物理記憶體上單獨開辟另外一塊空間,并更改子(或父)行程頁表的對應關系,
接下來就可以解釋前面的現象了:
子行程創建后,復刻和父行程一樣的虛擬地址空間,因為父子都沒有修改代碼里物件的值,為了節省物理記憶體,各自的頁表映射到同一塊物理空間,


接下來我們在父行程里修改g_val = 200,這時作業系統要在物理記憶體上重新開辟一塊空間存父行程修改后的g_val的值,并更改父行程頁表的映射關系,但是其它沒修改的資料依然共享,這樣做可以極致地節省記憶體空間,即能一起用就一起用,其中一個要進行寫操作時就重新給這個要寫的變數另外開辟一塊空間,這也叫作寫時拷貝,顧名思義寫的時候才拷貝,


子行程從fork()之后運行
我們是在父行程運行的程序當中創建的子行程,那么子行程從哪里開始運行呢?

要注意的是printf函式是行緩沖函式,滿足下面條件之一才會將緩沖區內容刷到對應的檔案(一般是stdout即顯示幕)中,
- 緩沖區被填滿
- 寫入的字符中有’\n’或’\r’
- 呼叫fflush手動重繪
- 程式結束

4. 從結構上理解行程
行程就是代碼 + 物理空間上的資料 + 一堆資料結構的集合,這些資料結構包括:PCB(行程控制塊)、mm_struct(行程的虛擬地址空間)、頁表,
四. 環境變數
1. 什么是環境變數
環境變數一般是指作業系統中指定作業系統運行環境的一些引數,它相當于一個指標,想要查看變數的值,需要加上“$”,類似于訪問指標物件所指向內容的解參考操作,
環境變數的查看方式有兩種
1. env (查看所有的環境變數以及他們的值)
2. echo $環境變數名 (查看特定環境變數的值)
2. 環境變數的作用
用來存盤一些資訊,這些資訊可以被系統訪問,也可以被我們的應用程式訪問,
3. 環境變數的組織方式
每個程式都有一張環境表,環境表是一個字串指標陣列,每個指標指向一個以‘\0’結尾的環境字串,

main函式的第三個引數就是環境表,

運行上圖的程式我們可以列印所有的環境變數和他們對應的值

補充:關于main函式的前兩個引數

執行可執行程式后main函式命令列引數的第一個引數是我們執行該程式的命令,即argv[0] = 該程式的執行命令,

如果我們在輸入執行程式的命令(./text)時加上選項,這些選項會按照先后順序加入到命令列引數的指標陣列里,

我們可以在代碼里讀取這些命令列引數,根據他們的值定義自己想要的操作,

4. 常見的環境變數介紹
4.1 PATH
指定命令的默認搜索路徑,
我們平時用的命令如:ls、pwd等都是作業系統在PATH環境變數里的這些路徑下尋找并執行的,

這也就解釋了為什么我們自己生成的可執行程式(也算是命令)在執行時必須加上路徑(比如是./text,即執行當前目錄下的text),因為PATH下的路徑里面沒有存放我們自己寫的可執行程式;而其他系統命令只要輸入它們的名稱就可以運行,是因為PATH下的路徑里有存放著這些命令,

我們也可以把自己的程式放到PATH下的路徑里,這樣不論在哪我們直接輸入自己的可執行程式名稱就可以運行,
舉個例子下面是我們自己寫的可執行程式:

方法一:永久的
把我們的可執行程式拷貝到PATH路徑里,比如我們把我們的可執行程式text拷貝到/use/bin里,

添加完成后可以直接運行text,不用在說嗎路徑,

不過不推薦這樣使用,因為他會污染linux自帶的命名池,洗掉的話就是用絕對路徑把這個可執行程式洗掉就行,

方法二:臨時的
這里的臨時的意思是,退出登錄后即失效,下一次登錄后就不存在了,方法是把這個可執行程式所在路徑的絕對路徑加到PATH環境變數里,這樣你只要輸入程式的名稱,系統就會到PATH里找到你添加的路徑,并在這個路徑下去找你的可執行程式,
//中間以冒號隔開,沒有空格
PATH=$PATH:<PATH1>:<PATH2>:<PATH3>:------:<PATHN>
還是以我們的text程式為例

我們添加的路徑在退出登錄后會自動洗掉,其實每一次登陸作業系統都會重新幫用戶配置環境變數,默認的都是作業系統給我們安排好的,所以自己的也需要重新配置,
4.2 HOME
指定用戶的主作業目錄(即用戶登陸到Linux系統中時,默認的目錄)

- 從結果上看HOME的值等價于執行指令執行 cd ~ + pwd的結果,
- cd ~這里的波浪號對應HOME的值,
4.3 SHELL
該變數的值為用戶當前使用的決議器 ,就是當前Shell,在linux中它的值通常是/bin/bash,

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/325577.html
標籤:其他
