前言
學習操作系統,首先便要學到process概念。process是什麼?Process – a program in execution。七十年代UNIX最先提出多進程的構想,之後該構想便廣泛用於linux與unix操作系統中。fork()函數給程序猿們提供了簡便的多進程編程方式。
fork() creates new process
exec() used after a fork to replace the process’ memory space with a new program
下面就來看看fork()函數和exec()究竟幹了什麼。
fork()
fork() 創建一個新的進程,該進程與主進程爲父子關係。
Linux下一個進程在內存裏有三部分的數據,就是"代碼段"、"堆棧段"和"數據段"。
"代碼段",顧名思義,就是存放了程序代碼的數據,假如機器中有數個進程運行相同的一個程序,那麼它們就可以使用相同的代碼段。"堆棧段"存放的就是子程序的返回地址、子程序的參數以及程序的局部變量。而數據段則存放程序的全局變量,常數以及動態數據分配的數據空間(比如用malloc之類的函數取得的空間)。這其中有許多細節問題,這裏限於篇幅就不多介紹了。系統如果同時運行數個相同的程序,它們之間就不能使用同一個堆棧段和數據段。
fork() 創建的子進程完全拷貝主進程,所以子進程的運行完全與主進程相同,且與主進程並行處理,競爭CPU的資源。
不同的是,主進程與子進程的fork()返回值不同,子進程中fork()的返回值是0,而主進程的fork()返回值是產生的子進程的進程id。
舉個栗子:
#include <sys/types.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(){
int i;
pid_t pid;
//for another process
pid = fork();
if(pid < 0){ //error
printf("Fork failed");
return 1;
}
else if(pid == 0){
for(i = 0; i < 1000; i++){
printf("This is a child\n");
}
}
else{
for(i = 0; i < 1000; i++){
printf("process\n");
}
}
return 0;
}
這樣運行時,就會交替輸出"This is a child" 與 "process", 可以看出子進程與主進程的並行處理。
這樣的競爭狀況是我們不希望看到的,所以下面修改一下
#include <sys/types.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(){
int i;
pid_t pid;
//for another process
pid = fork();
if(pid < 0){ //error
printf("Fork failed");
return 1;
}
else if(pid == 0){
printf("This is a child\n");
}
else{
wait(NULL);
printf("Child complete\n");
}
return 0;
}
這樣主進程會等到子進程運行完了再運行。
exec()
fork產生一個進程,該進程如果只是運行主進程的程序,那如果我們希望這個進程去幹其他的活動,比如所我們在打字的時候同時聽着歌,怎麼辦呢?這裏就用到了exec()。
一個進程一旦調用exec類函數,它本身就"死亡"了,系統把代碼段替換成新的程序的代碼,廢棄原有的數據段和堆棧段,併爲新程序分配新的數據段與堆棧段,唯一留下的,就是進程號,也就是說,對系統而言,還是同一個進程,不過已經是另一個程序了。(不過exec類函數中有的還允許繼承環境變量之類的信息。)
來看個栗子
#include <sys/types.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(){
int i;
pid_t pid;
//for another process
pid = fork();
if(pid < 0){ //error
printf("Fork failed");
return 1;
}
else if(pid == 0){ //run program.c
execl("./program", "program", NULL);
}
else{
wait(NULL);
printf("Child complete\n");
}
return 0;
}
其中program.c
#include <stdio.h>
#include <ctype.h>
int main(){
int i;
for(i = 0; i < 10; i++){
printf("A new program\n");
}
return 0;
}
這裏子進程就是運行的program.c。
總結
在傳統的Unix、linux環境下,有兩個基本的操作用於創建和修改進程:函數fork( )用來創建一個新的進程,該進程幾乎是當前進程的一個完全拷貝;函數族exec( )用來啓動另外的進程以取代當前運行的進程。這些函數給程序猿的多進程編程提供了遍歷,也增加了CPU的利用率。