目录
[信号量 API 函数](#信号量 API 函数)
[互斥体 API 函数](#互斥体 API 函数)
前言
Linux 内核提供的几种并发和竞争的处理方法,我们学习了:
本讲我们就继续学习:信号量和互斥体。
信号量
概念与特性
Linux 内核提供了信号量机制,信号量常常用于控制对共享资源的访问。
信号量是一种睡眠锁机制,通过计数器控制对共享资源的访问:
- 计数器值:表示可用资源数量
- P操作:申请资源(值减1,若为0则阻塞)
- V操作:释放资源(值加1,唤醒等待者)
信号量常见的有4种类型:

这几个信号量的主要区别如下:

总结一下信号量的特点:
- 信号量可以使等待资源线程进入休眠状态,因此适用于那些占用资源比较久的场合。
- 信号量不能用于中断中,因为信号量会引起休眠,中断不能休眠。
- 如果共享资源的持有时间比较短,那就不适合使用信号量了,因为频繁的休眠、切换线程引起的开销要远大于信号量带来的那点优势。
信号量 API 函数
Linux 内核使用 semaphore 结构体表示信号量,结构体内容如下:
cpp
struct semaphore {
raw_spinlock_t lock; // 保护信号量结构的自旋锁
unsigned int count; // 可用资源计数器
struct list_head wait_list; // 等待进程链表
};
有关信号量的 API 函数如表:

示例代码如下:
cpp
#include <linux/module.h>
#include <linux/semaphore.h>
#include <linux/sched.h>
// 定义并初始化信号量(方法1)
static DEFINE_SEMAPHORE(my_sem);
// 动态信号量(方法2)
static struct semaphore dynamic_sem;
static int __init semaphore_demo_init(void)
{
printk(KERN_INFO "Semaphore Demo Start\n");
// 初始化动态信号量(初始值设为2)
sema_init(&dynamic_sem, 2);
// 1. 基本获取/释放
down(&my_sem);
printk("Process %d entered critical section\n", current->pid);
up(&my_sem);
// 2. 非阻塞尝试
if (down_trylock(&dynamic_sem) == 0) {
printk("Got semaphore without waiting\n");
up(&dynamic_sem);
} else {
printk("Semaphore busy, continue other work\n");
}
// 3. 可中断等待
if (down_interruptible(&dynamic_sem)) {
printk("Interrupted by signal\n");
return -ERESTARTSYS;
}
/* 临界区操作(可安全休眠) */
msleep(100);
up(&dynamic_sem);
return 0;
}
static void __exit semaphore_demo_exit(void)
{
printk(KERN_INFO "Semaphore Demo End\n");
}
module_init(semaphore_demo_init);
module_exit(semaphore_demo_exit);
MODULE_LICENSE("GPL");
互斥体
概念与特性
将信号量的值设置为 1,就可以通过信号量实现互斥,但是 Linux 提供了一个比信号量更专业的机制来进行互斥,它就是互斥体---mutex。
互斥体(Mutual Exclusion)是一种睡眠锁机制,用于保护临界区资源,具有以下特性:
- 独占访问:同一时间仅允许一个线程持有锁
- 睡眠等待:获取锁失败时让出CPU(非忙等待)
- 进程上下文:只能在可调度上下文中使用
和自旋锁、信号相比,互斥体的优势如下:

Linux (5.15+)内核使用 mutex 结构体表示互斥体,定义如下:
cpp
struct mutex {
atomic_long_t owner; // 持有者标识 + 状态标志
spinlock_t wait_lock; // 保护等待队列的自旋锁
struct list_head wait_list; // 等待线程链表
#ifdef CONFIG_DEBUG_MUTEXES
const char *name; // 调试用名称
void *magic; // 调试用魔数
#endif
};
在使用 mutex 之前要先定义一个 mutex 变量。
在使用 mutex 的时候要注意如下几点:
- mutex 可以导致休眠,因此不能在中断中使用 mutex,中断中只能使用自旋锁。
- 和信号量一样, mutex 保护的临界区可以调用引起阻塞的 API 函数。
- 因为一次只有一个线程可以持有 mutex,因此,必须由 mutex 的持有者释放 mutex。并且 mutex 不能递归上锁和解锁。
互斥体 API 函数
有关互斥体的 API 函数如表:

示例代码如下:
cpp
#include <linux/module.h>
#include <linux/mutex.h>
static DEFINE_MUTEX(global_mutex); // 静态定义并初始化全局互斥体
static struct mutex dynamic_mutex; // 动态互斥体
static int shared_data = 0; // 共享数据
static int __init mutex_demo_init(void)
{
printk(KERN_INFO "Mutex Demo Start\n");
// 1. 初始化动态互斥体
mutex_init(&dynamic_mutex);
// 2. 检查锁状态
printk("Global mutex is %slocked\n",
mutex_is_locked(&global_mutex) ? "" : "not ");
// 3. 基本加锁/解锁
mutex_lock(&global_mutex);
shared_data = 100;
mutex_unlock(&global_mutex);
// 4. 尝试获取锁(非阻塞)
if (mutex_trylock(&dynamic_mutex)) {
printk("Got dynamic mutex immediately\n");
mutex_unlock(&dynamic_mutex);
} else {
printk("Dynamic mutex is busy\n");
}
// 5. 可中断锁(推荐用法)
if (mutex_lock_interruptible(&global_mutex)) {
printk("Interrupted by signal while waiting\n");
return -ERESTARTSYS;
}
/* 临界区操作(可安全休眠) */
shared_data += 50;
msleep(10); // 模拟耗时操作
mutex_unlock(&global_mutex);
return 0;
}
static void __exit mutex_demo_exit(void)
{
// 确保所有锁已释放
if (!mutex_is_locked(&dynamic_mutex)) {
mutex_lock(&dynamic_mutex);
shared_data = 0;
mutex_unlock(&dynamic_mutex);
}
printk(KERN_INFO "Final shared_data: %d\n", shared_data);
printk(KERN_INFO "Mutex Demo End\n");
}
module_init(mutex_demo_init);
module_exit(mutex_demo_exit);
MODULE_LICENSE("GPL");