MIT6.s081 2021 Lab Utilities

Boot xv6

按照示例切换到 util 分支后,看到目录下包含 Makefile 文件,执行 make qemu 即可。

sleep

思路

借助系统调用 sleep 实现一个命令行程序,关键是要找到封装了系统调用的 C 函数的位置,根据提示:

user/user.h for the C definition of sleep callable from a user program …

可知该函数的声明位于 user.h 头文件中,声明方式很简单:

int sleep(int);

将其“拷贝”(include)到需要编写的代码 user/sleep.c 中,调用 sleep(<睡眠时间>) 即可。

最后,按照提示,将编写的 sleep 代码添加到 Makefile 的 UPROGS 中,添加后如下所示:

UPROGS=\$U/_cat\$U/_echo\$U/_forktest\$U/_grep\$U/_init\$U/_kill\$U/_ln\$U/_ls\$U/_mkdir\$U/_rm\$U/_sh\$U/_stressfs\$U/_usertests\$U/_grind\$U/_wc\$U/_zombie\$U/_sleep\  # here

代码

// user/sleep.c#include "kernel/types.h" // 注意先包含types.h
#include "user/user.h"    // 再包含user.h(user.h中存在在types.h中定义的别名)int main(int argc, char* argv[]) {if (argc != 2) {fprintf(2, "sleep: argument count error\n");exit(1);}int time = atoi(argv[1]);sleep(time);exit(0);
}

pingpong

思路

本题主要是要理解管道的接口设计,以及借助该接口实现父进程与子进程之间的通信。这是 xv6 文档中对于 pipe 调用的描述:

int pipe(int p[]) // Create a pipe, put read/write file descriptors in p[0] and p[1].

pipe 创建一个管道,并分别将该管道的读、写端文件描述符置为 p[0]p[1],之后调用 fork 创建一个子进程,由于 fork 的作用是将父进程的数据直接拷贝给子进程,因此子进程同时继承了父进程的管道文件描述符,可以借助该文件描述符进行进程间通信(IPC),相当于借助一个共享文件进行通信,只不过该“文件”存储在内存的内核区域中,而不占用实际的磁盘存储空间。

利用管道解决本题的基本流程如下,首先需要创建两个管道 pa 和 pb,然后:

  1. 父进程向管道 pa 的写端写入 1 字节数据,然后关闭 pa 的写端。
  2. 子进程从管道 pa 的读端读取 1 字节数据,然后关闭 pa 的读端,打印信息,然后向管道 pb 的写端写入 1 字节数据,关闭 pb 的写端。
  3. 父进程从管道 pb 的读端读取 1 字节数据,关闭 pb 的读端,最后打印信息。

这里需要解释一下为什么需要两个管道,由于进程调度策略的影响,父进程和子进程的执行顺序并不确定。可能出现这样一种情况:在 fork 创建子进程后,父进程先被调度,将 1 字节数据写入管道,这时理想的情况是子进程被调度,然后读取父进程发送的数据,但是事实可能并不会如我们所愿,子进程可能一直得不到调度,父进程继续向下执行,从管道中读取自己刚刚发送的 1 字节的数据,这样子进程就无法收到父进程发送的数据,父子进程之间的通信也就失败了。

通过创建两个管道,并分别关闭对应的读端和写端,就能够得到两个单向数据流的管道,也就不会有上述自己写入的数据被被自己读取的情况出现。

代码

// user/pingpong.c#include "kernel/types.h"
#include "user/user.h"int main(int argc, char* argv[]) {char buf[2];int pa[2], pb[2];pipe(pa);pipe(pb);int pid = fork();if (pid == 0) {close(pa[1]);close(pb[0]);// phase_2read(pa[0], buf, 1);close(pa[0]);printf("%d: received ping\n", getpid());write(pb[1], "a", 1);close(pb[1]);}else if (pid > 0) {close(pa[0]);close(pb[1]);// phase_1write(pa[1], "a", 1);close(pa[1]);// phase_3read(pb[0], buf, 1);close(pb[0]);printf("%d: received pong\n", getpid());}else {fprintf(2, "pingpong: fork failed\n");exit(1);}exit(0);
}

primes

思路

实现一个基于管道的并发埃式筛(The sieve of Eratosthenes),关键是要理解管道的机制,以及仔细阅读题干给出的[文章](Bell Labs and CSP Threads (swtch.com)),该文章有关该埃式筛方法的介绍图片如下所示:

在这里插入图片描述

该算法的个人感觉十分精妙,以下是基本流程:

  1. 进程 0(主进程)发出一系列从 2 开始的整数序列。
  2. 进程 1 首先接收来自进程 0 发出的第一个整数 prime,prime 一定是一个质数,将其打印出来。然后继续按顺序接收来自进程 0 发出的其它整数,若接收到的某个整数能够被 prime 整除,则丢弃它(不做处理),否则将该整数发送给下一个进程。
  3. 后续进程的操作与进程 1 类似,直到没有任何整数发送给下一个进程,程序终止。

算法的思路并不复杂,主要问题在于如何使用管道实现上述流程中进程 i 与进程 i + 1 之间的通信。我这里只使用了一个 int[2] 来轮换地存放管道的文件描述符,并使用一个缓冲区来暂存每次要发送给下一个进程的数,在一个进程完成它所做的工作后,再将缓冲区中的数据批量写入管道,并创建子进程来完成接下来的工作。这里要千万注意管道完成读取或写入后及时关闭,否则可能会出现子进程读取管道时阻塞的情况。

我在写下这篇博客的过程中发现,虽然我使用的这个方法能够达到预期的效果,并成功通过测试用例,但是其实是有一定问题的:本方法的处理过程是串行的。事实上,每个进程都是在将本进程的所有工作全部完成之后,再调用 fork 来创建子进程,完成后续的工作,本质上与放在一个进程中完成所有工作并没有区别,与文章中提到的 “Concurrent” 完全相悖。理想的做法应该是创建一个 int[2] 数组来存放管道的文件描述符,并及时 fork 子进程来工作,以此来实现并发,具体的代码实现有待后续改进。

代码

// user/primes.c#include "kernel/types.h"
#include "user/user.h"int main(int argc, char* argv[]) {int p[2];pipe(p);int buf;int plist[35];for (int i = 2; i <= 35; ++i) {write(p[1], (char*)&i, 4);}close(p[1]);while (read(p[0], (char*)&buf, 4)) {int prime = buf;printf("prime %d\n", prime);int pcnt = 0;while (read(p[0], (char*)&buf, 4)) {if (buf % prime) {plist[pcnt++] = buf;}}close(p[0]);pipe(p); // rotating pipefor (int i = 0; i < pcnt; ++i) {write(p[1], (char*)(plist + i), 4);}close(p[1]);int pid = fork();if (pid == 0) {continue;}else if (pid > 0) {wait(0);exit(0);}else {fprintf(2, "primes: fork error\n");exit(1);}}exit(0);
}

find

思路

本题是这个 Lab 中我花费时间最长的,代码思路虽然不算很复杂,但是有很多的细节问题我在写的时候没有考虑到,感觉 debug 时间差不多是 coding 的几倍了。。。

题目要求实现一个简易的 find 命令,根据提示可以参考 user/ls.c 对目录的读取操作,并使用递归来实现对子目录的查找。基本思路就是打开一个指定路径的文件(目录也算是特殊的文件),并根据文件的类型做不同处理:

  1. 如果文件是常规文件,则判断改文件名是否是目标文件名(find 的第二个参数),如果是,则将其完整路径打印至标准输出。
  2. 如果文件是目录文件,则读取该目录下的所有文件名,并在该目录路径尾部加上 /st.name,依次构造一个新的文件名继续递归调用 find。注意不要递归进入 ...,否则将导致无限递归。

以上便是基本思路,具体实现可以阅读完整代码,下面讲一下我遇到的一些问题(bug):

  1. 使用 fstat 获取文件信息时 st.type 始终为 3(T_DEVICE 类型)。

这个问题其实挺难绷的,原因是我把 if ((fd = open(path, 0)) < 0) 写成了 if ((fd = open(path, 0) < 0)),因为 < 的优先级大于 =,所以导致 fd 的值始终为 0 或 1(逻辑表达式的值只能为真或假),那么后续产生意想不到的结果也就不意外了。。。

  1. 出现 find: cannot open file ./sh ,之后所有文件均打开失败

在打印出文件描述符的值后,问题的起因比较明显了。

在这里插入图片描述

文件描述符一直在增大,最终文件打开失败,open 返回 -1。很明显,是因为文件在打开后没有及时关闭,并释放文件描述符,最终文件描述符被全部占用,新的文件无法再被打开。这也解释了既然程序退出后,所有打开的文件会自动关闭,为什么还要建议手动关闭文件的问题。

  1. 读取到空文件名

前面的问题解决之后,我发现程序仍然会出现无限递归搜索的情况(如下图所示),按理说我已经对文件名进行了判断,如果是 . 或者 .. 则不做处理。

在这里插入图片描述

尝试打印文件名之后,我发现目录的最后一个文件名为空,这样的空文件名将导致程序不断往其末尾追加斜杠 / 而并没有递归进入该目录中。

在这里插入图片描述

事实上,使用 read 读取目录时,在读取目录的所有条目之后,会返回一个空的 dirent 结构体,此时 de.name 为空,作为循环结束的标志。其实 user/ls.c 有针对这个特性的判断,不过当时 coding 的时候没有细看。所以正如 Lab guidance 中所说:

Only when you have a firm grasp of the assignment and solution, then start coding.

代码

// user/find.c#include "kernel/types.h"
#include "kernel/stat.h"
#include "kernel/fs.h"
#include "user/user.h"char* file_name(char* path) {char* p;for (p = path + strlen(path); p >= path && *p != '/'; --p);++p;return p;
}void find(char* path, char* target) {int fd;struct dirent de;struct stat st;char buf[512];char* name, * p;if ((fd = open(path, 0)) < 0) {fprintf(2, "find: cannot open file %s\n", path);return;}if (fstat(fd, &st) < 0) {fprintf(2, "find: cannot stat file %s\n", path);close(fd);return;}switch (st.type) {case T_FILE:name = file_name(path);if (!strcmp(name, target)) {printf("%s\n", path);}break;case T_DIR:while (read(fd, &de, sizeof(de)) == sizeof(de)) {// prevent infinite recursionif (!de.inum || !strcmp(de.name, ".") || !strcmp(de.name, "..")) {continue;}// generate path of sub directorystrcpy(buf, path);p = buf + strlen(buf);*p++ = '/';memmove(p, de.name, DIRSIZ);p[DIRSIZ] = 0;find(buf, target);}break;default:break;}close(fd);
}int main(int argc, char* argv[]) {if (argc != 3) {fprintf(2, "find: argument count error\n");exit(1);}find(argv[1], argv[2]);exit(0);
}

xargs

思路

相较于 findxargs 的实现就简单很多了。由于之前自己实现过一个简单的 shell,因此对于 exec 系统调用还算比较熟悉,本题的主要内容就是根据 argv 和标准输入构造一个新的参数列表,作为指定命令行程序的参数,并使用 exec 来进行调用。

程序的流程比较简单,这里不过多介绍,直接查看完整代码即可。

代码

// user/xargs.c#include "kernel/types.h"
#include "user/user.h"
#include "kernel/param.h"int main(int argc, char* argv[]) {int i;char buf[512];char* nargv[MAXARG];while (gets(buf, MAXARG)) {buf[strlen(buf) - 1] = 0; // remove last '\n'for (i = 1; argv[i]; ++i) {nargv[i - 1] = argv[i];}nargv[i - 1] = buf;nargv[i] = 0;int pid = fork();if (pid == 0) {exec(argv[1], nargv);}else if (pid > 0) {wait(0);}else {fprintf(2, "xargs: fork error\n");exit(1);}}exit(0);
}

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

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

相关文章

【LLM】GLM系列模型要点

note 文章目录 noteGLM一、数据层面1. 预训练数据 二、GLM4模型层面三、GLM-4 All Tools四、GLM的其他技术Reference GLM Paper&#xff1a;https://arxiv.org/abs/2406.12793 GitHub&#xff1a;https://github.com/THUDM HF&#xff1a;https://huggingface.co/THUDM 经过…

将独立的 Python 网络应用程序分发给非技术用户

1. 问题背景 我们需要编写一个 Python 网络应用程序&#xff0c;供教师和学生在课堂上使用。该应用程序将在托管的网站上运行&#xff0c;但我们也希望用户能够下载一个自包含的应用程序&#xff0c;以便他们可以在本地安装&#xff0c;以获得更好的性能或他们根本无法在教室中…

【Solr 学习笔记】Solr 源码启动教程

Solr 源码启动教程 本教程记录了如何通过 IDEA 启动并调试 Solr 源码&#xff0c;从 Solr9 开始 Solr 项目已由 ant 方式改成了 gradle 构建方式&#xff0c;本教程将以 Solr 9 为例进行演示&#xff0c;IDE 选择使用 IntelliJ IDEA。 Solr github 地址&#xff1a;https://gi…

对比A100和4090:两者的区别以及适用点

自2022年年末英伟达发布4090芯片以来&#xff0c;这款产品凭借着其优异的性能迅速在科技界占据了一席之地。现如今&#xff0c;不论是在游戏体验、内容创作能力方面还是模型精度提升方面&#xff0c;4090都是一个绕不过去的名字。而A100作为早些发布的产品&#xff0c;其优异的…

盘点5款最热门的AI绘画软件!总有一款是你的菜

在数字化艺术日益盛行的今天&#xff0c;AI绘画软件成为了创作者们的新宠。这些软件不仅能够帮助艺术家们快速生成独特的艺术作品&#xff0c;还能为普通用户带来全新的绘画体验。今天&#xff0c;我们就来盘点五款最热门的AI绘画软件&#xff0c;看看哪一款是你的菜&#xff0…

Kubernates容器化JVM调优笔记(内存篇)

Kubernates容器化JVM调优笔记&#xff08;内存篇&#xff09; 先说结论背景思路方案 先说结论 1、首先如果是JDK8&#xff0c;需要使用JDK8_191版本以上&#xff0c;才支持容器化环境和以下参数&#xff0c;否则就更新到JDK10以上&#xff0c;选择对应的镜像构建就行了 2、在容…

Python使用策略模式绘制图片分析多组数据

趋势分析&#xff1a;折线图静态比较&#xff1a;条形图分布分析&#xff1a;箱线图离散情况&#xff1a;散点图 import matplotlib.pylab as plt from abc import ABC, abstractmethod import seaborn as sns import pandas as pd import plotly.graph_objects as go import p…

03 Shell编程之循环语句与函数

目录 3.1 for 循环语句 3.1.1 for 语句的结构 3.1.2 for 语句应用示例 1. 根据姓名列表批量添加用户 2. 根据IP地址列表检查主机状态 3.2 使用while循环语句 3.2.1 while语句的结构 3.2.2 while语句应用示例 1. 批量添加规律编号的用户 2. 猜价格游戏 3.3 until 循环语句 3.…

aws的eks(k8s)ingress+elb部署实践

eks&#xff08;k8s&#xff09;版本1.29 ingress 版本1.10.0 负载均衡elb 1. 创建Ingress-Nginx服务 部署项目地址【点我跳转】推荐自定义部署 可绑定acm证书什么的自己属性 这里就是aws上面Certificate Manager产品上面创建证书 导入 创建都行 对应集群版本推荐阵列GitH…

Arc2Face - 一张图生成逼真的多风格人脸,本地一键整合包下载

Arc2Face是用于人脸的基础模型训练&#xff0c;可批量生成超高质量主题的AI人脸艺术风格照&#xff0c;完美复制人脸。只需一张照片&#xff0c;几秒钟&#xff0c;即可批量生成超高质量主题的AI人脸艺术风格照&#xff0c;完美复制人脸。 Arc2Face 是一个创新的开源项目&…

测评:【ONLYOFFICE】版本更迭与AI加持下的最新ONLYOFFICE桌面编辑器8.1

你是否还在为没有一款合适的在线桌面编辑器而苦恼&#xff1f;你是否还在因为办公软件的选择过少而只能使用WPS或者office&#xff1f;随着办公需求的不断变化和发展&#xff0c;办公软件也在不断更新和改进。ONLYOFFICE 作为一款全功能办公软件&#xff0c;一直致力于为用户提…

WordPress如何删除前端评论中的网址字段?

前面跟大家分享的『WordPress插件Comment Link Remove and Other Comment Tools&#xff0c;删除评论网址字段』一文&#xff0c;通过安装插件可轻松删除前端评论中的网址字段&#xff0c;不过有些站长不喜欢安装插件&#xff0c;那么是否可以通过纯代码去掉网址字段呢&#xf…

车辆检测之图像识别

1. 导入资源包 import torch.nn as nn import tkinter as tk from tkinter import filedialog, messagebox from PIL import Image, ImageTk,ImageDraw,ImageFont import torch from torchvision import transforms, models from efficientnet_pytorch import EfficientNet im…

微信QQ视频裂变加群推广强制分享引流系统

用户在达到一定观看次数后&#xff0c;需要分享给好友或群组。只有好友点击推广链接后&#xff0c;观看次数才会增加。 通过引导用户分享至QQ和微信&#xff0c;实现快速裂变引流的效果&#xff01; 视频裂变推广程序通过强制分享链接&#xff0c;引导用户转发&#xff0c;从…

代理IP知识:导致代理IP访问超时的原因有哪些?

很多用户在使用代理IP进行网络访问时&#xff0c;可能会遇到代理IP超时的情况&#xff0c;也就是代理IP的延迟过高。代理IP延迟过高会影响用户的网络体验和数据获取效率。因此&#xff0c;了解代理IP延迟过高的原因很重要。以下是导致代理IP延迟过高的一些常见原因&#xff1a;…

怎么使用RSI指标分析现货黄金行情走势?

拿到一波现货黄金行情走势&#xff0c;如何着手对其进行分析呢&#xff1f;投资者只要在网络上搜索一下&#xff0c;保管能够找到各种各样的答案&#xff0c;而本文要讨论的就是其中一种&#xff0c;我们借助RSI指标进行分析。 RSI就是相对强弱指标的简称&#xff0c;这是市场中…

炎炎夏日,矿物质水为你防暑补水

炎炎夏日&#xff0c;整座城市如同一个巨大的“烤箱” 人们行走在炙热烈阳中 汗如雨下&#xff0c;口干舌燥 在这样的高温天气中 中暑的风险也随之增加 烈日当头的夏天 该如何预防中暑呢&#xff1f; 或许答案藏在一杯矿物质水中 为什么矿物质水能够预防中暑&#xff1f;…

编译xlnt开源库源码, 使用c++读写excel文件

编译xlnt开源库源码,在linux平台使用c读写excel文件 下载xnlt源码 官方网站https://tfussell.gitbooks.io/xlnt/content/ 下载地址https://github.com/tfussell/xlnt 下载libstudxml开源库源码 下载地址https://github.com/kamxgal/libstudxml 下载xnlt源码 官方网站https://…

项目启动端口被占用

项目启动端口被占用 Identify and stop the process that’s listening on port XXXX or configure this application to listen on another port. 1、查询占用端口的pid netstat -aon|findstr "端口号"2、终止进程 taskkill /pid 进程号 /f3、重启项目

宝宝早教电子图书 酷得电子方案

宝宝早教发声书是一种专为婴幼儿设计的图书&#xff0c;旨在通过有趣的图画和声音来吸引宝宝的注意力&#xff0c;帮助他们学习语言、认知和发展各种技能。这类书籍通常包括以下特点&#xff1a; 鲜艳的图画&#xff1a;发声书通常配有色彩鲜艳、形象生动的图画&#xff0c;以…