任務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:功能是創建一批子進程,格式爲“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”,如果需要可私信