- 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是因为被信号中断 - 尝试获取当前信号量的锁
- 查看线程是否被唤醒
-
可见, 信号量和互斥量不同, 互斥量是保护共享资源的访问, 而信号量自己就是共享资源, 保护的是信号量值本身, 更多的作用是控制线程之间的执行顺序