linux進程之fork 和 exec函數


---恢復內容開始---

fork函數

該函數是unix中派生新進程的唯一方法。

  #include <unistd.h>

  pid_t   fork(void);

返回: (調用它一次, 它返回 兩次 , 它在調用進程(稱為父進程)中返回一次, 返回值是新派生進程(稱為子進程)的進程ID號

    在子進程又返回一次,返回值為0。 因此,返回值本身告知當前進程是子進程還是父進程)

   在子進程中為0, 在父進程中為子進程ID,

   若出錯則為-1;

fork有兩個典型的用法:

  1.一個進程創建一個自身的副本,這樣每個副本都 可以在另一個副本執行其他任務的同時處理各自的某個操作。 這是網絡服務器的典型用法;

  2. 一個進程想要執行另一個程序。既然創建新進程的唯一辦法是調用fork, 該進程於是首先調用fork創建一個自身的副本,然后另一個副本(通常為子進程)調用exec把自身替換成新的程序。 這是shell之類程序的典型用法;

  exec把當前進程映像替換成新的程序文件,而且該新程序通常從main函數開始執行,進程ID並不改變。我們稱調用exec的進程為調用進程(calling process),稱新執行的程序為新程序(new program)

  #include <unistd.h>

  int  execl(const  char *pathname, const char *arg0,  .../);

  int   execv(const  char  *pathname, char *const  *argv[]);

  int    execle(const char *pathname, const char *arg(), ....);

  int   execve(const char  *pathname,  char *const argv[],  char *const envp[]);

  int   execlp(const  char *filename, con)

 並發服務器在listen到連接之后,accept()函數解除阻塞,然后服務器執行fork()函數,在父進程中有listenfd,  和 由 accpet返回的已連接套接字即connfd, 而子進程也存在listenfd, 和由accept返回的已經連接套接字即connfd。 

  下一步: 父進程關閉已連接的套接字connfd,保留listenfd繼續監聽。 子進程關閉listenfd套接字,保留connfd進行相應操作(read, write等操作)

getsockname  和  getpeername函數

===================================================================================================================

  信號(signal) 就是告知某個進程發生了某個事件的通知,  有時也稱為 “ software interrupt “ .  信號通常是異步發生的, 也就是說進程預先不知道信號的准確發生的時間;

  信號可以:

    1. 由一個信號發給另有一個進程(或自身)

    2. 由內核發給某個進程;

  每一個信號都有一個與之關聯的處置(disposition), 也稱為行為(action)。 我們通過調用sigaction函數來設定一個信號的處置,並有三種選擇。

  (1) 我們提供一個函數, 只要有特定信號發生它就被調用。   這樣的函數稱為“信號處理函數( signal handler)”, 這種行為稱為 捕獲信號(catch signal). 

    有兩個信號不能被捕獲,它們是SIGKILL  和 SIGSTOP。  信號處理函數由信號值這個單一的整數參數來調用, 且沒有返回值,

    其函數原型如下:

      void  handler(int  signo);

    對於大多信號來說,調用signcation函數   並指定信號發生所調用的函數  就是捕獲信號所需做的全部工作;此外,SIGIO, SIGPOLL, SIGURG這些個別信號還要求捕獲它們的進程 做其它額外 的工作;

  (2)我們可以把某個信號的處置設定為SIG_IGN來忽略(ignore)它。  SIGKILL和SIGSTOP這兩個信號不能被忽略;

  (3)我們可以把某個信號的處置設定為 SIG_DFL來啟用它的默認(default)處理。默認處置通常是在收到信號后終止進程,某些信號的默認default處理不同;

 

 

---恢復內容結束---

fork函數

該函數是unix中派生新進程的唯一方法。

  #include <unistd.h>

  pid_t   fork(void);

返回: (調用它一次, 它返回 兩次 , 它在調用進程(稱為父進程)中返回一次, 返回值是新派生進程(稱為子進程)的進程ID號

    在子進程又返回一次,返回值為0。 因此,返回值本身告知當前進程是子進程還是父進程)

   在子進程中為0, 在父進程中為子進程ID,

   若出錯則為-1;

fork有兩個典型的用法:

  1.一個進程創建一個自身的副本,這樣每個副本都 可以在另一個副本執行其他任務的同時處理各自的某個操作。 這是網絡服務器的典型用法;

  2. 一個進程想要執行另一個程序。既然創建新進程的唯一辦法是調用fork, 該進程於是首先調用fork創建一個自身的副本,然后另一個副本(通常為子進程)調用exec把自身替換成新的程序。 這是shell之類程序的典型用法;

  exec把當前進程映像替換成新的程序文件,而且該新程序通常從main函數開始執行,進程ID並不改變。我們稱調用exec的進程為調用進程(calling process),稱新執行的程序為新程序(new program)

  #include <unistd.h>

  int  execl(const  char *pathname, const char *arg0,  .../);

  int   execv(const  char  *pathname, char *const  *argv[]);

  int    execle(const char *pathname, const char *arg(), ....);

  int   execve(const char  *pathname,  char *const argv[],  char *const envp[]);

  int   execlp(const  char *filename, con)

 並發服務器在listen到連接之后,accept()函數解除阻塞,然后服務器執行fork()函數,在父進程中有listenfd,  和 由 accpet返回的已連接套接字即connfd, 而子進程也存在listenfd, 和由accept返回的已經連接套接字即connfd。 

  下一步: 父進程關閉已連接的套接字connfd,保留listenfd繼續監聽。 子進程關閉listenfd套接字,保留connfd進行相應操作(read, write等操作)

getsockname  和  getpeername函數

===================================================================================================================

  信號(signal) 就是告知某個進程發生了某個事件的通知,  有時也稱為 “ software interrupt “ .  信號通常是異步發生的, 也就是說進程預先不知道信號的准確發生的時間;

  信號可以:

    1. 由一個信號發給另有一個進程(或自身)

    2. 由內核發給某個進程;

  每一個信號都有一個與之關聯的處置(disposition), 也稱為行為(action)。 我們通過調用sigaction函數來設定一個信號的處置,並有三種選擇。

  (1) 我們提供一個函數, 只要有特定信號發生它就被調用。   這樣的函數稱為“信號處理函數( signal handler)”, 這種行為稱為 捕獲信號(catch signal). 

    有兩個信號不能被捕獲,它們是SIGKILL  和 SIGSTOP。  信號處理函數由信號值這個單一的整數參數來調用, 且沒有返回值,

    其函數原型如下:

      void  handler(int  signo);

    對於大多信號來說,調用signcation函數   並指定信號發生所調用的函數  就是捕獲信號所需做的全部工作;此外,SIGIO, SIGPOLL, SIGURG這些個別信號還要求捕獲它們的進程 做其它額外 的工作;

  (2)我們可以把某個信號的處置設定為SIG_IGN來忽略(ignore)它。  SIGKILL和SIGSTOP這兩個信號不能被忽略;

  (3)我們可以把某個信號的處置設定為 SIG_DFL來啟用它的默認(default)處理。默認處置通常是在收到信號后終止進程,某些信號的默認default處理不同;

 =======================================================================================================2.1 Linux下進程的結構

  Linux下一個進程在內存里有三部分的數據,就是"代碼段"、"堆棧段"和"數據段"。其實學過匯編語言的人一定知道,一般的CPU都有上述三種段寄存器,以方便操作系統的運行。這三個部分也是構成一個完整的執行序列的必要的部分。

  "代碼段",顧名思義,就是存放了程序代碼的數據,假如機器中有數個進程運行相同的一個程 序,那么它們就可以使用相同的代碼段。"堆棧段"存放的就是子程序的返回地址、子程序的參數以及程序的局部變量。而數據段則存放程序的全局變量,常數以及 動態數據分配的數據空間(比如用malloc之類的函數取得的空間)。這其中有許多細節問題,這里限於篇幅就不多介紹了。系統如果同時運行數個相同的程 序,它們之間就不能使用同一個堆棧段和數據段。

  如果一個大程序在運行中,它的數據段和堆棧都很大,一次fork就要復制一次,那么fork的系統開銷不是很大嗎?其實UNIX自有其解決的辦法,大家知 道,一般CPU都是以"頁"為單位來分配內存空間的,每一個頁都是實際物理內存的一個映像,象INTEL的CPU,其一頁在通常情況下是 4086字節大小,而無論是數據段還是堆棧段都是由許多"頁"構成的,fork函數復制這兩個段,只是"邏輯"上的,並非"物理"上的,也就是說,實際執 行fork時,物理空間上兩個進程的數據段和堆棧段都還是共享着的,當有一個進程寫了某個數據時,這時兩個進程之間的數據才有了區別,系統就將有區別的" 頁"從物理上也分開。系統在空間上的開銷就可以達到最小。

exec( )函數族

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

  以execlp為例,其它函數究 竟與execlp有何區別,請通過manexec命令來了解它們的具體情況。

  一個進程一旦調用exec類函數,它本身就"死亡"了,系統把代碼段替換成新的程序的代碼, 廢棄原有的數據段和堆棧段,並為新程序分配新的數據段與堆棧段,唯一留下的,就是進程號,也就是說,對系統而言,還是同一個進程,不過已經是另一個程序 了。(不過exec類函數中有的還允許繼承環境變量之類的信息。)

那么如果我的程序想啟動另一程序的執行但自己仍想繼續運行的話,怎么辦呢?那就是結合fork與exec的使用。下面一段代碼顯示如何啟動運行其它程序:

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #include <errno.h>
 4 #include <string.h>
 5 
 6 char command[256];
 7 int main()
 8 {
 9     int rtn;
10     printf(">>");
11     
12     fgets(command, 256, stdin);
13     command[strlen(command)-1] = 0;
14     if(fork()==0) //子進程
15     {
16         execlp(command, NULL);
17         perror(command);
18         exit(errno);
19     }    
20     else  //父進程
21     {
22         wait(&rtn);
23         printf("child process return %d\n", rtn);
24     }    
25     
26     return 0;
27 }

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM