1. Linux開機啓動html
2. Linux文件管理python
3. Linux的架構linux
4. Linux命令行與命令程序員
5. Linux文件管理相關命令正則表達式
6. Linux文本流shell
7. Linux進程基礎apache
8. Linux信號基礎編程
9. Linux進程關係windows
10. Linux用戶瀏覽器
11. Linux從程序到進程
12. Linux多線程與同步
13. Linux進程間通訊
14. Linux文件系統的實現
15. Linux經常使用命令
====================================================================================================
當咱們打開計算機電源,計算機會自動從主板的BIOS(Basic Input/Output System)讀取其中所存儲的程序。這一程序一般知道一些直接鏈接在主板上的硬件(硬盤,網絡接口,鍵盤,串口,並口)。如今大部分的BIOS容許你從軟盤、光盤或者硬盤中選擇一個來啓動計算機。
下一步,計算機將從你所選擇的存儲設備中讀取起始的512 bytes(好比光盤一開是的512 bytes,若是咱們從光盤啓動的話)。這512 bytes叫作主引導記錄MBR (master boot record)。MBR會告訴電腦從該設備的某一個分區(partition)來裝載引導加載程序(boot loader)。Boot loader儲存有操做系統(OS)的相關信息,好比操做系統名稱,操做系統內核 (kernel)所在位置等。經常使用的boot loader有GRUB和LILO。
隨後,boot loader會幫助咱們加載kernel。kernel其實是一個用來操做計算機的程序,它是計算機操做系統的內核,主要的任務是管理計算機的硬件資源,充當軟件和硬件的接口。操做系統上的任何操做都要經過kernel傳達給硬件。Windows和Linux各自有本身kernel。狹義的操做系統就是指kernel,廣義的操做系統包括kernel以及kernel之上的各類應用。(Linus Torvalds與其說是Linux之父,不如說是Linux kernel之父。他依然負責Linux kernel的開發和維護。至於Ubuntu, Red Hat, 它們都是基於相同的kernel之上,囊括了不一樣的應用和界面構成的一個更加完整的操做系統版本。)
實際上,咱們能夠在多個分區安裝boot loader,每一個boot loader對應不一樣的操做系統,在讀取MBR的時候選擇咱們想要啓動的boot loader。這就是多操做系統的原理。
小結:BIOS -> MBR -> boot loader -> kernel
若是咱們加載的是Linux kernel,Linux kernel開始工做。kernel會首先預留本身運行所需的內存空間,而後經過驅動程序(driver)檢測計算機硬件。這樣,操做系統就能夠知道本身有哪些硬件可用。隨後,kernel會啓動一個init進程。它是Linux系統中的1號進程(Linux系統沒有0號進程)。到此,kernel就完成了在計算機啓動階段的工做,交接給init來管理。
小結: kernel -> init process
(根據boot loader的選項,Linux此時能夠進入單用戶模式(single user mode)。在此模式下,初始腳本尚未開始執行,咱們能夠檢測並修復計算機可能存在的錯誤)
隨後,init會運行一系列的初始腳本(startup scripts),這些腳本是Linux中常見的shell scripts。這些腳本執行以下功能:
設置計算機名稱,時區,檢測文件系統,掛載硬盤,清空臨時文件,設置網絡……
當這些初始腳本,操做系統已經徹底準備好了,只是,尚未人能夠登陸!!!init會給出登陸(login)對話框,或者是圖形化的登陸界面。
輸入用戶名(好比說vamei)和密碼,DONE!
在此後的過程當中,你將以用戶(user)vamei的身份操做電腦。此外,根據你建立用戶時的設定,Linux還會將你歸到某個組(group)中,好比能夠是stupid組,或者是vamei組。因此你將是用戶vamei, 同時是vamei組的組員。(注意,組vamei和用戶vamei只是重名而已,就好想你能夠叫Dell, 同時仍是Dell公司的老闆同樣。你徹底也能夠是用戶vamei,同時爲stupid組的組員)
BIOS -> MBR -> boot loader -> kernel -> init process -> login
用戶,組。
對於計算機來講,所謂的數據就是0和1的序列。這樣的一個序列能夠存儲在內存中,但內存中的數據會隨着關機而消失。爲了將數據長久保存,咱們把數據存儲在光盤或者硬盤中。根據咱們的須要,咱們一般會將數據分開保存到文件這樣一個個的小單位中(所謂的小,是相對於全部的數據而言)。但若是數據只能組織爲文件的話,而不能分類的話,文件仍是會雜亂無章。每次咱們搜索某一個文件,就要一個文件又一個文件地檢查,太過麻煩。文件系統(file system)是就是文件在邏輯上組織形式,它以一種更加清晰的方式來存放各個文件。
文件被組織到文件系統(file system)中,一般會成爲一個樹狀(tree)結構。Linux有一個根目錄/, 也就是樹狀結構的最頂端。這個樹的分叉的最末端都表明一個文件,而這個樹的分叉處則是一個目錄(directory, 至關於咱們在windows界面中看到的文件夾)。在圖1中看到的是整個的一個文件樹。若是咱們從該樹中截取一部分,好比說從目錄vamei開始往下,實際上也構成一個文件系統。
要找到一個文件,除了要知道該文件的文件名,還須要知道從樹根到該文件的全部目錄名。從根目錄開始的全部途徑的目錄名和文件名構成一個路徑(path)。好比說,咱們在Linux中尋找一個文件file.txt,不只要知道文件名(file.txt),還要知道完整路徑,也就是絕對路徑(/home/vamei/doc/file.txt)。從根目錄錄/, 也就是樹狀結構的最頂端出發,通過目錄home, vamei, doc,最終纔看到文件file.txt。整個文件系統層層分級(hierarchy),vamei是home的子目錄,而home是vamei的父目錄。
在Linux中,咱們用ls命令來顯示目錄下的全部文件,好比 $ls /home/vamei/doc
圖1 文件樹
如該圖中所示的文件系統,即綠色構成的樹。最頂端的根目錄(/),沿紅色箭頭標出的路徑,咱們最終找到文件file.txt。
在Linux系統中,目錄也是一種文件。因此/home/vamei是指向目錄文件vamei的絕對路徑。
這個文件中至少包含有如下條目:
. 指向當前目錄
.. 指向父目錄
除此以外,目錄文件中還包含有屬於該目錄的文件的文件名,好比vamei中就還要有以下條目,指向屬於該目錄的文件:
doc
movie
photo
Linux解釋一個絕對路徑的方式以下:先找到根目錄文件,從該目錄文件中讀取home目錄文件的位置,而後從home文件中讀取vamei的位置……直到找到目錄doc中的file.txt的位置。
因爲目錄文件中都有.和..的條目,咱們能夠在路徑中加入.或者..來表示當前目錄或者父目錄,好比/home/vamei/doc/..與/home/vamei等同。
此外,Linux會在進程中,維護一個工做目錄(present working directory)的變量。在shell中,你能夠隨時查詢到到工做目錄(在命令行輸入$pwd)。這是爲了省去每次都輸入很長的絕對路徑的麻煩。好比說咱們將工做目錄更改成/home/vamei ($cd /home/vamei),那麼此時咱們再去找file.txt就能夠省去/home/vamei/ ($ls doc/file.txt),這樣獲得的路徑叫相對路徑(relative path),上面的doc/file.txt就是這樣一個相對路徑。
當文件出如今一個目錄文件中時,咱們就把文件接入到文件系統中,咱們稱創建一個到文件的硬連接(hard link)。一個文件容許出如今多個目錄中,這樣,它就有多個硬連接。當硬連接的數目(link count)降爲0時,文件會被Linux刪除。因此不少時候,unlink與remove在Linux操做系統中是一個意思。因爲軟連接(soft link)的普遍使用(soft link不會影響link count,並且能夠跨越文件系統),如今較少手動創建硬鏈接。
對於文件,咱們能夠讀取(read),寫入(write)和運行(execute)。讀取是從已經存在的文件中得到數據。寫入是向新的文件或者舊的文件寫入數據。若是文件儲存的是可執行的二進制碼,那麼它能夠被載入內存,做爲一個程序運行。在Linux的文件系統中,若是某個用戶想對某個文件執行某一種操做,那麼該用戶必須擁有對該文件進行這一操做的權限。文件權限的信息保存在文件信息(metadata)中, 見下一節。
文件自身包含的只有數據。文件名實際上儲存在目錄文件。除了這些以外,還有操做系統維護的文件附加信息,好比文件類型,文件尺寸,文件權限,文件修改時間,文件讀取時間等。能夠用ls命令查詢文件信息($ls -l file.txt),獲得以下結果:
-rw-r--r-- 1 vamei vamei 8445 Sep 8 07:33 file1.txt
各個部分的含義以下:
如上討論硬連接時說到的,軟連接不會影響文件的link count。若是還記得windows系統的快捷方式的話,Linux的軟連接(soft link,也叫作symbolic link)就是linux的快捷方式。軟連接本質上是一個文件,它的文件類型是symbolic link。在這個文件中,包含有連接指向的文件的絕對路徑。當你從這個文件讀取數據時,linux會把你導向所指向的文件,而後從那個文件中讀取(就好像你雙擊快捷方式的效果同樣)。軟連接能夠方便的在任何地方創建,並指向任何一個絕對路徑。
軟連接自己也是一個文件,也能夠執行文件所能夠進行的操做。當咱們對軟連接操做時,要注意咱們是對軟連接自己操做,仍是對軟連接指向的目標操做。若是是後者,咱們就說該操做跟隨連接指引(follow the link)。
當咱們建立文件的時候,好比使用touch,它會嘗試將新建文件建立爲權限666,也就是rw-rw-rw-。但操做系統要參照權限mask來看是否真正將文件建立爲666。權限mask表示操做系統不容許設置的權限位,好比說037(----wxrwx)的權限mask意味着不容許設置設置group的wx位和other的rwx位。若是是這個權限mask的話,最終的文件權限是rw-r----- (group的w位和other的rw位被mask)。
咱們能夠經過
$umask 022
的方式改變權限mask。
計算機本質上是對數據進行處理的工具,而文件是數據儲存的邏輯載體,因此瞭解Linux文件系統很重要。對於文件系統的瞭解要結合Linux的其餘方面(好比用戶管理)進行有機的學習。
文件權限,擁有者,擁有組,超級用戶root
硬連接,軟連接,follow the link
如下圖爲基礎,說明Linux的架構(architecture)。
最內層是硬件,最外層是用戶經常使用的應用,好比說firefox瀏覽器,evolution查看郵件,一個計算流體模型等等。硬件是物質基礎,而應用提供服務。但在二者之間,還要通過一番周折。Linux首先啓動內核 (kernel),內核是一段計算機程序,這個程序直接管理管理硬件,包括CPU、內存空間、硬盤接口、網絡接口等等。全部的計算機操做都要經過內核傳遞給硬件。
爲了方便調用內核,Linux將內核的功能接口製做成系統調用(system call)。系統調用看起來就像C語言的函數。你能夠在程序中直接調用。Linux系統有兩百多個這樣的系統調用。用戶不須要了解內核的複雜結構,就可使用內核。系統調用是操做系統的最小功能單位。一個操做系統,以及基於操做系統的應用,都不可能實現超越系統調用的功能。一個系統調用函數就像是漢字的一個筆畫。任何一個漢字都要由基本的筆畫(點、橫、撇等等)構成。我不能臆造筆畫。
在命令行中輸入$man 2 syscalls能夠查看全部的系統調用。你也能夠經過$man 2 read來查看系統調用read()的說明。在這兩個命令中的2都表示咱們要在2類(系統調用類)中查詢 (具體各個類是什麼能夠經過$man man看到)。
系統調用提供的功能很是基礎,因此使用起來很麻煩。一個簡單的給變量分配內存空間的操做,就須要動用多個系統調用。Linux定義一些庫函數(library routine)來將系統調用組合成某些經常使用的功能。上面的分配內存的操做,能夠定義成一個庫函數(像malloc()這樣的函數)。再好比說,在讀取文件的時候,系統調用要求咱們設置好所須要的緩衝。我可使用Standard IO庫中的讀取函數。這個讀取函數既負責設置緩衝,又負責使用讀取的系統調用函數。使用庫函數對於機器來講並無效率上的優點,但能夠把程序員從細節中解救出來。庫函數就像是漢字的偏旁部首,它由筆畫組成,但使用偏旁部首更容易組成字,好比"鐵"。固然,你也徹底能夠不使用庫函數,而直接調用系統函數,就像「人」字同樣,不用偏旁部首。
(實際上,一個操做系統要稱得上是UNIX系統,必需要擁有一些庫函數,好比ISO C標準庫,POSIX標準等。)
shell是一個特殊的應用。不少用戶將它稱爲命令行。shell是一個命令解釋器(interpreter),當咱們輸入「ls -l」的時候,它將此字符串解釋爲
我以前用>表示從新定向,用|表示管道,也是經過shell解釋&或者|的含義。Shell接着經過系統調,用指揮內核,實現具體的重定向或者管道。在沒有圖形界面以前,shell充當了用戶的界面,當用戶要運行某些應用時,經過shell輸入命令,來運行程序。shell是可編程的,它能夠執行符合shell語法的文本。這樣的文本叫作shell腳本(script)。能夠在架構圖中看到,shell下通系統調用,上通各類應用,同時還有許多自身的小工具可使用。Shell腳本能夠在寥寥數行中,實現複雜的功能。
UNIX的一條哲學是讓每一個程序儘可能獨立的作好一個小的功能。而shell充當了這些小功能之間的"膠水",讓不一樣程序可以以一個清晰的接口(文本流)協同工做,從而加強各個程序的功能。這也是Linux老鳥鼓勵新手多用shell,少用圖形化界面的緣由之一。
(shell也有不少種,最多見的是bash, 另外還有sh, csh, tcsh, ksh。它們出現的年代不一樣,所支持的功能也有差別。)
一個使用bash shell的終端
一個shell對應一個終端 (terminal)。曾經來講,終端是一個硬件設備,用來輸入並顯示輸出。現在,因爲圖形化界面的普及,終端每每就像上圖同樣,是一個圖形化的窗口。你能夠經過這個窗口輸入或者輸出文本。這個文本直接傳遞給shell進行分析解釋,而後執行。
最後,咱們進入通常的應用。應用是一個程序,它能夠
這些應用能夠由多種語言開發。最多見的是C語言。
Linux利用內核實現軟硬件的對話。
經過系統調用的這個接口,Linux將上層的應用與下層的內核分離,隱藏了底層的複雜性,也提升了上層應用的可移植性。
庫函數利用系統調用創造出模塊化的功能,
Shell則提供了一個用戶界面,並讓咱們能夠利用shell的語法編寫腳本,以整合程序。
咱們一般所說的Linux命令行是運行在終端(terminal)的shell
所謂的命令,是咱們在命令行輸入的一串字符。shell負責理解並執行這些字符串。shell命令能夠分爲以下幾類
1)可執行文件(executable file)
2)shell內建函數(built-in function)
3) 別名(alias)。
可執行文件爲通過編譯的程序文件,咱們輸入這些文件的路徑來讓shell運行,好比$/bin/ls。有些可執行文件被放在特殊的目錄(默認路徑)下面,從而使得操做系統能夠經過文件名找到,而不用老是輸入該文件的絕對路徑(absolute path)。好比說$ls(實際上,shell自動幫咱們補齊ls的路徑)。隨後,這些可執行文件中包含的程序運行,併成爲進程。shell的內建函數與上面相似,只是其對應的程序被保存在shell的內部。別名是指咱們給以上兩種命令起一個簡稱,以便減小輸入的工做量。
咱們能夠經過type命令來了解命令的類型:
$type ls
$type cd
當咱們在命令行輸入命令的時候,每每由一下方式構成:
$ls -l /home
整個一行命令由空格分爲三個部分(注意, $是自動出現的提示符,有時還會在此以前出現計算機名)。第一個爲命令的名字ls,這個命令ls的功能是列出目錄中全部文件,第二個-l是關鍵字,它告訴ls要列出每一個文件的詳細信息,第三個/home爲參數,表示我所要列出的目錄是/home。實際上關鍵字是一種特殊的參數,大部分狀況下用來開關程序的某些特殊功能 (用來選擇作出的是拿鐵咖啡仍是黑咖啡)。而參數是用來傳遞給程序的通常的變量。ls通過處理以後,將在終端輸出/home下面包含的各個文件名 (該文件系統見:http://www.cnblogs.com/vamei/archive/2012/09/09/2676792.html):
vamei another
關鍵字和參數能夠不止有一個,好比:
$ls -l -a /home /bin
$ls -la /home /bin
(上面兩個命令等價)
列出/home和/bin目錄下的文件,-a表示列出所有文件(即便是隱藏文件也要列出), -l表示列出每一個文件的詳細信息。
(若是命令沒有被放在默認路徑中,你也能夠輸入絕對路徑來執行)
每一個文件可否被執行要根據用戶所擁有的權限。命令其實是可執行文件,也是如此。系統相關的命令,或者某個命令中的定義的操做,每每會要求超級用戶root的身份才能使用。若是你是用戶vamei,那麼你就沒法使用這些命令。但以root的身份登陸是個糟糕的想法。爲了解決這一矛盾,你能夠以vamei的身份登陸,但在執行命令以前加上sudo, 以便臨時以root的身份執行某條命令。好比$sudo ls
對於大多數的shell來講,都有命令補齊的功能。當你在$的後面輸入命令的一部分時,好比rmdir的rmd的時候,按Tab鍵,Linux會幫你打剩下的字符,補充成爲rmdir。不止是命令,若是你輸入的是文件名,Linux也能夠幫你補齊。好比說, $ls a.txt。當你輸入到$ls a.t的時候,按Tab鍵,Linux會幫你補齊該文件名,成爲$ls a.txt。固然,這樣作的前提是你輸入到rmd的時候,默認路徑下能和它相符的命令只有一個rmdir了。若是有多個相符的命令,連按兩下Tab,Linux會顯示全部的相符的命令。
實際上,許多命令的功能均可以經過圖形化界面來實現,學習這些命令的意義在哪裏呢?
在UNIX發育的大部分歷史上,用戶都是經過shell來工做的。大部分命令都已經通過了幾十年的發展和改良,功能強大,性能穩定。Linux繼承自UNIX,天然也是如此。此外Linux的圖形化界面並很差,並非全部的命令都有對應的圖形按鈕。更別說在圖形化界面崩潰的狀況下,你就要靠shell輸入命令來恢復計算機了。
命令自己是一個函數 (function),是一個小的功能模塊。當咱們想要讓計算機作很複雜的事情 (好比說: 在晚上12:00下載某個頁面的全部連接,而後複製到移動硬盤)的時候,不斷地去按各個圖形化按鈕並非個很聰明的事情 (1. 要點不少下,2. 必須等到12:00)。咱們一般是經過shell編程來實現這樣一些複雜任務,這時,就能夠把命令做爲函數,嵌入到咱們的shell程序中, 從而讓不一樣命令協同工做 (好比使用date來查詢時間,再根據時間來使用wget下載等等)。
有一些命令能夠用來了解某個命令自己的狀況,好比這個命令的絕對路徑。
$which ls
which 在默認路徑中搜索命令,返回該命令的絕對路徑。
$whereis ls
whereis 在相對比較大的範圍搜索命令,返回該命令的絕對路徑。
$whatis ls
whatis 用很簡短的一句話來介紹命令。
$man ls
man 查詢簡明的幫助手冊。對於大部分的Linux自帶的命令來講,看成者編寫它的時候,都會帶有一個幫助文檔,告訴用戶怎麼使用這個命令。
(man能夠說是咱們瞭解Linux最好的百科全書,它不但能夠告訴你Linux自帶的命令的功能,還能夠查詢Linux的系統文件和系統調用。若是想要深刻學習Linux,就必需要懂得如何用man來查詢相關文檔。)
$info ls
info 查詢更詳細的幫助信息
此外,在shell中,你還能夠用向上箭頭來查看以前輸入運行的命令。
你也能夠用
$history
來查詢以前在命令行的操做。
當一個命令運行時,你中途想要中止它時,能夠用Ctrl + c。若是你只是想暫時中止,使用Ctrl + z。具體機制與信號(signal)有關,咱們將在之後介紹
命令行: 使用shell解釋輸入的字符串,以運行程序
type
sudo
which, whereis, whatis, man, info
使用Tab自動補齊,向上箭頭查詢歷史,history
Ctrl + c, Ctrl + z
有一些命令能夠幫助咱們"修剪"以前看到的文件樹。
$touch a.txt
若是a.txt不存在,生成一個新的空文檔a.txt。若是a.txt存在,那麼只更改該文檔的時間信息。(這個命令實際上用得並不普遍,但能夠幫咱們建立一個空文件來實驗下面操做)
$ls .
是list的簡寫,列出當前目錄下的全部文件名
$ls -l a.txt
列出文件的詳細信息
$cp a.txt b.txt
cp是copy的簡寫,用來複制文件。在工做目錄下,將a.txt複製到文件b.txt
$cp a.txt ..
將a.txt複製到父目錄的a.txt
$mv a.txt c.txt
mv是move的簡寫,用來移動文件。將a.txt移動成爲c.txt (至關於重命名rename)
$mv c.txt /home/vamei
將c.txt移動到/home/vamei目錄
$rm a.txt
rm是remove的縮寫,用於刪除文件。刪除a.txt
$rm -r /home/vamei
刪除從/home/vamei向下的整個子文件系統。-r表示recursive, 是指重複刪除的操做,/home/vamei文件夾爲空,而後刪除/home/vamei文件夾自己。
(程序員老是對這個命令很感興趣, $rm -rf / 它會刪除整個文件樹。f的目的是告訴rm放心幹,不用再確認了…… 通常狀況下,應該沒有人會用這個命令。)
$mkdir /home/vamei/good
建立一個新的目錄
$rmdir /home/vamei/good
刪除一個空的目錄
$chmod 755 a.txt
(你必須是文件a.txt的擁有者才能運行此命令。或者以$sudo chmod 755 a.txt的方式,以超級用戶的身份運行該命令。)
change mode 改變a.txt的讀、寫以及執行權限。還記得每一個文件都有九位的讀寫執行權限,分爲三組,分別對應擁有者(owner),擁有組(owner group)中的用戶和全部其餘用戶(other)。在這裏,咱們也有三個數字,755,對應三個組。7被分配給擁有者,5被分配給擁有組,最後一個5分配給其它用戶。Linux規定: 4爲有讀取的權利,2爲有寫入的權利,1爲有執行的權利。咱們看到的7其實是4 + 2 + 1,表示擁有者有讀、寫、執行三項權利。(想一想5 意味着什麼)
這時,運行$ls -l a.txt, 你應該看到九位的權限變成了rwxr-xr-x。根據本身的須要,你能夠用好比444, 744代替755,來讓文件有不一樣的權限。
$sudo chown root a.txt
change owner 改變文件的擁有者爲root用戶。這個命令須要有超級用戶權限才能執行,因此咱們在命令以前加上sudo。
$sudo chgrp root a.txt
change group 改變文件的擁有組爲root組
(wild card, 也叫filename pattern matching)
以前所講的命令,好比ls, mv, cp均可以接收多個參數,好比:
$ls -l a.txt b.txt c.txt
就能夠列出這三個文件的全部信息。
有時候,咱們想列出工做目錄下全部的以.txt結尾的文件的信息,能夠用下面的方式:
$ls -l *.txt
*.txt的寫法就運用了Linux通配表達式。它與正則表達式相相似,但語法有所不一樣。
Filename Pattern Matching 對應含義
* 任意多個任意字符
? 任意一個字符
[kl] 字符k或者字符l
[0-4] 數字0到4字符中的一個
[b-e] b到e字符中的一個
[^mnp] 一個字符,這個字符不是m,n,p
Linux會找到符合表達式的文件名,而後用這些文件名做爲參數傳遞給命令。注意,當使用rm的時候,要格外當心。下面兩個命令,只相差一個空格,但效果大爲不一樣:
$rm * .txt
$rm *.txt
第一個命令會刪除當前目錄下全部文件!
touch, ls, mv, cp, rm, mkdir, rmdir
chmod, chown, chgrp
wild card
文件用於數據的存儲,至關於一個個存儲數據的房子。咱們以前說,所謂的數據是0或者1的序列,但嚴格來講,Linux以字節(byte)來做爲數據的單位,也就是說這個序列每八位(bit)爲一個單位(八位二進制對應的十進制範圍爲0到255)。使用ASCII編碼,能夠將這樣一個字節轉換成爲字符。因此,在Linux中,咱們所說的數據,徹底能夠用字符表達出來,也就是說文本(text)的形式。
實際上,若是以bit爲單位處理字符的話,機器會更容易讀懂和傳輸,效率會更高。但爲何Linux依然以字節爲單位進行處理呢?緣由在於,相對於以bit爲單位處理數據,以byte爲單位能夠更容易將數據轉化爲字符。相對於枯燥的0和1,字符更容易被人讀懂 (human readable)。然而,並非全部的數據都是設計來讓人讀懂的,好比可執行文件包含的各類字符對於人來講並無什麼意義 (由於可執行文件是爲了讓機器讀懂的)。但Linux依然以字節爲單位處理全部文件,這是爲了讓全部文件可以共用一套接口 (virtual file system),從而減小Linux設計的複雜度。
("everything is a file"是一般所流傳的UNIX設計的哲學之一,但Linus對此做出糾正,改成"everything is a stream of bytes"。)
然而,數據不是在找到了本身的房子(file)以後就永遠的定居下來。它每每要被讀入到內存 (就像是到辦公室上班),或者被傳送到外部設備(好像去酒店休假),或者搬到別的房子中。在這樣的搬遷過程當中,數據像是一個個排着隊走路的人流,咱們叫它文本流(text stream,或者byte stream)。然而,計算機不一樣設備之間的鏈接方法差別很大,從內存到文件的鏈接像是登山,從內存到外設像是遊過一條河。爲此,Linux還定義了流 (stream),以此做爲修建鏈接各處的公路的標準。Stream的好處在於,不管你是從內存到外設,仍是從內存到文件,全部的公路都是相同的 (至於公路下面是石頭仍是土地,均可以不用操心)。
咱們再回味一下「everything is a stream of bytes」這句話。信息包含在文本流中,不斷在計算機的各個組件之間流動,不斷地接受計算機的加工,最終成爲用戶所須要的某種服務。
當Linux執行一個程序的時候,會自動打開三個流,標準輸入(standard input),標準輸出(standard output),標準錯誤(standard error)。好比說你打開命令行的時候,默認狀況下,命令行的標準輸入鏈接到鍵盤,標準輸出和標準錯誤都鏈接到屏幕。對於一個程序來講,儘管它總會打開這三個流,但它會根據須要使用,並非必定要使用。
想象一下敲擊一個
$ls
鍵盤敲擊的文本流("ls\n",\n是回車時輸入的字符,表示換行)命令行 (命令行實際上也是一個程序)。命令行隨後調用/bin/ls獲得結果("a.txt"),最後這個輸出的文本流("a.txt")流到屏幕,顯示出來,好比說:
a.txt
假設說咱們不想讓文本流流到屏幕,而是流到另外一個文件,咱們能夠採用從新定向(redirect)的機制。
$ls > a.txt
從新定向標準輸出。這裏的>就是提醒命令行,讓它知道我如今想變換文本流的方向了,咱們不讓標準輸出輸出到屏幕,而是要到a.txt這個文件 (好像火車軌道換軌)。此時,計算機會新建一個a.txt的文件,並將命令行的標準輸出指向這個文件。
有另外一個符號:
$ls >> a.txt
這裏>>的做用也是從新定向標準輸出。若是a.txt已經存在的話,ls產生的文本流會附加在a.txt的結尾,而不會像>那樣每次都新建a.txt。
咱們下面介紹命令echo:
$echo IamVamei
echo的做用是將文本流導向標準輸出。在這裏,echo的做用就是將IamVamei輸出到屏幕上。若是是
$echo IamVamei > a.txt
a.txt中就會有IamVamei這個文本。
咱們也能夠用<符號來改變標準輸入。好比cat命令,它能夠從標準輸入讀入文本流,並輸出到標準輸出:
$cat < a.txt
咱們將cat標準輸入指向a.txt,文本會從文件流到cat,而後再輸出到屏幕上。固然,咱們還能夠同時從新定向標準輸出:
$cat < a.txt > b.txt
這樣,a.txt的內容就複製到了b.txt中。
咱們還可使用>&來同時從新定向標準輸出和標準錯誤。假設咱們並無一個目錄void。那麼
$cd void > a.txt
會在屏幕上返回錯誤信息。由於此時標準錯誤依然指向屏幕。當咱們使用:
$cd void >& a.txt
錯誤信息被導向a.txt。
若是隻想從新定向標準錯誤,可使用2>:
$cd void 2> a.txt > b.txt
標準錯誤對應的老是2號,因此有以上寫法。標準錯誤輸出到a.txt,標準輸出輸出到b.txt。
理解了以上的內容以後,管道的概念就易如反掌。管道能夠將一個命令的輸出導向另外一個命令的輸入,從而讓兩個(或者更多命令)像流水線同樣連續工做,不斷地處理文本流。在命令行中,咱們用|表示管道:
$cat < a.txt | wc
wc命令表明word count,用於統計文本中的行、詞以及字符的總數。a.txt中的文本先流到cat,而後從cat的標準輸出流到wc的標準輸入,從而讓wc知道本身要處理的是a.txt這個字符串。
Linux的各個命令實際上高度專業化,並儘可能相互獨立。每個都只專一於一個小的功能。但經過pipe,咱們能夠將這些功能合在一塊兒,實現一些複雜的目的。
文本流,標準輸入,標準輸出,標準錯誤
cat, echo, wc
>, >>, <, |
計算機實際上能夠作的事情實質上很是簡單,好比計算兩個數的和,再好比在內存中尋找到某個地址等等。這些最基礎的計算機動做被稱爲指令(instruction)。所謂的程序(program),就是這樣一系列指令的所構成的集合。經過程序,咱們可讓計算機完成複雜的操做。程序大多數時候被存儲爲可執行的文件。這樣一個可執行文件就像是一個菜譜,計算機能夠按照菜譜做出可口的飯菜。
那麼,程序和進程(process)的區別又是什麼呢?
進程是程序的一個具體實現。只有食譜沒什麼用,咱們總要按照食譜的指點真正一步步實行,才能作出菜餚。進程是執行程序的過程,相似於按照食譜,真正去作菜的過程。同一個程序能夠執行屢次,每次均可以在內存中開闢獨立的空間來裝載,從而產生多個進程。不一樣的進程還能夠擁有各自獨立的IO接口。
操做系統的一個重要功能就是爲進程提供方便,好比說爲進程分配內存空間,管理進程的相關信息等等,就好像是爲咱們準備好了一個精美的廚房。
首先,咱們可使用$ps命令來查詢正在運行的進程,好比$ps -eo pid,comm,cmd,下圖爲執行結果:
(-e表示列出所有進程,-o pid,comm,cmd表示咱們須要PID,COMMAND,CMD信息)
每一行表明了一個進程。每一行又分爲三列。第一列PID(process IDentity)是一個整數,每個進程都有一個惟一的PID來表明本身的身份,進程也能夠根據PID來識別其餘的進程。第二列COMMAND是這個進程的簡稱。第三列CMD是進程所對應的程序以及運行時所帶的參數。
(第三列有一些由中括號[]括起來的。它們是kernel的一部分功能,被打扮成進程的樣子以方便操做系統管理。咱們沒必要考慮它們。)
咱們看第一行,PID爲1,名字爲init。這個進程是執行/bin/init這一文件(程序)生成的。當Linux啓動的時候,init是系統建立的第一個進程,這一進程會一直存在,直到咱們關閉計算機。這一進程有特殊的重要性,咱們會不斷提到它。
實際上,當計算機開機的時候,內核(kernel)只創建了一個init進程。Linux kernel並不提供直接創建新進程的系統調用。剩下的全部進程都是init進程經過fork機制創建的。新的進程要經過老的進程複製自身獲得,這就是fork。fork是一個系統調用。進程存活於內存中。每一個進程都在內存中分配有屬於本身的一片空間 (address space)。當進程fork的時候,Linux在內存中開闢出一片新的內存空間給新的進程,並將老的進程空間中的內容複製到新的空間中,此後兩個進程同時運行。
老進程成爲新進程的父進程(parent process),而相應的,新進程就是老的進程的子進程(child process)。一個進程除了有一個PID以外,還會有一個PPID(parent PID)來存儲的父進程PID。若是咱們循着PPID不斷向上追溯的話,總會發現其源頭是init進程。因此說,全部的進程也構成一個以init爲根的樹狀結構。
以下,咱們查詢當前shell下的進程:
root@vamei:~# ps -o pid,ppid,cmd PID PPID CMD 16935 3101 sudo -i 16939 16935 -bash 23774 16939 ps -o pid,ppid,cmd
咱們能夠看到,第二個進程bash是第一個進程sudo的子進程,而第三個進程ps是第二個進程的子進程。
還能夠用$pstree命令來顯示整個進程樹:
init─┬─NetworkManager─┬─dhclient │ └─2*[{NetworkManager}] ├─accounts-daemon───{accounts-daemon} ├─acpid ├─apache2─┬─apache2 │ └─2*[apache2───26*[{apache2}]] ├─at-spi-bus-laun───2*[{at-spi-bus-laun}] ├─atd ├─avahi-daemon───avahi-daemon ├─bluetoothd ├─colord───2*[{colord}] ├─console-kit-dae───64*[{console-kit-dae}] ├─cron ├─cupsd───2*[dbus] ├─2*[dbus-daemon] ├─dbus-launch ├─dconf-service───2*[{dconf-service}] ├─dropbox───15*[{dropbox}] ├─firefox───27*[{firefox}] ├─gconfd-2 ├─geoclue-master ├─6*[getty] ├─gnome-keyring-d───7*[{gnome-keyring-d}] ├─gnome-terminal─┬─bash │ ├─bash───pstree │ ├─gnome-pty-helpe │ ├─sh───R───{R} │ └─3*[{gnome-terminal}]
fork一般做爲一個函數被調用。這個函數會有兩次返回,將子進程的PID返回給父進程,0返回給子進程。實際上,子進程總能夠查詢本身的PPID來知道本身的父進程是誰,這樣,一對父進程和子進程就能夠隨時查詢對方。
一般在調用fork函數以後,程序會設計一個if選擇結構。當PID等於0時,說明該進程爲子進程,那麼讓它執行某些指令,好比說使用exec庫函數(library function)讀取另外一個程序文件,並在當前的進程空間執行 (這其實是咱們使用fork的一大目的: 爲某一程序建立進程);而當PID爲一個正整數時,說明爲父進程,則執行另一些指令。由此,就能夠在子進程創建以後,讓它執行與父進程不一樣的功能。
當子進程終結時,它會通知父進程,並清空本身所佔據的內存,並在kernel裏留下本身的退出信息(exit code,若是順利運行,爲0;若是有錯誤或異常情況,爲>0的整數)。在這個信息裏,會解釋該進程爲何退出。父進程在得知子進程終結時,有責任對該子進程使用wait系統調用。這個wait函數能從kernel中取出子進程的退出信息,並清空該信息在kernel中所佔據的空間。可是,若是父進程早於子進程終結,子進程就會成爲一個孤兒(orphand)進程。孤兒進程會被過繼給init進程,init進程也就成了該進程的父進程。init進程負責該子進程終結時調用wait函數。
固然,一個糟糕的程序也徹底可能形成子進程的退出信息滯留在kernel中的情況(父進程不對子進程調用wait函數),這樣的狀況下,子進程成爲殭屍(zombie)進程。當大量殭屍進程積累時,內存空間會被擠佔。
儘管在UNIX中,進程與線程是有聯繫但不一樣的兩個東西,但在Linux中,線程只是一種特殊的進程。多個線程之間能夠共享內存空間和IO接口。因此,進程是Linux程序的惟一的實現方式。
程序,進程,PID,內存空間
子進程,父進程,PPID,fork, wait
Linux以進程爲單位來執行程序。咱們能夠將計算機看做一個大樓,內核(kernel)是大樓的管理員,進程是大樓的房客。每一個進程擁有一個獨立的房間(屬於進程的內存空間),而每一個房間都是不容許該進程以外的人進入。這樣,每一個進程都只專一於本身乾的事情,而不考慮其餘進程,同時也不讓別的進程看到本身的房間內部。這對於每一個進程來講是一種保護機制。(想像一下幾百個進程老是要干涉對方,那會有多麼混亂,或者幾百個進程相互偷窺……)
然而,在一些狀況,咱們須要打破封閉的房間,以便和進程交流信息。好比說,內核發現有一個進程在砸牆(硬件錯誤),須要讓進程意識到這樣繼續下去會毀了整個大樓。再好比說,咱們想讓多個進程之間合做。這樣,咱們就須要必定的通訊方式。信號(signal)就是一種向進程傳遞信息的方式。咱們能夠將信號想象成大樓的管理員往房間的信箱裏塞小紙條。隨後進程取出小紙條,會根據紙條上的內容來採起必定的行動,好比燈壞了,提醒進程使用手電。(固然,也能夠徹底無視這張紙條,然而在失火這樣緊急的情況下,無視信號不是個好的選擇)。相對於其餘的進程間通訊方式(interprocess communication, 好比說pipe, shared memory)來講,信號所能傳遞的信息比較粗糙,只是一個整數。但正是因爲傳遞的信息量少,信號也便於管理和使用。信號所以被常常地用於系統管理相關的任務,好比通知進程終結、停止或者恢復等等。
信號是由內核(kernel)管理的。信號的產生方式多種多樣,它能夠是內核自身產生的,好比出現硬件錯誤(好比出現分母爲0的除法運算,或者出現segmentation fault),內核須要通知某一進程;也能夠是其它進程產生的,發送給內核,再由內核傳遞給目標進程。內核中針對每個進程都有一個表存儲相關信息(房間的信箱)。當內核須要將信號傳遞給某個進程時,就在該進程相對應的表中的適當位置寫入信號(塞入紙條),這樣,就生成(generate)了信號。當該進程執行系統調用時,在系統調用完成後退出內核時,都會順便查看信箱裏的信息。若是有信號,進程會執行對應該信號的操做(signal action, 也叫作信號處理signal disposition),此時叫作執行(deliver)信號。從信號的生成到信號的傳遞的時間,信號處於等待(pending)狀態(紙條尚未被查看)。咱們一樣能夠設計程序,讓其生成的進程阻塞(block)某些信號,也就是讓這些信號始終處於等待的狀態,直到進程取消阻塞(unblock)或者無視信號。
信號所傳遞的每個整數都被賦予了特殊的意義,並有一個信號名對應該整數。常見的信號有SIGINT, SIGQUIT, SIGCONT, SIGTSTP, SIGALRM等。這些都是信號的名字。你能夠經過
$man 7 signal
來查閱更多的信號。
上面幾個信號中,
SIGINT 當鍵盤按下CTRL+C從shell中發出信號,信號被傳遞給shell中前臺運行的進程,對應該信號的默認操做是中斷 (INTERRUPT) 該進程。
SIGQUIT 當鍵盤按下CTRL+\從shell中發出信號,信號被傳遞給shell中前臺運行的進程,對應該信號的默認操做是退出 (QUIT) 該進程。
SIGTSTP 當鍵盤按下CTRL+Z從shell中發出信號,信號被傳遞給shell中前臺運行的進程,對應該信號的默認操做是暫停 (STOP) 該進程。
SIGCONT 用於通知暫停的進程繼續。
SIGALRM 起到定時器的做用,一般是程序在必定的時間以後才生成該信號。
下面咱們實際應用一下信號。咱們在shell中運行ping:
$ping localhost
此時咱們能夠經過CTRL+Z來將SIGTSTP傳遞給該進程。shell中顯示:
[1]+ Stopped ping localhost
咱們使用$ps來查詢ping進程的PID (PID是ping進程的房間號), 在個人機器中爲27397
咱們能夠在shell中經過$kill命令來向某個進程發出信號:
$kill -SIGCONT 27397
來傳遞SIGCONT信號給ping進程。
在上面的例子中,全部的信號都採起了對應信號的默認操做。但這並不絕對。當進程決定執行信號的時候,有下面幾種可能:
1) 無視(ignore)信號,信號被清除,進程自己不採起任何特殊的操做
2) 默認(default)操做。每一個信號對應有必定的默認操做。好比上面SIGCONT用於繼續進程。
3) 自定義操做。也叫作獲取 (catch) 信號。執行進程中預設的對應於該信號的操做。
進程會採起哪一種操做,要根據該進程的程序設計。特別是獲取信號的狀況,程序每每會設置一些比較長而複雜的操做(一般將這些操做放到一個函數中)。
信號經常被用於系統管理,因此它的內容至關龐雜。深刻了解信號,須要必定的Linux環境編程知識。
信號機制; generate, deliver, pending, blocking
signal action/dispositon; ignore, default action, catch signal
$kill
Linux的進程相互之間有必定的關係。好比說,每一個進程都有父進程,而全部的進程以init進程爲根,造成一個樹狀結構。咱們在這裏講解進程組和會話,以便以更加豐富的方式了管理進程。
每一個進程都會屬於一個進程組(process group),每一個進程組中能夠包含多個進程。進程組會有一個進程組領導進程 (process group leader),領導進程的PID (PID見Linux進程基礎)成爲進程組的ID (process group ID, PGID),以識別進程組。
$ps -o pid,pgid,ppid,comm | cat
PID PGID PPID COMMAND 17763 17763 17751 bash 18534 18534 17763 ps 18535 18534 17763 cat
PID爲進程自身的ID,PGID爲進程所在的進程組的ID, PPID爲進程的父進程ID。從上面的結果,咱們能夠推測出以下關係:
圖中箭頭表示父進程經過fork和exec機制產生子進程。ps和cat都是bash的子進程。進程組的領導進程的PID成爲進程組ID。領導進程能夠先終結。此時進程組依然存在,並持有相同的PGID,直到進程組中最後一個進程終結。
咱們將一些進程歸爲進程組的一個重要緣由是咱們能夠將信號發送給一個進程組。進程組中的全部進程都會收到該信號。咱們會在下一部分深刻討論這一點。
更進一步,在shell支持工做控制(job control)的前提下,多個進程組還能夠構成一個會話 (session)。bash(Bourne-Again shell)支持工做控制,而sh(Bourne shell)並不支持。
會話是由其中的進程創建的,該進程叫作會話的領導進程(session leader)。會話領導進程的PID成爲識別會話的SID(session ID)。會話中的每一個進程組稱爲一個工做(job)。會話能夠有一個進程組成爲會話的前臺工做(foreground),而其餘的進程組是後臺工做(background)。每一個會話能夠鏈接一個控制終端(control terminal)。當控制終端有輸入輸出時,都傳遞給該會話的前臺進程組。由終端產生的信號,好比CTRL+Z, CTRL+\,會傳遞到前臺進程組。
會話的意義在於將多個工做囊括在一個終端,並取其中的一個工做做爲前臺,來直接接收該終端的輸入輸出以及終端信號。 其餘工做在後臺運行。
一個命令能夠經過在末尾加上&方式讓它在後臺運行:
$ping localhost > log &
此時終端顯示:
[1] 10141
括號中的1表示工做號,而10141爲PGID
咱們經過以下方式查詢更加詳細的信息:
$ps -o pid,pgid,ppid,sid,tty,comm
(tty表示控制終端)
信號能夠經過kill
$kill -SIGTERM -10141
或者
$kill -SIGTERM %1
的方式來發送給工做組。上面的兩個命令,一個是發送給PGID(經過在PGID前面加-來表示是一個PGID而不是PID),一個是發送給工做1(%1),二者等價。
一個工做能夠經過$fg從後臺工做變爲前臺工做:
$cat > log &
$fg %1
當咱們運行第一個命令後,因爲工做在後臺,咱們沒法對命令進行輸入,直到咱們將工做帶入前臺,才能向cat命令輸入。在輸入完成後,按下CTRL+D來通知shell輸入結束。
進程組(工做)的概念較爲簡單易懂。而會話主要是針對一個終端創建的。當咱們打開多個終端窗口時,實際上就建立了多個終端會話。每一個會話都會有本身的前臺工做和後臺工做。這樣,咱們就爲進程增長了管理和運行的層次。在沒有圖形化界面的時代,會話容許用戶經過shell進行多層次的進程發起和管理。好比說,我能夠經過shell發起多個後臺工做,而此時標準輸入輸出並不被佔據,我依然能夠繼續其它的工做。現在,圖形化界面能夠幫助咱們解決這一需求,但工做組和會話機制依然在Linux的許多地方應用。
process group, pgid
session, sid, job, forground, background
fg, kill -pid, &, %
Linux的用戶在登陸(login)以後,就帶有一個用戶身份(user ID, UID)和一個組身份(group ID, GID)。在Linux文件管理背景知識中,咱們又看到,每一個文件又有九位的權限說明,用來指明該文件容許哪些用戶執行哪些操做(讀、寫或者執行)。
通常來講,Linux的用戶信息保存在/etc/passwd中,組信息保存在/etc/group中,文件的每一行表明一個用戶/組。早期的Linux將密碼以名碼的形式保存在/etc/passwd中,而如今則多以暗碼(也就是加密以後的形式)的形式保存在/etc/shadow中。將密碼存儲在/etc/shadow中提升了密碼的安全性,由於/etc/passwd容許全部人查看,而/etc/shadow只容許root用戶查看。
可是,在Linux中,用戶的指令是在進程的範圍內進行的。當咱們向對某個文件進行操做的時候,咱們須要在進程中運行一個程序,在進程中對文件打開,並進行讀、寫或者執行的操做。所以,咱們須要將用戶的權限傳遞給進程,以便進程真正去執行操做。例如咱們有一個文件a.txt, 文件中爲一個字符串:
Hello world!
我以用戶Vamei的身份登陸,並在shell中運行以下命令:
$cat a.txt
整個運行過程以及文件讀取以下:
咱們能夠看到,整個過程當中咱們會有兩個進程,一個是shell自己(2256),一個是shell複製自身,再運行/bin/cat (9913)。圖中的fork, exec, PID可參看Linux進程基礎。第二個進程總共對文件系統進行了兩次操做,一次是執行(x)文件/bin/cat,另一次是讀取(r)文件a.txt。使用$ls -l 查看這兩個文件的權限:
$ls -l /bin/cat
-rwxr-xr-x 1 root root 46764 Apr 1 2012 /bin/cat
$ls -l a.txt
-rw-rw-r-- 1 Vamei Vamei 14 Oct 7 09:14 a.txt
/bin/cat讓全部用戶都享有執行的權利,而Vamei做爲a.txt的擁有者,對a.txt享有讀取的權利。
讓咱們進入更多的細節 (The devil is in the details)。在進行這兩次操做的時候,儘管用戶Vamei擁有相應的權限,但咱們發現,真正作工做的是進程9913。咱們要讓這個進程獲得相應的權限。實際上,每一個進程會維護有以下6個ID:
真實身份: real UID, real GID
有效身份: effective UID, effective GID
存儲身份:saved UID, saved GID
其中,真實身份是咱們登陸使用的身份,有效身份是當該進程真正去操做文件時所檢查的身份,存儲身份較爲特殊,咱們等一下再深刻。當進程fork的時候,真實身份和有效身份都會複製給子進程。大部分狀況下,真實身份和有效身份都相同。當Linux完成開機啓動以後,init進程會執行一個login的子進程。咱們將用戶名和密碼傳遞給login子進程。login在查詢了/etc/passwd和/etc/shadow,並肯定了其合法性以後,運行(利用exec)一個shell進程,shell進程真實身份被設置成爲該用戶的身份。因爲此後fork此shell進程的子進程都會繼承真實身份,因此該真實身份會持續下去,直到咱們登出並以其餘身份再次登陸(當咱們使用su成爲root的時候,實際上就是以root身份再次登陸,此後真實身份成爲root)。
每一個進程爲何不簡單地只維護真實身份,卻選擇費盡麻煩地去維護有效身份和存儲身份呢?這牽涉到Linux的「最小特權」(least priviledge)的原則。Linux一般但願進程只擁有足夠完成其工做的特權,而不但願賦予更多的特權給它。從設計上來講,最簡單的是賦予每一個進程以super user的特權,這樣進程就能夠想作什麼作什麼。然而,這對於系統來講是一個巨大的安全漏洞,特別是在多用戶環境下,若是每一個用戶都享有無限制的特權,就很容易破壞其餘用戶的文件或者系統自己。「最小特權」就是收縮進程所享有的特權,以防進程濫用特權。
然而,進程的不一樣階段可能須要不一樣的特權。好比一個進程最開始的有效身份是真實身份,但運行到中間的時候,須要以其餘的用戶身份讀入某些配置文件,而後再進行其餘的操做。爲了防止其餘的用戶身份被濫用,咱們須要在操做以前,讓進程的有效身份變動回來成爲真實身份。這樣,進程須要在兩個身份之間變化。
存儲身份就是真實身份以外的另外一個身份。當咱們將一個程序文件執行成爲進程的時候,該程序文件的擁有者(owner)和擁有組(owner group)能夠被,存儲成爲進程的存儲身份。在隨後進程的運行過程當中,進程就將能夠選擇將真實身份或者存儲身份複製到有效身份,以擁有真實身份或者存儲身份的權限。並非全部的程序文件在執行的過程都設置存儲身份的。須要這麼作的程序文件會在其九位(bit)權限的執行位的x改成s。這時,這一位(bit)叫作set UID bit或者set GID bit。
$ls -l /usr/bin/uuidd
-rwsr-sr-x 1 libuuid libuuid 17976 Mar 30 2012 /usr/sbin/uuidd
當我以root(UID), root(GID)的真實身份運行這個程序的時候,因爲擁有者(owner)有s位的設定,因此saved UID被設置成爲libuuid,saved GID被設置成爲libuuid。這樣,uuidd的進程就能夠在兩個身份之間切換。
咱們一般使用chmod來修改set-UID bit和set-GID bit:
$chmod 4700 file
咱們看到,這裏的chmod後面再也不只是三位的數字。最前面一位用於處理set-UID bit/set-GID bit,它能夠被設置成爲4/2/1以及或者上面數字的和。4表示爲set UID bit, 2表示爲set GID bit,1表示爲sticky bit (暫時不介紹)。必需要先有x位的基礎上,才能設置s位。
做爲一個Linux用戶來講,咱們並不須要特別關心上面的機制。可是,當咱們去編寫一個Linux應用程序的時候,就要注意在程序中實現以上切換(有必要的前提下),以便讓咱們的程序符合"最小權限"的原則,不給系統留下可能的安全隱患。給你的程序過分的權限的話,就像是吃下去下面的漢堡:
real/effective/saved UID/GID
saved UID/GID bit
「最小權限」原則
計算機如何執行進程呢?這是計算機運行的核心問題。即便已經編寫好程序,但程序是死的。只有活的進程才能產出。咱們已經從Linux進程基礎中瞭解了進程。如今咱們看一下從程序到進程的漫漫征程。
下面是一個簡單的C程序,假設該程序已經編譯好,生成可執行文件vamei.exe。
#include <stdio.h> int glob=0; /*global variable*/ void main(void) { int main1=5; /*local variable of main()*/ int main2; /*local variable of main()*/ main2 = inner(main1); /* call inner() function */ printf("From Main: glob: %d \n", glob); printf("From Main: main2: %d \n", main2); } int inner(int inner1) { /*inner1 is an argument, also local to inner()*/ int inner2=10; /*local variable of inner()*/ printf("From inner: glob: %d \n", glob); return(inner1+inner2); }
(選取哪個語言或者具體的語法並非關鍵,大部分語言均可以寫出相似上面的程序。在看Python教程的讀者也能夠利用Python的函數結構和print寫一個相似的python程序。固然,還能夠是C++,Java,Objective-C等等。選用C語言的緣由是:它是爲UNIX而生的語言。)
main()函數中調用了inner()函數。inner()中調用一次printf()以輸出。最後,在main()中進行了兩次printf()。
注意變量的做用範圍。簡單地說,變量能夠分爲全局變量和局部變量。在全部函數以外聲明的變量爲全局變量,好比glob,在任什麼時候候均可以使用。在函數內定義的變量爲局部變量,只能在該函數的做用域(range)內使用,好比說咱們在inner()工做的時候不能使用main()函數中聲明的main1變量,而在main()中咱們沒法使用inner()函數中聲明的inner2變量。
不用太過在乎這個程序的具體功能。要點是這個程序的運行過程。下圖爲該程序的運行過程,以及各個變量的做用範圍:
運行流程
爲了進一步瞭解上面程序的運行,咱們還須要知道,進程如何使用內存。當程序文件運行爲進程時,進程在內存中得到空間。這個空間是進程本身的小屋子。
每一個進程空間按照以下方式分爲不一樣區域:
內存空間
Text區域用來儲存指令(instruction),說明每一步的操做。Global Data用於存放全局變量,棧(Stack)用於存放局部變量,堆(heap)用於存放動態變量 (dynamic variable. 程序利用malloc系統調用,直接從內存中爲dynamic variable開闢空間)。Text和Global data在進程一開始的時候就肯定了,並在整個進程中保持固定大小。
棧(Stack)以幀(stack frame)爲單位。當程序調用函數的時候,好比main()函數中調用inner()函數,stack會向下增加一幀。幀中存儲該函數的參數和局部變量,以及該函數的返回地址(return address)。此時,計算機將控制權從main()轉移到inner(),inner()函數處於激活(active)狀態。位於棧最下方的幀,和全局變量一塊兒,構成了當前的環境(context)。激活函數能夠從環境中調用須要的變量。典型的編程語言都只容許你使用位於stack最下方的幀 ,而不容許你調用其它的幀 (這也符合stack結構「先進後出」的特徵。但也有一些語言容許你調用棧的其它部分,至關於容許你在運行inner()函數的時候調用main()中聲明的局部變量,好比Pascal)。當函數又進一步調用另外一個函數的時候,一個新的幀會繼續增長到棧的下方,控制權轉移到新的函數中。當激活函數返回的時候,會從棧中彈出(pop,讀取並從棧中刪除)該幀,並根據幀中記錄的返回地址,將控制權交給返回地址所指向的指令(好比從inner()函數中返回,繼續執行main()中賦值給main2的操做)。
下圖是棧在運行過程當中的變化。箭頭表示棧的增加方向。每一個方塊表明一幀。開始的時候咱們有一個爲main()服務的幀,隨着調用inner(),咱們爲inner()增長一個幀。在inner()返回時,咱們再次只有main()的幀,直到最後main()返回,其返回地址爲空,因此進程結束。
stack變化
在進程運行的過程當中,經過調用和返回函數,控制權不斷在函數間轉移。進程能夠在調用函數的時候,原函數的幀中保存有在咱們離開時的狀態,併爲新的函數開闢所需的幀空間。在調用函數返回時,該函數的幀所佔據的空間隨着幀的彈出而清空。進程再次回到原函數的幀中保存的狀態,並根據返回地址所指向的指令繼續執行。上面過程不斷繼續,棧不斷增加或減少,直到main()返回的時候,棧徹底清空,進程結束。
當中使用malloc的時候,堆(heap)會向上增加,其增加的部分就成爲malloc從內存中分配的空間。malloc開闢的空間會一直存在,直到咱們用free系統調用來釋放,或者進程結束。一個經典的錯誤是內存泄漏(memory leakage), 就是指咱們沒有釋放再也不使用的堆空間,致使堆不斷增加,而內存可用空間不斷減小。
棧和堆的大小則會隨着進程的運行增大或者變小。當棧和堆增加到二者相遇時候,也就是內存空間圖中的藍色區域(unused area)徹底消失的時候,再無可用內存。進程會出現棧溢出(stack overflow)的錯誤,致使進程終止。在現代計算機中,內核通常會爲進程分配足夠多的藍色區域,若是清理及時,棧溢出很容易避免。即使如此,內存負荷過大,依然可能出現棧溢出的狀況。咱們就須要增長物理內存了。
Stack overflow能夠說是最出名的計算機錯誤了,因此纔有IT網站(stackoverflow.com)以此爲名。
在高級語言中,這些內存管理的細節對於用戶來講不透明。在編程的時候,咱們只須要記住上一節中的變量做用域就能夠了。但在想要寫出複雜的程序或者debug的時候,咱們就須要相關的知識了。
除了上面的信息以外,每一個進程還要包括一些進程附加信息,包括PID,PPID,PGID(參考Linux進程基礎以及Linux進程關係)等,用來講明進程的身份、進程關係以及其它統計信息。這些信息並不保存在進程的內存空間中。內核會爲每一個進程在內核本身的空間中分配一個變量(task_struct結構體)以保存上述信息。內核能夠經過查看本身空間中的各個進程的附加信息就能知道進程的概況,而不用進入到進程自身的空間 (就好像咱們能夠經過門牌就能夠知道房間的主人是誰同樣,而不用打開房門)。每一個進程的附加信息中有位置專門用於保存接收到的信號
如今,咱們能夠更加深刻地瞭解fork和exec的機制了。當一個程序調用fork的時候,實際上就是將上面的內存空間,包括text, global data, heap和stack,又複製出來一個,構成一個新的進程,並在內核中爲改進程建立新的附加信息 (好比新的PID,而PPID爲原進程的PID)。此後,兩個進程分別地繼續運行下去。新的進程和原有進程有相同的運行狀態(相同的變量值,相同的instructions...)。咱們只能經過進程的附加信息來區分二者。
程序調用exec的時候,進程清空自身內存空間的text, global data, heap和stack,並根據新的程序文件重建text, global data, heap和stack (此時heap和stack大小都爲0),並開始運行。(現代操做系統爲了更有效率,改進了管理fork和exec的具體機制,但從邏輯上來講並無差異。
這一篇寫了整合了許多東西,因此有些長。這篇文章主要是概念性的,許多細節會根據語言和平臺乃至於編譯器的不一樣而有所變化,但大致上,以上的概念適用於全部的計算機進程(不管是Windows仍是UNIX)。更加深刻的內容,包括線程(thread)、進程間通訊(IPC)等,都依賴於這裏介紹的內容。
函數,變量的做用範圍,global/local/dynamic variables
global data, text,
stack, stack frame, return address, stack overflow
heap, malloc, free, memory leakage
進程附加信息, task_struct
fork & exec