程序的建立、等待、退出以及程式替換
文章目錄
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賦予程式的環境變數。