#token #cell #lock #borrow #ghost #data-access #borrowing

no-std tokenlock

提供解耦权限与数据的单元格类型

16 个版本

0.3.8 2022年6月12日
0.3.7 2021年12月19日
0.3.5 2021年9月4日
0.3.4 2021年1月31日
0.1.5 2017年10月27日

#1 in #ghost

Download history 33/week @ 2024-04-21 28/week @ 2024-04-28 19/week @ 2024-05-05 29/week @ 2024-05-12 22/week @ 2024-05-19 31/week @ 2024-05-26 23/week @ 2024-06-02 15/week @ 2024-06-09 27/week @ 2024-06-16 29/week @ 2024-06-23 101/week @ 2024-06-30 9/week @ 2024-07-07 35/week @ 2024-07-14 15/week @ 2024-07-21 142/week @ 2024-07-28 13/week @ 2024-08-04

209 每月下载量
10 个crate中使用 (2 直接使用)

MIT/Apache

100KB
1K SLoC

tokenlock

docs.rs

此crate提供了一种名为 TokenLock 的单元格类型,它只能通过展示正确的不可伪造的令牌来借用,从而解耦了权限与数据。

示例

基础

// Create a token
let mut token = IcToken::new();

// Create a keyhole by `token.id()` and use this to create a `TokenLock`.
let lock: IcTokenLock<i32> = TokenLock::new(token.id(), 1);
assert_eq!(*lock.read(&token), 1);

// Unlock the `TokenLock` using the matching token
let mut guard = lock.write(&mut token);
assert_eq!(*guard, 1);
*guard = 2;

只有匹配的 Token 的所有者才能访问其内容。 Token 不能被克隆

let lock = Arc::new(TokenLock::new(token.id(), 1));

let lock_1 = Arc::clone(&lock);
std::thread::spawn(move || {
    let lock_1 = lock_1;
    let mut token_1 = token;

    // I have `Token` so I can get a mutable reference to the contents
    lock_1.write(&mut token_1);
});

// can't access the contents; I no longer have `Token`
// lock.write(&mut token);

零大小令牌

一些令牌类型,如 BrandedTokenSingletonToken,完全依赖于类型安全和编译时检查来保证唯一性,不使用运行时数据来识别。因此,此类令牌的钥匙孔可以是默认构造的。 TokenLock::wrap 允许您使用默认构造的钥匙孔构建一个 TokenLock。另一方面,创建此类令牌通常有特定的要求。请参见以下使用 with_branded_token 的示例

with_branded_token(|mut token| {
    // The lifetime of `token: BrandedToken<'brand>` is bound to
    // this closure.

    // lock: BrandedTokenLock<'brand, i32>
    let lock = BrandedTokenLock::wrap(42);

    lock.set(&mut token, 56);
    assert_eq!(lock.get(&token), 56);
});

生命周期

返回引用的生存期受 TokenLockToken 两者的限制。

let mut token = IcToken::new();
let lock = TokenLock::new(token.id(), 1);
let guard = lock.write(&mut token);
drop(lock); // compile error: `guard` cannot outlive `TokenLock`
drop(guard);
drop(token); // compile error: `guard` cannot outlive `Token`
drop(guard);

它还防止在已经存在对该值的可变引用的情况下形成对该值的引用

let write_guard = lock.write(&mut token);
let read_guard = lock.read(&token); // compile error
drop(write_guard);

同时允许多个不可变引用

let read_guard1 = lock.read(&token);
let read_guard2 = lock.read(&token);

用例:链表

操作系统内核通常需要将全局状态存储在全局变量中。链表是内核中常用的数据结构,但Rust的所有权不允许将 'static 引用到由互斥锁保护的值。常见的解决方案,如智能指针和索引引用,会对具有单线程顺序流水线和没有硬件乘数的微控制器造成沉重负担。

struct Process {
    prev: Option<& /* what lifetime? */ Process>,
    next: Option<& /* what lifetime? */ Process>,
    state: u8,
    /* ... */
}
struct SystemState {
    first_process: Option<& /* what lifetime? */ Process>,
    process_pool: [Process; 64],
}
static STATE: Mutex<SystemState> = todo!();

tokenlock 通过将锁粒度从受保护数据的粒度中分离出来,使得 'static 引用方法成为可能。

use tokenlock::*;
use std::cell::Cell;
struct Tag;
impl_singleton_token_factory!(Tag);

type KLock<T> = UnsyncSingletonTokenLock<T, Tag>;
type KLockToken = UnsyncSingletonToken<Tag>;
type KLockTokenId = SingletonTokenId<Tag>;

struct Process {
    prev: KLock<Option<&'static Process>>,
    next: KLock<Option<&'static Process>>,
    state: KLock<u8>,
    /* ... */
}
struct SystemState {
    first_process: KLock<Option<&'static Process>>,
    process_pool: [Process; 1],
}
static STATE: SystemState = SystemState {
    first_process: KLock::new(KLockTokenId::new(), None),
    process_pool: [
        Process {
            prev: KLock::new(KLockTokenId::new(), None),
            next: KLock::new(KLockTokenId::new(), None),
            state: KLock::new(KLockTokenId::new(), 0),
        }
    ],
};

单元格类型

TokenLock 类型族由以下类型组成

Sync 令牌 !Sync 令牌²
未固定 TokenLock UnsyncTokenLock
固定¹ PinTokenLock UnsyncPinTokenLock

¹也就是说,这些类型尊重 T 被设置为 !Unpin 并防止通过 &mut T&SelfPin<&mut Self> 暴露。

²Unsync*TokenLock 要求令牌不可 !Sync(不可在线程间共享)。作为交换,即使包含的数据不可 Sync,此类单元也可以 Sync,就像 std::sync::Mutex

令牌类型

本包提供了以下实现 Token 的类型。

(std 仅限)IcToken 使用全局计数器(带线程局部池)来生成唯一的 128 位令牌。

(alloc 仅限)RcTokenArcToken 通过引用计数的内存分配来确保它们的唯一性。

SingletonToken<Tag> 是一个单例令牌,意味着在任何程序执行过程中只能存在此类实例的一个。使用 impl_singleton_token_factory! 实例化一个 static 标志来指示 SingletonToken 的活跃状态,并允许您通过 SingletonToken::new 安全地构造它。或者,您可以使用 SingletonToken::new_unchecked,但这在使用不当的情况下是不安全的。

BrandedToken<'brand> 实现了 GhostCell 的扩展。它通过 with_branded_tokenwith_branded_token_async 创建,使得创建的令牌仅可在提供的闭包或创建的 Future 中使用。此令牌不产生运行时开销。

令牌 ID(钥匙孔) 令牌(钥匙)
IcTokenId IcToken + u128 比较
RcTokenId RcToken + usize 比较
ArcTokenId ArcToken + usize 比较
SingletonTokenId<Tag> SingletonToken<Tag>
BrandedTokenId<'brand> BrandedToken<'brand>

!Sync 令牌

UnsyncTokenLockTokenLock 类似,但为非 Sync 令牌设计,对线程安全的内部类型要求较为宽松。具体来说,即使内部类型不是 Sync,它也可以是 Sync。这允许存储非 Sync 的单元格,如 Cell,并使用共享引用(所有这些都必须在同一个线程上,因为令牌是 !Sync)来读取和写入令牌。

use std::cell::Cell;
let mut token = ArcToken::new();
let lock = Arc::new(UnsyncTokenLock::new(token.id(), Cell::new(1)));

let lock_1 = Arc::clone(&lock);
std::thread::spawn(move || {
    // "Lock" the token to the current thread using
    // `ArcToken::borrow_as_unsync`
    let token = token.borrow_as_unsync();

    // Shared references can alias
    let (token_1, token_2) = (&token, &token);

    lock_1.read(token_1).set(2);
    lock_1.read(token_2).set(4);
});

当然,!Sync 令牌不能在线程之间共享

let mut token = ArcToken::new();
let token = token.borrow_as_unsync();
let (token_1, token_2) = (&token, &token);

// compile error: `&ArcTokenUnsyncRef` is not `Send` because
//                `ArcTokenUnsyncRef` is not `Sync`
std::thread::spawn(move || {
    let _ = token_2;
});

let _ = token_1;

Cargo 功能

  • std 启用依赖于 stdalloc 的项。
  • alloc 启用依赖于 alloc 的项。
  • unstable 启用不受 semver 保证的实验性项。
  • const-default_1 启用 ConstDefault 的实现,来自 const-default ^1
  • ghost-cellGhostCell 的官方实现,并被正式证明是正确的。它提供了一个类似于 BrandedTokenLock 的接口,更简单、更专注。

  • singleton-cell 中的 SCellGhostCell 的更通用版本,接受任何单例令牌类型,因此它更接近我们的 TokenLock。它提供了我们的 BrandedTokenSingletonToken 的现成等效。它牺牲了非 ZST 令牌类型以换取优势:SCell<Key, [T]> 可以转换为 [SCell<Key, T>]。它使用 singleton-trait 包(当 tokenlock::SingletonToken 被添加时不存在)来标记单例令牌类型。

  • qcell 提供了具有不同检查机制的不同单元格类型。 QCell 使用 32 位整数作为令牌标识符,TCellTLCell 使用标记类型,而 LCell 使用生命周期标记。

  • TokenCell 来自于 token-cell,与我们的 SingletonToken 有关,但像 SCell(略有不同),它支持从 &TokenCell<Token, &[T]>&[TokenCell<Token, T>] 的转换。它使用自定义特质来标记单例令牌类型。

许可协议:MIT/Apache-2.0

依赖项