0%

背景

初次接触 Rust,是在学校学习操作系统课程的时候。这门课的练习课中,需要用 Rust 语言来完成编程练习。在学习 Rust 之前,我对编程语言的学习并没有很大的热情。因为在我的印象里除了学习第一门编程语言 C 的时候花了些功夫,之后学习的 Java,Python,Javascript 之类的都是熟悉一下语法后就很快上手。当时我看学校里课程的安排也就是大概给学习 Rust 一周的时间,后面就开始做各种练习了,所以我并没从 Rust 这门语言中期待过什么特别复杂的东西。

真正开始学习的时候,发现事情好像并没有我想的那么简单。我发现除了要理解 Rust 的生命周期,所有权等概念意外,还要关注很多底层的细节。慢慢地我就明白了,想要真正理解 Rust 这门语言,我需要深入思考 Rust 的设计哲学,多看看 The Rust RFC Book,收获会非常大。

对于 Rust 这门编程语言的学习,离我最开始接触有一段时间了。这次训练营的第一阶段,其实对我个人来说是对这门编程语言的复习和巩固。

心得体会

学习 Rust 我感觉没有很难,多读一读 The Rust Programming Language, The Rust RFC Book标准库文档,对于初期学习完全足够。对于 Rustlings,我并不是特别喜欢,对于学习来说帮助并不大,里面的练习对于学习者来说就是难者不会,会者不难。多写多练多交流,有疑惑不要迟疑,打开 Rust Playground多写示例探索,在我看来是最好的学习方法。

对于语言本身那些细碎的知识点,也没做过详细的笔记。也就是在学习闭包的时候写过一篇总结:Rust中的闭包与关键字move

参加这次训练营也是想重新拾起 Rust,并将其运用到实践当中。

几年前就萌生了用 Rust 制作一个操作系统的想法, 但 x86 的复杂度又有点让人望而却步, 而当时恰好遇到了 riscv, 恰好遇到了 rCore. 到现在, 终于有了一个可以亲身加入训练营的机会, 在此感谢各位老师和学长们为此的付出

第一阶段 Rustlings 总结

几年前将自己学习 Rust 的过程汇总成笔记写成教程放在 个人博客 上了. 这里只再简单总结一下一些重要概念内容

所有权 (Ownership)

所有权是 Rust 的一个核心概念, 用于确保内存安全. 它规定了一个变量 (或数据) 在任何时刻都有且仅有一个所有者. 当所有者离开作用域时, 其所拥有的数据将自动释放, 防止内存泄漏. 通过移动赋值和不可变 / 可变借用, 得以控制数据的访问和生命周期

  1. 移动赋值: 当一个值从一个变量转移到另一个变量时, 原变量将不再拥有数据所有权, 不能再被访问
  2. 引用与借用:
    • 不可变引用: 允许安全读取但不修改原始数据
    • 可变引用: 独占访问, 允许修改数据, 同一作用域内不可存在多个可变引用指向同一数据

泛型 (Generics) 与生命周期 (Lifetimes)

泛型允许创建适用于多种类型的数据结构和函数, 无需重复实现. 生命周期可被视为一种特殊的泛型. 生命周期注解是用来描述引用的有效期的一种机制. 在函数签名或结构体定义中, 通过类似 'a 这样的符号来表示生命周期参数, 并通过上下文推断或显式声明来确保引用不会超过其底层数据的有效期

Traits (特性)

特质是 Rust 中的一种接口抽象, 它定义了一组方法签名, 供实现特质的类型遵守. 通过 traits, Rust 实现了面向对象编程中的多态性.

智能指针

Rust 提供了一系列智能指针类型, 如 Box<T> Rc<T> Arc<T> RefCell<T> 等, 它们分别用于管理堆上的唯一所有权, 引用计数共享所有权, 线程安全的引用计数共享所有权以及在编译时静态检查之外增加运行时借用检查等场景

多线程与异步编程

  • 多线程: Rust 支持原生线程, 通过 std::thread 模块相关API. Rust 的 Ownership 和借用规则有助于防止数据竞争和死锁等问题,Mutex, RwLock 等同步原语进一步加强了线程间的安全通信
  • 异步编程: Rust 异步编程模型基于 futures 和 async/await 关键字,提供了高效非阻塞IO的能力. tokioasync-std 等库提供了异步编程的基础设施, 包括任务调度, TCP / HTTP 网络编程等

除了这些基本语法, 在练习中也实践了手动实现链表, 双向链表, 堆栈, 深度优先和广度优先算法, 排序算法等. 通过 Rustlings 练习, 可以逐步掌握以上各个概念的实际应用, 通过解决实际问题加深对 Rust 编程特性的理解, 并学会如何在实践中遵循 Rust 的安全性和性能原则, 为接下来的第二阶段打下基础, 以构建安全高效的 OS 内核

此外也感谢群内的大佬们, 没事翻翻大佬的聊天记录总能学到新东西 :)

Rust OS 一阶段学习总结

个人背景概述

我是一名软件工程专业的大二本科生,曾参与过2023年秋冬季训练营。
在去年的训练营中,我学习了rust,但因为第一次学习,且当时需要准备期中考试和各科作业,没有按时通过第一阶段。
虽然我后续进入了二阶段群,但没有完成第二阶段的实验部分,今年重新参与,打算认真跟进下去。

今年一阶段学习时间表

  • 2024-03-27 重新报名参与活动,并邀请了一名本校大三学长共同进步
  • 2024-04-07 正式开始参与活动,又邀请了两位同校同学,我开始重新复习rust
  • 2024-04-12 通过rust圣经复习至基础部分完成,开始进行做题
  • 2024-04-13 累计做题3小时,做至43题
  • 2024-04-19 进行到cow指针部分
  • 2024-04-20 完成前100题
  • 2024-04-21 完成110题,开始提交

一阶段学习内容概述

我按照“rust语言圣经”上的讲解顺序进行学习,分别学习了:

  • 变量的绑定与解构
  • 基本类型
  • 所有权和借用
  • 复合类型
  • 流程控制
  • 模式匹配
  • 方法
  • 泛型和特征
  • 集合类型
  • 生命周期
  • 返回值、错误处理
  • 包和模块
  • 注释和文档
  • 格式化输出
  • 智能指针
  • 多线程
  • 闭包与迭代器
  • 宏编程
  • unsafe rust

问题概述

在一阶段过程中,我遇到了一定的问题,并尝试进行解决。以下内容是我认为,不是那么“较为基础”的问题。

2024-04-12 问题:unicode展示

问题描述

在primitive_types2.rs中,注释要求尝试传入unicode字符,来理解rust中的char。
但是,我通过https://emojidb.org/rust-emojis这个emojis网址,拿到了一个emoji:☢️,进行测试,却直接编译出错:

出错代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// primitive_types2.rs
//
// Fill in the rest of the line that has code missing! No hints, there's no
// tricks, just get used to typing these :)
//
// Execute `rustlings hint primitive_types2` or use the `hint` watch subcommand
// for a hint.


fn main() {
// Characters (`char`)

// Note the _single_ quotes, these are different from the double quotes
// you've been seeing around.
let my_first_initial = 'C';
if my_first_initial.is_alphabetic() {
println!("Alphabetical!");
} else if my_first_initial.is_numeric() {
println!("Numerical!");
} else {
println!("Neither alphabetic nor numeric!");
}

let your_character = '☢️'; // Finish this line like the example! What's your favorite character?
// Try a letter, try a number, try a special character, try a character
// from a different language than your own, try an emoji!
if your_character.is_alphabetic() {
println!("Alphabetical!");
} else if your_character.is_numeric() {
println!("Numerical!");
} else {
println!("Neither alphabetic nor numeric!");
}
}

问题分析

我与队伍里的成员们进行了初步的实验和讨论,确定报错的原因是:“☢️”无法被解析成字符,必须被解析成字符串。那么,问题来了,为什么它无法被解析成字符,而其它的unicode码可以被解析成字符?

解决方法

首先,我们进行了实验,从emojis网站中寻找了其他的emoji字符,发现大部分的emoji竟然都能被解析成字符。那问题就是出在“☢️”这个emoji上。

我们队内通过查找rust官方的文档,发现,文档中说,unicode码被分为了两大类,一类是UTF-16,一类是UTF-8。
那么,这两大类的unicode字符有什么区别?
我们通过搜集资料、询问ai等方式,得到:

UTF-16码点在0xD800到0xDFFF的范围内
UTF-8码点在0x80到0x10FFFF的范围内
如图
如图

我们的总结是:UTF-8是8位的,其由一个字节进行表示,而UTF-16是16位的,其由两个字节进行表示,很可能是因为“☢️”是UTF-16编码,导致其无法被解析成rust里的“字符char”。

2024-04-12 问题:if内部返回异常

问题描述

在编程的过程中,我意外发现,在if语句内,通过不写分号的方式进行返回,会产生问题,程序无法编译,见下图:

出错代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
// structs3.rs
//
// Structs contain data, but can also have logic. In this exercise we have
// defined the Package struct and we want to test some logic attached to it.
// Make the code compile and the tests pass!
//
// Execute `rustlings hint structs3` or use the `hint` watch subcommand for a
// hint.

#[derive(Debug)]
struct Package {
sender_country: String,
recipient_country: String,
weight_in_grams: i32,
}

impl Package {
fn new(sender_country: String, recipient_country: String, weight_in_grams: i32) -> Package {
if weight_in_grams <= 0 {
panic!("Can not ship a weightless package.")
} else {
Package {
sender_country,
recipient_country,
weight_in_grams,
}
}
}

fn is_international(&self) -> bool {
// if self.sender_country != self.recipient_country {
// return true;
// // true // error, 'true' return to the func is_international, not outside
// }
// // return false;
// false // it can pass the compile

// if self.sender_country == self.recipient_country {
// false
// } else {
// true
// }

self.sender_country != self.recipient_country // most elegent way
}

fn get_fees(&self, cents_per_gram: i32) -> i32 {
cents_per_gram * self.weight_in_grams
// Something goes here...
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
#[should_panic]
fn fail_creating_weightless_package() {
let sender_country = String::from("Spain");
let recipient_country = String::from("Austria");

Package::new(sender_country, recipient_country, -2210);
}

#[test]
fn create_international_package() {
let sender_country = String::from("Spain");
let recipient_country = String::from("Russia");

let package = Package::new(sender_country, recipient_country, 1200);

assert!(package.is_international());
}

#[test]
fn create_local_package() {
let sender_country = String::from("Canada");
let recipient_country = sender_country.clone();

let package = Package::new(sender_country, recipient_country, 1200);

assert!(!package.is_international());
}

#[test]
fn calculate_transport_fees() {
let sender_country = String::from("Spain");
let recipient_country = String::from("Spain");

let cents_per_gram = 3;

let package = Package::new(sender_country, recipient_country, 1500);

assert_eq!(package.get_fees(cents_per_gram), 4500);
assert_eq!(package.get_fees(cents_per_gram * 2), 9000);
}
}

问题分析

在遇到这个问题之后,我自己分析不出来为什么会出现这样的错误。于是,我去找队伍里的学长进行咨询。学长在他的教程书里找到了答案。出现这个问题的原因是,rust的但if语句的返回值必然是返回单元(),因此,我不使用return返回true时,破坏了if语句的语法规则,导致出错。具体教程如下:

额外练习

在第一阶段中,我在我们专业的操作系统实验课程中,使用rust编程,完成了实验内容,具体完成了:

  • 生产者与消费者问题
  • 时间片轮转和优先级队列的调度算法
  • 文件系统的实现

总结

在第一阶段的学习中,我巩固了我们所掌握的rust基础,并开始学习操作系统相关的知识。在这一部分,我还培养了我的思考能力,深入思考了rust的安全与不安全的地方。同时,通过和群友的交流,我也展开了对更多其它知识点的思考,比如“Copy”、“Clone”特征和“Drop”的冲突之处。
最重要的是,我深深意识到:Talk is cheap, show me the code!

展望

目前,我已经开始了第二阶段部分内容的学习,希望可以顺利的完成第二阶段的所有内容。之后参与到第三阶段的项目中,继续提高自己的能力。

前言

因为之前学C++结识的好友群中发了这个训练营的报名链接,想着正好OS还没有学就想着报名参加了:)。参加本次训练营之前只有csapp的计算机体系结构这一相关经验,对于Rust和RISC-V一无所知。编程能力上,主要是C++的服务器开发,做过几个分布式微服务项目,国外的一些公开课lab(CS144/CS106B/CS50等)。

总体来说第一阶段学习过程还是有点痛苦的,毕竟Rust学习曲线比较陡峭,不过也有许多特性能与C++等语言联系起来,帮助理解。在报名之后就开始翻阅先前训练营的资料开始自学Rust了,总体还是顺利的

一些链接🔗

第一阶段总结

第一阶段主要集中于Rust基础语法的学习,并完成rustlings练习题,其次是进行RISC-V指令集的学习

  • Rust因为是第一次上手,编码熟练度还不是很高,对于一些编码更高效的方式应用不熟练,算法和数据结构的实现遇到的磕绊较多
  • RISC-V基本指令比较好理解,实际编写还是差一些,能反应指令的行为,一些寄存器记不住
  • 计组和编译原理知识比较薄弱

Rust

Rust学习过程主要参考阅读了《Rust圣经》、《Rust by Example》、和Rust之旅
之后的时间主要是对《Rust圣经》中的进阶部分和基础部分进行阅读笔记并复习,基础部分难点主要集中在所有权和特征这两个知识点上,对于生命周期以及包和模块的讨论并不是特别深入。进阶部分都难哈哈哈哈,迭代器和智能指针还算比较好理解些,对于迭代器加闭包实现的函数式编程每次都让我觉得很强大,Cell和RefCell提供的内部可变性加上智能指针也给程序提供了很大的灵活性的前提下保证了一定的安全性。生命周期的使用也是一大难点了。之后还有unsafe Rust的使用以及宏编程(对比C/C++的难的多但功能性也更强大)

rustlings的基础语法部分不算特别难,不熟练的查一查就能写得出来。难点集中在test和algorithm章节,主要是test后半部分,一些环境变量的设置,以及rust实现数据结构算法所用到的unsafe rust和智能指针的操作。

之后尝试阅读Learning Rust With Entirely Too Many Linked Lists,和用Rust实现一些算法和数据结构来巩固,多敲一些代码提高熟练度,多查文档。

RISC-V学习

这部分知识学习首先是阅读了《计算机组成于设计:RISC-V版》的前两个章节,对RISC-V的基本指令和指令格式有一个了解,以及RISC-V的设计和计算机体系结构的8个伟大思想之间的联系。之前阅读csapp并完成lab实验,有些x86_64的经验,对于一些基本指令和作用的理解还算轻松。难点集中在原子指令上。之后主要阅读了《RISC-V手册》,主要重点是第十章的特权架构,涉及之后rCore的特权级切换操作以及虚拟内存的实现(SV39多级页表)

学习后可以看出RISC-V指令集的强大和简单,和x86_64不同的理念。六种基本指令格式,等长指令就能搞定一切,精妙的设计(就是有时候脑子转不过来)

查缺补漏

阅读操作系统导论,目前还在虚拟化部分,然后观看导学阶段的OS课录播,补足操作系统的理论知识和概念

初始rcore

第一次知道rcore来源于一篇知乎的帖子,当时沉迷于操作系统的学习,希望能写出一个自己的操作系统。但当时网络上更多的是对xv6的赞赏,rcore的仅仅是一句简单的提及。受限于当时初学计算机,只知语言C,不识Rust,不希望投入太多精力到语言的学习,因此并未细探rcore究竟。(当然,三分钟热度的我最后也没写完一个操作系统,希望这次能够完善地学完!)

直到去年了解到这个开源操作系统训练营,加上自学了rust的基础语法,对其很有兴趣。可惜秋冬季已经结营,加上网站上模糊的教学视频实在难以学习,便又戛然而止。直至今年四月初至,又闻开营之事(别问,问就是大数据推送),喜从心中来,急急忙忙地就上了船。

第一阶段总结

第一阶段主要是熟悉rust语法,前三十道题进展迅速,仅涉及简单语法;中间几十道题中,所有权和生命周期单列出来较为简单,但是复杂场景中的所有权问题令人头疼;最后几道算法题,除去算法本身的难度,rust对于所有权和借用的限制才是最大的难点,以至于最后纯粹是与编译器斗智斗勇,几乎快忘了实现逻辑和复杂度。

除此之外,有多道题目在完成过程中总是不解其意,仅仅是让其通过检测。知道提交通过之后,看到群友们的讨论才恍然大悟,但又懒于修改,还是默默学习的新的内容去吧。

code-debug是一个支持跨特权级调试的VSCode插件。在这篇文章中,我将介绍利用这个调试插件在VSCode上对ArceOS进行源代码级调试的过程。

首先我们需要下载 gdb-multiarch :

1
sudo apt install gdb-multiarch

接着我们运行一下 ArceOS,从而生成 bin 和 elf 文件. 这里以RISC-V上的单核arceos-helloworld为例:

1
make A=apps/helloworld/ ARCH=riscv64 LOG=info SMP=1 run

在 ArceOS 的输出中,我们发现了 QEMU的启动参数:

1
qemu-system-riscv64 -m 128M -smp 1 -machine virt -bios default -kernel apps/helloworld//helloworld_riscv64-qemu-virt.bin -nographic

我们将这些启动参数转移到配置文件 launch.json 中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
   //launch.json
{
"version": "0.2.0",
"configurations": [
{
"type": "gdb",
"request": "launch",
"name": "Attach to Qemu",
"executable": "${userHome}/arceos/apps/helloworld/helloworld_riscv64-qemu-virt.elf",
"target": ":1234",
"remote": true,
"cwd": "${workspaceRoot}",
"valuesFormatting": "parseText",
"gdbpath": "gdb-multiarch",
"showDevDebugOutput":true,
"internalConsoleOptions": "openOnSessionStart",
"printCalls": true,
"stopAtConnect": true,
"qemuPath": "qemu-system-riscv64",
"qemuArgs": [
"-M",
"128m",
"-smp",
"1",
"-machine",
"virt",
"-bios",
"default",
"-kernel",
"apps/helloworld/helloworld_riscv64-qemu-virt.bin",
"-nographic",
"-s",
"-S"
],

"KERNEL_IN_BREAKPOINTS_LINE":65, // src/trap/mod.rs中内核入口行号。可能要修改
"KERNEL_OUT_BREAKPOINTS_LINE":124, // src/trap/mod.rs中内核出口行号。可能要修改
"GO_TO_KERNEL_LINE":30, // src/trap/mod.rs中,用于从用户态返回内核的断点行号。在rCore-Tutorial-v3中,这是set_user_trap_entry函数中的stvec::write(TRAMPOLINE as usize, TrapMode::Direct);语句。
},
]
}

我们在qemuArgs中添加了 -s -S 参数,这样qemu在启动的时候会打开gdb调试功能并且停在第一条指令处,方便我们设置断点.

此外,应当注意executable参数指向包含符号表的elf文件,而不是去除符号表后的bin文件。

由于ArceOS是unikernel,没有用到用户态,因此以下这三个参数不需要填写:

1
2
3
"KERNEL_IN_BREAKPOINTS_LINE":65, // src/trap/mod.rs中内核入口行号。可能要修改
"KERNEL_OUT_BREAKPOINTS_LINE":124, // src/trap/mod.rs中内核出口行号。可能要修改
"GO_TO_KERNEL_LINE":30, // src/trap/mod.rs中,用于从用户态返回内核的断点行号。在rCore-Tutorial-v3中,这是set_user_trap_entry函数中的stvec::write(TRAMPOLINE as usize, TrapMode::Direct);语句。

最后我们再次按f5开始调试ArceOS. 我们发现Qemu虚拟机启动,ArceOS停在了第一条指令

1
oslab@oslab:~/arceos$  qemu-system-riscv64 -M 128m -smp 1 -machine virt -bios default -kernel apps/helloworld/helloworld_riscv64-qemu-virt.bin -nographic -s -S

接下来我们设置断点。比如我们在Hello, World输出语句打一个断点,然后按”▶️”.我们会发现断点触发了:

以上,通过一些简单的设置,我们就得以用code-debug调试器插件调试一个新OS.

Unikernel 学习心得

近期学习了一些关于Unikernel的知识,以下是一些心得体会:

  • Unikernel是基于组件化的思想设计的,由各种模块构成组件,再由各种组件构成最终的操作系统。
  • 而对于各种模块是如何被选择的,则是采用feature机制,指定最终所需要的模块

ArceOS实验

  • 这周进行了ArceOS的三个实验,在练习一中学习了用println!进行彩色打印。练习二主要学会了对ArceOS进行扩展开发,在axstd中加入了Hashmap,在axhal模块中加入rng生成器,初步了解了ArceOS的调用结构。
  • 练习三修改了axalloc模块中allocator的算法,改为early算法,同时用lock()和unimplemented!()禁用其他算法

学习流程

第一天的接触就是修改CI/CD的错误,我的认知里面是写构建的yml文件,实际上是需要解决starry跑在x86的问题,第一天就大概看了starry的设计文档了解了它和arceos的承接关系和相关的模块图,于是在和杨金博同学沟通后对编译选项和copy.S的代码进行了x86的改造之后发现pci等还有问题,于是问题交到金博哥手里,这时候进行了unikernel的学习,石磊老师ppt讲解和训练营的录课大概看过之后对unikernel有了大概的认识,之后就边处理strace那边的数据边学习和做lab。

进展

目前在做第三周的实验的lab1,将c程序的镜像打包到文件系统实现了,目前对hello的报错进行修改,目前的思考是hello的命令行参数处理有问题,需要内核提供支持,目前定位到的代码如下

1
2
3
4
5
6
7
8
9
10
11
if let Some(app_inode) = open_file(path.as_str(), OpenFlags::WRONLY) {
let all_data = app_inode.read_all();
let process = current_process();
let argc = args_vec.len();
process.exec(all_data.as_slice(), args_vec);
// return argc because cx.x[10] will be covered with it later
println!("exec argc is {}", argc as isize);
argc as isize
} else {
-1
}

执行后可在shell中看到初始为0执行hello程序后为1但是此时的参数处理是输出了argc为0的错误输出,目前在继续看代码准备把后续都尽快跑通。

#Unikernel 总结报告

经过若干天关于unikernel的学习,对于单一特权级的操作系统有了全新认识完成了练习1-5,练习分别为:支持彩色打印、支持HashMap、修改内存分配算法、增加一个axdtb的模块组件、修改fifo算法通过ex5测试应用

练习心得

练习了修改代码、不断检错纠错能力和熟悉了unikernel启动流程、单一特权级、单一应用、单一地址空间的操作系统的工作流程、练习一完成了axstd::println的修改、练习二;通过阅读标准库HashMap加以修改、连续三实现earlyallocator的内存分配方法、修改协作式调度为抢占式调度运行ex5程序、

##实验心得

实验一和二,将程序数据按照一定格式保存到app.bin文件中修改汇编指令,加载外部应用,实现app loader的外部应用加载器;实验三为公国ABI调用ArceOS功能,两个医用一个是nop、一个是wfi;实验四、实现sys_hellosys_putcharsys_terminate功能调用,通过std::process::exit退出ArceOS;实验五为改造应用hello_app,建立独立页表,把系统调用包装成函数,通过初始化和切换函数实现程序

本次主要采用是通过类似于在GNU/LD链接器的方式来完成整个任务。主要的参照依据是csapp中有关链接的相关说明。
目前的实际完成进度在于完成了符号的解析。但是后面的重定包括段重定位以及符号的重定位,目前还没有完成。

目前操作上来说,对于后续任务影响最大的内容在于之前任务的不正确完成,包括但不限于之前任务中埋藏的坑,比如说文件不能正确识别,将 1B 认定成为 一个16进制数而导致对于加载的文件中的align的错误判断,甚至还将用于解释rust内部结构体的自动align用于解释这里所出现的人为导致的align。

由于其他因素的影响,我在前半段时间其实没有过多的参与到其中来,近几天才加入到课题中。在参加课题之前其实对于libc,musl,甚至是elf文件没有什么过多的了解。简单的了解了下整个过程,本来看了下还想先直接用ld将对应的库链接好之后再放到arceos上读取(这是郝淼同学的实现思路),但是想到在 ld 将各种对象文件链接起来之后,实际上最后在使用的时候还是需要调用链接器 [–dynamic-linker可以指定对应链接器的地址] (当时实际上没想到可以直接实现链接器最后被调用的功能)。后面就按照实现一个链接器的想法继续实验。

目前主要使用 elf crates 实现了如下效果:

  1. 能够对于脚本中的符号进行重定位
    目前对于重定位的操作实现尚未完成,根据要求,每一步实现实际上都需要遍历多次,更别提最原始的实现中由于对象文件的顺序引起的问题可能也需要往复遍历,正在尝试优化算法以及修改elf crates使之有着更好的体验。

未来工作可能没有特别大的必要开展,原因如下,在相同情况下,就算最终能够实现了一个能够在arceos上运行的链接器也是没有必要的。

  1. 现有的基于GNU/LD的链接器可以比较好的行使其职能,重复实现一个实际上没有必要实现的链接器会丢失原先所实现的优化,同时因为测试相对于前者而言更少,反而可能会导致更多错误,然而在这种实现的链接器面前,除非同时支持大量的编译工具,否则意义实际上不太大,只能算是个展示玩具。

/// BLUE PRINT
///
/// 1. LOAD APPLICATION
/// 2. SYMBOL RESOLUTION: make sure all symbol has been meet.
/// 3. RELOCATION:
/// [1] RELOCATION SECTION:
/// merges all sections of the same type into a new aggregate section of the same typecombine
/// BC we only need SHT_PROGBITS thus not care about others (copy).
/// [2] RELOCATE SYMBOL REFERENCES IN SECTIONS:
/// Modify references to each symbol in code and data sections so that they point to the correct run-time address

下面的伪代码实际完成了符号加载工作,重定向部分的代码还没有开始实际测试,就算未来有按照这个方法实现的必要,类似郝淼同学重新实现 elf crates 甚至在原先基础上进行修改的行为是必要的,现有的调用过程太长,使用起来不太舒服。

直接通过 shdr.e_type 实现对于符号的定位,根据CSAPP的说明,使用多个集合对于符号进行处理,实现符号匹配效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
match ehdr.e_type {
ET_REL => { //
foreach symtabs {
if sym.is_undefined() {
// 加入 U 未定义符号组
} else {|
// 加入 D 已解析符号组
}
}
// 加入总管辖 elf 文件组 TODO 目前只实现了添加,并没有实现基于往复判断的剪枝
},
ET_DYN => {
foreach symbol in U {
if contains undefined symbols {
// 从 U 未定义符号组删除本符号
// 加入 D 已解析符号组
// 加入总管辖 elf 文件组 TODO 目前只实现了添加,并没有实现基于往复判断的剪枝
}
}
}

}