程序控制
-
- 程序建立
-
- fork函數
- vfork函數
- 程序終止
-
- 程序終止的場景
- 程式終止的方法
-
- 從 main 函數的 return 傳回
- exit函數--庫函數
- _exit函數--系統調用函數
- exit 和 _exit 的差別
- 執行使用者定義的清理函數
- 程序等待
-
- 程序等待的作用
- 程序等待的接口
-
- wait 函數
-
- 如何擷取程序退出碼?
- 如何擷取終止信号?
- 如何擷取coredump?
- waitpid 函數
- 程序等待的非阻塞模式
- 程序程式替換
-
- 作用
- 原理
- 應用場景
- 接口
-
- exec函數簇,這是一堆的接口
-
- execl
- execlp
- execle
- execv
程序建立
fork函數
子程序是拷貝父程序的PCB的,子程序的大部分資料是來源書父程序,例如:記憶體指針(資料段、代碼段)
父程序建立子程序成功之後,父子程序是獨立的兩個程序(程序的獨立性),父子程序的排程取決于作業系統的核心
程序是搶占式執行的,父子程序誰先運作是不能确定的
寫時拷貝
fork調用失敗的原因:
系統有太多的程序
實際使用者的程序數超過了限制
vfork函數
調用棧混亂的問題的解決:子程序先調用,子程序調用完畢後父程序再調用
程序終止
程序終止的場景
從 main 函數的 return 傳回
能得到的結果:
代碼執行完畢,沒有獲得既定的結果
代碼執行完畢,獲得了既定的結果
程式崩潰
程式終止的方法
從 main 函數的 return 傳回
寫一個程式,本身可以直接 return 結束,但是為了看的更清楚,是以列印一下程序号
使用 getpid 函數需要調用<unistd.h>頭檔案
運作後發現列印了一個程序号,即剛才的程序的程序号
exit函數–庫函數
void exit(int status);
status會被程序等待接口的參數擷取到
還是剛才的程式,這次使用 exit 函數
使用 exit 函數需要調用<stdlib.h>頭檔案
跑起來看看,确實是沒有走到列印
此時可以通過 echo $? 來檢視退出碼
也就是剛剛在代碼中寫的 exit(10);
_exit函數–系統調用函數
void _exit(int status);
接下來嘗試一下 _exit 函數
更改的地方很簡單,目的也是一樣,不想讓程式走到列印那一步,運作看看
發現 _exit 也達成了希望的目标
exit 和 _exit 的差別
對剛才的代碼做一些更改,首先是 exit 函數
此時希望的是能夠列印出“oh ho”,而不列印程序号
完成了目标,再此修改程式,使用 _exit 函數
再運作代碼
???
東西咧???
但是如果加上一個 \n 呢?
\n:想不到吧,我才是那個關鍵點哒
可能都沒發現删掉了這個吧
現在是 exit 函數,運作試試
顯而易見的成功了,那接着換成 _exit 函數
運作一下
發現這次成功了
原因在于,當我們使用 printf 函數列印時,資料是放在緩沖區中的,而使用 \n 會重新整理緩沖區,當使用 _exit 函數時,由于是系統調用函數,是以不會重新整理緩沖區
執行使用者定義的清理函數
int atexit(void (*function)(void));
繼續剛才的代碼
那麼此時出現了兩種情況
①代碼走到了 atexit 函數後進入 func 函數,列印後退出 func,再列印 oh ho,exit函數退出
②代碼先列印 oh ho,在進行到exit函數時進入到 atexit 中列印 func 函數
是以到底是哪種情況?
還是直接運作來看看吧
看來是第二種情況了
再次修改代碼
通過這次的修改,代碼在走到列印 oh ho 結束後進入死循環,那此時會怎樣輸出?
看起來連輸出都沒有輸出
atexit 函數就是在C庫中注冊一個函數,舉個例子就像是我們訂了一個鬧鈴,但是在到時間之前根本不會響,而 exit 函數就相當于鬧鈴響的時間,隻有當代碼運作到 exit 函數時才會使 atexit 在C庫中注冊的函數展現出來,即這是一個回調函數
程序等待
程序等待的作用
父程序進行程序等待,子程序在先于父程序退出之後,由于父程序在等待子程序,是以父程序會回收子程序的退出資源,進而防止子程序産生僵屍程序
程序等待的接口
wait 函數
pid_t wait(int *status);
傳回值:
成功:傳回子程序的 pid 号
失敗:-1
參數:整形指針,int*,占用4個位元組
處于高位的兩個位元組沒有被使用,低位的兩個位元組中高位的一個位元組用于存放程序退出碼,低位位元組中一個bit位用于存放coredump标志符,剩下的7bit位用于存放終止信号
coredump标志位:
1:如果取值為1,則表示當中的程序是由coredump(核心轉儲檔案)産生
0:如果取值為0,則表示目前程序沒有coredump産生
終止信号:目前程式是由什麼終止的
參數 int* status:是一個出參,調用者準備一個 int 類型的變量,将位址傳遞給 wait 函數,wait 函數在自己實作的内部進行指派,當 wait 函數傳回之後,調用者就可以通過 int 變量,擷取程序退出的資訊
使用 wait 函數時需要包含一個名為 <sys/wait.h> 的頭檔案
讓代碼運作起來看看
可以看到已經輸出了子程序和父程序的 pid 号
給父程序加一個死循環,再試着檢視他們的程序
再去查查子程序的 pid,看看是不是僵屍程序
現在再在代碼中删除 wait
可以看到産生了僵屍程序
如何擷取程序退出碼?
(status >> 8) & 0xff
如何擷取終止信号?
status & 0x7f
如何擷取coredump?
(status >> 7) & 0x1
拿到了程序退出符
再看看異常的情況
此時根本不會退出,直接就崩潰了
可以看到也輸出了
waitpid 函數
pid_t waitpid(pid_t pid, int *status, int options);
pid:
-1:等待任一子程序,一旦等待到了,則傳回
>0:等待特定的子程序,大于0的值就是子程序的 pid 号
status:出參,同 wait 函數
options:
WNOHANG:非阻塞等待方式--需要搭配循環去使用,直到完成函數功能
0:阻塞等待方式
程序等待的非阻塞模式
第一步,需要建立子程序,并模拟讓子程序先于父程序退出
第二步,在父程序的邏輯中執行程序等待,同時使用非阻塞的等待方式
代碼如下
#include <stdio.h>
2 #include <stdlib.h>
3 #include <unistd.h>
4 #include <sys/wait.h>
5
6 int main()
7 {
8 pid_t pid = fork();
9 if(pid < 0)
10 {
11 perror("fork file");
12 return 0;
13 }
14 else if(pid == 0)
15 {
16 //child
17 printf("i am child, my pid is %d\n, ppid is %d\n",getpid(), getppid());
18 sleep(5);
19 exit(1);
20 }
21 else
22 {
23 //father
24 //當沒又等待到相應的子程序,waitpid的傳回值為0;
25 //當等待到了相應的子程序,waitpid的傳回值為等待到的
26 //程序的pid号,即大于0
27 pid_t ret = 0;
28 do
29 {
30 ret = waitpid(-1,NULL,WNOHANG);
31 if(ret == 0)//輸出child的情況
32 {printf("child is running\n");}
33 }
34 while(ret == 0);
35
36 sleep(20);
37 printf("i am father, my pid is %d, exit...\n", getpid());
38 }
39 }
随後建立Makefile檔案後運作,同時檢視程序,發現存在兩個程序
再看一次,發現隻剩下一個程序了
更改一下代碼,使父程序能擷取子程序的pid随後輸出
再對比一下
程序程式替換
作用
将正在運作的程序替換為另一個程式的代碼
原理
①程序程式替換就是将程序的代碼段、資料段替換成為新的程式的低嗎和資料
②更新堆棧資訊
應用場景
①守護程序(本質上是為了讓服務不間斷,但是當子程序由于代碼崩潰退出後,雖然父程序會立即重新啟動一個子程序,讓子程序進行程式替換,我們一定要知道,導緻子程序退出的原因是崩潰,而崩潰并沒有被修複,是一種治标不治本的行為)
②bash(指令行解釋器)
接口
exec函數簇,這是一堆的接口
execl
path:并不是單純的路徑,而是 路徑+可執行程式名稱
可以總結為帶路徑的可執行程式
arg:給待要替換的可執行程式傳遞的參數
規定:
1、第一個參數是可執行程式本身
2、多個可執行參數,使用“,”進行間隔
3、參數的最後需要以NULL結尾
…:可變參數清單
使用which指令可以檢視指令的位置
傳回值:替換成功後,該函數是沒有傳回值的,也不知道傳回值應傳回給誰,隻有替換失敗之後才會有傳回值,傳回值 < 0
由上述傳回值來看,第一個 printf 可以列印出來,但是第二個 printf 無法列印
建立Makefile檔案後運作
已經顯示出了我們想要的内容
程序替換成功後,程序的PID是否發生了變化?答案是沒有,但是不好驗證
execlp
參數:
file:隻需要傳遞待替換的可執行程式
注意: 1、如果隻傳遞可執行程式的名稱,則該可執行程式一定要在環境變量PATH中可以找到
2、也可以将待替換的可執行程式的路徑寫全
3、函數名中帶有‘p’說明目前的函數會自動搜尋環境變量PATH
arg:同execl
替換也成功了
execle
path:帶有路徑的可執行程式
arg:同 execl
envp[]:指針數組,本質是數組,數組的每一個元素都是一個char*,程式員需要自己組織環境變量,發到envp[]數組當中,最後一個元素需要放入NULL
函數名稱當中帶有 ‘e’ 則表示目前的exec函數需要自己組織環境變量,放到envp指針數組當中,以NULL結尾
函數名稱當中帶有 ‘l’ 則表示給待替換的可執行程式,傳遞的指令行參數為可變參數清單
需要引用一下
替換成功了
execv
path:帶有路徑的可執行程式
argv[]:指針數組,指令行參數的數組,存放的是傳遞給可執行程式的指令行參數
規則: 1、第一個參數是可執行程式本身
2、參數的最後需要以NULL結尾
7.8更新
_exit()函數之是以不會重新整理緩沖區,是因為_exit函數是核心代碼,直接操作核心結束了程序,而此時不會通知上層C庫,
是以,C庫維護的緩沖區完全在無感覺的情況下程序就終止了