信号量实现

信号量定义分析

在文件 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中实现信号量的初始化,具体代码如下:

  1. 静态初始化

#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。

信号量操作分析
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类获取信号量函数中,有信号中断和操作退出类,在它们返回非零值的时候,是没有获取到信号量的。

2. 释放信号量
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在自加来释放信号量。

你可能感兴趣的:(信号量)