進程式控制制塊
在Linux中,每個進程在核心中都有一個進程式控制制塊(PCB)來維護進程相關的資訊,它通常包含如下資訊:
- 進程id。系統中每個進程有唯一的id,在C語言中用pid_t類型表示,其實就是一個非負整數。
- 進程的狀態,有運行、掛起、停止、殭屍等狀態。
- 進程切換時需要儲存和恢複的一些CPU寄存器。
- 描述虛擬位址空間的資訊。
- 描述控制終端的資訊。
- 當前工作目錄(Current Working Directory)。
- umask掩碼。
- 檔案描述符表,包含很多指向file結構體的指標。
- 和訊號相關的資訊。
- 使用者id和組id。
- 控制終端、Session和進程組。
- 進程可以使用的資源上限(Resource Limit)。
fork和exec
fork的作用是根據一個現有的進程複製出一個新進程,原來的進程稱為父進程(Parent Process),新進程稱為子進程(Child Process)。
系統中同時運行著很多進程,這些進程都是從最初只有一個進程開始一個一個複製出來的。在Shell下輸入命令可以運行一個程式,是因為Shell進程在讀取使用者輸入的命令之後會調用fork複製出一個新的Shell進程,然後新的Shell進程調用exec執行新的程式。
一個程式可以多次載入到記憶體,成為同時啟動並執行多個進程,例如可以同時開多個終端視窗運行/bin/bash,另一方面,一個進程在調用exec前後也可以分別執行兩個不同的程式,例如在Shell提示符下輸入命令ls,首先fork建立子進程,這時子進程仍在執行/bin/bash程式,然後子進程調用exec執行新的程式/bin/ls,如所示。
fork函數
fork函數的聲明如下:
pid_t fork(void);
當fork複製進程失敗時,會返回-1;當複製進程成功時,由於複製出來的進程只執行fork函數之後的代碼。雖然父進程和子進程都會執行後續的代碼,但可以通過fork的傳回值加以區分,一個簡單的樣本如下:
#include <sys/types.h>
#include
<unistd.h>
#include
<stdio.h>
#include
<stdlib.h>
int main(void)
{
pid_t pid;
char *message;
int n;
pid = fork();
if (pid < 0) {
perror("fork failed");
exit(1);
}
if (pid == 0) {
message = "This is the child\n";
n = 6;
} else {
message = "This is the parent\n";
n = 3;
}
for(; n > 0; n--) {
printf(message);
sleep(1);
}
return 0;
}
fork對父進程返回子進程的ID,對子進程返回0,因此可以通過傳回值來區分是父進程還是子進程。
這樣設計是有道理的,雖然子進程無法通過傳回值擷取進程ID,但可以通過getpid函數得到自己的進程id,也可以調用getppid函數得到父進程的id。在父進程中用getpid可以得到自己的進程id,然而要想得到子進程的id,只有將fork的傳回值記錄下來,別無它法。
根據傳回值區分進程後,既可以指定他們的流程了,上例中他們的流程分別如下:
exec函數
用fork建立子進程後執行的是和父進程相同的程式,子進程也可以通過exec函數執行另一個程式。當進程調用一種exec函數時,該進程的使用者空間代碼和資料完全被新程式替換,從新程式的啟動常式開始執行。功能上類似C的system函數。
一個樣本如下:
#include
<unistd.h>
#include
<stdlib.h>
int main(void)
{
execlp("ps", "ps", "-o", "pid,ppid,pgrp,session,tpgid,comm", NULL);
perror("exec ps");
exit(1);
}