Linux系統程式設計_程序
阿新 • • 發佈:2019-02-14
程式 vs. 程序:
程式,是指編譯好的二進位制檔案,是靜態概念,在磁碟上,不佔用系統資源(cpu、記憶體、開啟的檔案、裝置、鎖….);
程序,是一個抽象的動態概念,與作業系統原理聯絡緊密。程序是活躍的程式,佔用系統資源。在記憶體中執行。(程式執行起來,產生一個程序)
程式 → 劇本(紙) 程序 → 戲(舞臺、演員、燈光、道具…)
同一個劇本可以在多個舞臺同時上演。同樣,同一個程式也可以載入為不同的程序(彼此之間互不影響)
如:同時開兩個終端。各自都有一個bash但彼此ID不同。MMU(Memory Management Unit,記憶體管理單元):CPU內部的MMU完成從虛擬記憶體到實體記憶體的對映過程。MMU以4k大小的頁為單位。
- PCB(程序控制塊):在核心區,每個程序僅此一份。PCB中的常見包含的內容需瞭解。
程式執行的四級流水示意圖:
MMU的作用示意圖:
程序四狀態:就緒、執行、掛起\阻塞、停止。
環境變數:在作業系統中用來指定作業系統執行環境的一些引數。
程序控制
- 建立程序:
fork
函式,父程序的fork函式返回子程序的id,子程序的fork返回0。注意返回值,不是fork函式能返回兩個值,而是fork後,fork函式變為兩個,父子需各自返回一個。注意:雖然返回的子程序也擁有了一份和父程序一樣的程式,但是在子程序中,該程式是從該子程序被建立處,即fork函式返回處,繼續往後執行的,前面的程式部分不再執行。 - 迴圈建立指定數量的子程序並區分每個子程序:
注意:除去父程序外,第i
次fork()
後,共有2^i-1
個子程序被創建出來,因為第一次fork後就有不止一個程序在執行這個程式。
//迴圈建立5個子程序並打印出每個子程序的程序ID
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main(int argc, char *argv[]) {
pid_t pid;
int i;
for(i = 0; i < 5; i++) //出口1,父程序專用出口
if ((pid = fork()) == 0)
break; //出口2,子程序出口,i不自增
if(5 == i){
sleep(n);
printf("I am parent, pid = %d\n", getpid());
} else {
sleep(i);
printf("I'm %dth child, pid = %d\n", i+1, getpid());
}
return 0;
}
- 程序共享:
(1)讀時共享寫時複製原則:對於父子程序的相同部分內容是適用的,這樣以免相同的內容通過MMU被重複對映到記憶體中不同的空間,便於節省記憶體。不過該原則指出,在寫時就要複製一份相同的內容給子程序了,使得父子程序各自修改自己的內容。此處,有一個關於程式中全域性變數會干擾父子程序內容的誤區。事實上,寫時複製已經明確了,若是要修改父或子程序的內容,就會複製一份給子程序,從此修改一方的內容就不會再對另一方有任何影響,不管修改的是什麼內容,包括全域性變數。
(2)注意,上述原則僅適用於父子程序間的使用者空間資料,而父子程序的核心空間資料,即PCB的內容,經MMU對映到實際物理空間中是同一塊內容,這給父子程序間通訊打下了基礎。
(3)父子程序間相同和不同的內容:
父子相同處: 全域性變數、.data、.text、棧、堆、環境變數、使用者ID、宿主目錄、程序工作目錄、訊號處理方式…
父子不同處: 1.程序ID 2.fork返回值 3.父程序ID 4.程序執行時間 5.鬧鐘(定時器) 6.未決訊號集
【重點】:父子程序共享:1. 檔案描述符(開啟檔案的結構體) 2. mmap建立的對映區
特別的,fork之後父程序先執行還是子程序先執行不確定。取決於核心所使用的排程演算法。
(4)多程序gdb除錯:
set follow-fork-mode parent //跟蹤父程序(預設)
set follow-fork-mode child //跟蹤子程序
//注意,上述兩條指令要在fork函式呼叫之前設定!
(5)exec
函式族:在程式執行期間執行另外一個程式,讓子程序和父程序完全區分開來,執行內容與父程序無關,徹底獨立出來,但子程序id不變。可理解為子程序中,執行exec
函式族中任一函式後,子程序覆蓋了原來複制自父程序的程式碼段和資料段。
exec函式一旦呼叫成功即執行新的程式,不返回。只有失敗才返回,錯誤值-1。所以通常我們直接在exec函式呼叫後直接呼叫perror()和exit(),無需if判斷。
回收子程序
- 孤兒程序:父程序先於子程序結束,則子程序成為孤兒程序。孤兒子程序的父程序自動變為
init
程序,稱為init程序領養孤兒程序。 - 殭屍程序:程序終止,父程序尚未回收,子程序殘留資源(PCB)存放於核心中,變成殭屍(Zombie)程序。
特別注意,殭屍程序是不能使用kill命令清除掉的。因為kill命令只是用來終止程序的,而殭屍程序已經終止。那用什麼辦法可清除掉殭屍程序呢?殺死父程序即可,這樣一來該殭屍程序就被劃歸init程序的孤兒院並被回收。 - 回收子程序:
(1)wait
函式,pid_t wait(int *status)
,返回值是應該回收的子程序ID,引數是應該獲取的子程序退出狀態,注意這個引數是一個傳出引數,若不關心子程序退出狀態,那麼給wait函式傳NULL
。注意:用int型別變數status來存子程序狀態也是通過點陣圖(bitmap)的方式,所以接下來獲取子程序退出的訊號要藉助巨集函式,這一點類似於前面的檔案操作。
父程序呼叫wait函式可以回收子程序終止資訊。該函式有三個功能:
① 阻塞等待子程序退出
② 回收子程序殘留資源
③ 獲取子程序結束狀態(退出原因)。
//利用wait函式回收子程序並獲得其退出狀態
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
int main() {
pid_t pid, wpid;
int status;
pid = fork();
if(pid == -1) {
perror("fork error");
exit(1);
} else if(pid == 0) {
//sleep(100);
printf("I am child, pid = %d, ppid = %d\n", getpid(), getppid());
return 20;
} else if(pid > 0) {
sleep(1);
printf("I am parent, pid = %d, ppid = %d\n", getpid(), getppid());
wpid = wait(&status);
printf("wpid = %d\n", wpid);
if(WIFEXITED(status)) { //子程序正常退出
printf("exit with %d\n", WEXITSTATUS(status));
} else if(WIFSIGNALED(status)) { //子程序非正常退出
printf("killed by %d\n", WTERMSIG(status));
}
}
return 0;
}
(2)waitpid
函式:作用同wait,但可指定pid程序清理,可以不阻塞。
pid_t waitpid(pid_t pid, int *status, in options);
//返回值:
成功:返回清理掉的子程序ID;
失敗:-1(無子程序)
特殊引數和返回情況:
對於引數pid:
> 0 回收指定ID的子程序
-1 回收任意子程序(相當於wait)
0 回收和當前呼叫waitpid一個組的所有子程序
< -1 回收指定程序組內的任意子程序
返回0:引數3:options為WNOHANG,且子程序正在執行。
注意:一次wait或waitpid呼叫只能清理一個子程序,清理多個子程序應使用迴圈。