互斥体是什么?
互斥体是Linux内核专门设计的同步机制,用于实现“一次仅一个线程访问共享资源”的互斥控制。相比通过将信号量值设为 1 来模拟互斥,mutex 的语义更清晰、行为更规范,是驱动开发中保护临界区的首选方案。
核心特性:同一时刻只允许一个线程持有 mutex,进入临界区,其他线程必须等待
不可递归:mutex 不支持同一线程重复加锁,重复上锁会导致死锁。
适用场景:编写Linux驱动程序时,如需保护硬件寄存器操作、设备打开关闭等独占性操作,应优先使用 mutex。
Linux 内核中,mutex 由structmutex结构体表示,核心成员包括:
atomic_tcount:记录锁状态,1 表示未上锁,0 表示已上锁,负数表示已上锁且有等待者。
spinlock_t wait_lock:保护等待队列的自旋锁,用于协调等待线程。
注:现代内核的 mutex 实现更复杂,引入了乐观自旋、MCS 锁队列等优化,提升多核性能,但核心原理不变。
使用注意事项:
禁止用于中断上下文:mutex 加锁过程可能导致线程休眠,而中断上下文不允许休眠,否则引发系统崩溃。中断场景下应使用自旋锁。
临界区支持阻塞操作:mutex 允许休眠,因此临界区内可安全调用 copy_from_user()、msleep()、kmalloc(GFP_KERNEL)等可能阻塞的函数。
严格遵循所有权:只有持有 mutex 的线程才能解锁,禁止递归加锁和跨线程解锁,否则会导致死锁或逻辑错误。
互斥体 API 函数
Linux 内核提供了一组简洁的 mutex 操作接口,核心函数如下:
DEFINE_MUTEX(name):静态定义并初始化一个 mutex 变量,推荐优先使用,一步完成定义和初始化。
mutex_init(struct mutex *lock):动态初始化已定义的 mutex,常用于结构体内嵌 mutex 的场景。
mutex_lock(struct mutex *lock):获取 mutex,若已被占用则线程进入不可中断的休眠状态等待,成功获取后进入临界区。
mutex_unlock(struct mutex *lock):释放 mutex,唤醒等待队列中的第一个线程,退出临界区。
mutex_lock_interruptible(struct mutex *lock):可被信号中断的加锁函数,若等待期间收到信号则返回 -EINTR,适合需要响应用户信号的场景(如驱动的系统调用)。
mutex_trylock(struct mutex *lock):非阻塞尝试获取 mutex,成功返回 1,失败返回 0,不会休眠,适合需要快速判断的场景。
mutex_is_locked(struct mutex *lock):检查 mutex 是否已被持有,返回 1 表示已上锁,0 表示未上锁。
互斥体使用示例
方式1:
动态初始化struct mutex lock;mutex_init(&lock);
方式2:
静态初始化(推荐)
DEFINE_MUTEX(my_lock);// 加锁
进入临界区
mutex_lock(&lock);// 临界区:
访问共享资源,执行独占操作
mutex_unlock(&lock); // 解锁,退出临界区
核心对比:
mutex、信号量与自旋锁
mutex:专为互斥设计,支持休眠,不可用于中断,有严格的所有权语义,适合驱动开发中长临界区的场景,是互斥访问的首选。
二值信号量:可用于互斥,但语义不如 mutex 清晰,允许任意线程释放,且支持递归加锁,更适合通用同步场景,而非单纯的互斥控制。
自旋锁:不可休眠,通过忙等实现互斥,性能极高,适合短临界区且支持中断上下文,但会占用 CPU 资源,不能用于可能休眠的场景。
总结
mutex 是 Linux 内核为互斥场景量身定制的同步机制,语义明确、安全性高,是驱动开发中保护设备独占访问的核心工具。使用时需牢记:禁止在中断上下文使用、禁止递归加锁、严格遵循“谁加锁谁解锁”的原则。实际开发中,配合内核调试选项可有效检测死锁和误用问题,保障代码可靠性。