【MIT6.S81】-Lab1

【MIT6.S81】-Lab1

课程链接

MIT6.S081:https://pdos.csail.mit.edu/6.828/2020/overview.html

在做到lab3时卡壳,准备重新看一下前面的内容再继续实验,感觉对前面的内容理解的还是不透彻,所以在文档中加入一些6.S081的相关内容

本文档部分内容参考网上的文章和视频

Chapter 1部分的总结如下,参考xv6 book英文原文,如有错误,敬请指正。

Chapter 1: Operating system interfaces

1.1 process and memory

**int fork(): **创建一个新的进程,在父进程中返回子进程的PID, 在子进程中返回0

**int exit(int status): **终止现有的进程,释放资源,将结束进程的状态报告给wait()

**int wait(int *status): **返回刚刚结束的子进程的PID,将子进程的退出状态复制到传入wait()的地址,若调用wait的进程无子进程,则返回-1,若无子进程终止,则继续等待

**int exec(char *file, char *argv[]): **加载文件并传参运行

char *argv[3];

argv[0] = "echo";
argv[1] = "hello";
argv[2] = 0;
exec("/bin/echo", argv);
printf("exec error");

xv6shell利用以上调用来代替用户运行程序

xv6shell的代码如下

当shell开始运行时,main函数首先通过getcmd来获取用户输入,接着调用fork,创建一个子进程,在子进程中调用runcmd函数,runcmd函数再通过exec来运行用户输入的命令,在exec中会调用exit来退出子进程返回到父进程,对应的是父进程的wait函数。

1.2 I/O and file descriptors

文件描述符是一个小整数,表示进程可以读取或写入的内核管理对象。

文件描述符将不同的文件抽象成字节流。

xv6 内核使用文件描述符作为每个进程表的索引,以便每个进程都有一个从零开始的文件描述符的私有空间。通常情况下,0代表读,1代表写,2代表错误。

新分配的文件描述符始终是当前进程中编号最小的未使用描述符。

下图中,子进程会关闭0,再将0用于input.txt,cat在使用0来执行

为什么fork和exec要分别定义为不同的系统调用?

将fork和exec分开定义可以再不干扰主shell I/O设置的情况下重定向子进程的I/O

两个文件描述符共享offset的情况:这两个文件描述符是必须从fork或者dup调用中的原始文件描述符中产生的

1.3 pipe

A pipe is a small kernel buffer exposed to processes as a pair of file descriptors, one for reading and one for writing.

管道是一个小的内核缓冲区,作为一对文件描述符暴露给进程,一个用于读取,一个用于写入。

wc中对pipe的简单应用:

如果pipe的读端无可读数据,pipe等待数据写入或等待写入端的所有文件描述符关闭,在后一种情况下,read 将返回 0,就像已到达数据文件末尾一样,所以及时关闭pipe的写端是很有必要的

xv6shell实现管道如下图代码所示(user/sh.c:100),例如grep fork sh.c | wc -1

和wc中类似,子进程创建pipe连接”|“的左右两端

1.4 file system

这部分内容比较简单,xv6 book中已详细说明,此处不再赘述。

Lab: Xv6 and Unix utilities

Lab guidance

调试技巧

以下是调试解决方案的一些提示:

  • 确保您了解 C 和指针。 Kernighan 和 Ritchie 所著的《C 编程语言(第二版)》一书对 C 进行了简洁的描述。这里有一些有用的指针练习。 除非您已经精通 C 语言,否则请勿跳过或浏览上面的指针练习。 如果你不真正理解 C 中的指针,你将在实验室中遭受难以言喻的痛苦和痛苦,然后最终以艰难的方式理解它们。 相信我们; 你不想知道什么是“艰难的道路”。
  • 如果您的练习部分有效,请通过提交代码来检查进度。 如果您稍后破坏了某些内容,则可以回滚到检查点并以较小的步骤前进。 要了解有关 Git 的更多信息,请查看 Git 用户手册,或者您可能会发现此面向 CS 的 Git 概述http://eagain.net/articles/git-for-computer-scientists/很有用。
  • 如果测试失败,请确保您了解代码未通过测试的原因。 插入打印语句,直到您了解发生了什么。
  • 您可能会发现您的 print 语句可能会产生很多您想要搜索的输出; 一种方法是在script内运行 make qemu (在您的计算机上运行 man script),它将所有控制台输出记录到一个文件中,然后您可以搜索该文件。 不要忘记退出script
  • 在许多情况下,打印语句就足够了,但有时能够单步执行某些汇编代码或检查堆栈上的变量会很有帮助。 要将 gdbxv6 一起使用,请在一个窗口中运行 make qemu-gdb,在另一个窗口中运行 gdb(或 riscv64-linux-gnu-gdb),设置一个断点,然后是“c”(continue)和 xv6 将运行直到到达断点。 (有关有用的 GDB 提示,请参阅使用 GNU 调试器
  • 如果您想查看编译器为内核生成的程序集是什么,或者想了解特定内核地址处的指令是什么,请查看文件 kernel.asm,该文件是 Makefile 在编译内核时生成的。 (Makefile 还为所有用户程序生成 .asm。)
  • 如果内核panics,它将打印一条错误消息,列出崩溃时程序计数器的值; 您可以搜索 kernel.asm 以找出程序计数器崩溃时所在的函数,或者您可以运行 addr2line -e kernel/kernel pc-value (运行 man addr2line 了解详细信息)。 如果你想获得回溯,请使用 gdb8 重新启动:在一个窗口中运行“make qemu-gdb”,在另一个窗口中运行 gdb(或 riscv64-linux-gnu-gdb),在panic*中设置断点(“b panic”),然后 by 后跟“c”(continue)。 当内核到达断点时,输入“bt”以获取回溯。
  • 如果您的内核挂起(例如,由于死锁)或无法进一步执行(例如,由于执行内核指令时出现页面错误),您可以使用 gdb 找出挂起的位置。 在一个窗口中运行make qemu-gdb,在另一个窗口中运行 gdb ( riscv64-linux-gnu-gdb),然后运行“c”(continue)。 当内核出现挂起时,在 qemu-gdb 窗口中按 Ctrl-C 并输入“bt”以获取回溯。
  • qemu 有一个“监视器”,可以让您查询模拟机器的状态。 您可以通过输入control-a c(“c”代表控制台)来获取它。 一个特别有用的监视命令是 info mem,用于打印页表。 您可能需要使用 cpu 命令来选择要查看哪个核心信息 mem,或者您可以使用 make CPUS=1 qemu 来启动 *qemu8,以导致只有一个核心。

环境配置

VMware:https://www.vmware.com/cn.html

Ubuntu20.04:https://ubuntu.com/download/desktop/thank-you?version=20.04.2.0&architecture=amd64

尽量使用20.04版本进行环境的搭建,不然容易出现错误

安装后还需要进行更换软件源等初步准备工作,这里不在赘述

安装xv6

打开终端,输入

sudo apt install git build-essential gdb-multiarch qemu-system-misc gcc-riscv64-linux-gnu binutils-riscv64-linux-gnu libglib2.0-dev libpixman-1-dev gcc-riscv64-unknown-elf

下载xv6源码

git clone git://g.csail.mit.edu/xv6-labs-2020
cd xv6-riscv
make qemu

gdb调试

打开一个终端窗口,输入 make-gdb

再打开一个终端窗口,输入

echo "add-auto-load-safe-path YOUR_PATH/xv6-riscv/.gdbinit " >> ~/.gdbinit

输入gdb-multiarch

接下来就可以进行调试了

退出qemu:按下ctrl - a 放开后按x(之前一直搞错,要放开ctrl - a以后在按x)

Sleep

为 xv6 实现 UNIX 程序 sleep; sleep应该暂停用户指定的ticks。 时钟周期是 xv6 内核定义的时间概念,即定时器芯片两次中断之间的时间。 您的解决方案应该位于文件user/sleep.c中

原文如下

Implement the UNIX program sleep for xv6; your sleep should pause for a user-specified number of ticks. A tick is a notion of time defined by the xv6 kernel, namely the time between two interrupts from the timer chip. Your solution should be in the file user/sleep.c.

提示:

  • 在开始编码之前,请阅读 xv6 书的第一章。
  • 查看 user/ 中的一些其他程序(例如 user/echo.c、user/grep.c 和 user/rm.c),了解如何获取传递给程序的命令行参数。
  • 如果用户忘记传递参数,sleep 应该打印一条错误消息。
  • 命令行参数作为字符串传递;您可以使用 atoi 将其转换为整数(请参阅 user/ulib.c)。
  • 使用系统调用sleep。
  • 请参阅 kernel/sysproc.c 了解实现 sleep 系统调用的 xv6 内核代码(查找 sys_sleep),参见 user/user.h 了解可从用户程序调用 sleep 的 C 定义,以及 user/usys.S 了解汇编代码从用户代码跳转到内核的sleep。
  • 确保 main 调用 exit() 以退出程序。
  • 将你的睡眠程序添加到Makefile中的UPROGS中;完成此操作后,make qemu 将编译您的程序,您将能够从 xv6 shell 运行它。
  • 查看 Kernighan 和 Ritchie 的书《C 编程语言(第二版)》(K&R)来了解 C。

原文如下

Some hints:

  • Before you start coding, read Chapter 1 of the xv6 book.
  • Look at some of the other programs in user/ (e.g., user/echo.c, user/grep.c, and user/rm.c) to see how you can obtain the command-line arguments passed to a program.
  • If the user forgets to pass an argument, sleep should print an error message.
  • The command-line argument is passed as a string; you can convert it to an integer using atoi (see user/ulib.c).
  • Use the system call sleep.
  • See kernel/sysproc.c for the xv6 kernel code that implements the sleep system call (look for sys_sleep), user/user.h for the C definition of sleep callable from a user program, and user/usys.S for the assembler code that jumps from user code into the kernel for sleep.
  • Make sure main calls exit() in order to exit your program.
  • Add your sleep program to UPROGS in Makefile; once you’ve done that, make qemu will compile your program and you’ll be able to run it from the xv6 shell.
  • Look at Kernighan and Ritchie’s book The C programming language (second edition) (K&R) to learn about C.

传入main函数的argc表示参数的个数,argv[]表示传入的参数,argv[0]通常为,命令名

完整代码:

#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"

int main(int argc, char *argv[])
{
    if(argc != 2){ //传入的参数个数不为2,即不是sleep 3格式的
        fprintf(2,"Usage: sleep...");
        exit(1); //exit传入参数1表示错误结束
    }
    sleep(atoi(argv[1]));
    exit(0); //exit传入参数0表示成功结束
}

此外,还要在Makefile的UPROGS中加入sleep函数

$U/_sleep\

Pingpong

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

原文如下

Write a program that uses UNIX system calls to ‘‘ping-pong’’ a byte between two processes over a pair of pipes, one for each direction. The parent should send a byte to the child; the child should print “: received ping”, where is its process ID, write the byte on the pipe to the parent, and exit; the parent should read the byte from the child, print “: received pong”, and exit. Your solution should be in the file user/pingpong.c.

提示:

  • 使用pipe创建一个管道
  • 使用fork创建一个子进程
  • 使用read来读管道,write来写管道
  • 使用 getpid 查找调用进程的进程 ID
  • 将程序添加到 Makefile 中的 UPROGS 中
  • xv6 上的用户程序具有一组有限的可用库函数。您可以在 user/user.h 中看到该列表;源代码(系统调用除外)位于 user/ulib.c、user/printf.c 和 user/umalloc.c 中

原文如下

Some hints:

  • Use pipe to create a pipe.
  • Use fork to create a child.
  • Use read to read from the pipe, and write to write to the pipe.
  • Use getpid to find the process ID of the calling process.
  • Add the program to UPROGS in Makefile.
  • User programs on xv6 have a limited set of library functions available to them. You can see the list in user/user.h; the source (other than for system calls) is in user/ulib.c, user/printf.c, and user/umalloc.c.

完整代码

#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"

int
main(int argc, char *argv[])
{
	int p1[2];
	int p2[2];
	char buf[5];
	
	
	pipe(p1); //父进程到子进程
	pipe(p2); //子进程到父进程
	
	if(argc != 1)
	{
	fprintf(2, "Usage: pingpong");
	exit(1);
	}
	
	int pid = fork();
	
	if(pid == 0)//子进程
	{
	close(p1[1]); //关闭管道的写端(父进程到子进程)
        
		if(read(p1[0],buf,sizeof(buf))>0)//子进程开始读
	{
	fprintf(1,"%d:received ping",getpid());
	write(1, buf, sizeof(buf));
	}
		else
	{
	fprintf(2,"%d received failed!",getpid());
	}
        
	close(p2[0]); //关闭管道的读端(子进程到父进程)
	write(p2[1],"pong\n",5); //子进程开始写
	exit(0);
	}
	
	else
	{//父进程
	close(p1[0]); //关闭管道的读端(父进程到子进程)
	write(p1[1],"ping\n",5); //父进程开始写
	wait(0);
	
	close(p2[1]); //关闭管道的写端(子进程到父进程)
        
		if(read(p2[0],buf,sizeof(buf))>0)	//父进程开始读
	{
	fprintf(2,"%d:received ping",getpid());
	write(1, buf, sizeof(buf));
	}
		else
	{
	fprintf(2,"%d received failed!",getpid());
	}
	}
    
	exit(0);
	
	
}

Primes

使用管道编写素数筛的并发版本。这个想法源自 Unix 管道的发明者 Doug McIlroy。本页中间的图片和周围的文字解释了如何操作。您的解决方案应该位于文件 user/primes.c 中。

原文如下

Write a concurrent version of prime sieve using pipes. This idea is due to Doug McIlroy, inventor of Unix pipes. The picture halfway down this page and the surrounding text explain how to do it. Your solution should be in the file user/primes.c.

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

原文如下

Your goal is to use pipe and fork to set up the pipeline. The first process feeds the numbers 2 through 35 into the pipeline. For each prime number, you will arrange to create one process that reads from its left neighbor over a pipe and writes to its right neighbor over another pipe. Since xv6 has limited number of file descriptors and processes, the first process can stop at 35.

提示:

  • 请小心关闭进程不需要的文件描述符,否则您的程序将在第一个进程达到 35 之前耗尽资源来运行 xv6。
  • 一旦第一个进程达到 35,它应该等待整个管道终止,包括所有子进程、孙进程等。 因此,主 primes 进程仅应在所有输出打印完毕以及所有其他 primes 进程退出后退出。
  • 提示:当管道的写入端关闭时,read返回零。
  • 最简单的方法是直接将 32 位(4 字节)整数写入管道,而不是使用格式化的 ASCII I/O
  • 您应该仅在需要时在管道中创建进程
  • 将程序添加到 Makefile 中的 UPROGS 中。

原文如下

Some hints:

  • Be careful to close file descriptors that a process doesn’t need, because otherwise your program will run xv6 out of resources before the first process reaches 35.
  • Once the first process reaches 35, it should wait until the entire pipeline terminates, including all children, grandchildren, &c. Thus the main primes process should only exit after all the output has been printed, and after all the other primes processes have exited.
  • Hint: read returns zero when the write-side of a pipe is closed.
  • It’s simplest to directly write 32-bit (4-byte) ints to the pipes, rather than using formatted ASCII I/O.
  • You should create the processes in the pipeline only as they are needed.
  • Add the program to UPROGS in Makefile.

素数筛:

#include "kernel/types.h"
#include "user/user.h"

void process(int p[])
{
    //递归创建进程的函数
    close(p[1]);
    int prime;
    if (read(p[0], &prime, 4) > 0) { //从该进程的父进程中读入第一个数
        fprintf(1, "prime %d\n", prime);//输出读到的这个数
        int p2[2];
        pipe(p2);//创建该进程与其子进程的管道
        if (fork() > 0) {
            close(p2[0]);
            int i;
            while(read(p[0], &i, 4) > 0) {//从该进程的父进程中读入剩下的数
                if (i % prime != 0) {//将第一个数后的值,除了第一个数的倍数,通过pipe全部传给下一个子进程
                    write(p2[1], &i, 4);
                }
            }
            close(p2[1]);
            wait(0);
        } else {
            close(p[0]);
            process(p2);//子进程继续创建子进程
        }
    }
}

int
main(int argc, char* argv[])
{
    int p[2];
    pipe(p);
    int pid = fork();
    //最初的父进程
    if (pid > 0) { 
        close(p[0]);
        fprintf(1, "prime 2\n"); //由于是第一个进程,也就是最早的父进程,所以先输出第一个数2
        for (int i = 3; i <= 35; ++i) {//将2后的值,除了2的倍数,通过pipe全部传给下一个子进程
            if (i % 2 != 0) {
                write(p[1], &i, 4); //在pipe的写端写入4字节(int)的数据
            }
        }
        close(p[1]);
        wait(0);
    } else {
        process(p); //如果是子进程则调用函数
    }
    exit(0);
}

find

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

Write a simple version of the UNIX find program: find all the files in a directory tree with a specific name. Your solution should be in the file user/find.c.

提示:

  • 查看 user/ls.c 以了解如何读取目录
  • 使用递归允许 find 深入到子目录
  • 不要递归成“.” 和 ”…”
  • 文件系统的更改在 qemu 运行期间持续存在; 要获得干净的文件系统,请运行 make clean,然后运行 make qemu
  • 您需要使用 C 字符串。 看一下 K&R,例如第 5.5 节
  • 请注意,== 并不像 Python 中那样比较字符串。 请改用 strcmp()
  • 将程序添加到 Makefile 中的 UPROGS 中

Some hints:

  • Look at user/ls.c to see how to read directories.
  • Use recursion to allow find to descend into sub-directories.
  • Don’t recurse into “.” and “…”.
  • Changes to the file system persist across runs of qemu; to get a clean file system run make clean and then make qemu.
  • You’ll need to use C strings. Have a look at K&R (the C book), for example Section 5.5.
  • Note that == does not compare strings like in Python. Use strcmp() instead.
  • Add the program to UPROGS in Makefile.
#define T_DIR 1 //Directory
#define T_FILE 2 //File
#define T_DEVICE 3 //Device

struct stat {
    int dev; //file system's disk device
    uint ino; //inode number
    short type; //type of file
    short nlink; //number of links to file
    unit64 size; //size of file in bytes
};
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
#include "kernel/fs.h"

char* fmtname(char *path) { //将文件名格式化
  static char buf[DIRSIZ + 1];
  char *p;
  for (p = path + strlen(path); p >= path && *p != '/';p--);
  p++;
  if (strlen(p) >= DIRSIZ) return p;
  memmove(buf, p, strlen(p));
  memset(buf+strlen(p), ' ', DIRSIZ-strlen(p));
  buf[strlen(p)] = 0;
  return buf;
}

void find(char *path, char *fileName) {
  char buf[512], *p;
  int fd;
  struct stat st;
  struct dirent de;
  if ((fd = open(path, 0)) < 0 ) {
    fprintf(2, "find: cannot open %s\n", path);
	return;
  }
  if (fstat(fd, &st) < 0) {
    fprintf(2, "find: cannot stat %s\n", path);
	close(fd);
	return;
  }
  //printf("%s %s\n",path, fmtname(path));
  switch(st.type) {
  case T_FILE:
	//printf("%s\n", fmtname(path));
	if (strcmp(fmtname(path), fileName) == 0) {
	  printf("%s\n", path);
	}
	break;
  case T_DIR:
	//printf("%s %s\n", path, fmtname(path));
	if(strlen(path) + 1 + DIRSIZ + 1 > sizeof buf) {
	  printf("find: path too long\n");
	  break;
	}
	strcpy(buf, path);
	p = buf + strlen(buf);
	*p++ = '/';
	while (read(fd, &de, sizeof(de)) == sizeof(de)) {
	  if (de.inum == 0 || strcmp(de.name, ".") == 0 || strcmp(de.name, "..") == 0) {
		continue;
	  }
	  memmove(p, de.name, DIRSIZ);
	  p[DIRSIZ] = 0;
	  find(buf, fileName);
	}
	break;
  }
  close(fd);
}

int main(int argc, char *argv[]) {
  if (argc < 3) {
    printf("error\n");
	exit(1);
  }
  find(argv[1], argv[2]);
  exit(0);
}

xargs

编写一个简单版本的 UNIX xargs 程序:从标准输入读取行并为每行运行一个命令,将该行作为参数提供给命令。您的解决方案应该位于文件 user/xargs.c 中

Write a simple version of the UNIX xargs program: read lines from the standard input and run a command for each line, supplying the line as arguments to the command. Your solution should be in the file user/xargs.c.

提示:

  • 使用 fork 和 exec 在每一行输入上调用命令。 在父级中使用 wait 来等待子级完成命令
  • 要读取单行输入,请一次读取一个字符,直到出现换行符 (‘\n’)。kernel/param.h 声明了 MAXARG,如果您需要声明 argv 数组,这可能很有用。
  • 将程序添加到 Makefile 中的 UPROGS 中。
  • 文件系统的更改在 qemu 运行期间持续存在; 要获得干净的文件系统,请运行 make clean,然后运行 make qemu。
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
#include "kernel/param.h"

int main(int argc, char *argv[])
{
    char *args[MAXARG];
    int i;
    //将xargs的参数保存在args中
    for(int i=0;i < argc;i++)
        args[i] = argv[i];
    
    char buf[256];
    
    for( ; ; ){
        int j = 0;
        //从标准输入读入一行
        while((read(0, buf + j, sizeof(char)) ! = 0) && buf[j] != '\n')
            ++j;
        if(j == 0) break; //全部读完
        buf[j] = 0;//添加结尾
        //将标准输入传进的一行参数加到args后
        args[i] = buf;
        args[i + 1] = 0;//添加字符串结尾
        //执行命令
        if(fork() == 0){
            //args[0]是xargs,args[1]是要执行的命令,剩余的是参数
            exec(args[1], args + 1);
            printf("exec error\n");
	} else{
            wait((void*)0);
	}
	}
    exit(0);
}
    args[i] = argv[i];

char buf[256];

for( ; ; ){
    int j = 0;
    //从标准输入读入一行
    while((read(0, buf + j, sizeof(char)) ! = 0) && buf[j] != '\n')
        ++j;
    if(j == 0) break; //全部读完
    buf[j] = 0;//添加结尾
    //将标准输入传进的一行参数加到args后
    args[i] = buf;
    args[i + 1] = 0;//添加字符串结尾
    //执行命令
    if(fork() == 0){
        //args[0]是xargs,args[1]是要执行的命令,剩余的是参数
        exec(args[1], args + 1);
        printf("exec error\n");
} else{
        wait((void*)0);
}
}
exit(0);

}


MIT 6.824 课程的 Lab1 是关于 Map 的实现,这里单介绍一下实现过程。 MapReduce 是一种布式计算模型,它可以用来处理大规模数据集。MapReduce 的核心想是将数据划分为多个块,每个块都可以在不同的节点上并行处理,然后将结果合并在一起。 在 Lab1 中,我们需要实现 MapReduce 的基本功能,包括 Map 函数、Reduce 函数、分区函数、排序函数以及对作业的整体控制等。 首先,我们需要实现 Map 函数。Map 函数会读取输入文件,并将其解析成一系列键值对。对于每个键值对,Map 函数会将其传递给用户定义的 Map 函数,生成一些新的键值对。这些新的键值对会被分派到不同的 Reduce 任务中,进行进一步的处理。 接着,我们需要实现 Reduce 函数。Reduce 函数接收到所有具有相同键的键值对,并将它们合并成一个结果。Reduce 函数将结果写入输出文件。 然后,我们需要实现分区函数和排序函数。分区函数将 Map 函数生成的键值对映射到不同的 Reduce 任务中。排序函数将键值对按键进行排序,确保同一键的所有值都被传递给同一个 Reduce 任务。 最后,我们需要实现整个作业的控制逻辑。这包括读取输入文件、调用 Map 函数、分区、排序、调用 Reduce 函数以及写入输出文件。 Lab1 的实现可以使用 Go 语言、Python 或者其他编程语言。我们可以使用本地文件系统或者分布式文件系统(比如 HDFS)来存储输入和输出文件。 总体来说,Lab1 是一个比较简单的 MapReduce 实现,但它奠定了 MapReduce 的基础,为后续的 Lab 提供了良好的基础。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值