原文: Green threads explained in 200 lines of rust language
地址: https://cfsamson.gitbook.io/green-threads-explained-in-200-lines-of-rust/
作者: Carl Fredrik Samson(cfsamson@Github)
翻译: 耿腾

在这个例子中,我们将创建自己的栈,并使 CPU 从它当前执行的上下文切换到到我们创建的栈。我们将在后面的章节中基于这些概念进行构建(不过我们不会在这些代码的基础上构建)。

译者注:阅读本章时建议读者自己也动手写代码运行一下,很多问题就容易理解了。

创建我们的项目

首先,让我们在名为 green_threads 的文件夹中启动一个新项目。命令行执行:

cargo init

我们需要使用 nightly 版本的 Rust,因为我们将使用一些尚未稳定的功能:

rustup override set nightly

在我们的 main.rs 文件中,我们首先启用一个功能,它允许我们使用 asm! 宏:

#![feature(asm)]

我们在这里设置一个较小的栈尺寸,只有 48 个字节,这样我们可以在切换上下文之前打印并查看这个栈:

const SSIZE: isize = 48;

在 OSX 中使用这么小的栈的好像有些问题。此代码运行的最小值是 624 字节的栈大小。如果你想要遵循这个确切的例子,代码可以在 Rust Playground 上运行(但是由于最终代码中的循环,你需要等待大概 30 秒的超时时间)。

然后,我们添加一个表示 CPU 状态的结构。我们现在只关注存储 “栈指针” 的寄存器,所以只需要添加下面的代码:

#[derive(Debug, Default)]
#[repr(C)]
struct ThreadContext {rsp: u64,
}

在后面的示例中,我们将使用之前链接中的规范文档中标记为 “callee saved”(由被调用者保存的) 的所有寄存器。这些就是 x86-64 ABI 描述的寄存器中那些用来保存上下文的寄存器,但是现在我们只需要一个寄存器来使 CPU 跳转到我们的栈。

需要注意的是,这个结构定义需要加上 #[repr(C)],因为我们需要按照汇编代码的方式去访问数据。Rust 没有稳定的 ABI,因此我们无法确保它会在内存中以 rsp 作为前 8 个字节来表示。C 具有稳定的 ABI,这一属性正是在告诉编译器使用兼容 C-ABI 的内存布局。当然,我们的结构现在只有一个字段,但我们稍后会添加更多字段。

fn hello() -> ! {println!("I LOVE WAKING UP ON A NEW STACK!");loop {}
}
对于这个非常简单的例子,我们将定义一个函数,它只打印一条消息,然后永远循环:

接下来是我们的内联汇编,我们切换到自己的栈。

unsafe fn gt_switch(new: *const ThreadContext) {asm!("mov 0x00($0), %rspret":: "r"(new):: "alignstack" // 目前没有这句也可以工作,不过后面会用到);
}

我们在这里使用了一个技巧。我们写入要在新栈上运行的函数的地址。然后我们将存储此地址的第一个字节的地址传递给 rsp 寄存器(我们设置给 new.rsp 的地址值将指向 位于我们自己的栈上的地址,该地址将导致上述函数被调用)。我讲清楚了吗?

ret 关键字将程序控制转移到位于栈顶部的返回地址。由于我们将地址推送到 %rsp 寄存器,因此CPU会认为它是当前运行的函数的返回地址,因此当我们传递 ret 指令时,它会直接返回到我们自己的栈中。

CPU 做的第一件事就是读取函数的地址并运行它。

Rust 内联汇编宏的快速入门

如果您之前没有使用内联汇编,可能会看起来很陌生,但我们稍后会使用扩展版本来切换上下文,所以我将逐行解释我们正在做什么:

unsafe 是一个关键字,表示 Rust 无法在我们编写的函数中强制执行安全保证。由于我们直接操作 CPU,这绝对是不安全的。

gt_switch(new: *const ThreadContext)

在这里我们获取一个指向 ThreadContext 实例的指针,我们只读取一个字段。

asm!("

这是 Rust 标准库中的 asm! 宏。它将检查我们的语法,在遇到看起来不像 AT&T(默认情况下)汇编语法的情况时会产生一个错误消息。

这个宏里第一个输入是汇编模板:

mov 0x00($0), %rsp

这是一个简单的指令,它将存储在基地址为 $0 偏移量为 0x00 处的值(这意味着在十六进制中完全没有偏移)移动到 rsp 寄存器。由于 rsp 寄存器存储指向栈上下一个值的指针,因此我们有效地将我们提供的地址压到当前的栈上,覆盖了当前已有的值。

在普通的汇编代码中,你不会看到这样使用的 $0。这是汇编模板的一部分,是第一个参数的占位符。参数编号为 0,1,2 …… 从输出参数开始,然后继续输入参数。我们这里只有一个输入参数,对应于 $0。

如果在普通汇编中遇到 $,它很可能意味着一个立即值(一个整数常量),但这取决于(是的,$可以表示方言之间以及 x86 汇编和 x86-64 汇编之间的不同之处)。

ret

ret 关键字指示 CPU 从栈顶部弹出一个内存位置,然后无条件跳转到该位置。实际上我们已经劫持了我们的 CPU 并使其返回到我们的栈。

:

内联 ASM 与普通 ASM 略有不同。我们在汇编模板后传递了四个附加参数。这是第一个被称为 output(输出) 的,它是我们传递输出参数的地方,这些参数是我们想要在Rust函数中用作返回值的参数。

: "r"(new)

第二个是我们的输入参数。在编写内联汇编时,"r" 被称为一个 constraint(约束)。您可以使用这些约束来有效地指导编译器决定放置输入的位置(例如,在一个寄存器中作为值或将其用作“内存”位置)。 "r" 仅表示将其放入编译器选择的通用寄存器中。内联汇编中的约束本身是一个很大的课题,幸运的是我们的需求很简单。

:

下一个选项是 clobber 列表,您可以在其中指定编译器不应触及的寄存器,并让它知道我们要在汇编代码中管理这些寄存器。如果我们弹出栈的任何值,我们需要在这里指定哪些寄存器并让编译器知道,因此它知道它不能自由地使用这些寄存器。我们不需要它,因为我们返回了一个全新的栈。

: "alignstack"

最后一个是我们的 options(选项)。这些对于 Rust 来说是独一无二的,我们可以设置的选项由三种:alignstack,volatile 和 intel。我会向你介绍文档以了解它们,在这里有具体解释。值得注意的是,我们需要为代码指定 “对齐栈(alignstack)” 才能在 Windows 上运行。

运行我们的例子

fn main(){let mut ctx = ThreadContext::default();let mut stack = vec![0_u8; SSIZE as usize];let stack_ptr = stack.as_mut_ptr();unsafe {std::ptr::write(stack_ptr.offset(SSIZE - 16) as * mut u64, hello as u64);ctx.rsp = stack_ptr.offset(SSIZE - 16) as u64;gt_switch(&mut ctx);}
}
所以这实际上是在设计我们的新栈。 hello 已经是一个指针了(一个函数指针),所以我们可以直接把它转换为一个 u64,因为 64 位系统上的所有指针都是 64 位,然后我们将这个指针写入我们的新栈。

我们将在下一章中详细讨论栈,但现在我们需要知道的一件事是栈向下增长。如果我们的 48 字节栈在索引 0处开始,并在索引 47 处结束,则索引 32 将是从栈末尾开始的 16 字节偏移量的第一个索引。

请注意,我们将指针写入距离栈底部16字节的偏移量(还记得我写的关于16字节对齐的内容吗?)。

我们把它作为指向 u64 的指针而不是指向 u8 的指针。我们想要写入位置 32、33、34、35、36、37、38、39,这是我们存储 u64 所需的 8 字节空间。如果我们不进行这个类型转换,我们实际上是在尝试将 u64 写入位置 32(译者注:即将一个 u64 写入到一个 u8 中,显然存不下),这不是我们想要的。

译者注:stack_ptr 的类型为 * mut u8,stack_ptr.offset(SSIZE - 16) 也是 * mut u8。

我们将 rsp(栈指针)设置为 栈中索引为 32 的内存地址,我们传递的不是存储在该位置的 u64 值而是首字节的地址。

译者注:如果传递的是存储在该位置的值,代码就应该是 (stack_ptr.offset(SSIZE - 16) as * mut u64).read(),即 hello 函数指针的值;如果是首字节地址,就是上面那段代码中的 stack_ptr.offset(SSIZE - 16) as u64(这个地址以 u64 类型存储,因为我们要把它赋值给 u64 类型的 ctx.rsp)。

当我们执行 cargo run 命令时,我们将看到如下输出:

Finished dev [unoptimized + debuginfo] target(s) in 0.58s
Running `target\debug\green_thread_start.exe`
I LOVE WAKING UP ON A NEW STACK!
好的,究竟发生了什么?我们在任何时候都没有调用函数 hello,但它仍然运行了。发生的事情是我们实际上让 CPU 跳转到我们自己的栈并在那里执行代码。我们迈出了实现上下文切换的第一步。

在接下来的章节中,我们会在实现绿色线程之前先探讨一点栈相关的内容,这个过程会更加容易,因为目前我们已经涵盖了很多基础知识。

如果要运行它,可以在这里查看完整代码。

【连载】两百行Rust代码解析绿色线程原理(二)一个能跑通的例子相关推荐

  1. 【连载】 两百行Rust代码解析绿色线程原理(一)绪论及基本概念

    原文: Green threads explained in 200 lines of rust language 地址: https://cfsamson.gitbook.io/green-thre ...

  2. Rust代码检查工具Clippy原理浅析

    一.用途 使用Rust的开发者大部分都接触过Clippy,正如使用C或Python的的开发者接触过Pclint和Pylint一样,那么这些工具是做什么的呢?Clippy的readme一句话讲的很清楚, ...

  3. 牛逼,两百行Python代码带你打造一款《天天酷跑》游戏!

    公众号关注 "菜鸟学Python" 第431篇原创,设为 "星标",带你一起学编程! 最近一段时间,小编发现已经好久没有给大家带来趣味游戏的案例展示了.刚好小编 ...

  4. 两百行C++代码实现yolov5车辆计数部署(通俗易懂版)

    本文是文章传统图像处理方法实现车辆计数的后续.这里用OpenCV实现了基于yolov5检测器的单向车辆计数功能,方法是撞线计数.该代码只能演示视频demo效果,一些功能未完善,离实际工程应用还有距离. ...

  5. 15行Python代码能干嘛?能写一个抖音网页版的简易爬虫(附源码)

    前言 随着互联网时代的到来,人们更加倾向于互联网购物,某宝又是电商行业的巨头,在某宝平台中有很多商家数据,今天带大家使用python+selenium工具获取这些公开的商家数据 环境介绍: pytho ...

  6. 只有20行Javascript代码!手把手教你写一个页面模板引擎

    http://blog.jobbole.com/56689/ 关于一个 不错网站 http://www.jobbole.com/members/njuyz/ 转载于:https://www.cnblo ...

  7. python应用(1)两百行代码实现微信好友数据爬取与可视化

    前段时间发现了一个好玩的东西,一个python的第三方库itchat,它的功能很强大.只要你扫一下它所生成的二维码即可模拟登陆你的微信号,然后可以实现自动回复,爬取微信列表好友信息等功能.基于这个第三 ...

  8. [GCN] 代码解析 of GitHub:Semi-supervised classification with graph convolutional networks

    本文解析的代码是论文Semi-Supervised Classification with Graph Convolutional Networks作者提供的实现代码. 原GitHub:Graph C ...

  9. 100行JavaScript代码实现JavaScript

    先看效果: 100行JavaScript代码实现经典游戏俄罗斯方块 新建一个html文件,复制如下代码,用浏览器打开即可: <!doctype html> <html> < ...

最新文章

  1. python使用fpdf的multi_cell API实现长文本写入的自动换行功能实战
  2. 全文搜索技术—Lucene
  3. 进程池的同步方法 pool.apply
  4. Android 自定义AlertDialog,调用方法与系统一致
  5. 当对项目强名时自动构建失败(TeamFoudationServer试用笔记)
  6. zephyr 系统--- 内存池使用方法
  7. CC++刚開始学习的人编程教程(9) Windows8.1安装VS2013并捆绑QT与编程助手
  8. OkHttp自定义重试次数
  9. oracle11g memory_target,Oracle11g启动报:ORA-00845: MEMORY_TARGET not supported on this system
  10. Python Tricks(十)—— 递归修改文件名
  11. python经典实例-python经典实例
  12. ODBC操作excel
  13. (转)GridView固定表头
  14. 日本惊现史上最大数字货币被盗案,加密货币交易所疑遭黑客盗走620亿日元!
  15. 计算机组成原理期末复习
  16. 详细解读Latent Diffusion Models:原理和代码
  17. Yolo-V4数据增强
  18. 温莎计算机应用硕士是针对国际学生的吗,专业推荐 | 加拿大留学,温莎大学英语计算机专业了解一下...
  19. Day 01-创建 Vue3.0 工程
  20. 完美洗牌问题(打乱数组间各元素的顺序)

热门文章

  1. 电子电路测试软件,清华大学出版社-图书详情-《电子电路软件仿真实验教程》...
  2. 基于STM32F401RET6字库烧录(SPIW25Q64驱动)
  3. 2017年全国大学生数学建模B题数据处理
  4. python爬虫实战之逆向分析酷狗音乐
  5. 开源BI工具3:dataease
  6. JavaScript交互式网页设计 • 【第8章 jQuery动画与特效】
  7. Euraka 服务注册与发现
  8. Notepad++最新版简体中文的GitHub下载
  9. Pascal voc2007安装和pytorch使用
  10. ADS使用技巧/debug