https://blog.csdn.net/liigo/article/details/19249145
Rust運行時指南(官方文檔翻譯)
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)的運行時、如何使用它、它能做什么。
1、什么是運行時?
Rust運行時可以被視為提供以下功能代碼的組合:輸入/輸出(I/O)、任務孵化(task spawning)、任務本地存儲(TLS)等等。本質上,它提供一少部分對象,為實現常見功能提供便利支持。Rust運行時自身的實現是自包含的(self-contained),避免干擾其他庫。
運行時目前提供以下功能特性(不完整列表):
- 輸入/輸出(I/O)
- 任務孵化(task spawning)
- 消息傳遞(message passing)
- 任務同步(task synchronization)
- 任務本地存儲(TLS, task-local storage)
- 日志(logging)
- 本地堆(local heaps)(GC heaps)
- 任務展開(task unwinding)
1.1、運行時的目標是什么?
運行時的設計初衷是達成以下目標:
- Rust庫能夠在多種環境中運行,而不需要關心各種環境的具體細節。經常被提及的兩種環境是M:N和1:1。Rust運行時最初首先支持M:N,現在也同時支持了1:1。
- Rust運行時在達成在多種環境中運行的目標時,不需要強制區分不同的編譯模式。一個庫被編譯一次就能在多種不同的環境中永久運行,是我們明確的設計目標。
- Rust運行時應當高效運行。在其架構設計上,不應該有阻止程序高效運行的障礙。但也不是說非得在任何情況下都必須以最快的速度運行。
- Rust運行時應當盡可能對用戶透明。不鼓勵用戶直接與運行時交互。
2、運行時的體系結構
本節將介紹目前的Rust運行時的體系結構。Rust運行時曾經被重寫了幾遍,本節僅涉及當前最新版本。
2.1、本地任務(a local task)
Rust運行時的核心抽象概念是任務(Task)。任務代表了運行Rust代碼的“線程”,但此“線程”並不一定直接對應於操作系統里的線程。運行時里的大多數服務都是通過Task提供給用戶,因而可以做到單個任務內部決策。
采用這種策略的結果是,要求所有使用標准庫的Rust代碼,都有一個本地的Task結構體(local Task structure)。該結構體被存儲在操作系統的線程局部存儲(TLS, Thread Local Storage)內部,以便高效訪問。
一定有這么一個Task結構體存在,是Rust運行時本質上唯一的假定。這是一個核心假定,令所有使用標准庫的代碼受益,因此Task被定義在標准庫內。幾乎所有運行時服務都是通過Task提供的。
2.2、輸入/輸出(I/O)
當處理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中。)
2.3、任務孵化(Task spawning)
任務(Task)的一項常見操作是,孵化(spawning)一個子任務(child task),在其中執行某些工作。這意味着並行執行被啟用。如何孵化子任務,沒有一個統一方法(未在標准庫中定義),由各(運行時內的)任務自行決定。
任務孵化被解釋為“孵化一個子任務(原文為sibling,疑為child之筆誤)”,其高層操作接口定義在std::task模塊中。孵化子任務前,可以事先設定子任務的參數,運行時的實現必須依據這些參數,執行具體的孵化行為。
任務的另一個操作是處理自身的運行狀態,如阻塞(block)和喚醒(wake up)任務。具體操作細節由任務自己決定,標准庫未做規定。
2.4、運行時接口(trait Runtime)和任務結構體(struct Task)
運行時的所有特性都被定義在接口Runtime
和結構體Task
。在不同運行時庫(libgreen、libnative)中,結構體Task都是相同的,而接口Runtime的實現各不相同。Task內部存儲了Runtime接口的實現對象,因而可以調用其接口函數。
3、運行時的實現(implementations)
Rust發行版提供了兩個運行時庫,分別是1:1線程模型的libnative和M:N線程模型的libgreen。就像許多計算機科學問題一樣,你很難說選擇哪個運行時庫是正確的,它們各自都有優勢和劣勢。下面分別介紹兩個運行時庫提供的功能和沒有的功能,供程序員參考並自行決定選擇使用哪一個。
3.1、1:1 - 使用libnative
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實現引發更多損耗。
3.2、M:N - 使用libgreen
運行時庫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模型中,孵化新任務(綠色線程)時可以完全避免系統調用,效率更高。
-
任務切換的速度快。因為上下文切換是在用戶空間進行的,所有任務間競爭操作(互斥、管道等)不用執行系統調用,因而速度更快。更高效的上下文切換也會促成更大的吞吐量。
3.2.1、調度器池(Pool of Schedulers)
M:N線程模型基於以下思路構建:通過M個操作系統本地線程(在libgreen中被稱為M個調度器,它們共同組成一個調度器池),調度運行N個Rust任務(或稱之綠色線程)。通過green::SchedPool類型可以細粒度地控制調度器池。SchedPool還是唯一能夠孵化新的M:N任務的類型。新孵化的任務,跟當前任務一樣,平等的從屬於同一個調度器池。新任務必然持有調度器池的句柄,用於內部調用以便孵化其他任務。
3.3、選擇哪一個?
既然有兩個運行時庫的實現,顯然要做出決定選擇使用哪一個。默認情況下,編譯器總是鏈接其中之一。當前默認的運行時庫是libgreen,但是今后默認的運行時庫將會是libnative。
編譯器默認選擇鏈接一個運行時庫,滿足了用戶的簡單需求。而且這種默認行為不是強制性的,用戶還可以自行選擇使用哪個運行時庫。
例如,這個程序將鏈接到默認運行時庫:
fn main() {}
然而下面這個程序就是由用戶決定明確的鏈接到特定的運行時庫(libgreen):
-
extern mod green;
-
-
# [start]
-
fn start(argc: int, argv: **u8) -> int {
-
green::start(argc, argv, main);
-
}
-
-
fn main() {}
兩個運行時庫libgreen和libnative都提供了上層的start函數,用於在各自運行時中啟動初始的第一個Rust任務(Task)。
4、運行時在哪里?
運行時的源代碼分散在以下多個地方:
---------------- 全文完 ----------------
譯者Liigo注:這篇文章英文原文多有重復混亂處,而我限於英文水平和Rust技術水平,有時也不能完全理解原意。如有翻譯不周,敬請諒解,並懇請指正。我的聯系方式在本文開頭。
本文地址:
- CSDN:http://blog.csdn.net/liigo/article/details/19249145
- Github:https://github.com/liigo/learn-rust/blob/master/doc/rust-runtime-zh.md
- Gdrive:https://docs.google.com/document/d/1uqW9BfPtop9_Nuj0akGRtotsjO0WfkJj3JZ-5gDLAds
- Rust中文圈:https://plus.google.com/+LiigoZhuang/posts/5wAqgkJxq1m
- PDF可打印版:https://github.com/liigo/learn-rust/raw/master/doc/rust-runtime-zh.pdf