天天看點

linux ls不能用_對不起,原諒我學會這些linux知識之後,飄了Linux 接口Linux 組成部分Linux 應用程式Linux 核心結構Linux 程序和線程Linux 程序間通信Linux 程序和線程的實作Linux 程序Linux 線程使用者級線程核心級線程混合實作Linux 排程Linux 系統中的同步Linux 啟動Linux 記憶體管理基本概念

Linux 簡介

UNIX 是一個互動式系統,用于同時處理多程序和多使用者同時線上。為什麼要說 UNIX,那是因為 Linux 是由 UNIX

發展而來的,UNIX 是由程式員設計,它的主要服務對象也是程式員。Linux 繼承了 UNIX的設計目标。從智能手機到汽車,超級計算機和家用電器,從家用桌上型電腦到企業伺服器,Linux 作業系統無處不在。

如果你在學習C/C++的過程中遇到了問題,關注我的首頁來加入小編的企鵝圈問小編哦~小編很熱情的(●’◡’●)

linux ls不能用_對不起,原諒我學會這些linux知識之後,飄了Linux 接口Linux 組成部分Linux 應用程式Linux 核心結構Linux 程式和線程Linux 程式間通信Linux 程式和線程的實作Linux 程式Linux 線程使用者級線程核心級線程混合實作Linux 排程Linux 系統中的同步Linux 啟動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 提供了成千上萬個可以輕松找到并安裝的高品質軟體。

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

shell 指令行使用速度快、功能更強大、而且易于擴充、并且不會帶來肢體重複性勞損(RSI)。

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

等使用者輸入一個指令後,shell提取其中的第一個詞,這裡的詞指的是被空格或制表符分隔開的一連串字元。假定這個詞是将要運作程式的程式名,那麼就會搜尋這個程式,如果找到了這個程式就會運作它。然後shell 會将自己挂起直到程式運作完畢,之後再嘗試讀入下一條指令。shell

也是一個普通的使用者程式。它的主要功能就是讀取使用者的輸入和顯示計算的輸出。shell 指令中可以包含參數,它們作為字元串傳遞給所調用的程式。比如

cp src dest

會調用 cp 應用程式并包含兩個參數src和dest。這個程式會解釋第一個參數是一個已經存在的檔案名,然後建立一個該檔案的副本,名稱為 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]*

會列舉出所有以a、b或者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 腳本其實也是一段程式,shell 腳本中可以對變量進行指派,也包含循環控制語句比如if、for、while等,shell 的設計目标是讓其看起來和 C 相似(There is no doubt that C is father)。由于 shell 也是一個使用者程式,是以使用者可以選擇不同的 shell。

Linux 應用程式

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

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

linux ls不能用_對不起,原諒我學會這些linux知識之後,飄了Linux 接口Linux 組成部分Linux 應用程式Linux 核心結構Linux 程式和線程Linux 程式間通信Linux 程式和線程的實作Linux 程式Linux 線程使用者級線程核心級線程混合實作Linux 排程Linux 系統中的同步Linux 啟動Linux 記憶體管理基本概念

除了這些标準應用程式外,還有其他應用程式比如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可以允許所有者改變通路權限;檔案目錄的的建立和删除可以使用mkdir和rmdir指令;使用ls可以檢視目錄檔案,ls 可以顯示很多屬性,比如大小、使用者、建立日期等;sort 決定檔案的顯示順序

Linux 應用程式還包括過濾器 grep,grep從标準輸入或者一個或多個輸入檔案中提取特定模式的行;sort将輸入進行排序并輸出到标準輸出;head提取輸入的前幾行;tail 提取輸入的後面幾行;除此之外的過濾器還有cut和paste,允許對文本行的剪切和複制;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 的核心結構

linux ls不能用_對不起,原諒我學會這些linux知識之後,飄了Linux 接口Linux 組成部分Linux 應用程式Linux 核心結構Linux 程式和線程Linux 程式間通信Linux 程式和線程的實作Linux 程式Linux 線程使用者級線程核心級線程混合實作Linux 排程Linux 系統中的同步Linux 啟動Linux 記憶體管理基本概念

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

注意這裡的排程器是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,将系統從使用者态轉換為核心态,然後将控制權移交給上面的核心部件。

Linux 程序和線程

下面我們就深入了解一下 Linux 核心來了解 Linux 的基本概念之程序和線程。系統調用是作業系統本身的接口,它對于建立程序和線程,記憶體配置設定,共享檔案和 I/O 來說都很重要。

我們将從各個版本的共性出發來進行探讨。

每個程序都會運作一段獨立的程式,并且在初始化的時候擁有一個獨立的控制線程。換句話說,每個程序都會有一個自己的程式計數器,這個程式計數器用來記錄下一個需要被執行的指令。Linux 允許程序在運作時建立額外的線程。

Linux 是一個多道程式設計系統,是以系統中存在彼此互相獨立的程序同時運作。此外,每個使用者都會同時有幾個活動的程序。因為如果是一個大型系統,可能有數百上千的程序在同時運作。

在某些使用者空間中,即使使用者登出,仍然會有一些背景程序在運作,這些程序被稱為守護程序(daemon)。

Linux 中有一種特殊的守護程序被稱為計劃守護程序(Cron daemon),計劃守護程序可以每分鐘醒來一次檢查是否有工作要做,做完會繼續回到睡眠狀态等待下一次喚醒。

Cron 是一個守護程式,可以做任何你想做的事情,比如說你可以定期進行系統維護、定期進行系統備份等。在其他作業系統上也有類似的程式,比如 Mac OS X 上 Cron 守護程式被稱為launchd的守護程序。在 Windows 上可以被稱為計劃任務(Task Scheduler)。

在 Linux 系統中,程序通過非常簡單的方式來建立,fork系統調用會建立一個源程序的拷貝(副本)。調用 fork 函數的程序被稱為父程序(parent process),使用 fork 函數建立出來的程序被稱為子程序(child process)。父程序和子程序都有自己的記憶體映像。如果在子程序建立出來後,父程序修改了一些變量等,那麼子程序是看不到這些變化的,也就是 fork 後,父程序和子程序互相獨立。

雖然父程序和子程序保持互相獨立,但是它們卻能夠共享相同的檔案,如果在 fork 之前,父程序已經打開了某個檔案,那麼 fork 後,父程序和子程序仍然共享這個打開的檔案。對共享檔案的修改會對父程序和子程序同時可見。

那麼該如何區分父程序和子程序呢?子程序隻是父程序的拷貝,是以它們幾乎所有的情況都一樣,包括記憶體映像、變量、寄存器等。區分的關鍵在于fork函數調用後的傳回值,如果 fork 後傳回一個非零值,這個非零值即是子程序的程序辨別符(Process Identiier, PID),而會給子程序傳回一個零值,可以用下面代碼來進行表示

pid = fork();    // 調用 fork 函數建立程序

if(pid < 0){

  error()                // pid < 0,建立失敗

}

else if(pid > 0){

  parent_handle() // 父程序代碼

}

else {

  child_handle()  // 子程序代碼

}

父程序在 fork 後會得到子程序的 PID,這個 PID 即能代表這個子程序的唯一辨別符也就是 PID。如果子程序想要知道自己的 PID,可以調用getpid方法。當子程序結束運作時,父程序會得到子程序的 PID,因為一個程序會 fork 很多子程序,子程序也會 fork 子程序,是以 PID 是非常重要的。我們把第一次調用 fork 後的程序稱為原始程序,一個原始程序可以生成一顆繼承樹

Linux 程序間通信

Linux 程序間的通信機制通常被稱為Internel-Process communication,IPC下面我們來說一說 Linux 程序間通信的機制,大緻來說,Linux 程序間的通信機制可以分為 6 種

信号 signal

linux ls不能用_對不起,原諒我學會這些linux知識之後,飄了Linux 接口Linux 組成部分Linux 應用程式Linux 核心結構Linux 程式和線程Linux 程式間通信Linux 程式和線程的實作Linux 程式Linux 線程使用者級線程核心級線程混合實作Linux 排程Linux 系統中的同步Linux 啟動Linux 記憶體管理基本概念

信号是 UNIX 系統最先開始使用的程序間通信機制,因為 Linux 是繼承于 UNIX 的,是以 Linux 也支援信号機制,通過向一個或多個程序發送異步事件信号來實作,信号可以從鍵盤或者通路不存在的位置等地方産生;信号通過 shell 将任務發送給子程序。

程序可以選擇忽略發送過來的信号,但是有兩個是不能忽略的:SIGSTOP和SIGKILL信号。SIGSTOP 信号會通知目前正在運作的程序執行關閉操作,SIGKILL 信号會通知目前程序應該被殺死。除此之外,程序可以選擇它想要處理的信号,程序也可以選擇阻止信号,如果不阻止,可以選擇自行處理,也可以選擇進行核心處理。如果選擇交給核心進行處理,那麼就執行預設處理。

作業系統會中斷目标程式的程序來向其發送信号、在任何非原子指令中,執行都可以中斷,如果程序已經注冊了新号處理程式,那麼就執行程序,如果沒有注冊,将采用預設處理的方式。

例如:當程序收到SIGFPE浮點異常的信号後,預設操作是對其進行dump(轉儲)和退出。信号沒有優先級的說法。如果同時為某個程序産生了兩個信号,則可以将它們呈現給程序或者以任意的順序進行處理。

下面我們就來看一下這些信号是幹什麼用的

SIGABRT 和 SIGIOT

SIGABRT 和 SIGIOT 信号發送給程序,告訴其進行終止,這個 信号通常在調用 C标準庫的abort()函數時由程序本身啟動

SIGALRM 、 SIGVTALRM、SIGPROF

當設定的時鐘功能逾時時會将

SIGALRM 、 SIGVTALRM、SIGPROF 發送給程序。當實際時間或時鐘時間逾時時,發送 SIGALRM。 當程序使用的 CPU

時間逾時時,将發送 SIGVTALRM。 當程序和系統代表程序使用的CPU 時間逾時時,将發送 SIGPROF。

SIGBUS

SIGBUS 将造成總線中斷錯誤時發送給程序

SIGCHLD

當子程序終止、被中斷或者被中斷恢複,将 SIGCHLD 發送給程序。此信号的一種常見用法是訓示作業系統在子程序終止後清除其使用的資源。

SIGCONT

SIGCONT 信号訓示作業系統繼續執行先前由 SIGSTOP 或 SIGTSTP 信号暫停的程序。該信号的一個重要用途是在 Unix shell 中的作業控制中。

SIGFPE

SIGFPE 信号在執行錯誤的算術運算(例如除以零)時将被發送到程序。

SIGUP

當 SIGUP 信号控制的終端關閉時,會發送給程序。許多守護程式将重新加載其配置檔案并重新打開其日志檔案,而不是在收到此信号時退出。

SIGILL

SIGILL 信号在嘗試執行非法、格式錯誤、未知或者特權指令時發出

SIGINT

當使用者希望中斷程序時,作業系統會向程序發送 SIGINT 信号。使用者輸入 ctrl - c 就是希望中斷程序。

SIGKILL

SIGKILL 信号發送到程序以使其馬上進行終止。 與 SIGTERM 和 SIGINT 相比,這個信号無法捕獲和忽略執行,并且程序在接收到此信号後無法執行任何清理操作,下面是一些例外情況

僵屍程序無法殺死,因為僵屍程序已經死了,它在等待父程序對其進行捕獲

處于阻塞狀态的程序隻有再次喚醒後才會被 kill 掉

init程序是 Linux 的初始化程序,這個程序會忽略任何信号。

SIGKILL 通常是作為最後殺死程序的信号、它通常作用于 SIGTERM 沒有響應時發送給程序。

SIGPIPE

SIGPIPE 嘗試寫入程序管道時發現管道未連接配接無法寫入時發送到程序

SIGPOLL

當在明确監視的檔案描述符上發生事件時,将發送 SIGPOLL 信号。

SIGRTMIN 至 SIGRTMAX

SIGRTMIN 至 SIGRTMAX 是實時信号

SIGQUIT

當使用者請求退出程序并執行核心轉儲時,SIGQUIT 信号将由其控制終端發送給程序。

SIGSEGV

當 SIGSEGV 信号做出無效的虛拟記憶體引用或分段錯誤時,即在執行分段違規時,将其發送到程序。

SIGSTOP

SIGSTOP 訓示作業系統終止以便以後進行恢複時

SIGSYS

當 SIGSYS 信号将錯誤參數傳遞給系統調用時,該信号将發送到程序。

SYSTERM

我們上面簡單提到過了 SYSTERM 這個名詞,這個信号發送給程序以請求終止。與 SIGKILL 信号不同,該信号可以被過程捕獲或忽略。這允許程序執行良好的終止,進而釋放資源并在适當時儲存狀态。 SIGINT 與SIGTERM 幾乎相同。

SIGTSIP

SIGTSTP 信号由其控制終端發送到程序,以請求終端停止。

SIGTTIN 和 SIGTTOU

當 SIGTTIN 和SIGTTOU 信号分别在背景嘗試從 tty 讀取或寫入時,信号将發送到該程序。

SIGTRAP

在發生異常或者 TRAP 時,将 SIGTRAP 信号發送到程序

SIGURG

當套接字具有可讀取的緊急或帶外資料時,将 SIGURG 信号發送到程序。

SIGUSR1 和 SIGUSR2

SIGUSR1 和 SIGUSR2 信号被發送到程序以訓示使用者定義的條件。

SIGXCPU

當 SIGXCPU 信号耗盡 CPU 的時間超過某個使用者可設定的預定值時,将其發送到程序

SIGXFSZ

當 SIGXFSZ 信号增長超過最大允許大小的檔案時,該信号将發送到該程序。

SIGWINCH

SIGWINCH 信号在其控制終端更改其大小(視窗更改)時發送給程序。

管道 pipe

Linux 系統中的程序可以通過建立管道 pipe 進行通信。

在兩個程序之間,可以建立一個通道,一個程序向這個通道裡寫入位元組流,另一個程序從這個管道中讀取位元組流。管道是同步的,當程序嘗試從空管道讀取資料時,該程序會被阻塞,直到有可用資料為止。shell 中的管線 pipelines就是用管道實作的,當 shell 發現輸出

sort <f | head

它會建立兩個程序,一個是 sort,一個是 head,sort,會在這兩個應用程式之間建立一個管道使得

sort 程序的标準輸出作為 head 程式的标準輸入。sort 程序産生的輸出就不用寫到檔案中了,如果管道滿了系統會停止 sort 以等待

head 讀出資料

管道實際上就是|,兩個應用程式不知道有管道的存在,一切都是由 shell 管理和控制的。

共享記憶體 shared memory

兩個程序之間還可以通過共享記憶體進行程序間通信,其中兩個或者多個程序可以通路公共記憶體空間。兩個程序的共享工作是通過共享記憶體完成的,一個程序所作的修改可以對另一個程序可見(很像線程間的通信)。

在使用共享記憶體前,需要經過一系列的調用流程,流程如下

建立共享記憶體段或者使用已建立的共享記憶體段(shmget())将程序附加到已經建立的記憶體段中(shmat())從已連接配接的共享記憶體段分離程序(shmdt())對共享記憶體段執行控制操作(shmctl())先入先出隊列 FIFO

先入先出隊列 FIFO 通常被稱為命名管道(Named Pipes),命名管道的工作方式與正常管道非常相似,但是确實有一些明顯的差別。未命名的管道沒有備份檔案:作業系統負責維護記憶體中的緩沖區,用來将位元組從寫入器傳輸到讀取器。一旦寫入或者輸出終止的話,緩沖區将被回收,傳輸的資料會丢失。相比之下,命名管道具有支援檔案和獨特 API ,命名管道在檔案系統中作為裝置的專用檔案存在。當所有的程序通信完成後,命名管道将保留在檔案系統中以備後用。命名管道具有嚴格的 FIFO 行為

寫入的第一個位元組是讀取的第一個位元組,寫入的第二個位元組是讀取的第二個位元組,依此類推。

消息隊列 Message Queue

一聽到消息隊列這個名詞你可能不知道是什麼意思,消息隊列是用來描述核心尋址空間内的内部連結清單。可以按幾種不同的方式将消息按順序發送到隊列并從隊列中檢索消息。每個消息隊列由 IPC 辨別符唯一辨別。消息隊列有兩種模式,一種是嚴格模式, 嚴格模式就像是 FIFO 先入先出隊列似的,消息順序發送,順序讀取。還有一種模式是非嚴格模式,消息的順序性不是非常重要。

套接字 Socket

還有一種管理兩個程序間通信的是使用socket,socket 提供端到端的雙相通信。一個套接字可以與一個或多個程序關聯。就像管道有指令管道和未命名管道一樣,套接字也有兩種模式,套接字一般用于兩個程序之間的網絡通信,網絡套接字需要來自諸如TCP(傳輸控制協定)或較低級别UDP(使用者資料報協定)等基礎協定的支援。

套接字有以下幾種分類

順序包套接字(Sequential Packet Socket): 此類套接字為最大長度固定的資料報提供可靠的連接配接。此連接配接是雙向的并且是順序的。資料報套接字(Datagram Socket):資料包套接字支援雙向資料流。資料包套接字接受消息的順序與發送者可能不同。流式套接字(Stream Socket):流套接字的工作方式類似于電話對話,提供雙向可靠的資料流。原始套接字(Raw Socket): 可以使用原始套接字通路基礎通信協定。Linux 中程序管理系統調用

現在關注一下 Linux 系統中與程序管理相關的系統調用。在了解之前你需要先知道一下什麼是系統調用。

作業系統為我們屏蔽了硬體和軟體的差異,它的最主要功能就是為使用者提供一種抽象,隐藏内部實作,讓使用者隻關心在 GUI 圖形界面下如何使用即可。作業系統可以分為兩種模式

核心态:作業系統核心使用的模式使用者态:使用者應用程式所使用的模式

我們常說的上下文切換指的就是核心态模式和使用者态模式的頻繁切換。而系統調用指的就是引起核心态和使用者态切換的一種方式,系統調用通常在背景靜默運作,表示計算機程式向其作業系統核心請求服務。

系統調用指令有很多,下面是一些與程序管理相關的最主要的系統調用

fork 調用用于建立一個與父程序相同的子程序,建立完程序後的子程序擁有和父程序一樣的程式計數器、相同的 CPU 寄存器、相同的打開檔案。

exec 系統調用用于執行駐留在活動程序中的檔案,調用 exec 後,新的可執行檔案會替換先前的可執行檔案并獲得執行。也就是說,調用 exec 後,會将舊檔案或程式替換為新檔案或執行,然後執行檔案或程式。新的執行程式被加載到相同的執行空間中,是以程序的PID不會修改,因為我們沒有建立新程序,隻是替換舊程序。但是程序的資料、代碼、堆棧都已經被修改。如果目前要被替換的程序包含多個線程,那麼所有的線程将被終止,新的程序映像被加載執行。

這裡需要解釋一下程序映像(Process image)的概念

什麼是程序映像呢?程序映像是執行程式時所需要的可執行檔案,通常會包括下面這些東西

代碼段(codesegment/textsegment)

又稱文本段,用來存放指令,運作代碼的一塊記憶體空間

此空間大小在代碼運作前就已經确定

記憶體空間一般屬于隻讀,某些架構的代碼也允許可寫

在代碼段中,也有可能包含一些隻讀的常數變量,例如字元串常量等。

資料段(datasegment)

可讀可寫

存儲初始化的全局變量和初始化的 static 變量

資料段中資料的生存期是随程式持續性(随程序持續性) 随程序持續性:程序建立就存在,程序死亡就消失

bss 段(bsssegment):

可讀可寫

存儲未初始化的全局變量和未初始化的 static 變量

bss 段中的資料一般預設為 0

Data 段

是可讀寫的,因為變量的值可以在運作時更改。此段的大小也固定。

棧(stack):

可讀可寫

存儲的是函數或代碼中的局部變量(非 static 變量)

棧的生存期随代碼塊持續性,代碼塊運作就給你配置設定空間,代碼塊結束,就自動回收空間

堆(heap):

可讀可寫

存儲的是程式運作期間動态配置設定的 malloc/realloc 的空間

堆的生存期随程序持續性,從 malloc/realloc 到 free 一直存在

下面是這些區域的構成圖

exec 系統調用是一些函數的集合,這些函數是

execlexecleexeclpexecvexecveexecvp

下面來看一下 exec 的工作原理

目前程序映像被替換為新的程序映像新的程序映像是你做為 exec 傳遞的燦睡結束目前正在運作的程序新的程序映像有 PID,相同的環境和一些檔案描述符(因為未替換程序,隻是替換了程序映像)CPU 狀态和虛拟記憶體受到影響,目前程序映像的虛拟記憶體映射被新程序映像的虛拟記憶體代替。waitpid

等待子程序結束或終止

exit

在許多計算機作業系統上,計算機程序的終止是通過執行exit系統調用指令執行的。0 表示程序能夠正常結束,其他值表示程序以非正常的行為結束。

其他一些常見的系統調用如下

系統調用指令

描述

pause

挂起信号

nice

改變分時程序的優先級

ptrace

程序跟蹤

kill

向程序發送信号

pipe

建立管道

mkfifo

建立 fifo 的特殊檔案(命名管道)

sigaction

設定對指定信号的處理方法

msgctl

消息控制操作

semctl

信号量控制

Linux 程序和線程的實作Linux 程序

在 Linux 核心結構中,程序會被表示為任務,通過結構體structure來建立。不像其他的作業系統會區分程序、輕量級程序和線程,Linux 統一使用任務結構來代表執行上下文。是以,對于每個單線程程序來說,單線程程序将用一個任務結構表示,對于多線程程序來說,将為每一個使用者級線程配置設定一個任務結構。Linux 核心是多線程的,并且核心級線程不與任何使用者級線程相關聯。

linux ls不能用_對不起,原諒我學會這些linux知識之後,飄了Linux 接口Linux 組成部分Linux 應用程式Linux 核心結構Linux 程式和線程Linux 程式間通信Linux 程式和線程的實作Linux 程式Linux 線程使用者級線程核心級線程混合實作Linux 排程Linux 系統中的同步Linux 啟動Linux 記憶體管理基本概念

對于每個程序來說,在記憶體中都會有一個task_struct程序描述符與之對應。程序描述符包含了核心管理程序所有有用的資訊,包括排程參數、打開檔案描述符等等。程序描述符從程序建立開始就一直存在于核心堆棧中。

Linux 和 Unix 一樣,都是通過PID來區分不同的程序,核心會将所有程序的任務結構組成為一個雙向連結清單。PID 能夠直接被映射稱為程序的任務結構所在的位址,進而不需要周遊雙向連結清單直接通路。

我們上面提到了程序描述符,這是一個非常重要的概念,我們上面還提到了程序描述符是位于記憶體中的,這裡我們省略了一句話,那就是程序描述符是存在使用者的任務結構中,當程序位于記憶體并開始運作時,程序描述符才會被調入記憶體。

程序位于記憶體被稱為PIM(Process In Memory),這是馮諾伊曼體系架構的一種展現,加載到記憶體中并執行的程式稱為程序。簡單來說,一個程序就是正在執行的程式。

程序描述符可以歸為下面這幾類

排程參數(scheduling parameters):程序優先級、最近消耗 CPU 的時間、最近睡眠時間一起決定了下一個需要運作的程序記憶體映像(memory image):我們上面說到,程序映像是執行程式時所需要的可執行檔案,它由資料和代碼組成。信号(signals):顯示哪些信号被捕獲、哪些信号被執行寄存器:當發生核心陷入 (TRAP) 時,寄存器的内容會被儲存下來。系統調用狀态(system call state):目前系統調用的資訊,包括參數和結果檔案描述符表(file descriptor table):有關檔案描述符的系統被調用時,檔案描述符作為索引在檔案描述符表中定位相關檔案的 i-node 資料結構統計資料(accounting):記錄使用者、程序占用系統 CPU 時間表的指針,一些作業系統還儲存程序最多占用的 CPU 時間、程序擁有的最大堆棧空間、程序可以消耗的頁面數等。核心堆棧(kernel stack):程序的核心部分可以使用的固定堆棧其他: 目前程序狀态、事件等待時間、距離警報的逾時時間、PID、父程序的 PID 以及使用者辨別符等

有了上面這些資訊,現在就很容易描述在 Linux 中是如何建立這些程序的了,建立新流程實際上非常簡單。為子程序開辟一塊新的使用者空間的程序描述符,然後從父程序複制大量的内容。為這個子程序配置設定一個 PID,設定其記憶體映射,賦予它通路父程序檔案的權限,注冊并啟動。

當執行 fork 系統調用時,調用程序會陷入核心并建立一些和任務相關的資料結構,比如核心堆棧(kernel stack)和thread_info結構。

這個結構中包含程序描述符,程序描述符位于固定的位置,使得 Linux 系統隻需要很小的開銷就可以定位到一個運作中程序的資料結構。

程序描述符的主要内容是根據父程序的描述符來填充。Linux 作業系統會尋找一個可用的 PID,并且此 PID 沒有被任何程序使用,更新程序标示符使其指向一個新的資料結構即可。為了減少 hash table 的碰撞,程序描述符會形成連結清單。它還将 task_struct 的字段設定為指向任務數組上相應的上一個/下一個程序。

task_struct : Linux 程序描述符,内部涉及到衆多 C++ 源碼,我們會在後面進行講解。

從原則上來說,為子程序開辟記憶體區域并為子程序配置設定資料段、堆棧段,并且對父程序的内容進行複制,但是實際上 fork 完成後,子程序和父程序沒有共享記憶體,是以需要複制技術來實作同步,但是複制開銷比較大,是以 Linux 作業系統使用了一種欺騙方式。即為子程序配置設定頁表,然後新配置設定的頁表指向父程序的頁面,同時這些頁面是隻讀的。當程序向這些頁面進行寫入的時候,會開啟保護錯誤。核心發現寫入操作後,會為程序配置設定一個副本,使得寫入時把資料複制到這個副本上,這個副本是共享的,這種方式稱為寫入時複制(copy on write),這種方式避免了在同一塊記憶體區域維護兩個副本的必要,節省記憶體空間。

在子程序開始運作後,作業系統會調用 exec 系統調用,核心會進行查找驗證可執行檔案,把參數和環境變量複制到核心,釋放舊的位址空間。

現在新的位址空間需要被建立和填充。如果系統支援映射檔案,就像 Unix 系統一樣,那麼新的頁表就會建立,表明記憶體中沒有任何頁,除非所使用的頁面是堆棧頁,其位址空間由磁盤上的可執行檔案支援。新程序開始運作時,立刻會收到一個缺頁異常(page fault),這會使具有代碼的頁面加載進入記憶體。最後,參數和環境變量被複制到新的堆棧中,重置信号,寄存器全部清零。新的指令開始運作。

下面是一個示例,使用者輸出 ls,shell 會調用 fork 函數複制一個新程序,shell 程序會調用 exec 函數用可執行檔案 ls 的内容覆寫它的記憶體。

Linux 線程

現在我們來讨論一下 Linux 中的線程,線程是輕量級的程序,想必這句話你已經聽過很多次了,輕量級展現在所有的程序切換都需要清除所有的表、程序間的共享資訊也比較麻煩,一般來說通過管道或者共享記憶體,如果是 fork 函數後的父子程序則使用共享檔案,然而線程切換不需要像程序一樣具有昂貴的開銷,而且線程通信起來也更友善。線程分為兩種:使用者級線程和核心級線程

linux ls不能用_對不起,原諒我學會這些linux知識之後,飄了Linux 接口Linux 組成部分Linux 應用程式Linux 核心結構Linux 程式和線程Linux 程式間通信Linux 程式和線程的實作Linux 程式Linux 線程使用者級線程核心級線程混合實作Linux 排程Linux 系統中的同步Linux 啟動Linux 記憶體管理基本概念

使用者級線程

使用者級線程避免使用核心,通常,每個線程會顯示調用開關,發送信号或者執行某種切換操作來放棄 CPU,同樣,計時器可以強制進行開關,使用者線程的切換速度通常比核心線程快很多。在使用者級别實作線程會有一個問題,即單個線程可能會壟斷 CPU 時間片,導緻其他線程無法執行進而餓死。如果執行一個 I/O 操作,那麼 I/O 會阻塞,其他線程也無法運作。

一種解決方案是,一些使用者級的線程包解決了這個問題。可以使用時鐘周期的螢幕來控制第一時間時間片獨占。然後,一些庫通過特殊的包裝來解決系統調用的 I/O 阻塞問題,或者可以為非阻塞 I/O 編寫任務。

核心級線程

核心級線程通常使用幾個程序表在核心中實作,每個任務都會對應一個程序表。在這種情況下,核心會在每個程序的時間片内排程每個線程。

所有能夠阻塞的調用都會通過系統調用的方式來實作,當一個線程阻塞時,核心可以進行選擇,是運作在同一個程序中的另一個線程(如果有就緒線程的話)還是運作一個另一個程序中的線程。

從使用者空間 -> 核心空間 -> 使用者空間的開銷比較大,但是線程初始化的時間損耗可以忽略不計。這種實作的好處是由時鐘決定線程切換時間,是以不太可能将時間片與任務中的其他線程占用時間綁定到一起。同樣,I/O 阻塞也不是問題。

混合實作

結合使用者空間和核心空間的優點,設計人員采用了一種核心級線程的方式,然後将使用者級線程與某些或者全部核心線程多路複用起來

在這種模型中,程式設計人員可以自由控制使用者線程和核心線程的數量,具有很大的靈活度。采用這種方法,核心隻識别核心級線程,并對其進行排程。其中一些核心級線程會被多個使用者級線程多路複用。

Linux 排程

下面我們來關注一下 Linux 系統的排程算法,首先需要認識到,Linux 系統的線程是核心線程,是以 Linux 系統是基于線程的,而不是基于程序的。

為了進行排程,Linux 系統将線程分為三類

實時先入先出實時輪詢分時

實時先入先出線程具有最高優先級,它不會被其他線程所搶占,除非那是一個剛剛準備好的,擁有更高優先級的線程進入。實時輪轉線程與實時先入先出線程基本相同,隻是每個實時輪轉線程都有一個時間量,時間到了之後就可以被搶占。如果多個實時線程準備完畢,那麼每個線程運作它時間量所規定的時間,然後插入到實時輪轉線程末尾。

注意這個實時隻是相對的,無法做到絕對的實時,因為線程的運作時間無法确定。它們相對分時系統來說,更加具有實時性

Linux 系統會給每個線程配置設定一個nice值,這個值代表了優先級的概念。nice 值預設值是 0 ,但是可以通過系統調用 nice 值來修改。修改值的範圍從 -20 - +19。nice 值決定了線程的靜态優先級。一般系統管理者的 nice 值會比一般線程的優先級高,它的範圍是 -20 - -1。

下面我們更詳細的讨論一下 Linux 系統的兩個排程算法,它們的内部與排程隊列(runqueue)的設計很相似。運作隊列有一個資料結構用來監視系統中所有可運作的任務并選擇下一個可以運作的任務。每個運作隊列和系統中的每個 CPU 有關。

Linux O(1)排程器是曆史上很流行的一個排程器。這個名字的由來是因為它能夠在常數時間内執行任務排程。在 O(1) 排程器裡,排程隊列被組織成兩個數組,一個是任務正在活動的數組,一個是任務過期失效的數組。

大緻流程如下:

排程器從正在活動數組中選擇一個優先級最高的任務。如果這個任務的時間片過期失效了,就把它移動到過期失效數組中。如果這個任務阻塞了,比如說正在等待

I/O 事件,那麼在它的時間片過期失效之前,一旦 I/O

操作完成,那麼這個任務将會繼續運作,它将被放回到之前正在活動的數組中,因為這個任務之前已經消耗一部分 CPU

時間片,是以它将運作剩下的時間片。當這個任務運作完它的時間片後,它就會被放到過期失效數組中。一旦正在活動的任務數組中沒有其他任務後,排程器将會交換指針,使得正在活動的數組變為過期失效數組,過期失效數組變為正在活動的數組。使用這種方式可以保證每個優先級的任務都能夠得到執行,不會導緻線程饑餓。

在這種排程方式中,不同優先級的任務所得到 CPU 配置設定的時間片也是不同的,高優先級程序往往能得到較長的時間片,低優先級的任務得到較少的時間片。

這種方式為了保證能夠更好的提供服務,通常會為互動式程序賦予較高的優先級,互動式程序就是使用者程序。

Linux 系統不知道一個任務究竟是 I/O 密集型的還是 CPU 密集型的,它隻是依賴于互動式的方式,Linux 系統會區分是靜态優先級還是動态優先級。動态優先級是采用一種獎勵機制來實作的。獎勵機制有兩種方式:獎勵互動式線程、懲罰占用 CPU 的線程。在 Linux O(1) 排程器中,最高的優先級獎勵是 -5,注意這個優先級越低越容易被線程排程器接受,是以最高懲罰的優先級是 +5。具體展現就是作業系統維護一個名為sleep_avg的變量,任務喚醒會增加 sleep_avg 變量的值,當任務被搶占或者時間量過期會減少這個變量的值,反映在獎勵機制上。

O(1) 排程算法是 核心版本的排程器,最初引入這個排程算法的是不穩定的 版本。早期的排程算法在多處理器環境中說明了通過通路正在活動數組就可以做出排程的決定。使排程可以在固定的時間 O(1) 完成。

O(1) 排程器使用了一種啟發式的方式,這是什麼意思?

在計算機科學中,啟發式是一種當傳統方式解決問題很慢時用來快速解決問題的方式,或者找到一個在傳統方法無法找到任何精确解的情況下找到近似解。

O(1) 使用啟發式的這種方式,會使任務的優先級變得複雜并且不完善,進而導緻在處理互動任務時性能很糟糕。

為了改進這個缺點,O(1) 排程器的開發者又提出了一個新的方案,即公平排程器(Completely Fair Scheduler, CFS)。 CFS 的主要思想是使用一顆紅黑樹作為排程隊列。

資料結構太重要了。

CFS 會根據任務在 CPU 上的運作時間長短而将其有序地排列在樹中,時間精确到納秒級。

CFS算法總是優先排程哪些使用 CPU 時間最少的任務。最小的任務一般都是在最左邊的位置。當有一個新的任務需要運作時,CFS會把這個任務和最左邊的數值進行對比,如果此任務具有最小時間值,那麼它将進行運作,否則它會進行比較,找到合适的位置進行插入。然後 CPU運作紅黑樹上目前比較的最左邊的任務。

在紅黑樹中選擇一個節點來運作的時間可以是常數時間,但是插入一個任務的時間是O(loog(N)),其中 N 是系統中的任務數。考慮到目前系統的負載水準,這是可以接受的。

排程器隻需要考慮可運作的任務即可。這些任務被放在适當的排程隊列中。不可運作的任務和正在等待的各種 I/O 操作或核心事件的任務被放入一個等待隊列中。等待隊列頭包含一個指向任務連結清單的指針和一個自旋鎖。自旋鎖對于并發處理場景下用處很大。

Linux 系統中的同步

下面來聊一下 Linux 中的同步機制。早期的 Linux 核心隻有一個大核心鎖(Big Kernel Lock,BKL)。它阻止了不同處理器并發處理的能力。是以,需要引入一些粒度更細的鎖機制。

Linux 提供了若幹不同類型的同步變量,這些變量既能夠在核心中使用,也能夠在使用者應用程式中使用。在地層中,Linux 通過使用atomic_set和atomic_read這樣的操作為硬體支援的原子指令提供封裝。硬體提供記憶體重排序,這是 Linux 屏障的機制。

具有進階别的同步像是自旋鎖的描述是這樣的,當兩個程序同時對資源進行通路,在一個程序獲得資源後,另一個程序不想被阻塞,是以它就會自旋,等待一會兒再對資源進行通路。Linux 也提供互斥量或信号量這樣的機制,也支援像是mutex_tryLock和mutex_tryWait這樣的非阻塞調用。也支援中斷處理事務,也可以通過動态禁用和啟用相應的中斷來實作。

Linux 啟動

下面來聊一聊 Linux 是如何啟動的。

當計算機電源通電後,BIOS會進行開機自檢(Power-On-Self-Test, POST),對硬體進行檢測和初始化。因為作業系統的啟動會使用到磁盤、螢幕、鍵盤、滑鼠等裝置。下一步,磁盤中的第一個分區,也被稱為MBR(Master Boot Record)主引導記錄,被讀入到一個固定的記憶體區域并執行。這個分區中有一個非常小的,隻有 512 位元組的程式。程式從磁盤中調入 boot 獨立程式,boot 程式将自身複制到高位位址的記憶體進而為作業系統釋放低位位址的記憶體。

複制完成後,boot 程式讀取啟動裝置的根目錄。boot 程式要了解檔案系統和目錄格式。然後 boot 程式被調入核心,把控制權移交給核心。直到這裡,boot 完成了它的工作。系統核心開始運作。

核心啟動代碼是使用彙編語言完成的,主要包括建立核心堆棧、識别 CPU 類型、計算記憶體、禁用中斷、啟動記憶體管理單元等,然後調用 C 語言的 main 函數執行作業系統部分。

這部分也會做很多事情,首先會配置設定一個消息緩沖區來存放調試出現的問題,調試資訊會寫入緩沖區。如果調試出現錯誤,這些資訊可以通過診斷程式調出來。

然後作業系統會進行自動配置,檢測裝置,加載配置檔案,被檢測裝置如果做出響應,就會被添加到已連結的裝置表中,如果沒有相應,就歸為未連接配接直接忽略。

配置完所有硬體後,接下來要做的就是仔細手工處理程序0,設定其堆棧,然後運作它,執行初始化、配置時鐘、挂載檔案系統。建立init 程序(程序 1 )和守護程序(程序 2)。

getty 程式會在終端上輸入

login:

等待使用者輸入使用者名,在輸入使用者名後,getty 程式結束,登陸程式/bin/login開始運作。login 程式需要輸入密碼,并與儲存在/etc/passwd中的密碼進行對比,如果輸入正确,login 程式以使用者 shell 程式替換自身,等待第一個指令。如果不正确,login 程式要求輸入另一個使用者名。

Linux 記憶體管理

Linux 記憶體管理模型非常直接明了,因為 Linux 的這種機制使其具有可移植性并且能夠在記憶體管理單元相差不大的機器下實作 Linux,下面我們就來認識一下 Linux 記憶體管理是如何實作的。

基本概念

每個 Linux 程序都會有位址空間,這些位址空間由三個段區域組成:text 段、data 段、stack 段。

資料段(data segment)包含了程式的變量、字元串、數組和其他資料的存儲。資料段分為兩部分,已經初始化的資料和尚未初始化的資料。其中尚未初始化的資料就是我們說的 BSS。資料段部分的初始化需要編譯就期确定的常量以及程式啟動就需要一個初始值的變量。所有 BSS 部分中的變量在加載後被初始化為 0 。

和代碼段(Text segment)不一樣,data segment 資料段可以改變。程式總是修改它的變量。而且,許多程式需要在執行時動态配置設定空間。Linux 允許資料段随着記憶體的配置設定和回收進而增大或者減小。為了配置設定記憶體,程式可以增加資料段的大小。在 C 語言中有一套标準庫malloc經常用于配置設定記憶體。程序位址空間描述符包含動态配置設定的記憶體區域稱為堆(heap)。

第三部分段是棧段(stack segment)。在大部分機器上,棧段會在虛拟記憶體位址頂部位址位置處,并向低位置處(向位址空間為 0 處)拓展。舉個例子來說,在 32 位 x86 架構的機器上,棧開始于0xC0000000,這是使用者模式下程序允許可見的 3GB 虛拟位址限制。如果棧一直增大到超過棧段後,就會發生硬體故障并把頁面下降一個頁面。

當程式啟動時,棧區域并不是空的,相反,它會包含所有的 shell 環境變量以及為了調用它而向 shell 輸入的指令行。舉個例子,當你輸入

cp cxuan lx

時,cp 程式會運作并在棧中帶着字元串cp cxuan lx,這樣就能夠找出源檔案和目标檔案的名稱。

當兩個使用者運作在相同程式中,例如編輯器(editor),那麼就會在記憶體中保持編輯器程式代碼的兩個副本,但是這種方式并不高效。Linux 系統支援共享文本段作為替代。下面圖中我們會看到 A 和 B 兩個程序,它們有着相同的文本區域。

資料段和棧段隻有在 fork 之後才會共享,共享也是共享未修改過的頁面。如果任何一個都需要變大但是沒有相鄰空間容納的話,也不會有問題,因為相鄰的虛拟頁面不必映射到相鄰的實體頁面上。

除了動态配置設定更多的記憶體,Linux 中的程序可以通過記憶體映射檔案來通路檔案資料。這個特性可以使我們把一個檔案映射到程序空間的一部分而該檔案就可以像位于記憶體中的位元組數組一樣被讀寫。把一個檔案映射進來使得随機讀寫比使用 read 和 write 之類的 I/O 系統調用要容易得多。共享庫的通路就是使用了這種機制。

繼續閱讀