lab1 utilities

news2024/11/25 9:29:14

在这里插入图片描述

测试和运行

参考大佬

  1. 修改grade-lab-util文件中的python为python3
  2. xv6.out这个文件的所有者可能是root,需要修改为用户,sudo chown woaixiaoxiao xv6.out
    每完成一个函数,执行下面的步骤
  3. 在Makefile中加入新增的程序$U/_sleep\
  4. make qemu,顺便可以自测一下命令是否有效
  5. ./grade-lab-util sleep

sleep


要求

  1. xv6实现sleep函数,用户可以指定暂停多少个tick
  2. user/sleep.c中实现,这个文件要自己创建

思路

  1. 首先根据argc和argv判断指令是否输入正确,并提取出sleep的tick数量
  2. 然后使用系统调用sleep函数
    1. 这里我一开始很疑惑,既然都有了一个sleep系统调用了,干嘛还要我们自己实现一个。
    2. 并且我发现只需要引用user/user.h头文件就可以使用这个系统调用,但是我没有发现user/user.c文件,目前我还看不太懂这个系统调用是如何实现的。
    3. 看了一些博客后,发现后面会讲,所以现在就把这些系统调用当做黑盒子来用就行了。
    4. 系统调用必须通过程序来使用的,也就是说就算我们有了sleep系统调用,但是我们依然需要写一个比如说c语言程序sleep来调用这个系统调用

注意点

  1. 头文件的顺序是有讲究的,因为user.h中使用了types.h中定义的变量,因此必须也包含types.h并且需要放到user.h前面

代码实现

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

int main(int argc, char** argv) {
    if (argc < 2) {
        printf("usage: sleep time_val...\n");
        exit(1);
    }
    int tick = atoi(argv[1]);
    sleep(tick);
    exit(0);
}

pingpong


要求

  1. 在父子进程之间利用两个管道进行通信,每个管道负责一个方向
  2. 只需要传输一个字节的数据,父子进程在收到数据后应该打印信息
  3. 顺序上应该父进程先发送,子进程后发送

思路

  1. 按照题目要求的来就行了,顺序上通过read自带的阻塞就可以让子进程在收到父进程发送数据之后再发给父进程了

注意点

  1. 对于管道,可以看成是已经open的文件,不需要open,直接read和write
  2. read和write需要给出size,而c语言里的字符串往往是通过一个0来结尾的,那在调用read或者write时的size需要加上这个0吗?答案是不需要,只需要给出有效数据的size。而这个0只需要在我们创建buf数组的时候考虑即可
  3. 可以定义宏来表示0和1,这样在使用管道的时候更加清晰

实现

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

#define SIZE 1

int main() {
  // p1 父写子读
  int p1[2];
  pipe(p1);
  // p2 子写父读
  int p2[2];
  pipe(p2);
  // 子进程
  if (fork() == 0) {
    // 子进程阻塞p1的写端,p2的读端
    close(p1[1]);
    close(p2[0]);
    char buf[SIZE + 1];
    read(p1[0], buf, SIZE);
    printf("%d: received ping\n", getpid());
    write(p2[1], buf, SIZE);
    exit(0);
  } else {
    // 父进程
    close(p1[0]);
    close(p2[1]);
    char buf[SIZE] = "1";
    write(p1[1], buf, SIZE);
    read(p2[0], buf, SIZE);
    printf("%d: received pong\n", getpid());
  }
  exit(0);
}

primes


题意

  1. 要我们输出35以内的所有质数(实验文档给的参考资料是求1000以内的,但是好像因为xv6资源的限制,搞不了这么多,所以只需要求35以内的)
  2. 需要用到一种很神奇的方法
    1. 我们会用到很多进程,每个进程之间用一个管道相连接
    2. 每个进程会收到上一个进程发来的很多数字,其中第一个数字一定是质数,后面的数字有两种情况
      1. 如果是第一个数字的倍数,那肯定不是质数,不需要管了
      2. 如果不是第一个数字的倍数,则传给下一个进程,让下一个进程去处理

思路

  1. 解题的方法以及比较明确了,关键是如何实现这个过程?
  2. 我们最开始只有一个main进程,而我们需要很多的进程,并且这些进程之间是串联的,最重要的是逻辑都一样,所以可以用递归
  3. 这些进程是通过管道进行通信的,那么最简单的方法就是将管道作为递归函数的参数进行传递,这样就把所有的进程给串起来了

注意点

  1. 因为xv6的文件描述符是有限的,所以我们最好在能够关闭管道的时候都给它关了
  2. 在我的实现中,递归程序很早的就fork了,但是因为在fork之前先执行了一个read操作,因此,不会导致无限增殖,一个进程想要fork,起码也需要它获得了一个值

代码实现

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

#define INT_SIZE 4
#define RD 0
#define WR 1
#define NULL 0

void recursion(int p[]) {
  // 首先关闭写端
  close(p[WR]);
  // 读取base数字
  int base;
  int temp = read(p[RD], &base, INT_SIZE);
  // 压根没有输入到这个进程
  if (temp == 0) {
    return;
  }
  printf("prime %d\n", base);
  // 创建管道,并和子进程联动
  int p2[2];
  pipe(p2);
  if (fork() == 0) {
    recursion(p2);
    exit(0);
  }
  // 关闭读端
  close(p2[RD]);
  // 开始不断接受父进程的输入,判断之后传递给子进程
  int rec, res;
  while (1) {
    res = read(p[RD], &rec, INT_SIZE);
    // 父进程结束写了
    if (res == 0) {
      break;
    }
    // 如果这个数字不是base的倍数,那么写给子进程
    if (rec % base != 0) {
      write(p2[WR], &rec, INT_SIZE);
    }
  }
  // 写完了,关闭自己的写端
  close(p2[WR]);
  // 关闭父进程给的管道的读端
  close(p[RD]);
  wait(NULL);
}

int main() {

  int p[2];
  pipe(p);
  // 子进程
  if (fork() == 0) {
    recursion(p);
    exit(0);
  }
  // 父进程
  close(p[RD]);
  for (int i = 2;i <= 35;i++) {
    write(p[WR], &i, INT_SIZE);
  }
  close(p[WR]);
  // 等待子进程结束
  wait(NULL);

  exit(0);
}

find


题意

  1. 实现find指令,find dir filename的作用是找到dir目录中(包括所有子目录中),所有文件名为filename的文件
  2. 因此,我们要做的就是取出dir目录的每一项,如果是文件,那就比较这个文件的名称,如果是目录,那我们就递归进去

思路

  1. hints里提示我们去看ls.c文件是如何访问目录的
    1. 首先,我们需要根据目录的路径打开目录。目录也是一个文件,所以方法和打开普通的文件一样,并且得到一个文件描述符
    2. 通过文件描述符去read,但是这个时候要用dirent这个结构去读取每一个目录项
    3. 通过direntname以及当前目录的path,可以构造出这个目录项的path
    4. 通过这个目录项的pathstat函数,可以把这个目录项的信息给读到stat结构中
    5. 通过stat结构体的type可以区分目录和文件
      1. 文件则直接比较名字
      2. 目录如果不是...,那就递归进去

注意点

  1. 打开的文件描述符最好自己记得把它给关了

实现代码

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

#define O_RDONLY 0
#define MAX_PATH 512

void search(char* dir, char* filename) {
  // 获取当前目录的文件描述符
  int fd = open(dir, O_RDONLY);
  // 不断读取目录项,进行判断
  // 这个p是用来拼接这个目录中的子项的路径的
  char buf[MAX_PATH];
  strcpy(buf, dir);
  char* p = buf + strlen(buf);
  *p = '/';
  p++;
  // 正式读取每一行,并根据目录还是文件进行讨论
  struct dirent de;
  struct stat st;
  while (read(fd, &de, sizeof(de)) == sizeof(de)) {
    // 无效
    if (de.inum == 0) {
      continue;
    }
    // 拼接出目录的这一项的path
    memmove(p, de.name, DIRSIZ);
    p[DIRSIZ] = 0;
    // 取出这一项的信息
    stat(buf, &st);
    // 如果是文件
    if (st.type == T_FILE) {
      // 文件名相同,打印path
      if (strcmp(de.name, filename) == 0) {
        printf("%s\n", buf);
      }
    } else if (st.type == T_DIR) {
      // 这一项是目录,只要不是 . 或者 .. 那就递归进去
      if (strcmp(de.name, ".") != 0 && strcmp(de.name, "..") != 0) {
        search(buf, filename);
      }
    }
  }
  // 记得关闭文件描述符
  close(fd);
}

int main(int argc, char** argv) {
  if (argc != 3) {
    printf("usage: find dir filename\n");
    exit(1);
  }
  char* dir = argv[1];
  char* filename = argv[2];
  search(dir, filename);
  exit(0);
}

xargs


题意

  1. 实现xargs函数,xargs a b代表执行a程序,a程序的第一个参数是bxargs会从标准输入中按行读取,将每一行作为a的其他参数,假如读入了某一行是c d,那么说明a程序有三个参数,依次是b c d
  2. xargs按行读取的意思是,对于每一行,它都会读取这一行所有被空格分开的值,将它作为a程序的参数。假如有n行,就会执行na程序
  3. 实验文档里给的示例echo hello too | xargs echo bye
    1. 这里面|的作用是把它前面的运行结果给放到后面这个函数的标准输入中
    2. 在这个例子里,就是把hello too作为一行放到了xargs函数的标准输入里,也就是说,如果xargs能够调用scanf函数,就可以读到hello too

思路

  1. 从标准输入中不断地读取,以每一行为单位进行fork和exec
  2. 每一行以\n为结束符,实验文档推荐我们每一次读一个字节,这样比较好判断\n,可以通过read函数实现读取
  3. 读取的每个字节有三种情况
    1. 是空格,则说明空格前那个参数已经被读取完了,加入到之后要调用的exec函数的args数组中
    2. 是换行符,首先也要加入前面的参数,然后记得给args数组封尾,即放一个null在最后,之后fork,exec,并进入新一行的读取
  4. 如果read失败,说明读完了,跳出整个循环,wait等到所有子进程结束

注意点

  1. 我们不断从标准输入中读取,其实就是为了给exec函数准备参数数组,这里用args表示,一开始xargs的main函数的argv数组就包括了要执行的程序的一些信息,所以应该将其挪到args数组中
  2. 当我们成功从某一行中读取一个参数的时候,这里是把这个参数边读边存到input_line字符串中,不能直接将这个字符串放到args数组,因为这个字符串被重复使用,所以我们要进行深拷贝,这里手写了一个strdup函数完成这个效果
  3. 然后就是注意各种字符串的操作,要给它封住尾巴
  4. 最后通过这个操作等待所有子进程结束while (wait(0) != -1)

实现代码

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

#define stdin 0
#define NULL 0
#define MAX_SIZE 1024
#define CHAR_SIZE 1

char* strdup(char* p) {
  char* temp = (char*)malloc(strlen(p) + 1);
  memcpy(temp, p, strlen(p));
  return temp;
}

int main(int argc, char* argv[]) {
  // 初始化我们要传递给execute函数的参数
  char* args[MAXARG + 2];
  for (int i = 1;i < argc;i++) {
    args[i - 1] = argv[i];
  }
  int arg_count = argc - 1;
  // 从标准输入一行一行地读
  char input_line[MAX_SIZE];
  while (1) {
    // 最外层的这个while循环,每循环一次代表要execute一次
    // 因此arg_count和p都应该重置,其中p用来操作input_line
    arg_count = argc - 1;
    char* p = input_line;
    // 用read从标准输入端读
    int res;
    while ((res = read(stdin, p, CHAR_SIZE)) > 0) {
      // 如果读入的既不是空格也不是换行,那就让p++,继续读入这个参数
      if (*p != ' ' && *p != '\n') {
        p++;
        // 读入的是空格,说明第一个参数已经完成了
        // 那么我们可以将这个参数写入args,并且修改p指针
      } else if (*p == ' ') {
        *p = '\0';
        args[arg_count] = strdup(input_line);
        arg_count++;
        p = input_line;
        // 读入的是换行,说明已经完全读完了
        // 存当前的这个参数到args,并且为args增加一个结尾标志null
        // 然后fork,exec
      } else if (*p == '\n') {
        *p = '\0';
        args[arg_count] = strdup(input_line);
        arg_count++;
        args[arg_count] = NULL;
        // 子进程,去exec
        if (fork() == 0) {
          exec(args[0], args);
          exit(0);
        }
        // 父进程,现在应该去读新的一行了,break
        break;
      }
    }
    // 读完了
    if (res <= 0) {
      break;
    }
  }
  // 等待所有子进程结束
  while (wait(0) != -1) {
  }
  exit(0);
}

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

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

相关文章

git 使用远端代码强制覆盖本地

有时候会遇到这种情景&#xff0c;我们本地的代码不需要了&#xff0c;需要使用远端的代码强制覆盖&#xff0c;这时候可以使用下面的命令 git fetch --all然后再执行下面的命令&#xff0c;重置为远端的代码&#xff0c;即使用远端的代码将本地覆盖 origin/远端分之名 git re…

Map中compute、putIfAbsent、computeIfAbsent、merge、computeIfPresent使用

目录 putIfAbsent computeIfAbsent computeIfPresent compute merge putIfAbsent 解释&#xff1a;【不存在则添加】&#xff0c;如果map中没有该key&#xff0c;则直接添加&#xff1b;如果map中已经存在该key&#xff0c;则value保持不变 default V putIfAbsent(K key,…

Metasploitable2靶机漏洞复现

一、信息收集 nmap扫描靶机信息 二、弱口令 1.系统弱口令 在Kali Linux中使用telnet远程连接靶机 输入账号密码msfadmin即可登录 2.MySQL弱口令 使用mysql -h 靶机IP地址即可连接 3.PostgreSQL弱密码登录 输入psql -h 192.168.110.134 -U postgres 密码为postgres 输入\…

Python中的lambda函数

前言 嗨喽~大家好呀&#xff0c;这里是魔王呐 ❤ ~! Python中的lambda函数 在Python中&#xff0c;我们使用lambda关键字来声明一个匿名函数&#xff0c; 这就是为什么我们将它们称为“lambda函数”。 匿名函数是指没有声明函数名称的函数。 尽管它们在语法上看起来不同&a…

【MySQL】并发执行事务可能存在的问题, 事物的四种隔离级别

文章目录 前言一、并发执行事务可能存在的问题1, 脏读问题2, 不可重复读3, 幻读 二、MySQL 的四种隔离级别1, READ UNCOMMITTED 读未提交2, READ COMMITTED 读已提交3, REPEATABLE READ 可重复读 (MySQL 的默认事务隔离级别)4, SERIALIZABLE 串行化 总结 前言 各位读者好, 我是…

百度智能云“千帆大模型平台”升级,大模型最多,Prompt模板最全

1、前言 从ChatGPT正式推出之后&#xff0c;大模型开始逐渐火爆起来&#xff0c;基于大模型的潜力与广泛应用前景&#xff0c;多个厂商也开始在大模型领域进行深耕布局。越来越多的人也开始尝试使用大模型来解决日常工作或生活中的问题&#xff0c;有效地提高了处理问题的效率。…

探讨uniapp的生命周期问题

在uniapp中,生命周期函数分为应用生命周期函数、页面生命周期函数和组件生命周期函数. 1应用声明周期 应用生命周期函数只能在 App.vue 中监听有效&#xff0c;在其他页监听无效。 onLaunch&#xff1a;当uni-app 初始化完成时触发&#xff08;全局只触发一次&#xff09;on…

【变形金刚03】使用 Pytorch 开始构建transformer

一、说明 在本教程中&#xff0c;我们将使用 PyTorch 从头开始构建一个基本的转换器模型。Vaswani等人在论文“注意力是你所需要的一切”中引入的Transformer模型是一种深度学习架构&#xff0c;专为序列到序列任务而设计&#xff0c;例如机器翻译和文本摘要。它基于自我注意机…

【传输层】Tcp协议的原理(二)

文章目录 一、TCP协议原理&#xff08;二&#xff09;总结 一、TCP协议原理&#xff08;二&#xff09; 1.解决TIME_WAIT状态引起的bind失败的方法 我们之前实现tcp服务器的时候发现&#xff0c;服务器经常有时候断开可以立即重启&#xff0c;有时候断开必须换端口号才能重新…

qt事件系统源码-----定时器

qt定时器的使用一般有以下几种方式&#xff1a; 1、直接使用QTimer对象&#xff0c;绑定定时器的timeout信号&#xff1b; 2、使用QTimer的静态方法singleshot方法&#xff0c;产生一个一次性的定时事件 3、在QObject子类中&#xff0c;调用startTimer方法&#xff0c;产生定…

Vue.js从入门到精通:软件开发视频大讲堂

&#x1f482; 个人网站:【工具大全】【游戏大全】【神级源码资源网】&#x1f91f; 前端学习课程&#xff1a;&#x1f449;【28个案例趣学前端】【400个JS面试题】&#x1f485; 寻找学习交流、摸鱼划水的小伙伴&#xff0c;请点击【摸鱼学习交流群】 前言 随着Web应用程序的…

nodejs+vue+elementui+express旅游出行指南网站_655ms

本文主要介绍了一种基于windows平台实现的旅游出行指南。该系统为用户找到景点信息和酒店信息提供了更安全、更高效、更便捷的途径。本系统有两个角色&#xff1a;管理员和用户&#xff0c;要求具备以下功能&#xff1a; &#xff08;1&#xff09;用户可以浏览主页了解旅游出行…

2023牛客暑期多校训练营8-C Clamped Sequence II

2023牛客暑期多校训练营8-C Clamped Sequence II https://ac.nowcoder.com/acm/contest/57362/C 文章目录 2023牛客暑期多校训练营8-C Clamped Sequence II题意解题思路代码 题意 解题思路 先考虑不加紧密度的情况&#xff0c;要支持单点修改&#xff0c;整体查询&#xff0…

阿里云服务器安装AMH面板建站教程

本文阿里云百科分享使用阿里云服务器安装AMH面板建站教程&#xff0c;AMH是一套通过Web控制和管理Linux服务器以及虚拟主机的管理系统。您可以使用云服务器ECS安装AMH来搭建PHP网站。本篇教程分别介绍如何在Linux系统实例中部署AMH并快速搭建PHP网站。 目录 前提条件 手动部…

Electron 应用实现截图并编辑功能

Electron 应用实现截图并编辑功能 Electron 应用如何实现截屏功能&#xff0c;有两种思路&#xff0c;作为一个框架是否可以通过框架实现截屏&#xff0c;另一种就是 javaScript 结合 html 中画布功能实现截屏。 在初步思考之后&#xff0c;本文优先探索使用 Electron 实现截屏…

CCF-CSP 29次 第三题【202303-3 LDAP】(多个STL+递归)

计算机软件能力认证考试系统 #include <iostream> #include <cstring> #include <algorithm> #include <vector> #include <unordered_map> #include <string>using namespace std;typedef long long LL;const int N 2510, M 510;int n…

Qt应用开发(基础篇)——拆分器窗口 QSplitter QSplitterHandle

一、前言 QSplitter继承于QFrame&#xff0c;QFrame继承于QWidget&#xff0c;是Qt的一个部件容器工具类。 框架类QFrame介绍 QSplitter拆分器&#xff0c;用户通过拖动子部件之间的边界来控制子部件的大小&#xff0c;在应用开发中数据分模块展示、图片展示等场景下使用。 二、…

Nuxt.js--》解锁 Nuxt 项目的潜力:从配置开始,迈向成功

博主今天开设Nuxt.js专栏&#xff0c;带您深入探索 Nuxt.js 的精髓&#xff0c;学习如何利用其强大功能构建出色的前端应用程序。我们将探讨其核心特点、灵活的路由系统、优化技巧以及常见问题的解决方案。无论您是想了解 Nuxt.js 的基础知识&#xff0c;还是希望掌握进阶技巧&…

FastDFS安装教程

FastDFS安装 软件下载 需要的软件&#xff1a;fastdfs-6.0.4、libfastcommon-1.0.42、fastdfs-nginx-module-1.22.tar.gz 下载地址 安装 fastdfs是使用c语言写的&#xff0c;需要先配置c语言环境。 yum install -y gcc gcc-cyum install libevent安装libfastcommon函数库…

Leetcode-每日一题【剑指 Offer 20. 表示数值的字符串】

题目 请实现一个函数用来判断字符串是否表示数值&#xff08;包括整数和小数&#xff09;。 数值&#xff08;按顺序&#xff09;可以分成以下几个部分&#xff1a; 若干空格一个 小数 或者 整数&#xff08;可选&#xff09;一个 e 或 E &#xff0c;后面跟着一个 整数若干空…