在文件 linux-3.10/include/linux/semaphore.h中定义了信号量,定义如下:
/* Please don't access any members of this structure directly */ struct semaphore { raw_spinlock_t lock; // 其中lock为自旋锁,放到这里为了保护count的原子增加 unsigned int count; // 无符号数count为竞争信号量 struct list_head wait_list; // wait_list为等待此信号量的进程链表 }; 其中struct list_head结构体是定义在文件linux-3.10/include/linux/types.h中,用于连接信号量链表,具体定义如下: struct list_head { struct list_head *next, *prev; }; 信号量最核心的结构体raw_spinlock_t是和CPU相关的,这里以X86为例来介绍,该结构体定义在文件linux-3.10/include/linux/spinlock_types.h中 typedef struct raw_spinlock { arch_spinlock_t raw_lock; #ifdef CONFIG_GENERIC_LOCKBREAK unsigned int break_lock; #endif #ifdef CONFIG_DEBUG_SPINLOCK unsigned int magic, owner_cpu; void *owner; #endif #ifdef CONFIG_DEBUG_LOCK_ALLOC struct lockdep_map dep_map; #endif } raw_spinlock_t; 补充说明一下,在头文件semaphore.h包含头文件spinlock.h,而头文件spinlock.h中又包含头文件spinlock_types.h,因此semaphore.h文件可以直接调用到spinlock_types.h中的定义 接下来是结构体arch_spinlock_t的定义,从名字上看,就知道和CPU相关,注意到在linux-3.10/include/linux/spinlock_types.h文件中有如下宏定义: #if defined(CONFIG_SMP) #include <asm/spinlock_types.h> #else #include <linux/spinlock_types_up.h> #endif 由于现在都是多核CPU,即#if defined(CONFIG_SMP)为真,选择文件linux-3.10/arch/x86/include/asm/spinlock_types.h为定义的头文件,查看该文件中arch_spinlock_t的定义: typedef struct arch_spinlock { union { __ticketpair_t head_tail; struct __raw_tickets { __ticket_t head, tail; } tickets; }; } arch_spinlock_t; 其中__ticketpair_t和__ticket_t根据CPU的NR定义为对应的8位或16位无符号整型数 #if (CONFIG_NR_CPUS < 256) typedef u8 __ticket_t; typedef u16 __ticketpair_t; #else typedef u16 __ticket_t; typedef u32 __ticketpair_t; #endif
在文件 linux-3.10/include/linux/semaphore.h中实现信号量的初始化,具体代码如下:
静态初始化
#define __SEMAPHORE_INITIALIZER(name, n) \ { \ .lock = __RAW_SPIN_LOCK_UNLOCKED((name).lock), \ .count = n, \ .wait_list = LIST_HEAD_INIT((name).wait_list), \ } #define DEFINE_SEMAPHORE(name) \ struct semaphore name = __SEMAPHORE_INITIALIZER(name, 1)
从上边代码可以看出,这种初始化只需要我们在编程的时候直接使用一条语句DEFINE_SEMAPHORE(name),就可以完成声明与初始化,但是只能把信号量初始化为1。
2. 动态初始化
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); }
从上边代码可以知道, 需要先定义一个信号量变量,然后在调用sema_init函数初始化该信号量,这里就可以把信号量初始化为0或者1。
void down(struct semaphore *sem) { unsigned long flags; //保存目前CPU中断位信息 raw_spin_lock_irqsave(&sem->lock, flags); //获取自旋锁,禁止中断和保存中断位信息,避免中断造成死锁 if (likely(sem->count > 0)) //likely是gun编译器优化的宏,代表很有可能是真的意思,相反的宏为unlikely, //具体定义在linux-3.10/include/linux/compiler.h文件中 sem->count--; //获取信号量 else __down(sem); //等待获取信号量 raw_spin_unlock_irqrestore(&sem->lock, flags); //释放自旋锁并使能中断,并恢复flags中保存的中断位信息 } __down(sem)的实现方式: 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.task = task; waiter.up = false; for (;;) { if (signal_pending_state(state, task)) goto interrupted; if (unlikely(timeout <= 0)) goto timed_out; __set_task_state(task, 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; } 这里的__down_common根据不同的参数实现down类获取信号量的方式,首先判断是否有信号中断和超时退出条件,如果都不满足这些条件,对应的工作队列进入休眠状态,等待下一次唤醒。具体进入休眠的位置是在timeout = schedule_timeout(timeout);中,具体实现如下: signed long __sched schedule_timeout(signed long timeout) { struct timer_list timer; unsigned long expire; switch (timeout) { case MAX_SCHEDULE_TIMEOUT: schedule(); //切换到其他进程,进入休眠 goto out; ..... }
从上边代码可以知道,如果信号量没有被其他进程占有,则直接成功获取到信号量,即信号量计数count自己减,完成信号量的获取。如果已经有其他进程已经获取到信号量,则请求信号量的进程就进入休眠,等待占有信号量的进程执行完后唤醒其中等待的进程。注意,在down类获取信号量函数中,有信号中断和操作退出类,在它们返回非零值的时候,是没有获取到信号量的。
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); } __up(sem)的实现方式: 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); }
从上面代码可以看出,如果等待队列中只有当前进程存在,直接将信号量增加即释放了信号量。如果有多个进行在等待获取信号量,则唤醒一个其他的进程并让换醒的进程拥有信号量(实际上信号量count是没有发生变化的),直到最后一个等待信号量的进程执行完后,信号量count在自加来释放信号量。