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. 以下关于程序设计语言的描述&#xff0c;错误的选项是&#xff1a; A Python语言是一种脚本编程语言 B 汇编语言是直接操作计算机硬件的编程语言 C 程序设计语言经历了机器语言、汇编语言、脚本语言三个阶段 D 编译和解释的区别是一次性翻译程序还是每次执行时都要翻…

将独立的 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…

mysql:mysql XA事务的简单例子

# 测试 创建测试表&#xff0c;并写入数据 create table cash_account(name varchar(10),balance decimal(10,2) ) engineinnodb;insert into cash_account values(Tom, 210000);第一个会话&#xff0c;执行XA事务的业务内容 xa start transfer_of_account,cash;update cas…

对比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…

TC397的Trap机制

Trap机制 Trap是MCU内部引发的硬件中断。 Trap是由不可屏蔽中断 (NMI)、指令异常、内存管理异常或非法访问等事件导致的。 Trap

秒杀系统设计与实现

将从11 个点来进行秒杀系统得设计与实现分析。 1、哪些地方存在瞬时高并发&#xff1f; 1. 预抢购业务&#xff1a;活动未正式开始前&#xff0c;先进行活动预约。在真正秒杀的时间点&#xff0c;很多数据都是预处理好的了&#xff0c;可以很大程度削减系统压力。比如&#xff…

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…

503.下一个更大元素||

循环解题&#xff1a; class Solution { public:vector<int> nextGreaterElements(vector<int>& nums) {int n nums.size();vector<int> res;for (int i 0; i <n; i){int k i;bool flag false;for (int j 1;j <n-1;j){if((kj)%n ! k &&a…

MySQL中实现随机排序

MySQL中实现随机排序有多种方法&#xff1a; 1、使用 RAND() 函数 通过使用 RAND() 函数可以给每条记录生成一个随机数&#xff0c;然后按照这个随机数进行排序。例如&#xff1a; SELECT * FROM table_name ORDER BY RAND();这种方法简单易用&#xff0c;但不适用于大数据量…

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

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

微信小程序-scroll-view实现上拉加载和下拉刷新

一.scroll-view实现上拉加载 scroll-view组件通过自身一些属性实现上拉加载的功能。 lower-threshold“100"属性表示距离底部多少px就会实现触发下拉加载的事件。 类似于在.json文件里面配置"onReachBottomDistance”: 100 bindscrolltolower"getMore"属…

测评:【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…