1. 程式人生 > >程序的建立、等待、退出以及程式替換

程序的建立、等待、退出以及程式替換

文章目錄

1.程序建立

1)fork函式

fork函式從已經存在的程序中建立一個新程序,新程序為子程序,而原程序為父程序。
程序呼叫fork,當控制轉移到核心中的fork程式碼後核心。

 - 分配新的記憶體塊和核心資料結構給子程序
 - 將父程序部分資料結構內容拷貝到子程序
 - 新增子程序到系統程序列表中
 - fork返回,開始排程器排程

fork有兩個返回值,一個接受值(一父多子,一子一父)。

 子程序返回0。
 父程序返回子程序的PID。
 失敗返回-1。

父子程序程式碼共享,資料各自開闢空間,私有一份(採用寫時拷貝)。
fork之後通常用if進行分流,父子程序執行具有獨立性,由排程演算法決定。

#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
int main()
{
  int ret =fork();
  if(ret<0){
      perror("fork");
      return 1;
  }
  else if(ret == 0){//child
         printf("I am child : %d,ret : %d\n",getpid(),ret);
         }else{//father
          printf("I am parent : %d,ret : %d\n",getpid(),ret);
          }
          sleep(1);
          return 0;
 }

2)排程演算法瞭解

1.時間片輪轉排程演算法(RR) : 給每個程序固定的執行時間,根據程序到達的先後順序讓程序在單位時間片內執行,執行完成後便排程下一個程序執行,時間片輪轉排程不考慮程序等待時間和執行時間,屬於搶佔式排程。優點是兼顧長短作業;缺點是平均等待時間較長,上下文切換較費時。適用於分時系統。
先來先服務排程演算法(FCFS) :根據程序到達的先後順序執行程序,不考慮等待時間和執行時間,會產生飢餓現象。屬於非搶佔式排程,優點是公平,實現簡單;缺點是不利於短作業。
優先順序排程演算法 (HPF):在程序等待佇列中選擇優先順序最高的來執行。
多級反饋佇列排程演算法 :將時間片輪轉與優先順序排程相結合,把程序按優先順序分成不同的佇列,先按優先順序排程,優先順序相同的,按時間片輪轉。優點是兼顧長短作業,有較好的響應時間,可行性強,適用於各種作業環境。
高響應比優先排程演算法 :根據“響應比=(程序執行時間+程序等待時間)/ 程序執行時間”這個公式得到的響應比來進行排程。高響應比優先演算法在等待時間相同的情況下,作業執行的時間越短,響應比越高,滿足段任務優先,同時響應比會隨著等待時間增加而變大,優先順序會提高,能夠避免飢餓現象。優點是兼顧長短作業,缺點是計算響應比開銷大,適用於批處理系統。

3)fork的寫時拷貝

通常父子程式碼共享,父子不再寫入時,資料也是共享的,當任意一方試圖寫入,便以寫時拷貝方式各自一份副本。
在這裡插入圖片描述
寫時拷貝考慮因素

- 記憶體資源
 - 效能,更合理的使用空間

4)fork呼叫失敗原因

  • 系統中有太多的程序
  • 實際使用者的程序數量超過了限制

5)vfork函式

  • vfork也是用於建立子程序。而vfork之後的父子程序共享地址空間,也就是說他們除了PCB不一樣,別的都一樣,fork的子程序則具有獨立的地址空間。

  • vfork的子程序保證讓子程序先執行,在呼叫exec後父程序才可能被排程。

 #include<stdio.h>
   #include<sys/types.h>
   #include<unistd.h>
   #include<stdlib.h>
   
   int num=30;
   int main()
   {
      pid_t id=vfork();                                                                                                             
      if(id<0){
          perror("use fork");
          exit(1);
      }
      else if(id==0){//child
          sleep(4);
          num=80;
        printf("child:num=%d\n",num);
        exit(0);
    }
    else{//parent
        printf("parent:num=%d\n",num);
    }
   return 0;
 }

執行結果是:先等待4秒,然後先執行子程序,而子程序對資料修改為80,,父程序也是80,子程序直接改變了父程序的變數值,因為子程序在父程序的地址空間執行。

2.程序等待

1)程序等待的必要性

1.子程序退出,父程序如果不管不顧,就可能造成“殭屍狀態”,進而造成記憶體洩漏
2.程序一旦變成殭屍狀態,就刀匠不如,kill -9也無能為力
3.父程序派給子程序的任務完成的如何,我們需要知道,如:子程序執行完成,結果對還是不對,或者是否正常退出
4.父程序通過程序等待的方式,回收子程序,獲取子程序退出資訊。

2)程序等待的方法

1>wait

#include<sys/types.h>
#include<sys/wait.h>

pid_t wait(int status);
返回值:
    成功返回被等待程序pid,失敗返回-1.
引數:
   輸出型引數,獲取子程序退出狀態,不關心則可以設定成為NULL

程序呼叫wait後會阻塞自己,分析是否存在殭屍子程序,找到就銷燬並返回,沒找到就一直阻塞。wait只要有一個退出,父程序知道子程序id

2>waitpid

#include<sys/types.h>
#include<sys/wait.>

pid_t waitpid(pid_t pid,int  *status,int  options);d

3)返回值

當正常返回的時候waitpid返回收集到的子程序的程序ID;
如果設定了選項WNOHANG,而呼叫中waitpid發現沒有已經退出的子程序可收集,則返回0;
如果呼叫中出錯,則返回-1,這時error會被設定成相應的值以指示錯誤所在。

4)引數

1>pid

   pid>0,等待其程序ID與pid相等的子程序,只要等待的子程序還沒有結束,waitpid就會一直等下去。
   pid=-1,等待任一個子程序,與wait等效。
   pid=0,等待同一個程序組中的任何子程序,如果子程序已經加入到別的程序waitpid便不會再對它進行處理。
   pid<-1,等待一個指定程序組中的任何子程序,這個程序組的id等於pid的絕對值。

2>status

  1.WIFEXITED(status) 判斷子程序是否為正常退出的,如果是,它會返回一個非零值
  2.WEXITSTATUS(status) 當WIFEXITED返回非零值時,我們可以用這個巨集 來提取子程序的退出碼,如果子程序呼叫exit(25)退出,WEXITSTATUS(status)就會返回25。如果程序不是正常退出的,WIFEXITED返回0,這個值就無意義。

3>options

      WNOHANG(非阻塞 1):若pid指定的子程序沒有結束,則waitpid()函式返回0,不予以等待,若正常結束,則返回該子程序的ID。
      WUNTRACED(阻塞 0)

這是兩個常數,可以使用“|”把他們連起來使用,eg:waitpid(-1,NULL,WNOHANG|WUNTRACED);
如果不使用設為0就好,(-1,NULL,0);

阻塞與非阻塞理解:假如夢先生在等燒水,阻塞式表示他一直在等,子程序沒有退出,父程序就卡住;非阻塞式表示,他可以在等的過程中幹別的比如玩玩手機,聊聊天什麼,子程序沒退出,父程序可退出,不會卡住。

5)獲取子程序的status

  • wait和waitpid,都有一個status引數,該引數是一個輸出型引數,由作業系統填充
  • 如果傳遞NULL,表示不關心子程序的退出狀態資訊
  • 否則,作業系統會根據該引數,將子程序的子程序資訊反饋給父程序
  • status不能簡單的當做整型來看待,只有它的低16位被用作status,可以當做點陣圖來看待,具體細節如下:
    正常終止次2低8位(8-15)是退出碼,0~7是0。
    被訊號殺死,status的低7位是退出碼。

    在這裡插入圖片描述

6)wait使用

#include<stdio.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<errno.h>
#include<unistd.h>
#include<stdlib.h>

int main(){                                                                                                                                                             
    pid_t pid=fork();
    if(pid==-1){
        perror("use fork");
        exit(1);
    }   
    else if(pid==0){//child
        sleep(5);
        exit(10);
    }   
    else{//parent
        int st;//status
        int ret=wait(&st);
        if(ret>0&&(st&0X7F)==0)
            printf("child exit code is [%d]\n",(st>>8)&0XFF);
         else
            printf("sig code is[%d]\n",st&0X7F);
    }       
} 


開啟兩個終端,一個執行後,開啟另一個終端,殺死子程序,再執行一次。

7)waitpid的使用

阻塞式

#include<stdio.h>
#include<stdlib.h>
#include<sys/wait.h>
#include<unistd.h>
int main(){
    pid_t id=fork();                                                                                                                                                    
    if(id<0){
        perror("use fork");
        exit(1);
    }   
    else if(id==0){//child
        printf("child is run,pid is [%d]\n",getpid());
        sleep(5);
        exit(37);
    }   
    else{//parent
        int st;//status
        pid_t ret=waitpid(-1,&st,0);//0代表阻塞式
        printf("this is test for waitpid\n");
        if(WIFEXITED(st)&&ret==id)//WIFEXITED判斷是否正常返回
            printf("wait child 5s success,child return code is [%d]\n",WEXITSTATUS(st));
            //WEXITSTATUS巨集獲取退出碼
        else{
            printf("wait child failed,return.\n");
            return 1;
        }
    }
    return 0;
}

在這裡插入圖片描述

非阻塞式

#include<stdio.h>
#include<stdlib.h>
#include<sys/wait.h>
#include<unistd.h>

int main(){
    pid_t id=fork();
    if(id<0){
        perror("use fork");
        exit(1);
    }   
    else if(id==0){//child
        printf("child is run,pid is [%d]\n",getpid());
        sleep(5);
        exit(1);
    }   
    else{//parent
        int st;//status
        pid_t ret=0;
        do{ 
            ret=waitpid(-1,&st,WNOHANG);//WNOHANG是1,代表非阻塞式等待
            if(ret==0){
                printf("child is running\n");
            }   
            sleep(1);
        }while(ret==0);
        if(WIFEXITED(st)&&ret==id)
            printf("wait child 5s success,child return code is [%d]\n",WEXITSTATUS(st));
        else
            printf("wait child failed,return.\n");
            return 1;
    }   
    return 0;
} 

在這裡插入圖片描述

3.程序的程式替換

1)基本概念

用fork建立子程序後執行的是和父程序相同的程式,當然可以執行不同的分支(如果我們用fork建立一個子程序之後讓子程序做和父程序同樣的事,那麼這個子程序沒有任何意義)。
所以在fork之後,我們應該呼叫exec函式用來替換子程序的程式和資料,讓子程序執行和負程序不同的程式。當程序呼叫exec函式時,該程序的使用者空間得到程式碼和資料完全被新的程式替換。
呼叫exec並不建立新的程序,所以呼叫exec前後的程序的id並未改變。
在這裡插入圖片描述

2)替換函式

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 execvpe(const char *file, char *const argv[],char *const envp[]);

3)函式解釋

這些函式如果呼叫成功則載入新的程式從啟動端開始執行,不再返回。
如果出錯,返回-1.

4)命名理解

1.l:表示引數採用列表例如:execl("/bin/ls",“ls”,"-a",“NULL”)
2.v:引數用陣列
例如:
char* myargv[]={“ls”,"-a,“NULL”};
execv(“bin/ls”,myargv);
3.p:有p自動搜尋環境變數PATH
4.e:表示自己維護環境變數
在這裡插入圖片描述

5)使用

char * const argv[ ] ={ "ps","-ef",NULL);
char * const envp[ ]={"PATH=/bin:/usr/bin","TERM=console",NULL);

execl("/bin/ps","ps","-ef",NULL);


//帶P的,可以使用環境變數PATH,無需寫全路徑
execlp("ps","ps","-ef",NULL");

//帶e的自己組裝環境變數
execle("ps","ps","-ef",NULL,envp);
execv("/bin/ps",argv);
//帶P的,可以使用環境變數PATH,無需寫全路徑
execvp("ps",argv);
//帶e的自己組裝環境變數
execvp("/bin/ps",argv,envp);

注:這6個函式只有execve是真正的系統呼叫,其他5個函式都是在execve上包裝的,它們6個函式有以下的關係。

在這裡插入圖片描述

4.程序退出

1)程序退出場景

1.程式碼執行完畢,結果正確
2.程式碼執行完畢,結果不正確
3.程式碼異常終止

2)程序常見退出方法

a.正常退出

1.man函式返回
2.呼叫exit,
3._exit

b.異常退出

1.ctrl+c ,訊號終止

3)_exit與exit函式

a._exit函式

#include<unistd.h>
void _exit(int status);

引數status定義了程序的終止狀態,父程序通過wait獲取該值(0正常返回,1異常返回),雖然status是int,但是僅有8低8位可以被父程序所用,所以_exit(-1)時,在終端執行$?發現返回值為255.

b.exit函式

#include<unistd.h>
void _exit(int status);
  • exit與_exit函式的區別:exit重新整理快取區,_exit不重新整理。
  • return退出
    return退出是一種常見的程序退出方法,return m等同於exit(m),因為呼叫main的執行時函式會把main的返回值當做exit的引數。

5.setenv函式

作用:改變或增加環境變數
相關函式getenv,putenv,unsetenv,首先要說明的是,通過此函式並不能新增或修改shell程序的環境變數,或者說通過setenv函式設定的環境變數只在本程序,而且是本次執行中有效。如果在某一次執行程式時執行了setenv函式,程序終止再次執行該程式,上次的設定是無效的,上次設定的環境變數是不能讀到的。
引數value則為變數內容,引數overwrite用來決定是否要改變已存在的環境變數。註釋stdlib.h在Linux和windows中略不同,比如setenv函式是用在linux中的,在Windows中沒有setenv函式而用putenv來代替。
定義函式:int setenv(const char *name,const char *value,int overwrite);
函式說明setenv函式用來改變環境變數或增加環境變數的內容,引數name為環境變數名稱字串,引數value則為變數的內容,引數overwrite用來決定是否要改變已存在的環境變數。如果沒有此環境變數,則無論overwrite為何值均新增此環境變數。若此環境變數存在,overwrite不為0時,原內容會被改為引數value所指的變數內容,當overwrite為0時,則引數會被忽略。返回值執行成功則返回0,有錯誤發生時,返會-1

說明:通過此函式並不能新增或修改 shell 程序的環境變數,或者說通過setenv函式設定的環境變數只在本程序,而且是本次執行中有效。如果在某一次執行程式時執行了setenv函式,程序終止後再次執行該程式,上次的設定是無效的,上次設定的環境變數是不能讀到的。
語法setenv [變數名稱] [變數值]

6.export函式

1)作用

設定或顯示環境變數

2)說明

在shell中執行程式時,shell會提供一組環境變數。export可新增,修改或刪除環境變數,供後續執行的程式使用。export的效力僅及於該次登陸操作。

3)語法

export [-fnp] [變數名稱] = [變數設定值]

引數說明:

-f  代表[變數名稱]中為函式名稱。

-n  刪除指定的變數。變數實際上並未刪除,只是不會輸出到後續指令的執行環境中。

-p  列出所有的shell賦予程式的環境變數。