通過 上一部分的學習,我們了解了進程的概念以及在Linux中進程的實現(xiàn),,此部分我們將具體學習如何在Linux中創(chuàng)建一個進程,。
一前言:
通過原理知識的學習,我們知道每個進程由進程ID號標識,。進程被創(chuàng)建時系統(tǒng)會為其分配一個唯一的進程ID號,。當一個進程向其父進程(創(chuàng)建該進程的進程)傳遞其終止消息時,意味這個進程的整個生命周期結(jié)束,。此時,,該進程占用的所用資源包括進程ID被全部釋放。
那么在Linux中如何創(chuàng)建一個進程呢,?
創(chuàng)建進程有兩種方式:一是由操作系統(tǒng)創(chuàng)建,,二是由父進程創(chuàng)建的進程(通常為子進程)。
系統(tǒng)調(diào)用fork是創(chuàng)建一個新進程的唯一方式,。vfork也可創(chuàng)建進程,,但它實際上還是調(diào)用了fork函數(shù)。
Tiger-John 說明:
1.由操作系統(tǒng)創(chuàng)建的進程它們之間是平等的不存在資源繼承關系,。
2.由父進程創(chuàng)建的進程通常為子進程它們之間有繼承關系,。
3. 在系統(tǒng)啟動時,OS會創(chuàng)建一些進程,,它們承擔著管理和分配系統(tǒng)資源的任務,即系統(tǒng)進程,。
如0號idle進程,,它是從無到有誕生的第一個線程,主要用于節(jié)能 ,;關于 idle進程 ,, 系統(tǒng)最初引導 0號進程,對應的 PCB為 init_task(),,要說明下它是 0號進程 PCB的頭,,并不是 1號 init進程,在引導結(jié)束后即成為 cpu 上的 idle進程。在每個 cpu上都有一個 idle進程,,這些進程登記在 init_tasks[]數(shù)組中,。 idle進程不進入就緒隊列,系統(tǒng)穩(wěn)定后,,僅當就緒隊列為空的時候 idle 進程才會被調(diào)度到,,在沒有其它進程運行的情況下,它大量時間占用 cpu ,。
1號進程(init進程)它是一個由內(nèi)核啟動的用戶級進程 ,,它是所有用戶進程的父進程。實際上,,Linux2.6在初始化階段首先把它建立為一個內(nèi)核線程kernel_init:
kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);
參數(shù)CLONE_FS | CLONE_FILES | CLONE_SIGHAND表示0號線程和1號線程分別共享文件系統(tǒng)(CLONE_FS),、打開的文件(CLONE_FILES)和信號處理程序(CLONE_SIGHAND)。當調(diào)度程序選擇到kernel_init內(nèi)核線程時,,kernel_init就開始執(zhí)行內(nèi)核的一些初始化函數(shù)將系統(tǒng)初始化,。
那么,kernel_init()內(nèi)核線程是怎樣變?yōu)橛脩暨M程的呢,?
實際上,,kernel_init()內(nèi)核函數(shù)中調(diào)用了execve()系統(tǒng)調(diào)用,該系統(tǒng)調(diào)用裝入用戶態(tài)下的可執(zhí)行程序init(/sbin/init),。注意,,內(nèi)核函數(shù)kernel_init()和用戶態(tài)下的可執(zhí)行文件init是不同的代碼,處于不同的位置,,也運行在不同的狀態(tài),,因此,init是內(nèi)核線程啟動起來的一個普通的進程,,這也是用戶態(tài)下的第一個進程,。init進程從不終止,因為它創(chuàng)建和監(jiān)控操作系統(tǒng)外層所有進程的活動,。
二fork()函數(shù)和vfork()函數(shù)的學習
1.fork()函數(shù)
調(diào)用fork函數(shù)后,,當前進程分裂為兩個進程,一個是原來的父進程,,另一個是剛創(chuàng)建的子進程,。父進程調(diào)用fork后返回值是子進程的ID,子進程中返回值是0,,若進程創(chuàng)建失敗,,只返回-1。失敗原因一般是父進程擁有的子進程個數(shù)超過了規(guī)定限制(返回EAGAIN)或者內(nèi)存不足(返回ENOMEM),。我們可以依據(jù)返回值判斷進程,,一般情況下調(diào)用fork函數(shù)后父子進程誰先執(zhí)行是未定的,取決于內(nèi)核所使用的調(diào)度算法。一般情況下os讓所有進程享有同等執(zhí)行權,,除非某些進程優(yōu)先級高,。若有一個孤兒進程,即父進程先于子進程死去,,子進程將會由init進程收養(yǎng),。
函數(shù)實例:通過fork()函數(shù)創(chuàng)建一個進程:
1 #include<sys/types.h>
2 #include<unistd.h>
3 #include<stdio.h>
4
5 main()
6 {
7 pid_t pid;
8 printf("PID before fork() :%d\n",(int)getpid());
9
10 pid = fork();
11 if(pid < 0){
12 printf("error in fork!\n");
13 }
14 else if(0 == pid){
15 printf("I'm the child process, CurpPID is %d,ParentPid is %d \n",pid,(int)getppid());
16 }
17 else{
18 printf("I'm the parent process,child PID is %d,ParentPID is %d\n",pid,(int)getpid());
19 }
20
程序經(jīng)過調(diào)試后結(jié)果如下:
think@ubuntu:~/work/process_thread/fork$ ./fork
PID before fork() :4566
I'm the parent process,child PID is 4567,ParentPID is 4566
I'm the child process, CurpPID is 0,ParentPid is 4566
從程序執(zhí)行結(jié)果可以看出: 調(diào)后fork()函數(shù)后返回兩個值,子進程返回值為0,,而父進程的返回值為創(chuàng)建的子進程的進程ID,。
Tiger-John說明:
1> Linux進程一般包括代碼段,數(shù)據(jù)段和堆棧段,。代碼段存放程序的可執(zhí)行代碼,;數(shù)據(jù)段存放程序的全局變量、常量,、靜態(tài)變量,;堆存放動態(tài)分配的內(nèi)存變量,棧用于函數(shù)調(diào)用,,存放函數(shù)參數(shù),、函數(shù)內(nèi)部定義的局部變量。
2>有人說調(diào)用fork函數(shù)后,,fork()函數(shù)返回了兩個值,,這是一中錯誤的說法。其實,,當系統(tǒng)調(diào)用fork()函數(shù)后 fork()會將調(diào)用進程的所有內(nèi)容原封不動的拷貝到新產(chǎn)生的子進程中去,, 當前進程分裂成了兩個進程分別在執(zhí)行,互不干擾,。
3>.看一個函數(shù)實例來仔細體會一下系統(tǒng)調(diào)用fork()函數(shù)后是如何執(zhí)行的,。
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<sys/types.h>
4
5 int main()
6 {
7 pid_t pid;
8 int count = 0;
9 pid = fork();
10
11 printf("This is first time,pid = %d\n",pid);
12 printf("This is the second time,pid = %d\n",pid);
13 count++;
14 printf("count = %d\n",count);
15
16 if(pid > 0){
17 printf("This is the parent process,the child has the pid :%d \n",pid);
18 }
19 else if(!pid){
20 printf("This is the child process.\n");
21 }
22 else{
23 printf("fork failed.\n");
24 }
25
26 printf("This is third,pid = %d\n",pid);
27 printf("This is four time,pid = %d\n",pid);
28 return 0;
29 }
程序經(jīng)過調(diào)試后結(jié)果
think@ubuntu:~/work/process_thread/fork1$ ./fork
This is first time,pid = 4614
This is the second time,pid = 4614
count = 1
This is the parent process,the child has the pid :4614
This is third,pid = 4614
This is four time,pid = 4614
This is first time,pid = 0
This is the second time,pid = 0
count = 1
This is the child process.
This is third,pid = 0
This is four time,pid = 0
think@ubuntu:~/work/process_thread/fork1$
Tiger-John說明:
從上面的程序的執(zhí)行結(jié)果我們看到一個奇怪的現(xiàn)象:為什么printf的語句執(zhí)行兩次,
而那句“count++;”的語句卻只執(zhí)行了一次 ,?
系統(tǒng)在調(diào)用fork()后分裂成了兩個函數(shù)分別執(zhí)行 ,,互不干擾 。
2.Vfork和fork的區(qū)別:
1> vfork也可創(chuàng)建進程,,但它實際上還是調(diào)用了fork函數(shù),。
2>在說明他們的區(qū)別之前我們先看兩個程序和執(zhí)行結(jié)果
函數(shù)實例1:用fork()函數(shù)創(chuàng)建進程
1 #include<stdio.h>
2 #include<sys/types.h>
3 #include<unistd.h>
4 #include<stdlib.h>
5 int globVar = 5;
6
7 int main(void)
8 {
9 pid_t pid;
10 int var = 1;
11 int i;
12 printf("fork is different with vfork \n");
13
14 pid = fork();
15 if(!pid){
16 i=3;
17 while(i-- > 0){
18 printf("Child process is running\n");
19 globVar++;
20 var++;
21 sleep(1);
22 }
3 printf("Child's globVar = %d,var = %d\n",globVar,var);
24 }
25 else if(pid){
26 i=5;
27 while(i-- > 0){
28 printf("Parent process is running\n");
29 globVar++;
30 var++;
31 sleep(1);
32 }
33 printf("Parent's globVar = %d,var %d\n",globVar,var);
34 exit(0);
35 }
36 else{
37 perror("Process creation failed\n");
38 exit(-1);
39 }
40 }
程序經(jīng)過調(diào)試后;
think@ubuntu:~/work/process_thread/fork3$ ./fork
fork is different with vfork
Parent process is running
Child process is running
Child process is running
Parent process is running
Child process is running
Parent process is running
Child's globVar = 8,var = 4
Parent process is running
Parent process is running
Parent's globVar = 10,var = 6
函數(shù)實例2:用vfork()函數(shù)創(chuàng)建一個進程
1 #include<stdio.h>
2 #include<sys/types.h>
3 #include<unistd.h>
4 #include<stdlib.h>
5 int globVar = 5;
6 int main(void)
7 {
8 pid_t pid;
9 int var = 1;
10 int i;
11
12 printf("fork is different with vfork!\n");
13
14 pid = vfork();
15 if(!pid){
16 i=3;
17 while(i-- > 0)
18 {
19 printf("Child process is running\n");
20 globVar++;
21 var++;
22 sleep(1);
23 }
24 printf("Child's globVar = %d,var =%d\n",globVar,var);
25 }
26 else if(pid){
27 i = 5;
28 while(i-- > 0)
29 {
30 printf("Parent process is running\n");
31 globVar++;
32 var++;
33 sleep(1);
34 }
35 printf("Parent's globVar = %d,var %d\n",globVar,var);
36 exit(0);
37 }
38 else {
39 perror("Process creation failed\n");
40 exit(0);
41 }
42
43 }
程序經(jīng)過調(diào)試后結(jié)果為:
think@ubuntu:~/work/process_thread/fork3$ ./vfork
fork is different with vfork!
Child process is running
Child process is running
Child process is running
Child's globVar = 8,var = 4
Parent process is running
Parent process is running
Parent process is running
Parent process is running
Parent process is running
Parent's globVar = 13,var = 5
Tiger-John說明:
我們通過以上兩個函數(shù)的執(zhí)行可以看到一些區(qū)別:
1. 使用 fork 創(chuàng)建子進程時,,子進程繼承了父進程的全局變量和局部變量。在子進程中,,最后全局變量 globVar 和局部變量 var 的值均遞增 3 ,,分別為 8 和 4. 不管是全局變量還是局部變量,子進程與父進程對它們的修改互不影響。
2. 父進程中兩者分別遞增 5. 最后結(jié)果為 10 和 6
通過以上程序的運行結(jié)果可以證明 fork 的子進程有自己獨立的地址空間,。
3. 子進程和父進程的執(zhí)行順序是很隨意的,,沒有固定的順序。父子進程的輸出是混雜在一起的,。
--------------------------------------------
1.用vfork()函數(shù)創(chuàng)建子進程后,,父進程中globVar和var最后均遞增了8.這是因為vfork的子進程共享父進程的地址空間,子進程修改變量對父進程是可見的,。
2.使用vfork()函數(shù)子進程后打印的結(jié)果是子進程在前,,父進程在后,說明vfork()保證子進程先執(zhí)行,,在子進程調(diào)用exit獲exec之前父進程處于阻塞等待狀態(tài),。
3>那么現(xiàn)在來看看fork()和vfork()函數(shù)之間的區(qū)別:
(1)fork():使用fork()創(chuàng)建一個子進程時,子進程只是完全復制父進程的資源,。這樣得到的子進程獨立于父進程具有良好的并發(fā)性,。
vfork(): 使用 vfor創(chuàng)建一個子進程時,操作系統(tǒng)并不將父進程的地址空間完全復制到子進程,。而是子進程共享父進程的地址空間,,即子進程完全運行在父進程的地址空間上。子進程對該地址空間中任何數(shù)據(jù)的修改同樣為父進程所見,。
(2)fork():父子進程執(zhí)行順序不定,;
vfork():保證子進程先運行,在調(diào)用exec或exit之前與父進程共享數(shù)據(jù),,在它調(diào)用exec或exit之后父進程才可能被調(diào)度運行,。
(3)vfork保證子進程先運行,在它調(diào)用exec或exit后父進程才可能被調(diào)度運行,。如果在調(diào)用這兩個函數(shù)之前子進程依賴于父進程的進一步動作,,則會導致死鎖。