- sem结构体:
struct semaphore {
raw_spinlock_t lock;
unsigned int count;
struct list_head wait_list;
};
struct semaphore_waiter {
struct list_head list;
struct task_struct *task;
bool up;
};
-
sem_post和sem_wait就是在加减count
-
在加减count前后, 会获取和释放lock
-
wait_list保存信号量上等待的线程, 元素是
semaphore_waiter对象, 通过list_head双向链表连接 -
task:
task_struct类似于一个抽象类, 保存的是当前线程的运行信息 -
up: 如果等待队列只有一个线程, 那么__up函数唤醒该线程时设置up为true, 避免其在__down_common中一直阻塞
-
sem_post的源码对应的是up, 如下:
void up(struct semaphore *sem) { unsigned long flags; raw_spin_lock_irqsave(&sem->lock, flags); if (likely(list_empty(&sem->wait_list))) sem->count++; else __up(sem); raw_spin_unlock_irqrestore(&sem->lock, flags); }- 先获取锁
- 如果当前信号量的等待队列是空, 直接对count进行加操作
- 否则调用__up函数
- 释放锁
- __up函数的源码如下:
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); }- 取信号量的等待队列中的第一个线程
- 移除等待队列中该线程元素
- 系统调用
wake_up_process激活waiter中保存的线程
-
sem_wait的源码对应的是down系列, 包括down_killable, down_interruptible, down_timeout, down_trylock, 对于前面三个, 底层实现是__down_common函数, 只是参数略有不同, __down_common源码如下:
static inline int __sched __down_common(struct semaphore *sem, long state, long timeout) { struct semaphore_waiter waiter; list_add_tail(&waiter.list, &sem->wait_list); waiter.task = current; waiter.up = false; 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; }- timed_out, interrupted用来返回超时和被中断
- 创建一个waiter对象, 并添加到信号量等待队列队尾
- 启动阻塞循环, 退出循环的条件有:
- 查看当前线程的未决信号, 如果有就跳转到interrupted并返回
- 查看当前的剩余等待时间是否<=0, 如果是就跳转到超时并返回
- 某线程up了信号量, 并唤醒了等待队列的第一个线程, 而当前线程就是等待队列的第一个线程, 则返回0, 表示获取信号量(减信号量值)成功
- 重新设置当前线程状态
- 解锁
- schedule_timeout让当前线程睡眠timeout, 该函数定义在
kernel\time\time.c中, 返回值0表示超时到期, >0表示剩余超时时间, 一般大于0是因为被信号中断 - 尝试获取当前信号量的锁
- 查看线程是否被唤醒
-
可见, 信号量和互斥量不同, 互斥量是保护共享资源的访问, 而信号量自己就是共享资源, 保护的是信号量值本身, 更多的作用是控制线程之间的执行顺序
本文详细介绍了Linux内核中的信号量结构及其操作函数sem_post和sem_wait。信号量用于控制线程执行顺序,保护信号量值本身。sem_post增加信号量计数,若等待队列为空则直接操作,否则唤醒等待线程;sem_wait在信号量上等待,当信号量值允许时获取信号量,或者在超时、接收信号后返回。信号量与互斥量的主要区别在于,互斥量主要用于保护共享资源,而信号量自身即为一种共享资源。

被折叠的 条评论
为什么被折叠?



