---恢復內容開始---
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 }