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); /* 唤醒对应任务 */
}
相关推荐
HLC++11 小时前
Linux的进程间通信
android·linux·服务器
华清远见IT开放实验室13 小时前
实验室建设案例 | 石家庄科技信息职业学院嵌入式实验室——从底层硬件到系统应用,一所应用型高校的嵌入式人才培养这样落地
linux·arm开发·stm32·嵌入式硬件·高校·实验室建设
groundhappy15 小时前
idalib安装和codex ida-mcp配置
linux·开发语言·python
通信小小昕16 小时前
Ubuntu 26.04 中文输入法安装
linux·运维·ubuntu
张小姐的猫17 小时前
【Linux】网络编程 —— HTTP协议(上)
linux·运维·服务器·网络·http·单例模式·策略模式
栩栩云生17 小时前
AI 写代码犯的错,早被写进了错题集
linux·安全·ai编程
imc.1118 小时前
linux基础IO
linux·运维·服务器
BelongPanda20 小时前
Linux Nginx 纯手动 Let‘s Encrypt 泛域名证书配置教程
linux·nginx
酷可达拉斯21 小时前
Linux操作系统-shell编程(0)
linux·运维·服务器·python·云计算
2301_7779983421 小时前
Linux中断机制:操作系统如何高效运行
linux·运维·服务器