这是我的解决方案;它不太美观,但它能够工作并通过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;
} 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和对齐检查),特别是因为您正在实现加密库。
编辑:示例主程序通过错误地在新线程外部锁定互斥锁并在线程内部解锁它们来调用未定义的行为,而不是在每个线程内部锁定互斥锁并在完成后解锁它们。不确定我为什么觉得那是个好主意。
Mutex<()>
,并将数据分开存储(可能在UnsafeCell
中),然后手动确保保护。不过我对 PKCS#11 不熟悉,可能有更好的方法。 - kmdreko