1、上下文和并發(fā)
在討論并發(fā)前,先要了解以下幾個(gè)概念:執(zhí)行流,上下文,共享與臨界等。
什么叫執(zhí)行流:
- 【執(zhí)行流】:有開始有結(jié)束總體順序執(zhí)行的一段代碼 又稱
上下文
。
上下文分類:
- 【任務(wù)上下文】:普通的,具有五種狀態(tài)(就緒態(tài)、運(yùn)行態(tài)、睡眠態(tài)、暫停態(tài)、僵死態(tài)),可被阻塞的上下文。
- 【異常上下文】:異常時(shí)的中斷處理上下文。不能進(jìn)入阻塞狀態(tài)。
\qquad
對于應(yīng)用層編程而言,只有任務(wù)上下文。
\qquad
對于內(nèi)核編程而言,即有任務(wù)上下文也有異常上下文。
什么是共享與臨界:
- 【競態(tài)】:多任務(wù)并行執(zhí)行時(shí),如果在一個(gè)時(shí)刻同時(shí)操作同一個(gè)資源,會(huì)引起資源的錯(cuò)亂,這種錯(cuò)亂情形被稱為競態(tài)
- 【共享資源】:可能會(huì)被多個(gè)任務(wù)同時(shí)使用的資源。
- 【臨界區(qū)】:操作共享資源的代碼段。
因此為了控制競態(tài),就需要有相應(yīng)的“并發(fā)控制機(jī)制”。
- 【并發(fā)控制機(jī)制】:為了解決競態(tài),需要提供一種控制機(jī)制,來避免在同一時(shí)刻使用共享資源,這種機(jī)制被稱為并發(fā)控制機(jī)制
2、并發(fā)控制機(jī)制的分類與使用場景
內(nèi)核中并發(fā)控制機(jī)制分為以下幾類:
- 原子操作類 :不會(huì)被系統(tǒng)包括異常打斷的操作。
- 忙等待類 :通過循環(huán)輪詢的方式,等待資源可用的操作。
- 阻塞類:資源不可用時(shí),進(jìn)入睡眠態(tài)等待資源可用。
并發(fā)控制機(jī)制的使用場景:
- 互斥場景:多個(gè)任務(wù)不能同時(shí)使用同一資源,一個(gè)在用時(shí),其它的處于等待狀態(tài),互斥體現(xiàn)的是一種排它性。一般操作過程如下:
對互斥鎖初始化為可用 --> 對互斥鎖進(jìn)行P操作鎖定 --> 臨界區(qū) --> 對互斥鎖進(jìn)行V操作放開
- 同步場景:多個(gè)并行任務(wù)對資源的有序訪問,同步體現(xiàn)的是一種協(xié)作性。
對互斥鎖初始化為不可用 --> “先行任務(wù)”完成所有操作后 --> “先行方”進(jìn)行V操作釋放資源 --> “后行方”P操作直到鎖定資源 -->“后行方”執(zhí)行操作。
3、并發(fā)控制詳述
3.1 并發(fā)控制機(jī)制–中斷屏蔽
使用原則:
\qquad
當(dāng)一個(gè)中斷服務(wù)程序ISR與被打斷的任務(wù)可能使用相同的資源時(shí)。這時(shí),就需要在被打斷的任務(wù)在進(jìn)入臨界區(qū)之前先進(jìn)行中斷屏蔽,等執(zhí)行完成后再恢復(fù)中斷。
中斷屏蔽相關(guān)的函數(shù)如下:
中斷屏蔽相關(guān)函數(shù) | 使能中斷相關(guān)函數(shù) | |
---|---|---|
local_irq_disable() | loacal_irq_enable() | |
local_irq_save(flags) | loacal_irq_restore(flags) | 涉及cpu的中斷屏蔽字相關(guān) |
local_bh_disable() | local_bh_enable() | 與中斷低半部有關(guān),操作軟中斷 |
注意事項(xiàng)
- 中斷屏蔽后的臨界區(qū)代碼不能占用太長時(shí)間,需要盡快完成。否則中被屏蔽會(huì)引起系統(tǒng)調(diào)度等一系列問題。
- Local_irq_disable()和local_irq_enable()都只能禁止和使能本CPU內(nèi)的中斷,因此,并不能解決SMP多CPU引發(fā)的況態(tài)。因此單獨(dú)使用
- 中斷屏蔽通常不是一種值得推薦的避免況態(tài)的方法,它適合與下面要介紹的自旋鎖聯(lián)合使用。
適用場合:中斷上下文與某任務(wù)共享資源時(shí),或多個(gè)不同優(yōu)先級的中斷上下文間共享資源時(shí)。
3.1.1 函數(shù)詳解
3.1.1.1 local_irq_enable 與 local_irq_disable
local_irq_enable()和local_irq_disable()函數(shù)用于在Linux內(nèi)核中臨時(shí)啟用和禁用本地中斷。
原型為:
#include <asm/irq.h>
void local_irq_enable(void);
void local_irq_disable(void);
這兩個(gè)函數(shù)的作用是:
- local_irq_enable(): 重新啟用本地中斷,允許硬件在CPU上發(fā)送中斷信號。
- local_irq_disable(): 禁用本地中斷,防止硬件在CPU上發(fā)送任何中斷信號。
本地中斷是CPU上除NMI之外的所有外部中斷,比如時(shí)鐘中斷、串口中斷、網(wǎng)卡中斷等。
使用示例:
void do_some_thing(void)
{
local_irq_disable(); // 禁用本地中斷
/*做一些需要防止中斷的操作*/
local_irq_enable(); // 操作完成,重新啟用中斷
}
這兩個(gè)函數(shù)通常用于:
- 臨時(shí)保護(hù)一小段關(guān)鍵代碼或共享資源,防止中斷處理程序干擾。因?yàn)橹袛嗵幚沓绦驎?huì)打斷正常指令流,可能訪問共享資源。
- 在原子操作周圍,確保整個(gè)操作序列不被中斷打斷,從而避免競爭條件。
- 在本地時(shí)鐘節(jié)拍的開頭和結(jié)尾,用于測量某段代碼的執(zhí)行時(shí)間等。
它們會(huì)修改CPU的狀態(tài)寄存器中的IF標(biāo)志位來禁用或重新啟用中斷。在禁用中斷期間,所有的中斷請求都會(huì)被屏蔽,直到再次啟用中斷。
3.1.1.2 local_irq_save 與 local_irq_restore
local_irq_save()和local_irq_restore()函數(shù)也用于在Linux內(nèi)核中臨時(shí)保存和恢復(fù)本地中斷狀態(tài)。
原型為:
#include <asm/irq.h>
unsigned long local_irq_save(void);
void local_irq_restore(unsigned long flags);
這兩個(gè)函數(shù)的作用是:
- local_irq_save(): 禁用本地中斷,并返回此前的中斷狀態(tài)。
- local_irq_restore(): 根據(jù)傳入的flags恢復(fù)之前保存的本地中斷狀態(tài)。
與local_irq_enable()和local_irq_disable()相比,local_irq_save()可以保存當(dāng)前的中斷狀態(tài),并在稍后通過local_irq_restore()恢復(fù)。而local_irq_enable()和local_irq_disable()無法達(dá)到保存與恢復(fù)中斷狀態(tài)的效果。
使用示例:
unsigned long flags;
flags = local_irq_save(); // 禁用中斷并保存狀態(tài)
/*做一些需要防止中斷的操作*/
local_irq_restore(flags); // 操作完成,恢復(fù)之前的中斷狀態(tài)
這兩個(gè)函數(shù)的用途與local_irq_enable()和local_irq_disable()類似,常用于:
- 臨時(shí)保護(hù)關(guān)鍵代碼或共享資源,但需要在稍后恢復(fù)原有的中斷狀態(tài)。
- 實(shí)現(xiàn)復(fù)雜的同步機(jī)制,根據(jù)需要禁用與恢復(fù)中斷。
- 精確測量某段代碼的執(zhí)行時(shí)間,通過保存并恢復(fù)中斷狀態(tài)實(shí)現(xiàn)準(zhǔn)確計(jì)時(shí)。
與local_irq_enable()/disable()相比,這兩個(gè)函數(shù)可以更精細(xì)地控制中斷狀態(tài),實(shí)現(xiàn)根據(jù)條件條件性禁用與恢復(fù)中斷。這在編寫驅(qū)動(dòng)程序和內(nèi)核同步機(jī)制時(shí)非常有用。
3.1.1.3 local_bh_enable 與 local_bh_disable
local_bh_enable()和local_bh_disable()函數(shù)用于在Linux內(nèi)核中臨時(shí)啟用和禁用**底半部(Bottom Half)**處理。
原型為:
#include <asm/irq.h>
void local_bh_enable(void);
void local_bh_disable(void);
這兩個(gè)函數(shù)的作用是:
- local_bh_enable(): 重新啟用底半部處理,允許待處理的軟中斷和任務(wù)調(diào)度被執(zhí)行。
- local_bh_disable(): 禁用底半部處理,禁止執(zhí)行任何軟中斷處理程序和任務(wù)調(diào)度。
底半部處理通常由兩個(gè)部分組成:
- 軟中斷處理:對應(yīng)內(nèi)核定時(shí)器中斷,用于處理超時(shí)和延遲機(jī)制。
- 任務(wù)調(diào)度:當(dāng)中斷被重新啟用時(shí),如果有更高優(yōu)先級的任務(wù)就緒,則調(diào)度程序會(huì)選擇它運(yùn)行。
這兩個(gè)函數(shù)通過修改本地的嵌套計(jì)數(shù)來禁用或重新啟用底半部處理。只有當(dāng)嵌套計(jì)數(shù)變?yōu)?時(shí),底半部機(jī)制才會(huì)被重新啟用。
使用示例:
void do_some_thing(void)
{
/* 首先禁用底半部處理 */
local_bh_disable();
/* 開始處理一個(gè)硬中斷 */
handle_hard_irq();
/* 硬中斷處理完成,檢查是否接收到信號 */
if (signal_pending(current)) {
/* 收到信號,禁止底半部機(jī)制并處理信號 */
local_bh_disable();
handle_signal();
local_bh_enable(); // 信號處理完成,重新啟用底半部
}
/* 其他與硬中斷處理相關(guān)的操作 */
do_some_task_1();
do_some_task_2();
/* 檢查軟中斷是否到期,如果到期則立即處理 */
if (softirq_pending(smp_processor_id()))
invoke_softirq();
/* 所有操作完成,重新啟用底半部機(jī)制 */
local_bh_enable();
}
這個(gè)例子展示了如何在不同的場景下使用local_bh_enable()和local_bh_disable():
- 一開始禁用底半部機(jī)制,以避免硬中斷處理過程中出現(xiàn)任務(wù)調(diào)度或軟中斷。
- 在硬中斷處理完成后,如果收到信號也首先禁用底半部機(jī)制,然后處理信號,最后再重新啟用。這是因?yàn)樾盘柼幚硪膊荒苓M(jìn)行任務(wù)調(diào)度與軟中斷。
- 處理與硬中斷相關(guān)的其它操作時(shí),底半部機(jī)制仍然被禁用。
- 如果在此期間軟中斷到期,則立即處理。因?yàn)檐浿袛嗟某瑫r(shí)時(shí)間很短,必須先處理。
- 所有操作完成后,重新啟用底半部機(jī)制,允許任務(wù)調(diào)度與軟中斷處理。
所以,這段代碼展示了根據(jù)運(yùn)行上下文與需求,采取啟用或禁用底半部機(jī)制的方法。這可以最大限度地避免不同的處理過程之間出現(xiàn)干擾,實(shí)現(xiàn)良好的同步與時(shí)序控制。
這兩個(gè)函數(shù)通常用于:
- 臨時(shí)禁止軟中斷和任務(wù)調(diào)度,從而防止關(guān)鍵代碼或資源被打亂。
- 處理收到的信號,避免在信號處理期間進(jìn)行任務(wù)調(diào)度。
- 實(shí)現(xiàn)同步機(jī)制,根據(jù)需要選擇性啟用或禁用底半部處理。
如果對底半部機(jī)制與這兩個(gè)函數(shù)還不太理解,可以參考我的前面的相關(guān)介紹。理解內(nèi)核的不同運(yùn)行上下文、調(diào)度機(jī)制與同步手段是成為Linux內(nèi)核開發(fā)高手的基礎(chǔ)。
3.2、并發(fā)控制機(jī)制–原子變量
原子變量: 存取時(shí)不可被打斷的特殊整型變量。
適用場合: 共享資源為單個(gè)整型變量的互斥場合。
3.2.1 相關(guān)函數(shù)
對原子變量的操作必須用下面這些專用宏或函數(shù):
3.2.1.1 atomic_t 原子量類型
頭文件 /arch/arm/include/asm/atomic.h
#include <linux/types.h
typedef struct {
int counter;
} atomic_t;
該類型本質(zhì)是一個(gè)數(shù)據(jù)結(jié)構(gòu)。
3.2.1.2 宏ATOMIC_INIT 創(chuàng)建原子變量
ATOMIC_INIT(i)是一個(gè)宏,用于初始化一個(gè)原子變量為值i。
它定義在頭文件中,展開后的定義為:
#include <asm/atomic.h>
#define ATOMIC_INIT(i) { (i) }
也就是將傳入的值i放在一個(gè)括號對{}中。
這個(gè)宏用于靜態(tài)初始化一個(gè)原子變量,語法為:
atomic_t my_atomic = ATOMIC_INIT(10);
這會(huì)將my_atomic初始化為10。
原子變量是Linux內(nèi)核實(shí)現(xiàn)同步機(jī)制的基礎(chǔ),它可以用于實(shí)現(xiàn)自旋鎖、計(jì)數(shù)器、標(biāo)志位等。ATOMIC_INIT宏提供了一個(gè)簡單的方法來初始化原子變量。
在內(nèi)核開發(fā)中,我們通常會(huì)定義如下變量使用ATOMIC_INIT初始化:
atomic_t counter = ATOMIC_INIT(0); // 計(jì)數(shù)器
atomic_t spinlock = ATOMIC_INIT(0); // 自旋鎖
atomic_t ready = ATOMIC_INIT(0); // 標(biāo)志位
然后可以使用如下方法操作這些原子變量:
atomic_inc(&counter); // 計(jì)數(shù)器+1
atomic_dec(&counter); // 計(jì)數(shù)器-1
atomic_set(&spinlock, 1); // 獲得自旋鎖
atomic_set(&spinlock, 0); // 釋放自旋鎖
atomic_set(&ready, 1); // 設(shè)置標(biāo)志位
atomic_read(&ready); // 讀取標(biāo)志位
所以,ATOMIC_INIT宏提供了初始化原子變量的簡單方法,配合其它原子操作宏可以實(shí)現(xiàn)各種同步機(jī)制與計(jì)數(shù)功能。
3.2.1.3 宏atomic_set 設(shè)置原子量的值
頭文件 /arch/arm/include/asm/atomic.h
原碼:
#include <asm/atomic.h>
#define atomic_set(v,i) (((v)->counter) = (i))
所以,可以如下定義原子量的初始值
atomic_t v = ATOMIC_INIT(0); //定義原子變量v并初始化為0
void atomic_set(atomic_t *v,int i); //設(shè)置原子量的值為i
或
atomic_t my_atomic;
atomic_set(&my_atomic, 10);
而像:v = 10;
這樣做是錯(cuò)誤的, 因?yàn)椴荒軐υ幼兞抠x值操作,所以這里是錯(cuò)誤的
3.2.1.4 宏atomic_read 獲取原子量的值
這個(gè)宏用于讀取一個(gè)原子變量的值
頭文件 /arch/arm/include/asm/atomic.h
原碼:
#include <asm/atomic.h>
#define atomic_read(v) (*(volatile int *)&(v)->counter)
返回值:
返回原子變量*v的counter成員的值 , int類型。
用法為:
atomic_t my_atomic;
int val = atomic_read(&my_atomic);
這會(huì)將my_atomic的值讀取到val變量中。
atomic_read宏是操作原子變量的基本方法之一,它可以直接讀取原子變量的值而不需要任何同步機(jī)制。這是因?yàn)樵幼兞吭趯?shí)現(xiàn)上包含了必要的鎖或其它同步手段,可以保證各個(gè)CPU對其的操作是串行的。
在內(nèi)核開發(fā)中,我們常會(huì)使用atomic_read宏:
- 讀取自旋鎖的狀態(tài),判斷是否上鎖:
atomic_t lock = ATOMIC_INIT(0);
if (atomic_read(&lock)) {
/* 上鎖,需要處理 */
}
- 讀取標(biāo)志位的狀態(tài):
atomic_t ready = ATOMIC_INIT(0);
if (atomic_read(&ready)) {
/* 標(biāo)志位已置,進(jìn)行相關(guān)處理 */
}
- 直接獲取計(jì)數(shù)器的值:
atomic_t count = ATOMIC_INIT(0);
int val = atomic_read(&count);
所以,atomic_read宏提供了一種簡單的方法來讀取原子變量的值,配合其它原子操作宏可以實(shí)現(xiàn)讀取各種同步機(jī)制與狀態(tài)。
3.2.1.5 宏atomic_add 與 atomic_sub原子變量加減
頭文件 /arch/arm/include/asm/atomic.h
原碼:
#include <asm/atomic.h>
>#define atomic_add(i, v) (void) atomic_add_return(i, v)
>static inline int atomic_add_return(int i, atomic_t *v)
{
unsigned long flags;
int val;
raw_local_irq_save(flags);
val = v->counter;
v->counter = val += i;
raw_local_irq_restore(flags);
return val;
}
atomic_add宏利用atomic_add_return()函數(shù)來實(shí)現(xiàn)真正的ADD操作,后者會(huì)根據(jù)體系結(jié)構(gòu)采用基于鎖或無鎖的機(jī)制來同步該操作。對使用者來說,atomic_add提供了一個(gè)簡單的接口來原子更新原子變量的值。
#include <asm/atomic.h>
>#define atomic_sub(i, v) (void) atomic_sub_return(i, v)
>static inline int atomic_sub_return(int i, atomic_t *v)
{
unsigned long flags;
int val;
raw_local_irq_save(flags);
val = v->counter;
v->counter = val -= i;
raw_local_irq_restore(flags);
return val;
}
所以可以這樣用:
void atomic_add(int i,atomic_t *v);//原子變量增加I,返回V的Int類型值
void atomic_sub(int i,atomic_t *v);//原子變量減少I,返回V的Int類型值
3.2.1.6 宏 atomic_inc 和 atomic_dec 原子變量自增自減
頭文件 /arch/arm/include/asm/atomic.h
原型:
#include <asm/atomic.h>
>#define atomic_inc(v) atomic_add(1, v)
>#define atomic_dec(v) atomic_sub(1, v)
atomic_inc和atomic_dec宏利用atomic_add()和atomic_sub()來實(shí)現(xiàn)加減一操作,這兩個(gè)函數(shù)會(huì)根據(jù)體系結(jié)構(gòu)采用基于鎖或無鎖的機(jī)制來同步該操作。所以,對使用者來說,atomic_inc和atomic_dec提供了簡單的接口來原子更新原子變量的值。
所以可以這樣用:
void atomic_inc(atomic_t *v);//原子變量增加1,返回V的Int類型值
void atomic_dec(atomic_t *v);//原子變量減少1,返回V的Int類型值
3.2.1.7 操作并測試
頭文件 /arch/arm/include/asm/atomic.h
原型:
#define atomic_inc_and_test(v) (atomic_add_return(1, v) == 0)
#define atomic_dec_and_test(v) (atomic_sub_return(1, v) == 0)
#define atomic_sub_and_test(i, v) (atomic_sub_return(i, v) == 0)
所以可以這樣使用函數(shù):
運(yùn)算后結(jié)果為0則返回真,否則返回假
int atomic_inc_and_test(atomic_t *v);
int atomic_dec_and_test(atomic_t *v);
int atomic_sub_and_test(int i,atomic_t *v);
3.2.1.8 ATOMIC_BITOP原子位操作方法
ATOMIC_BITOP()宏用于在一個(gè)原子變量上執(zhí)行位操作,如設(shè)置、清除和翻轉(zhuǎn)指定的位。
頭文件:/arch/arm/include/asm/bitops.h
原碼:
#include <asm/bitops.h>
#define ATOMIC_BITOP(name,nr,p) (__builtin_constant_p(nr) ? ____atomic_##name(nr, p) : _##name(nr,p))
這個(gè)宏定義的意思是:
如果nr(代表bit number,即位號)是一個(gè)常數(shù),則使用____atomic_##name形式的位操作函數(shù)。
否則,使用_##name形式的位操作函數(shù)。
舉個(gè)例子,如果定義如下宏:
#define SET_BIT(nr, p) ATOMIC_BITOP(set_bit, nr, p)
那么當(dāng)調(diào)用SET_BIT(5, ptr)時(shí),如果5是一個(gè)常數(shù),會(huì)展開為:
____atomic_set_bit(5, ptr)
否則,會(huì)展開為:
_set_bit(nr, ptr)
==============================================================
補(bǔ)充:
在宏定義中使用“##”
運(yùn)算符可以將兩個(gè)符號連接成一個(gè)符號。這被稱為“宏連接(Macro Concatenation)”。
例如在這個(gè)宏定義中:#define ATOMIC_BITOP(name,nr,p) (__builtin_constant_p(nr) ? ____atomic_##name(nr, p) : _##name(nr,p))
“##”運(yùn)算符被用于:____atomic_##name: 將name和____atomic_連接為一個(gè)符號,例如____atomic_set_bit
“_##name”:將_和name連接為一個(gè)符號,例如_set_bit
所以如果定義:#define SET_BIT(nr, p) ATOMIC_BITOP(set_bit, nr, p)
那么這個(gè)宏可以展開為:(__builtin_constant_p(nr) ? ____atomic_set_bit(nr, p) : _set_bit(nr,p))
=============================================================
3.2.1.9 用ATOMIC_BITOP構(gòu)造所位操作函數(shù)
原型
頭文件:/arch/arm/include/asm/bitops.h
#define set_bit(nr,p) ATOMIC_BITOP(set_bit,nr,p)
#define clear_bit(nr,p) ATOMIC_BITOP(clear_bit,nr,p)
#define change_bit(nr,p) ATOMIC_BITOP(change_bit,nr,p)
#define test_and_set_bit(nr,p) ATOMIC_BITOP(test_and_set_bit,nr,p)
#define test_and_clear_bit(nr,p) ATOMIC_BITOP(test_and_clear_bit,nr,p)
#define test_and_change_bit(nr,p) ATOMIC_BITOP(test_and_change_bit,nr,p)
解釋:
- set_bit(nr,p):設(shè)置p中的第nr位,等價(jià)于p |= (1 << nr)
- clear_bit(nr,p):清除p中的第nr位,等價(jià)于p &= ~(1 << nr)
- change_bit(nr,p):翻轉(zhuǎn)p中的第nr位,等價(jià)于p ^= (1 << nr)
- test_and_set_bit(nr,p):原子地測試第nr位是否為0,如果是則設(shè)置該位,并返回原值
- test_and_clear_bit(nr,p):原子地測試第nr位是否為1,如果是則清除該位,并返回原值
- test_and_change_bit(nr,p):原子地測試第nr位,并翻轉(zhuǎn)該位,返回原值
所以可以這么用:
- 設(shè)置位
void set_bit(nr, void *addr);
//設(shè)置addr所指向的數(shù)據(jù)的第nr位為1 - 清除位
void clear_bit(nr , void *addr);
//清除addr所指向的數(shù)據(jù)的第nr位為0 - 改變位
void change_bit(nr , void *addr);
//改變addr所指向的數(shù)據(jù)的第nr位為1 - 測試位
void test_bit(nr , void *addr);
//測試addr所指向的數(shù)據(jù)的第nr位是否為1
3.2.2 實(shí)例
要求:字符驅(qū)動(dòng)只能被一個(gè)應(yīng)用進(jìn)程使用。即只能被一個(gè)任務(wù)open,其它任務(wù)在同一時(shí)間要打開該設(shè)備時(shí)會(huì)出錯(cuò)提示。
/*************************************************************************
> File Name: atomic-only.c
> 作用:以原子變量做為并發(fā)控制的手段,使本驅(qū)動(dòng)同時(shí)只能被一個(gè)應(yīng)用層進(jìn)程所open
************************************************************************/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <asm/uaccess.h>
#include <linux/slab.h>
#include <linux/types.h>
/*1、定義重要的變量及結(jié)構(gòu)體*/
struct x_dev_t {
struct cdev my_dev; //cdev設(shè)備描述結(jié)構(gòu)體變量
atomic_t have_open; //記錄驅(qū)動(dòng)是否被打開的原子變量,在init()時(shí)初始化,設(shè)置初值。在open()時(shí)打開
};
struct x_dev_t *pcdev;
/*所有驅(qū)動(dòng)函數(shù)聲明*/
int open (struct inode *, struct file *);
int close (struct inode *, struct file *);
//驅(qū)動(dòng)操作函數(shù)結(jié)構(gòu)體,成員函數(shù)為需要實(shí)現(xiàn)的設(shè)備操作函數(shù)指針
//簡單版的模版里,只寫了open與release兩個(gè)操作函數(shù)。
struct file_operations fops={
.open = open,
.release = close,
};
static int __init my_init(void){
int unsucc =0;
dev_t devno;
int major,minor;
pcdev = kzalloc(sizeof(struct x_dev_t), GFP_KERNEL);
/*2、創(chuàng)建 devno */
unsucc = alloc_chrdev_region(&devno , 0 , 1 , "atomic-char");
if (unsucc){
printk(" creating devno faild\n");
return -1;
}
major = MAJOR(devno);
minor = MINOR(devno);
printk("devno major = %d ; minor = %d;\n",major , minor);
/*3、初始化 cdev結(jié)構(gòu)體,并將cdev結(jié)構(gòu)體與file_operations結(jié)構(gòu)體關(guān)聯(lián)起來*/
/*這樣在內(nèi)核中就有了設(shè)備描述的結(jié)構(gòu)體cdev,以及設(shè)備操作函數(shù)的調(diào)用集合file_operations結(jié)構(gòu)體*/
cdev_init(&pcdev->my_dev , &fops);
pcdev->my_dev.owner = THIS_MODULE;
/*4、注冊cdev結(jié)構(gòu)體到內(nèi)核鏈表中*/
unsucc = cdev_add(&pcdev->my_dev,devno,1);
if (unsucc){
printk("cdev add faild \n");
return 1;
}
//初始化原子量have_open為1
atomic_set(&pcdev->have_open,1);
printk("the driver atomic-char initalization completed\n");
return 0;
}
static void __exit my_exit(void)
{
cdev_del(&pcdev->my_dev);
unregister_chrdev_region(pcdev->my_dev.dev , 1);
printk("***************the driver atomic-char exit************\n");
}
/*5、驅(qū)動(dòng)函數(shù)的實(shí)現(xiàn)*/
/*file_operations結(jié)構(gòu)全成員函數(shù).open的具體實(shí)現(xiàn)*/
int open(struct inode *pnode , struct file *pf){
struct x_dev_t *p = container_of(pnode->i_cdev,struct x_dev_t , my_dev);
pf->private_data = (void *)p;
//在open函數(shù)中對原子量have_open進(jìn)行減1并檢測。=0,允許打開文件,<0則不允許打開
if (atomic_dec_and_test(&p->have_open)){
printk("atomic-char is opened\n");
return 0;
}else{
printk("device atomic-char can't be opened again\n");
atomic_inc(&p->have_open);//原子量=-1,記得這里要把原子量加回到0
return -1;
}
}
/*file_operations結(jié)構(gòu)全成員函數(shù).release的具體實(shí)現(xiàn)*/
int close(struct inode *pnode , struct file *pf){
struct x_dev_t *p = (struct x_dev_t *)pf->private_data;
printk("atomic-char is closed \n");
atomic_set(&p->have_open,1);
return 0;
}
module_init(my_init);
module_exit(my_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("");
3.3、并發(fā)控制機(jī)制–自旋鎖
3.3.1 概念
\qquad 自旋鎖(Spin Lock)是一種典型的對臨界資源進(jìn)行互斥訪問的手段,其名稱來源于它的工作方式。為了獲得一個(gè)自旋鎖,在某個(gè)CPU上運(yùn)行的代碼需先執(zhí)行一個(gè)原子操作,該操作測試并設(shè)置某個(gè)內(nèi)存變量。由于它是原子操作,所以在該操作完成之前其它執(zhí)行單元不可能訪問這個(gè)內(nèi)存變量。如果測試結(jié)果表明鎖已經(jīng)空閑,則程序獲得這個(gè)自旋鎖并繼續(xù)執(zhí)行;如果測試結(jié)果表明鎖仍被占用,程序?qū)⒃谝粋€(gè)小的循環(huán)內(nèi)重復(fù)這個(gè)“測試并設(shè)置”操作,即進(jìn)行所謂的“自旋”,通俗說就是“在原地打轉(zhuǎn)”。當(dāng)自旋鎖 持有者通過重置該變量釋放這個(gè)自旋鎖后,某個(gè)等待的“測試并設(shè)置”操作向其調(diào)用者報(bào)告鎖已釋放。
\qquad 自旋鎖是基于忙等待的并發(fā)控制機(jī)制,由于在獲取不到資源鎖時(shí)會(huì)進(jìn)入忙等待,不會(huì)進(jìn)入睡眠狀態(tài)。忙等待是占時(shí)間片的,因此這個(gè)機(jī)制不要用于過長的臨界區(qū)執(zhí)行。也因此,這個(gè)互斥鎖可用于異常上下文,不會(huì)使異常進(jìn)入阻塞狀態(tài)。
適用場合:
- 異常上下文之間或異常上下文與任務(wù)上下文之間共享資源時(shí)
- 任務(wù)上下文之間且臨界區(qū)執(zhí)行時(shí)間很短時(shí)
- 互斥問題
3.3.2 函數(shù)
頭文件: /include/linux/spinlock_types.h
3.3.2.1.定義自旋鎖
原碼:
#include <linux/spinlock.h>
typedef struct spinlock {
union {
struct raw_spinlock rlock;
#ifdef CONFIG_DEBUG_LOCK_ALLOC
# define LOCK_PADSIZE (offsetof(struct raw_spinlock, dep_map))
struct {
u8 __padding[LOCK_PADSIZE];
struct lockdep_map dep_map;
};
#endif
};
} spinlock_t;
- rlock - 這個(gè)是Raw spinlock,用于實(shí)際的自旋鎖定。它包含lock和unlock操作,這是真正控制互斥鎖的部分。
- dep_map - 這個(gè)是依賴關(guān)系映射,在開啟CONFIG_DEBUG_LOCK_ALLOC宏的情況下使用。它用于記錄自旋鎖獲取和釋放之間的依賴關(guān)系,以檢測潛在的死鎖問題。
所以spinlock_t中嵌入了兩個(gè)結(jié)構(gòu):
- raw_spinlock - 用于實(shí)際的鎖定,所有配置都會(huì)包含這個(gè)結(jié)構(gòu)。
- lockdep_map - 僅在debug配置中包含,用于記錄鎖依賴關(guān)系,以便檢測死鎖。
所以使用前先定義自旋鎖:
spinlock_t lock;
3.3.2.2 初始化自旋鎖
源碼
#define <linux/spinlock.h>
#define spin_lock_init(_lock) \
do { \
spinlock_check(_lock); \
raw_spin_lock_init(&(_lock)->rlock); \
} while (0)
- spinlock_check()用來檢查_lock是否指向一個(gè)正確的spinlock_t對象。
- raw_spin_lock_init()用來初始化raw_spinlock部分,將lock置0等。
用法
spin_lock_init(spinlock_t *);
3.3.2.3 獲得自旋鎖(即P操作,置資源為被用)
原型:
#include <linux/spinlock.h>
static inline void spin_lock(spinlock_t *lock)
{
raw_spin_lock(&lock->rlock);
}
static inline void spin_lock_bh(spinlock_t *lock)
{
raw_spin_lock_bh(&lock->rlock);
}
static inline int spin_trylock(spinlock_t *lock)
{
return raw_spin_trylock(&lock->rlock);
}
static inline void spin_lock_irq(spinlock_t *lock)
{
raw_spin_lock_irq(&lock->rlock);
}
解釋:
- spin_lock():成功獲得自旋鎖立即返回,否則自旋在那里,直到該自旋鎖的保持者釋放。禁止中斷。
- spin_lock_bh():獲取自旋鎖。禁止軟中斷。
- spin_trylock():成功獲得自旋鎖立即返回真,否則返回假,而不是像上一個(gè)那樣"在原地打轉(zhuǎn)”
- spin_lock_irq: 獲取自旋鎖lock,禁止中斷,并保存中斷標(biāo)志位。
它們的定義均通過調(diào)用raw_spinlock部分的對應(yīng)函數(shù)來實(shí)現(xiàn)實(shí)際的鎖定操作。raw_spinlock部分實(shí)現(xiàn)最基本的自旋鎖機(jī)制,這三個(gè)接口在它的基礎(chǔ)上實(shí)現(xiàn)了Disable中斷,Disable軟中斷等額外功能。
spin_lock(lock); // 禁止中斷,上鎖
// 臨界區(qū)代碼
spin_unlock(lock); // 解鎖,開中斷
spin_lock_bh(lock); // 禁止軟中斷,上鎖
// 臨界區(qū)代碼
spin_unlock_bh(lock); // 解鎖,開軟中斷
spin_lock_irq(lock);
// 臨界區(qū)代碼
spin_unlock_irq(lock); // 釋放鎖,根據(jù)之前保存的狀態(tài)決定是否開中斷
if (spin_trylock(lock)) { // 嘗試上鎖,成功則進(jìn)入臨界區(qū)
// 臨界區(qū)代碼
} else {
// 獲取鎖失敗,做其他事情
}
這幾個(gè)接口與自旋鎖spinlock_t的初始化和銷毀等一起,提供了Linux內(nèi)核中的自旋鎖機(jī)制。它們在許多需要互斥訪問共享資源的場景中得到使用,是內(nèi)核同步的基本方法之一。
重點(diǎn)
在中斷服務(wù)程序(ISR)中選擇合適的自旋鎖接口,需要考慮單核與多核的差異:
在單核下:
- spin_lock()就足夠,它會(huì)禁止中斷,保證ISR的互斥訪問。
- spin_lock_irq()和spin_lock_irqsave()則沒必要,因?yàn)橹挥幸粋€(gè)CPU,不會(huì)有競爭的ISR同時(shí)訪問臨界資源。
在多核下:
- spin_lock()不夠,它僅禁止中斷,無法在多個(gè)CPU之間提供互斥。
- 應(yīng)選擇spin_lock_irq()或spin_lock_irqsave()。
- spin_lock_irq()會(huì)禁止本地CPU的中斷,并通過自旋等待在其他CPU上執(zhí)行的競爭ISR,進(jìn)而實(shí)現(xiàn)互斥。
- spin_lock_irqsave()同spin_lock_irq(),但可以提供更大的靈活性,因?yàn)橹袛酄顟B(tài)被保存在外部變量中,可以在任意時(shí)刻進(jìn)行恢復(fù)。
所以,總結(jié)如下:
在單核下,ISR中可以直接使用spin_lock()。
在多核下,ISR應(yīng)使用spin_lock_irq()或spin_lock_irqsave()以實(shí)現(xiàn)多個(gè)CPU之間的互斥訪問。
選擇spin_lock_irq()還是spin_lock_irqsave()需要根據(jù)具體的使用場景而定。如果需要在鎖定外實(shí)現(xiàn)更復(fù)雜的中斷狀態(tài)控制流程,應(yīng)選擇spin_lock_irqsave(),否則spin_lock_irq()就足夠簡單高效。
3.3.2.4 釋放自旋鎖(即V操作,置資源為可用)
原型
#include <linux/spinlock.h>
static inline void spin_unlock(spinlock_t *lock)
{
raw_spin_unlock(&lock->rlock);
}
static inline void spin_unlock_bh(spinlock_t *lock)
{
raw_spin_unlock_bh(&lock->rlock);
}
static inline void spin_unlock_irq(spinlock_t *lock)
{
raw_spin_unlock_irq(&lock->rlock);
}
- spin_unlock(): 釋放自旋鎖,開中斷。
- spin_unlock_bh(): 釋放自旋鎖,開軟中斷。
它們通過調(diào)用raw_spinlock部分的對應(yīng)接口來實(shí)現(xiàn)實(shí)際的解鎖操作。整個(gè)流程為:
spin_lock(lock); // 上鎖,禁止中斷
// 臨界區(qū)代碼
spin_unlock(lock); // 解鎖,開中斷
spin_lock_bh(lock);// 上鎖,禁止軟中斷
// 臨界區(qū)代碼
spin_unlock_bh(lock); // 解鎖,開軟中斷
spin_lock_irq(lock);
// 臨界區(qū)代碼
spin_unlock_irq(lock); // 釋放鎖,并根據(jù)spin_lock_irq()保存的狀態(tài)
// 決定是否開中斷
與獲取自旋鎖spin_lock()和spin_lock_bh()相對應(yīng),這兩個(gè)接口實(shí)現(xiàn)了自旋鎖的釋放,并恢復(fù)中斷和軟中斷。
3.3.3 實(shí)例
要求:用自旋鎖實(shí)現(xiàn)上節(jié)中一個(gè)驅(qū)動(dòng)同一時(shí)只能被打開一次。
/*************************************************************************
> File Name: spinlock-only.c
> 作用:以自旋鎖做為并發(fā)控制的手段,使本驅(qū)動(dòng)同時(shí)只能被一個(gè)應(yīng)用層進(jìn)程所open
************************************************************************/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <asm/uaccess.h>
#include <linux/slab.h>
#include <linux/types.h>
#include <linux/spinlock.h>
/*1、定義重要的變量及結(jié)構(gòu)體*/
struct x_dev_t {
struct cdev my_dev; //cdev設(shè)備描述結(jié)構(gòu)體變量
spinlock_t lock; //自旋鎖
int have_open; //開啟標(biāo)志,0為已開啟,1為未開啟
};
struct x_dev_t *pcdev;
/*所有驅(qū)動(dòng)函數(shù)聲明*/
int open (struct inode *, struct file *);
int close (struct inode *, struct file *);
//驅(qū)動(dòng)操作函數(shù)結(jié)構(gòu)體,成員函數(shù)為需要實(shí)現(xiàn)的設(shè)備操作函數(shù)指針
//簡單版的模版里,只寫了open與release兩個(gè)操作函數(shù)。
struct file_operations fops={
.open = open,
.release = close,
};
static int __init my_init(void){
int unsucc =0;
dev_t devno;
int major,minor;
pcdev = kzalloc(sizeof(struct x_dev_t), GFP_KERNEL);
/*2、創(chuàng)建 devno */
unsucc = alloc_chrdev_region(&devno , 0 , 1 , "spinlock-char");
if (unsucc){
printk(" driver: creating devno faild\n");
return -1;
}
major = MAJOR(devno);
minor = MINOR(devno);
printk("driver : devno major = %d ; minor = %d;\n",major , minor);
/*3、初始化 cdev結(jié)構(gòu)體,并將cdev結(jié)構(gòu)體與file_operations結(jié)構(gòu)體關(guān)聯(lián)起來*/
/*這樣在內(nèi)核中就有了設(shè)備描述的結(jié)構(gòu)體cdev,以及設(shè)備操作函數(shù)的調(diào)用集合file_operations結(jié)構(gòu)體*/
cdev_init(&pcdev->my_dev , &fops);
pcdev->my_dev.owner = THIS_MODULE;
/*4、注冊cdev結(jié)構(gòu)體到內(nèi)核鏈表中*/
unsucc = cdev_add(&pcdev->my_dev,devno,1);
if (unsucc){
printk("driver : cdev add faild \n");
return 1;
}
//初始化自旋鎖have_open 和 開啟標(biāo)志
spin_lock_init(&pcdev->lock);
pcdev->have_open = 1;
printk("driver : the driver spinlock-char initalization completed\n");
return 0;
}
static void __exit my_exit(void)
{
cdev_del(&pcdev->my_dev);
unregister_chrdev_region(pcdev->my_dev.dev , 1);
printk("***************the driver spinlock-char exit************\n");
}
/*5、驅(qū)動(dòng)函數(shù)的實(shí)現(xiàn)*/
/*file_operations結(jié)構(gòu)全成員函數(shù).open的具體實(shí)現(xiàn)*/
int open(struct inode *pnode , struct file *pf){
struct x_dev_t *p = container_of(pnode->i_cdev,struct x_dev_t , my_dev);
pf->private_data = (void *)p;
//在open函數(shù)中對原子量have_open進(jìn)行減1并檢測。=0,允許打開文件,<0則不允許打開
spin_lock(&p->lock);
if (p->have_open == 1)
{
p->have_open = 0;
printk("driver : spinlock-char is opened\n");
spin_unlock(&p->lock);
return 0;
}else{ //已被打開
printk("driver : device spinlock-char Could not opend again\n");
spin_unlock(&p->lock);
return -1;
}
}
/*file_operations結(jié)構(gòu)全成員函數(shù).release的具體實(shí)現(xiàn)*/
int close(struct inode *pnode , struct file *pf){
printk("driver : spinlock-char is closed \n");
return 0;
}
module_init(my_init);
module_exit(my_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("");
3.3.4 自旋鎖的衍生與注意事項(xiàng)
3.4 、并發(fā)控制機(jī)制–信號量
信號量(Semaphore)是操作系統(tǒng)中最典型的用于同步和互斥的手段,信號量的值可以是0、1或者n。信號量與操作系統(tǒng)中的經(jīng)典概念PV操作對應(yīng)。
- P(S): 將信號量S的值減1,即S=S-1; 如果S>=0,則該進(jìn)程繼續(xù)執(zhí)行;否則該進(jìn)程置為睡眠狀態(tài)(阻塞),進(jìn)入等待隊(duì)列。
- V(S):將信號量S的值加1,即S=S+1; 如果S>0,喚醒隊(duì)列中阻塞而等待信號量的進(jìn)程。
基于阻塞的并發(fā)控制機(jī)制,當(dāng)要取資源時(shí),如遇資源不足,則會(huì)使本任務(wù)進(jìn)入阻塞狀態(tài)。即P操作不成功時(shí)會(huì)進(jìn)入阻塞睡眠狀態(tài)。
適用場合:只能用于任務(wù)上下文之間且臨界區(qū)執(zhí)行時(shí)間較長時(shí)的互斥或同步問題
3.4.1 函數(shù)
#include <linux/semaphore.h>
3.4.1.1 定義信號量
原碼:
#include <linux/semaphore.h>
struct semaphore {
raw_spinlock_t lock;
unsigned int count;
struct list_head wait_list;
};
- lock:自旋鎖,用于保護(hù)semaphore的訪問。
- count:資源的數(shù)量或可用數(shù)量。
- wait_list:等待獲取semaphore的進(jìn)程鏈表。
主要接口:
- void sema_init(struct semaphore *sem, int val); 初始化semaphore,count為val。
- void down(struct semaphore *sem); 獲取semaphore,如果sem不可用,進(jìn)入睡眠。
- int down_interruptible(struct semaphore *sem); 獲取semaphore,如果 sem不可用,進(jìn)入可中斷睡眠。
- int down_trylock(struct semaphore *sem); 嘗試獲取semaphore,如果獲取不到立即返回。
- void up(struct semaphore *sem); 釋放semaphore,喚醒等待進(jìn)程。
3.4.1.2 初始化信號量
原型:
#include <linux/semaphore.h>
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);
}
sema_init()函數(shù)用于初始化一個(gè)semaphore結(jié)構(gòu)。
它主要做了兩件事:
- 使用__SEMAPHORE_INITIALIZER宏初始化semaphore的各個(gè)成員。該宏的定義為:
#define __SEMAPHORE_INITIALIZER(name, n) \
{ \
.lock = __RAW_SPIN_LOCK_UNLOCKED(name.lock), \
.count = n, \
.wait_list = LIST_HEAD_INIT((name).wait_list) \
}
它會(huì)初始化:
- lock為解鎖狀態(tài)
- count為傳入的n值
- wait_list為空
- 使用lockdep_init_map()為semaphore的lock成員指定依賴關(guān)系跟蹤信息。這個(gè)主要用于lockdep(鎖依賴檢測)機(jī)制。
所以,總結(jié)來說,sema_init()的作用是對一個(gè)semaphore結(jié)構(gòu)進(jìn)行初始化,給其一個(gè)初始的count值,并為其lock成員lockdep設(shè)置需要的信息。這樣,在后續(xù)的使用過程中,lockdep就可以根據(jù)lock的依賴關(guān)系跟蹤semaphore的加鎖順序,檢查是否存在潛在的死鎖問題。
這個(gè)函數(shù)是使用semaphore結(jié)構(gòu)的第一步,在創(chuàng)建semaphore實(shí)例后,必須首先調(diào)用sema_init()進(jìn)行必要的初始化,否則semaphore處于未定義狀態(tài),使用結(jié)果未知。
用法
struct semaphore empty, full;
sema_init(&empty, 10); // 初始化為空的緩沖區(qū)數(shù)為10
sema_init(&full, 0); // 初始化為滿的緩沖區(qū)數(shù)為0
3.4.1.3 獲得信號量P
#include <linux/semaphore.h>
void down(struct semaphore *sem);
int __must_check down_interruptible(struct semaphore *sem);
int __must_check down_killable(struct semaphore *sem);
int __must_check down_trylock(struct semaphore *sem);
int __must_check down_timeout(struct semaphore *sem, long jiffies);
參數(shù):
- sem:要獲取的semaphore結(jié)構(gòu)體指針。
- jiffies:等待超時(shí)時(shí)間,以jiffies為單位。
返回值
- 0:獲取成功
-EINTR:等待過程被致命信號中斷(針對down()、down_interruptible()、down_killable())
-EBUSY:semaphore不可用 (針對down_trylock())
-ETIMEOUT:等待超時(shí)未能獲取semaphore (針對down_timeout())
區(qū)別:
- void down(struct semaphore *sem); 獲得semaphore,如果不可用則睡眠,直到semaphore變?yōu)榭捎?。這是最基本的down操作,會(huì)一直阻塞進(jìn)程。
- int down_interruptible(struct semaphore *sem); 獲得semaphore,如果不可用則進(jìn)入可中斷睡眠。如果在睡眠中被信號中斷,會(huì)返回-EINTR。這在一些需要對睡眠進(jìn)行精細(xì)控制的場景下很有用。
- int down_killable(struct semaphore *sem); 獲得semaphore,如果不可用則進(jìn)入可殺死的睡眠。如果在睡眠中被致命信號中斷,會(huì)返回-EINTR。
- int down_trylock(struct semaphore *sem); 嘗試獲得semaphore,如果獲得成功則返回0,否則立即返回-EBUSY。這在需要嘗試獲取但不必等待的情況下使用。
- int down_timeout(struct semaphore *sem, long jiffies); 嘗試獲得semaphore,如果在jiffies設(shè)定的超時(shí)時(shí)間內(nèi)未獲得,則返回-ETIMEOUT。如果在此超時(shí)時(shí)間內(nèi)semaphore變?yōu)榭捎脛t返回0。這在需要等待一定時(shí)間后再使用其他手段的場景使用。
以上接口為semaphore提供了豐富的down行為,使用它們可以實(shí)現(xiàn)對semaphore獲取過程精細(xì)化的控制,滿足各種同步場景的需要。理解這幾個(gè)接口的區(qū)別有助于根據(jù)具體需求選擇最適合的semaphore獲取方式。
int down(struct semaphore *sem);//深度睡眠
int down_interruptible(struct semaphore *sem);//淺度睡眠
一旦信號量sem的值為0時(shí),則該函數(shù)會(huì)進(jìn)入睡眠狀態(tài)。直到sem值大于0后。
3.4.1.4 釋放信號量V
void up(struct semaphore *sem)函數(shù)用于釋放一個(gè)semaphore信號量。它的作用是:
- 如果有進(jìn)程等待該semaphore,則喚醒最先等待的進(jìn)程。
- 如果沒有等待進(jìn)程,則semaphore的count值加1。
原型:
#include <linux/semaphoreh>
void up(struct semaphore *sem)
{
if (list_empty(&sem->wait_list))
sem->count++;
else
__up(sem);
}
它首先檢查sem的wait_list是否為空,如果是則直接增加sem的count值。否則就通過__up()喚醒wait_list中的最先進(jìn)入睡眠的進(jìn)程。
3.4.1.5 semaphore 使有和模式
1. 生產(chǎn)者-消費(fèi)者問題:
struct semaphore empty, full;
sema_init(&empty, 10); // 初始化為空的緩沖區(qū)數(shù)為10
sema_init(&full, 0); // 初始化為滿的緩沖區(qū)數(shù)為0
void producer() {
while (1) {
// 生產(chǎn)一個(gè)項(xiàng)
down(&empty); // 獲取一個(gè)空緩沖區(qū)
// 將項(xiàng)放入緩沖區(qū)
up(&full); // 發(fā)出一個(gè)滿緩沖區(qū)信號
}
}
void consumer() {
while (1) {
down(&full); // 獲取一個(gè)滿緩沖區(qū)
// 從緩沖區(qū)中取走一項(xiàng)
up(&empty); // 發(fā)出一個(gè)空緩沖區(qū)信號
}
}
empty和full兩個(gè)semaphore控制了緩沖區(qū)的空滿狀態(tài),實(shí)現(xiàn)了生產(chǎn)者和消費(fèi)者之間的同步。
2. 讀者-寫者問題:
struct semaphore mutex, wr_sem;
sema_init(&mutex, 1); // 互斥鎖,初始化為1
sema_init(&wr_sem, 1); // 寫者信號量,初始化為1
void writer() {
down(&wr_sem); // 獲取寫者信號量
down(&mutex); // 獲取互斥鎖
// 寫操作
up(&mutex); // 釋放互斥鎖
up(&wr_sem); // 釋放寫者信號量
}
void reader() {
down(&mutex); // 獲取互斥鎖
// 讀操作
up(&mutex); // 釋放互斥鎖
}
mutex實(shí)現(xiàn)互斥,保證同一時(shí)刻只有一個(gè)讀者或?qū)懻?。wr_sem實(shí)現(xiàn)寫者優(yōu)先,在進(jìn)行寫操作時(shí)阻塞其他讀者或?qū)懻摺?/p>
3.4.2 實(shí)例
要求:一個(gè)內(nèi)存緩沖虛擬一個(gè)設(shè)備。該設(shè)備允許應(yīng)用層多個(gè)進(jìn)程同時(shí)對該設(shè)備進(jìn)行讀寫操作。這樣就需要驅(qū)動(dòng)對并發(fā)場景進(jìn)行控制。
/*************************************************************************
> File Name: semaphore-memory.c
************************************************************************/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
/*1、定義重要的變量及結(jié)構(gòu)體*/
#define MEM_SIZE 500
struct mem_dev_t{
dev_t devno;
struct cdev my_dev; //cdev設(shè)備描述結(jié)構(gòu)體變量
char mem[MEM_SIZE]; //內(nèi)存池,當(dāng)成虛擬設(shè)備
struct semaphore sem; //信號量
};
struct mem_dev_t *mem_dev;
/*驅(qū)動(dòng)函數(shù)聲明*/
ssize_t read (struct file *, char __user *, size_t, loff_t *);
ssize_t write (struct file *, const char __user *, size_t, loff_t *);
int open (struct inode *, struct file *);
int release (struct inode *, struct file *);
//驅(qū)動(dòng)操作函數(shù)結(jié)構(gòu)體,成員函數(shù)為需要實(shí)現(xiàn)的設(shè)備操作函數(shù)指針
//簡單版的模版里,只寫了open與release兩個(gè)操作函數(shù)。
struct file_operations fops={
.open = open,
.release = release,
.read = read,
.write = write,
};
/*3、初始化 cdev結(jié)構(gòu)體,并將cdev結(jié)構(gòu)體與file_operations結(jié)構(gòu)體關(guān)聯(lián)起來*/
/*這樣在內(nèi)核中就有了設(shè)備描述的結(jié)構(gòu)體cdev,以及設(shè)備操作函數(shù)的調(diào)用集合file_operations結(jié)構(gòu)體*/
static int cdev_setup(struct mem_dev_t *mem_dev ){
int unsucc =0;
cdev_init(&mem_dev->my_dev , &fops);
mem_dev->my_dev.owner = THIS_MODULE;
/*4、注冊cdev結(jié)構(gòu)體到內(nèi)核鏈表中*/
unsucc = cdev_add(&mem_dev->my_dev,mem_dev->devno,1);
if (unsucc){
printk("cdev add faild \n");
return -1;
}
/*********************************************************/
sema_init( &mem_dev->sem,1); //初始化信號量,為1,意味著有資源mem
/*********************************************************/
return 0;
}
static int __init my_init(void){
int major , minor;
int unsucc =0;
mem_dev = kzalloc(sizeof(struct mem_dev_t) , GFP_KERNEL);
if (!mem_dev){
printk(" allocating memory is failed");
return -1;
}
/*2、創(chuàng)建 devno */
unsucc = alloc_chrdev_region(&mem_dev->devno , 0 , 1 , "operate_memory");
if (unsucc){
printk(" creating devno is failed\n");
return -1;
}else{
major = MAJOR(mem_dev->devno);
minor = MINOR(mem_dev->devno);
printk("major = %d ; minor = %d\n",major,minor);
}
/*3、 初始化cdev結(jié)構(gòu)體,并聯(lián)cdev結(jié)構(gòu)體與file_operations.*/
/*4、注冊cdev結(jié)構(gòu)體到內(nèi)核鏈表中*/
if (cdev_setup(mem_dev) == 0){
printk("the driver operate_memory initalization completed\n");
return 0;
} else
return -1;
}
static void __exit my_exit(void)
{
cdev_del(&mem_dev->my_dev);
unregister_chrdev_region(mem_dev->devno , 1);
printk("***************the driver operate_memory exit************\n");
}
/*5、驅(qū)動(dòng)函數(shù)的實(shí)現(xiàn)*/
/*file_operations結(jié)構(gòu)全成員函數(shù).open的具體實(shí)現(xiàn)*/
int open(struct inode *pnode , struct file *pf){
pf->private_data = (void*)mem_dev; //把全局變量指針放入到struct file結(jié)構(gòu)體里
printk("operate_memory is opened\n");
return 0;
}
/*file_operations結(jié)構(gòu)全成員函數(shù).release的具體實(shí)現(xiàn)*/
int release(struct inode *pnode , struct file *pf){
printk("operate_memory is closed \n");
return 0;
}
/*file_operations結(jié)構(gòu)全成員函數(shù).read的具體實(shí)現(xiàn)*/
ssize_t read (struct file * pf, char __user * buf, size_t size , loff_t * ppos){
struct mem_dev_t *pdev = pf->private_data;
int count = 0;
//判斷偏移量的有效性
if (*ppos >= MEM_SIZE){
return 0;
}
//判斷能夠讀到的字節(jié)數(shù)量
if (size > MEM_SIZE - *ppos){
count = MEM_SIZE - *ppos;
}else{
count = size;
}
//copy_from_user返回值大于0失敗
/*********************************************************/
down(&pdev->sem); //信號量P操作,表示占用資源
if ( copy_to_user(buf , &pdev->mem[*ppos] , count )){
up(&pdev->sem); //退出前釋放信號量,V操作
return 0;
}else{
*ppos += count;
up(&pdev->sem); //退出前釋放信號量,V操作
return count;
}
/*********************************************************/
}
/*file_operations結(jié)構(gòu)全成員函數(shù).write的具體實(shí)現(xiàn)*/
ssize_t write (struct file * pf, const char __user *buf, size_t size , loff_t *ppos){
struct mem_dev_t *pdev = pf->private_data;
int count = 0;
//判斷偏移量的有效性
if (*ppos >=MEM_SIZE ){
return 0;
}
//判斷能夠?qū)懭氲淖止?jié)數(shù)量
if (size > MEM_SIZE-*ppos){
count = MEM_SIZE-*ppos;
}else{
count = size;
}
//copy_from_user返回值大于0失敗
/*********************************************************/
down(&pdev->sem); //信號量P操作
if ( copy_from_user(&pdev->mem[*ppos] , buf , count)){
up(&pdev->sem); //退出前釋放信號量,V操作
return 0;
}else{
*ppos +=count;
up(&pdev->sem); //退出前釋放信號量,V操作
return count;
}
/*********************************************************/
}
module_init(my_init);
module_exit(my_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("");
3.5、互斥鎖
基于阻塞的互斥機(jī)制。
=適用場合:任務(wù)上下文之間且臨界區(qū)執(zhí)行時(shí)間較長時(shí)的互斥問題
3.5.1 函數(shù)
#include <linux/mutex.h>
struct mutex
原型:
struct mutex {
/* 1: unlocked, 0: locked, negative: locked, possible waiters */
atomic_t count;
spinlock_t wait_lock;
struct list_head wait_list;
#if defined(CONFIG_DEBUG_MUTEXES) || defined(CONFIG_SMP)
struct task_struct *owner;
#endif
#ifdef CONFIG_MUTEX_SPIN_ON_OWNER
void *spin_mlock; /* Spinner MCS lock */
#endif
#ifdef CONFIG_DEBUG_MUTEXES
const char *name;
void *magic;
#endif
#ifdef CONFIG_DEBUG_LOCK_ALLOC
struct lockdep_map dep_map;
#endif
};
參數(shù):
- count 來表示鎖定狀態(tài),
- wait_list保存等待的進(jìn)程,
- ,owner記錄mutex的持有者。
主要接口:
- void mutex_init(struct mutex *lock); 初始化mutex。
- void mutex_lock(struct mutex *lock); 獲得mutex,如果已加鎖則睡眠。
- int mutex_lock_interruptible(struct mutex *lock); 可中斷版本的mutex_lock。
- int mutex_trylock(struct mutex *lock); 嘗試獲得mutex,如果未獲得立即返回。
- void mutex_unlock(struct mutex *lock); 釋放mutex,喚醒等待進(jìn)程。
3.5.1.1 初始化
void mutex_init(struct mutex *lock)函數(shù)用于初始化一個(gè)mutex鎖。
#include <linux/mutex.h>
# define mutex_init(mutex) \
do { \
static struct lock_class_key __key; \
\
__mutex_init((mutex), #mutex, &__key); \
} while (0)
使用:
struct mutex my_mutex;
mutex_init(&my_mutex);
3.5.1.2 獲取互斥體
原型:
#include <linux/mutex.h>
void mutex_lock(struct mutex *lock);
void mutex_lock(struct mutex *lock)函數(shù)用于鎖住一個(gè)mutex鎖。它的主要功能是:
- 通過atomic_dec_if_positive()嘗試將mutex的count值減1,如果減1成功,則獲得鎖,函數(shù)返回。
- 如果count值減1失敗,則表示鎖已被其他進(jìn)程持有。此時(shí),調(diào)用會(huì)被加入到wait_list等待鏈表,并睡眠。
- 一旦鎖被釋放,睡眠的進(jìn)程會(huì)被喚醒,再次嘗試獲取鎖。重復(fù)該流程,直到獲得鎖為止。
- 獲取鎖后,lock->owner會(huì)設(shè)置為當(dāng)前進(jìn)程,用于調(diào)試目的。
mutex_lock()是獲取mutex鎖的最基本接口,它實(shí)現(xiàn)了等待已持有鎖的進(jìn)程,喚醒并重新調(diào)度等機(jī)制。理解這個(gè)接口有助于正確使用mutex實(shí)現(xiàn)同步互斥。
使用示例:
void func()
{
mutex_lock(&my_mutex); // 獲取鎖
// 此區(qū)域互斥
mutex_unlock(&my_mutex); // 釋放鎖
}
3.5.1.3 釋放互斥體
原型:
#include <linux/mutex.h>
void mutex_unlock(struct mutex *lock);
void mutex_unlock(struct mutex *lock)函數(shù)用于釋放一個(gè)mutex鎖。它的主要功能是:
- 通過atomic_inc()將mutex的count值增加1,表示釋放鎖。
- 如果wait_list不為空,則喚醒其中的第一個(gè)進(jìn)程。被喚醒的進(jìn)程將重新嘗試獲取鎖。
- mutex的owner成員被設(shè)置為NULL。這個(gè)成員僅在開啟相關(guān)配置時(shí)使用,用于調(diào)試目的。
- 如果mutex使用遞歸鎖定,則調(diào)用mutex_release()釋放鎖。這個(gè)函數(shù)將遞歸鎖定的count減1,如果減到0則真正釋放鎖。
使用步驟:
- 定義對應(yīng)類型的變量
- 初始化對應(yīng)變量
P/加鎖
臨界區(qū)
V/解鎖
3.5.2 范例
這里舉兩個(gè)使用mutex的例子:
- 保護(hù)共享資源
struct mutex my_mutex;
void shared_resource_access(void)
{
mutex_lock(&my_mutex);
// 操作共享資源
mutex_unlock(&my_mutex);
}
多個(gè)進(jìn)程通過mutex保護(hù)對共享資源的訪問,實(shí)現(xiàn)同步互斥。
2. 讀-寫鎖
struct mutex rw_mutex;
void reader(void)
{
mutex_lock(&rw_mutex); // 獲取讀鎖
// 讀操作
mutex_unlock(&rw_mutex); // 釋放讀鎖
}
void writer(void)
{
mutex_lock(&rw_mutex); // 獲取寫鎖,阻塞其他讀者和寫者
// 寫操作
mutex_unlock(&rw_mutex); // 釋放寫鎖,喚醒等待進(jìn)程
}
讀鎖和寫鎖通過同一個(gè)mutex實(shí)現(xiàn),但優(yōu)先級不同。寫鎖的請求會(huì)導(dǎo)致其他讀鎖和寫鎖等待,實(shí)現(xiàn)寫優(yōu)先。
3. 遞歸mutex
struct mutex recursive_mutex;
void recursive_func(void)
{
mutex_lock(&recursive_mutex);
// 遞歸調(diào)用自身
recursive_func();
mutex_unlock(&recursive_mutex);
}
遞歸mutex允許同一個(gè)進(jìn)程對其進(jìn)行多次加鎖。鎖需與解鎖次數(shù)匹配,否則會(huì)發(fā)生死鎖。
4、選擇并發(fā)控制機(jī)制的原則
-
不允許睡眠的上下文(異常上下文)需要采用忙等待類(自旋鎖,原子變量),可以睡眠的上下文可以采用阻塞類(信號量,互斥鎖)。在異常上下文中訪問的競爭資源一定采用忙等待類。
-
臨界區(qū)操作較長的應(yīng)用建議采用阻塞類,臨界區(qū)很短的操作建議采用忙等待類。
-
中斷屏蔽僅在有與中斷上下文共享資源時(shí)使用。文章來源:http://www.zghlxwxcb.cn/news/detail-419840.html
-
共享資源僅是一個(gè)簡單整型量時(shí)用原子變量文章來源地址http://www.zghlxwxcb.cn/news/detail-419840.html
到了這里,關(guān)于【嵌入式環(huán)境下linux內(nèi)核及驅(qū)動(dòng)學(xué)習(xí)筆記-(5-驅(qū)動(dòng)的并發(fā)控制機(jī)制)】的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!