天天看點

程序控制(程序建立、程序終止、程序等待、程序程式替換)

程序控制

    • 程序建立
      • 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庫維護的緩沖區完全在無感覺的情況下程序就終止了
           

繼續閱讀