Linux内核原语(八)——信号量(semaphore)

 

 

Linux内核原语(八)——信号量(semaphore)

小狼@http://blog.csdn.net/xiaolangyangyang

 


Linux Kernel 除了提供了自旋锁,还提供了睡眠锁,信号量就是一种睡眠锁。信号量的特点是,如果一个任务试图获取一个已经被占用的信号量,他会被推入等待队列,让其进入睡眠。此刻处理器重获自由,去执行其他的代码。当持有的信号量被释放,处于等待队列的任务将被唤醒,并获取到该信号量。

从信号量的睡眠特性得出一些结论:

  • 由于竞争信号量的时候,未能拿到信号的进程会进入睡眠,所以信号量可以适用于长时间持有。
  • 而且信号量不适合短时间的持有,因为会导致睡眠的原因,维护队列,唤醒,等各种开销,在短时间的锁定某对象,反而比忙等锁的效率低。
  • 由于睡眠的特性,只能在进程上下文进行调用,无法再中断上下文中使用信号量。
  • 一个进程可以在持有信号量的情况下去睡眠(可能并不需要,这里只是假如),另外的进程尝试获取该信号量时候,不会死锁。
  • 期望去占用一个信号量的同时,不允许持有自旋锁,因为企图去获取信号量的时候,可能导致睡眠,而自旋锁不允许睡眠。


在有一些特定的场景,自旋锁和信号量没得选,比如中断上下文,只能用自旋锁,比如需要要和用户空间做同步的时候,代码需要睡眠,信号量是唯一选择。如果有的地方,既可以选择信号量,又可以选自旋锁,则需要根据持有锁的时间长短来进行选择。理想情况下是,越短的时间持有,选择自旋锁,长时间的适合信号量。与此同时,信号量不会关闭调度,他不会对调度造成影响。

信号量允许多个锁持有者,而自旋锁在一个时刻,最多允许一个任务持有。信号量同时允许的持有者数量可以在声明信号的时候指定。绝大多数情况下,信号量允许一个锁的持有者,这种类型的信号量称之为二值信号量,也就是互斥信号量。

一个任务要想访问共享资源,首先必须得到信号量,获取信号量的操作将把信号量的值减1,若当前信号量的值为负数,表明无法获得信号量,该任务必须挂起在该信号量的等待队列等待该信号量可用;若当前信号量的值为非负数,表示可以获得信号量,因而可以立刻访问被该信号量保护的共享资源。

当任务访问完被信号量保护的共享资源后,必须释放信号量,释放信号量通过把信号量的值加1实现,如果信号量的值为非正数,表明有任务等待当前信号量,因此它也唤醒所有等待该信号量的任务。

 

信号量的操作

信号量相关的东西放置到了: include/linux/semaphore.h 文件

初始化一个信号量有两种方式:

struct semaphore sem;
 
sema_init(&sem, val);
DEFINE_SEMAPHORE(sem)

内核针对信号量提供了一组操作接口:

函数定义功能说明
sema_init(struct semaphore *sem, int val)初始化信号量,将信号量计数器值设置val
down(struct semaphore *sem)获取信号量,不建议使用此函数,因为是 UNINTERRUPTABLE 的睡眠
down_interruptible(struct semaphore *sem)可被中断地获取信号量,如果睡眠被信号中断,返回错误-EINTR
down_killable (struct semaphore *sem)可被杀死地获取信号量。如果睡眠被致命信号中断,返回错误-EINTR
down_trylock(struct semaphore *sem)尝试原子地获取信号量,如果成功获取,返回0,不能获取,返回1
down_timeout(struct semaphore *sem, long jiffies)在指定的时间jiffies内获取信号量,若超时未获取,返回错误-ETIME
up(struct semaphore *sem)释放信号量sem

注意:down_interruptible 接口,在获取不到信号量的时候,该任务会进入 INTERRUPTABLE 的睡眠,但是 down() 接口会导致进入 UNINTERRUPTABLE 的睡眠,down 用的较少。

 

信号量的实现

1. 信号量的结构:

struct semaphore {
    raw_spinlock_t        lock;
    unsigned int        count;
    struct list_head    wait_list;
};

信号量用结构semaphore描述,它在自旋锁的基础上改进而成,它包括一个自旋锁、信号量计数器和一个等待队列。用户程序只能调用信号量API函数,而不能直接访问信号量结构。

 

2. 初始化函数sema_init

#define __SEMAPHORE_INITIALIZER(name, n)                \
{                                    \
    .lock        = __RAW_SPIN_LOCK_UNLOCKED((name).lock),    \
    .count        = n,                        \
    .wait_list    = LIST_HEAD_INIT((name).wait_list),        \
}
 
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);
}

初始化了信号量中的 spinlock 结构,count 计数器和初始化链表。

 

3. 可中断获取信号量函数down_interruptible

static noinline int __sched __down_interruptible(struct semaphore *sem)
{
    return __down_common(sem, TASK_INTERRUPTIBLE, MAX_SCHEDULE_TIMEOUT);
}
 
int down_interruptible(struct semaphore *sem)
{
    unsigned long flags;
    int result = 0;
 
    raw_spin_lock_irqsave(&sem->lock, flags);
    if (likely(sem->count > 0))
        sem->count--;
    else
        result = __down_interruptible(sem);
    raw_spin_unlock_irqrestore(&sem->lock, flags);
 
    return result;
}

down_interruptible 进入后,获取信号量获取成功,进入临界区,否则进入 __down_interruptible->__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;
}

加入到等待队列,将状态设置成为 TASK_INTERRUPTIBLE , 并设置了调度的 Timeout : MAX_SCHEDULE_TIMEOUT

在调用了 schedule_timeout,使得进程进入了睡眠状态。

 

4. 释放信号量函数 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:

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);
}

取出队列中的元素,进行唤醒操作。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值