android 驅動中的併發和競爭——semaphore(訊號量)
在現代的Linux系統中,有非常多的併發源,因此而帶來競爭情況,競爭來源於不同的程序對同一共享資源的同時存取。而Linux系統提供了一系列的處理併發和資源競爭的方法,下面介紹幾個:
1、semaphore(訊號量)
訊號量的使用類似於一對P、V函式,通常一個想進入臨界區的程序會呼叫P函式,返回值是可用的資源數,如果這個數大於0,負責記錄資源數的變數減1程序繼續,相反則程序需要等待資源被釋放。而在程序釋放資源的時候會呼叫V函式,負責記錄資源數的變數加1,說明多一個空閒的資源可供程序呼叫。
訊號量與P、V函式不同的事,他通常被用作互斥,所謂互斥就是阻止多個程序在同一臨界區內執行,他的值初始化為1,並且在規定的時間內只能允許單個程序或執行緒持有,相當於加了一個控制互斥的鎖。
先看一下定義android\kernel\include\linux\Senaphore.h:
struct semaphore {
raw_spinlock_tlock; //鎖
unsigned int count; //記錄資源數的變數
struct list_headwait_list; //等待該資源的程序列表
};
定義並初始化一個semaphore:
#define DEFINE_SEMAPHORE(name)struct semaphore name = __SEMAPHORE_INITIALIZER(name, 1)
該函式用作定義一個名為“name”的semaphore,
並呼叫__SEMAPHORE_INITIALIZER(name, n)
再看__SEMAPHORE_INITIALIZER(name, n)定義:
#define __SEMAPHORE_INITIALIZER(name, n)
{
.lock = __RAW_SPIN_LOCK_UNLOCKED((name).lock),
.count = n,
.wait_list = LIST_HEAD_INIT((name).wait_list),
}
還有一種方法是對已經存在的semaphore結構體直接初始化:
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); }
到此,關於semaphore的定義和初始化完成,下面看看怎麼使用。
(1)過時的請求函式
void down(struct semaphore *sem)
{
unsigned long flags;
raw_spin_lock_irqsave(&sem->lock, flags);
if (likely(sem->count > 0))
sem->count--;
else
__down(sem);
raw_spin_unlock_irqrestore(&sem->lock, flags);
}
這個函式用來請求一個訊號量(其實就是這個訊號量所標識的資源),
如果沒有可用的資源就把準備呼叫這個資源的程序“睡眠”,直到有可用的資源釋放出來。
看程式碼,如果請求的資源可用就把計數變數count減1,表示該資源被佔用,程序繼續;
如果請求的資源不可用就呼叫__down()把當前的程序掛到該semaphore的等待列表裡。
static noinline void __sched __down(struct semaphore *sem)
{
__down_common(sem, TASK_UNINTERRUPTIBLE, MAX_SCHEDULE_TIMEOUT);
}
不過這個函式已經過時,很少使用。
(2)可中斷的請求函式
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;
}
這個函式用來請求一個訊號量(其實就是這個訊號量所標識的資源),
如果沒有可用的資源就把準備呼叫這個資源的程序“睡眠”,不過這個“睡眠”是可以中斷的。
當“睡眠”被中斷的時候,函式返回一個-EINTR訊號,若函式成功請求到訊號量則返回0,程序繼續。
看程式碼,如果請求的資源可用就把計數變數count減1,表示該資源被佔用,返回0程序繼續;
如果請求的資源不可用就呼叫__down_interruptible()把當前的程序掛到該semaphore的等待列表裡,
返回一個非0值。
static noinline int __sched __down_interruptible(struct semaphore *sem)
{
return __down_common(sem, TASK_INTERRUPTIBLE, MAX_SCHEDULE_TIMEOUT);
}
(3)可結束程序的請求函式
int down_killable(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_killable(sem);
raw_spin_unlock_irqrestore(&sem->lock, flags);
return result;
}
這個函式用來請求一個訊號量(其實就是這個訊號量所標識的資源),
如果沒有可用的資源就把準備呼叫這個資源的程序“睡眠”,不過這個“睡眠”是可以被殺死的。
當“睡眠”被致命訊號kill掉的時候,函式返回一個-EINTR訊號,若函式成功請求到訊號量則返回0,程序繼續。
看程式碼,如果請求的資源可用就把計數變數count減1,表示該資源被佔用,返回0程序繼續;
如果請求的資源不可用就呼叫__down_killable()把當前的程序掛到該semaphore的等待列表裡,
返回一個非0值。
static noinline int __sched __down_killable(struct semaphore *sem)
{
return __down_common(sem, TASK_KILLABLE, MAX_SCHEDULE_TIMEOUT);
}
(4)不會“睡眠”,立即返回的請求函式
int down_trylock(struct semaphore *sem)
{
unsigned long flags;
int count;
raw_spin_lock_irqsave(&sem->lock, flags);
count = sem->count - 1;
if (likely(count >= 0))
sem->count = count;
raw_spin_unlock_irqrestore(&sem->lock, flags);
return (count < 0);
}
這個函式用來請求一個訊號量(其實就是這個訊號量所標識的資源),若請求成功則函式返回0,否則函式返回1。
程序不會等待資源。
(5)可以設定超時返回的請求函式
int down_timeout(struct semaphore *sem, long jiffies)
{
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, jiffies);
raw_spin_unlock_irqrestore(&sem->lock, flags);
return result;
}
這個函式用來請求一個訊號量(其實就是這個訊號量所標識的資源),
如果沒有可用的資源就把準備呼叫這個資源的程序“睡眠”,不過這個“睡眠”是有時間限制的。
當限制時間內沒有請求到資源時,函式返回一個-EINTR訊號,若函式成功請求到訊號量則返回0,程序繼續。
看程式碼,如果請求的資源可用就把計數變數count減1,表示該資源被佔用,返回0程序繼續;
如果請求的資源不可用就呼叫__down_timeout()把當前的程序掛到該semaphore的等待列表裡,
返回一個非0值。
static noinline int __sched __down_timeout(struct semaphore *sem, long jiffies)
{
return __down_common(sem, TASK_UNINTERRUPTIBLE, jiffies);
}
現在看一下(1)(2)(3)(5)中,當資源不可用(即semaphore訊號量請求不到時)呼叫的__down_common()函式:
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 = 0;
for (;;) {
if (signal_pending_state(state, task))
goto interrupted;
if (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;
}
根據狀態引數和時間限制判斷是否需要把當前執行緒新增到等待佇列或者是移除。
最後看一下訊號量的釋放函式:
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()的時候,先判斷該semaphore->wait_list(等待這個訊號量的等待佇列)是否為空,
如果為空則說明當前沒有等待使用該訊號量的程序,令semaphore->count++,說明可用資源數加1;
如果等待佇列不為空則呼叫__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 = 1;
wake_up_process(waiter->task);
}
至此,關於訊號量的使用告一段落,下一篇分析如何使用Completions 機制。