less than 1 minute read

设计目标

  • 正确性:正确管理已经使用的内存,已经回收的内存,确保不会引发未定义错误。
  • 高效性:高效的利用可获得的内存并且保存低碎片化。
  • 并发性:在并发的应用中能够高效发挥作用。并且适应多核的处理器。
  • 利用 CPU cache提高局部性原理。 如果我们要实现上面的目标,我们能够做出一个好的 allocator,但是我们也将收获一个非常复杂的 allocator,每一个 bug 都将会是致命的,我们并不必要在目前的内核中引入这么复杂的 allocator.

Bump allocator (Stack allocator)

为什么 GlobalAllocator 使用的是 &self 而不是 &mut self 作为参数? 这是因为 #[global_allocator] 属性是一个 实现了 GlobalAllocator 的静态变量。 我们已经很熟悉在 rust 当中静态变量是不可变的。因此我们不能在一个静态变量的方法当中使用 可变引用。我们也很清楚其中的原理——静态变量不允许同一时间被两个操作改动,如果我们使用锁就可以解决这个问题。

Linked List Allocator(poor allocator)

重构问题

抢占式调度

操作系统完全控制进程的执行权,每个进程维护自己的调用栈,我们并不确定操作系统会在什么时候进程调度,可能是计算的时候,但是作为操作系统来说,他必须要公平的调度,或者说按照我们最想要的效率最高的方式调度,这才是最终的目的。 那么下一个需要思考的问题是调用栈到底存在哪里,使用协作式调度的话,我们就不需要保存这些调用栈了吗?

协作式调度

进程自愿放弃对 CPU 的调度权。我们并不完全靠比喻说明这一点,这件事情真实发生的时间是当进程进入 IO 的等待时间等情况。这种调度经常发生在语言级层面上,我们把协作式调度和异步编程联系在一起是一种常见的做法。 所以在 dora 的运行过程当中 状态的返回是否也是语言层面的调度。 所以我们需要深入的看一下 async/await 到底是怎么存的调用栈,以至于能够比抢占式调度节省内存。 future 返回了异步任务的执行状态,pending or ready。我们并不通过 loop 来验证这一点,相反我们通过状态机在编译器翻译这段代码。每一个有 .await 的函数都会被翻译成状态机中的一个。那么每一个异步任务都会被根据状态转换关系翻译成一个分支执行状态转换的任务,并且在异步函数执行完毕之后返回状态的结果。实际上我们并不需要在同一时刻保存所有的局部变量。对于每一个状态我们都有与之对应的变量需要保存,因此编译器会确定每个状态需要保存的变量,在这个前提下,我们就不需要保存全部的调用栈,这就是我们之前所说的协作式调度会在状态保存上节省内存的原因。 在每一次 poll 的过程当中,我们都只是保存这个任务的栈,当这个任务结束之后我们会换成另一个任务,所以我们可以在同一个栈中保存另一个任务的变量。

rust 中的异步编程

  1. async/await
  2. futures

    Box

    我们在堆上分配内存,但是我们改变 Box 里面的东西,这是因为我们可以解引用,然后获取可变引用,然后改变里面的东西,这时候如果里面的东西是自引用,我们就会犯错。 这时候我们有两种方法:

  3. 使用一种数据结果我们并不能获得这个数据结构里面的值。
  4. 我们要阻止解引用并且获取可变引用这件事情发生。

poll 和 executor

我们在第一次 poll 之前什么都不会做。就像我们可以想象的一样。我们需要通过一个循环来检查 future 是否处于 ready 的状态,但是在一个有很多 future 的大项目中,这显然是效率低下的。

关于异步的流程

首先这里有一个无锁的队列 队列初始化,我们想要它是一个静态变量并且延迟初始化。这里有一个重要的问题是我们什么时候初始化 我们还会轮询去队列里拿东西。我们拿东西会有两种情况:拿到或者拿不到 如果我们第一次拿东西,我们要初始化并且拿东西。

Updated: