MIT6.S081Lab1: Xv6 and Unix utilities

2023-10-18 21:44
文章标签 unix mit6 xv6 utilities s081lab1

本文主要是介绍MIT6.S081Lab1: Xv6 and Unix utilities,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

MIT6.S081 Lab1: Xv6 and Unix utilities

官方文档

一.Boot xv6

如何成功的boot xv6可以看之前的文章MIT6.S081实验环境搭建,只是多一个步骤,在clone的文件夹中执行

git checkout util

切换为util分支即可。

二.sleep

在user/sleep.c中编写程序完成用户可以指定tricks数目休眠的sleep程序。

step1

检查参数,转换参数

#include "kernel/types.h"
#include "user.h"int main(int argc, char* argv[]) {// 检查参数数量if (argc != 2) {fprintf(2, "输出参数数量错误!\n");exit(1);}// 转换参数数量int times = atoi(argv[1]);exit(0);
}

step2

调用user中的sleep从而触发系统调用

#include "kernel/types.h"
#include "user.h"int main(int argc, char* argv[]) {// 检查参数数量if (argc != 2) {fprintf(2, "输出参数数量错误!\n");exit(1);}// 转换参数数量int times = atoi(argv[1]);sleep(times);exit(0);
}

step3

进行xv6中运行sleep可观察到明显的延迟

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在对应文件夹命令行执行

sudo ./grade-lab-util sleep

可看到

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

编写成功!

三.pingpong

父子进程互相发送一个字节并打印内容输出。

step1

创建两个管道

#include "kernel/types.h"
#include "user.h"int main() {// 用于接收一个字节char buf[1];// 两个管道,p1用于父写子读,p2用于子写父读int p1[2], p2[2];pipe(p1);pipe(p2);exit(0);
}

step2

fork一个子进程

#include "kernel/types.h"
#include "user.h"int main() {// 用于接收一个字节char buf[1];// 两个管道,p1用于父写子读,p2用于子写父读int p1[2], p2[2];pipe(p1);pipe(p2);int pid = fork();if (pid > 0) {} else if (pid == 0) {} else {printf(2, "fork error!\n");}exit(0);
}

step3

互相传递一个字节,注意写完后要及时关闭否则read会一直阻塞。

#include "kernel/types.h"
#include "user.h"int main() {// 用于接收一个字节char buf[1];// 两个管道,p1用于父写子读,p2用于子写父读int p1[2], p2[2];pipe(p1);pipe(p2);int pid = fork();if (pid > 0) {// 发送字节write(p1[1], "1", 1);close(p1[1]);// 接收字节read(p2[0], buf, 1);fprintf(1, "%d: received pong\n", getpid());exit(0);} else if (pid == 0) {read(p1[0], buf, 1);fprintf(1, "%d: received ping\n", getpid());write(p2[1], "1", 1);close(p2[1]);exit(0);} else {fprintf(2, "fork error!\n");exit(1);}exit(0);
}

step4

在xv6中运行可看到

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在对应文件夹命令行中执行

sudo ./grade-lab-util pingpong

可看到

!外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

编写成功!

四.primes

利用fork和pipe组成一个pipeline,并进行2到35的素数筛选。

具体的解决方法可见官方教程。总结来说,就是从左邻居读取的第一个数打印出来作为自己的基数,此后循环从左邻居读取数,判断是否可以被基数整除,如果可以说明不是素数,如果不可以则移交给右邻居。循环这个过程就可以把素数筛选出来。

有两个关键点:1,左右邻居之间怎么通信;2,及时地关闭不需要的文件描述符,否则xv6支持不了这么多文件描述符。

左右邻居之间怎么通信:所谓左右邻居就是父子进程。对于每个素数,我们都要创建一个进程,也就是每个进程(除了最后一个)都要fork一次,在fork前我们要创建管道用于通信,但子进程并不知道管道的文件描述符,所以我们要把管道的读端传给子进程,而子进程又要复刻父进程的行为,很自然就想到要用递归函数,函数的参数就是管道的读端文件描述符。

及时地关闭文件描述符:fork之后父进程用不到读端关闭,子进程用不到写端关闭,子进程用不到父进程本来有的之前的读端关闭

然后注意每个进程只fork一次,如果fork了之后的循环就要判断不fork了。

其实这个最主要的就是利用fork和pipe组成一个pipeline,也就是每个线程要有一个读端一个写端,从父进程那里读,写向子进程,不需要的文件描述符关闭,读端来自于父进程的开辟,写端来自于自己的开辟,然后每个子进程要重复父进程的行为,所以用递归函数,参数传递的读端。

代码如下:

#include "kernel/types.h"
#include "user.h"// 每个进程都要执行的操作
void ProcessOperate(int listen_fd) {// 把第一个读取的数作为基数并打印出来int base = -1;if (read(listen_fd, &base, 4) == -1) {fprintf(2, "%d read listen_fd error!\n", getpid());exit(-1);}fprintf(1, "prime %d\n", base);int is_fork = 0; // 判断当前线程是否已经fork过// 持续读取数据并处理int num = -1;int pipes[2]; // 用于父子进程通信while (read(listen_fd, &num, 4)) {if (num % base != 0) {if (!is_fork) {is_fork = 1;if (pipe(pipes) == -1) {fprintf(2, "%d process create pipe error!\n", getpid());exit(-1);}int pid = fork();if (pid > 0) {close(pipes[0]);} else if (pid == 0) {close(pipes[1]);close(listen_fd);ProcessOperate(pipes[0]);} else {fprintf(2, "%d process fork error\n", getpid());exit(-1);}}write(pipes[1], &num, 4);}}close(listen_fd);close(pipes[1]);wait(0);exit(0);
}int main() {int pipes[2];if (pipe(pipes) == -1) {fprintf(2, "main process create pipe error!\n");exit(-1);}// 先把所有数据写到第一个管道里面for (int i = 2; i <= 35; ++i) {if (write(pipes[1], &i, 4) == -1) {fprintf(2, "main process write pipe error!\n");exit(-1);}}// 关闭不需要的写端close(pipes[1]);// 传递读端,从第一个进程开始筛选素数ProcessOperate(pipes[0]);exit(0);
}

在命令行中运行

sudo ./grade-lab-util primes

可看到

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

成功!

五.find

写一个简化版本的UNIX的find程序:查找目录树中具有特定名称的所有文件。

这个程序其实只要看懂了ls程序是怎么写的基本就会写了。

具体的思路就是对我们输入的目录中的文件或目录进行循环遍历,当遍历到文件,就比较文件的名字和我们要找的名字;当遍历到目录,就递归这个过程。其中用到的函数接口在ls中也会用到,所以看懂了ls程序就知道那些接口怎么用了。

有几点需要注意的地方:

  • 比较文件的名字和我们要找的名字的时候,我们需要提取简化文件的名字,我们当然可以用ls中的fmtname函数,但是注意fmtname中有一个memset(buf+strlen§, ’ ', DIRSIZ-strlen§); 这个是我们不需要的,因为这个是补空方便输出,我们加上这个名字比较就不对了。
  • 每个目录开头的是两个目录".“和”…",如果不加以排除的话会无限递归,所以要注意
  • 对我来说还有一个,exit是让程序直接退出了,在函数里面还是用return(这个错误找了好久,晕)。

代码如下:

#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
#include "kernel/fs.h"// 获取名称
char* fmtname(char *path)
{static char buf[512];char *p;// 提取最后一个/跟的名字for(p=path+strlen(path); p >= path && *p != '/'; p--);p++;memmove(buf, p, strlen(p));buf[strlen(p)] = 0;return buf;
}void find(char *path, char *name) {char buf[512], *p;int fd;struct dirent de;struct stat st;// 打开目录if ((fd = open(path, 0)) < 0) {fprintf(2, "ls: cannot open %s!\n", path);exit(-1);}// 复制路径strcpy(buf, path);p = buf + strlen(buf);*p++ = '/';// 循环判断目录下的文件或目录while (read(fd, &de, sizeof(de)) == sizeof(de)) {if (de.inum == 0) {continue;}// 排除目录开头的.和..if (!strcmp(de.name, ".") || !strcmp(de.name, "..")) {continue;}// 把文件或目录名加到buf后面memmove(p, de.name, strlen(de.name));p[strlen(de.name)] = 0;if (stat(buf, &st) < 0) {fprintf(1, "ls: cannot stat %s \n", buf);continue;}switch(st.type) {case T_FILE:// 如果是文件直接比较if (!strcmp(fmtname(buf), name)) {fprintf(1, "%s\n", buf);}break;case T_DIR:// 目录递归find(buf, name);break;}}close(fd);return;}int main(int argc, char* argv[]) {// 调用函数进行递归查找if (argc < 3) {find(".", argv[1]);exit(0);}find(argv[1], argv[2]);exit(0);
}

六.xargs

编写一个简化版UNIX的xargs程序:它从标准输入中按行读取,并且为每一行执行一个命令,将行作为参数提供给命令。

说明白点,xargs就是执行命令,命令的参数即来自于自身的参数,也来自于标准输入,为标准输入中每一行都执行这样的命令和自身的参数。

那其实就很简单了,就直接从标准输入中读取数据,然后根据\n来划分行,每找到一行就fork一次让子进程exec来执行它。父进程调整一下指针的位置继续执行。

代码如下:

#include "kernel/types.h"
#include "kernel/param.h"
#include "user.h"int main(int argc, char* argv[]) {if (argc > MAXARG) {fprintf(2, "argc > MAXARG\n");exit(-1);}// xargs自身输入的命令及参数char* new_argv[MAXARG];for (int i = 0; i < argc - 1; ++i) {new_argv[i] = argv[i + 1];}char buf[32];char *p = buf;// 读取命令行的输入read(0, buf, 32);int pid;for (int i = 0; i < 32; ++i) {// 如果检测到换行符说明遇到了新的一行,那么就fork让子进程execif (buf[i] == '\n') {pid = fork();if (pid == 0) {// 设定结尾buf[i] = 0;// 设定标准输入作为一个参数new_argv[argc - 1] = p;exec(new_argv[0], new_argv);fprintf(2, "exec error\n");} else {// 更新指针,指向下一行p = &buf[i + 1];wait(0);}}}wait(0);exit(0);
}

在这里插入图片描述
在这里插入图片描述

本来我还有一个版本的,就是用while一个字节一个字节的读,但是死活报错,不知道为什么。感觉应该是对于换行符的判断有问题,所以还是这种直接把所有的标准输入读下来确保会结束的比较保险。

总结

这几个实验因为中间有其他事情,所以时间跨度还是挺长的,不过我觉得MIT6.S081的实验真的很有价值,之前可能用过这些指令,但是不清楚这些指令具体怎么实现的,在实现这些指令的过程我对fork,pipe,exec这些系统调用的使用更加熟练了,之前其实没怎么使用过这些系统调用。

期待下一个实验!

这篇关于MIT6.S081Lab1: Xv6 and Unix utilities的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/235306

相关文章

列举你能想到的UNIX信号,并说明信号用途

信号是一种软中断,是一种处理异步事件的方法。一般来说,操作系统都支持许多信号。尤其是UNIX,比较重要应用程序一般都会处理信号。 UNIX定义了许多信号,比如SIGINT表示中断字符信号,也就是Ctrl+C的信号,SIGBUS表示硬件故障的信号;SIGCHLD表示子进程状态改变信号;SIGKILL表示终止程序运行的信号,等等。信号量编程是UNIX下非常重要的一种技术。 Unix信号量也可以

【unix高级编程系列】线程

引言 我们知道unix进程中可以有多个线程,进程中的线程可以访问该进程的所有组成部分。并且CPU的调度单元就是线程。这就面临一个问题:当进程中的临界资源需要在多个线程中共享时,如何解决一致性问题? 本文将从线程的概念、线程的使用方式、unix提供哪些方式解决一致性问题进行介绍,加深对线程的理解。 线程概念 线程的优点: 简化代码结构。比如在业务上为每种事件类型分配单独的处理线程,可以

linux的nohup命令的用法。在应用Unix/Linux时,我们一般想让某个程序在后台运行,于是我们将常会用 在程序结尾来让程序自动运行。比如我们要运行mysql在后台: /usr/local

在应用Unix/Linux时,我们一般想让某个程序在后台运行,于是我们将常会用 & 在程序结尾来让程序自动运行。比如我们要运行mysql在后台: /usr/local/mysql/bin/mysqld_safe –user=mysql &。可是有很多程序并不想mysqld一样,这样我们就需要nohup命令,怎样使用nohup命令呢?这里讲解nohup命令的一些用法。 nohup /root/

【Unix编程】进程间通信(IPC)

进程间通信(IPC,InterProcess Communication)是指在不同进程之间传播或交换信息。IPC的方式通常有管道(包括无名管道和命名管道)、消息队列、信号量、共享存储、Socket、Streams等。其中 Socket和Streams支持不同主机上的两个进程IPC。 一、管道 管道,通常指无名管道,是 UNIX 系统IPC最古老的形式。 1、特点: 它是半双工

Unix与Linux的关系

Unix 和 Linux 之间有着密切的历史和技术联系,尽管它们是两个独立的操作系统。 Unix 的历史 Unix 的历史可以追溯到1969年,当时 AT&T 的贝尔实验室的工程师肯·汤普逊(Ken Thompson)和丹尼斯·里奇(Dennis Ritchie)开始开发 Unix 操作系统。Unix 设计之初是为了方便进行程序开发工作,它强调的是多用户、多任务的能力,以及良好的文件系统支持。

【docker无法启动】 can't create unix socket /var/run/docker.sock: is a directory

一次重启docker后出现的问题 Oct 18 19:18:20 worker systemd[1]: Starting Docker Application Container Engine...Oct 18 19:18:20 worker1 dockerd-current[118257]: time="2018-10-18T19:18:20.734668371+08:00" level=w

在 Linux 和类 Unix 系统中,终端(Terminal)和 Shell

在 Linux 和类 Unix 系统中,终端(Terminal)和 Shell 是两个相关但不同的概念。以下是它们的定义和关系: 1. 终端(Terminal) 终端 是一个用于与计算机交互的用户界面。它可以是一个物理设备(如早期的硬件终端)或一个软件应用程序(如现代的终端模拟器)。终端提供了一个输入和输出的窗口,用户可以在其中输入命令,并查看计算机的响应。 物理终端: 在早期计算机中,物

Linux和Unix的区别及为什么鸿蒙系统不用Unix的原因

目录 Linux是什么? Unix是什么? 他们的区别: 鸿蒙系统介绍及鸿蒙系统不用Unix的原因 Linux是什么? Linux的历史可以追溯到1991年,由芬兰的计算机科学家林纳斯·托瓦兹(Linus Torvalds)为了学习操作系统的工作原理而开始编写的个人项目。Linux的起源与Unix操作系统有着密切的联系,Unix最初由Ken Thompson和Denni

Unix环境高级编程开篇-apue.h配置

书就不多说了,被称为Unix下C编程的圣经;不过现在国内貌似部分人都喜欢向别人推荐书,我很怀疑着部分人是不是推荐的每一本都看过。这个我暂时也不敢推荐,因为我也没有看完。 这本书上几乎所有的代码都用到了作者编程的一个头文件:apue.h,但是这个不是ISO C自带的,所以需要配置一下。 我用的这本书是第三版,第三版,第三版 重要的事情说三遍 1:先去这本书的官网把源代码下载下来,传送门 2:

linux/UNIX体系结构图

以上是linux结构图 以下是UNIX体系结构图 <script>window._bd_share_config={"common":{"bdSnsKey":{},"bdText":"","bdMini":"2","bdMiniList":false,"bdPic":"","bdStyle":"0","bdSize":"