当前位置: 首页 > news >正文

【MIT 6.S081】2020, 实验记录(1),Lab: Xv6 and Unix utilities

目录

    • 实验准备
    • Tasks
      • Task 1: Boot xv6
      • Task 2: sleep
      • Task 3: pingpong
      • Task 4: primes
      • Task 5: find
      • Task 6: xargs

实验准备

这个 lab 用来学习尝试如何通过 system call 来实现常见的 shell 命令行程序,比如 lssleepxargs 等。

  • 实验官网

可以使用 docker 搭建实验环境:

docker pull debian:bullseye
docker run -it --name mit6.s081 -d debian:bullseye /bin/bash

然后按照实验官网的介绍,在 git 下载源代码并启动。

Tasks

Task 1: Boot xv6

这个 task 就是在 git 上下载源代码,并启动 xv6 系统:

$ make qemu

成功后就可以看到 OS 成功启动。

Task 2: sleep

本 task 以及接下来的 task,都是在 /user 目录下实现用户级程序。

这个 task 新建一个 user/sleep.c,并通过调用 system call 来实现睡眠的功能,代码较为简单:

#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"int
mian(int argc, char *argv[])
{if (argc < 2) {fprintf(2, "Usage: sleep ticks...\n");exit(1);}int ticks = atoi(argv[1]);sleep(ticks);  // 系统调用的 sleep,声明在 `user/user.h` 中exit(0);
}

完成后在 Makefile 中的 UPROGS 里添加上 sleep。

测试:

./grade-lab-util sleep

sleep task

Task 3: pingpong

通过该 task 学会 fork 一个子进程,并实现父进程与子进程的通信。

我们需要实现 fork 一个子进程,并让父进程向子进程发送一个字节,然后子进程再向父进程发送一个字节。

关键技术细节:

  • 使用 pipe 函数建立用于进程间通信的通道:
int fds[2];
pipe(fds);

这里 fds[0] 用于 read,fds[1] 用于 write,每个进程在使用完某个 fd 后需要 close 掉 fd。

  • read() 函数的行为:在另一方未关闭写的 fd 时,自己读取且没有更多消息时会阻塞住;而在另一方关闭了写的 fd 且自己没有更多消息可以读时,会立刻返回 0.

代码实现:

#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"int
main(int argc, char *argv[])
{int fds[2], pid, n, xstatus;enum { BYTE_SZ=1 };if (pipe(fds) != 0) {printf("pipe() failed\n");exit(1);}pid = fork();if (pid > 0) {  // parent proc// 向子进程发送一个字节char parent_buf[BYTE_SZ];if (write(fds[1], "x", BYTE_SZ) != BYTE_SZ) {printf("pingpong oops 1\n");exit(1);}// 等待子进程结束wait(&xstatus);if (xstatus != 0) {exit(xstatus);}// 读取子进程发送过来的字节while ((n = read(fds[0], parent_buf, BYTE_SZ)) > 0) {if (parent_buf[0] != 'x') {printf("pingpong oops 2\n");exit(1);}printf("%d: received pong\n", getpid());close(fds[0]);close(fds[1]);exit(0);}} else if (pid == 0) {  // child proc// 等待读取父进程发送的字节char child_buf[BYTE_SZ];while ((n = read(fds[0], child_buf, BYTE_SZ)) > 0) {if (child_buf[0] != 'x') {printf("pingpong oops 2\n");exit(1);}printf("%d: received ping\n", getpid());// 向父进程发送一个字节if (write(fds[1], "x", BYTE_SZ) != BYTE_SZ) {printf("pingpong oops 2\n");exit(1);}close(fds[0]);close(fds[1]);exit(0);}} else {printf("fork() failed\n");exit(1);}exit(0);
}

测试:

在这里插入图片描述

Task 4: primes

这里需要使用 fork 子进程和进程间通信的技术实现 2~35 以内质数的筛选。

思路如下:

质数筛选

大致就是:

  • 第一个进程将 2~35 写入管道
  • 然后 fork 第二个进程,逐个从管道中读出数字,再将符合条件的数字送入下一个管道给第三个进程
  • 第三个进程类似…

代码实现如下:

#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"#define INT_SIZE (sizeof(int))
#define MAX_LIMIT  35
#define READ_END 0
#define WRITE_END 1int
main(int argc, char *argv[])
{int fds1[2], fds2[2], n, p, xstatus, pid;int *left_fds = fds1;  // 左通道int *right_fds = fds2; // 右通道if (pipe(left_fds) != 0 || pipe(right_fds) != 0) {printf("pipe() failed\n");exit(1);}// feed numbersfor (int i = 2; i < MAX_LIMIT; ++i) {write(left_fds[WRITE_END], &i, INT_SIZE);}close(left_fds[WRITE_END]);int first_loop = 1;while (1) {int is_success = read(left_fds[READ_END], &n, INT_SIZE);  // read from leftif (is_success == 0) {   // if read endclose(left_fds[READ_END]);close(right_fds[READ_END]);close(right_fds[WRITE_END]);if (first_loop != 1) {wait(&xstatus);exit(xstatus);} else {exit(0);}}// 如果是第一次进入 Loop,则需要打印 prime 并创建一个子进程if (first_loop == 1) {pid = fork();if (pid == 0) {  // child procfirst_loop = 1;close(left_fds[READ_END]);close(left_fds[WRITE_END]);int *temp = left_fds;left_fds = right_fds;right_fds = temp;if (pipe(right_fds) != 0) {printf("pipe() failed\n");exit(1);}close(left_fds[WRITE_END]);continue;} else if (pid > 0) {  // parent procfirst_loop = 0;p = n;printf("prime %d\n", p);continue;} else {printf("fork() failed\n");exit(1);}} else {if ((n % p) != 0) {write(right_fds[WRITE_END], &n, INT_SIZE);}}}
}

测试:

质数筛选测试

Task 5: find

find 命令实现在一个目录中递归地寻找特定名的文件。

在代码实现中,需要对子目录进行递归查询,这里对文件和目录的遍历可以参考 ls.c 中的代码。代码实现如下:

#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
#include "kernel/fs.h"/*** 获取一个 path 的名称,比如 `./a/b` 将返回 `b`
*/
char* fmtname(char *path)
{static char buf[DIRSIZ+1];char *p;// Find first character after last slash.for(p=path+strlen(path); p >= path && *p != '/'; p--);p++;// Return blank-padded name.if(strlen(p) >= DIRSIZ)return p;memmove(buf, p, strlen(p));buf[strlen(p)] = '\0';return buf;
}void find(char *path, char const * const target) {int fd;struct dirent de;struct stat st;if ((fd = open(path, 0)) < 0) {fprintf(2, "find: cannot open %s\n", path);return;}if (fstat(fd, &st) < 0) {fprintf(2, "find: cannot open %s\n", path);close(fd);return;}switch (st.type) {case T_FILE: {char *fname = fmtname(path);if (strcmp(fname, target) == 0) {printf("%s\n", path);}break;}case T_DIR: {char buf[512], *p;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)continue;memmove(p, de.name, DIRSIZ);p[DIRSIZ] = 0;if (stat(buf, &st) < 0) {printf("find: cannot stat %s\n", buf);continue;}char* dir_name = fmtname(buf);if (strcmp(dir_name, ".") != 0 && strcmp(dir_name, "..") != 0) {find(buf, target);}}break;}}close(fd);
}int
main(int argc, char *argv[])
{if (argc < 2) {fprintf(2, "Usage: find path file\n");exit(1);}char *path = argv[1];char const *target = argv[2];find(path, target);exit(0);
}

实验测试:

find 实验测试

Task 6: xargs

先解释一下这个命令的作用,以下面这个示例为例:

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

| 前面命令的 stdout 输出当作 xargs 命令的 stdin,然后 xargs 依次读取 stdin 的每一行,把每一行拼到 xargs 后面“echo byte”字符串后面并当成一个命令来执行。所以上面这个命令执行的结果等价于 xargs echo bye hello too

再举一个例子,比如 echo "1\n2" | xargs echo bye 的执行结果,相当于依次执行 echo bye 1echo bye 2

在代码实现中,就是每次从 stdin 中读取一行,然后将其与 xargs 后面的字符串拼起来形成一个命令,fork 出一个子进程并交给 exec 来执行:

#include "kernel/types.h"
#include "kernel/stat.h"
#include "kernel/param.h"
#include "user/user.h"#define STDIN 0
#define BUF_SZ 512static char buf[1024];
static char *args[MAXARG];
static int arg_num_init = 0;
static int argnum = 0;int readline(int fd) {char readbuf[2];argnum = arg_num_init;int offset = 0;while (1) {// 找到第一个非空字符while (1) {if (read(fd, readbuf, 1) != 1) {return 0;}if (readbuf[0] == '\n') {return 1;}if (readbuf[0] != ' ' && readbuf[0] != '\t') {args[argnum++] = buf + offset;buf[offset++] = readbuf[0];break;}}// 找到一个 argwhile (1) {if (read(fd, buf + offset, 1) != 1) {buf[offset] = '\0';return 0;}if (buf[offset] == '\n') {buf[offset] = '\0';return 1;}if (buf[offset] == ' ' || buf[offset] == '\t') {buf[offset++] = '\0';break;}++offset; }}
}int
main(int argc, char *argv[])
{int pid, status;if (argc < 2) {fprintf(2, "Usage: xargs command ...\n");exit(1);}char *command = argv[1];arg_num_init = argc - 1;args[0] = command;for (int i = 1; i < argc; i++) {args[i] = argv[i + 1];}int flag = 1;while (flag) {flag = readline(STDIN);args[argnum] = 0;if (flag == 0 && argnum == arg_num_init) {exit(0);}pid = fork();if (pid == 0) {exec(command, args);printf("exec failed!\n");exit(1);} else {wait(&status);}}exit(0);
}

实验测试:

xargs 测试

相关文章:

  • uniapp微信小程序投票系统实战 (SpringBoot2+vue3.2+element plus ) -后端鉴权拦截器实现
  • CentOS 7.8 安装 Docker
  • Python 架构模式:第五章到第九章
  • 代码随想录day24 开启回溯算法
  • go 修改postgresql的配置参数
  • C++完成Query执行sql语句的接口封装和测试
  • 开启Android学习之旅-5-Activity全屏
  • CHS_01.1.4+操作系统体系结构 一
  • C++推箱子游戏开发
  • React Hooks的useState、useRef使用
  • Linux-进程间通信_管道
  • <设计模式> 七大原则
  • Linux--好玩的进度条
  • 【博士每天一篇文-算法】Graph Structure of Neural Networks
  • hfish蜜罐docker部署
  • JS 中的深拷贝与浅拷贝
  • @angular/forms 源码解析之双向绑定
  • 【node学习】协程
  • 0x05 Python数据分析,Anaconda八斩刀
  • android 一些 utils
  • Druid 在有赞的实践
  • es6
  • JavaScript类型识别
  • Nginx 通过 Lua + Redis 实现动态封禁 IP
  • node-glob通配符
  • nodejs调试方法
  • vue-cli3搭建项目
  • 初识 beanstalkd
  • 从零开始在ubuntu上搭建node开发环境
  • 后端_MYSQL
  • 精彩代码 vue.js
  • 开发基于以太坊智能合约的DApp
  • 你真的知道 == 和 equals 的区别吗?
  • 如何使用 OAuth 2.0 将 LinkedIn 集成入 iOS 应用
  • 使用 Xcode 的 Target 区分开发和生产环境
  • 云大使推广中的常见热门问题
  • 正则学习笔记
  • 06-01 点餐小程序前台界面搭建
  • ​油烟净化器电源安全,保障健康餐饮生活
  • ${factoryList }后面有空格不影响
  • (Arcgis)Python编程批量将HDF5文件转换为TIFF格式并应用地理转换和投影信息
  • (C#)一个最简单的链表类
  • (笔试题)合法字符串
  • (第8天)保姆级 PL/SQL Developer 安装与配置
  • (分类)KNN算法- 参数调优
  • (万字长文)Spring的核心知识尽揽其中
  • (学习日记)2024.04.10:UCOSIII第三十八节:事件实验
  • (转)编辑寄语:因为爱心,所以美丽
  • (转)程序员技术练级攻略
  • (转)自己动手搭建Nginx+memcache+xdebug+php运行环境绿色版 For windows版
  • *_zh_CN.properties 国际化资源文件 struts 防乱码等
  • .halo勒索病毒解密方法|勒索病毒解决|勒索病毒恢复|数据库修复
  • .NET Core、DNX、DNU、DNVM、MVC6学习资料
  • .Net IOC框架入门之一 Unity
  • .Net Remoting(分离服务程序实现) - Part.3