進程標識
每個進程都有一個非負整數表示的唯一進程ID。常將其用作其他標識符的一部分以保證其唯一性。例如:應用程序有時就把進程ID作爲名字的一部分來創建一個唯一的文件名。
系統中有一些專用進程,但具體細節隨實現而不同。ID 0的進程通常是調度進程,常常被稱爲交換進程(swapper)。該進程是內核的一部分,他不執行任何磁盤上的程序,所以稱爲系統進程。進程ID 1通常是init進程…
每個UNIX系統實現都有他自己的一套提供操作系統服務的內核進程,例如:某些UNIX的虛擬存儲器實現中,進程ID 2是頁守護進程,負責支持虛擬存儲器系統的分頁操作。
除了進程ID,每個進程還有一些其他的標識符,下列函數返回這些標識符。
include<unistd.h>
pid_t getpid(void); //返回值:調用進程的進程ID
pid_t getppid(void); //返回值:調用進程的父進程ID
uid_t getuid(void); //返回值:調用進程的實際用戶ID
uid_t geteuid(void); //返回值:調用進程的有效用戶ID
gid_t getgid(void); //返回值:調用進程的實際組ID
gid_t getegid(void); //返回值:調用進程的有效組ID
注意:這些函數都沒有出錯返回。
函數fork
#include<unistd.h>
pid_t fork(void);
功能:一個現有的進程可以通過fork函數創建新進程
返回值:子進程返回0,父進程返回子進程ID;若出錯,返回-1
注意:子進程是父進程的副本。子進程獲得父進程的數據空間、堆、棧、I/O流(共享文件指針和文件描述符)、緩衝區的拷貝等,與父進程共享代碼段。
int main(void)
{
int i=0;
for(i=0;i<3;i++){
pid_t fpid=fork();
if(fpid==0)
printf("son/n");
else
printf("father/n");
}
return 0;
}
這裏就不做詳細的解釋了,只做一個大概分析。
fork通常有以下兩種用法:
1、一個父進程希望複製自己,使父進程和子進程同時執行不同的代碼段。這在網絡服務進程中是常見的
——父進程等待客戶端的服務請求。當這種請求到達時,父進程調用fork,使子進程處理該請求。父進程則繼續等待下一個服務請求。
2、一個進程要執行一個不同的程序。這對shell是常見的情況。在這種情況下,子進程從fork返回後立即調用exec。
vfork函數
vfork函數與fork函數功能基本一致。
區別:
1、通過vfork創建的進程不復制父進程的地址空間(數據段、BSS段、堆、棧、I/O流(共享文件指針和文件描述符)、緩衝區),因爲子進程會立即調用exec。
2、vfork保證子進程先運行,在它調用exec或exit之後父進程才能被調度運行,當子進程調用這兩個函數中的任意一個時,父進程會恢復運行。
exec系列函數
int execl(const char *path, const char *arg, ...);
int execv(const char *path, char *const argv[]);
int execle(const char *path, const char *arg, ...,char * const envp[]);
int execve(const char *path, char *const argv[],char *const envp[]);
int execlp(const char *file, const char *arg, ...);
int execvp(const char *file, char *const argv[]);
int fexecve(int fd, char *const argv[],char *const envp[]);
這些函數之間的第一個區別是前四個函數取路徑名作爲參數,後兩個函數取文件名作爲參數,最後一個取文件描述符作爲參數。
在很多UNIX實現中,這7個函數只有execve是內核系統調用。另外六個是庫函數,他們最終都要調用該系統調用。這七個函數之間的關係如下圖
exec函數實例:
#include<stdio.h>
#include<sys/wait.h>
#include<unistd.h>
char* env_init[] = {"USER=unknow","PATH=/temp",NULL};
int main()
{
pid_t pid;
if(pid=fork()<0)
{
err_sys("fork error");
}
else if(pid==0)
{
if(execle("/home/sar/bin/echoall","echoall","myarg1","MY ARG2",(char *)0,env_init)<0)
{
err_sys("execle error");
}
if(waitpid(pid,NULL,0)<0)
{
err_sys("wait error");
}
if(pid=fork()<0)
{
err_sys("fork error");
}else if(pid==0)
{
if(execlp("echoall","echoall","only 1 arg",(char *)0)<0)
{
err_sys("execlp error");
}
}
}
exit(0);
}
函數wait和waitpid
當一個進程正常或異常終止時,內核就向其父親發送SIGCHLD信號。因爲子進程終止是異步事件,所以這種信號也是內核向父進程發的異步通知。
調用wait或waitpid函數的進程會發生什麼?
● 如果其所有進程都還在運行,則阻塞。
● 如果一個子進程已終止,正等待父進程獲取其終止狀態,則取得該子進程的終止狀態立即返回。
●如果它沒有任何進程,則立即出錯返回。
pid_t wait(int *statloc);
功能:等待所有子進程結束 ,並獲取到最終的狀態碼。只要有一個進程結束就立即返回。
pid_t waitpid(pid_t pid,int *statloc,int options);
功能:等待指定的進程結束,並獲取到最終的狀態碼。
statloc是一個整形指針。如果statloc不是一個空指針,則終止進程的終止狀態就存放在它所指向的單元裏。如果不關心終止狀態,可將參數指定爲空指針。
POSIX.1規定,終止狀態用定義在<sys/wait.h>中的各個宏來查看,有4個互斥的宏可用來取得進程終止的原因,它們的名字都以WIF開始。
宏 | 說明 |
WIFEXITED | 若爲正常終止子進程返回的狀態,則爲真。對於這種情況,可執行WEXITSTATUS,獲取子進程傳送給exit或_exit參數的低8位 |
WIFSIGNALLED | 若爲異常終止子進程返回的狀態,則爲真 |
WIFSTOPPED | 若爲當前暫停子進程的返回的狀態,則爲真 |
WIFCONTINUED | 若在作業控制暫停後已經繼續的子進程返回了狀態,則爲真 |
關於waitpid函數中pid參數的作用解釋
pid==-1 | 等待任一子進程,此種情況下,waitpid與wait等效 |
pid>0 | 等待進程ID與pid相等的子進程 |
pid==0 | 等待組ID等於調用進程組ID的任一子進程 |
pid<-1 | 等待組ID等於pid絕對值的任一子進程 |
options參數使我們能進一步控制waitpid的操作
常量 | 說明 |
WCONTINUED | 由pid指定的子進程停止後已經繼續,但其狀態尚未報告,則返回其狀態 |
WNOHANG | 若由pid指定的子進程並不是立即可用的,則waitpid不阻塞,此時返回0 |
WUNTRACED | 由pid指定的子進程已停止,但其狀態尚未報告,則返回其狀態 |
這兩個函數的區別:
1、子一個子進程終止前,wait使其調用者阻塞,而waitpid有一個選項,可使調用者不阻塞。
2、waitpid並不等待在其調用之後的第一個終止子進程,他又若干個選項,可控制所等待的進程。
函數system
int system(const char *cmdstring)
功能:執行系統命令,也可以加載可執行程序。
因爲system在實現中調用了fork、exec、waitpid,所以有3種返回值
(1)、fork失敗或者waitpid返回除EINTR之外的出錯,則sysytem返回-1,並且設置errno以指示錯誤類型。
(2)、如果exec失敗,則其返回值如同shell執行了exit(127)一樣。
(3)、三個函數都成功,那麼返回值是shell的終止狀態。
進程終止
有8種方式使進程終止,其中5種爲正常終止,它們是:
(1)、從main函數返回;
(2)、調用exit函數;
(3)、調用_exit或_Exit;
(4)、最後一個線程從其啓動例程返回;
(5)、從最後一個線程調用pthread_exit;
異常終止有3種方式,它們是
(6)、調用abort;
(7)、接收到一個信號;
(8)、最後一個線程對取消請求做出響應。
void exit(int status);
功能:調用者立即結束該進程
status:退出狀態碼,可以在父進程中獲取到,子進程留給父進程的遺言
退出前會做的事情:
1、先調用所有事先註冊的函數(通過 atexit/on_exit 註冊的)
int atexit(void (*function)(void));
功能:註冊一個函數,當進程通過exit函數結束時調用
function:函數指針,無返回值無參數
返回值:成功爲0,失敗-1
int on_exit(void (*function)(int , void *), void *arg);
功能:註冊一個函數,當進程通過exit函數開始結束時調用
function:函數指針,無返回值,參數1爲exit函數的參數,參數2爲on_exit的第二個參數。
arg:當function函數被調用是傳遞給它第二參數
2、沖刷所有處在未關閉狀態的標準I/O流
3、返回一個整數( 0 (EXIT_SUCCESS) / 1 (EXIT_FAILURE) )給操作系統
4、該函數不會返回,它的功能實現藉助了_exit/_Exit
注意:atexit 和 on_exit 註冊的函數都會加入同一個棧結構中,誰最後登記的誰最先執行,他們的區別只有註冊的函數格式不同。
#include <unistd.h>
void _exit(int status);
#include <stdlib.h>
void _Exit(int status);
//調用了系統的_exit(爲了兼容)
功能:調用的進程會結束,沒有返回值。
status:會被父進程獲取到(低八位,一個字節)。
說明:
1、進程結束前會關閉所有處於打開狀態的文件描述符
2、把所有的子進程託付給孤兒院init
3、向它的父進程發送SIGCHLD信號。
注意:exit函數也會執行以上操作,因爲它底層調用了_exit/_Exit
進程時間
任一進程都可以調用times函數獲得它自己以及已終止子進程的上述值。
#include <sys/times.h>
clock_t times(struct tms *buf);
此函數填寫由buf指向的tms結構,該結構定義如下
struct tms {
clock_t tms_utime; /* user time */
clock_t tms_stime; /* system time */
clock_t tms_cutime; /* user time of children */
clock_t tms_cstime; /* system time of children */
};