行程控制
- 再遇fork
- 寫時拷貝
- fork的常規用法
- 行程終止
- 行程退出
- 行程等待
- 行程等待的必要性
- 行程等待的方法
- 獲取子行程status
- wait
- waitpid
- 非阻塞等待
- 行程替換
- 替換原理
- 替換函式
- 函式解釋
- 命名解釋
再遇fork
fork,它從已經在的行程中創建一個新的行程,新的行程叫子行程,原行程叫父行程,
先來看一段代碼:
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<stdlib.h>
5 int main()
6 {
7 pid_t pid;
8 printf("Before: pid is %d\n",getpid());
9
10 pid = fork();
11 printf("After:pid is %d, fork return %d\n", getpid(), pid);
12 sleep(1);
13 return 0;
14 }


fork在內核干了什么?
分配新的記憶體塊和內核資料結構給子行程
將父行程部分資料結構內容拷貝至子行程
添加子行程到系統行程串列當中
fork回傳,開始調度器調度
寫時拷貝
通常,父子代碼共享,父子再不寫入時,資料也是共享的,當任意一方試圖寫入,便以寫時拷貝的方式各自一份副本,
共享:父子行程通過頁表映射時指向同一空間

寫時拷貝后,頁表那里的只讀權限就沒了,
為什么要寫時拷貝?
行程具有獨立性,子行程需要寫入資料就會在物理空間重新開一段空間在修改映射關系
子行程不一定全要用父行程的資料我需要多少拷貝多少,節省空間
fork的常規用法
1.一個父行程希望復制自己,使父子行程同時執行不同的代碼段,例如,父行程等待客戶端請求,生成子行程來處理請求,
2.一個行程要執行一個不同的程式,例如子行程從fork回傳后,呼叫exec函式,
fork呼叫失敗的原因:
系統中有太多的行程
實際用戶的行程數超過了限制
fork小結:
1.fork父行程回傳子行程的pid,子行程回傳0,它們資料私有,代碼共享,
回傳子行程pid的原因:創建子行程是讓它辦理業務的,父行程需要知道子行程有沒有完成,還要對子行程完成回收,
2.如何理解子行程的創建?

3.fork為什么有2個回傳值?

行程終止
行程退出
行程退出的場景
1.代碼跑完,結果正確
2.代碼跑完,結果不正確
3.代碼例外終止
行程常見退出方法(可以通過 echo $? 查看行程退出碼)
- 從main回傳
- 呼叫exit
- _exit
從main函式退出的數字叫做退出碼,0在函式設計一般代表正確,非0運行出錯,

exit和return的區別:
exit:終止整個行程
return :終止函式

exit和_exit

2者的區別:exit會釋放曾經占用的資源而_exit不會進行清理作業,
例外退出:
ctrl+c,j行程例外退出后退出碼便沒有意義了,
行程等待
行程等待的必要性
1.子行程退出,父行程如果不管不顧,就可能造成‘僵尸行程’的問題,進而造成記憶體泄漏,
2.行程一旦變成僵尸狀態,kill -9 也無能干掉它,因為誰也沒有辦法殺死一個已經死去的行程,
3.父行程派給子行程的任務完成的如何,我們需要知道,如,子行程運行完成,結果對還是不對,
或者是否正常退出,父行程通過行程等待的方式,回收子行程資源,獲取子行程退出資訊
行程等待的方法
獲取子行程status
wait和waitpid,都有一個status引數,該引數是一個輸出型引數,由作業系統填充,
如果傳遞NULL,表示不關心子行程的退出狀態資訊,
否則,作業系統會根據該引數,將子行程的退出資訊反饋給父行程,
status不能簡單的當作整形來看待,可以當作位圖來看待,具體細節如下圖(只研究status低16位元位):

wait
wait:是父行程通過wait等系統呼叫,用來等待子行程狀態的一種現象,
防止子行程變成僵尸行程造成記憶體泄露,讀取子行程的狀態,
1.可以通過位運算來得到退出碼和退出信號
code=(status >> 8) & 0Xff;//退出碼
signal=status & 0x7f//退出信號
2.還可以用系統 中的2個宏
WIFEXITED(status):查看行程是否正常退出,即查看退出碼
WEXITSTATUS(status):查看退出信號
wait是行程阻塞等待,父行程會一直處于阻塞,等待子行程,知道子行程運行結束,
1 #include<stdio.h>
2 #include<unistd.h>
3 #include <sys/wait.h>
4 #include <stdlib.h>
5 #include <string.h>
6
7 int main()
8 {
9 pid_t pid;
10 pid = fork();
11
12 if(pid < 0 )
13 {
14 perror("error");
15 }
16 else if( pid == 0)
17 {
18 //child
19 int i = 5;
20 for(;i >= 0;--i)
21 {
22 printf("i am a child:%d %d\n",getpid(),getppid());
23 sleep(1);
24 }
25 exit(0);//終止子行程
26 }
27 else
28 {
29 //parent
30 int status;
31 pid_t ret = wait(&status);
32 if(ret > 0)
33 {
34 printf("wait child scuess:\n");
35 if(WIFEXITED(status))
36 {
37 printf("exit code:%d\n",WEXITSTATUS(status));
38 }
39 }
40 }
41 sleep(3);
42 return 0;
43 }

如果把exit(0)改為10:

waitpid
pid_ t waitpid(pid_t pid, int *status, int options);
回傳值:
1.當正常回傳的時候waitpid回傳收集到的子行程的行程ID;
2. 如果設定了選項WNOHANG,而呼叫中waitpid發現沒有已退出的子行程可收集,則回傳0;
3. 如果呼叫中出錯,則回傳-1,這時errno會被設定成相應的值以指示錯誤所在;
引數:
pid:
Pid=-1,等待任一個子行程,與wait等效,
Pid>0.等待其行程ID與pid相等的子行程,
status:
WIFEXITED(status): 若為正常終止子行程回傳的狀態,則為真,(查看行程是否是正常退出)
WEXITSTATUS(status): 若WIFEXITED非零,提取子行程退出碼,(查看行程的退出碼)
options:
WNOHANG: 若pid指定的子行程沒有結束,則waitpid()函式回傳0,不予以等待,若正常結束,則回傳該子行程的ID,

父行程創建子行程后,也可以通過waitpid一直等待子行程退出,此時將第3個引數給成0,直到子行程退出讀取退出資訊,
1 #include<stdio.h>
2 #include<unistd.h>
3 #include <sys/wait.h>
4 #include <stdlib.h>
5 #include <string.h>
6
7 int main()
8 {
9 pid_t pid;
10 pid = fork();
11
12 if(pid < 0 )
13 {
14 perror("error");
15 }
16 else if( pid == 0)
17 {
18 //child
19 int i = 10;
20 for(;i >= 0;--i)
21 { 22 printf("i am a child: pid:%d ppid: %d\n",getpid(),getppi d());
23 sleep(1);
24 }
25 exit(0);//終止子行程
26 }
27 else
28 {
29 //parent
30 int status;
31 pid_t ret = waitpid(pid,&status,0);
32 if(ret > 0)
33 {
34 printf("wait child scuess:\n");
35 if(WIFEXITED(status))
36 {
37 printf("exit code:%d\n",WEXITSTATUS(status));
38 }
39 else
40 {
41 printf("sig code:%d\n",status&0x7F);
42 }
43 }
44 }
45 sleep(3);
46 return 0;
47 }
在子行程運行時,可以通過kill - 9干掉子行程,父行程也能成功等待子行程

非阻塞等待
上面的wait是父行程什么都不干干等著子行程退出,waitpid父行程可以一邊等子行程一邊做自己的事情,此時將低3個引數傳WNOHANG若pid指定的子行程沒有結束,則waitpid()函式回傳0,不予以等待,若正常結束,則回傳該子行程的ID,,
1 #include<stdio.h>
2 #include<unistd.h>
3 #include <sys/wait.h>
4 #include <stdlib.h>
5 #include <string.h>
6
7 int main()
8 {
9 pid_t pid;
10 pid = fork();
11
12 if(pid < 0 )
13 {
14 perror("error");
15 }
16 else if( pid == 0)
17 {
18 //child
19 int i = 10;
20 for(;i >= 0;--i)
21 {
22 printf("i am a child: pid:%d ppid: %d\n",getpid(),getppid());
23 sleep(1);
24 }
25 exit(0);//終止子行程
26 }
27 else
28 {
29 //parent
30 while(1)
31 {
32 int status;
33 pid_t ret = waitpid(pid,&status,WNOHANG);
34 if(ret > 0)
35 {
36 printf("wait child scuess:\n");
37 printf("exit code:%d\n",WEXITSTATUS(status));
38 break;
39 }
40 else if(ret == 0)
41 {
42 printf("parent do other\n");
43 sleep(1);
44 }
45 else
46 {
47 printf("error\n");
48 break;
49 }
50 }
51 }
52 sleep(3);
53 return 0;
54 }

父行程隔段時間看看子行程退沒退出,最后拿到子行程的退出資訊,
行程替換
用fork創建子行程后執行的是和父行程相同的程式(但有可能執行不同的代碼分支),子行程往往要呼叫一種exec函式以執行另一個程式,
當行程呼叫一種exec函式時,該行程的用戶空間代碼和資料完全被新程式替換,從新程式的啟動例程開始執行,呼叫exec并不創建新行程,所以呼叫exec前后該行程的id并未改變
先來一段小代碼:
替換原理
1 #include<stdio.h>
2 #include<unistd.h>
3
4 int main()
5 {
6
7 execl("/usr/bin/ls","ls","-a","-l",NULL);
8 printf("Linux!\n");
9 return 0;
10 }
結果如下:


替換函式
#include <unistd.h>
int execl(const char *path, const char *arg, …);
int execlp(const char *file, const char *arg, …);
int execle(const char *path, const char *arg, …,char *const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execve(const char *path, char *const argv[], char *const envp[]);
函式解釋
1.這些函式如果呼叫成功則加載新的程式從啟動代碼開始執行,不再回傳,
2.如果呼叫出錯則回傳-1
3.所以exec函式只有出錯的回傳值而沒有成功的回傳值,
命名解釋
l(list) : 表示引數采用串列
v(vector) : 引數用陣列
p(path) : 有p自動搜索環境變數PATH
e(env) : 表示自己維護環境變數

execl

execlp,帶p的可以使用環境變數
1 #include<stdio.h>
2 #include<unistd.h>
3
4 int main()
5 {
7 execlp("ls","ls","-a","-l",NULL);
8 printf("Linux!\n");
9 return 0;
10 }

execle // 帶e的,需要自己組裝環境變數


execvp//帶v的用陣列
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<stdlib.h>
4
5 int main()
6 {
7
8 char * argv[]=
9 {
10 "ls",
11 "-a",
12 "-l",
13 NULL
14 };
15 execvp("ls",argv);
16 printf(".......\n");
17 }

這個和帶L的沒什么區別,其他的小伙伴可以自己試試,
事實上,只有execve是真正的系統呼叫,其它五個函式最終都呼叫 execve,所以execve在man手冊 第2節,其它函式在man手冊第3節,這些函式之間的關系如下圖所示,

exec小結
常用的4個:execl,execlp,exec.execp
exec系列函式我們不會自己呼叫,一般是fork子行程呼叫,父行程只需等待即可,一旦命令有問題不會波及到父行程,
本篇內容到這就結束了!由于博主水平有限,如有錯誤還請直接聯系博主,萬分感謝!!!

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/384254.html
標籤:其他
