rCore_Lab1

发布时间 2024-01-07 19:05:15作者: TLSN

lab1代码 https://github.com/TL-SN/rCore

记录一下写rCore中学到的OS知识与rust知识

一、永不返回的发散函数

image-20231226211811033

二、string与&str所有权问题

image-20231227084014688

另外:

let s1 = String::from("hello");
let s2 = s1;

println!("{}, world!", s1);

会报错,而

fn main() {
    let x: &str = "hello, world";
    let y = x;
    println!("{},{}",x,y);
}

不会报错

这段代码和之前的 String 有一个本质上的区别:在 String 的例子中 s1 持有了通过String::from("hello") 创建的值的所有权,而这个例子中,x 只是引用了存储在二进制中的字符串 "hello, world"并没有持有所有权

因此 let y = x 中,仅仅是对该引用进行了拷贝,此时 yx 都引用了同一个字符串。

有些变量是不具备所有权特征的:

image-20231227084837347

任何基本类型的组合可以 Copy

三、不可变引用与可变引用解决所有权权问题

由于string的所有权问题,导致我们在函数参数传递的时候不得不小心,以防string的所有权转移,为此,rust提供了引用操作

image-20231227090008526

image-20231227090731778

可变引用;

使用 &mut 借用:

image-20231227094219437

需要注意的是:

1、可变引用同时只能存在一个

2、可变引用与不可变引用不能同时存在

image-20231227094418058

四、循环访问与循环修改

image-20231227104818535

image-20231227104824014

五、模式匹配 / switch

image-20231227105318201

六、package与包(crate)

package是项目工程,包是指编译单元

比如: src/main.rssrc/lib.rs 都是编译单元,因此它们都是包。

不过一般main.rs或者lib.rs被称为包根

而项目工程就是cargo new的整个项目

比如,这样一个经典的package:

image-20231228085853675

七、闭包与迭代器

有点像lamda

八、内联汇编与编译器优化

asm 宏的 inoutinoutlateoutinlateout 参数就是为了让编译器帮助分配寄存器的。

in 表示将变量的值传给寄存器,编译器生成的汇编代码会使得在内联汇编代码中读取相应的寄存器,就得到了传入的变量的值;

out 表示将寄存器的值写到变量中,在内联汇编代码中写入相应寄存器,编译器在内联汇编之后生成的汇编代码会使得相应变量具有写入相应寄存器的值;

late 则是代表编译器可以采取进一步的策略来优化寄存器分配:默认的分配策略给每个参数分配不同的寄存器,使用 lateoutinlateout 的参数则允许编译器复用某个 in 参数的寄存器,只要内联汇编代码中先读完所有的 in 寄存器,再输出 lateoutinlateout 寄存器即可。

全局内联汇编:

global_asm 我们可以写出源代码完全是汇编代码的函数,函数名就是汇编代码中的标签,函数参数和返回值需要按照 ABI 约定来处理

image-20231229153043863

九、不可恢复错误与可恢复错误

不可恢复错误

panic

可恢复错误

image-20231229164019964

一般的话,可以这样使用:

image-20231230083600451

在c语言中,我们是使用if判断NULL来完成是否打开文件成功的

十、option

option用来表示一个值可能存在的抽象概念

image-20231229164947552

image-20231229165000533

目的就是 强制做类型检查

image-20231229165209484

比如图中会报错

而如果在rust下,就不可能出现这种情况:

image-20231230081746720

十一、unwrap 与 expect与?与.

image-20231230083757906

这个东西与下面这个东西等价

image-20231230083753178

缺点就是不可以自定义错误信息

那么我们可以使用 expect 关键字:

image-20231230084034714

我们再看 ? 这个关键字

正常下我们读取文件的操作是这样的:

image-20231230084643833

这里的Ok(_) => OK(s) 为什么要返回OK(s)呢,直接返回s不好吗,这是因为函数返回类型是Result类型

我们可以简化为:

image-20231230084724238

函数是Result类型,所以最后要加上 OK(s)

第一句话与其下面四行代码等价

image-20231230084748441

下面是 . 这个关键字(链式调用)

image-20231230085737590

等价于

image-20231230085818204

十二、泛型与其限制

如图,这里泛型的比较会报错

image-20231230092359715

我们需要对泛型T添加类型限制:

这里涉及到了trait上的知识

> 这个关键词实际上对应了std::cmp::PartialOrd这个方法,我们需要对T实现这个trait

=》

image-20231230110456974

但还会报错,因为list的T没有实现copy元素

image-20231230110523401

再加上copy trait就行了

image-20231230110705757

泛型结构体中,我们如果想对具体的某个变量类型实现函数,而不对其他泛型实现函数,则可以这样操作:

image-20231230093049239

impl Point 表示对所有泛型实现函数

impl Point 表示只对i32 这个变量类型实现函数

十三、trait

image-20231230093608565

trait是用来约束泛型的,泛型提供多态,trait提供重载

比如下面这张图上面那个函数是泛含,下面那个函数用trait添加了对泛函的约束,只有同时满足Disply与Partialord两个trait的T才能执行

image-20231230110952534

https://www.bilibili.com/video/BV1hp4y1k7SV/?p=45&spm_id_from=pageDriver&vd_source=87f7ad8544d4c3ad070c5c2ff28b7698

lib.rs:

pub struct NewsArticle{
    pub headline:String,
    pub location:String,
    pub author:String,
    pub content:String,
}
impl Summary for NewsArticle{
    fn summarize(&self) ->String{
        format!("{},by{} ({})",self.headline,self.author,self.location)
    }
}
pub struct Tweet{
    pub username:String,
    pub content:String,
    pub reply:bool,
    pub retweet:bool,
}

impl Summary for Tweet{
    fn summarize(&self)->String{
        format!("{} : {}",self.username,self.content)
    }
}


pub trait Summary{
    fn summarize(&self)->String;
}

main.rs

use test_code::Summary;
use test_code::Tweet;

fn main(){
    let tweet =  Tweet{
        username:String::from("horse_ebooks"),
        content:String::from("of course,as you probably already know,people"),
        reply:false,
        retweet:false,
    };
    println!("1 new tweet:{}",tweet.summarize());
}

trait的默认实现

为trait实现默认行为

衔接上文,把Summary特征关联到Tweet的时候不复写summarize实例,并在Summary设置summarize内部实现

image-20240103163151509

image-20240103163322593

trait作为参数---impl trait

继续衔接上文,实现一个notify函数,其参数是Summary trait

image-20240103163607108

trait作为参数---trait bound

下面这种写法相当于impl trait 写法

image-20240103163742495

再次对比:

image-20240103163838941

impl trait语法是trait bound的语法糖

trait作为参数---+指定多个trait

同时实现Summary与Display两个trait

image-20240103163959300

trait作为参数---where子句简化trait约束

image-20240103164045594

=>

w

这样看起来函数签名就没那么乱了..

trait作为返回类型

impl trait可以实现

十四、生命周期

每个引用都有生命周期

1、泛型生命周期参数 <'a>

image-20231230112754713

对于longest这个函数,返回类型包含了一个借用的值,但返回类型没有说明这个借用的值是来自x还是y

解决方法: 加入泛型生命周期参数,表示这三个生命周期是一样的

image-20231230112948259

<'a>代表的作用域或生命周期是指x和y中比较短的那一个的生命周期

比如这样就会报错:

image-20231230113619761

result的生命周期取的是较短的那一个,即string2的生命周期,故其作用不到第8行就被销毁了

生命周期标注语法:

image-20231230113052795

结构体生命周期引用:

要求结构体的生命周期大于实例的生命周期

image-20231230140655758

生命周期三大规则

image-20231230141740758

n、static---静态生命周期

它会使变量在整个程序执行时间内一直存活

十五、rust 宏

https://rustwiki.org/zh-CN/reference/macros-by-example.html

image-20231230160909318

这里就是用的宏

基础知识---语法

宏看起来和函数很像,只不过名称末尾有一个感叹号 !

宏的参数使用一个美元符号 $ 作为前缀,并使用一个指示符(designator)来注明类型:

image-20240102113647048

重载

image-20240102113853613

重复

image-20240102114010671

可变参数接口

image-20240102114456317

std与core

image-20231227161231781

移除 println!

println! 宏是由rust标准库std提供的,而std标准库应该由本地平台的rust工作集提供,由于我们要在裸机上运行操作系统,所以我们就不能依赖std库。又因为core库不依赖于任何操作系统且包含了rust语言里面相当一部分的核心机制,因此我们可以用core来满足裸机的需要

第一步便是去除println!的宏,以达到不使用std库的目的

1、我们在 main.rs 的开头加上一行 #![no_std] 来告诉 Rust 编译器不使用 Rust 标准库 std 转而使用核心库 core(core库不需要操作系统的支持)。

SBI与RustSBI

SBI 是 RISC-V Supervisor Binary Interface 规范的缩写,OpenSBI 是RISC-V官方用C语言开发的SBI参考实现;RustSBI 是用Rust语言实现的SBI。

RustSBI功能:它实际上作为内核的执行环境,它还有另一项职责:即在内核运行时响应内核的请求为内核提供服务。当内核发出请求时,计算机会转由 RustSBI 控制来响应内核的请求,待请求处理完毕后,计算机控制权会被交还给内核。

RISC-V 特权级架构

image-20240102095436316

我们编写的 OS 内核位于 Supervisor 特权级,而 RustSBI 位于 Machine 特权级,也是最高的特权级。

类似 RustSBI 这样运行在 Machine 特权级的软件被称为 Supervisor Execution Environment(SEE),即 Supervisor 执行环境。两层软件之间的接口被称为 Supervisor Binary Interface(SBI),即 Supervisor 二进制接口。

ecall指令

image-20231229160003835

Syscall 场景下是在 U-mode(用户模式)下执行 ecall 指令,主要会触发如下变更:

简单来说,ecall 指令将权限提升到内核模式并将程序跳转到指定的地址。操作系统内核和应用程序其实都是相同格式的文件,最关键的区别就是程序执行的特权级别不同。所以 Syscall 的本质其实就是提升特权权限到内核模式,并跳转到操作系统指定的用于处理 Syscall 的代码地址。

为了让应用程序能在计算机上执行,操作系统与编译器之间需要达成哪些协议?

编译器依赖操作系统提供的程序库,操作系统执行应用程序需要编译器提供段位置、符号表、依赖库等信息。 ELF 就是比较常见的一种文件格式。

消除rust对std的依赖

1、#![no_std]
2、#![no_main]

因为main函数的加载依赖了std,所以我们直接禁用main函数

3、rust-objcopy --strip-all

4、qemu启动!