kernel信号量源码分析

Linux内核中的信号量是一种经典的同步机制,用于控制对共享资源的并发访问。与完成量(Completion)不同,信号量更适用于资源计数管理,例如限制同时访问某资源的线程数量。以下是信号量的核心知识点以及与完成量的对比:

一、 结构体成员

c 复制代码
struct semaphore {
  raw_spinlock_t lock;      /* 自旋锁 */
  unsigned int count;       /* 可用资源计数 */
  struct list_head wait_list; /* 等待改信号量的任务队列 */
};

struct semaphore_waiter 用于描述一个正在等待信号量的任务,它不是信号量本体,而是挂入信号量等待队列的等待者。

c 复制代码
struct semaphore_waiter {
	struct list_head list; /* 等待队列链表节点 */
	struct task_struct *task; /* 指向当前等待的进程 */
	bool up; /* 该等待者是否被唤醒 */
};

二、 API函数

2.1 初始化信号量

动态初始化

  • sem:指向要初始化的信号量对象。
  • val:信号量初始计数值,表示初始可用的资源数量。
c 复制代码
static inline void sema_init(struct semaphore *sem, int val)
{
	static struct lock_class_key __key;
	/* 宏构造一个临时的semaphore,整体赋值给sem,该步骤会初始化其结构体的三个成员 */
	*sem = (struct semaphore) __SEMAPHORE_INITIALIZER(*sem, val);
	lockdep_init_map(&sem->lock.dep_map, "semaphore->lock", &__key, 0);
}

静态初始化,只能定义初始值为 1 的信号量。

c 复制代码
DEFINE_SEMAPHORE(my_sem);
/*展开后等价于*/
struct semaphore name = {
	.lock = __RAW_SPIN_LOCK_UNLOCKED(name.lock),
	.count = 1,
	.wait_list = LIST_HEAD_INIT(name.wait_list),
};

2.2 获取信号量

阻塞等待,直到获取信号量:

c 复制代码
void down(struct semaphore *sem)
{
	unsigned long flags;

	might_sleep(); /* 声明该函数可能睡眠 */
	raw_spin_lock_irqsave(&sem->lock, flags); /* 获取锁、保存并关闭中断 */
	if (likely(sem->count > 0))
		sem->count--; /* 获取到资源 */
	else
		__down(sem); /* 资源不可用,进入等待队列 */
	raw_spin_unlock_irqrestore(&sem->lock, flags);
}
EXPORT_SYMBOL(down);

__down函数会调用__down_common(sem, TASK_UNINTERRUPTIBLE, MAX_SCHEDULE_TIMEOUT)

c 复制代码
static inline int __sched __down_common(struct semaphore *sem, long state,
								long timeout)
{
	struct semaphore_waiter waiter;
  /* 创建一个等待节点,挂入wait_list */
	list_add_tail(&waiter.list, &sem->wait_list);
	waiter.task = current; /* 记录当前任务 */
	waiter.up = false; /* 表明当前等待者未被up选中 */

	for (;;) {
		if (signal_pending_state(state, current)) /* 决定当前等待是否应被信号中断 */
			goto interrupted;
		if (unlikely(timeout <= 0))
			goto timed_out; /* 等待时间耗尽 */
		__set_current_state(state); /* 设置任务以什么状态睡眠 */
		raw_spin_unlock_irq(&sem->lock);
		timeout = schedule_timeout(timeout); /* 任务睡眠,直到超时或被唤醒 */
		raw_spin_lock_irq(&sem->lock);
		if (waiter.up)
			return 0; /* 成功获取到信号量,返回 */
	}

 timed_out: /* 等待超时,从等待队列中删除 */
	list_del(&waiter.list);
	return -ETIME;

 interrupted:
	list_del(&waiter.list);
	return -EINTR;
}

可中断的等待(允许信号打断):

c 复制代码
int down_interruptible(struct semaphore *sem)
{
	unsigned long flags;
	int result = 0;

	might_sleep(); /* 声明函数可能上睡眠 */
	raw_spin_lock_irqsave(&sem->lock, flags);
	if (likely(sem->count > 0))
		sem->count--; /* 获取到资源 */
	else
		result = __down_interruptible(sem);
	raw_spin_unlock_irqrestore(&sem->lock, flags);

	return result;
}

__down_interruptible会调用__down_common(sem, TASK_INTERRUPTIBLE, MAX_SCHEDULE_TIMEOUT) 。和阻塞等待的区别就是参数传递的是TASK_INTERRUPTIBLE ,表示可以被信号打断。

非阻塞尝试(立即返回):

c 复制代码
int down_trylock(struct semaphore *sem)
{
	unsigned long flags;
	int count;

	raw_spin_lock_irqsave(&sem->lock, flags);
	count = sem->count - 1;
	if (likely(count >= 0))
		sem->count = count;
	raw_spin_unlock_irqrestore(&sem->lock, flags);

	return (count < 0);
}

2.3 释放信号量

释放信号量并唤醒等待线程:

c 复制代码
static noinline void __sched __up(struct semaphore *sem)
{
	struct semaphore_waiter *waiter = list_first_entry(&sem->wait_list,
						struct semaphore_waiter, list); /* 取出等待队列的第一个等待者 */
	list_del(&waiter->list); /* 从队列里面删除 */
	waiter->up = true; /* 标记已获取到信号量 */
	wake_up_process(waiter->task); /* 唤醒对应任务 */
}
相关推荐
结衣结衣.2 小时前
手把手教你实现文档搜索引擎
linux·c++·搜索引擎·开源·c++11
sdm0704273 小时前
进程间通信
linux·运维·服务器
蚰蜒螟3 小时前
Linux内核启动(init)与程序执行(execve)深度解析:从kernel_init到load_elf_binary
linux·运维·服务器
thethefighter3 小时前
信创综合档案管理系统单机版部署与使用
linux·银河麒麟·档案管理系统·单机版·nhdeep·信创版·综合档案管理系统
道清茗3 小时前
【RH294知识点汇总】第 6 章 《 管理复杂的 Play 和 Playbook 》常见问题
linux·服务器·网络
哼?~3 小时前
序列化与反序列化
linux·网络
嵌入式×边缘AI:打怪升级日志4 小时前
从硬编码按键驱动到 Linux Platform 设备树驱动:逐行解剖与融会贯通
linux·运维·服务器
小周技术驿站5 小时前
Linux 权限管理细节详解
linux·运维·服务器·ubuntu·centos
思麟呀5 小时前
Select多路转接
linux·网络·c++·网络协议·http