MIT S6081 2024 Lab 1 | Operating System | Notes

目录

安装与下载

实验1

开始我们的实验

sleep(简单)

pingpong(简单)

primes (中等)/(困难)

find(中等)

xargs(中等)

finally

Reference

I. Tools

Debian 或 Ubuntu

Arch Linux

在 Windows 上安装

运行 Linux VM

在 macOS 上安装

Athena

测试您的安装

其他参考


笔者最近打算小小的开始重新温习一下MIT实验,打牢自己操作系统的知识基础。

安装与下载

https://pdos.csail.mit.edu/6.S081/2024/tools.html

这里是麻省理工官方的下载指南,附文里附上了笔者的翻译。可以参考。笔者建议是使用Arch Linux WSL来完成这个实验。

关于ArchWSL,参考项目:GitHub - yuk7/ArchWSL: ArchLinux based WSL Distribution. Supports multiple install.

关于配置一份Arch开发环境,可以借鉴笔者之前写的博客:Arch Linux With KDE6(x11)安装小记-CSDN博客

实验1

$ git clone git://g.csail.mit.edu/xv6-labs-2024
Cloning into 'xv6-labs-2024'...
...
$ cd xv6-labs-2024

配置实验是很简单的。只需要从github上下载我们的实验代码,配好了必备的依赖就可以开始猛猛开冲了。

运行我们的虚拟机也很简单:

make qemu
qemu-system-riscv64 -machine virt -bios none -kernel kernel/kernel -m 128M -smp 3 -nographic -global virtio-mmio.force-legacy=false -drive file=fs.img,if=none,format=raw,id=x0 -device virtio-blk-device,drive=x0,bus=virtio-mmio-bus.0
​
xv6 kernel is booting
​
hart 2 starting
hart 1 starting
init: starting sh

注意,想要退出虚拟机只需要摁住Crtl A + X就可以退出去了。

xv6 kernel is booting
​
hart 1 starting
hart 2 starting
init: starting sh
$ ls
.              1 1 1024
..             1 1 1024
README         2 2 2403
xargstest.sh   2 3 93
cat            2 4 34176
echo           2 5 33072
forktest       2 6 16216
grep           2 7 37440
init           2 8 33552
kill           2 9 33008
ln             2 10 32824
ls             2 11 40280
mkdir          2 12 33072
rm             2 13 33048
sh             2 14 54648
stressfs       2 15 33944
usertests      2 16 179480
grind          2 17 49080
wc             2 18 35048
zombie         2 19 32424
console        3 20 0
$ QEMU: Terminated

开始我们的实验

在实验开始之前,我仍然建议各位看官复习温习一下xv6 book这本书,他入门的介绍了整个实验所用到的操作系统的知识。

笔者这里假设各位已经对xv6的情况有了基本的了解。

sleep(简单)

为 xv6 实现一个用户级 sleep 程序,类似于 UNIX sleep 命令。您的 sleep 应该暂停用户指定的滴答数。滴答是 xv6 内核定义的时间概念,即定时器芯片两次中断之间的时间。您的解决方案应该在文件 user/sleep.c 中。

一些提示:

在开始编码之前,请阅读 xv6 书的第 1 章。

  • 将您的代码放入 user/sleep.c。查看 user/ 中的其他一些程序(例如,user/echo.c、user/grep.c 和 user/rm.c)以了解如何将命令行参数传递给程序。

  • 将您的 sleep 程序添加到 Makefile 中的 UPROGS;完成此操作后,make qemu 将编译您的程序,您将能够从 xv6 shell 运行它。

  • 如果用户忘记传递参数,sleep 应该会打印一条错误消息。

  • 命令行参数以字符串形式传递;您可以使用 atoi 将其转换为整数(请参阅 user/ulib.c)。

  • 使用系统调用 sleep。

  • 请参阅 kernel/sysproc.c 了解实现 sleep 系统调用的 xv6 内核代码(查找 sys_sleep),请参阅 user/user.h 了解可从用户程序调用的 sleep 的 C 定义,并参阅 user/usys.S 了解从用户代码跳转到内核进行 sleep 的汇编代码。

  • sleep 的 main 应在完成后调用 exit(0)。

我们首先就要看看我们的sleep函数,大多数使用过Linux的朋友都知道:sleep就是促使当前进程悬挂指定的秒数,也就是放弃CPU指定长的时间。

我们随意的看看其他的程序,做一个简单的分析,比如说我们的cat程序:

#include "kernel/types.h"
#include "kernel/fcntl.h"
#include "user/user.h"
​
char buf[512];
​
void
cat(int fd)
{int n;
​while((n = read(fd, buf, sizeof(buf))) > 0) {if (write(1, buf, n) != n) {fprintf(2, "cat: write error\n");exit(1);}}if(n < 0){fprintf(2, "cat: read error\n");exit(1);}
}
​
int
main(int argc, char *argv[])
{int fd, i;
​if(argc <= 1){cat(0);exit(0);}
​for(i = 1; i < argc; i++){if((fd = open(argv[i], O_RDONLY)) < 0){fprintf(2, "cat: cannot open %s\n", argv[i]);exit(1);}cat(fd);close(fd);}exit(0);
}
​

这个程序是一个典型的遵循经典C规范的程序。笔者实际上不太喜欢这一风格。但是毫无疑问的,我们需要实现主函数一个,以及sleep的impl函数。

好在sleep这个函数本身并不复杂。实际上,我们只需要写一个套壳程序就好。但是为了理解,笔者打算严肃的遵从上面的指示一一分析。

我们知道,sleep是一个经典的syscall,在xv6中被实现到了:

uint64
sys_sleep(void)
{int n;uint ticks0;
​argint(0, &n);if(n < 0)n = 0;acquire(&tickslock);ticks0 = ticks;while(ticks - ticks0 < n){if(killed(myproc())){release(&tickslock);return -1;}sleep(&ticks, &tickslock);}release(&tickslock);return 0;
}

当然,我们可以先不用管细节,只需要知道我们的sleep就是向系统发起系统调用即可。或者,当你调试的时候会发现他跳转到了汇编,实际上调用的是函数sys_sleep,也就是上面的implement。

套壳函数写的很简单,笔者是这样写的:

#include "kernel/types.h"
#include "kernel/fcntl.h"
#include "user/user.h"
​
​
int inline sleep_impl(const int sleep_sc){return sleep(sleep_sc);
}
​
void inline help_usage(const char* app_name)
{printf("usage: %s <sleep_second>\n", app_name);
}
​
void inline invalid_args(){printf("you should submit a valid sleep second!\n");
}
​
int main(int argc, char* argv[])
{if(argc != 2){help_usage(argv[0]);exit(0);}
​int second = atoi(argv[1]);
​if(second <= 0){invalid_args();exit(0);}
​sleep_impl(second);
​exit(0);
}

记得在Makefile的UPROGS后面加上$_U/_sleep

    ...$U/_wc\$U/_zombie\$U/_sleep

测试通过,这个函数还是很简单的。

➜  ./grade-lab-util sleep
make: 'kernel/kernel' is up to date.
== Test sleep, no arguments == sleep, no arguments: OK (1.5s) 
== Test sleep, returns == sleep, returns: OK (1.1s) 
== Test sleep, makes syscall == sleep, makes syscall: OK (1.1s) 

pingpong(简单)

编写一个用户级程序,使用 xv6 系统调用通过一对管道在两个进程之间“ping-pong”一个字节,每个方向一个。父进程应该向子进程发送一个字节;子进程应该打印“<pid>: received ping”,其中 <pid> 是其进程 ID,将字节写入管道上的父进程,然后退出;父进程应该从子进程读取字节,打印“<pid>: received pong”,然后退出。您的解决方案应该在文件 user/pingpong.c 中。

一些提示:

  • 将程序添加到 Makefile 中的 UPROGS。

  • 您需要使用管道、fork、write、read 和 getpid 系统调用。

  • xv6 上的用户程序可用的库函数有限。您可以在 user/user.h 中看到列表;源代码(系统调用除外)位于 user/ulib.c、user/printf.c 和 user/umalloc.c 中。

从 xv6 shell 运行该程序,它应该会产生以下输出:

$ make qemu
...
init: Starting sh
$ pingpong
4: received ping
3: received pong
$

您的程序应该在两个进程之间交换一个字节并产生如上所示的输出。运行 make grade 进行检查。

这里设计到了管道的问题,还有线程的问题。笔者这里想要吐槽的是:不少人的代码没有考虑到竞争的问题,即printf会乱码输出,所以,我们理清一下就会发现这个实际上是一个同步任务,因此需要在子进程完成向文件写之后再允许父进程读取信息。

这样,实际上就是如下的代码:

#include "kernel/types.h"
#include "kernel/fcntl.h"
#include "user/user.h"
​
#define READ_INDEX      (0)
#define WRITE_INDEX     (1)
​
/*pipe1 here is the local pipe, pipe2 is remote
*/
void on_child_pipe(int pipe1[2], int pipe2[2])
{char buffer;close(pipe2[WRITE_INDEX]);read(pipe2[READ_INDEX], &buffer, sizeof(buffer));printf("%d: received ping\n", getpid());close(pipe1[READ_INDEX]);   write(pipe1[WRITE_INDEX], &buffer, sizeof(buffer));close(pipe2[READ_INDEX]);close(pipe1[WRITE_INDEX]);
}
​
/*pipe1 here is the local pipe, pipe2 is remote
*/
void on_parent_pipe(int pipe1[2], int pipe2[2])
{// parents should do write at firstchar buffer = 'C';close(pipe2[READ_INDEX]);write(pipe2[WRITE_INDEX], &buffer, sizeof(buffer));close(pipe1[WRITE_INDEX]);wait((void*)0);read(pipe1[READ_INDEX], &buffer, sizeof(buffer));
​printf("%d: received pong\n", getpid());close(pipe2[WRITE_INDEX]);close(pipe1[READ_INDEX]);
}
​
int main()
{int pipe_array_parent[2];int pipe_array_son[2];
​pipe(pipe_array_parent);pipe(pipe_array_son);
​int pid = fork();if(pid == 0){on_child_pipe(pipe_array_son, pipe_array_parent);}else{on_parent_pipe(pipe_array_parent, pipe_array_son);}exit(0);
}

现在可以测试这个代码,尝试一下。

➜  ./grade-lab-util pingpong
make: 'kernel/kernel' is up to date.
== Test pingpong == pingpong: OK (2.0s) (Old xv6.out.pingpong failure log removed)

primes (中等)/(困难)

使用管道和本页中间图片及周围文本中所示的设计,为 xv6 编写一个并发素数筛选程序。这个想法来自 Unix 管道的发明者 Doug McIlroy。您的解决方案应位于文件 user/primes.c 中。

您的目标是使用管道和 fork 来设置管道。第一个进程将 2 到 280 的数字输入到管道中。对于每个素数,您将安排创建一个进程,该进程通过管道从其左侧邻居读取,并通过另一个管道写入其右侧邻居。由于 xv6 的文件描述符和进程数量有限,因此第一个进程可以在 280 处停止。

一些提示:

  • 请小心关闭进程不需要的文件描述符,否则您的程序将在第一个进程达到 280 之前耗尽 xv6 的资源。 一旦第一个进程达到 280,它应该等到整个管道终止,包括所有子进程、孙进程等。因此,主 primes 进程应该仅在打印完所有输出并且所有其他 primes 进程退出后退出。

  • 提示:当管道的写入端关闭时,read 返回零。

  • 最简单的方法是直接将 32 位(4 字节)整数写入管道,而不是使用格式化的 ASCII I/O。

  • 您应该只在需要时在管道中创建进程。

  • 将程序添加到 Makefile 中的 UPROGS。

  • 如果编译器对函数 primes 给出无限递归错误,则可能必须声明 void primes(int) attribute((noreturn)); 以表明 primes 不会返回。

实际上

这个难度最大,笔者自己调试了半天,看了别人的代码(有写的复杂的,有写的简单的,甚至还有写错的直接搬上来(笔者认为有些误人子弟了)的,都不太适合),笔者自己写了一份,各位可以进行测试。值得注意的是笔者测试到的是280的范围,提示各位一些网传代码因为资源清理差导致系统资源耗尽返回非法值。所以各位需要做好资源清理,不用马上关掉!

下图就是关键。一句话说清楚思想:就是打印上一级传递过来的素数,将之作为除数,筛好其他的素数传递到下一级,直到自己的工作完成退出。

#include "kernel/types.h"
#include "kernel/fcntl.h"
#include "user/user.h"
#define LIMIT           (280)
#define READ_INDEX      (0)
#define WRITE_INDEX     (1)
​
/* This marks promissed the function that shell never recursive call */
/* maybe :) */
/* using in solving ERROR: infinite recursion detected  */
__attribute__((noreturn))
void do_find_prime(int left_pipe[2])
{// read from the left pipe// so close the write oneclose(left_pipe[WRITE_INDEX]);
​// now read the number transfer from the leftint prime;
​// clean up the sources neatedly :)if(read(left_pipe[READ_INDEX], &prime, sizeof(int)) != sizeof(int)){close(left_pipe[READ_INDEX]);exit(0);}
​printf("prime %d\n", prime);// do fork// create the write pipeint right_pipe[2];pipe(right_pipe);int pid = fork();if(pid != 0){// do for the next// in this way we are supposed to clean the old pipe resoursesclose(left_pipe[READ_INDEX]);do_find_prime(right_pipe);}else{// here we shell provide the sources that is prime// disable the read issue in hereclose(right_pipe[READ_INDEX]);// now find the numberint num;// if we can get the next number, we jugde the issuewhile(read(left_pipe[READ_INDEX], &num, sizeof(int))){if(num % prime != 0){// yes, it is the prime// write to pass the next onewrite(right_pipe[WRITE_INDEX], &num, sizeof(int));}}// close and clear the resoursesclose(right_pipe[WRITE_INDEX]);// close(left_pipe[READ_INDEX]);// wait the sub processwait((void*)0);}exit(0);
}
​
int main()
{int begin_pipe[2];pipe(begin_pipe);int pid = fork();if(pid == 0){do_find_prime(begin_pipe);}else{close(begin_pipe[READ_INDEX]);// we only write issuesfor(int begin = 2; begin <= LIMIT; begin++)write(begin_pipe[WRITE_INDEX], &begin, sizeof(int));close(begin_pipe[WRITE_INDEX]);wait((void*)0);}exit(0);
}

如何书写代码上面的流程我已经说清楚了。

find(中等)

为 xv6 编写一个 UNIX find 程序的简单版本:查找目录树中具有特定名称的所有文件。您的解决方案应该在文件 user/find.c 中。

一些提示:

  • 查看 user/ls.c 以了解如何读取目录。

  • 使用递归允许 find 进入子目录。

  • 不要递归到“.”和“..”。

  • 对文件系统的更改在 qemu 运行期间保持不变;要获得干净的文件系统,请运行 make clean,然后运行 make qemu。

  • 您需要使用 C 字符串。查看 K&R(C 书),例如第 5.5 节。

  • 请注意,== 不会像 Python 中那样比较字符串。改用 strcmp()。

  • 将程序添加到 Makefile 中的 UPROGS。

  • 您的解决方案应产生以下输出(当文件系统包含文件 b、a/b 和 a/aa/b 时):

$ make qemu
...
init: Starting sh
$ echo > b
$ mkdir a
$ echo > a/b
$ mkdir a/aa
$ echo > a/aa/b
$ find . b
./b
./a/b
./a/aa/b
$

这里的难点在于理解我们的程序,笔者建议的是理解一下ls.c先:

#include "kernel/types.h"   
#include "kernel/stat.h"    
#include "user/user.h"     
#include "kernel/fs.h"      
#include "kernel/fcntl.h"  
​
// fmtname 函数:根据给定的路径,返回文件或目录的名称。
// 它会从路径中提取出最后一部分(即文件名或目录名),并确保返回的名称长度为 DIRSIZ。
char* fmtname(char *path)
{static char buf[DIRSIZ+1];  // 用于存放文件名,最大长度为 DIRSIZchar *p;
​// 从路径的末尾开始,寻找最后一个斜杠 '/',以此来获取文件名部分for(p=path+strlen(path); p >= path && *p != '/'; p--);p++;  // 跳过斜杠,指向文件名的第一个字符
​// 如果文件名的长度大于等于 DIRSIZ,直接返回文件名if(strlen(p) >= DIRSIZ)return p;
​// 否则,返回一个被空格填充的文件名,确保长度为 DIRSIZmemmove(buf, p, strlen(p));  // 将文件名拷贝到 buf 中memset(buf+strlen(p), ' ', DIRSIZ-strlen(p));  // 填充空格,保证 buf 长度为 DIRSIZreturn buf;
}
​
// ls 函数:实现了简单的“列出目录”功能,类似 Unix 中的 `ls` 命令。
// 它根据路径类型(文件、目录或设备)列出文件或目录的相关信息。
void ls(char *path)
{char buf[512], *p;  // buf 用来存放路径信息int fd;              // 文件描述符struct dirent de;    // 目录项结构体,存放目录中的文件信息struct stat st;      // 文件状态结构体,存放文件或目录的属性信息
​// 尝试打开路径对应的文件或目录,如果打开失败,输出错误信息并返回if((fd = open(path, O_RDONLY)) < 0){fprintf(2, "ls: cannot open %s\n", path);  // 打开失败return;}
​// 获取文件或目录的状态信息if(fstat(fd, &st) < 0){fprintf(2, "ls: cannot stat %s\n", path);  // 获取失败close(fd);  // 关闭文件描述符return;}
​// 根据文件类型做不同处理switch(st.type){case T_DEVICE:  // 设备文件case T_FILE:    // 普通文件// 打印文件名、类型、inode号和文件大小printf("%s %d %d %d\n", fmtname(path), st.type, st.ino, (int) st.size);break;
​case T_DIR:  // 目录// 如果路径太长,无法容纳完整的文件名,则输出错误信息if(strlen(path) + 1 + DIRSIZ + 1 > sizeof buf){printf("ls: path too long\n");break;}strcpy(buf, path);  // 将路径复制到 buf 中p = buf + strlen(buf);  // p 指向路径末尾*p++ = '/';  // 添加斜杠,指向目录下的文件
​// 读取目录中的每一项while(read(fd, &de, sizeof(de)) == sizeof(de)){// 跳过无效的目录项(inum == 0)if(de.inum == 0)continue;
​// 构造完整的路径memmove(p, de.name, DIRSIZ);  // 将目录项的名称拷贝到路径后面p[DIRSIZ] = 0;  // 确保路径以 '\0' 结尾
​// 获取该文件或目录的状态信息if(stat(buf, &st) < 0){printf("ls: cannot stat %s\n", buf);  // 获取失败continue;}// 打印文件名、类型、inode号和文件大小printf("%s %d %d %d\n", fmtname(buf), st.type, st.ino, (int) st.size);}break;}close(fd);  // 关闭文件描述符
}

我们这里发现,实际上就是在是目录的时候发生递归就行,只需要提供我们的新路径就OK。这里笔者做一个尝试:

#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
#include "kernel/fs.h"
#include "kernel/fcntl.h"
​
#define BUF_LEN (512)
​
// functions make up the new path waiting to be anaylsis
void create_new_drient_path(char* op_src, const char* base, const char* new_dirent_name)
{if(strlen(base) + 1 + DIRSIZ + 1 > BUF_LEN){printf("ls: path too long, will not enum this dirent case...\n");return;}memset(op_src, 0, sizeof(char) * BUF_LEN);strcpy(op_src, base);char* index = op_src + strlen(op_src);*index++ = '/';memmove(index, new_dirent_name, strlen(new_dirent_name));index[DIRSIZ] = '\0';
}
​
void find_impl(const char* path, const char* target)
{// check the name, at this case we ensure this is the targetchar    buffer[512];
​struct stat     file_stat;struct dirent   dirent_handler;
​int fd = open(path, O_RDONLY);if(fd < 0){fprintf(2, "find: cannot open %s\n", path);return;}
​while(read(fd, &dirent_handler, sizeof(dirent_handler)) == sizeof(dirent_handler)){if( dirent_handler.inum == 0                ||  // no dirent herestrcmp(".", dirent_handler.name) == 0    ||  // is selfstrcmp("..", dirent_handler.name) == 0)      // is parent{continue;}
​create_new_drient_path(buffer, path, dirent_handler.name);if(stat(buffer, &file_stat) < 0){fprintf(2, "ls: cannot stat %s\n", buffer);close(fd);continue;}
​switch(file_stat.type){case T_FILE: // file is required direct compareif(strcmp(dirent_handler.name, target) == 0){printf("%s\n", buffer);}break;case T_DIR:find_impl(buffer, target);break;case T_DEVICE:  break; // device make no need to enum}} 
}
​
void inline inform_usage()
{printf( "usage: \n""find <path> <target>\n");
}
​
int main(int argc, char* argv[])
{if(argc != 3){inform_usage();exit(0);}
​struct stat file_stat;int file_handle = open(argv[1], O_RDONLY);if(fstat(file_handle, &file_stat) < 0){printf("Can not stat file: %s\n", argv[1]);exit(-1);}
​if(file_stat.type != T_DIR){fprintf(2, "The target is not a dirent!\n");close(file_handle);exit(-1);}
​const char* path = argv[1];const char* target = argv[2];
​// find(path, target);find_impl(path, target);exit(0);
}

#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
#include "kernel/param.h"
​
#define BUF_LEN (512)
#define NULL    ((void*)0)
​
​
int main(int argc, char* argv[])
{char from_stdin[BUF_LEN];int index_ptr = 1;char* new_args[MAXARG];
​if(argc < 2){fprintf(2, "usage: xargs <command>\n");exit(1);}
​if(argc + 1 >= MAXARG){printf("Too many args, not support yet!");exit(0);}
​while(argv[index_ptr])  // if it is not the terminations, we do copy{new_args[index_ptr - 1] = argv[index_ptr];index_ptr++;}
​new_args[argc] = NULL;
​while(1)    {int old_args_counter = 0;while(1)    // phase the arg from stdin{int len = read(0, &from_stdin[old_args_counter], sizeof(char));if(len == 0 || from_stdin[old_args_counter] == '\n') break;old_args_counter++;}
​if(old_args_counter == 0) // we have not read anything worth :)break; // break the outliar
​from_stdin[old_args_counter] = '\0'; // view as the terminationsnew_args[argc - 1] = from_stdin;
​// now do the fork actuallyint pid = fork();if(pid == 0) // as the children do the sub process{exec(new_args[0], new_args);exit(0);}else{wait(NULL);}}exit(0);
}

xargs(中等)

手搓一个XArgs,这个比较抽象,但本质上是对参数进行逐字节的解析。为 xv6 编写一个 UNIX xargs 程序的简单版本:它的参数描述要运行的命令,它从标准输入读取行,并运行每行的命令,将该行附加到命令的参数中。您的解决方案应位于文件 user/xargs.c 中。

以下示例说明了 xargs 的行为:

$ echo hello too | xargs echo bye
bye hello too
$

请注意,此处的命令是“echo bye”,附加参数是“hello too”,使命令“echo bye hello too”输出“bye hello too”。 请注意,UNIX 上的 xargs 进行了优化,它将一次向命令提供多个参数。我们不希望您进行此优化。要使 UNIX 上的 xargs 按照我们希望的方式运行,请在运行它时将 -n 选项设置为 1。例如

$ (echo 1 ; echo 2) | xargs -n 1 echo
1
2
$

一些提示:

  • 使用 fork 和 exec 在输入的每一行上调用命令。在父进程中使用 wait 等待子进程完成命令。

  • 要读取输入的每一行,请一次读取一个字符,直到出现换行符 ('\n')。

  • kernel/param.h 声明 MAXARG,如果您需要声明 argv 数组,这可能会很有用。

  • 将程序添加到 Makefile 中的 UPROGS。

  • 对文件系统的更改在 qemu 运行期间保持不变;要获得干净的文件系统,请运行 make clean,然后运行 make qemu。

  • xargs、find 和 grep 结合得很好:

$ find . b | xargs grep hello

将在“.”下面的目录中对名为 b 的每个文件运行“grep hello”。要测试 xargs 的解决方案,请运行 shell 脚本 xargstest.sh。您的解决方案应产生以下输出:

$ make qemu
...
init: Starting sh
$ sh < xargstest.sh
$ $ $ $ $ $ hello
hello
$ $

您可能需要返回并修复 find 程序中的错误。输出中有许多 $,因为 xv6 shell 没有意识到它正在处理来自文件而不是控制台的命令,并为文件中的每个命令打印一个 $。

下面是笔者参考了: MIT 6.S081 2020 LAB1记录 - 知乎 (zhihu.com)的一份解析。比较简陋

#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
#include "kernel/param.h"
​
#define BUF_LEN (512)
#define NULL    ((void*)0)
​
​
int main(int argc, char* argv[])
{char from_stdin[BUF_LEN];int index_ptr = 1;char* new_args[MAXARG];
​if(argc < 2){fprintf(2, "usage: xargs <command>\n");exit(1);}
​if(argc + 1 >= MAXARG){printf("Too many args, not support yet!");exit(0);}
​while(argv[index_ptr])  // if it is not the terminations, we do copy{new_args[index_ptr - 1] = argv[index_ptr];index_ptr++;}
​new_args[argc] = NULL;
​while(1)    {int old_args_counter = 0;while(1)    // phase the arg from stdin{int len = read(0, &from_stdin[old_args_counter], sizeof(char));if(len == 0 || from_stdin[old_args_counter] == '\n') break;old_args_counter++;}
​if(old_args_counter == 0) // we have not read anything worth :)break; // break the outliar
​from_stdin[old_args_counter] = '\0'; // view as the terminationsnew_args[argc - 1] = from_stdin;
​// now do the fork actuallyint pid = fork();if(pid == 0) // as the children do the sub process{exec(new_args[0], new_args);exit(0);}else{wait(NULL);}}exit(0);
}

finally

== Test sleep, no arguments == 
$ make qemu-gdb
sleep, no arguments: OK (4.9s) 
== Test sleep, returns == 
$ make qemu-gdb
sleep, returns: OK (1.1s) 
== Test sleep, makes syscall == 
$ make qemu-gdb
sleep, makes syscall: OK (1.1s) 
== Test pingpong == 
$ make qemu-gdb
pingpong: OK (1.1s) 
== Test primes == 
$ make qemu-gdb
primes: OK (1.6s) 
== Test find, in current directory == 
$ make qemu-gdb
find, in current directory: OK (1.2s) 
== Test find, in sub-directory == 
$ make qemu-gdb
find, in sub-directory: OK (1.2s) 
== Test find, recursive == 
$ make qemu-gdb
find, recursive: OK (1.6s) 
== Test xargs == 
$ make qemu-gdb
xargs: OK (1.8s) 
== Test xargs, multi-line echo == 
$ make qemu-gdb
xargs, multi-line echo: OK (1.1s) 

舒舒服服大满贯!完结撒小花!

Reference

I. Tools

对于本课程,您需要 QEMU 7.2+、GDB 8.3+、GCC 和 Binutils 的 RISC-V 版本。如果您在设置时遇到问题,请在办公时间前来或在 Piazza 上发帖。我们很乐意为您提供帮助!

Debian 或 Ubuntu
sudo apt-get install git build-essential gdb-multiarch qemu-system-misc gcc-riscv64-linux-gnu binutils-riscv64-linux-gnu

您可能需要运行 Ubuntu 24(或更高版本),以便 apt-get 安装足够新的 qemu。

Arch Linux
sudo pacman -S riscv64-linux-gnu-binutils riscv64-linux-gnu-gcc riscv64-linux-gnu-gdb qemu-emulators-full
在 Windows 上安装

鼓励运行 Windows 的学生在本地计算机上安装 Linux 或使用 WSL 2(适用于 Linux 2 的 Windows 子系统)。我们还鼓励学生安装 Windows 终端工具,而不是使用 Powershell/命令提示符。

要使用 WSL 2,首先请确保已安装适用于 Linux 的 Windows 子系统。然后从 Microsoft Store 添加 Ubuntu 24.04。之后,您应该能够启动 Ubuntu 并与机器交互。

重要提示:确保您正在运行 WSL 的第 2 版。WSL 1 不适用于 6.1810 实验室。要进行检查,请在 Windows 终端中运行 wsl -l -v 以确认已安装 WSL 2 和正确的 Ubuntu 版本。

要安装本课程所需的所有软件,请运行:

$ sudo apt-get update && sudo apt-get upgrade
$ sudo apt-get install git build-essential gdb-multiarch qemu-system-misc gcc-riscv64-linux-gnu binutils-riscv64-linux-gnu

从 Windows,您可以访问

“\\wsl$\”目录下的所有 WSL 文件。例如,Ubuntu 20.04 安装的主目录应位于“\\wsl$\Ubuntu-20.04\home\<username>\”
运行 Linux VM

如果您运行的操作系统不方便安装 RISC-V 工具,您可能会发现运行 Linux 虚拟机 (VM) 并在 VM 中安装工具很有用。安装 Linux 虚拟机分为两个步骤。首先,获取虚拟化平台;我们建议:

VirtualBox(适用于 Mac、Linux、Windows)— 下载页面 安装虚拟化平台后,获取所选 Linux 发行版的启动磁盘映像。

Ubuntu Desktop 是一种选择。 这将下载一个名为 ubuntu-20.04.3-desktop-amd64.iso 的文件。启动虚拟化平台并创建一个新的(64 位)虚拟机。使用 Ubuntu 映像作为启动磁盘;不同虚拟机的启动过程不同,但应该不会太难。

在 macOS 上安装

首先,安装开发者工具:

$ xcode-select --install

接下来,安装适用于 macOS 的软件包管理器 Homebrew:

$ /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

接下来,安装 RISC-V 编译器工具链:

$ brew tap riscv/riscv
$ brew install riscv-tools

brew 公式可能未链接到 /usr/local。您需要更新 shell 的 rc 文件(例如 ~/.bashrc)以将相应目录添加到 $PATH。

PATH=$PATH:/usr/local/opt/riscv-gnu-toolchain/bin

最后,安装 QEMU:

brew install qemu
Athena

我们强烈反对使用 Athena,因为过去在 Athena 上运行实验室时出现了很多问题。如果您必须使用 Athena,可以通过 athena.dialup.mit.edu 使用运行 Linux 的 MIT Athena 机器在实验室中工作。实验室所需的所有工具都位于 6.828 储物柜中。

ssh 进入其中一台 Athena 拨号机器并添加工具:

$ ssh {您的 kerberos}@athena.dialup.mit.edu
$ add -f 6.828

如果您使用 Athena,则必须使用 x86 机器;也就是说,uname -a 应该提到 i686 GNU/Linux 或 x86_64 GNU/Linux。

测试您的安装

要测试您的安装,您应该能够编译并运行 xv6。您可以按照第一个实验中的说明进行尝试。您还可以通过运行以下命令来仔细检查您的安装是否正确:

$ qemu-system-riscv64 --version

QEMU 模拟器版本 7.2.0以及至少一个 RISC-V 版本的 GCC:

$ riscv64-linux-gnu-gcc --version
riscv64-linux-gnu-gcc (Debian 10.3.0-8) 10.3.0
...
$ riscv64-unknown-elf-gcc --version
riscv64-unknown-elf-gcc (GCC) 10.1.0
...
$ riscv64-unknown-linux-gnu-gcc --version
riscv64-unknown-linux-gnu-gcc (GCC) 10.1.0
...

其他参考

[MIT6.S081-Lab1 Utilities 2021Fall] - duile - 博客园 (cnblogs.com)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/64423.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

华为认证HCIA——数据传输形式,数据封装的基本概念

前言&#xff1a; 整理下学习笔记&#xff0c;打好基础&#xff0c;daydayup!!! 对网络概念有基本理解后&#xff08;华为认证HCIA——网络基本概念&#xff09;&#xff0c;开始进一步学习数据传输。 数据传输的形式 数据传输主要有三种形式&#xff1a; 1&#xff0c;电路传…

opencv小练习(未完成版)

读取一张彩色图像并将其转换为灰度图。 import cv2# 读取图片 img cv2.imread("./duck.png") img cv2.resize(img, dsizeNone, fx0.4, fy0.4, interpolationcv2.INTER_LINEAR) # 读取一张灰度图 img_gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)# 展示图片 cv2.im…

WSL (Windows Subsystem for Linux)

文章目录 Windows下使用Linux的三种方式&#xff1a;1.WSL(1)安装WSL(2)初始化Linux系统(3)安装、创建、激活 Python虚拟环境 2.虚拟机3.Docker Windows下使用Linux的三种方式&#xff1a; 1.WSL 是最简单的在 Windows 上运行 Linux 环境的方式&#xff0c;适用于日常开发和命…

金融分析-Transformer模型(基础理论)

Transformer模型 1.基本原理 transformer的core是注意力机制&#xff0c;其本质就是编码器-解码器。他可以通过多个编码器进行编码&#xff0c;再把编码完的结果输出给解码器进行解码&#xff0c;然后得到最终的output。 1.1编码器 数据在编码器中会经过一个self-attention的…

【一本通】两个数的最小公倍数

【一本通】两个数的最小公倍数 C语言代码C 代码Java代码Python代码 &#x1f490;The Begin&#x1f490;点点关注&#xff0c;收藏不迷路&#x1f490; 输入两个正整数&#xff0c;编程计算两个数的最小公倍数。 输入 两个整数 输出 最小公倍数 样例输入 12 18样例输出 …

D 咖智能饮品机器人:开启商业新篇

在科技迅猛发展的当下&#xff0c;智能机器人正逐步渗透到各个商业领域&#xff0c;D 咖智能饮品机器人便是其中的佼佼者&#xff0c;它的出现为饮品行业带来全新的发展契机&#xff0c;有望开启商业新篇。 从大环境来看&#xff0c;消费者对于饮品的需求日益多元化和个性化。他…

35. Three.js案例-创建带阴影的球体与平面

35. Three.js案例-创建带阴影的球体与平面 实现效果 知识点 WebGLRenderer WebGLRenderer 是Three.js中用于渲染场景的主要类之一&#xff0c;它负责将场景中的对象渲染到画布上。 构造器 new THREE.WebGLRenderer(parameters : Object) 参数类型描述parametersObject可选…

leetcode212. 单词搜索 II

给定一个 m x n 二维字符网格 board 和一个单词&#xff08;字符串&#xff09;列表 words&#xff0c; 返回所有二维网格上的单词 。 单词必须按照字母顺序&#xff0c;通过 相邻的单元格 内的字母构成&#xff0c;其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一…

容器化技术

文章目录 虚拟化容器容器隔离实现隔离文件 chroot隔离访问 namespace隔离资源 cgroups Dockerkubernetes 虚拟化容器 容器的首要目标是让软件分发部署过程从传统的发布安装包、靠人工部署转变为直接发布已经部署好的、包含整套运行环境的虚拟计划镜像。 一个计算机软件能够给正…

【潜意识Java】javaee中的SpringBoot在Java 开发中的应用与详细分析

目录 一、前言 二、Spring Boot 简介 三、Spring Boot 核心模块 四、Spring Boot 项目实战&#xff1a;构建一个简单的 RESTful API 1. 创建 Spring Boot 项目 2. 配置数据库 3. 创建实体类 4. 创建 JPA 仓库接口 5. 创建服务层 6. 创建控制器层 7. 测试 API 8. 运…

土地档案管理关系[源码+文档]

目录 摘 要 Abstract 1 绪论 1.1 可行性研究编写目的 1.2 项目背景 1.3 土地管理现状 1.4 土地档案管理研究方向 1.5 项目目标 1.6 项目设计原则 1.6.1 实用性原则 1.6.2 经济性原则 1.6.3 合法性原则 2 相关技术介绍 2.1 三层架构的选择 2.2 编程…

使用C语言编写UDP循环接收并打印消息的程序

使用C语言编写UDP循环接收并打印消息的程序 前提条件程序概述伪代码C语言实现编译和运行C改进之自由设定端口注意事项在本文中,我们将展示如何使用C语言编写一个简单的UDP服务器程序,该程序将循环接收来自指定端口的UDP消息,并将接收到的消息打印到控制台。我们将使用POSIX套…

Audiocraft智能音频和音乐生成工具部署及使用

1、概述 Facebook开源了一款名为AudioCraft的AI音频和音乐生成工具。 该工具可以直接从文本描述和参考音乐生成高质量的音频和音乐。AudioCraft包含MusicGen、AudioGen和EnCodec三个模型&#xff0c;分别实现音乐生成、音频生成和自定义音频模型构建。 2、项目地址 https://…

vulnhub靶场【DriftingBlues】之7

前言 靶机&#xff1a;DriftingBlues-6&#xff0c;IP地址192.168.1.65 攻击&#xff1a;kali&#xff0c;IP地址192.168.1.16 都采用虚拟机&#xff0c;网卡为桥接模式 主机发现 使用arp-scan -l或netdiscover -r 192.168.1.1/24 信息收集 使用nmap扫描端口 SSH服务&…

SCAU期末笔记 - Linux系统应用与开发教程样卷解析(2024版)

我真的不理解奥&#xff0c;为什么会有给样卷不自带解析的&#xff0c;对答案都没得对&#xff0c;故整理一篇 样卷1 一、选择题 1、为了遍历shell脚本调用时传入的参数&#xff0c;需要在shell脚本中使用_____。 A.$#表示参数的个数B.S表示所有参数C.$0表示脚本名D.$1表示…

spring\strust\springboot\isp前后端那些事儿

后端 一. 插入\更新一条数据&#xff08;老&#xff09; Map<String, Object> parameterMap MybatisUtil.initParameterSave("Send_ProjectFrozenLog", sendProjectFrozenLog); commonMapper.insert(parameterMap);parameterMap MybatisUtil.initParameter…

对golang的io型进程进行off-cpu分析

背景&#xff1a; 对于不能占满所有cpu核数的进程&#xff0c;进行on-cpu的分析是没有意义的&#xff0c;因为可能程序大部分时间都处在阻塞状态。 实验例子程序&#xff1a; 以centos8和golang1.23.3为例&#xff0c;测试下面的程序&#xff1a; pprof_netio.go package m…

CSS Grid 布局:属性及使用详解

CSS Grid 布局&#xff1a;属性及使用详解 一、CSS Grid 布局的基础概念二、主要的 CSS Grid 属性1、display: grid / display: inline-grid声明 Grid 容器2、grid-template-columns / grid-template-rowsGrid 容器中列和行的尺寸3、 grid-template-areas命名布局区域4、gap/ g…

【数学建模】利用Matlab绘图(2)

一、Matlab中plot函数的基本用法 在matlab中&#xff0c;函数的基本用法主要包括以下几种 第一类&#xff1a; plot(X,Y,LineSpec) 第二类&#xff1a; plot(tbl,xvar,yvar) 1.1 第一类 1.1.1x-y坐标 x和y的选择取决于绘图所需的数据类型以及图像的类型。下表列出了几种…

编写工具模块

文章目录 1.新建模块1.新建模块sun-common-tool2.sun-dependencies指定依赖3.sun-common统一管理sun-common-tool子模块4.sun-common-tool的pom.xml5.清除掉创建模块时默认sun-frame对sun-common-tool进行管理 2.常用工具类1.DateUtils.java2.EncodeUtils.java3.IpUtils.java4.…