程序管理實驗——POSIX下程序控制(一)
今天第一次真正意義上進行作業系統的實驗,比起以前C語言,Java的實驗,作業系統實驗顯得更有樂趣,也更有挑戰性,因而整理為一篇部落格,進一步的鞏固知識。
目的
通過分析實驗現象,深入理解程序及程序在排程執行和記憶體空間等方面的特點,掌握在POSIX規範中fork和kill系統呼叫的功能和使用。
實驗前準備
科普:POSIX是Portable Operating System Interface for UNIX的首字母縮寫詞,是一套IEEE和ISO標準。這個標準定義了應用程式和作業系統之間的一個介面。只要保證他們的程式設計的符合POSIX標準,開發人員就能確信他們的程式可以和支援POSIX
1、fork()函式:pid_t fork(void);
返回值:fork僅僅被呼叫一次,卻能夠返回兩次,它可能有三種不同的返回值 (1)在父程序中,fork返回新建立子程序的程序ID; (2)在子程序中,fork返回0; (3)如果出現錯誤,fork返回一個負值; 在fork函式執行完畢後,如果建立新程序成功,則出現兩個程序,一個是子程序,一個是父程序。在子程序中,fork函式返回0,在父程序中,fork返回新建立子程序的程序ID。我們可以通過fork返回的值來判斷當前程序是子程序還是父程序。
2、kill()函式:int kill(pid_t pid, int sig);
函式引數:①pid:指定程序的程序ID,注意使用者的許可權,比如普通使用者不可以殺死1號程序(init)。
pid>0:傳送訊號給指定程序
pid=0:傳送訊號給與呼叫kill函式程序屬於同一程序組的所有程序
pid<0:傳送訊號給pid絕對值對應的程序組
pid=-1:傳送給程序有許可權傳送的系統中的所有程序
②訊號量:本實驗用SIGTERAM。程式結束(terminate)訊號,和SIGKILL不同的是該訊號可以被阻塞和處理,通常用來要求程式自己退出。如果終止不了,我們才會嘗試SIGKILL。
實驗程式:(已在Ubuntu 14.04下完美執行)
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
#include <ctype.h>
/* 允許建立的子程序個數最大值 */
#define MAX_CHILD_NUMBER 10
/* 子程序睡眠時間 */
#define SLEEP_INTERVAL 2
int proc_number = 0; /* 子程序的自編號id,從0開始 */
void do_something();
void do_something() {
for(;;) {
/* 列印子程序自編號。為清晰,在每個號碼前加“號碼+3”個空格
* 比如號碼是1,就列印" 1" */
printf("This is process No.%*d\n", proc_number+3, proc_number);
sleep(SLEEP_INTERVAL); /* 主動阻塞兩秒鐘 */
}
}
int main(int argc, char* argv[]) {
int child_proc_number = MAX_CHILD_NUMBER; /* 子程序個數 */
int i, ch;
pid_t child_pid;
pid_t pid[10]={0}; /* 存放每個子程序的id */
if (argc > 1) {
/* 命令列引數中的第一個引數表示建立幾個子程序,最多10個 */
child_proc_number = atoi(argv[1]);
child_proc_number = (child_proc_number > 10) ? 10 : child_proc_number;
}
for (i = 0; i < child_proc_number; i++) {
/* 在這裡填寫程式碼,建立child_proc_number個子程序
* 子程序要執行
* proc_number = i;
* do_something();
* 父程序把子程序的id儲存到pid[i] !!!!!!!!!!!!!*/
child_pid = fork();
if (child_pid > 0) {
pid[i] = child_pid;
} else if (child_pid == 0) {
proc_number = i;
do_something();
} else {
printf("Fail to fork!\n");
}
}
/* 讓使用者選擇殺死哪個程序。輸入數字(自編號)表示殺死該程序
* 輸入q退出 */
while ((ch = getchar()) != 'q') {
if (isdigit(ch)) {
/* 在這裡填寫程式碼,向pid[ch-'0']發訊號SIGTERM,
* 殺死該子程序 */
kill(pid[ch-'0'], SIGTERM);
}
}
/* 在這裡填寫程式碼,殺死本組的所有程序 */
kill(0, SIGTERM);
return 0;
}
/*
函式原型:fork()函式:pid_t fork(void);
返回值:fork僅僅被呼叫一次,卻能夠返回兩次,它可能有三種不同的返回值
(1)在父程序中,fork返回新建立子程序的程序ID;
(2)在子程序中,fork返回0;
(3)如果出現錯誤,fork返回一個負值;
在fork函式執行完畢後,如果建立新程序成功,則出現兩個程序,一個是子程序,一個是父程序。
在子程序中,fork函式返回0,在父程序中,fork返回新建立子程序的
程序ID。我們可以通過fork返回的值來判斷當前程序是子程序還是父程序。
函式原型:int kill(pid_t pid, int sig);
函式引數
①pid:指定程序的程序ID,注意使用者的許可權,比如普通使用者不可以殺死1號程序(init)。
pid>0:傳送訊號給指定程序
pid=0:傳送訊號給與呼叫kill函式程序屬於同一程序組的所有程序
pid<0:傳送訊號給pid絕對值對應的程序組
pid=-1:傳送給程序有許可權傳送的系統中的所有程序
*/
實驗過程:
先猜想一下這個程式的執行結果。假如執行“./process 20”,輸出會是什麼樣?然後按照註釋裡的要求把程式碼補充完整,執行程式。可以多執行一會兒,並在此期間啟動、關閉一些其它程序,看process 的輸出結果有什麼特點,記錄下這個結果。 開另一個終端視窗,執行“ps aux|grep process”命令,看看process 究竟啟動了多少個程序。回到程式執行視窗,按“數字鍵+回車”嘗試殺掉一兩個程序,再到另一個視窗看程序狀況。按q 退出程式再看程序情況。
十個程序在執行:
殺死id為2的子程序,用“ps aux|grep process”命令檢視:
經過自己動手實驗,真的發現很有意思,但裡面也存在很多很多的坑,還需自己慢慢摸索。 要是覺得輸出結果還是不清晰,可以在多輸出getpid()。來檢視程序的識別碼。
實驗問題:
1、你最初認為執行結果會怎麼樣?
會建立10個程序,程序號為0~9。若輸入數字、回車會殺死指定程序。若輸入q、回車,會殺死所有程序。
2、實際的結果什麼樣?有什麼特點?試對產生該現象的原因進行分析。
程式最多會產生十個程序,否則根據命令列第二個引數來看。每隔SLEEP_INTERVAL秒重新整理一次輸出在命令列介面。直到輸入數字+回車,將會殺死指定編號的程序,或直到輸入q、回車,會殺死本組所有程序。
特點:每次都輸出一定數目的程序,但是,發現每次輸出的程序的次序不一樣,隨機輸出。程序的pid識別碼是遞增的。每次呼叫fork(),都會生成一個父程序,一個子程序。把生成父程序返回的子程序pid儲存到pid[i]中;把i直接賦給子程序的自編號proc_number,然後呼叫死迴圈函式do_something()進行輸出。程序是迴圈建立的,所以程序自編號proc_number是隨著i由小變大的,pid也是依次遞增的
3、proc_number這個全域性變數在各個子程序裡的值相同嗎?為什麼?
相同,因為子程序相互獨立,資源互不影響。
4、kill命令在程式中使用了幾次?每次的作用是什麼?執行後的現象是什麼?
兩次。kill(pid[ch=’0’], SIGTERM);kill(0,SIGTERM);第一個是殺死程序號pid[ch-‘0’],執行後輸出的結果中不會再有該程序號。第二次是殺死本組所有程序。即主程序以及它所建立的所有子程序,執行後程序退出結束。
5、使用kill 命令可以在程序的外部殺死程序。程序怎樣能主動退出?這兩種退出方式哪種更好一些?
程序在主函式中return,或呼叫exit()都可以主動退出。使用kill()則是強制性的異常退出。 主動退出更好,若在子程序退出前使用kill()殺死其父程序,則系統會讓init程序(一號程序)接管子程序,該子程序就會成為孤兒程序。當使用kill()殺死子程序使得子程序先於父程序退出時,父程序又沒有呼叫wait()函式等待子程序結束,子程序處於僵死狀態,即殭屍程序。且一直會保持下去,直到系統重啟。子程序處於僵死狀態時核心只儲存該程序的必要資訊以備父程序所需,此時子程序始終佔著資源,這也就減少了系統可以建立的最大程序數。
在執行 kill 命令傳送訊號後,目標程序會異常退出。這也是系統管理員終結某個程序的最常用方法,類似於在 Windows 平臺通過工作管理員殺死某個程序。