Linux 作業系統!開篇!!!

程式設計師cxuan發表於2020-07-02

此篇文章主要會帶你介紹 Linux 作業系統,包括 Linux 本身、Linux 如何使用、以及系統呼叫和 Linux 是如何工作的。

Linux 簡介

UNIX 是一個互動式系統,用於同時處理多程式和多使用者同時線上。為什麼要說 UNIX,那是因為 Linux 是由 UNIX 發展而來的,UNIX 是由程式設計師設計,它的主要服務物件也是程式設計師。Linux 繼承了 UNIX 的設計目標。從智慧手機到汽車,超級計算機和家用電器,從家用桌上型電腦到企業伺服器,Linux 作業系統無處不在。

大多數程式設計師都喜歡讓系統儘量簡單,優雅並具有一致性。舉個例子,從最底層的角度來講,一個檔案應該只是一個位元組集合。為了實現順序存取、隨機存取、按鍵存取、遠端存取只能是妨礙你的工作。相同的,如果命令

ls A*

意味著只列出以 A 為開頭的所有檔案,那麼命令

rm A*

應該會移除所有以 A 為開頭的檔案而不是隻刪除檔名是 A* 的檔案。這個特性也是最小吃驚原則(principle of least surprise)

最小吃驚原則一半常用於使用者介面和軟體設計。它的原型是:該功能或者特徵應該符合使用者的預期,不應該使使用者感到驚訝和震驚。

一些有經驗的程式設計師通常希望系統具有較強的功能性和靈活性。設計 Linux 的一個基本目標是每個應用程式只做一件事情並把他做好。所以編譯器只負責編譯的工作,編譯器不會產生列表,因為有其他應用比編譯器做的更好。

很多人都不喜歡冗餘,為什麼在 cp 就能描述清楚你想幹什麼時候還使用 copy?這完全是在浪費寶貴的 hacking time。為了從檔案中提取所有包含字串 ard 的行,Linux 程式設計師應該輸入

grep ard f

Linux 介面

Linux 系統是一種金字塔模型的系統,如下所示

應用程式發起系統呼叫把引數放在暫存器中(有時候放在棧中),併發出 trap 系統陷入指令切換使用者態至核心態。因為不能直接在 C 中編寫 trap 指令,因此 C 提供了一個庫,庫中的函式對應著系統呼叫。有些函式是使用匯編編寫的,但是能夠從 C 中呼叫。每個函式首先把引數放在合適的位置然後執行系統呼叫指令。因此如果你想要執行 read 系統呼叫的話,C 程式會呼叫 read 函式庫來執行。這裡順便提一下,是由 POSIX 指定的庫介面而不是系統呼叫介面。也就是說,POSIX 會告訴一個標準系統應該提供哪些庫過程,它們的引數是什麼,它們必須做什麼以及它們必須返回什麼結果。

除了作業系統和系統呼叫庫外,Linux 作業系統還要提供一些標準程式,比如文字編輯器、編譯器、檔案操作工具等。直接和使用者打交道的是上面這些應用程式。因此我們可以說 Linux 具有三種不同的介面:系統呼叫介面、庫函式介面和應用程式介面

Linux 中的 GUI(Graphical User Interface) 和 UNIX 中的非常相似,這種 GUI 建立一個桌面環境,包括視窗、目標和資料夾、工具欄和檔案拖拽功能。一個完整的 GUI 還包括視窗管理器以及各種應用程式。

Linux 上的 GUI 由 X 視窗支援,主要組成部分是 X 伺服器、控制鍵盤、滑鼠、顯示器等。當在 Linux 上使用圖形介面時,使用者可以通過滑鼠點選執行程式或者開啟檔案,通過拖拽將檔案進行復制等。

Linux 組成部分

事實上,Linux 作業系統可以由下面這幾部分構成

  • 載入程式(Bootloader):載入程式是管理計算機啟動過程的軟體,對於大多數使用者而言,只是彈出一個螢幕,但其實內部作業系統做了很多事情
  • 核心(Kernel):核心是作業系統的核心,負責管理 CPU、記憶體和外圍裝置等。
  • 初始化系統(Init System):這是一個引導使用者空間並負責控制守護程式的子系統。一旦從引導載入程式移交了初始引導,它就是用於管理引導過程的初始化系統。
  • 後臺程式(Daemon):後臺程式顧名思義就是在後臺執行的程式,比如列印、聲音、排程等,它們可以在引導過程中啟動,也可以在登入桌面後啟動
  • 圖形伺服器(Graphical server):這是在監視器上顯示圖形的子系統。通常將其稱為 X 伺服器或 X。
  • 桌面環境(Desktop environment):這是使用者與之實際互動的部分,有很多桌面環境可供選擇,每個桌面環境都包含內建應用程式,比如檔案管理器、Web 瀏覽器、遊戲等
  • 應用程式(Applications):桌面環境不提供完整的應用程式,就像 Windows 和 macOS 一樣,Linux 提供了成千上萬個可以輕鬆找到並安裝的高質量軟體。

Shell

儘管 Linux 應用程式提供了 GUI ,但是大部分程式設計師仍偏好於使用命令列(command-line interface),稱為shell。使用者通常在 GUI 中啟動一個 shell 視窗然後就在 shell 視窗下進行工作。

shell 命令列使用速度快、功能更強大、而且易於擴充套件、並且不會帶來肢體重複性勞損(RSI)

下面會介紹一些最簡單的 bash shell。當 shell 啟動時,它首先進行初始化,在螢幕上輸出一個 提示符(prompt),通常是一個百分號或者美元符號,等待使用者輸入

等使用者輸入一個命令後,shell 提取其中的第一個詞,這裡的詞指的是被空格或製表符分隔開的一連串字元。假定這個詞是將要執行程式的程式名,那麼就會搜尋這個程式,如果找到了這個程式就會執行它。然後 shell 會將自己掛起直到程式執行完畢,之後再嘗試讀入下一條指令。shell 也是一個普通的使用者程式。它的主要功能就是讀取使用者的輸入和顯示計算的輸出。shell 命令中可以包含引數,它們作為字串傳遞給所呼叫的程式。比如

cp src dest

會呼叫 cp 應用程式幷包含兩個引數 srcdest。這個程式會解釋第一個引數是一個已經存在的檔名,然後建立一個該檔案的副本,名稱為 dest。

並不是所有的引數都是檔名,比如下面

head -20 file

第一個引數 -20,會告訴 head 應用程式列印檔案的前 20 行,而不是預設的 10 行。控制命令操作或者指定可選值的引數稱為標誌(flag),按照慣例標誌應該使用 - 來表示。這個符號是必要的,比如

head 20 file

是一個完全合法的命令,它會告訴 head 程式輸出檔名為 20 的檔案的前 10 行,然後輸出檔名為 file 檔案的前 10 行。Linux 作業系統可以接受一個或多個引數。

為了更容易的指定多個檔名,shell 支援 魔法字元(magic character),也被稱為萬用字元(wild cards)。比如,* 可以匹配一個或者多個可能的字串

ls *.c

告訴 ls 列舉出所有檔名以 .c 結束的檔案。如果同時存在多個檔案,則會在後面進行並列。

另一個萬用字元是問號,負責匹配任意一個字元。一組在中括號中的字元可以表示其中任意一個,因此

ls [abc]*

會列舉出所有以 ab 或者 c 開頭的檔案。

shell 應用程式不一定通過終端進行輸入和輸出。shell 啟動時,就會獲取 標準輸入、標準輸出、標準錯誤檔案進行訪問的能力。

標準輸出是從鍵盤輸入的,標準輸出或者標準錯誤是輸出到顯示器的。許多 Linux 程式預設是從標準輸入進行輸入並從標準輸出進行輸出。比如

sort	

會呼叫 sort 程式,會從終端讀取資料(直到使用者輸入 ctrl-d 結束),根據字母順序進行排序,然後將結果輸出到螢幕上。

通常還可以重定向標準輸入和標準輸出,重定向標準輸入使用 < 後面跟檔名。標準輸出可以通過一個大於號 > 進行重定向。允許一個命令中重定向標準輸入和輸出。例如命令

sort <in >out

會使 sort 從檔案 in 中得到輸入,並把結果輸出到 out 檔案中。由於標準錯誤沒有重定向,所以錯誤資訊會直接列印到螢幕上。從標準輸入讀入,對其進行處理並將其寫入到標準輸出的程式稱為 過濾器

考慮下面由三個分開的命令組成的指令

sort <in >temp;head -30 <temp;rm temp

首先會呼叫 sort 應用程式,從標準輸入 in 中進行讀取,並通過標準輸出到 temp。當程式執行完畢後,shell 會執行 head ,告訴它列印前 30 行,並在標準輸出(預設為終端)上列印。最後,temp 臨時檔案被刪除。輕輕的,你走了,你揮一揮衣袖,不帶走一片雲彩

命令列中的第一個程式通常會產生輸出,在上面的例子中,產生的輸出都不 temp 檔案接收。然而,Linux 還提供了一個簡單的命令來做這件事,例如下面

sort <in | head -30

上面 | 稱為豎線符號,它的意思是從 sort 應用程式產生的排序輸出會直接作為輸入顯示,無需建立、使用和移除臨時檔案。由管道符號連線的命令集合稱為管道(pipeline)。例如如下

grep cxuan *.c | sort | head -30 | tail -5 >f00

對任意以 .t 結尾的檔案中包含 cxuan 的行被寫到標準輸出中,然後進行排序。這些內容中的前 30 行被 head 出來並傳給 tail ,它又將最後 5 行傳遞給 foo。這個例子提供了一個管道將多個命令連線起來。

可以把一系列 shell 命令放在一個檔案中,然後將此檔案作為輸入來執行。shell 會按照順序對他們進行處理,就像在鍵盤上鍵入命令一樣。包含 shell 命令的檔案被稱為 shell 指令碼(shell scripts)

推薦一個 shell 命令的學習網站:https://www.shellscript.sh/

shell 指令碼其實也是一段程式,shell 指令碼中可以對變數進行賦值,也包含迴圈控制語句比如 if、for、while 等,shell 的設計目標是讓其看起來和 C 相似(There is no doubt that C is father)。由於 shell 也是一個使用者程式,所以使用者可以選擇不同的 shell。

Linux 應用程式

Linux 的命令列也就是 shell,它由大量標準應用程式組成。這些應用程式主要有下面六種

  • 檔案和目錄操作命令
  • 過濾器
  • 文字程式
  • 系統管理
  • 程式開發工具,例如編輯器和編譯器
  • 其他

除了這些標準應用程式外,還有其他應用程式比如 Web 瀏覽器、多媒體播放器、圖片瀏覽器、辦公軟體和遊戲程式等

我們在上面的例子中已經見過了幾個 Linux 的應用程式,比如 sort、cp、ls、head,下面我們再來認識一下其他 Linux 的應用程式。

我們先從幾個例子開始講起,比如

cp a b

是將 a 複製一個副本為 b ,而

mv a b

是將 a 移動到 b ,但是刪除原檔案。

上面這兩個命令有一些區別,cp 是將檔案進行復制,複製完成後會有兩個檔案 a 和 b;而 mv 相當於是檔案的移動,移動完成後就不再有 a 檔案。cat 命令可以把多個檔案內容進行連線。使用 rm 可以刪除檔案;使用 chmod 可以允許所有者改變訪問許可權;檔案目錄的的建立和刪除可以使用 mkdirrmdir 命令;使用 ls 可以檢視目錄檔案,ls 可以顯示很多屬性,比如大小、使用者、建立日期等;sort 決定檔案的顯示順序

Linux 應用程式還包括過濾器 grep,grep 從標準輸入或者一個或多個輸入檔案中提取特定模式的行;sort 將輸入進行排序並輸出到標準輸出;head 提取輸入的前幾行;tail 提取輸入的後面幾行;除此之外的過濾器還有 cutpaste,允許對文字行的剪下和複製;od 將輸入轉換為 ASCII ;tr 實現字元大小寫轉換;pr 為格式化列印輸出等。

程式編譯工具使用 gcc

make 命令用於自動編譯,這是一個很強大的命令,它用於維護一個大的程式,往往這類程式的原始碼由許多檔案構成。典型的,有一些是 header files 標頭檔案,原始檔通常使用 include 指令包含這些檔案,make 的作用就是跟蹤哪些檔案屬於標頭檔案,然後安排自動編譯的過程。

下面列出了 POSIX 的標準應用程式

程式 應用
ls 列出目錄
cp 複製檔案
head 顯示檔案的前幾行
make 編譯檔案生成二進位制檔案
cd 切換目錄
mkdir 建立目錄
chmod 修改檔案訪問許可權
ps 列出檔案程式
pr 格式化列印
rm 刪除一個檔案
rmdir 刪除檔案目錄
tail 提取檔案最後幾行
tr 字符集轉換
grep 分組
cat 將多個檔案連續標準輸出
od 以八進位制顯示檔案
cut 從檔案中剪下
paste 從檔案中貼上

Linux 核心結構

在上面我們看到了 Linux 的整體結構,下面我們從整體的角度來看一下 Linux 的核心結構

核心直接坐落在硬體上,核心的主要作用就是 I/O 互動、記憶體管理和控制 CPU 訪問。上圖中還包括了 中斷排程器,中斷是與裝置互動的主要方式。中斷出現時排程器就會發揮作用。這裡的低階程式碼停止正在執行的程式,將其狀態儲存在核心程式結構中,並啟動驅動程式。程式排程也會發生在核心完成一些操作並且啟動使用者程式的時候。圖中的排程器是 dispatcher。

注意這裡的排程器是 dispatcher 而不是 scheduler,這兩者是有區別的

scheduler 和 dispatcher 都是和程式排程相關的概念,不同的是 scheduler 會從幾個程式中隨意選取一個程式;而 dispatcher 會給 scheduler 選擇的程式分配 CPU。

然後,我們把核心系統分為三部分。

  • I/O 部分負責與裝置進行互動以及執行網路和儲存 I/O 操作的所有核心部分。

從圖中可以看出 I/O 層次的關係,最高層是一個虛擬檔案系統,也就是說不管檔案是來自記憶體還是磁碟中,都是經過虛擬檔案系統中的。從底層看,所有的驅動都是字元驅動或者塊裝置驅動。二者的主要區別就是是否允許隨機訪問。網路驅動裝置並不是一種獨立的驅動裝置,它實際上是一種字元裝置,不過網路裝置的處理方式和字元裝置不同。

上面的裝置驅動程式中,每個裝置型別的核心程式碼都不同。字元裝置有兩種使用方式,有一鍵式的比如 vi 或者 emacs ,需要每一個鍵盤輸入。其他的比如 shell ,是需要輸入一行按Enter鍵將字串傳送給程式進行編輯。

網路軟體通常是模組化的,由不同的裝置和協議來支援。大多數 Linux 系統在核心中包含一個完整的硬體路由器的功能,但是這個不能和外部路由器相比,路由器上面是協議棧,包括 TCP/IP 協議,協議棧上面是 socket 介面,socket 負責與外部進行通訊,充當了門的作用。

磁碟驅動上面是 I/O 排程器,它負責排序和分配磁碟讀寫操作,以儘可能減少磁頭的無用移動。

  • I/O 右邊的是記憶體部件,程式被裝載進記憶體,由 CPU 執行,這裡會涉及到虛擬記憶體的部件,頁面的換入和換出是如何進行的,壞頁面的替換和經常使用的頁面會進行快取。

  • 程式模組負責程式的建立和終止、程式的排程、Linux 把程式和執行緒看作是可執行的實體,並使用統一的排程策略來進行排程。

在核心最頂層的是系統呼叫介面,所有的系統呼叫都是經過這裡,系統呼叫會觸發一個 trap,將系統從使用者態轉換為核心態,然後將控制權移交給上面的核心部件。

相關文章