linux知識(shí)點(diǎn)介紹:內(nèi)核并發(fā)和竟態(tài)
1.概念:并發(fā):多個(gè)執(zhí)行單元同時(shí)發(fā)生;注意:執(zhí)行單元包括硬件中斷、軟件中斷、多進(jìn)程(進(jìn)程的搶占通過中斷實(shí)現(xiàn)),
竟態(tài):并發(fā)的多個(gè)執(zhí)行單元同時(shí)訪問共享資源,引起的競(jìng)爭狀態(tài)
形成竟態(tài)條件:1一定要有并發(fā)情況2一定要有共享資源 硬件資源(小到寄存器的而某個(gè)bit位)軟件上的全局變量,例如open_cnt3并發(fā)的多個(gè)執(zhí)行單元要同時(shí)訪問共享資源
互斥訪問:當(dāng)多個(gè)執(zhí)行單元對(duì)共享資源進(jìn)行訪問時(shí),只能允許一個(gè)執(zhí)行單元對(duì)共享資源進(jìn)行訪問,其他執(zhí)行單元被禁止訪問!
互斥:指多個(gè)進(jìn)程不能同時(shí)使用同一個(gè)資源;
死鎖:指多個(gè)進(jìn)程互不相讓,都得不到足夠的資源;
饑餓:指一個(gè)進(jìn)程一直得不到資源(其他進(jìn)程可能輪流占用資源)
臨界資源:系統(tǒng)中某些資源一次只允許一個(gè)進(jìn)程使用,稱這樣的資源為臨界資源或互斥資源或共享變量
臨界區(qū):訪問共享資源的代碼區(qū)域,所以互斥訪問就是對(duì)臨界區(qū)的互斥訪問!
進(jìn)程的同步(直接制約):synchronism
指系統(tǒng)中一些進(jìn)程需要相互合作,共同完成一項(xiàng)任務(wù)。具體說,一個(gè)進(jìn)程運(yùn)行到某一點(diǎn)時(shí)要求另一伙伴進(jìn)程為它提供消息,在未獲得消息之前,該進(jìn)程處于等待狀態(tài),獲得消息后被喚醒進(jìn)入就緒態(tài)。同步是指在互斥的基礎(chǔ)上(大多數(shù)情況),通過其它機(jī)制實(shí)現(xiàn)訪問者對(duì)資源的有序訪問。在大多數(shù)情況下,同步已經(jīng)實(shí)現(xiàn)了互斥,特別是所有寫入資源的情況必定是互斥的。少數(shù)情況是指可以允許多個(gè)訪問者同時(shí)訪問資源。
進(jìn)程的互斥(間接制約)mutual exclusion
由于各進(jìn)程要求共享資源,而有些資源需要互斥使用,因此各進(jìn)程間競(jìng)爭使用這些資源,進(jìn)程的這種關(guān)系為進(jìn)程的互斥。某一資源同時(shí)只允許一個(gè)訪問者對(duì)其進(jìn)行訪問,具有唯一性和排它性。但互斥無法限制訪問者對(duì)資源的訪問順序,即訪問是無序的。
例如:
cpu讀取數(shù)據(jù)到寄存器,修改數(shù)據(jù),寫回?cái)?shù)據(jù)
static?int?open_cnt=1;
int?uart_open(struct?inode*inode,struct?file?*file){
???unsigned?long?flags;
???local_irq_save(flags);?//屏蔽中斷?
if(--open_cnt!=0)
{
printk("已經(jīng)被打開!n");
open_cnt++;
//local_irq_retore(flags);//使能中斷
rerturn?-EBUSY;
}
local_irq_retore(flags);//使能中斷
printk("打開成功n");
return?0;
}
由于linux內(nèi)核支持進(jìn)程之間的搶占,當(dāng)A在執(zhí)行以上臨界區(qū)時(shí),一定要進(jìn)行互斥訪問,不讓B進(jìn)程發(fā)生搶占情況,保證A的執(zhí)行路徑不被打斷!?
總結(jié):互斥訪問本質(zhì)上其實(shí)就是讓臨界區(qū)的而執(zhí)行路徑具有原子性(不可再發(fā)-不能被打斷)。
?
問題:如何做到一個(gè)執(zhí)行單元在訪問臨界區(qū)。其他執(zhí)行單元不被打斷正在訪問臨界區(qū)的執(zhí)行單元的路徑呢?強(qiáng)調(diào):執(zhí)行單元:中斷和進(jìn)程。
?
2.linux內(nèi)核產(chǎn)生竟態(tài)的情形
第一種是多核(多cpu),多個(gè)cpu他們共享系統(tǒng)總線,共享內(nèi)存,外存,系統(tǒng)io單只竟態(tài);
第二種是單cpu的進(jìn)程之間的搶占(必須具備搶占,搶占的原因是進(jìn)程能夠指定優(yōu)先級(jí)),
由于linux內(nèi)核支持進(jìn)程的搶占,多個(gè)進(jìn)程訪問共享資源,并且有搶占,也會(huì)產(chǎn)生竟態(tài);
第三種情形是中斷(硬件中斷,軟中斷)和進(jìn)程也會(huì)形成竟態(tài)(中斷的優(yōu)先級(jí)高于進(jìn)程)
第四種是中斷和中斷.硬件中斷優(yōu)先級(jí)高于軟中斷,軟中斷又分優(yōu)先級(jí)。
?
3.linux內(nèi)核解決竟態(tài)的方法:
這些方法的本質(zhì)目的就是讓臨界區(qū)的訪問具有原子性。
1.中斷屏蔽?2.原子操作?3.自旋鎖?4.信號(hào)量
1.中斷操作:屏蔽的硬件中斷和軟中斷
能夠解決竟態(tài)情形:1進(jìn)程與進(jìn)程之間的搶占(由于linux進(jìn)程的調(diào)度,搶占都是基于軟中斷實(shí)現(xiàn))2中斷和進(jìn)程?3中斷和中斷?中斷發(fā)生時(shí)會(huì)向多個(gè)cpu發(fā)送中斷信號(hào).
linux內(nèi)核提供相關(guān)的中斷屏蔽的方法:#include?
1.屏蔽中斷
unsigned?long?flsgs;
local_irq_disable();//屏蔽中斷
local_irq_save(floags);//屏蔽中斷并且保存中斷狀態(tài)到flags中
2.使能中斷
unsigned?long?flags;
local_irq_enable();//使能中斷
local_irq_restore(flags);?//使能中斷,并且從flags中恢復(fù)屏蔽中斷前保存的中斷狀態(tài)
?linux內(nèi)核中斷屏蔽的使用方法:
????1.臨界區(qū)之前屏蔽中斷
????2.執(zhí)行臨界區(qū),中斷不能被打斷,進(jìn)程的搶占也不能發(fā)生
????3.臨界區(qū)之后恢復(fù)中斷
2.原子操作:
筆試題:請(qǐng)實(shí)現(xiàn)將一個(gè)數(shù)的某個(gè)bit置1或者清0;?
第一個(gè):
int?data?=0x1234;data?|=(1<<5);data?&=~(1<<5);
第二個(gè)
void?set_bit(int?nbit,int?*data){
.....
}?
第三個(gè):
void?set_bit(int?nbit,void?*data){
?....
}?
第四個(gè):
#define?SET_BIT(nr,data)......
基于linux系統(tǒng)的參考答案(GNU?C)--析取c++的優(yōu)秀代碼:
inline?void?set_bit(int?nr,void?*data){
.....
}?
linux內(nèi)核原子操作:
原子操作能夠解決所有竟態(tài)問題:
原子操作分為:位原子操作?整形原子操作
1位原子操作:如果以后驅(qū)動(dòng)中對(duì)共享資源進(jìn)行位操作,并且為了避免竟態(tài)問題,一定要使用內(nèi)核提供的位原子操作的方法,保證位操作的方法,保證位操作的過程是原子的,不能自己去實(shí)現(xiàn)位操作,例如:
static?int?data;?//全局變量,共享資源?
//臨界區(qū)
data?|=(1<<5);?//這個(gè)代碼不是原子的,有可能被別的任務(wù)打斷!
如果不考慮多核引起的竟態(tài),還有一種通過中斷屏蔽(屏蔽只是單核)來解決以上代碼的竟態(tài)問題:
insigned?long?flags;
local_irq_save(flags);
data?|=(1<<5);
local_irq_restore(flags);
以上代碼無法表面多核引起的竟態(tài)!?
內(nèi)核提供的位原子操作的方法:
#include?
set_bit/clear_bit/change_bit/test_bit組合函數(shù)
Tset_and_set_bit/test_and_clear_bit/test_and_change_bit
對(duì)于data進(jìn)行位操作的正確方法
static?int?data;????//將data數(shù)據(jù)的第5位設(shè)置為1
set_bit(5,&data);?//這個(gè)代碼時(shí)原子的,不能被別的任務(wù)打斷?
注意:以上函數(shù)在多核cpu情況下,會(huì)使用兩條arm的原子指令:ldrex,strex,這個(gè)兩條保證在cpu那一級(jí)別能夠避免竟態(tài),以上函數(shù)都是采用c的內(nèi)嵌匯編來實(shí)現(xiàn),如果用c語言來實(shí)現(xiàn),編譯器肯定用ldr,str,但這個(gè)兩條指令不能避免竟態(tài)!
?
案例:利用位原子操作將0x5555->0xaaaa,不允許使用change_bit函數(shù)!取反操作
?
2整形原子操作:
如果以后驅(qū)動(dòng)程序中,涉及的共享資源是整型數(shù),就是原型要定義為char?short?int
long型的數(shù)據(jù),并且他們是共享資源,為了避免竟態(tài),可以考慮使用內(nèi)核提供的整型原子
操作機(jī)制來避免竟態(tài)問題。說白了就是將原先的char?short?int?long型換成atomic_t數(shù)據(jù)類型即可,然后配合內(nèi)核提供的整型原子操作的函數(shù)對(duì)整型變量進(jìn)行數(shù)學(xué)運(yùn)算。
?
整形原子變量數(shù)據(jù)類型定義#include?
typedef?struct{
volatile?int?counter;
}atomic_t;
?
如何使用:分配整型原子變量atomic_t?v;
進(jìn)行對(duì)整型變量的操作:
atomic_set/atomic_read/atomic_add/atomi_sub/atomic_inc/atomic_dec、/atomic_inc_and_test。。
對(duì)整型變量的操作一定要使用以上的函數(shù)進(jìn)行,保證具有原子性。不能使用如下代碼:
static?int?data;?//全局變量,共享資源?
//臨界區(qū)
data++;//不是原子的!有可能被打斷?
解決的方法:如果不考慮多核:
unsigned?long?flags;
local_irq_save(flags);
data++;
loacl_irq_restore(flags);
如果考慮多核:atomic?data;atomic_inc(&data);?
注意:以上整型原子操作的函數(shù),如果在多核情況下,他們的實(shí)現(xiàn)都是c的內(nèi)嵌匯編
來實(shí)現(xiàn)的,都調(diào)用了ldrex,strex來避免竟態(tài).
?
案例;實(shí)現(xiàn)led燈驅(qū)動(dòng),要求這個(gè)設(shè)備只能被一個(gè)應(yīng)用程序打開
分析:明確:app會(huì)調(diào)用open開發(fā)設(shè)備,close關(guān)閉設(shè)備
驅(qū)動(dòng):一定要提供對(duì)應(yīng)的底層open,close的實(shí)現(xiàn),注意不能省略
底層的這兩個(gè)函數(shù),因?yàn)樾枰诘讓拥膐pen,close函數(shù)中做一些用戶需求的代碼(設(shè)備只能被一個(gè)應(yīng)用程序打開)
方案:static?int?open_cnt;?//可以采用中斷
方案;static?atomic_t?open_cnt;?//利用整型原子操作 top查看進(jìn)程狀態(tài)
實(shí)驗(yàn)步驟:
1.insmod?led_drv.ko
2.cat?/proc/devices?//查看申請(qǐng)的主設(shè)備號(hào)
3.cat?/sys/class/myleds/myled/uevent?//查看創(chuàng)建設(shè)備文件的原材料
4.ls?/dev/myled?//查看設(shè)備文件
5../led_test?&、、啟動(dòng)a進(jìn)程,讓其后臺(tái)運(yùn)行,a進(jìn)程進(jìn)入休眠
6.ps?//查看a進(jìn)程的pid
7.top?//查看a進(jìn)程的狀態(tài)和cpu的利用率,內(nèi)存使用率
8../led_test?//啟動(dòng)b進(jìn)程
9.kill?a進(jìn)程的pid?//殺死a進(jìn)程
?
*******************************************************************************
自旋鎖:等于“自旋”?+?“鎖”
自旋鎖特點(diǎn):
1.自旋鎖一般要附加在共享資源上;類似光有自行車鎖沒有自行車是沒有意義的!
2.自旋鎖的“自旋”不是鎖在自旋,意思是想獲取的自旋鎖的執(zhí)行的單元,在沒有獲取自旋鎖的情況下,原地打轉(zhuǎn),忙著等待獲取自旋鎖;
3.一旦一個(gè)執(zhí)行單元獲取了自旋鎖,在執(zhí)行臨界區(qū)時(shí),不要進(jìn)行休眠操作。?“不夠意思”
4.自旋鎖也是讓臨界區(qū)的訪問具有原子性!
?
linux內(nèi)核如何描述一個(gè)自旋鎖#include?
Typedef?struct?{
Raw_spinlock_t?lock;
}spinlock_t;
Typedef?struct?{
Volatile?unsigned?int?lock;
}raw_spinlock_t;
如何使用自旋鎖來對(duì)臨界區(qū)進(jìn)行互斥訪問:
static?int?open_cnt;?//全局變量,共享資源
1.分配自旋鎖spinlock_t?lock
2.初始化自旋鎖spin_lock_init(&lock);
3.訪問臨界區(qū)之前獲取自旋鎖,進(jìn)行鎖定spin_lock(&lock);?//如何執(zhí)行單元獲取自旋鎖,函數(shù)立即返回,如果執(zhí)行單元沒有獲取鎖,執(zhí)行單元不會(huì)返回,而是原地打轉(zhuǎn)!處于忙等待,直到持有自旋鎖的執(zhí)行單元釋放自旋鎖或者:
spin_trylock(&lock);?//如果執(zhí)行單元獲取自旋鎖,函數(shù)返回true,如果沒有獲取咨詢所,返回false,不會(huì)原地打轉(zhuǎn)!??
4.執(zhí)行臨界區(qū)的代碼
if(--opencnt!=0){
.......
}
這個(gè)過程其他cpu或者本cpu的搶占進(jìn)程就無法執(zhí)行臨界區(qū),但是還會(huì)被中斷鎖打斷!如果考慮中斷的因素,要使用衍生自旋鎖。?
5.釋放自旋鎖
spin_unlock(&lock);?//獲取鎖的執(zhí)行單元釋放鎖,然后等待獲取鎖的執(zhí)行單元停止原地打轉(zhuǎn)而是獲取自旋鎖,然后開始對(duì)臨界資源的訪問。?
注意;以上自旋鎖的操作只能解決多cpu和本cpu的進(jìn)程搶占引起的竟態(tài),但是無法處理中斷(中斷和中斷底半部)引起的競(jìng)態(tài),如果考慮到中斷,必須采用衍生自旋鎖!
?
衍生自旋鎖本質(zhì)上其實(shí)就是在普通的自旋鎖的基礎(chǔ)上進(jìn)行屏蔽中斷和使能中斷的動(dòng)作。
衍生自旋鎖的使用:static?int?open_cnt;?//全局變量,共享資源
1.分配自旋鎖spinlock_t?lock
2.初始化自旋鎖spin_lock_init(&lock);
3.訪問臨界區(qū)之前獲取自旋鎖,進(jìn)行鎖定
spin_lock_irq(&lock);?//屏蔽中斷,獲取自旋鎖
=local_irq_disable()?+?spin_lock()
或者spin_lock_irqsave(&lock,flags);?//屏蔽中斷,保存中斷狀態(tài),獲取自旋鎖
=?lock_irq_save()?+?spin_lock();?
4.執(zhí)行臨界區(qū)的代碼
if(--opencnt!=0){
.......
}
5.釋放自旋鎖spin_unlock_irq(*lock);?//釋放自旋鎖,使能中斷
Local_irq_disable()?+?spin_lock();
或者spin_unlock_irqrestore(&lock,flags);??//釋放自旋鎖,使能中斷,保存中斷狀態(tài)=?spin_unlock()?+?local_irq_restore();?
注意:衍生自旋鎖能夠解決所有的竟態(tài)問題。
?
自旋鎖使用的注意事項(xiàng):
1.? 一旦獲取自旋鎖,臨界區(qū)的執(zhí)行速度要快,不能做休眠動(dòng)作。由于獲取鎖是一直等待,所以臨界區(qū)較大或有共享設(shè)備的時(shí)候,使用自旋鎖會(huì)降低系統(tǒng)性能.
2.??????自旋鎖可能導(dǎo)致死鎖:--遞歸調(diào)用,也就是已經(jīng)擁有自旋鎖的CPU想要第二次獲取鎖
--獲取自旋鎖之后再被阻塞,所以,再自旋鎖占有期間,不能調(diào)用可能引起阻塞的函數(shù):如kammoc(),copy_from_uesr()等.?
案例:利用自旋鎖,來實(shí)現(xiàn)一個(gè)設(shè)備只能被一個(gè)應(yīng)用程序打開
******************************************************************
linux系統(tǒng)進(jìn)程的狀態(tài):三個(gè)狀態(tài)?
1.進(jìn)程的運(yùn)行狀態(tài),linux系統(tǒng)描述運(yùn)行中的進(jìn)程通過TASK_RUNNING宏來表示!
2.進(jìn)程的準(zhǔn)備就緒狀態(tài),linux系統(tǒng)描述準(zhǔn)備就緒用TASK_READY來表示.
3.進(jìn)程的休眠狀態(tài),進(jìn)程的休眠狀態(tài)又分可中斷的休眠狀態(tài)和不可休眠的。
3.1不可中斷休眠狀態(tài),linux系統(tǒng)用TASK_UNINTERRUPTIBLE來表示,睡眠期間如果
接受到了信號(hào),不會(huì)立即處理信號(hào),但是喚醒以后會(huì)判斷是否接受到信號(hào),有,處理信號(hào)。
3.2可中斷的休眠狀態(tài),linux系統(tǒng)用TASK_INTERRUPTIBLE,在休眠期間,如果接受到了信號(hào),會(huì)被信號(hào)喚醒,并且立即處理信號(hào)。
?
信號(hào)量--不可以在中斷上下文使用(因?yàn)闀?huì)導(dǎo)致休眠)
由于自旋鎖在使用的時(shí)候,要求臨界區(qū)不能做休眠操作,但是在某些場(chǎng)合需要在臨界區(qū)做休眠操作,又要考慮竟態(tài)問題,此時(shí)可以使用信號(hào)量來保護(hù)臨界區(qū)。
信號(hào)量的特定:1.又叫”睡眠鎖“,內(nèi)核的信號(hào)量在概念和原理上與用戶態(tài)的信號(hào)量是一樣的,但是它不能再內(nèi)核之外使用;
2.如果一個(gè)執(zhí)行但愿你想要獲取信號(hào)量,如果信號(hào)量已經(jīng)被別的執(zhí)行單元所持有,那么這個(gè)執(zhí)行單元將會(huì)進(jìn)入休眠狀態(tài);直到持有信號(hào)量的執(zhí)行單元釋放信號(hào)量為止。
3.已經(jīng)獲取信號(hào)量的執(zhí)行單元在執(zhí)行臨界區(qū)的代碼時(shí),也可以進(jìn)行休眠操作!
4.明確信號(hào)量能讓進(jìn)程放入一個(gè)等待隊(duì)列中,然后讓其進(jìn)行休眠操作,
?
linux內(nèi)核描述信號(hào)量的數(shù)據(jù)類型:#include?
struct?semaphore{
spinlock_t?lock;
unsigned?int?count;
struct?list_head?wait_list;
};
一般使用方法:DECLARE_MUTEX(semm);//定義一個(gè)互斥信號(hào)量
..
Down(&semm);//獲取信號(hào)量,保護(hù)臨界區(qū)
Critical section code; //執(zhí)行臨界區(qū)代碼
Up(&semm); //釋放信號(hào)量?
如何使用信號(hào)量:1.分配信號(hào)量struct?semaphore?sema;
2.初始化信號(hào)量為互斥信號(hào)量init_MUTEX(&sema,1);
3.在訪問臨界區(qū)之前獲取信號(hào)量,對(duì)臨界區(qū)進(jìn)行鎖定
dowm(&sema);?//獲取信號(hào)量?,如果信號(hào)量已經(jīng)被別的任務(wù)給持有,d為不可中斷的信號(hào)狀態(tài)那么,進(jìn)程將進(jìn)入不可中斷的休眠狀態(tài);不能使用在中斷上下文。
或者:dowm_interruptible(&sema);?//獲取信號(hào)量,如果信號(hào)量已經(jīng)被別的任務(wù)給持有,那么進(jìn)程將進(jìn)入可中斷的休眠狀態(tài);一般在使用的時(shí)候一定要對(duì)這個(gè)函數(shù)的返回值進(jìn)行判斷,如果函數(shù)返回0,表明進(jìn)程正常獲取信號(hào)量,然后訪問臨界區(qū);如果函數(shù)返回非0,表明進(jìn)程是由于接受到了信號(hào)引起的喚醒:
if(dowm_interruptible(&sema)){
printk("進(jìn)程被喚醒的原因是接受到了信號(hào)n");
return?-EINTR;
}
else?{
printk("正常獲取信號(hào)量引起的喚醒");
printk("進(jìn)程可以訪問臨界區(qū)");
}?或者:
dowm_trylock(&sema);?//獲取信號(hào)量,如果沒有獲取信號(hào)量,返回false,
如果獲取信號(hào)量,返回true。所以對(duì)返回值也要做判斷
if(dowm_trylock(&sema)){
printk("無法獲取信號(hào)量");
return?-EBUSY;
}else?{
printk("獲取信號(hào)量");
printk("訪問臨界區(qū)");
}
4.訪問臨界區(qū)?
5.釋放信號(hào)量up(&sema);?//一方面釋放信號(hào)量,另一方面還要喚醒之前休眠的進(jìn)程?
案例;采用信號(hào)量,實(shí)現(xiàn)一個(gè)設(shè)備只能被一個(gè)應(yīng)用程序打開;?一般設(shè)置為可中斷的狀態(tài)
對(duì)于這個(gè)案例
gpc0_3
gpio_config{
//gpio共享資源?互斥訪問
unsigned?long?flags;?
local_irq_save(flags);
gpio_set_vluae(0);
udelay(5);?
gpio_set_vluae(1);
udelay(10);
gpio_set_vluae(0);
udelay(15);
........
local_irq_restore(flags);
}