首先看看信号量的相关数据结构:
<include/linux/semaphore.h>
struct semaphore { spinlock_t lock; #lock应该是这个信号量的自旋锁 unsigned int count; #count表示的是这个信号量的计数器 struct list_head wait_list; #wait_list顾名思义应该是等待链表了 }; |
信号量的初始化:
<include/linux/semaphore.h>
#define DECLARE_MUTEX(name) \ struct semaphore name = __SEMAPHORE_INITIALIZER(name, 1) #define __SEMAPHORE_INITIALIZER(name, n) \ { \ .lock = __SPIN_LOCK_UNLOCKED((name).lock), \ #初始化自旋锁 .count = n, \ #将信号量计数器赋值为n .wait_list = LIST_HEAD_INIT((name).wait_list), \ #初始化等待队列 } |
也可以用以下方法来初始化信号量:
<include/linux/semaphore.h>
static inline void sema_init(struct semaphore *sem, int val) { static struct lock_class_key __key; *sem = (struct semaphore) __SEMAPHORE_INITIALIZER(*sem, val); lockdep_init_map(&sem->lock.dep_map, "semaphore->lock", &__key, 0); } #define init_MUTEX(sem) sema_init(sem, 1) #define init_MUTEX_LOCKED(sem) sema_init(sem, 0) |
PV操作:
P操作
<kernel/semaphore.c>
void up(struct semaphore *sem) { unsigned long flags; spin_lock_irqsave(&sem->lock, flags); if (likely(list_empty(&sem->wait_list)))#如果等待链表为空,表示没有正在等待此信号量的进程,count++就行 sem->count++; else __up(sem); spin_unlock_irqrestore(&sem->lock, flags); } |
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 = 1; wake_up_process(waiter->task); #唤醒这个进程 } |
此函数从等待列表中移出最前面的那个进程,然后唤醒它
struct semaphore_waiter { struct list_head list; #链表项 struct task_struct *task; #进程结构,也可以叫做进程PCB int up; }; |
这个结构主要就是用来辅助把进程放到信号量的等待队列,后面的V操作会再介绍它
V操作:
<kernel/semaphore.c>
void down(struct semaphore *sem) { unsigned long flags; spin_lock_irqsave(&sem->lock, flags); #要对sem进行操作了,加锁 if (likely(sem->count > 0)) #如果count>0,直接count--就行了 sem->count--; else __down(sem); #调用__down() spin_unlock_irqrestore(&sem->lock, flags); #对sem操作完毕,释放锁 } |
如果count是<=0的,那么调用__down()来把进程放进等待队列里,现在看看__down()是怎么实现的
static noinline void __sched __down(struct semaphore *sem) { __down_common(sem, TASK_UNINTERRUPTIBLE, MAX_SCHEDULE_TIMEOUT); } static inline int __sched __down_common(struct semaphore *sem, long state, long timeout) { struct task_struct *task = current; #得到当前正在运行的进程 struct semaphore_waiter waiter; list_add_tail(&waiter.list, &sem->wait_list); #将waiter加到链表sem->wait_list后面 waiter.task = task; #waiter的task设置成正在运行的这个进程 waiter.up = 0; for (;;) { if (signal_pending_state(state, task)) #如果进程被一个意外的信号中断,直接放弃等待(从等待链表中删除),返回-EINTR错误号 goto interrupted; if (timeout <= 0) #如果等待时间到,从等待链表中删除之,返回-ETIME错误号 goto timed_out; __set_task_state(task, state); #设置进程状态为TASK_UNINTERRUPTIBLE spin_unlock_irq(&sem->lock); timeout = schedule_timeout(timeout); #调度 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; } |
该函数首先申请一个semaphore_waiter结构,
<kernel/semaphore.c>
struct semaphore_waiter { struct list_head list; struct task_struct *task; int up; }; |
然后将这个结构加入到sem信号量的等待链表sem->wait_list中,接着将当前进程task填入waiter.task中,
然后在一个循环中进行进程调度schedule_timeout,每进行一次调度,timeout会减小
如果当前进程task的状态位就绪,则跳转到interrupt,从信号量列表sem-wait_list中删除这个任务对应的节点
如果遇到意外的信号或者等待时间到,从信号量列表sem-wait_list中删除这个任务对应的节点,返回相应的错误号
内核还提供了其他几种V操作:
static noinline int __down_interruptible(struct semaphore *sem);
static noinline int __down_killable(struct semaphore *sem);
static noinline int __down_timeout(struct semaphore *sem, long jiffies);
这里就不一一介绍了
读者也可以参考这篇文章
内核同步机制-信号量