Linux下進程的結構
Linux下一個進程在內存里有三部分的數據,就是"代碼段"、"堆棧段"和"數據段"。其實學過匯編語言的人一定知道,一般的CPU都有上述三種段寄存器,以方便操作系統的運行。這三個部分也是構成一個完整的執行序列的必要的部分。
"代碼段",顧名思義,就是存放了程序代碼的數據,假如機器中有數個進程運行相同的一個程序,那么它們就可以使用相同的代碼段。
"堆棧段"存放的就是子程序的返回地址、子程序的參數以及程序的局部變量。
而數據段則存放程序的全局變量,常數以及動態數據分配的數據空間(比如用malloc之類的函數取得的空間)。
Linux下的進程控制
在傳統的Unix環境下,有兩個基本的操作用於創建和修改進程:函數fork( )用來創建一個新的進程,該進程幾乎是當前進程的一個完全拷貝;函數族exec( )用來啟動另外的進程以取代當前運行的進程。
fork()
fork在英文中是"分叉"的意思。為什么取這個名字呢?因為一個進程在運行中,如果使用了fork,就產生了另一個進程,於是進程就"分叉"了,所以這個名字取得很形象。
調用這個fork函數時發生了什么呢?fork函數啟動一個新的進程,這個進程幾乎是當前進程的一個拷貝:子進程和父進程使用相同的代碼段;子進程復制父進程的堆棧段和數據段。個人理解,還拷貝了環境變量。這樣,父進程的所有數據都可以留給子進程,但是,子進程一旦開始運行,雖然它繼承了父進程的一切數據,但實際上數據卻已經分開,相互之間不再有影響了,也就是說,它們之間不再共享任何數據了。它們再要交互信息時,只有通過進程間通信來實現。
也就是說,fork出的子進程的一切都來自父進程,包括代碼、數據、堆棧、打開的文件等,就連代碼的執行位置(狀態)都是一樣的。
exec( )函數族
一個進程一旦調用exec類函數,它本身就"死亡"了,系統把代碼段替換成新的程序的代碼,廢棄原有的數據段和堆棧段,並為新程序分配新的數據段與堆棧段,並重新加載配置文件。唯一留下的,就是進程號,個人理解,還留下了拷貝自父進程環境變量和重新加載的配置文件中的內容(環境變量,函數等等)
我們平時使用shell命令時,產生的子進程方式大體有以下兩種
第一種只使用 fork() 函數,子進程幾乎是當前進程的一個拷貝,父進程中的函數、全局變量、別名以及環境變量在子進程中仍然有效。不會重新加載配置文件
注意:子進程只是父進程的拷貝,兩者並不共享,所以在子進程中改變了某個全局變量或者環境變量,不會反應到父進程中,父進程中該變量的值保持原樣。同樣,在子進程被創建后,在改變父進程中變量的值,也不會反應到子進程中。
第二種使用 fork() 后,又使用了 exec() 函數。這樣新建子進程只留下了進程ID和環境變量,及重新加載的配置文件。其余的全局變量,別名等就不存在了。相當於exce對子進程進行了格式化一樣
以新進程的方式運行腳本文件,比如bash ./test.sh
、chmod +x ./test.sh; ./test.sh
,或者在當前 Shell 中使用 bash 命令啟動新的 Shell,它們都屬於第二種創建子進程的方式,所以子進程除了能繼承父進程的環境變量外,基本上也不能使用父進程的什么東西了,比如,父進程的全局變量、局部變量、文件描述符、別名等在子進程中都無效。
但是,組命令、命令替換、管道這幾種語法都使用第一種方式創建進程,所以子進程可以使用父進程的一切,包括全局變量、局部變量、別名等。
為了方便兩者的區別,我們稱第一種為子shell,第二種為子進程。