此篇文章主要會帶你介紹 linux 操作系統(tǒng),包括 Linux 本身、Linux 如何使用、以及系統(tǒng)調(diào)用和 Linux 是如何工作的。
Linux 簡介
UNIX 是一個(gè)交互式系統(tǒng),用于同時(shí)處理多進(jìn)程和多用戶同時(shí)在線。為什么要說 UNIX,那是因?yàn)?Linux 是由 UNIX 發(fā)展而來的,UNIX 是由程序員設(shè)計(jì),它的主要服務(wù)對象也是程序員。Linux 繼承了 UNIX 的設(shè)計(jì)目標(biāo)。從智能手機(jī)到汽車,超級計(jì)算機(jī)和家用電器,從家用臺式機(jī)到企業(yè)服務(wù)器,Linux 操作系統(tǒng)無處不在。
大多數(shù)程序員都喜歡讓系統(tǒng)盡量簡單,優(yōu)雅并具有一致性。舉個(gè)例子,從最底層的角度來講,一個(gè)文件應(yīng)該只是一個(gè)字節(jié)集合。為了實(shí)現(xiàn)順序存取、隨機(jī)存取、按鍵存取、遠(yuǎn)程存取只能是妨礙你的工作。相同的,如果命令
ls A*
意味著只列出以 A 為開頭的所有文件,那么命令
rm A*
應(yīng)該會移除所有以 A 為開頭的文件而不是只刪除文件名是 A* 的文件。這個(gè)特性也是最小吃驚原則(principle of least surprise)
最小吃驚原則一半常用于用戶界面和軟件設(shè)計(jì)。它的原型是:該功能或者特征應(yīng)該符合用戶的預(yù)期,不應(yīng)該使用戶感到驚訝和震驚。
一些有經(jīng)驗(yàn)的程序員通常希望系統(tǒng)具有較強(qiáng)的功能性和靈活性。設(shè)計(jì) Linux 的一個(gè)基本目標(biāo)是每個(gè)應(yīng)用程序只做一件事情并把他做好。所以編譯器只負(fù)責(zé)編譯的工作,編譯器不會產(chǎn)生列表,因?yàn)橛衅渌麘?yīng)用比編譯器做的更好。
很多人都不喜歡冗余,為什么在 cp 就能描述清楚你想干什么時(shí)候還使用 copy?這完全是在浪費(fèi)寶貴的 hacking time。為了從文件中提取所有包含字符串 ard 的行,Linux 程序員應(yīng)該輸入
grep ard f
Linux 接口
Linux 系統(tǒng)是一種金字塔模型的系統(tǒng),如下所示
應(yīng)用程序發(fā)起系統(tǒng)調(diào)用把參數(shù)放在寄存器中(有時(shí)候放在棧中),并發(fā)出 trap 系統(tǒng)陷入指令切換用戶態(tài)至內(nèi)核態(tài)。因?yàn)椴荒苤苯釉?C 中編寫 trap 指令,因此 C 提供了一個(gè)庫,庫中的函數(shù)對應(yīng)著系統(tǒng)調(diào)用。有些函數(shù)是使用匯編編寫的,但是能夠從 C 中調(diào)用。每個(gè)函數(shù)首先把參數(shù)放在合適的位置然后執(zhí)行系統(tǒng)調(diào)用指令。因此如果你想要執(zhí)行 read 系統(tǒng)調(diào)用的話,C 程序會調(diào)用 read 函數(shù)庫來執(zhí)行。這里順便提一下,是由 POSIX 指定的庫接口而不是系統(tǒng)調(diào)用接口。也就是說,POSIX 會告訴一個(gè)標(biāo)準(zhǔn)系統(tǒng)應(yīng)該提供哪些庫過程,它們的參數(shù)是什么,它們必須做什么以及它們必須返回什么結(jié)果。
除了操作系統(tǒng)和系統(tǒng)調(diào)用庫外,Linux 操作系統(tǒng)還要提供一些標(biāo)準(zhǔn)程序,比如文本編輯器、編譯器、文件操作工具等。直接和用戶打交道的是上面這些應(yīng)用程序。因此我們可以說 Linux 具有三種不同的接口:系統(tǒng)調(diào)用接口、庫函數(shù)接口和應(yīng)用程序接口
Linux 中的 GUI(Graphical User Interface) 和 UNIX 中的非常相似,這種 GUI 創(chuàng)建一個(gè)桌面環(huán)境,包括窗口、目標(biāo)和文件夾、工具欄和文件拖拽功能。一個(gè)完整的 GUI 還包括窗口管理器以及各種應(yīng)用程序。
Linux 上的 GUI 由 X 窗口支持,主要組成部分是 X 服務(wù)器、控制鍵盤、鼠標(biāo)、顯示器等。當(dāng)在 Linux 上使用圖形界面時(shí),用戶可以通過鼠標(biāo)點(diǎn)擊運(yùn)行程序或者打開文件,通過拖拽將文件進(jìn)行復(fù)制等。
Linux 組成部分
事實(shí)上,Linux 操作系統(tǒng)可以由下面這幾部分構(gòu)成
- 引導(dǎo)程序(Bootloader):引導(dǎo)程序是管理計(jì)算機(jī)啟動過程的軟件,對于大多數(shù)用戶而言,只是彈出一個(gè)屏幕,但其實(shí)內(nèi)部操作系統(tǒng)做了很多事情
- 內(nèi)核(Kernel):內(nèi)核是操作系統(tǒng)的核心,負(fù)責(zé)管理 CPU、內(nèi)存和外圍設(shè)備等。
- 初始化系統(tǒng)(Init System):這是一個(gè)引導(dǎo)用戶空間并負(fù)責(zé)控制守護(hù)程序的子系統(tǒng)。一旦從引導(dǎo)加載程序移交了初始引導(dǎo),它就是用于管理引導(dǎo)過程的初始化系統(tǒng)。
- 后臺進(jìn)程(Daemon):后臺進(jìn)程顧名思義就是在后臺運(yùn)行的程序,比如打印、聲音、調(diào)度等,它們可以在引導(dǎo)過程中啟動,也可以在登錄桌面后啟動
- 圖形服務(wù)器(Graphical server):這是在監(jiān)視器上顯示圖形的子系統(tǒng)。通常將其稱為 X 服務(wù)器或 X。
- 桌面環(huán)境(Desktop environment):這是用戶與之實(shí)際交互的部分,有很多桌面環(huán)境可供選擇,每個(gè)桌面環(huán)境都包含內(nèi)置應(yīng)用程序,比如文件管理器、Web 瀏覽器、游戲等
- 應(yīng)用程序(Applications):桌面環(huán)境不提供完整的應(yīng)用程序,就像 windows 和 macOS 一樣,Linux 提供了成千上萬個(gè)可以輕松找到并安裝的高質(zhì)量軟件。
Shell
盡管 Linux 應(yīng)用程序提供了 GUI ,但是大部分程序員仍偏好于使用命令行(command-line interface),稱為shell。用戶通常在 GUI 中啟動一個(gè) shell 窗口然后就在 shell 窗口下進(jìn)行工作。
shell 命令行使用速度快、功能更強(qiáng)大、而且易于擴(kuò)展、并且不會帶來肢體重復(fù)性勞損(RSI)。
下面會介紹一些最簡單的 bash shell。當(dāng) shell 啟動時(shí),它首先進(jìn)行初始化,在屏幕上輸出一個(gè) 提示符(prompt),通常是一個(gè)百分號或者美元符號,等待用戶輸入
等用戶輸入一個(gè)命令后,shell 提取其中的第一個(gè)詞,這里的詞指的是被空格或制表符分隔開的一連串字符。假定這個(gè)詞是將要運(yùn)行程序的程序名,那么就會搜索這個(gè)程序,如果找到了這個(gè)程序就會運(yùn)行它。然后 shell 會將自己掛起直到程序運(yùn)行完畢,之后再嘗試讀入下一條指令。shell 也是一個(gè)普通的用戶程序。它的主要功能就是讀取用戶的輸入和顯示計(jì)算的輸出。shell 命令中可以包含參數(shù),它們作為字符串傳遞給所調(diào)用的程序。比如
cp src dest
會調(diào)用 cp 應(yīng)用程序并包含兩個(gè)參數(shù) src 和 dest。這個(gè)程序會解釋第一個(gè)參數(shù)是一個(gè)已經(jīng)存在的文件名,然后創(chuàng)建一個(gè)該文件的副本,名稱為 dest。
并不是所有的參數(shù)都是文件名,比如下面
head -20 file
第一個(gè)參數(shù) -20,會告訴 head 應(yīng)用程序打印文件的前 20 行,而不是默認(rèn)的 10 行。控制命令操作或者指定可選值的參數(shù)稱為標(biāo)志(flag),按照慣例標(biāo)志應(yīng)該使用 - 來表示。這個(gè)符號是必要的,比如
head 20 file
是一個(gè)完全合法的命令,它會告訴 head 程序輸出文件名為 20 的文件的前 10 行,然后輸出文件名為 file 文件的前 10 行。Linux 操作系統(tǒng)可以接受一個(gè)或多個(gè)參數(shù)。
為了更容易的指定多個(gè)文件名,shell 支持 魔法字符(magic character),也被稱為通配符(wild cards)。比如,* 可以匹配一個(gè)或者多個(gè)可能的字符串
ls *.c
告訴 ls 列舉出所有文件名以 .c 結(jié)束的文件。如果同時(shí)存在多個(gè)文件,則會在后面進(jìn)行并列。
另一個(gè)通配符是問號,負(fù)責(zé)匹配任意一個(gè)字符。一組在中括號中的字符可以表示其中任意一個(gè),因此
ls [abc]*
會列舉出所有以 a、b 或者 c 開頭的文件。
shell 應(yīng)用程序不一定通過終端進(jìn)行輸入和輸出。shell 啟動時(shí),就會獲取 標(biāo)準(zhǔn)輸入、標(biāo)準(zhǔn)輸出、標(biāo)準(zhǔn)錯(cuò)誤文件進(jìn)行訪問的能力。
標(biāo)準(zhǔn)輸出是從鍵盤輸入的,標(biāo)準(zhǔn)輸出或者標(biāo)準(zhǔn)錯(cuò)誤是輸出到顯示器的。許多 Linux 程序默認(rèn)是從標(biāo)準(zhǔn)輸入進(jìn)行輸入并從標(biāo)準(zhǔn)輸出進(jìn)行輸出。比如
sort
會調(diào)用 sort 程序,會從終端讀取數(shù)據(jù)(直到用戶輸入 ctrl-d 結(jié)束),根據(jù)字母順序進(jìn)行排序,然后將結(jié)果輸出到屏幕上。
通常還可以重定向標(biāo)準(zhǔn)輸入和標(biāo)準(zhǔn)輸出,重定向標(biāo)準(zhǔn)輸入使用 < 后面跟文件名。標(biāo)準(zhǔn)輸出可以通過一個(gè)大于號 > 進(jìn)行重定向。允許一個(gè)命令中重定向標(biāo)準(zhǔn)輸入和輸出。例如命令
sort <in >out
會使 sort 從文件 in 中得到輸入,并把結(jié)果輸出到 out 文件中。由于標(biāo)準(zhǔn)錯(cuò)誤沒有重定向,所以錯(cuò)誤信息會直接打印到屏幕上。從標(biāo)準(zhǔn)輸入讀入,對其進(jìn)行處理并將其寫入到標(biāo)準(zhǔn)輸出的程序稱為 過濾器。
考慮下面由三個(gè)分開的命令組成的指令
sort <in >temp;head -30 <temp;rm temp
首先會調(diào)用 sort 應(yīng)用程序,從標(biāo)準(zhǔn)輸入 in 中進(jìn)行讀取,并通過標(biāo)準(zhǔn)輸出到 temp。當(dāng)程序運(yùn)行完畢后,shell 會運(yùn)行 head ,告訴它打印前 30 行,并在標(biāo)準(zhǔn)輸出(默認(rèn)為終端)上打印。最后,temp 臨時(shí)文件被刪除。輕輕的,你走了,你揮一揮衣袖,不帶走一片云彩。
命令行中的第一個(gè)程序通常會產(chǎn)生輸出,在上面的例子中,產(chǎn)生的輸出都不 temp 文件接收。然而,Linux 還提供了一個(gè)簡單的命令來做這件事,例如下面
sort <in | head -30
上面 | 稱為豎線符號,它的意思是從 sort 應(yīng)用程序產(chǎn)生的排序輸出會直接作為輸入顯示,無需創(chuàng)建、使用和移除臨時(shí)文件。由管道符號連接的命令集合稱為管道(pipeline)。例如如下
grep cxuan *.c | sort | head -30 | tail -5 >f00
對任意以 .t 結(jié)尾的文件中包含 cxuan 的行被寫到標(biāo)準(zhǔn)輸出中,然后進(jìn)行排序。這些內(nèi)容中的前 30 行被 head 出來并傳給 tail ,它又將最后 5 行傳遞給 foo。這個(gè)例子提供了一個(gè)管道將多個(gè)命令連接起來。
可以把一系列 shell 命令放在一個(gè)文件中,然后將此文件作為輸入來運(yùn)行。shell 會按照順序?qū)λ麄冞M(jìn)行處理,就像在鍵盤上鍵入命令一樣。包含 shell 命令的文件被稱為 shell 腳本(shell scripts)。
推薦一個(gè) shell 命令的學(xué)習(xí)網(wǎng)站:https://www.shellscript.sh/
shell 腳本其實(shí)也是一段程序,shell 腳本中可以對變量進(jìn)行賦值,也包含循環(huán)控制語句比如 if、for、while 等,shell 的設(shè)計(jì)目標(biāo)是讓其看起來和 C 相似(There is no doubt that C is father)。由于 shell 也是一個(gè)用戶程序,所以用戶可以選擇不同的 shell。
Linux 應(yīng)用程序
Linux 的命令行也就是 shell,它由大量標(biāo)準(zhǔn)應(yīng)用程序組成。這些應(yīng)用程序主要有下面六種
- 文件和目錄操作命令
- 過濾器
- 文本程序
- 系統(tǒng)管理
- 程序開發(fā)工具,例如編輯器和編譯器
- 其他
除了這些標(biāo)準(zhǔn)應(yīng)用程序外,還有其他應(yīng)用程序比如 Web 瀏覽器、多媒體播放器、圖片瀏覽器、辦公軟件和游戲程序等。
我們在上面的例子中已經(jīng)見過了幾個(gè) Linux 的應(yīng)用程序,比如 sort、cp、ls、head,下面我們再來認(rèn)識一下其他 Linux 的應(yīng)用程序。
我們先從幾個(gè)例子開始講起,比如
cp a b
是將 a 復(fù)制一個(gè)副本為 b ,而
mv a b
是將 a 移動到 b ,但是刪除原文件。
上面這兩個(gè)命令有一些區(qū)別,cp 是將文件進(jìn)行復(fù)制,復(fù)制完成后會有兩個(gè)文件 a 和 b;而 mv 相當(dāng)于是文件的移動,移動完成后就不再有 a 文件。cat 命令可以把多個(gè)文件內(nèi)容進(jìn)行連接。使用 rm 可以刪除文件;使用 chmod 可以允許所有者改變訪問權(quán)限;文件目錄的的創(chuàng)建和刪除可以使用 mkdir 和 rmdir 命令;使用 ls 可以查看目錄文件,ls 可以顯示很多屬性,比如大小、用戶、創(chuàng)建日期等;sort 決定文件的顯示順序
Linux 應(yīng)用程序還包括過濾器 grep,grep 從標(biāo)準(zhǔn)輸入或者一個(gè)或多個(gè)輸入文件中提取特定模式的行;sort 將輸入進(jìn)行排序并輸出到標(biāo)準(zhǔn)輸出;head 提取輸入的前幾行;tail 提取輸入的后面幾行;除此之外的過濾器還有 cut 和 paste,允許對文本行的剪切和復(fù)制;od 將輸入轉(zhuǎn)換為 ASCII ;tr 實(shí)現(xiàn)字符大小寫轉(zhuǎn)換;pr 為格式化打印輸出等。
程序編譯工具使用 gcc ;
make 命令用于自動編譯,這是一個(gè)很強(qiáng)大的命令,它用于維護(hù)一個(gè)大的程序,往往這類程序的源碼由許多文件構(gòu)成。典型的,有一些是 header files 頭文件,源文件通常使用 include 指令包含這些文件,make 的作用就是跟蹤哪些文件屬于頭文件,然后安排自動編譯的過程。
下面列出了 POSIX 的標(biāo)準(zhǔn)應(yīng)用程序
程序應(yīng)用ls列出目錄cp復(fù)制文件head顯示文件的前幾行make編譯文件生成二進(jìn)制文件cd切換目錄mkdir創(chuàng)建目錄chmod修改文件訪問權(quán)限ps列出文件進(jìn)程pr格式化打印rm刪除一個(gè)文件rmdir刪除文件目錄tail提取文件最后幾行tr字符集轉(zhuǎn)換grep分組cat將多個(gè)文件連續(xù)標(biāo)準(zhǔn)輸出od以八進(jìn)制顯示文件cut從文件中剪切paste從文件中粘貼
Linux 內(nèi)核結(jié)構(gòu)
在上面我們看到了 Linux 的整體結(jié)構(gòu),下面我們從整體的角度來看一下 Linux 的內(nèi)核結(jié)構(gòu)
內(nèi)核直接坐落在硬件上,內(nèi)核的主要作用就是 I/O 交互、內(nèi)存管理和控制 CPU 訪問。上圖中還包括了 中斷 和 調(diào)度器,中斷是與設(shè)備交互的主要方式。中斷出現(xiàn)時(shí)調(diào)度器就會發(fā)揮作用。這里的低級代碼停止正在運(yùn)行的進(jìn)程,將其狀態(tài)保存在內(nèi)核進(jìn)程結(jié)構(gòu)中,并啟動驅(qū)動程序。進(jìn)程調(diào)度也會發(fā)生在內(nèi)核完成一些操作并且啟動用戶進(jìn)程的時(shí)候。圖中的調(diào)度器是 dispatcher。
注意這里的調(diào)度器是 dispatcher 而不是 scheduler,這兩者是有區(qū)別的
scheduler 和 dispatcher 都是和進(jìn)程調(diào)度相關(guān)的概念,不同的是 scheduler 會從幾個(gè)進(jìn)程中隨意選取一個(gè)進(jìn)程;而 dispatcher 會給 scheduler 選擇的進(jìn)程分配 CPU。
然后,我們把內(nèi)核系統(tǒng)分為三部分。
- I/O 部分負(fù)責(zé)與設(shè)備進(jìn)行交互以及執(zhí)行網(wǎng)絡(luò)和存儲 I/O 操作的所有內(nèi)核部分。
從圖中可以看出 I/O 層次的關(guān)系,最高層是一個(gè)虛擬文件系統(tǒng),也就是說不管文件是來自內(nèi)存還是磁盤中,都是經(jīng)過虛擬文件系統(tǒng)中的。從底層看,所有的驅(qū)動都是字符驅(qū)動或者塊設(shè)備驅(qū)動。二者的主要區(qū)別就是是否允許隨機(jī)訪問。網(wǎng)絡(luò)驅(qū)動設(shè)備并不是一種獨(dú)立的驅(qū)動設(shè)備,它實(shí)際上是一種字符設(shè)備,不過網(wǎng)絡(luò)設(shè)備的處理方式和字符設(shè)備不同。
上面的設(shè)備驅(qū)動程序中,每個(gè)設(shè)備類型的內(nèi)核代碼都不同。字符設(shè)備有兩種使用方式,有一鍵式的比如 vi 或者 emacs ,需要每一個(gè)鍵盤輸入。其他的比如 shell ,是需要輸入一行按回車鍵將字符串發(fā)送給程序進(jìn)行編輯。
網(wǎng)絡(luò)軟件通常是模塊化的,由不同的設(shè)備和協(xié)議來支持。大多數(shù) Linux 系統(tǒng)在內(nèi)核中包含一個(gè)完整的硬件路由器的功能,但是這個(gè)不能和外部路由器相比,路由器上面是協(xié)議棧,包括 TCP/IP 協(xié)議,協(xié)議棧上面是 socket 接口,socket 負(fù)責(zé)與外部進(jìn)行通信,充當(dāng)了門的作用。
磁盤驅(qū)動上面是 I/O 調(diào)度器,它負(fù)責(zé)排序和分配磁盤讀寫操作,以盡可能減少磁頭的無用移動。
- I/O 右邊的是內(nèi)存部件,程序被裝載進(jìn)內(nèi)存,由 CPU 執(zhí)行,這里會涉及到虛擬內(nèi)存的部件,頁面的換入和換出是如何進(jìn)行的,壞頁面的替換和經(jīng)常使用的頁面會進(jìn)行緩存。
- 進(jìn)程模塊負(fù)責(zé)進(jìn)程的創(chuàng)建和終止、進(jìn)程的調(diào)度、Linux 把進(jìn)程和線程看作是可運(yùn)行的實(shí)體,并使用統(tǒng)一的調(diào)度策略來進(jìn)行調(diào)度。
在內(nèi)核最頂層的是系統(tǒng)調(diào)用接口,所有的系統(tǒng)調(diào)用都是經(jīng)過這里,系統(tǒng)調(diào)用會觸發(fā)一個(gè) trap,將系統(tǒng)從用戶態(tài)轉(zhuǎn)換為內(nèi)核態(tài),然后將控制權(quán)移交給上面的內(nèi)核部件。