Linux操作系統基礎綜述筆記

一、Linux內核基礎白話

1. 在操作系統中,輸入設備驅動其實就像客戶對接員。有時候新插上一個鼠標的時候,會彈出一個通知安裝驅動,這就是操作系統這家外包公司在配備對接人員。當客戶告訴對接員需求的時候,對於操作系統來說,輸入設備會發送一個中斷。這個概念很好理解,客戶肯定希望外包公司把正在做的事情都停下來服務它。所以,這個時候客戶發送的需求就被稱爲中斷事件(Interrupt Event)。

顯卡驅動在操作系統中稱爲輸出設備驅動,也就像是交付人員。有了客戶對接員和交付人員,外包公司就可以處理用戶“在桌面上點擊QQ圖標”的事件了。首先,鼠標雙擊會觸發一箇中斷,相當於客戶告知客戶對接員“有了新需求需要處理”。操作系統事先把處理這種問題的方法教給客戶對接員,就是調用中斷處理函數。操作系統發現雙擊的是一個圖標,就準備運行QQ。

對QQ這個程序來說,它能做哪些事情,每個事情怎麼做和順序,都已經作爲程序邏輯寫在裏面,並且編譯成爲二進制了,這個程序就相當於項目執行計劃書。電腦上各程序都以二進制文件的形式保存在硬盤上,而硬盤要按照規定格式化成爲文件系統,才能存放這些程序。文件系統需要一個系統進行統一管理,稱爲文件管理子系統(File Management Subsystem)。當操作系統拿到QQ的二進制執行文件時,就可以運行這個文件了。QQ的二進制文件是靜態的,稱爲程序(Program),而運行起來的QQ,是不斷進行的,稱爲進程(Process)。

2. 項目立項過程中,需要用到公司各種資源,這裏有個兩難的權衡,一方面資源是有限的,甚至是涉及機密的,不能由項目組濫取濫用;另一方面就是效率,保證項目申請資源的時候只跑一次,這樣才能比較高效。爲了平衡這一點,一方面涉及核心權限的資源,還是應該被公司嚴格把控,審批了才能用;另外一方面,爲了提高效率,最好有個統一的辦事大廳,明文列出提供哪些服務,誰需要可以來申請,然後就會有迴應。

在操作系統中也有同樣的問題,例如多個進程都要往打印機上打印文件,如果隨便亂打印進程,就會出現同樣一張紙,第一行是A進程輸出的文字,第二行是B進程輸出的文字,全亂套了。所以,打印機的直接操作是放在操作系統內核裏面的,進程不能隨便操作。但是操作系統也提供一個辦事大廳,也就是系統調用(System Call)。系統調用列出來提供哪些接口可以調用,進程有需要的時候就可以去調用。項目立項是辦事大廳提供的關鍵服務之一,同樣任何一個程序要想運行起來,就需要調用系統調用,創建進程

3. 就像項目經理管理調度開發人員在各項目組中進行工作,在操作系統中,進程的執行也需要分配CPU進行執行,也就是按照程序裏面的二進制代碼一行一行地執行。於是,爲了管理進程還需要一個進程管理子系統(Process Management Subsystem)。如果運行的進程很多,則一個CPU會併發運行多個進程,也就需要CPU的調度能力了。

每個項目都有自己的私密資料,這些資料不能被其他項目組看到。如果不同項目的辦公空間不隔離,一方面,項目的私密性不能得到保證,A項目的細節B項目也能看到;另一方面項目之間會相互干擾,A項目組的人剛在白板上畫了一個架構圖,出去上個廁所,結果 B 項目組的人就給擦了。如果把不同的項目組分配到不同的會議室,就解決了這個問題。當然會議室是有限的,需要需要一個會議室管理系統。在操作系統中,不同的進程有不同的內存空間,但是整個電腦內存有限,所以需要統一的管理和分配,這就需要內存管理子系統(Memory Management Subsystem)。

4. QQ啓動之後,有一部分代碼會在顯示器上畫一個對話框,並且將鍵盤的焦點放在了輸入框裏面。CPU根據這些指令,就會告知顯卡驅動程序,將這個對話框畫出來。當用戶通過鍵盤打字的時候,鍵盤也是輸入設備,也會觸發中斷,通知相應的輸入設備驅動程序。假設用戶輸入了一個“a”,QQ進程是不能直接發送網絡包的,需要調用系統調用,內核使用網卡驅動程序進行發送。這就像客戶對接員接到一個需求,但是這個需求需要和其他公司溝通,這就需要依靠公司的對外合作部,對外合作部在辦事大廳有專門的窗口。各子系統的比喻和關係如下所示:

二、常用Linux命令

5. 在Linux中,以管理員賬戶身份在命令行裏使用useradd命令就能創建一個子用戶:

 useradd username

執行這個命令,一個用戶就被創建了。它不會彈出讓輸入密碼之類的頁面,就會直接返回了。因爲接下來需要自己調用passwd username設置密碼再進行登錄。在Windows裏設置用戶的時候,用戶有一個“組”的概念,比如“Adminsitrator組”“Guests組”“Power User組”等。同樣Linux裏也是分組的。前面創建用戶的時候,沒有說加入哪個組,默認就會創建一個同名的組

通過命令創建的用戶,是放在/etc/passwd文件裏的,這是一個文本文件。可以通過cat命令,將裏面的內容輸出在命令行上。組的信息放在/etc/group文件中,如下所示:

# cat /etc/passwd
root:x:0:0:root:/root:/bin/bash
......
username:x:1000:1000::/home/cliu8:/bin/bash


# cat /etc/group
root:x:0:
......
username:x:1000:

在/etc/passwd文件裏,可以看到root 戶和剛創建的username用戶。x的地方是密碼,密碼當然不會放在這裏,不然誰都知道了。接下來是用戶ID和組ID,這和/etc/group裏面就對應上了。/root和/home/username分別是root用戶和username用戶的主目錄,主目錄是用戶登錄進去後默認的路徑

/bin/bash的位置是用於配置登錄後的默認交互命令行的,不像Windows登錄進去是界面,其實就是explorer.exe。而Linux登錄後的交互命令行是一個解析腳本的程序,這裏配置的是/bin/bash。

6. cd命令就是 change directory,就是切換目錄;cd .表示切換到當前目錄;cd ..表示切換到上一級目錄;在Windows中使用dir命令,可以列出當前目錄下的文件,Linux列出當前目錄下的文件用的是ls,意思是list。常用的是ls -l,也就是用列表的方式列出文件,如下所示:

# ls -l
drwxr-xr-x 6 root root    4096 Oct 20  2017 apt
-rw-r--r-- 1 root root     211 Oct 20  2017 hosts

其中第一個字段的第一個字符是文件類型,如果是“-”表示普通文件;如果是d就表示目錄。第一個字段剩下的9個字符是模式,其實就是權限位(access permission bits),3個一組,每一組rwx表示“讀(read)”“寫(write)”“執行(execute)”。如果是字母,就說明有這個權限;如果是橫線,就是沒有這個權限。這三組分別表示文件所屬的用戶權限、文件所屬的組權限以及其他用戶的權限。例如上面的例子中,-rw-r–r--就可以翻譯爲,這是一個普通文件,對於所屬用戶,可讀可寫不能執行;對於所屬的組,僅僅可讀;對於其他用戶,也是僅僅可讀。如果想改變權限,可以使用命令chmod 711 hosts

第二個字段是硬鏈接(hard link)數目,第三個字段是所屬用戶,第四個字段是所屬組。第五個字段是文件的大小,第六個字段是文件被修改的日期,最後是文件名。可以通過命令chown改變所屬用戶,chgrp改變所屬組。在Linux中,沒有雙擊安裝這一說(如果無圖形界面),因此想要安裝還得需要命令。CentOS下使用rpm -i XXX_linux-x64_bin.rpm進行安裝,Ubuntu下使用dpkg -i XXX_linux-x64_bin.deb,其中-i就是install的意思。

7. 在Linux下,憑藉rpm -qadpkg -l就可以查看安裝的軟件列表,-q就是 query,a就是all,-l的意思就是list。如果真的去運行的話,會發現這個列表很長很長,很難找到安裝的軟件。如果知道要安裝的軟件包含某個關鍵詞,可以用一個很好用的搜索工具 grep。例如rpm -qa | grep jdk,這個命令是將列出來的所有軟件形成一個輸出。| 是管道,用於連接兩個程序,前面rpm -qa的輸出就放進管道里面,然後作爲grep的輸入,grep將在裏面進行搜索帶關鍵詞jdk的行,並且輸出出來。

grep支持正則表達式,因此搜索的時候很靈活,再加上管道,這是一個很常用的模式。同理dpkg -l | grep jdk也是能夠找到的。如果不知道關鍵詞,可以使用rpm -qa | more和rpm -qa | less這兩個命令,它們可以將很長的結果分頁展示出來,這樣就可以一個個來找了。more是分頁後只能往後翻頁,翻到最後一頁自動結束返回命令行,less是往前往後都能翻頁,需要輸入q返回命令行,q就是 quit。如果要刪除,可以用rpm -edpkg -r。-e就是erase,-r就是remove。

8. Linux也有自己的軟件管家,CentOS下面是 yum,Ubuntu下面是apt-get。可以根據關鍵詞搜索,例如搜索jdk可以使用yum search jdkapt-cache search jdk,可以搜索出很多很多可以安裝的jdk版本。如果數目太多,可以通過管道grep、more、less來進行過濾。選中一個之後就可以進行安裝了,可以用yum install java-11-openjdk.x86_64或apt install openjdk-9-jdk來進行安裝。安裝以後可以使用yum erase java-11-openjdk.x86_64apt purge openjdk-9-jdk來卸載JDK。

Linux允許配置從哪裏下載這些軟件,對於CentOS來說配置文件在/etc/yum.repos.d/CentOS-Base.repo裏,如下所示:

[base]
name=CentOS-$releasever - Base - 163.com
baseurl=http://mirrors.163.com/centos/$releasever/os/$basearch/
gpgcheck=1
gpgkey=http://mirrors.163.com/centos/RPM-GPG-KEY-CentOS-7

對於Ubuntu來說,配置文件在/etc/apt/sources.list裏,如下所示:

deb http://mirrors.163.com/ubuntu/ xenial main restricted universe multiverse
deb http://mirrors.163.com/ubuntu/ xenial-security main restricted universe multiverse
deb http://mirrors.163.com/ubuntu/ xenial-updates main restricted universe multiverse
deb http://mirrors.163.com/ubuntu/ xenial-proposed main restricted universe multiverse
deb http://mirrors.163.com/ubuntu/ xenial-backports main restricted universe multiverse

其實無論是先下載再安裝,還是通過軟件管家進行安裝,都是下載一些文件,然後將這些文件放在某個路徑下,然後在相應的配置文件中配置一下。例如在Windows裏,最終會變成C:\Program Files下面的一個文件夾以及註冊表裏面的一些配置。對應 Linux裏會放的更散一點。例如主執行文件會放在/usr/bin或者/usr/sbin下面,其他的庫文件會放在/var下面,配置文件會放在/etc下面

9. 通過tar命令解壓縮下載的壓縮包之後,也需要配置環境變量,可以通過export命令來配置。如下所示:

export JAVA_HOME=/root/jdk-XXX_linux-x64
export PATH=$JAVA_HOME/bin:$PATH

export命令僅在當前命令行的會話中管用,一旦退出重新登錄進來,就不管用了。在當前用戶的默認工作目錄,例如/root或者/home/username下面,有一個.bashrc文件,這個文件是以點開頭的,默認看不到,需要ls -al才能看到,a就是all。每次登錄的時候,這個文件都會運行,因而可以把該export命令放在這個文件裏。當然也可以通過source .bashrc手動執行。要編輯.bashrc文件,可以使用文本編輯器vim。

10. Linux不是像Windows那樣根據.exe後綴名來執行的。它的執行條件是這樣的:只要文件有x執行權限,都能到文件所在的目錄下,通過./filename運行這個程序。當然,如果放在PATH裏設置的路徑下面,就不用./了,直接輸入文件名就可以運行了,Linux會自動找到路徑。一般命令行被關閉了,該會話下的所有程序都會退出,爲了持續後臺運行一個程序,往往使用nohup命令,這個命令的意思是no hang up(不掛起),也就是說當前交互命令行退出的時候,程序還要在。當然這個時候,程序不能霸佔交互命令行,而是應該在後臺運行,因此最後再加一個&,就表示後臺運行。

另外一個要處理的就是輸出,原來什麼都打印在交互命令行裏,現在在後臺運行了,輸出到文件是最好的。最終命令的一般形式爲nohup command >out.file 2>&1 &,這裏“1”表示文件描述符1,表示標準輸出,“2”表示文件描述符2,意思是標準錯誤輸出,“2>&1”表示標準輸出和錯誤輸出合併了,合併到out.file裏。

如果要關閉進程,假設啓動的程序包含某個關鍵字,那就可以使用下面的命令:

ps -ef |grep 關鍵字  |awk '{print $2}'|xargs kill -9

其中ps -ef可以單獨執行,列出所有正在運行的程序,awk工具可以很靈活地對文本進行處理,這裏的awk '{print $2}'是指第二列的內容,是運行的程序ID。然後可以通過xargs傳遞給kill -9,也就是發給這個運行的程序一個信號,讓它關閉。如果已經知道運行的程序ID,可以直接使用kill關閉運行的程序。

11. Linux也有相應的開機啓動服務。例如MySQL就可以使用這種方式運行。例如在Ubuntu中可以通過apt install mysql-server的方式安裝MySQL,然後通過命令systemctl start mysql啓動MySQL,通過systemctl enable mysql設置開機啓動。之所以成爲服務並且能夠開機啓動,是因爲在/lib/systemd/system目錄下會創建一個XXX.service的配置文件,裏面定義瞭如何啓動、如何關閉。常用Linux命令如下所示:

三、常見系統調用

12. Linux中創建進程的系統調用叫fork。在 Linux 裏,要創建一個新的進程,需要一個老的進程調用fork來實現,其中老的進程叫作父進程(Parent Process),新的進程叫作子進程(Child Process)。一個進程的運行是要有一個程序的,就像一個項目的執行,要有一個項目執行計劃書。本來老的項目,按照項目計劃書按部就班地來,項目執行到一半,突然接到命令,說要新啓動一個項目需要涉及公司各個部門的工作,比如說,項目管理部門需要給這個項目組開好Jira和Wiki,會議室管理部要爲這個項目分配會議室等等。

所以,現在有兩種方式,一種是列一個清單,清單裏面寫明每個新項目組都要開哪些賬號。但是,這樣每次有項目,都要重新配置一遍新的Jira、Wiki,複雜得很。另一種方式就是程序員常用的方式,CTRL/C + CTRL/V。也就是說,如果想爲新項目建立一套Jira,但是一個個填Jira裏面的選項太麻煩,那就可以拷貝一個別人的,然後根據新項目的實際情況,將相應的配置改改。

Linux 就是這樣想的。當父進程調用 fork 創建進程的時候,子進程將各個子系統爲父進程創建的數據結構也全部拷貝了一份,甚至連程序代碼也是拷貝過來的。按理說,如果不進行特殊的處理,父進程和子進程都按相同的程序代碼進行下去,這樣就沒意義了,所以往往會這樣處理:對於fork系統調用的返回值,如果當前進程是子進程,就返回0;如果當前進程是父進程,就返回子進程的進程號。這樣首先在返回值這裏就有了一個區分,然後通過if-else語句判斷,如果是父進程,還接着做原來應該做的事情;如果是子進程,需要請求另一個系統調用execve來執行另一個程序,這個時候,子進程和父進程就徹底分道揚鑣了,也即產生了一個分支(fork)了。

既然新進程都是父進程fork出來的,那到底誰是第一個呢?作爲一個外包公司老闆,有了新項目當然會分給手下做,但是當公司剛起步的時候沒有下屬,只好自己上了。先建立項目運行體系,等後面再做項目的時候,就都按這個來。對於操作系統也一樣,啓動的時候先創建一個所有用戶進程的“祖宗進程”。有時候,父進程要關心子進程的運行情況,有個系統調用waitpid,父進程可以調用它,將子進程的進程號作爲參數傳給它,這樣父進程就知道子進程運行完了沒有,成功與否等。

13. 在操作系統中,每個進程都有自己的內存,互相之間不干擾,即有獨立的進程內存空間。對於進程的內存空間來說,放程序代碼的這部分稱爲代碼段(Code Segment)。放進程運行中產生數據的這部分,稱爲數據段(Data Segment)。其中局部變量的部分,在當前函數執行的時候起作用,當進入另一個函數時,這個變量就釋放了;也有動態分配的,會較長時間保存,指明才銷燬的,這部分稱爲(Heap)。

只有進程要去使用部分內存的時候,纔會使用內存管理的系統調用來登記,說自己馬上就要用了,希望分配一部分內存給它,但是這還不代表真的就對應到了物理內存。只有真的寫入數據時,發現沒有對應物理內存,纔會觸發一箇中斷,現分配物理內存。而且儘管分配的邏輯虛擬內存地址是連續的,但是對應的物理內存地址並不一定連續,操作系統會維護內存地址分配的映射關係,通俗的理解如下所示:

在堆裏面分配內存的系統調用有兩種,即brkmmap當分配的內存數量比較小的時候,使用brk會和原來的堆的數據連在一起,這就像多分配兩三個工位,在原來的區域旁邊搬兩把椅子就行了。當分配的內存數量比較大的時候,使用mmap會重新劃分一塊區域,也就是說當辦公空間需要太多的時候,索性來個一整塊。

14. 對於文件的操作,這六個系統調用是最重要的:(1)對於已經有的文件,可以使用open打開這個文件,close關閉這個文件;(2)可以使用creat創建文件;(3)打開文件以後,可以使用lseek跳到文件的某個位置;(4)讀的系統調用是read,寫是write

Linux裏有一個特點,那就是一切皆文件。啓動一個進程,需要一個程序文件,這是一個二進制文件。啓動的時候,要加載一些配置文件,例如 yml、properties 等,這是文本文件;啓動之後會打印一些日誌,如果寫到硬盤上,也是文本文件。但如果想把日誌打印到交互控制檯上,在命令行上打印出來,這其實也是一個文件,是標準輸出stdout文件。這個進程的輸出可以作爲另一個進程的輸入,這種方式稱爲管道,管道也是一個文件。進程可以通過網絡和其他進程進行通信,建立的Socket,也是一個文件。進程需要訪問外部設備,設備也是一個文件。文件都被存儲在文件夾裏面,其實文件夾也是一個文件。進程運行起來,要想看到進程運行的情況,會在/proc下面有對應的進程號,還是一系列文件

每個文件Linux都會分配一個文件描述符(File Descriptor),這是一個整數。有了這個文件描述符,就可以使用系統調用,查看或者干預進程運行的方方面面。所以說文件操作是貫穿始終的,這也是“一切皆文件”的優勢,就是統一了操作的入口,提供了極大的便利

15. 在進程運行的過程中,如果遇到各種異常情況,此時系統就需要發送異常處理信號。經常遇到的信號有以下幾種:(1)在執行一個程序的時候,在鍵盤輸入“CTRL+C”,這就是中斷的信號,正在執行的命令就會中止退出;(2)如果非法訪問無權限的內存區域,例如跑到別人的會議室,可能會看到不該看的東西;(3)硬件故障,設備出了問題;(4)用戶進程通過kill函數,將一個用戶信號發送給另一個進程。

對於一些不嚴重的信號,可以忽略,但是像SIGKILL(用於終止一個進程的信號)和SIGSTOP(用於中止一個進程的信號)是不能忽略的,可以執行對於該信號的默認動作。每種信號都定義了默認的動作,例如硬件故障,默認終止;也可以提供信號處理函數,可以通過sigaction系統調用,註冊一個信號處理函數。

16. 進程之間的溝通方式有多種:

(1)首先就是發個消息,不需要一段很長的數據,這種方式稱爲消息隊列(Message Queue)。消息隊列是在內核裏的,可以通過msgget創建一個新的隊列,msgsnd將消息發送到消息隊列,而消息接收方可以使用msgrcv從隊列中取消息。

(2)當兩個進程需要交互的信息比較大時,可以使用共享內存的方式,也即兩個項目組共享一個會議室(這樣數據就不需要拷貝來拷貝去)。這時候,可以通過shmget創建一個共享內存塊,通過shmat將共享內存映射到自己的內存空間,然後就可以讀寫了。

但是,兩個進程共同訪問一個共享內存裏的數據,就會存在“競爭”的問題。如果大家同時修改同一塊數據,就需要有一種方式,讓不同的人能夠排他地訪問,這就是信號量的機制Semaphore。例如,對於只允許一個人訪問的需求,可以將信號量設爲1。當一個人要訪問的時候,先調用sem_wait。如果這時候沒有人訪問,則佔用這個信號量,就可以開始訪問了。如果這個時候另一個人要訪問,也會調用sem_wait。由於前一個人已經在訪問了,所以後面這個人就必須等待上一個人訪問完之後才能訪問。當上一個人訪問完畢後,會調用sem_post將信號量釋放,於是下一個人等待結束,可以訪問這個資源了。

17. 當兩臺Linux機器通過網絡相互通信時,要遵循相同的網絡協議,也即TCP/IP網絡協議棧。網絡服務是通過套接字Socket來提供服務的。在通信之前,雙方都要建立一個Socket。可以通過Socket系統調用建立一個Socket。Socket也是一個文件,也有一個文件描述符,也可以通過讀寫函數進行通信。對於64位操作系統,找到unistd_64.h文件,裏面對於系統調用的定義,就是下面這樣:

#define __NR_restart_syscall    0
#define __NR_exit      1
#define __NR_fork      2
#define __NR_read      3
#define __NR_write      4
#define __NR_open      5
#define __NR_close      6
#define __NR_waitpid      7
#define __NR_creat      8
......

18. Glibc是Linux下使用的開源的標準C庫,它是GNU發佈的libc庫。Glibc爲程序員提供豐富的API,除了例如字符串處理、數學運算等用戶態服務之外,最重要的是封裝了操作系統提供的系統服務,即系統調用的封裝。每個特定的系統調用對應了至少一個Glibc封裝的庫函數,比如系統提供的打開文件系統調用sys_open對應的是Glibc中的open函數。有時候Glibc一個單獨的API可能調用多個系統調用,比如Glibc提供的printf函數就會調用如 sys_open、sys_mmap、sys_write、sys_close等等系統調用。也有時候多個API也可能只對應同一個系統調用,如 Glibc下實現的malloc、calloc、free等函數用來分配和釋放內存,都利用了內核的sys_brk的系統調用。Linux的諸多系統調用如下所示:

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章