标签:details 英文 文件描述符 决策 声明 上下 功能 常见操作 kernel
https://blog.csdn.net/liigo/article/details/19249145
A Guide to the Rust Runtime, by Alex Crichton and Brian Anderson
翻译:庄晓立(Liigo),com.liigo@gmail.com,G+,Weibo,CSDN,Rust中文圈
日期:2014年2月。
2015年5月20日译者Liigo注:此文形成于Rust 1.0之前的开发动荡期,目前已经严重过时(outdated)!相关设施在Rust 1.0标准化过程中发生了巨大变化(参见RFC #230),Runtime已经不存在了。特此声明,以免误导读者。
Rust编程语言的标准发行版包含两个运行时库(libgreen和libnative),提供I/O等基础设施的统一接口。但对Rust语言本身而言,运行时(runtime)并不是必需的;Rust编译器可以生成在所有环境中运行的代码,包括内核(kernel)环境。Rust语言也不需要运行时提供内存安全,因为它的类型系统本身已经足够安全——通过编译时静态验证给予保证。运行时只是利用语言的安全特性提供一系列便利的、安全的、高层的抽象。
如果Rust没有运行时(runtime),我们编程能做的事情非常有限,所以Rust需要提供运行时。这份指导手册将探讨Rust用户空间(user-space)的运行时、如何使用它、它能做什么。
Rust运行时可以被视为提供以下功能代码的组合:输入/输出(I/O)、任务孵化(task spawning)、任务本地存储(TLS)等等。本质上,它提供一少部分对象,为实现常见功能提供便利支持。Rust运行时自身的实现是自包含的(self-contained),避免干扰其他库。
运行时目前提供以下功能特性(不完整列表):
运行时的设计初衷是达成以下目标:
本节将介绍目前的Rust运行时的体系结构。Rust运行时曾经被重写了几遍,本节仅涉及当前最新版本。
Rust运行时的核心抽象概念是任务(Task)。任务代表了运行Rust代码的“线程”,但此“线程”并不一定直接对应于操作系统里的线程。运行时里的大多数服务都是通过Task提供给用户,因而可以做到单个任务内部决策。
采用这种策略的结果是,要求所有使用标准库的Rust代码,都有一个本地的Task结构体(local Task structure)。该结构体被存储在操作系统的线程局部存储(TLS, Thread Local Storage)内部,以便高效访问。
一定有这么一个Task结构体存在,是Rust运行时本质上唯一的假定。这是一个核心假定,令所有使用标准库的代码受益,因此Task被定义在标准库内。几乎所有运行时服务都是通过Task提供的。
当处理I/O时,通常有一些约定俗成的方法,但这些方法未必在任何情况下都正确。I/O的处理非常复杂,几乎不可能在多种环境中使用一致的处理方案。不能保证Rust任务(Task)有权限处理I/O,也不能保证它以何种方式处理I/O。
这意味着,标准库中无法定义处理I/O功能的具体实现代码,只能定义一批I/O操作接口,由各环境下的Task各自实现具体功能。这些I/O接口被设计为以同步I/O调用为核心。此架构不会根本性的阻碍以其他形式处理I/O,但目前还没有别的处理方式。
运行时(runtime)必须实现的这些I/O接口被定义在std::rt::rtio模块内。注意这些接口是不稳定的,是不对用户公开的(仅作为标准库内部实现细节)。
(译者Liigo注:任务的接口被定义在标准库libstd中,任务的具体实现被定义在运行时库libgreen/libnative中。)
任务(Task)的一项常见操作是,孵化(spawning)一个子任务(child task),在其中执行某些工作。这意味着并行执行被启用。如何孵化子任务,没有一个统一方法(未在标准库中定义),由各(运行时内的)任务自行决定。
任务孵化被解释为“孵化一个子任务(原文为sibling,疑为child之笔误)”,其高层操作接口定义在std::task模块中。孵化子任务前,可以事先设定子任务的参数,运行时的实现必须依据这些参数,执行具体的孵化行为。
任务的另一个操作是处理自身的运行状态,如阻塞(block)和唤醒(wake up)任务。具体操作细节由任务自己决定,标准库未做规定。
运行时的所有特性都被定义在接口Runtime
和结构体Task
。在不同运行时库(libgreen、libnative)中,结构体Task都是相同的,而接口Runtime的实现各不相同。Task内部存储了Runtime接口的实现对象,因而可以调用其接口函数。
Rust发行版提供了两个运行时库,分别是1:1线程模型的libnative和M:N线程模型的libgreen。就像许多计算机科学问题一样,你很难说选择哪个运行时库是正确的,它们各自都有优势和劣势。下面分别介绍两个运行时库提供的功能和没有的功能,供程序员参考并自行决定选择使用哪一个。
libnative运行时库的实现,是基于操作系统本地线程,加上libc阻塞I/O调用。因其用户空间的线程一一对应于操作系统线程,而被称为1:1线程模型。
在这种模型下,每一个Rust任务(Task)对应于一个操作系统线程,并且每一个I/O对象唯一对应一个文件描述符(fd)(或者其他系统内的对等物)。
使用libnative的一些优势:
保证与FFI绑定(外部函数接口绑定)交互操作。即使你调用的外部C库函数(例如数据库驱动)阻塞在线程I/O,也不会干扰其他Rust任务(Task)正常执行(因为它们在不同的系统线程内)。
在某些情况下相比M:N有更少的I/O损耗。并非所有M:N I/O都保证尽最大可能的快,而且有些东西(比如文件系统API)在某些平台下不是真正的异步操作,意味着M:N实现可能会比1:1实现引发更多损耗。
运行时库libgreen基于异步I/O框架libuv实现了“绿色线程”。M:N线程模型中的M是指当前进程内的操作系统本地线程个数,N是指Rust任务个数(即绿色线程个数)。在这种模型中,M个系统线程调度运行N个Rust绿色线程,在用户空间中进行线程上下文切换(context switching)。(译者Liigo注:通常情况下,N远大于M。)
M:N模型中很重要的一点是,Rust任务不能使用同步系统调用,阻塞任务自身。一旦被阻塞,任务所属的系统本地线程就整个僵化,无法再运行其他Rus任务。这意味着M个本地线程被(暂时)废掉了一个(但还有M-1个本地线程继续工作,因而能够做到0死锁)。通过在底层调用异步I/O接口(但从用户使用的角度看仍然像同步接口),系统本地线程永远不会阻塞。
libgreen库没有任何I/O实现,仅实现了Rust绿色线程的调度器(Schedulers)。真正的I/O实现位于libuv的封装库librustuv中。这么做的目的是希望将来会有不依赖libuv的I/O实现版本(当然目前还没有)。
使用libgreen的一些优势:
任务孵化的速度快。在M:N模型中,孵化新任务(绿色线程)时可以完全避免系统调用,效率更高。
任务切换的速度快。因为上下文切换是在用户空间进行的,所有任务间竞争操作(互斥、管道等)不用执行系统调用,因而速度更快。更高效的上下文切换也会促成更大的吞吐量。
M:N线程模型基于以下思路构建:通过M个操作系统本地线程(在libgreen中被称为M个调度器,它们共同组成一个调度器池),调度运行N个Rust任务(或称之绿色线程)。通过green::SchedPool类型可以细粒度地控制调度器池。SchedPool还是唯一能够孵化新的M:N任务的类型。新孵化的任务,跟当前任务一样,平等的从属于同一个调度器池。新任务必然持有调度器池的句柄,用于内部调用以便孵化其他任务。
既然有两个运行时库的实现,显然要做出决定选择使用哪一个。默认情况下,编译器总是链接其中之一。当前默认的运行时库是libgreen,但是今后默认的运行时库将会是libnative。
编译器默认选择链接一个运行时库,满足了用户的简单需求。而且这种默认行为不是强制性的,用户还可以自行选择使用哪个运行时库。
例如,这个程序将链接到默认运行时库:
fn main() {}
然而下面这个程序就是由用户决定明确的链接到特定的运行时库(libgreen):
两个运行时库libgreen和libnative都提供了上层的start函数,用于在各自运行时中启动初始的第一个Rust任务(Task)。
运行时的源代码分散在以下多个地方:
---------------- 全文完 ----------------
译者Liigo注:这篇文章英文原文多有重复混乱处,而我限于英文水平和Rust技术水平,有时也不能完全理解原意。如有翻译不周,敬请谅解,并恳请指正。我的联系方式在本文开头。
本文地址:
标签:details 英文 文件描述符 决策 声明 上下 功能 常见操作 kernel
原文地址:https://www.cnblogs.com/dhcn/p/12945976.html