目录
- 一、目的
- 二、内容与设计思想
- 三、使用环境
- 四、实验过程
- 1、实现的主要函数:
- 1)int parseline(const char *cmdline, char **argv);
- 2)void exeCommand(char *cmdline);
- 3)void pipeline(char *process1[],char *process2[]);
- 4)int builtin_cmd(char **argv);
- 5)void getkinfo();
- 6)int print_memory();
- 7)void get_procs();
- 8)void parse_dir()
- 9)void parse_file(pid_t pid);
- 10)u64_t cputicks(struct proc *p1, struct proc *p2, int timemode);
- 11)void print_procs(struct proc *proc1,struct proc *proc2,int cputimemode);
- 2、实验结果
一、目的
理解shell程序的设计方法,系统编程,实现一个基本的shell。
二、内容与设计思想
1、shell主体
shell主体结构是一个while循环,不断接受用户键盘输入行并给出反馈。shell将输入的命令行进行解析,根据命令名称分为两类分别处理,即shell内置命令和program命令。识别为shell内置命令后,执行对应操作。接受program命令后,利用Minix自带的程序创建一个或多个新进程,并等待进程结束。如果末尾包含&参数,则为后台任务,shell不等待进程结束,直接返回。
2、shell内置命令
(1)cd +路径名,改变工作路径
shell本身是一个程序,启动时Minix会分配一个当前工作目录,利用chdir系统调用改变shell的工作目录,并调用getcwd函数获取当前工作目录的路径。
(2)history n,显示最近执行的n条指令
利用一个二维数组保存shell每次输入的命令行,根据指令打印出相应数量的行。
(3)exit,shell退出
退出shell的while循环,结束shell的main函数。
(4)mytop,输出内存使用情况和CPU使用百分比
在minix系统/proc文件夹中通过fopen/fscanf获取进程信息。
3、program命令
(1)运行程序
利用fork调用创建子进程,利用execvp调用加载并运行,对shell中的命令进行处理,处理完之后新进程结束,shell利用wait/waitpid调用等待进程结束并回收。
(2)重定向
重定向输入(>):
调用open得到文件描述符fd,再调用dup2(fd,1)函数,将文件描述符映射到标准输出。最后,调用execvp运行程序。
重定向输出(<):
调用open得到文件描述符fd,再调用dup2(fd,0)函数,将文件描述符映射到标准输入。最后,调用execvp运行程序。
(3)管道
利用pipe函数创建一个管道fd[2],再fork一个子进程,在子进程中调用dup2(fd[1],1)函数将管道写端fd[1]映射到标准输出,调用execvp运行程序,将进程的输出写入管道。在父进程中,等待子进程结束并回收,再调用dup2(fd[0],0)函数将管道读端fd[0]映射到标准输入,从管道中读入数据,并执行。
(4)后台运行
将子进程的标准输入、输出映射到/dev/null,屏蔽键盘和控制台。调用signal(SIGCHLD, SIG_IGN),使得Minix接管此进程,shell不用等待子进程结束直接运行下一条命令。
三、使用环境
物理机:Windows10
虚拟机:Minix3
虚拟机软件:Vmware
四、实验过程
1、实现的主要函数:
函数 | 功能 |
---|---|
void exeCommand(char *cmdline); | 实现内置命令、program命令和后台运行等功能。 |
int parseline(const char *cmdline, char **argv); | 解析命令行,得到参数序列,并判断是前台作业还是后台作业 |
void pipeline(char *process1[],char *process2[]); | 实现管道,完成进程间的通信。 |
int builtin_cmd(char **argv); | 实现内置命令exit、history n、cd、mytop的具体实现。 |
void getkinfo(); | 在/proc/kinfo中查看进程和任务的数量 |
int print_memory(); | 在/proc/meminfo中查看内存信息,计算出内存大小并打印 |
void get_procs(); | 创建struct proc数组,为每个任务分配一个struct proc结构体,保存信息 |
void parse_dir(); | 读取目录下每一个文件信息,并获得进程号 |
void parse_file(pid_t pid); | 在/proc/pid/psinfo中,查看进程pid的信息,并保存每个文件对应的结构体struct proc中 |
u64_t cputicks(struct proc *p1, struct proc *p2, int timemode); | 利用时间差,计算每个任务、进程的cputicks |
void print_procs(struct proc *proc1,struct proc *proc2,int cputimemode); | 计算总体CPU使用占比并打印结果 |
1)int parseline(const char *cmdline, char **argv);
利用strtok函数,根据空格对命令行进行划分,得到argv参数序列。同时判断命令行最后是否带有参数’&’,若有则为后台作业,反之,则为前台作业。
2)void exeCommand(char *cmdline);
· 调用parseline函数解析命令行并判断前、后台作业。
· 调用builtin_cmd函数判断是否为内置命令,若是内置命令则执行相应的内置命令操作;若不是内置命令,则返回继续执行。
· 判断命令行中是否包含” > ”, “ < ”, “ | ”,“&”。在利用switch语句,对每种情况做出相应的处理。
(1)命令不包含管道、重定向、后台运行
· 调用fork函数,创建一个新的子进程
· 在子进程中,调用execvp函数运行程序
· 在父进程中,调用waitpid函数,父进程等待子进程结束并回收
pid = Fork();
if (pid == 0)
{
execvp(argv[0], argv);
exit(0);
}
if (waitpid(pid, &status, 0) == -1)
{
printf("wait for child process error\n");
}
(2)命令包含重定向输出
· 利用参数列表,得到重定向符后的文件名file
· 调用fork函数,创建一个新的子进程
· 在子进程中调用open函数得到file的文件描述符fd;再调用dup2(fd, 1),将file映射到标准输出;最后,调用execvp执行重定向符前的指令
· 在父进程中,调用waitpid函数,父进程等待子进程结束并回收
pid = Fork();
if (pid == 0)
{
fd = open(file, O_RDWR | O_CREAT | O_TRUNC, 0644);
if (fd == -1)
{
printf("open %s error!\n", file);
}
dup2(fd, 1);
close(fd);
execvp(argv[0], argv);
exit(0);
}
if (waitpid(pid, &status, 0) == -1)
{
printf("wait for child process error\n");
}
(3)命令包含重定向输入
· 利用参数列表,得到重定向符后的文件名file
· 调用fork函数,创建一个新的子进程
· 在子进程中,调用open函数得到file的文件描述符fd;再调用dup2(fd,0),将file映射到标准输入;最后,调用execvp执行重定向符前的指令
· 在父进程中调用waitpid函数,父进程等待子进程结束并回收
pid = Fork();
if (pid == 0)
{
fd = open(file, O_RDONLY);
dup2(fd, 0);
close(fd);
execvp(argv[0], argv);
exit(0);
}
if (waitpid(pid, &status, 0) == -1)
{
printf("wait for child process error\n");
}
(4)命令包含管道
· 利用参数列表,得到管道符之前的命令参数和管道符之后的命令参数
· 调用fork函数,创建一个新的子进程
· 在子进程中调用辅助函数pipeline实现管道
· 在父进程中,调用waitpid函数,父进程等待子进程结束并回收
if((pid = fork()) < 0){
printf("fork error\n");
return ;
}
if (pid == 0)
{
pipeline(argv,leftargv);
}
else
{
if (waitpid(pid, &status, 0) == -1)
{
printf("wait for child process error\n");
}
}
(5)后台任务
· 调用函数fork函数,创建一个新的子进程
· 子进程调用signal(SIGCHLD, SIG_IGN),使Minix接管此进程;在调用open函数得到”/dev/null/”的文件描述符,并映射到标准输入输出;最后execvp执行命令
pid = Fork();
if (pid == 0)
{
signal(SIGCHLD, SIG_IGN);
int a = open("/dev/null", O_RDONLY);
dup2(a, 0);
dup2(a, 1);
execvp(argv[0], argv);
exit(0);
}
3)void pipeline(char *process1[],char *process2[]);
· 调用pipe函数创建一个管道fd[2]
· 调用fork函数创建一个子进程
· 在子进程中,分别调用函数close(fd[0])和close(1)关闭管道读端fd[0]和文件描述符1;再调用dup(fd[1])将管道的写端映射到标准输出;然后,调用函数close(fd[1])关闭管道写端,避免堵塞;最后,调用execvp执行管道前部分指令,其结果将输出到管道中。
· 在父进程中,分别调用函数colse(fd[1])和close(0)关闭管道写端fd[1]和文件描述符0;再调用dup(fd[0])将管道的读端映射到标准输入;然后,调用函数close(fd[0])关闭管道读端,避免堵塞;最后,调用execvp执行管道后部分指令,从管道中读入数据。
void pipeline(char *process1[],char *process2[]){
int fd[2];
pipe(&fd[0]);
int status;
pid_t pid;
pid=Fork();
if(pid==0){
close(fd[0]);
close(1);
dup(fd[1]);
close(fd[1]);
execvp(process1[0],process1);
}else{
close(fd[1]);
close(0);
dup(fd[0]);
close(fd[0]);
execvp(process2[0],process2);
}
}
4)int builtin_cmd(char **argv);
(1)exit
· exit(0),退出shell的while循环,结束shell的main函数
if (!strcmp(argv[0], "exit"))
{
exit(0);
}
(2)cd
· 调用chdir函数,改变工作目录
· 调用getcwd函数,获取当前所在目录
if (!strcmp(argv[0], "cd"))
{
if (!argv[1])
{
argv[1] = ".";
}
int ret;
ret = chdir(argv[1]); //改变工作目录
if (ret < 0)
{
printf("No such directory!\n");
}
else
{
path = getcwd(NULL, 0); //利用getcwd取当前所在目录
}
return 1;
}
(3)history n
· 在main函数中将输入的指令保存在二维数组history中
· 根据参数n,打印出最近输入的n条指令;若仅输入history未带参数n则打印出所有历史指令;若n大于历史指令数量则输出错误信息history error
if (!strcmp(argv[0], "history"))
{
if (!argv[1])
{ //当只输入history时,打印已有的所有指令
for (int j = 1; j <= n_his; j++)
{
printf("%d ", j);
puts(history[j - 1]);
}
}
else
{
int t = atoi(argv[1]);
if (n_his - t < 0)
{ //如果history后未带参数或带的参数大于已有指令数
printf("history error\n");
}
else
{
for (int j = n_his - t; j < n_his; j++)
{
printf("%d ", j + 1);
puts(history[j]);
}
}
}
return 1;
}
(4)mytop
· 调用辅助函数getkinfo,查看进程和任务总数
· 调用辅助函数print_memory,查看内存信息,从计算出总体内存大小、空闲内存大小、缓存大小
· 根据进程和任务总数,分别调用两次辅助函数get_procs为每一个进程和结构体分配一个struct proc结构体,得到两个struct proc数组proc1、proc2
· 调用辅助函数print_procs,打印出CPU使用百分比情况
if (!strcmp(argv[0], "mytop"))
{
int cputimemode = 1;
getkinfo();
print_memory();
//得到prev_proc
get_procs();
if (prev_proc == NULL)
{
get_procs();//得到proc
}
print_procs(prev_proc, proc, cputimemode);
return 1;
}
5)void getkinfo();
调用fopen函数打开文件”/proc/kinfo”,读入进程和任务数量,相加得到进程和任务总数nr_total。
if ((fp = fopen("/proc/kinfo", "r")) == NULL)
{
fprintf(stderr, "opening /proc/kinfo failed\n");
exit(1);
}
if (fscanf(fp, "%u %u", &nr_procs, &nr_tasks) != 2)
{
fprintf(stderr, "reading from /proc/kinfo failed");
exit(1);
}
fclose(fp);
nr_total = (int)(nr_procs + nr_tasks);
6)int print_memory();
调用fopen函数打开文件”/proc/meminfo”,查看内存信息,分别读入页面大小pagesize、总页数量total、空闲页数量free、最大页数量largest、缓存页数量cached。根据公式算出内存大小,同理可算出其他页内存的大小,并打印。
if ((fp = fopen("/proc/meminfo", "r")) == NULL)
{
return 0;
}
if (fscanf(fp, "%lu %lu %lu %lu %lu", &pagesize, &total, &free, &largest, &cached) != 5)
{
fclose(fp);
return 0;
}
fclose(fp);
printf("main memory: %ldk total,%ldk free,%ldk contig free,%ldk cached\n", (pagesize * total) / 1024,
(pagesize * free) / 1024, (pagesize * largest) / 1024, (pagesize * cached) / 1024);
7)void get_procs();
根据进程和任务总数nr_total,为每个任务和进程都分配一个struct proc结构体来保存相关信息,得到一个struct proc数组proc。
proc = malloc(nr_total * sizeof(proc[0])); //struct proc的大小
if (proc == NULL)
{
fprintf(stderr, "Out of memory!\n");
exit(0);
}
8)void parse_dir()
调用opendir函数打开目录”/proc”,调用readdir函数读取目录下每个文件的信息,并保存在每个文件对应的结构体struct proc中。每个文件都会得到一个struct dirent结构体的返回值储存了文件信息,结构体成员包括索引节点号、在目录文件中的偏移、文件名长、文件类型、文件名。再调用strtol函数由文件名获取进程号,最后调用parse_file函数查看进程信息。循环执行以上步骤,直至目录中的所有文件都被读取。
9)void parse_file(pid_t pid);
调用fopen函数打开文件”/proc/pid/psinfo”,查看进程pid的信息。依次读入版本version,类型type,端点endpt,名字name,状态state,阻塞状态blocked,动态优先级priority,滴答ticks,高周期highcycle,低周期lowcycle,内存memory,有效用户ID effuid等。利用&proc[slot]获取相应的struct proc,并将信息存储在结构体中。
若type是task则将struct proc的结构体成员p_flags倒数第二位标记为1(p->p_flags |= IS_TASK),若type是system则将p_flags倒数第三位标记为1(p->p_flags |= IS_SYSTEM)。若进程状态state不是在运行状态则将p_flags倒数第四位标记为1(p->p_flags |=BLOCKED)。
然后,调用make64(cycles_lo, cycles_hi)根据高低频计算出cputicks。最后,将p->flags最后一位标记为1,表示已访问过该进程信息(p->p_flags |=USED)。
10)u64_t cputicks(struct proc *p1, struct proc *p2, int timemode);
(p2->p_cpucycles[i] — p1->p_cpucycles[i])计算出task i在一段时间内的cputicks
if (p1->p_endpoint == p2->p_endpoint)
{
t = t + p2->p_cpucycles[i] - p1->p_cpucycles[i];
}
else
{ //否则t直接加上p2
t = t + p2->p_cpucycles[i];
}
11)void print_procs(struct proc *proc1,struct proc *proc2,int cputimemode);
对struct proc数组中每个proc,调用函数cputicks计算他们的CPU时钟周期。并将数组中每个任务的cputicks累加得到total cputicks。在利用p_flags标记来判断是systemticks还是usertickes。最后分别除以total cputicks得到CPU使用百分比。
2、实验结果
完整代码与测试文件: https://github.com/RachelllYe/OSProject
欢迎探讨与指正,谢谢!