計算機實際能做的事情其實很簡單,比如計算兩個數的和,在內存中找壹個地址。這些基本的計算機動作被稱為指令。所謂程序就是這樣壹系列指令的集合。通過程序,我們可以使計算機完成復雜的運算。程序大部分時間是作為可執行文件存儲的。這樣的可執行文件就像壹個菜譜,電腦可以根據菜譜做出美味的飯菜。
那麽,程序和流程的區別是什麽呢?
過程是程序的具體實現。只有菜譜是沒有用的,我們總是要按照菜譜的指示壹步壹步來,才能做出菜來。過程就是執行程序的過程,類似於根據菜譜實際烹飪的過程。同壹個程序可以執行多次,每次都可以在內存中打開壹個獨立的空間進行加載,這樣就產生了多個進程。不同的進程也可以有自己獨立的IO接口。
操作系統的壹個重要作用就是為進程提供便利,比如為進程分配內存空間,管理進程的相關信息等。,就像為我們準備了壹個漂亮的廚房。
看壹看進度
首先,我們可以使用$ps命令來查詢正在運行的進程,比如$ps -eo pid、comm、cmd。下圖顯示了執行結果:
(-e表示列出所有進程,-o pid,comm,cmd表示我們需要pid,COMMAND,CMD信息)。
每壹行代表壹個過程。每行分為三列。第壹列PID(進程標識)是壹個整數,每個進程都有壹個唯壹的PID來表示自己的標識,並且該進程還可以根據該PID來標識其他進程。第二列COMMAND是這個過程的縮寫。第三列CMD是進程對應的程序和運行時帶來的參數。
(第三列有壹些,用括號[]括起來。它們是內核功能的壹部分,它們被裝扮成進程,以便於操作系統的管理。我們不必考慮它們。)
我們來看第壹行,PID是1,名字是init。這個過程是通過執行文件/bin/init生成的。Linux啟動時,init是系統創建的第壹個進程,這個進程會壹直存在,直到我們關閉電腦。這個過程特別重要,我們會壹直提到。
如何創建流程
事實上,當計算機打開時,內核只建立壹個init進程。Linux內核不提供直接建立新進程的系統調用。其余所有進程都是由init進程通過fork機制建立的。新進程應該通過舊進程復制自己,這就是fork。Fork是壹個系統調用。這個過程存在於內存中。每個進程在內存中都被分配了自己的地址空間。當進程分叉時,Linux在內存中為新進程開辟壹個新的內存空間,並將舊進程空間的內容復制到新空間中,之後兩個進程同時運行。
舊進程成為新進程的父進程,相應地,新進程是舊進程的子進程。除了PID之外,進程還將具有由PPID存儲的父進程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庫函數讀取另壹個程序文件,在當前進程空間執行(這其實是使用fork的壹個很大的目的:為某個程序創建壹個進程);當PID為正整數時,表示是父進程,執行其他指令。因此,在建立子進程之後,可以使它執行與父進程不同的功能。
子進程的終止。
子進程終止時,會通知父進程,清除其占用的內存,並在內核中留下自己的退出信息(退出代碼,如果運行順利為0;如果有錯誤或異常情況,則為>;0的整數)。在此消息中,它將解釋進程退出的原因。當父進程知道子進程結束時,就有責任為子進程使用等待系統調用。這個wait函數可以從內核中提取子進程的退出信息,並清除這個信息在內核中占用的空間。但是,如果父進程在子進程之前終止,子進程將成為孤兒進程。孤兒進程將被init進程采用,init進程將成為該進程的父進程。當子進程終止時,init進程負責調用wait函數。
當然,壹個壞的程序也可能導致子進程的退出信息滯留在內核中(父進程不向子進程調用wait函數)。在這種情況下,子進程就變成了僵屍進程。當僵屍進程大量積累時,內存空間就會被擠壓。
進程和線程
雖然在UNIX中,進程和線程是相關但不同的東西,但在Linux中,線程只是壹個特殊的進程。多個線程可以共享內存空間和IO接口。因此,進程是實現Linux程序的必由之路。