Skip to content

StrivingLee/os_lab

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

8 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

OS LAB 1 Rust与双链表

提交截止时间:2023年3月20日24时

[TOC]

简介

在这个实验中,你需要自学Rust语言,并根据提示实现一个简单的双链表(可以使用Unsafe)。

这是一个单人项目,你需要独立完成项目。我们提供了若干测试用例,你的代码需要能够通过测试用例。作为我们评分的标准,你需要提交代码以及一份简短的实验报告。实验报告的内容可以包括下面的三个部分:

  • Rust学习心得 or 笔记 or 学习记录
  • 简要描述你的整体设计
  • 对Rust实现链表其他方法的探究 or 实验中遇到的困难 or 对本实验的内容和难度的反馈

共2页左右即可。

我们推荐你使用以下两本书进行学习:

前者你可以学习到Rust的基本语法,而后者可以帮助你完成这个lab。你可能并不需要完整地看完它们的全部内容。但是下面这些知识点你可能会用到:

如果你希望更深入的学习Rust,或者你觉得某方面的知识点没有理解,你还可以参考以下视频和文档:

评分标准

部分 子项 分值
Part 1 - test_rev_for_loop``test_iter5分,其余5个每个测试点4分,共30分
Part 2 - test_get,test_remove,test_insert 每个7分,其他(3个)每个8分,共45分
Part 3 选做
报告 - 25分
总分 100分

环境的搭建和项目说明

本小节将介绍如何搭建一个Rust环境,并运行项目中的测试。你可以直接参考安装 Rust 环境 - Rust语言圣经(Rust Course)搭建环境。

Windows 下Rust的安装

点击链接Rust安装包 下载,或者在Rust网站上面找到对应的链接。

1

你可以选择1使用msvc版本,需要下载Microsoft C++ Build Tools(如果没装过VS Studio, 会自动安装,需要5GB空间);或者安装在msys上面(见安装 Rust 环境 - Rust语言圣经(Rust Course))。

安装的流程一路按确定就可以:

1

1

自动安装完成后,旁边的控制台会打印类似这样的输出:

1

输入1,默认安装即可。

你可能会因为网络原因无法下载成功,这时可以切换源进行下载。编辑系统环境变量,添加下面两个环境变量:

RUSTUP_DIST_SERVER=https://mirrors.ustc.edu.cn/rust-static RUSTUP_UPDATE_ROOT=https://mirrors.ustc.edu.cn/rust-static/rustup

1

1

重新打开rustup-init.exe即可

安装成功时,会看到这样的界面:

1

这里提示说我们要配置以下环境变量。在PATH里面添加%USERPROFILE%\.cargo\bin

1

1

最后,可以在命令行下面验证是否安装完成

1

Linux和Mac OS下的安装

命令行中输入下面这行即可:

curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf | sh

你可能会因为网络原因无法下载成功,你可以直接在浏览器内输入链接访问下载,也可以切换镜像地址,下面是中科大的镜像服务器:

export RUSTUP_DIST_SERVER=https://mirrors.ustc.edu.cn/rust-static
export RUSTUP_UPDATE_ROOT=https://mirrors.ustc.edu.cn/rust-static/rustup
curl https://sh.rustup.rs -sSf | sh

如果要让代理永久生效的话,可以把环境变量加入到bashrc里面:

echo 'export RUSTUP_DIST_SERVER=https://mirrors.ustc.edu.cn/rust-static' >> ~/.bashrc
echo 'export RUSTUP_UPDATE_ROOT=https://mirrors.ustc.edu.cn/rust-static/rustup' >> ~/.bashrc

当运行成功时,你会遇到选择数字的界面,选择1即可:

1

最后,运行下面的命令配置shell:

source "$HOME/.cargo/env"

你可以输入rustc -V查看rust版本,以验证是否安装成功。

IDE环境

推荐使用vscode作为IDE,也可以使用CLion。

Vscode链接:Windows, Mac OS

在Vsode Extensions中,找到rust-analyzer,Install即可

1

如果下载插件用时过久,可以尝试使用代理。参考这篇说明:Visual Studio Code代理设置

运行项目的测试

使用vscode打开项目的目录。打开src/tests.rs文件。如果插件运行正常,你会看到类似这样的按钮:

1

点击Run Tests,就可以运行测试;如果你安装了gdb等调试器,在需要断点的行号位置点击一下,点击Debug就可以进行断点和调试了。

当测试失败时,你会看到红色的提示,以及对应调用栈。你可以找到错误的行号;

1

而当测试通过时,你会看到绿色的提示。

1

对于答案错误的情形,你还会看到期望的结果和输出的结果:

1

src/libs.rs中,我们提供了运行多个测试的函数,你可以点击run tests进行测试。或者使用cargo命令运行单元测试。

项目文件说明

.
├── Cargo.lock    cargo生成的文件,用于管理依赖
├── Cargo.toml    用户配置包管理的地方
├── README.md     实验指导手册 markdown 版本
├── README.pdf    实验指导手册 pdf 版本
├── assets        实验指导手册的图片等资源            
└── src           项目源代码
    ├── double_linked_list.rs  * 双链表的实现 (主要需要实现的部分)
    ├── lib.rs                 管理对外接口;这里还提供了一些测试函数。
    └── tests.rs               测试用例

需要实现的部分

你需要补全src/double_linked_list.rs中的代码,并让它通过对应的测试。我们已经在你需要补充的部分加上了TODO: YOUR CODE HERE 的标记,你可以在拓展中心安装TODO Hightlight 插件以得到一个醒目的标记。 标记 当然,如果需要的话,你可以在其他地方添加代码。

在编写时,你需要先删掉unimplemented;对于PhantomData,你可以删掉也可以保留。

在一些函数中,参数的变量使用_作为前缀,表明这个函数没有被用到;当你用到这个参数时,建议把_前缀删掉。

PhantomData 的作用是什么?

在这里,我们主要用PhantomData来表示未使用的生命周期和类型参数。例如,迭代器中需要取链表元素的引用,这时候需要一个生命周期。而如果你用的是类似裸指针直接指向链表元素的方式,那结构体里就没有带引用的成员,不能加上'a的生命周期符号

impl<'a, T> Iterator for Iter<T> {
    type Item = &'a T;
    
    // .....
}

这时候编译器会提示说the lifetime parameter 'a is not constrained by the impl trait, self type, or predicate

因此我们需要在Iter上面添加生命周期符号。PhantomData 就是这样一种解决方式。它是一种零成本抽象,本身不占空间,只是让编译器以为当前结构体会拥有一个生命周期为'a的成员,从而通过编译。你可以阅读官方的文档和例子深入了解。

让编译器以为结构体拥有类型的所有权还可以让编译器进行drop check,感兴趣可以阅读这个关于Box实现的博客:讲讲让我熬了几天夜的Drop Check

Part 1 链表的基本功能

在Rust实现一个安全,高效的双链表是比较复杂的。Rust 非常注重安全性和内存管理。在 Rust 中,你需要清楚地指定哪些对象可以使用哪些资源,以及在何时何地释放这些资源。这就是所谓的所有权(ownership)和借用(borrowing)机制。

为实现指针的效果,常见的工具有所有权,引用,智能指针。所有权不适用于链表,在所有权的情况下,前驱和后继不能互为owner。引用可能会出现生命周期有问题的情况。比如有时候需要后面的节点生命周期比前面长,有时候则要反过来。解决引用生命周期问题可以使用智能指针Rc(引用计数)。引用还有一个问题,就是不能对一个对象同时可变引用多次。这个问题使用RefCell(内部可变性)解决。RefCell告诉编译器这个结构体是不可变的(实际上内部是可变的)。因此,实现单链表的一个经典组合是Rc<RefCell<Node<T>>>。但是放到双链表上面,这个组合会有一些问题。因为每个节点都有两个指针,因此会产生一个循环计数。如果用运行时检查可以解决部分问题,但是实现迭代器可能无法返回引用:不太优秀的双端队列-迭代器

目前的双链表的实现方案有很多:

  • 使用Weak智能指针。把两个Rc<RefCell<Node<T>>>的一个指针换成弱指针。例如,这里把所有的prev指针换成了Weak的指针:50行在stable,safe Rust实现双链表

  • 使用unsafe,像C/C++那样手动管理指针。这也是collections::Linkedlist中的实现方案。具体来说,就是管理节点的时候就用box::from_raw/into_raw来添加节点,直接使用裸指针解引用来访问数据。

  • 使用arena(Region-based memory management)。Arenas 本质上是一种将预期生命周期相同的内存进行分组的模式。比如说有时需要在某个生命周期内分配一堆对象,之后这些对象将被全部销毁。这可以简单理解为一个堆空间。我们可以用类似C/C++中的方式new一个对象,然后显式地释放(不释放也可以,后面一起销毁)。这样就规避了Rust中声明周期的问题。关于arena的库,可以参考:「译」Arenas in Rust。同时,这里也有一个实现链表的例子:基于slotmap实现链表

  • crossbeam-epoch: 这是一种基于epoch的无锁GC。相当于自动帮我们管理声明周期了,这也就没有之前的那么多问题了。

  • GhostCell+StaticRcGhostCell 提供了一种零开销的内部可变机制,StaticRc则提供了零开销的共享所有权机制,可以结合二者实现安全零开销链表。

在这一部分,你可以阅读Learning Rust With Entirely Too Many Linked Lists其中的An Ok Unsafe Queue和A Production Unsafe Deque章节来设计和实现你的链表。

首先,你需要完善struct LinkedList<T>struct Node<T> 的结构体,并完成下面的方法:

  • new
  • len
  • front
  • front_mut
  • back
  • back_mut

它们都仅需要几行代码就能完成。

接下来开始处理插入操作。

  • push_front
  • pop_front
  • push_back
  • pop_back

它们的语义和函数名相同,你也可以参考函数名上的注释。

如果你的实现正确,你应该可以通过:

  • test_front
  • test_back
  • test_len

3个测试。

你可以在项目根目录打开一个命令行,运行cargo test test_front,cargo test test_back逐一运行测试,也可以通过vscode点击测试函数上的run test运行测试。

在lib.rs中,我们提供了一次运行多个测试的函数test_part1,你可以一次运行多个测试点。


接下来是迭代器部分。迭代器是一个维护链表迭代的数据结构。你可以先尝试实现一个单向的迭代器,这可能需要一个指向当前迭代数据的指针,数据的长度。完善IterIterMut之后,为链表提供一个返回迭代器的接口iteriter_mut

然后我们为迭代器实现Iterator trait 。这个trait(可以认为是接口)要求我们实现nextsize_hint两个函数。next返回一个元素,并把迭代器指针往后移动。size_hint需要返回元素下界和上界。size_hint() 主要用于优化,比如为迭代器的元素保留空间。你可以直接返回(链表长度,Some(链表长度))。

最后为链表实现反向的迭代。修改Iter使其包含一个反向的指针,然后为迭代器实现DoubleEndedIterator 的trait。

如果你的实现正确,你应该可以通过:

  • test_iter
  • test_iter_mut
  • test_for_loop
  • test_rev_for_loop

4个测试。

Part 2 6个简单的链表操作

下面你需要自己实现6个简单的链表操作:

  • get/get_mut : 获取下标为at的元素的引用
  • remove : 删除下标为at的元素
  • insert : 插入一个元素到指定位置
  • contains : 判断链表内是否存在元素
  • split_off:将链表分割成两个链表,原链表为[0,at-1],新链表为[at,len-1]
  • find_mut : 找到链表中满足匹配条件的元素,返回它的引用

如果上面的表述不清楚,你可以参考这些函数上面的注释中的examples

如果你的实现正确,你应该可以通过:

  • test_get
  • test_remove
  • test_insert
  • test_contains
  • test_split
  • test_find_mut

6个测试。

Part 3 归并排序(选做)

在这一部分,你需要给链表写一个就地的归并排序。如果你忘了归并排序怎么写,你可以参考leetcode 链表排序的实现。

下面是递归实现的伪代码:

function merge_sort(list m) is
    // Base case. A list of zero or one elements is sorted, by definition.
    if length of m ≤ 1 then
        return m

    // Recursive case. First, divide the list into equal-sized sublists
    // consisting of the first half and second half of the list.
    // This assumes lists start at index 0.
    var left := empty list
    var right := empty list
    for each x with index i in m do
        if i < (length of m)/2 then
            add x to left
        else
            add x to right

    // Recursively sort both sublists.
    left := merge_sort(left)
    right := merge_sort(right)

    // Then merge the now-sorted sublists.
    return merge(left, right)
    
function merge(left, right) is
    var result := empty list

    while left is not empty and right is not empty do
        if first(left) ≤ first(right) then
            append first(left) to result
            left := rest(left)
        else
            append first(right) to result
            right := rest(right)

    // Either left or right may have elements left; consume them.
    // (Only one of the following loops will actually be entered.)
    while left is not empty do
        append first(left) to result
        left := rest(left)
    while right is not empty do
        append first(right) to result
        right := rest(right)
    return result

如果你需要为你的实现添加辅助函数,可以在impl<T:PartialOrd+Default> LinkedList<T>块中进行编写。

如果你的实现正确,你应该可以通过:

  • test_merge_sort1
  • test_merge_sort2
  • test_merge_sort3

3个测试

提交

你需要提交一个git patch到我们的平台上。报告可能会交到ucloud上面,也可能在同一平台。

在提交之前,你应该在本地运行测试,服务器上的测试数据和本地是相同的,只是为了检查你的完成情况。因此不必频繁提交。

首先需要安装一个git。

你可以在git官网找到对应的下载链接,双击安装包安装即可(中间的选项可以直接一路Next),如果使用debian,ubuntu,可以直接使用sudo apt install -y git来安装。

安装好git后,可以使用git --version查看git版本

接下来,切换到我们的项目下面,配置一下用户名和邮箱。

git config user.name "<用户名>"
git config user.email "<邮箱>"

我们暂时不使用git中的邮箱作为判定账户的依据,因此这里随便填一个也可以。

commit

首先,你需要为你的代码生成一个commit。一般来说,你只要提交src/double_linked_list.rs的修改。

你可以使用命令行或者vscode来实现这个操作。

命令行

在你完成全部或者部分练习后,可以git add <文件名>来添加对一个文件的修改。例如,git add src/double_linked_list.rs

然后,git commit -m "<message>"来创建一个commit。

如果你commit完后悔了,要恢复上一个版本,可以使用git reset --soft HEAD^

vscode

1 点击切换到vscode的git面板,然后在Changes里面的文件中找到你要添加的修改文件。点击上面的加号确认修改,在Message框中输入commit的信息,最后点击Commit创建一个commitment。

patch

在完成练习后,你需要生成一个git patch。

使用git log查看之前的commit id,复制后,按q退出。

找到你提交的commit范围

git format-patch –-stdout <from>..<to>  > example.patch
# 或者
git format-patch --stdout -<向前n个commit> HEAD  > example.patch
# 只有一个commit就可以用
git format-patch -1 HEAD --stdout > example.patch

就会在当前目录下面创建一个example.patch文件,你只需要提交这个文件就可以了。

例如下图中,commit id的范围是5d87bc88d72d 到 a181054e61cda75, 1

因此可以用:

git format-patch --stdout 5d87bc88d72d..a181054e61cda75  > example.patch

在平台上提交

我们搭建了一个网站来接收和运行git patch,目前的url为:http://10.109.246.160:8765/login

登录后,将文件在提交界面上传即可。 1 你可以看到运行的结果和日志输出。

About

操作系统课程设计

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • Rust 97.0%
  • Dockerfile 3.0%