接口簡介
down_timeout接口,在指定時間內獲取信號量。
整體流程:
用戶嘗試獲取信號量
- 如果資源充足(count>0),則count自減,代表占用一個資源;
- 如果資源不足(count<=0),則進行睡眠等待;
- 如果直到超時,資源還沒有被釋放,則返回 -ETIME;
- 如果在等待期間線程被中斷,則返回 -EINTR;
- 如果在超時前獲取到了資源,則返回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);
總結下就是兩句話:
- sem->count > 0 時,sem->count--
- 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的總結:
- sem->count > 0 sem->count--
- 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;
}
再上面的流程總結一下:
- 先使用list_add_tail把當前的線程壓入等待隊列
- 死循環等待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);
上面的流程再總結下:
- 如果隊列為空,sem->count++
- 如果隊列非空,則調用 __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);
}
總結:
- 取出處在等待隊列隊首的semaphore_waiter
- 從隊列中刪除這個semaphore_waiter
- 激活當前進程
回顧上面的__down_common,這里的代碼正好跟上面__down_common實現對應上。
因為__down_common是入隊去睡眠,__up是出隊並激活。
留意這里的激活流程,也並沒有sem->count++的操作。所以這里又跟down_timeout的else分支正好對應上。
舉個粟子:
故事背景:
現在排隊進屋,由於小屋面積有限,只能容納固定人數(sem->count)。
而你是守門員,每進一個人,你需要把可容納人數減1(sem->count--)。
-
當人少時
有的時候進屋的人很少,你又想玩游戲,所以不想在門口一直守着。
於是你就把幾把鑰匙(sem->count把)掛在門外,誰想進來就取一把開門,並把鑰匙帶進屋,誰要離開就再把鑰匙掛回去。 -
當人多時
有的時候准備進屋的人太多,外面已經排起一個很長的隊伍,這個時候屋里一直是滿員的。
所以此時不應再和之前一樣,把鑰匙掛出去等別人拿,因為要拿的人就在外面等着呢。
最省事的方法是當有人出去時,把鑰匙留在屋里,直接開門叫隊伍最前面的人進來就好了。
而此時屋外始終是沒有掛鑰匙的,所以不會有人亂入,人數也就不會超過sem->count。
結語
看完這個故事,再去通讀一遍上面的代碼,應該就可以理解 up 與 down 兩個接口對else分支的定義了。都不需要處理這把鑰匙(sem->count)。
不得不說,開源社區的大佬們,是真的厲害。
