如何协调 Rust Mutex 和 (C) 调用者提供的锁机制?

4

我正在用Rust编写一个PKCS#11库,并且在如何协调调用者提供的锁定机制(即CreateMutex,DestroyMutex,LockMutex,UnlockMutex)与Rust互斥锁实现时遇到了一些问题。

当然,C语言中的互斥锁不与数据绑定。它们只是对某个部分设置锁定,程序员负责创建/销毁和锁定/解锁互斥锁。而Rust将数据与互斥锁本身绑定。

我该如何协调这两种情况:当来自调用程序的锁定机制(未绑定到数据且手动创建/锁定/解锁/销毁)与没有提供锁定机制并使用Rust的锁定(绑定到数据和MutexGuard以及范围丢弃/解锁互斥锁)的情况下?

我应该使用parking_lot::Mutex和raw_lock/raw_unlock使模式相似,例如未绑定到数据吗?


通常使用自旋锁来保护数据,而不是互斥锁。 - tstanisl
2
@tstanisl,你能向我解释一下自旋锁如何解决我的问题吗? - dirtbiker
1
你可以将互斥锁定义为 Mutex<()>,并将数据分开存储(可能在 UnsafeCell 中),然后手动确保保护。不过我对 PKCS#11 不熟悉,可能有更好的方法。 - kmdreko
1
我不确定你所说的“当从调用程序提供锁定机制时”的意思。你是在编写一个函数,它需要特定类型的互斥量并期望在内部对其进行锁定吗? - trent
1
请注意,所以互斥锁都与共享数据有某种联系-如果没有需要共享的数据,则无需锁定。在 C 程序中,很难确定特定锁保护哪些数据,并且这些数据可能太过模糊或分布式,无法放入单个容器中-但这并不意味着互斥锁不保护任何数据。这可能会导致我对“提供的锁定机制”的困惑-任何提供锁定机制的人都应该告诉您它保护什么以及如何使用它,并且您需要遵守他们的要求。 - trent
1个回答

1
这是我的解决方案;它不太美观,但它能够工作并通过miri测试。一般的想法是我们捆绑一个Mutex<()>和一个可选的MutexGuard用于该互斥体。我没有添加正确的错误处理或命名以匹配标准。
结构体:
struct MutexContainer {
    mutex: Mutex<()>,
    guard: Cell<Option<MutexGuard<'static, ()>>>,
}
pub struct OpaqueMutex {
    _data: [usize;0],
}

请注意,OpaqueMutex只是一个不透明的句柄,以避免泄露MutexContainer的内部细节,在ffi期间这是最佳实践。 这种设计的关键在于,如果结构体持有一个守卫,则该结构体不能移动。
创建互斥锁:
pub unsafe extern "C" fn create_mutex(mutex: *mut *mut OpaqueMutex) -> libc::c_ulong {
    *mutex = Box::into_raw(Box::new(
        MutexContainer {
            mutex: Mutex::new(()),
            guard: Cell::new(None),
        },
    ))
    .cast();
    0
}

这里没有什么花哨的东西,只是在堆上泄漏了互斥锁。

互斥锁锁定:

pub unsafe extern "C" fn lock_mutex(mutex: *mut OpaqueMutex) -> libc::c_ulong {
    let container:&MutexContainer = &*mutex.cast();
    let lock:MutexGuard<'static, _> = mem::transmute(container.mutex.lock().unwrap());
    container.guard.set(Some(lock));
    0
}

这里我使用mem::transmute来延长锁的生命周期并存储结果。由于我们确保不移动其父互斥体,并且在此处或其他地方不让它超过互斥体的寿命,因此transmute是安全的。互斥锁将保持锁定状态直到保护程序被释放。

互斥锁解锁:

pub unsafe extern "C" fn unlock_mutex(mutex: *mut OpaqueMutex) -> c_ulong{
    let container:&MutexContainer = &*mutex.cast();
    if container.mutex.try_lock().is_ok(){
        return !0; //can't unlock an unlocked mutex
    } else {
        container.guard.set(None);
    }
    0
}

在这里,我们使用非阻塞的try_lock来确定互斥锁是否被锁定。如果try_lock成功,那么意味着互斥锁未锁定,我们返回一个错误。否则,我们通过用None覆盖它来丢弃保护。
互斥锁丢弃:
pub unsafe extern "C" fn drop_mutex(mutex: *mut OpaqueMutex) -> c_ulong{
    unlock_mutex(mutex);
    drop(Box::from_raw(mutex.cast::<MutexContainer>()));
    0
}

在这里,我们确保解锁互斥锁以防止guard出现悬空引用,然后通过重新包装指针来回收内存。

完整的游乐场链接加测试工具。

免责声明:虽然此代码通过了miri的测试,但它远非可投入生产环境:基本上没有错误处理。您应该确保错误得到稳健处理,并进行理智检查(例如null和对齐检查),特别是因为您正在实现加密库。

编辑:示例主程序通过错误地在新线程外部锁定互斥锁并在线程内部解锁它们来调用未定义的行为,而不是在每个线程内部锁定互斥锁并在完成后解锁它们。不确定我为什么觉得那是个好主意。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接