轉(zhuǎn)自 |嵌入式藝術(shù) 除了原子操作,中斷屏蔽,自旋鎖以及自旋鎖的衍生鎖之外,在Linux內(nèi)核中還存在著一些其他同步互斥的手段。
下面我們來理解一下信號量,互斥體,完成量機(jī)制。
1、信號量介紹
信號量(Semaphore)是操作系統(tǒng)中最典型的用于同步和互斥的手段,信號量的值可以是0、1或者n。信號量與操作系統(tǒng)中的經(jīng)典概念PV操作對應(yīng)。 P(Produce):
將信號量S的值減1,即S=S-1;
如果S≥0,則該進(jìn)程繼續(xù)執(zhí)行;否則該進(jìn)程置為等待狀態(tài),排入等待隊(duì)列。
V(Vaporize):
將信號量S的值加1,即S=S+1;
如果S>0,喚醒隊(duì)列中等待信號量的進(jìn)程。
信號量核心思想: 信號量的操作,更適合去解決生產(chǎn)者和消費(fèi)者的問題,比如:我做出來一個(gè)餅,你才能吃一個(gè)餅;如果我沒做出來,你就先釋放CPU去忙其他的事情。
2、信號量的API
struct semaphore sem; // 定義信號量
void sema_init(struct semaphore *sem, int val); // 初始化信號量,并設(shè)置信號量sem的值為val。
void down(struct semaphore * sem); // 獲得信號量sem,它會導(dǎo)致睡眠,因此不能在中斷上下文中使用。
int down_interruptible(struct semaphore * sem); // 該函數(shù)功能與down類似,不同之處為,因?yàn)閐own()進(jìn)入睡眠狀態(tài)的進(jìn)程不能被信號打斷,但因?yàn)閐own_interruptible()進(jìn)入睡眠狀態(tài)的進(jìn)程能被信號打斷,信號也會導(dǎo)致該函數(shù)返回,這時(shí)候函數(shù)的返回值非0
int down_trylock(struct semaphore * sem); // 嘗試獲得信號量sem,如果能夠立刻獲得,它就獲得該信號量并返回0,否則,返回非0值。它不會導(dǎo)致調(diào)用者睡眠,可以在中斷上下文中使用。
void up(struct semaphore * sem); // 釋放信號量,喚醒等待者。
由于新的Linux內(nèi)核傾向于直接使用mutex作為互斥手段,信號量用作互斥不再被推薦使用。 信號量也可以用于同步,一個(gè)進(jìn)程A執(zhí)行down()等待信號量,另外一個(gè)進(jìn)程B執(zhí)行up()釋放信號量,這樣進(jìn)程A就同步地等待了進(jìn)程B。
3、API實(shí)現(xiàn)
3.1 semaphore
struct semaphore {
raw_spinlock_t lock;
unsigned int count;
struct list_head wait_list;
};
結(jié)構(gòu)體名稱:semaphore
文件位置:include/linux/semaphore.h
主要作用:用于定義一個(gè)信號量。
raw_spinlock_t:信號量結(jié)構(gòu)體也使用了自旋鎖,避免互斥。
count:表示信號量的計(jì)數(shù)器,表示資源的數(shù)量
struct list_head wait_list: 這是一個(gè)鏈表頭,用于管理等待信號量的線程。當(dāng)信號量的 count 等于0,即沒有可用資源時(shí),等待信號量的線程會被加入到這個(gè)鏈表中,以便在資源可用時(shí)進(jìn)行喚醒。
3.2 sema_init
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);
}
#define __SEMAPHORE_INITIALIZER(name, n)
{
.lock = __RAW_SPIN_LOCK_UNLOCKED((name).lock),
.count = n,
.wait_list = LIST_HEAD_INIT((name).wait_list),
}
#define LIST_HEAD_INIT(name) { &(name), &(name) }
函數(shù)名稱:sema_init
文件位置:include/linux/semaphore.h
主要作用:初始化信號量,并設(shè)置信號量sem的值為val。
實(shí)現(xiàn)流程:
使用__SEMAPHORE_INITIALIZER宏定義來初始化信號量
使用__RAW_SPIN_LOCK_UNLOCKED宏定義來初始化自旋鎖
直接將val賦值給信號量的值count
使用LIST_HEAD_INIT來初始化一個(gè)鏈表
#defineLIST_HEAD_INIT(name){&(name),&(name)}
該宏接受一個(gè)參數(shù) name,并返回一個(gè)結(jié)構(gòu)體對象。這個(gè)對象有兩個(gè)成員 next 和 prev,分別指向 name 本身。
這樣,當(dāng)我們使用該宏來初始化鏈表頭節(jié)點(diǎn)時(shí),會得到一個(gè)擁有 next 和 prev 成員的結(jié)構(gòu)體對象。其中 next 和 prev 成員都指向該結(jié)構(gòu)體對象本身。
這種初始化方式可以用于創(chuàng)建一個(gè)空的雙向鏈表,因?yàn)樵诔跏紶顟B(tài)下,鏈表頭節(jié)點(diǎn)的 next 和 prev 指針都指向自身,表示鏈表為空。
3.3 down
/**
down - acquire the semaphore
@sem: the semaphore to be acquired
Acquires the semaphore. If no more tasks are allowed to acquire the
semaphore, calling this function will put the task to sleep until the
semaphore is released.
Use of this function is deprecated, please use down_interruptible() or
down_killable() instead.
*/
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);
}
EXPORT_SYMBOL(down);
static noinline void __sched __down(struct semaphore *sem)
{
__down_common(sem, TASK_UNINTERRUPTIBLE, MAX_SCHEDULE_TIMEOUT);
}
/*
Because this function is inlined, the 'state' parameter will be
constant, and thus optimised away by the compiler. Likewise the
'timeout' parameter for the cases without timeouts.
*/
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;
}
函數(shù)名稱:down
文件位置:kernel/locking/semaphore.c
主要作用:獲取信號量,如果信號量的值大于0,則消耗一個(gè);如果不存在,則讓線程進(jìn)入休眠狀態(tài)并等待信號量被釋放。
函數(shù)調(diào)用流程:
down(kernel/locking/semaphore.c)
|--> raw_spin_lock_irqsave // 獲取鎖,并保存中斷信息
||-> sem->count--; // 如果sem->count信號量存在,則消耗一個(gè)
|-> __down // 如果sem->count信號量不存在,則進(jìn)入休眠狀態(tài)
|--> __down_common
|--> list_add_tail // 將當(dāng)前線程添加到信號量的等待鏈表中,表示當(dāng)前線程正在等待信號量。
|--> __set_current_state// 設(shè)置線程為休眠狀態(tài)
|--> raw_spin_unlock_irq// 釋放自旋鎖,讓其他線程可用
|--> schedule_timeout // 讓線程進(jìn)入睡眠狀態(tài),等待信號量釋放或超時(shí)。
|--> raw_spin_lock_irq // 重新獲取自旋鎖,繼續(xù)執(zhí)行后續(xù)操作
|--> raw_spin_unlock_irqrestore // 釋放鎖,并恢復(fù)中斷信息
實(shí)現(xiàn)流程:
獲取信號量時(shí),先使用raw_spin_lock_irqsave和raw_spin_unlock_irqrestore將信號量的操作包裹起來,避免競態(tài)發(fā)生。
然后對sem->count判斷,如果信號量大于0,就消耗一個(gè),否則的話,將當(dāng)前線程設(shè)置為休眠態(tài)
調(diào)用__down_common接口,默認(rèn)將當(dāng)前線程設(shè)置為TASK_UNINTERRUPTIBLE中斷不可打斷狀態(tài),并設(shè)置最大超時(shí)時(shí)間MAX_SCHEDULE_TIMEOUT
struct semaphore_waiter waiter:創(chuàng)建waiter結(jié)構(gòu)體,表示當(dāng)前線程的狀態(tài)
調(diào)用__set_current_state接口,設(shè)置當(dāng)前線程的狀態(tài)信息,即TASK_UNINTERRUPTIBLE
調(diào)用schedule_timeout,讓該線程讓出CPU,進(jìn)入休眠態(tài),并且在前面加上raw_spin_unlock_irq保證其他線程可以正常使用信號量。
當(dāng)線程時(shí)間片到時(shí),獲取CPU,并調(diào)用raw_spin_lock_irq,獲取鎖,來防止競態(tài)發(fā)生。
3.4 up
/**
up - release the semaphore
@sem: the semaphore to release
Release the semaphore. Unlike mutexes, up() may be called from any
context and even by tasks which have never called down().
*/
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);
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);
}
函數(shù)名稱:up
文件位置:kernel/locking/semaphore.c
主要作用:獲取信號量,如果信號量的值大于0,則消耗一個(gè);如果不存在,則讓線程進(jìn)入休眠狀態(tài)并等待信號量被釋放。
實(shí)現(xiàn)流程:
相信分析完down后,up也變得很簡單
釋放信號量時(shí),先使用raw_spin_lock_irqsave和raw_spin_unlock_irqrestore將信號量的操作包裹起來,避免競態(tài)發(fā)生。
然后對sem->wait_list判斷,如果其為空,說明沒有等待的線程,直接將sem->count自增,如果有等待的線程,則喚醒下一個(gè)線程。
調(diào)用wake_up_process來喚醒線程
4、總結(jié)
信號量較為簡單,一般常用來解決生產(chǎn)者和消費(fèi)者的問題,其主要還是通過自旋鎖來實(shí)現(xiàn)互斥的作用,通過鏈表來管理等待隊(duì)列的線程信息,通過變量來代表資源的數(shù)量。
審核編輯:劉清
-
計(jì)數(shù)器
+關(guān)注
關(guān)注
32文章
2291瀏覽量
96425 -
LINUX內(nèi)核
+關(guān)注
關(guān)注
1文章
317瀏覽量
22411 -
信號量
+關(guān)注
關(guān)注
0文章
53瀏覽量
8573 -
自旋鎖
+關(guān)注
關(guān)注
0文章
11瀏覽量
1681
原文標(biāo)題:信號量實(shí)現(xiàn)原理
文章出處:【微信號:strongerHuang,微信公眾號:strongerHuang】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
FreeRTOS信號量使用教程

信號量是什么?信號量怎么運(yùn)作
如何用VxWorks的信號量機(jī)制實(shí)現(xiàn)任務(wù)同步
簡單介紹信號與信號量

uCOS信號量源碼的詳細(xì)資料分析

Linux信號量(2):POSIX 信號量
LINUX內(nèi)核的信號量設(shè)計(jì)與實(shí)現(xiàn)
LINUX內(nèi)核的信號量設(shè)計(jì)與實(shí)現(xiàn)
FreeRTOS的二值信號量
Free RTOS的計(jì)數(shù)型信號量

使用Linux信號量實(shí)現(xiàn)互斥點(diǎn)燈

評論