rCore_Lab1
lab1代码 https://github.com/TL-SN/rCore
记录一下写rCore中学到的OS知识与rust知识
一、永不返回的发散函数
二、string与&str所有权问题
另外:
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
中,仅仅是对该引用进行了拷贝,此时 y
和 x
都引用了同一个字符串。
有些变量是不具备所有权特征的:
任何基本类型的组合可以 Copy
三、不可变引用与可变引用解决所有权权问题
由于string的所有权问题,导致我们在函数参数传递的时候不得不小心,以防string的所有权转移,为此,rust提供了引用操作
可变引用;
使用 &mut 借用:
需要注意的是:
四、循环访问与循环修改
五、模式匹配 / switch
六、package与包(crate)
package是项目工程,包是指编译单元
比如: src/main.rs
和 src/lib.rs
都是编译单元,因此它们都是包。
不过一般main.rs或者lib.rs被称为包根
而项目工程就是cargo new的整个项目
比如,这样一个经典的package:
七、闭包与迭代器
有点像lamda
八、内联汇编与编译器优化
asm
宏的 in
,out
,inout
,lateout
,inlateout
参数就是为了让编译器帮助分配寄存器的。
in
表示将变量的值传给寄存器,编译器生成的汇编代码会使得在内联汇编代码中读取相应的寄存器,就得到了传入的变量的值;
out
表示将寄存器的值写到变量中,在内联汇编代码中写入相应寄存器,编译器在内联汇编之后生成的汇编代码会使得相应变量具有写入相应寄存器的值;
late
则是代表编译器可以采取进一步的策略来优化寄存器分配:默认的分配策略给每个参数分配不同的寄存器,使用 lateout
或 inlateout
的参数则允许编译器复用某个 in 参数的寄存器,只要内联汇编代码中先读完所有的 in
寄存器,再输出 lateout
或 inlateout
寄存器即可。
全局内联汇编:
用 global_asm
我们可以写出源代码完全是汇编代码的函数,函数名就是汇编代码中的标签,函数参数和返回值需要按照 ABI 约定来处理
九、不可恢复错误与可恢复错误
不可恢复错误
panic
可恢复错误
一般的话,可以这样使用:
在c语言中,我们是使用if判断NULL来完成是否打开文件成功的
十、option
option用来表示一个值可能存在的抽象概念
目的就是 强制做类型检查
比如图中会报错
而如果在rust下,就不可能出现这种情况:
十一、unwrap 与 expect与?与.
这个东西与下面这个东西等价
缺点就是不可以自定义错误信息
那么我们可以使用 expect 关键字:
我们再看 ? 这个关键字
正常下我们读取文件的操作是这样的:
这里的Ok(_) => OK(s) 为什么要返回OK(s)呢,直接返回s不好吗,这是因为函数返回类型是Result类型
我们可以简化为:
函数是Result类型,所以最后要加上 OK(s)
第一句话与其下面四行代码等价
下面是 . 这个关键字(链式调用)
等价于
十二、泛型与其限制
如图,这里泛型的比较会报错
我们需要对泛型T添加类型限制:
这里涉及到了trait上的知识
> 这个关键词实际上对应了std::cmp::PartialOrd这个方法,我们需要对T实现这个trait
=》
但还会报错,因为list的T没有实现copy元素
再加上copy trait就行了
泛型结构体中,我们如果想对具体的某个变量类型实现函数,而不对其他泛型实现函数,则可以这样操作:
impl
impl Point
十三、trait
trait是用来约束泛型的,泛型提供多态,trait提供重载
比如下面这张图上面那个函数是泛含,下面那个函数用trait添加了对泛函的约束,只有同时满足Disply与Partialord两个trait的T才能执行
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内部实现
trait作为参数---impl trait
继续衔接上文,实现一个notify函数,其参数是Summary trait
trait作为参数---trait bound
下面这种写法相当于impl trait 写法
再次对比:
impl trait语法是trait bound的语法糖
trait作为参数---+指定多个trait
同时实现Summary与Display两个trait
trait作为参数---where子句简化trait约束
=>
这样看起来函数签名就没那么乱了..
trait作为返回类型
impl trait可以实现
十四、生命周期
每个引用都有生命周期
1、泛型生命周期参数 <'a>
对于longest这个函数,返回类型包含了一个借用的值,但返回类型没有说明这个借用的值是来自x还是y
解决方法: 加入泛型生命周期参数,表示这三个生命周期是一样的
<'a>代表的作用域或生命周期是指x和y中比较短的那一个的生命周期
比如这样就会报错:
result的生命周期取的是较短的那一个,即string2的生命周期,故其作用不到第8行就被销毁了
生命周期标注语法:
结构体生命周期引用:
要求结构体的生命周期大于实例的生命周期
生命周期三大规则
n、static---静态生命周期
它会使变量在整个程序执行时间内一直存活
十五、rust 宏
https://rustwiki.org/zh-CN/reference/macros-by-example.html
这里就是用的宏
基础知识---语法
宏看起来和函数很像,只不过名称末尾有一个感叹号 !
宏的参数使用一个美元符号 $
作为前缀,并使用一个指示符(designator)来注明类型:
重载
重复
可变参数接口
std与core
移除 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 特权级架构
我们编写的 OS 内核位于 Supervisor 特权级,而 RustSBI 位于 Machine 特权级,也是最高的特权级。
类似 RustSBI 这样运行在 Machine 特权级的软件被称为 Supervisor Execution Environment(SEE),即 Supervisor 执行环境。两层软件之间的接口被称为 Supervisor Binary Interface(SBI),即 Supervisor 二进制接口。
ecall指令
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启动!