1.進程的定義
在討論進程定義之前,我們可以先來了解一下計算機操作系統爲什麼要引入進程這個概念。
1.1 進程的引入
當計算機系統只有一個程序運行時,稱之爲單道程序,此時這個程序獨佔系統中的所有資源,在執行的過程中不受外界的影響;而多道程序在執行時,就是所謂的程序併發執行,即若干個程序同時在系統中執行,這時,這些程序就不可能獨佔所有的系統資源了,而需要多個程序共享系統的資源,從而導致各個程序在執行時出現相互制約的關係。爲了刻畫系統內部出現的這種動態情況,描述程序併發執行的活動規律,操作系統就引入了進程這個概念,進程的出現是爲了使多個程序併發執行,用以改善資源利用率,並且提高系統的吞吐量。
1.2 進程的定義
進程是一個“執行中的程序”,即程序在處理機上執行時所發生的活動。
由上述簡單的定義可以發現,進程與程序有着密不可分的關係,運行中的程序在內存中的映像就叫做進程。
1.3查看進程信息
在Windows系統下,可以通過任務管理器來查看系統中的進程信息。
在Linux系統下,可以通過命令 “ps -aux”來查看系統中的進程信息。如下圖所示:
2.進程的基本操作
2.1 進程創建
在Linux下,提供了好幾個關於創建進程的操作函數,如fork(), vfork()
(1)fork()函數
該函數的功能是創建一個進程,新進程爲當前進程的子進程,當前進程就被稱爲父進程。該函數的調用形式爲:pid_t fork(void);使用該函數要引用頭文件<sys/types.h>和<unistd.h>頭文件,函數返回值類型爲pid_t,表示一個非負整數。若程序運行在父進程中,函數返回的PID爲子進程的進程號;若運行在子進程中,返回的PID爲0.若創建子進程失敗,則會返回-1.
程序代碼如下:
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
pid_t pid;
pid = fork();
if(pid <0)
{
printf("fork error!\n");
exit(1);
}
else if(pid == 0)
{
printf("in the child process!\n");
}
else
{
printf("in the parent process!\n");
}
exit(0);
}
從程序的結果可以看出fork()函數的一個特點,即“調用一次,返回兩次”,這又是爲什麼呢?
原來,在一個程序中,調用fork()函數後,就出現了分叉。在子進程中,fork()函數返回0;在父進程中,fork()函數返回子進程的PID。但是,調用fork()之後,誰先執行完全由調度器決定。
(2)vfork()函數
與fork()函數相同,這兩個函數都是系統調用函數。而兩者的區別是在創建子進程時fork()函數會複製父進程的所有資源,包括進程環境、內存資源等。而vfork()函數在創建子進程時並不會複製父進程的所有資源,父子進程共享地址空間。這樣,在子進程中對虛擬地址空間中的變量修改,實際上是在修改父進程虛擬地址空間中的值。
以下是個實例,演示兩個函數的區別。
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int gvar = 2;
int main()
{
pid_t pid;
int var = 5 ;
printf("process id: %ld\n", (long)getpid());
printf("gvar = %d var = %d\n",gvar, var);
pid = vfork();
if(pid<0)
{
perror("error!\n");
return 1;
}
else if(pid ==0)
{
gvar--;
var++;
printf("the child process id: %ld\ngvar = %d var=%d\n",(long)getpid(),gvar,var);
_exit(0);
}
else
{
printf("the parent process id: %ld\ngvar = %d var=%d\n",(long)getpid(),gvar,var);
return 0;
}
}
結果如下:
由運行結果可以看出,父進程中輸入的變量值也是在子進程中變化後的值。由此可知,調用vfrok()函數改變子進程中的值,其實就是改變父進程中的值。
2.進程等待
進程等待就是爲了同步父進程與子進程,通常需要通過wait()函數使父進程等待子進程結束。如果父進程沒有調用等待函數,子進程就會進入“殭屍(Zombie)”狀態。
Linux提供的等待函數原型如下:
#include<sys/types.h>
#include<sys/wait.h>
pid_t wait(int* status);
pid_t waitpid(pid_t pid,int* status, int options);
wait()函數系統調用的工作過程是:首先判斷子進程是否存在,即是否成功創建了一個子進程。如果創建失敗,子進程不存在,則會直接退出進程,並且提示相關錯誤信息;如果創建成功,那麼wait()函數會將父進程掛起,直到子進程結束,並且返回結束時的狀態和最後結束的子進程的PID。
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
/*定義一個功能函數,通過返回的狀態,判斷進程是正常退出還是信號導致的退出*/
void exit_s(int status)
{
if(WIFEXITED(status))
{
printf("正常退出,status=%d\n",WEXITSTATUS(status));
}
else if(WIFSIGNALED(status))
{
printf("信號退出,status=%d\n",WTERMSIG(status));
}
}
int main()
{
pid_t pid, pid1;
int status;
//創建一個子進程
if((pid=fork())<0)
{
printf("child process error!\n");
exit(0);
}
else if(pid ==0) //子進程
{
printf("the child process!\n");
exit(2); //調用exit()退出函數正常退出
}
if(wait(&status)!=pid) //在父進程中調用wait()函數等待子進程結束
{
printf("This is a parent process!\nwait error!\n");
exit(0);
}
exit_s(status); //wait()函數調用成功,調用自定義的功能函數來判斷退出類型
/*又一次創建子進程,在子進程中,使用kill()函數發送信號,導致退出*/
if((pid=fork()) <0)
{
printf("child process error!\n");
exit(0);
}
else if(pid == 0)
{
printf("the child process!\n");
pid1 = getpid();
/*使用kill()函數發送信號*/
kill(pid1,19);
}
if(wait(&status)!=pid1)
{
printf("This is a parent process!\nwait error!\n");
exit(0);
}
exit_s(status);
exit(0);
}
提示:在Linux系統的終端輸入“kill -l”命令,可以列出信號的具體情況、信號類型和其所對應的數字。
關於進程等待函數,還有一個常用的等待函數waitpid(),該函數實現的功能與wait()函數相同,但它們區別在於:wait()函數用於等待所有子進程的結束,而waitpid()函數僅用於等待某個特定進程的結束,這個特定的進程是指其pid與函數中的參數pid相關時。所謂的相關,有如下幾種可能:
(1) pid=-1,等待任一個子進程,與wait()等效
(2)pid>0, 等待其進程ID與pid相等的子進程。
3.進程結束
當想要終止或者結束一個進程時,會使用系統調用exit()函數正常退出進程。該系統調用包括exit()和_exit()兩個函數。
1.exit()函數
原型爲:
#include <stdlib.h>
void exit(int status);
注意:該函數調用成功與失敗都沒有返回值,並且沒有出錯信息的提示。
exit()函數的作用是終止進程,並將運算status&0377表達式後的值返回給父進程,在父進程中通過wait()函數來獲得。
2._exit()函數
原型爲:
#include <stdlib.h>
void exit(int status);
同上,無論成功與否,都沒有返回值。
在之前的進程創建時,vfork()函數創建的子進程在退出時只能使用_exit()函數退出進程,而不能使用exit()函數來退出。這是因爲在調用exit()函數時,會對輸入/輸出流進行刷新,釋放所佔用的資源以及清空緩衝區等;而 _exit()函數則不具備刷新緩衝區等操作的功能。