Linux進程控制編程實驗

任務1:編寫一個進程創建實驗程序task51.c,創建如圖所示的進程族親結構,其中p1是程序啓動時由加載程序創建第一個進程。各進程的輸出信息分別如下:
p1:I am father process p11: 當前時間是< 年 月 日 時 分 秒> p12: I am young
brother process p121:我的學號是<您的學號xxx> p122:我的姓名是<您的姓名xxx>
提示:獲得當前系統時間的函數是 time_t time(time_t *t);
將time_t類型的的時間轉換成時間字符串的函數是:char *ctime(const time_t *timep);
其使用方法見教材3.2.4 思考:如何驗證產生的進程符合如圖的族親關係?

在這裏插入圖片描述
思考題:使用getpid()和getppid()函數結合使用就可以驗證設計的族親結構是否準確,其中getpid是得到自己的pid,getppid是得到父進程的pid。

任務2:參考教材shellex.c代碼,實現一個簡單的交互式shell程序task52.c,具備的功能至少有:
(1)打印提示符%;獲取用戶輸入指令;解析指令;尋找命令文件,執行指令 (1)顯示命令提示符%;
(2)獲取用戶輸入指令;解析指令;尋找命令文件,執行指令; (3)前一個命令完成後才繼續顯示命令提示符%,等待用戶輸入下一條命令;
(3)如果輸入“exit”或“log out”,則退出shell. (4)允許命令參數間有多個空格,也允許命令前後有空格
(5)支持輸出重定向和管道功能。 提示:可參考上一次實驗分解命令行的代碼

任務3:寫一個信號機制實驗程序task53.c,由父進程創建兩個子進程,通過終端輸入Crtl+\組合鍵向父進程發送SIGQUIT軟中斷信號或由系統時鐘產生SIGALRM軟中斷信號發送給父進程;父進程接收到這兩個軟中斷的其中某一個後,向其兩個子進程分別發送整數值爲SIGUSR1
(10)和SIGUSR1 (12)軟中斷信號,子進程獲得對應軟中斷信號後,分別輸出“<進程PID> killed by
<信號編號>”後,終止運行;父進程調用wait()函數等待兩個子進程終止,然後自我終止。

任務4(可選):寫一個子進程管理程序task54.c,借鑑圖5-34的procmask2.c方法管理子進程,父進程循環讀取用戶輸入的操作命令,創建子進程、顯示相關信息和終止子進程等。具體用戶命令爲:

  1. 命令1:功能是創建一批子進程,格式爲“create <進程數>” ,命令執行成功後,顯示所有新創建子進程PID。比如“create
    10”表示創建10個子進程,子進程執行的代碼可以爲:“while(1){};exit(100);”
    2)命令2:終止一批子進程,格式爲“kill …”(如“kill 123 456
    789”爲終止PID號爲123、456、789的三個子進程),子進程顯示“killed by
    parent”後終止,父進程通過SIGCHLD信號處理程序等待子進程終止,顯示終止的子進程PID。
    3)命令3:顯示當前子進程列表,命令格式爲:“ps -u”
    4)命令4:父進程終止命令,格式爲“exit”,當所有子進程都結束後,才允許執行該命令。

提示:可用fgets函數將整個命令作爲一行文本輸入,再調用庫函數(如strtok或strchr)將各個命令參數提取出來。

任務1:

#include"wrapper.h"
#include<stdio.h>
#include<time.h>
int main() {                         //main函數就是p1進程
	pid_t  pid1, pid2,pid3;
	printf("I am father process\n");      //p1進程的輸出語句
	pid1 = fork();                    //p進程創建第一個子進程p11
	if (pid1 == 0) {
		time_t timer;
		time(&timer);
		printf("p1:%s\n", ctime(&timer));
		exit(0);
	}
	else {
		pid2 = fork();                 //由p進程創建子進程p12
		if (pid2 == 0) {
			printf("p12: I am young brother process\n");
			pid3 = fork();
			if (pid3 == 0) {            //由p12進程創建子進程p121
				printf("p121:我的學號:201841413302\n");
				exit(0);
			}
			pid3 = fork();
			if (pid3 == 0) {            //由p12進程創建子進程p122
				printf("p122:我的姓名:陳霖\n");
				exit(0);
			}
			exit(0);
		}
		exit(0);
	}

}

思考題:使用getpid()和getppid()函數結合使用就可以驗證設計的族親結構是否準確, 其中getpid是得到自己的pid,getppid是得到父進程的pid。

任務2:

#include"wrapper.h"
#include<stdio.h>
void execute(char* cmdline);
int builtin_command(char** argv) ;
int parseline(char* buf, char** argv);
void MYdup1();
void MYdup2();
int main() {
	char cmdline[MAXLINE]; /* 命令行緩衝區 */
	while (1) {
		printf("%%");
		fgets(cmdline, MAXLINE, stdin);/* 讀取命令行 */
		if (feof(stdin))
			exit(0);
		execute(cmdline);/* 執行命令 */
	}
}
void MYdup1(){	//重定向到一個data.txt文件路徑下面
	int fd;
	fd = open("data.txt",O_CREAT|O_WRONLY|O_APPEND,0777);
   dup(1);//先把標準輸出備份好,等待未來重定向恢復
	close(1);
	dup(fd);
}
void MYdup2(){	//路徑恢復,標準輸出
	close(1);
	dup2(4,1);
}
void execute(char* cmdline) {
	char* argv[MAXLINE]; /*execve()參數表 */
	char buf[MAXLINE]; /* 保存修改後的命令行 */ 
	int bg; /* 是否在後臺執行 */ 
	pid_t pid; /* 子進程PID*/ 
	strcpy(buf, cmdline); 
	bg = parseline(buf, argv);/* 解析命令行 */ 
	if (argv[0] == NULL) return; /* 如果第1個參數爲空,則忽略命令 */
	if (!builtin_command(argv)) {
		if ((pid = fork()) == 0) { /* 創建子進程 */ 
			if (execvp(argv[0], argv) < 0) { 
				printf("%s:Command not found.\n", argv[0]); exit(0); 
			} 
		}
		if (!bg) { /* 前臺執行 */ 
			int status;

			if (waitpid(pid, &status, 0) < 0) 
				perror("waitpid error");
		}
		else 
			printf("%d%s", pid, cmdline);
	} return;
}
/* 判斷和執行內置命令 */ 
int builtin_command(char** argv) {
	if (!strcmp(argv[0], "exit")) /* 內置命令exit */
		exit(0);
	if(!strcmp(argv[0],"log out"))/* 內置命令logout */
		exit(0);
	if(!strcmp(argv[0],"MYdup1")){/* 重定向 */
		MYdup1();
       return 1;
	}
	if(!strcmp(argv[0],"MYdup2")){/* 路徑恢復 */
		MYdup2();
       return 1;
	}
	if (!strcmp(argv[0], "&")) /* 忽略由&開始的命令串 */
		return 1;
	return 0; /* 非內置命令 */
}
int parseline(char* buf, char** argv) {
	char* delim; /* 指向第1個分隔符 */
	int argc; /* 字符串數組args中命令行參數個數 */
	int bg; /* 後臺作業 */
	buf[strlen(buf) - 1] = ' '; /* 用空格替換行末換行符 */
	while (*buf && (*buf == ' '))/* 刪除行首空格 */
		buf++;
	/* 創建 argv數組 */
	argc = 0;
	while ((delim = strchr(buf, ' ')))
	{
		argv[argc++] = buf;
		*delim = '\0';
		buf = delim + 1;
		while (*buf && (*buf == ' ')) /* 忽略空格,查找下一個參數起始位置 */
			buf++;
	}
	argv[argc] = NULL;
	if (argc == 0) /* 忽略空行*/
		return 1;
	/* 命令是否應在後臺執行 */
	if ((bg = (*argv[argc - 1] == '&')) != 0)
		argv[--argc] = NULL;
	return bg;
}

任務3

#include <stdio.h> 
#include <stdlib.h> 
#include <signal.h> 
#include <unistd.h>  
#define sec 5  
void waiting();
void stop();
int wait_mark;
int main() {
	int p1, p2;  /*定義兩個進程號變量*/
	while ((p1 = fork()) == -1);  /*循環創建進程至成功爲止*/
	if (p1 > 0) {
		while ((p2 = fork()) == -1);  /*循環創建進程至成功爲止*/
		if (p2 > 0) {
			wait_mark = 1;
			alarm(sec);/*5秒後執行*/
			signal(SIGQUIT, stop);
			signal(SIGALRM, stop);/*父進程接受兩種信號,均用stop去響應*/
			waiting();
			kill(p1, 10);/*向p1發送軟中斷信號10*/
			kill(p2, 12);/*向p2發送軟中斷信號12*/
			wait(0);
			wait(0);/*等待兩個子進程終止後才進行下一條代碼*/
			printf("parent process is killed!\n");
			exit(0);/*自我終止*/
		}
		else {
			signal(SIGQUIT, SIG_IGN);/*忽略信號*/
			signal(SIGALRM, SIG_IGN);/*忽略信號*/
			wait_mark = 1;
			signal(12, stop); /*接收到軟中斷信號12,轉stop*/
			waiting();/*在wait置0前,不可往下執行*/
			lockf(1, 1, 0);  /*加鎖*/
			printf("進程%d is killed by 信號12!\n", getpid());
			lockf(1, 0, 0); /*解鎖*/
			exit(0); /*子進程2退出*/
		}
	}
	else {
		signal(SIGQUIT, SIG_IGN);/*忽略信號*/
		signal(SIGALRM, SIG_IGN);/*忽略信號*/
		wait_mark = 1; /*將等待標記置1直到中斷信號刺激stop*/
		signal(10, stop);/*接收到軟中斷信號10,轉stop*/
		waiting();  /*在wait置0前,不可往下執行*/
		lockf(1, 1, 0);
		printf("進程%d  is killed by 信號10!\n", getpid());/*接收到父進程發送信號後,父進程殺死子進程1*/
		lockf(1, 0, 0);/*解鎖*/
		exit(0); /*子進程1退出*/
	}
	return 0;
}
void waiting() {
	while (wait_mark != 0);
}
void stop() {
	wait_mark = 0;
}

任務4:

#include"wrapper.h"
#include<stdio.h>
#include<string.h>
void execute(char* cmdline);/*執行命令*/
int builtin_command(char** argv);/*檢查是否爲用戶內置命令*/
int parseline(char* buf, char** argv);/*將命令切割成標準格式的若個部分*/
void create(int num);		/*創建子進程num個*/
void Exit();
void stop();
void waitBack();
void MyKill(char** argv);
int n;
int mask;
static int count = 0;/*父進程下面的子進程數量,若爲0才能調用Exit()函數*/

int main() {
	char cmdline[MAXLINE]; /* 命令行緩衝區 */
	signal(SIGCHLD, waitBack);

	while (1) {
		printf("%%");
		fgets(cmdline, MAXLINE, stdin);/* 讀取命令行 */
		if (feof(stdin))
			exit(0);

		execute(cmdline);/* 執行命令 */
		//usleep(100);/*每次執行完任務,就停止100微秒*/
	}
}

void execute(char* cmdline) {
	char* argv[MAXLINE]; /*execve()參數表 */
	char buf[MAXLINE]; /* 保存修改後的命令行 */
	pid_t pid; /* 子進程PID*/
	strcpy(buf, cmdline);
	parseline(buf, argv);/* 解析命令行 */
	if (argv[0] == NULL) 
		return; /* 如果第1個參數爲空,則忽略命令 */
	if (!builtin_command(argv)) {
		if ((pid = fork()) == 0) { /* 創建一個臨時進程,因爲調用execvp後的進程就不會回來了 */
			count++;
			if (execvp(argv[0], argv) < 0) {
				printf("%s:Command not found.\n", argv[0]);
				exit(0);/*立即放棄該臨時進程*/
			}
		}
		else{
			usleep(200);
			waitpid(pid,NULL,0);
		}
	} 
}
/* 判斷和執行內置命令 */
int builtin_command(char** argv) {
	if (!strcmp(argv[0], "exit")) { /* 內置命令exit */
		Exit();
		return 1;
	}

	if (!strcmp(argv[0], "create")) { /* 內置命令create */
		create(atoi(argv[1]));
		return 1;
	}
	if (!strcmp(argv[0], "kill")) { /* 內置命令kill */
		MyKill(argv);
		return 1;
	}
	if (!strcmp(argv[0], "&")) /* 忽略由&開始的命令串 */
		return 1;

	return 0; /* 非內置命令 */
}

int parseline(char* buf, char** argv) {
	char* delim; /* 指向第1個分隔符 */
	int argc; /* 字符串數組args中命令行參數個數 */
	int bg; /* 後臺作業 */
	buf[strlen(buf) - 1] = ' '; /* 用空格替換行末換行符 */
	while (*buf && (*buf == ' '))/* 刪除行首空格 */
		buf++;
	/* 創建 argv數組 */
	argc = 0;
	while ((delim = strchr(buf, ' ')))
	{
		argv[argc++] = buf;
		*delim = '\0';
		buf = delim + 1;
		while (*buf && (*buf == ' ')) /* 忽略空格,查找下一個參數起始位置 */
			buf++;
	}
	argv[argc] = NULL;
	n = argc;/*記錄命令中出現的參數個數*/
	if (argc == 0) /* 忽略空行*/
		return 1;

	/* 命令是否應在後臺執行 */
	if ((bg = (*argv[argc - 1] == '&')) != 0)
		argv[--argc] = NULL;
	return bg;
}
void create(int num) {/* 命令1:創建num個子進程 */
	int pid, i;
	if (num <= 0) {
		return;
	}
	if ((pid = fork()) > 0) {
		create(num - 1);
		count++;
		//printf("%d\n",count);      //測試
	}
	else {
		signal(10, stop);
		printf("新創建的子進程PID=%d\n", getpid());
		mask = 1;
		while (mask);/*持續循環,等待終止信號10*/
		printf("%d killed by parent\n",getpid());
		exit(100);
	}
}
void MyKill(char** argv) {
	int i = 1;
	while (i < n) {	/*向命令中出現的所有Pid進程發送信號10*/
		
		kill(atoi(argv[i]), 10);/* 10 用戶自定義信號,我定義爲終止信號*/
		i++;
	}
	//sleep(1); //測試
}
void Exit() {/* 命令4 */
	if (count <= 0)
		exit(0);
	else {
		printf("error!還有子進程沒有終止\n");
	}
}
void stop() {/*子進程停止循環*/
	mask = 0;
}
void waitBack() {/*父進程回收子進程資源,並顯示子進程的pid*/
	int a;
	while ((a = waitpid(-1, NULL, 0)) > 0) {
		printf("終止的子進程PID=%d\n", a);
		count--;
		//printf("%d\n",count); //測試
	}
}

總結

任務 Value
任務一 考察對進程的創建和族親結構的掌握,一般 來說如果不太確定的話,可以在紙上做個草圖,主要是熟悉fork()函數的使用。
任務二 通過調用fgets函數來獲取用戶輸入指令,然後參照shellex.c的思想,調用parseline函數將這行輸入切割成多個字符串;即解析指令;然後尋找命令文件,執行指令。按照題目要求設置兩個內置命令:“exit”或“log out”,接收到則退出shell。最後就是設置兩個函數MYdup1和MYdup2,來支持輸出重定向和管道功能。
任務三 這個信號發送的章節掌握的不是很到位,所以費了不少功夫。主要是理解幾個新函數,signal->指明當前進程遇到信號(參數1)時,該怎麼處理(參數2)。Wait(0)->停止向下運行,等待子進程終止。Kill()->向pid(參數1)的進程發送信號(參數2),alram()->在(參數一)/秒後產生時鐘信號,然後是自制函數,waiting()->持續執行,知道wait_mask變成0,而stop函數就是把wait_mask置0.
任務四(可選) 最後寫出來的程序還是有bug的,最嚴重的bug是有時候shell終端就會癱瘓不接受stdin的數據,導致命令shell無法向下運行,所以最後只好ctrl+\中斷,但還是大部分時候還是可以運行的,苟蒻技拙,找了整整一天也沒能發現問題所在,但總體的業務邏輯大體上是合乎情理的,爭取有空時再彌補一下,唉,還是太懶了-- _- -,就怕沒有有空的時候了。(不努力的苦果。。。)

ps:以上代碼的頭文件均有“wrapper.h”,如果需要可私信

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章