天天看點

程序管理實驗——POSIX下程序控制(一)

今天第一次真正意義上進行作業系統的實驗,比起以前C語言,Java的實驗,作業系統實驗顯得更有樂趣,也更有挑戰性,因而整理為一篇部落格,進一步的鞏固知識。

目的

通過分析實驗現象,深入了解程序及程序在排程執行和記憶體空間等方面的特點,掌握在POSIX規範中fork和kill系統調用的功能和使用。

實驗前準備

科普:POSIX是Portable Operating System Interface for UNIX的首字母縮寫詞,是一套IEEE和ISO标準。這個标準定義了應用程式和作業系統之間的一個接口。隻要保證他們的程式設計的符合POSIX标準,開發人員就能确信他們的程式可以和支援POSIX的作業系統互聯。這樣的作業系統包括大部分版本的UNIX。

1、fork()函數:pid_t fork(void);

傳回值:fork僅僅被調用一次,卻能夠傳回兩次,它可能有三種不同的傳回值

            (1)在父程序中,fork傳回新建立子程序的程序ID;

            (2)在子程序中,fork傳回0;

            (3)如果出現錯誤,fork傳回一個負值;

            在fork函數執行完畢後,如果建立新程序成功,則出現兩個程序,一個是子程序,一個是父程序。在子程序中,fork函數傳回0,在父程序中,fork傳回新建立子程序的程序ID。我們可以通過fork傳回的值來判斷目前程序是子程序還是父程序。

程式管理實驗——POSIX下程式控制(一)

 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 退出程式再看程序情況。

十個程序在運作:

程式管理實驗——POSIX下程式控制(一)
程式管理實驗——POSIX下程式控制(一)

殺死id為2的子程序,用“ps aux|grep process”指令檢視:

程式管理實驗——POSIX下程式控制(一)

經過自己動手實驗,真的發現很有意思,但裡面也存在很多很多的坑,還需自己慢慢摸索。 要是覺得輸出結果還是不清晰,可以在多輸出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 平台通過任務管理器殺死某個程序。