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.rhkb.cn/news/492213.html

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

相关文章

【Java】mac安装Java17(JDK17)

文章目录 下载java17一、安装二、环境变量 下载java17 官网下载&#xff1a;https://www.oracle.com/java/technologies/downloads 提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面案例可供参考 一、安装 直接安装后&#xff0c;安装完后目录会存放在下面目录下 /…

【USB-HID】“自动化键盘“

这里写目录标题 【USB-HID】"自动化键盘"1. 前言2. 框架3. 实现3.1 模拟键盘按键输入 【USB-HID】“自动化键盘” 1. 前言 最近从朋友那了解了一种"自动化键盘"&#xff0c;能够通过上位机录制按键脚本&#xff0c;然后执行脚本&#xff0c;实现物理键盘…

XXE靶机漏洞复现通关

1.扫描XXE靶机的ip地址 将kali虚拟机和XXE靶机部署在同一局域网中&#xff0c;都采用NAT网络模式 搭建好后在kali终端中进行扫描XXE靶机的ip arp-scan -l 根据常识我们可以推断192.168.27.153为靶机的ip地址 2.访问靶机页面并扫描附录 进入页面后我们可以打开御剑扫描网页中…

leetcode 36.有效的数独

1.题目要求: 2.题目步骤: 写好判断函数 3.题目代码: class Solution { public:bool isvalid(vector<vector<char>>& board,char num,int row,int col){//先找左下标int leftrow row - 1;while(leftrow > 0){if(board[leftrow][col] num){return fals…

在C#中测试比较目录的不同方法以查看它们有哪些共同的文件

C# 中的示例“比较目录以查看它们有哪些共同的文件”使用Directory.GetFiles获取两个目录中的文件。它对文件进行排序&#xff0c;并比较两个排序后的列表以查看哪些文件位于第一个目录中、第二个目录中或两个目录中。有关其工作原理的详细信息&#xff0c;请参阅该示例。 Kur…

【Java基础面试题019】什么是Java中的不可变类?

回答重点 不可变类是指在创建后无法被修改的类。一旦对象被创建&#xff0c;它的所有属性都不能被更改。这种类的实例在整个生命周期内保持不变。 关键特征&#xff1a; 声明类为final&#xff0c;防止子类继承类的所有字段都是private和final&#xff0c;确保它们在初始化后…

【论文笔记】Editing Models with Task Arithmetic

&#x1f34e;个人主页&#xff1a;小嗷犬的个人主页 &#x1f34a;个人网站&#xff1a;小嗷犬的技术小站 &#x1f96d;个人信条&#xff1a;为天地立心&#xff0c;为生民立命&#xff0c;为往圣继绝学&#xff0c;为万世开太平。 基本信息 标题: Editing Models with Task…

HarmonyOS(71) 自定义事件分发之TouchTestStrategy使用说明

TouchTestStrategy 1、前言2、TouchTestStrategy简介2.1、TouchTestStrategy枚举类型简介2.2、TouchTestStrategy.DEFAULT效果1.3、TouchTestStrategy.FORWARD_COMPETITION效果2.3、TouchTestStrategy.FORWARD效果3、参考资料1、前言 本文根据官方文档自定义事件分发整理而来,…

【附源码】Electron Windows桌面壁纸开发中的 CommonJS 和 ES Module 引入问题以及 Webpack 如何处理这种兼容

背景 在尝试让 ChatGPT 自动开发一个桌面壁纸更改的功能时&#xff0c;发现引入了一个 wallpaper 库&#xff0c;这个库的入口文件是 index.js&#xff0c;但是 package.json 文件下的 type:"module"&#xff0c;这样造成了无论你使用 import from 还是 require&…

WebGIS城市停水及影响范围可视化实践

目录 前言 一、相关信息介绍 1、停水信息的来源 2、停水包含的相关信息 二、功能简介 1、基础小区的整理 2、停水计划的管理 三、WebGIS空间可视化 1、使用到的组件 2、停水计划的展示 3、影响小区的展示 4、实际效果 四、总结 前言 城市停水&#xff0c;一个看似…

群落生态学研究进展】Hmsc包开展单物种和多物种分析的技术细节及Hmsc包的实际应用

联合物种分布模型&#xff08;Joint Species Distribution Modelling&#xff0c;JSDM&#xff09;在生态学领域&#xff0c;特别是群落生态学中发展最为迅速&#xff0c;它在分析和解读群落生态数据的革命性和独特视角使其受到广大国内外学者的关注。在学界不同研究团队研发出…

docker 部署 redis

docker 部署 redis 1. 下载 redis 镜像 # docker images | grep redis bitnami/redis 7.2.4-debian-11-r5 45de196aef7e 10 months ago 95.2MB2. docker-compose 部署 version: "3" services:redis:image: bitnami/redis:7.2.4-debian-11-…

Windows环境 (Ubuntu 24.04.1 LTS ) 国内镜像,用apt-get命令安装RabbitMQ,java代码样例

一、环境 Windows11 WSL(Ubuntu 24.04.1) 二、思路 1 用Windows中的Ubuntu安装RabbitMQ&#xff0c;贴近Linux的线上环境&#xff1b; 2 RabbitMQ用erlang语言编写的&#xff0c;先安装erlang的运行环境&#xff1b; 2 用Linux的apt-get命令安装&#xff0c;解决软件依赖…

Linux网络应用——高级IO

目录 1. I/O 的重新理解 2. 五种 I/O 模型 ① 阻塞 && 非阻塞 ② 同步 I/O && 异步 I/O 3. select 模型 ① 非阻塞 I/O ② select 接口介绍 ③ 代码实现 ④ select 模型的优缺点 4. poll 模型 ① poll 接口介绍 ② 代码实现 ③ 与 select 模型的…

Dcoker Redis哨兵模式集群介绍与搭建 故障转移 分布式 Java客户端连接

介绍 Redis 哨兵模式&#xff08;Sentinel&#xff09;是 Redis 集群的高可用解决方案&#xff0c;它主要用于监控 Redis 主从复制架构中的主节点和从节点的状态&#xff0c;并提供故障转移和通知功能。通过 Redis 哨兵模式&#xff0c;可以保证 Redis 服务的高可用性和自动故…

使用 Wireshark 和 Lua 脚本解析通讯报文

在复杂的网络环境中&#xff0c;Wireshark 凭借其强大的捕获和显示功能&#xff0c;成为协议分析不可或缺的工具。然而&#xff0c;面对众多未被内置支持的协议或需要扩展解析的场景&#xff0c;Lua 脚本的引入为Wireshark 提供了极大的灵活性和可扩展性。本文将详细介绍如何使…

宝塔SSL证书申请失败,报错:申请SSL证书错误 module ‘OpenSSL.crypto‘ has no attribute ‘sign‘(已解决)

刚安装宝塔申请SSL就报错&#xff1a;申请SSL证书错误 module OpenSSL.crypto has no attribute sign 面板、插件版本&#xff1a;9.2.0 系统版本&#xff1a;Alibaba Cloud Linux 3.2104 LTS 问题&#xff1a;申请SSL证书错误 module OpenSSL.crypto has no attribute sign…

自动驾驶控制与规划——Project 2: 车辆横向控制

目录 零、任务介绍一、环境配置二、算法三、代码实现四、效果展示 零、任务介绍 补全src/ros-bridge/carla_shenlan_projects/carla_shenlan_stanley_pid_controller/src/stanley_controller.cpp中的TODO部分。 一、环境配置 上一次作业中没有配置docker使用gpu&#xff0c;…

双内核架构 Xenomai 4 安装教程

Xenomai 4是一种双内核架构, 继承了Xenomai系列的特点&#xff0c;通过在Linux内核中嵌入一个辅助核心&#xff08;companion core&#xff09;&#xff0c;来提供实时能力。这个辅助核心专门处理那些需要极低且有界响应时间的任务。 本文将在官网教程(https://evlproject.org/…

MySQL学习之DML操作

目录 插入 删除 修改 数据库事务 事务的特征&#xff08;ACID原则&#xff09; 原子性 一致性 隔离性 持久性 事务隔离级别 读未提交 读已提交 可重复读 序列化 脏读 虚读 幻读 插入 insert into 表名 values(); 要求插入数据的数量&#xff0c;类型要和定义…