進程創建時機
進程創建,是指作業系統創建一個新的進程。UNIX系統用fork()系統調用,而windows系統用CreatProcess()。進程創建的時機有:
(1)系統初始化。系統的調度進程創建init進程。
(2)執行中的進程調用了fork()系統函式。程式中有fork()函式。
(3)用戶登錄,用戶命令請求創建進程。例如:用戶雙擊一個圖示。
(4)一個批處理作業初始化。大型機、高性能計算機用戶提交一個課題,則系統建立作業控制塊,在作業調度後在系統記憶體中創建進程。
進程創建原語
進程藉助創建原語實現創建一個新進程。首先為被創建進程在進程表集中區建立一個PCB--UNIX系統還要為進程創建U區和記憶體映像,從進程表索取一個空白PCB表目,記錄它的下標;然後,把調用者提供的所有參數(見PCB塊的內容),作業系統分配給新進程的PID和調用者的PID,就緒狀態和CPU記賬數據填入該PCB塊;最後,把此PCB塊分別列置到就緒佇列RQ和進程隸屬關係族群中。
UNIX系統使用fork()函式創建新進程時,為子進程複製EP進程的記憶體映像並不是主要目標。這時,若用exec()執行一個新程式,則子進程的正文段將全部更換,而數據段也將更新。
創建原語可描述如下:
Procedurecreate(n,S0,K0,M0,R0,acc)
begin
i:=getinternalname(n);//進程表下標
i.id:=n;i.priority:=K0;//進程PID,進程優先權
i.CPUstate=S0;i.mainstore:=M0;//初始CPU狀態,記憶體地址
i.resources:=R0;i.status:=readys;//資源清單,就緒狀態
j:=EP;i.parent:=j;i.progeny:=φ;//父進程是EP進程,子進程空
j.progeny:=i;//進程隸屬關係
i.sdata=RQ;insert(RQ,i);//到就緒進程佇列排隊
continue
end
fork函式的程式設計方法
1.fork()系統調用格式
系統調用1:fork
#include<types.h>
#include
pid_tfork()
返回值:子進程返回0,父進程返回子進程ID,出錯返回-1。
功能:創建一個進程。
fork的三個返回值
2.fork程式設計
源程式
#include
#include
#include<types.h>
int main(intargc,char*argv[])
{
intvalue=5;
pid_tpid;
pid=fork();//fork一個子進程
if(pid<0){//返回值小於0
printf(“forkfailed\n”);
exit(-1);
}
elseif(pid==0){//子進程
value+=15;
printf(“\nchildprocesspid=%d”,getpid());
printf(“\nvalue=%d”,value);
exit(0);
}
elseif(pid>0){//EP進程(parentprocess)
value+=5;
pirntf(“\nvalue=%d”,value);
printf(“\EPprocesspid=%d”,getpid());
printf(“\nvalue=%d”,value);
exit(0);
}
}
在程式設計中,fork()的套用最重要的是:掌握利用對子進程和EP進程的兩個不同返回值的方法。經典方式是使用 if語句,if((pid=fork())==0)是子進程完成任務的編程範圍,應編寫與子進程功能相關的語句;而elseif(pid>0)則是EP進程(parentprocess)完成任務的語句範圍。在以上程式中,兩個進程完成的功能相同,然而在大多數情況,它們的功能不同,這也是使用fork()的原因之一。應注意一個函式同時有不同返回值的問題,這是和以前的編程經驗不同的地方。
為了避免作業系統崩潰,pid<0是必須考慮的情況,創建進程失敗稱為異常。在UNIX系統程式中,不能有考慮不到的異常情況,這是編程能力的高低區別。
新創建的子進程和EP進程有各相互獨立的數據段,EP進程和子進程對同一個變數所做的任何改變都是獨立的,不會放映到另一個進程的存儲器中。因此,value在子進程中的值為20,而在EP進程中的值則為10。變數value在不同的進程中有不同的值,充分說明儘管子進程是複製EP進程而來,有相同的正文段,然而,它們的數據段各自獨立。EP進程不能訪問子進程的數據段,所以它的第一個printf()語句輸出的value值為5。
UNIXV6fork()原始碼剖析
1.數據結構proc和user
UNIXV6採用進程控制塊PCB和U區管理和表示一個進程控制信息。PCB塊使用結構proc表示,稱為進程基本控制塊,而U區使用結構user表示稱為進程擴充控制塊。進程控制信息分為兩部分的原因是:結構proc常駐記憶體,管理經常被作業系統核心訪問的那部分信息;而結構user管理進程分配的資源,包括打開的檔案或目錄等信息,有可能被移至外存交換空間。由於作業系統核心只需要當前執行進程的user,因此當某一進程被換出至外存時,對應的U區被移至交換空間。這是早年計算機記憶體容量緊張造成的。
PCB塊原本是進程控制塊的統稱,但是常用來表示proc,所以今後PCB塊都表示進程基本控制塊。
一.PCB塊
#include
#define NPROC50
structproc
{
charp_stat;//狀態。CPU相關
SRUN:可執行。執行和就緒
SIDL:進程生成中。fork()
SSLEEP:高阻塞
SWAIT:低阻塞
SSTOP:trace
SZOME:終止還沒回收
charp_flag;//標誌。記憶體相關(是否調到外存)
SSYS:系統進程。procpid=0
init進程是proc,並不是系統進程
SLOAD:在記憶體中(可執行)
SLOCK:進程上鎖不被調出
SSWAP:進程在外存
r5,r6
STRC:跟蹤狀態
SWTED:在被跟蹤時使用
charp_pri;//進程優先權。可變。
charp_sig;//進程接收到的信號,軟中斷信號。
//記錄其它進程發來的信號
charp_uid;//進程所屬用戶UID。哪一個用戶登錄。
charp_time;//進程的存在時間。
//給出進程的執行時間和對資源的利用情況。
當剛交換到記憶體或外存交換區時,p_time=0。
charp_cpu;//CPU累計時間,在CPU上運行的時間。
charp_nice;//用戶調整優先權的值
//偏置值nice,固定值。用戶的許可權。
intp_ttyp://發出進程的終端號,uid所在終端。與信號相關。
intp_pid;//進程PID
intP_ppid;//父進程PID,創建進程的PID。
//當父進程退出,則子進程的父進程可為init進程。
intp_addr;//數據段的物理地址。進程PPDA和U區的地址
intp_size;//數據段長度。可交換映像大小
intp_wchan;//阻塞原因。
//事件描述符,記錄使進程進入阻塞狀態的原因。
可為系統資源,例如:記憶體緩衝區。
int*p_textp;//代碼段。源程式編譯的執行檔在text[]中的地址。
}proc[NPROC];
proc[NPROC]數組是進程表,NPROC規定了UNIX作業系統允許擁有的最多進程數。結構proc包含15個成員。PCB塊的proc.p_stat和proc.p_flag常組合使用。根據PCB塊的描述,它們分為七類:
1.進程標識符proc.p_pid它等於全局變數mpid。mpid的變化範圍是0~(215-1)。儘管很長時間不會重複,然而當mpid是最大值是,它將又一次從0開始計數。應注意mpid與NPROC不同。
2.進程狀態proc.p_stat和進程標誌proc.p_flag
與進程能否被調度在CPU上運行密切相關,因此進程狀態又稱為進程調度狀態。進程的各種調度狀態可依據一定的原因和條件變化。一個已存在系統中的進程不斷在這些狀態中變化。
UNIXV6使用進程調入調出系統,並不是現在的虛擬記憶體管理系統。進程調入調出是完整的記憶體映像調出,包括PPDA。進程調出的標誌並不是代碼段調出到外存,而是PPDA調出到外存。Perprocessdataarea(PPDA)由進程的U區和核心棧區域構成,在數據段的首部,有1kB長度。
當EP進程生成子進程時,狀態proc.p_stat=SIDL,而且當時它不可能被調出記憶體,所以標誌proc.p_flag=SLOCK。因此,=。就緒進程很少被調出記憶體,因此大多數情況=。
3.CPU運行信息和進程優先數進程優先數動態變化,相關參數有三項。第一項proc.p_pri,值越小優先權越高。它是處理機調度的主要依據。數值變化範圍-100~127,進程調度的優先權不能有很豐富的變動。第二項proc.p_cpu反映了進程使用處理機的程度。proc.p_cpu值越大表示進程使用CPU的時間越長,因此被調度的可能性就越小。它是UNIX作業系統計算p_pri的一個主要參考數據。第三項是proc.p_nice計算進程優先數時所用的一個偏置值。在三項中,是用戶唯一能設定的一個值。
UNIX作業系統的優先權調度算法見第1.4節。
4.進程的記憶體映像地址表示進程圖像最近一次調入調出後,在記憶體或外存交換區的時間。這是0#進程在內、外存之間傳送繼承的一個主要依據。proc.p_addr不僅是數據段,而且是棧區域和PPDA的物理地址。它們同在數據段中,根據APR頁表和它們的區域長度,能計算出實際的物理地址。因此,不能說PCB塊中沒有給出棧區域的物理地址。proc.p_size=數據區域長度+棧區域長度+PPDA長度。根據proc.p_addr,proc.p_textp,proc.p_size能找到進程的記憶體圖像,若進程被調出到外存則proc.p_addr是進程數據段在外存的地址。
7.進程的組織隸屬關係用戶標識符proc.p_uid保存在一份用戶花名冊檔案中/etc/passwd,每一個合法用戶在該檔案中都有一個記錄,格式為:
loginname:password:uid:gid::loginworkingdir:shell
loginname是用戶進入系統時使用的登錄名;password是密碼形式的用戶口令;uid、gid是高級用戶或者系統管理員分配給該用戶的標識符和所在組號(0~255);最後兩項分別是用戶工作目錄和作業系統提供給用戶的命令程式。系統管理員的proc.p_uid=0。
proc.p_ppid是進程的父進程標識符,在進程樹中,除了0#進程以外,其它進程都是父進程要求生成的,因此都不是系統進程。
二、U區
#include
structuser
{
intu_rsav;//進程切換時保存暫存器r5,r6的值(數據段)
intu_fsav;//處理器為PDP-11/40時不用
charu_segflg;//讀寫檔案時使用的標誌變數
charu_error;//出錯時用來保存錯誤代碼
charu_uid;//實效用戶標識符
charu_gid;//實效組
charu_ruid;//實效用戶
charu_rgid;//實際組
int*u_procp;//U區對應的PCB塊
char*u_base;//讀寫檔案時用於傳遞參數,起始地址
char*u_count;//讀寫檔案時傳遞參數,長度
char*u_offset;//讀寫檔案時傳遞參數,活動偏移量
int*u_cdir;//當前目錄對應的數組inode[]的元素
charu_dbuf[DIRSIZ];//namei()
char*u_dirp;
struct{
intu_ino;
charu_name[DIRSIZ];
}u_dent;
int*u_pdir;
intu_uisa;
intu_uisd;
intu_ofile[NOFILE];//進程打開的檔案。外部設備是設備檔案/dev
intu_arg;
intu_tsize;//代碼段長度
intu_dsize;//數據區域長度
intu_ssize;//棧區域長度
intu_sep;
intu_qsav;//處理信號時保存r5,r6當前值
intu_ssav;//進程調出時保存r5,r6當前值
intu_signal[NSIG];
intu_utime;
intu_stime;
intu_cutime;
intu_cstime;
intu_ar0;//系統調用,操作通用暫存器或PSW時使用
intu_prof;
charu_intflg;
}u;
.globl–u
-u=140000
作業系統通過全局變數u訪問執行進程的數據段。全局變數u的地址0140000是八進制數,高3位是110,為6。所以APR頁表的第6頁面被選擇,而低位全部是0,所以u指向作業系統核心空間第6頁的起始地址。所以作業系統通過核心空間第6頁的起始地址找到執行進程數據段的位置,用proc.p_addr標識。
2.fork()源程式分析
#include
#includeken.h>
fork()
{
registerstructproc*p1,*p2;
p1=u.u_procp;//執行進程u是全局變數
for(p2=≺oc;p2<≺oc[NPROC];p2++)//proc[]={NULL,proc[i]}
if(p2->p_stat==NULL)
gotofound
u.u_error=EAGAID;//進程數量超過系統規定
gotoout;
found://若進程表有空白PCB塊
if(newproc()){//newproc()向EP進程返回0,向新進程返回1
u.u_ar0[R0]=p1->p_pid;//EP進程進程號
u.u_cstime=0;u.u_cstime=0;//設定CPU時間
u.u_stime=0;
u.u_cutime=0;u.u_cutime=0;
u.u_utime=0;
return;
}
u.u_ar0[R0]=p2->p_pid;
//fork()對EP進程的返回值是新進程的PID,存放在u.u_ar0[R0]
out:
u.u_ar0[R7]=+2;//指向EP進程的下一條指令地址
}
newproc()//創建新進程的函式
{
inta1,a2;
structproc*p;*up;
registerstructproc*rpp;
register*rip,n;
p=NULL;
retry:
mpid++;//mpid的值mpid={0}
if(mpid<0){//若mpid分配結束,則從0開始分配
mpid=0;
gotoretry;
}
for(rpp=≺oc;rpp<≺oc[NPROC];rpp++)
if(rpp->p_stat==NULL&p=NULL)//p_stat={NULL,pid}
p=rpp;//若有空白PCB塊,記錄在p中
if(rpp->p_pid==mpid)//若mpid已經分配
gotoretry;//重新分配mpid
}//總結進程表的三種情況proc[]={full,,null*}
If((rpp=p)==NULL)
panic(“noprocs”);
rip=u.u_procp;//賦值新進程的proc。Rip執行進程
up=rip;//保存EP進程的PCB塊
rpp->p_stat=SRUN;//新進程的狀態SRUN就緒
rpp->p_flag=SLOAD;//新進程在記憶體中SLOAD
rpp->p_uid=rip->p_uid;//複製EP進程PCB塊中的值
rpp->p_ttyp=rip->p_ttyp;
rpp->p_nice=rip->p_nice;
rpp->p_textp=rip->p_textp;//新的進程複製EP進程正文段
rpp->p_pid=mpid;//新進程PID=mpid
rpp->p_ppid=rip->p_pid;//新進程的父進程是EP進程
rpp->p_time=0;//CPU執行時間為0
for(rip=&u.u_ofile;rip<&ofile[NOFILE];rip++)
//新進程複製EP進程分配的系統資源,是特殊檔案。
if((rpp=*rip++)!=NULL)
rpp->f_count++;
if((rpp=up->p_textp)!=NULL){//複製共享代碼段
rpp->x_count++;//text[]計數器增加1
rpp->x_ccount++;
}
u.u_cdir->i_count++;//目錄inode節點計算器增加1
savu(u.u_rsav);//執行進程切換時,保存EP進程的r5,r6到U區
rpp=p;
u.u_procp=rpp;//執行繼承u是新的進程newproc
rip=up;
n=rip->p_size;//複製數據段長度
a1=rip->p_addr;//”原”EP進程數據段地址
rpp->p_size=n;
a2=malloc(coremap,n);//為新進程申請記憶體區域
if(a2==NULL){//若申請的記憶體區域為空,a2={null,address}
rip->p_stat=SIDL;//EP進程狀態為SIDL:創建進程中
rpp->p.addr=a1;//新進程數據段地址=EP進程數據段地址
savu(u.u_ssav);//新的進程調出到外存時保存r5,r6到U區
xswap(rpp,0,0);//複製新進程的數據段到外存交換空間
rpp->p_flag=|SSWAP;//新進程標誌:在外存
rip->p_stat=SRUN;//EP進程狀態還原為SRUN
}else{//申請的記憶體區域不為空,在記憶體複製數據段
rpp->p_addr=a2;//新進程的數據段地址是申請到的記憶體地址
while(n--)copyseg(a1++,a2--);//複製EP進程的數據段
}
u.u_procp=rip;//執行進程u是EP進程
return(0);//向fork()返回0
}
注意:newproc通過swtch()切換執行,swtch()返回值為1,newproc獲得返回值1。