任务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”,这是一个万能头文件,如果需要可去本人的下载资源查找,但设置的0分下载被系统修改了,如果没有积分的同学可以私信我,但是作者很懒,说不准哪个时候上博客,只能说尽量吧。-_-!