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); /* 唤醒对应任务 */
}
相关推荐
bosins8 小时前
WSL 迁移指南:如何将 Linux 环境完整复制到另一台电脑
linux·运维·服务器
企鹅的蚂蚁9 小时前
LubanCat RK3588 实时 Linux 开发(六):启用 gs_usb 驱动并完成 USB-CAN 收发
linux·rk3588·linux驱动·socketcan·gs_usb·usb-can
2601_9622998810 小时前
Linux执行Python脚本方法
linux·python·脚本·解释器·shebang
snow@li10 小时前
服务器运维:Linux命令速查手册(全景梳理)
linux·服务器·网络
handler0110 小时前
【Linux】线程与虚拟内存的底层世界
linux·运维·服务器·c++·线程·虚拟地址空间·共享内存
新时代牛马10 小时前
实时 Linux:PREEMPT_RT、延迟来源与观测
linux·运维·服务器
微露清风11 小时前
# Linux 进程全景:从 PCB 到 O(1) 调度队列
linux·服务器
H_oRIZoN_11 小时前
Linux入门DAY38(手写 HTTP 客户端抓取天气 + 简易 HTTP 服务器实现元器件 Web 系统)
linux·linux应用编程
fangjianj11 小时前
windows环境反弹shell
linux·windows·web安全
明月不会写代码11 小时前
Ubuntu 下 Kitty 终端配置记录
linux·ubuntu