嵌入式Linux學習---進程(1)

什麼是一個進程?當用戶敲入命令執行一個程序的時候,對系統而言,它將啓動一個進程。但和程序不一樣的是,在這個進程中,系統可能須要再啓動一個或多個進程來完成獨立的多個任務。多進程編程的主要內容包括進程控制和進程間通訊。shell

 

1       Linux下進程的結構

Linux下一個進程在內存裏有三部分的數據,就是"代碼段"、"堆棧段"和"數據段"。這三個部分也是構成一個完整的執行序列的必要的部分。編程

 

"代碼段",顧名思義,就是存放了程序代碼的數據,假如機器中有數個進程運行相同的一個程序,那麼它們就可使用相同的代碼段。"堆棧段"存放的就是子程序的返回地址、子程序的參數以及程序的局部變量。而數據段則存放程序的全局變量,常數以及動態數據分配的數據空間(好比用malloc之類的函數取得的空間)。系統若是同時運行數個相同的程序,它們之間就不能使用同一個堆棧段和數據段。小程序

 

2       Linux下的進程控制

Linux環境下,有兩個基本的操做用於建立和修改進程:函數fork()用來建立一個新的進程,該進程幾乎是當前進程的一個徹底拷貝;函數族exec()用來啓動另外的進程以取代當前運行的進程。框架

 

2.1    fork()

fork在英文中是"分叉"的意思。爲何取這個名字呢?由於一個進程在運行中,若是使用了fork,就產生了另外一個進程,因而進程就"分叉"了,因此這個名字取得很形象。下面就看看如何具體使用fork,這段程序演示了使用fork的基本框架:函數

void main(){操作系統

        int i;設計

        if ( fork() == 0 ) {繼承

                /* 子進程程序 */進程

                for ( i = 1; i <1000; i ++ ) printf("This is child process/n");ip

        }

        else {

                /* 父進程程序*/

                for ( i = 1; i <1000; i ++ ) printf("This is process process/n");

        }

}

 

程序運行後,你就能看到屏幕上交替出現子進程與父進程各打印出的一千條信息了。若是程序還在運行中,你用ps命令就能看到系統中有兩個它在運行了。

 

那麼調用這個fork函數時發生了什麼呢?fork函數啓動一個新的進程,這個進程幾乎是當前進程的一個拷貝:子進程和父進程使用相同的代碼段;子進程複製父進程的堆棧段和數據段。這樣,父進程的全部數據均可以留給子進程,可是,子進程一旦開始運行,雖然它繼承了父進程的一切數據,但實際上數據卻已經分開,相互之間再也不有影響了,也就是說,它們之間再也不共享任何數據了。它們再要交互信息時,只有經過進程間通訊來實現,這將是咱們下面的內容。

 

既然它們如此相象,系統如何來區分它們呢?這是由函數的返回值來決定的。對於父進程,fork函數返回了子程序的進程號,而對於子程序,fork函數則返回零。在操做系統中,咱們用ps函數就能夠看到不一樣的進程號,對父進程而言,它的進程號是由比它更低層的系統調用賦予的,而對於子進程而言,它的進程號便是fork函數對父進程的返回值。在程序設計中,父進程和子進程都要調用函數fork()下面的代碼,而咱們就是利用fork()函數對父子進程的不一樣返回值用if……else……語句來實現讓父子進程完成不一樣的功能。咱們看到,上面例子執行時兩條信息是交互無規則的打印出來的,這是父子進程獨立執行的結果,雖然咱們的代碼彷佛和串行的代碼沒有什麼區別。

 

讀者也許會問,若是一個大程序在運行中,它的數據段和堆棧都很大,一次fork就要複製一次,那麼fork的系統開銷不是很大嗎?不管是數據段仍是堆棧段都是由許多"頁"構成的,fork函數複製這兩個段,只是"邏輯"上的,並不是"物理"上的,也就是說,實際執行fork時,物理空間上兩個進程的數據段和堆棧段都仍是共享着的,當有一個進程寫了某個數據時,這時兩個進程之間的數據纔有了區別,系統就將有區別的"頁"從物理上也分開。系統在空間上的開銷就能夠達到最小。

 

下面演示一個足以"搞死"Linux的小程序,其源代碼很是簡單:

void main()

{

  for( ; ; ) fork();

}

這個程序什麼也不作,就是死循環地fork,其結果是程序不斷產生進程,而這些進程又不斷產生新的進程,很快,系統的進程就滿了,系統就被這麼多不斷產生的進程"撐死了"。固然只要系統管理員預先給每一個用戶設置可運行的最大進程數,這個惡意的程序就完成不了企圖了。

 

2.2    exec( )函數族

下面咱們來看看一個進程如何來啓動另外一個程序的執行。在Linux中要使用exec函數族。系統調用execve()對當前進程進行替換,替換者爲一個指定的程序,其參數包括文件名(filename)、參數列表(argv)以及環境變量(envp)。exec函數族固然不止一個,但它們大體相同,在Linux中,它們分別是:execl,execlp,execle,execv,execve和execvp,下面我只以execlp爲例。

 

一個進程一旦調用exec類函數,它自己就"死亡"了,系統把代碼段替換成新的程序的代碼,廢棄原有的數據段和堆棧段,併爲新程序分配新的數據段與堆棧段,惟一留下的,就是進程號,也就是說,對系統而言,仍是同一個進程,不過已是另外一個程序了。

 

那麼若是個人程序想啓動另外一程序的執行但本身仍想繼續運行的話,怎麼辦呢?那就是結合fork與exec的使用。下面一段代碼顯示如何啓動運行其它程序:

 

char command[256];

void main()

{

        int rtn; /*子進程的返回數值*/

        while(1) {

                /* 從終端讀取要執行的命令 */

                printf( ">" );

                fgets( command, 256, stdin );

                command[strlen(command)-1] = 0;

                if ( fork() == 0 ) {

                       /* 子進程執行此命令 */

                       execlp( command, command );

                       /* 若是exec函數返回,代表沒有正常執行命令,打印錯誤信息*/

                       perror( command );

                       exit( errorno );

                }

                else {

                       /* 父進程, 等待子進程結束,並打印子進程的返回值 */

                       wait ( &rtn );

                       printf( " child process return %d/n",. rtn );

                }

        }

}

 

此程序從終端讀入命令並執行之,執行完成後,父進程繼續等待從終端讀入命令。

 

在這一節裏,咱們還要講講system()和popen()函數。system()函數先調用fork(),而後再調用exec()來執行用戶的登陸shell,經過它來查找可執行文件的命令並分析參數,最後它麼使用wait()函數族之一來等待子進程的結束。函數popen()和函數system()類似,不一樣的是它調用pipe()函數建立一個管道,經過它來完成程序的標準輸入和標準輸出。

相關文章
相關標籤/搜索