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