down_timeout接口分析


接口简介

down_timeout接口,在指定时间内获取信号量。


整体流程:

用户尝试获取信号量

  1. 如果资源充足(count>0),则count自减,代表占用一个资源;
  2. 如果资源不足(count<=0),则进行睡眠等待;
    1. 如果直到超时,资源还没有被释放,则返回 -ETIME;
    2. 如果在等待期间线程被中断,则返回 -EINTR;
    3. 如果在超时前获取到了资源,则返回0。

代码实现

先看下接口的代码实现:

// kernel/locking/semaphore.c
int down_timeout(struct semaphore *sem, long timeout)
{
    unsigned long flags;
    int result = 0;

    raw_spin_lock_irqsave(&sem->lock, flags);
    if (likely(sem->count > 0))
        sem->count--;
    else
        result = __down_timeout(sem, timeout);
    raw_spin_unlock_irqrestore(&sem->lock, flags);

    return result;
}
EXPORT_SYMBOL(down_timeout);

总结下就是两句话:

  1. sem->count > 0 时,sem->count--
  2. sem->count <= 0 时, 等待资源

两个疑问

1. 如何对sem进行占用

其实获取资源的实现很简单,就是看当前资源是否有余量。
先看下semaphore结构的实现:

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

总结起来就是看count值的大小:

大于0,则利用自减代表被当前线程占用一个资源
小于等于0,则等待资源。

2. 为什么else分支没有count--

2.1 else分支存在的问题

我们看上面down_timeout的总结:

  1. sem->count > 0 sem->count--
  2. sem->count <= 0 等待资源(好像对sem->count什么都没有做)

这里就比较有意思了,为什么不是等到资源后,保持与if分支一致,对 sem->count--呢?

我们继续深入看下else分支在做什么:

// else分支:
    result = __down_timeout(sem, timeout);

// __down_timeout实现
static noinline int __sched __down_timeout(struct semaphore *sem, long timeout)
{
    return __down_common(sem, TASK_UNINTERRUPTIBLE, timeout);
}

// __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;
}

再上面的流程总结一下:

  1. 先使用list_add_tail把当前的线程压入等待队列
  2. 死循环等待schedule_timeout超时

2.2 up接口的实现

我们先把上面的思路压栈,去看下另外一个接口:up

有down去申请资源,那必然要有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);
}
EXPORT_SYMBOL(up);

上面的流程再总结下:

  1. 如果队列为空,sem->count++
  2. 如果队列非空,则调用 __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);
}

总结:

  1. 取出处在等待队列队首的semaphore_waiter
  2. 从队列中删除这个semaphore_waiter
  3. 激活当前进程

回顾上面的__down_common,这里的代码正好跟上面__down_common实现对应上。
因为__down_common是入队去睡眠,__up是出队并激活。

留意这里的激活流程,也并没有sem->count++的操作。所以这里又跟down_timeout的else分支正好对应上。




举个粟子:

故事背景:
现在排队进屋,由于小屋面积有限,只能容纳固定人数(sem->count)。
而你是守门员,每进一个人,你需要把可容纳人数减1(sem->count--)。

  1. 当人少时
    有的时候进屋的人很少,你又想玩游戏,所以不想在门口一直守着。
    于是你就把几把钥匙(sem->count把)挂在门外,谁想进来就取一把开门,并把钥匙带进屋,谁要离开就再把钥匙挂回去。

  2. 当人多时
    有的时候准备进屋的人太多,外面已经排起一个很长的队伍,这个时候屋里一直是满员的。
    所以此时不应再和之前一样,把钥匙挂出去等别人拿,因为要拿的人就在外面等着呢。
    最省事的方法是当有人出去时,把钥匙留在屋里,直接开门叫队伍最前面的人进来就好了。
    而此时屋外始终是没有挂钥匙的,所以不会有人乱入,人数也就不会超过sem->count。


结语

看完这个故事,再去通读一遍上面的代码,应该就可以理解 up 与 down 两个接口对else分支的定义了。都不需要处理这把钥匙(sem->count)。


不得不说,开源社区的大佬们,是真的厉害。



免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM