|
-----------------------------------------------------------------
工作之后,比較少時(shí)間看書學(xué)習(xí),也比較少時(shí)間做筆記。
買下這本書,雖然和自己預(yù)想的有些出入,但仍對(duì)自己有蠻大的幫助。
自己買了很多的書都只是看了一點(diǎn)就丟在一旁,浪費(fèi)啊。。。這次可不能浪費(fèi)了。。。
回首2014,發(fā)現(xiàn)自己獲得的很少,原因在于自己不懂充分利用好時(shí)間,所以今年需要充分利用好時(shí)間,更加的努力,吼吼吼,聚集正能量。。。
-----------------------------------------------------------------
花了一個(gè)星期上下班的時(shí)間,看到了RCU機(jī)制,原以為今天下午能寫完 原子操作、自旋鎖、讀寫鎖、順序鎖、RCU的筆記,沒想到當(dāng)真正實(shí)際上機(jī)操作時(shí),發(fā)現(xiàn)那些自認(rèn)為已經(jīng)理解的知識(shí)點(diǎn)在機(jī)器上得不到證實(shí)。看來(lái)不能光看書,還得要多多實(shí)踐實(shí)踐。
-----------------------------------------------------------------
第三篇 Linux 驅(qū)動(dòng)開發(fā)高級(jí)技術(shù)
----------------------------------------------------------------
第11章 Linux 驅(qū)動(dòng)程序中的并發(fā)控制
驅(qū)動(dòng)程序是為上層服務(wù)的,一個(gè)驅(qū)動(dòng)程序可能會(huì)被多個(gè)應(yīng)用進(jìn)程同時(shí)使用。
一、原子操作:即不可再細(xì)分的操作,最小的執(zhí)行單位,在操作完之前都不會(huì)被任何事件中斷。
整型原子操作:對(duì)int類型的操作變成原子操作。
int i = 0;
i = i + 2; <--- 轉(zhuǎn)換為匯編時(shí),不止一條語(yǔ)句,所以可能會(huì)被中斷。
數(shù)據(jù)類型:atomic_t 在 linux/types.h 中定義。
typedef struct
{
int counter;
}atomic_t;
atomic_t.counter的變量值變化就是原子的,當(dāng)然我們不能直接去讀寫這個(gè)變量值,要使用一些函數(shù)才能對(duì)它進(jìn)行操作,
這些函數(shù)都是圍繞著 atomic_t.counter 變量的修改、獲取而設(shè)計(jì)的。
示例:
/* 一般定義為全局變量 */
atomic_t n = ATOMIC_INIT(3); // 將變量 n.counter 的初始值設(shè)為 3 --> n.counter = 3
n.counter = 10; // 這種寫法沒有意義,它并不是原子操作。
atomic_set(&n, 2); // 將變量 n.counter 的初始值設(shè)為 2 --> n.counter = 2
atomic_add(5, &n); // 將變量 n.counter 的值加上 5 --> n.counter += 5
atomic_dec(&n); // 將變量 n.counter 的值減 1 --> n.counter -= 1
printk("n = %d", atomic_read(&n)); // 讀取變量 n.counter 的值 此時(shí) n.counter == 6
接口:
32位整型原子操作的其他的函數(shù)(列出,方便查詢):
ATOMIC_INIT(int i) 宏 用 i 初始化 atomic_t 類型的變量
int atomic_read(atomic_t *v) 宏 讀 v 的值
void atomic_set(atomic_t *v, int i); 宏 設(shè) v 的值為 i
void atomic_add(int i, atomic_t *v); 宏 將 v 的值加 i
void atomic_sub(int i, atomic_t *v); 宏 將 v 的值減 i
void atomic_inc(atomic_t *v); 宏 將 v 的值加 1
void atomic_dec(atomic_t *v); 宏 將 v 的值減 1
int atomic_sub_and_test(int i, atomic_t *v); 宏 將 v 的值減 i,(0==v) ? 非0值 : 0;
int atomic_inc_and_test(atomic_t *v); 宏 將 v 的值加 1,(0==v) ? 非0值 : 0;
int atomic_dec_and_test(atomic_t *v); 宏 將 v 的值減 1,(0==v) ? 非0值 : 0;
int atomic_add_negative(int i, atomic_t *v); 宏 將 v 的值加 1,(v<0) ? 非0值 : 0;
int atomic_add_return(int i, atomic_t *v); 函 將 v 的值加 i,并返回 +i 后的結(jié)果
int atomic_sub_return(int i, atomic_t *v); 函 將 v 的值減 i,并返回 -i 后的結(jié)果
int atomic_inc_return(atomic_t *v); 宏 將 v 的值加 1,并返回 +1 后的結(jié)果
int atomic_dec_return(atomic_t *v); 宏 將 v 的值減 1,并返回 -1 后的結(jié)果
int atomic_add_unless(atomic_t *v, int a, int u); 涵 ( v!=u ) ? v+a,返回非0值 : 0;
int atomic_inc_not_zero(atomic_t *v); 宏 ( v!=0 ) ? v+1,返回非0值 : 0;
64位整型原子操作:和32位整型原子操作一致,所操作的接口只是名稱不同,功能一致。
ATOMIC64_INIT(int i) 宏 用 i 初始化 atomic_t 類型的變量
int atomic64_read(atomic_t *v) 宏 讀 v 的值
void atomic64_set(atomic_t *v, int i); 宏 設(shè) v 的值為 i
void atomic64_add(int i, atomic_t *v); 宏 將 v 的值加 i
void atomic64_sub(int i, atomic_t *v); 宏 將 v 的值減 i
void atomic64_inc(atomic_t *v); 宏 將 v 的值加 1
void atomic64_dec(atomic_t *v); 宏 將 v 的值減 1
...
...
注意:
32位整型原子操作在64位下執(zhí)行不會(huì)有問題,但是64位整型原子操作在32位系統(tǒng)下執(zhí)行會(huì)造成難以預(yù)料的后果。
為了讓自己的驅(qū)動(dòng)程序通用,若非必要?jiǎng)t盡量使用32位整型原子操作。
位原子操作:
這種操作的數(shù)據(jù)類型是 unsigned long, 32位系統(tǒng)下為32bit,64位系統(tǒng)下為64bit。
位原子操作函數(shù)主要功能是將 unsigned long 變量中的指定位設(shè)為0或設(shè)為1。
示例:
unsigned long value = 0;
// 設(shè)置 value 的第0位為1, value = 0000000000000000 0000000000000001
set_bit(0, &value);
// 設(shè)置 value 的第2位為1, value = 0000000000000000 0000000000000101
set_bit(2, &value);
// 設(shè)置 value 的第0位為0, value = 0000000000000000 0000000000000100
clear_bit(0, &value);
// 將 value 的第0位取反,第0位為1則設(shè)為0,為0則設(shè)為1
change_bit(0, &value);
接口:都是宏
void set_bit(int nr, void *addr); 將addr的第nr位設(shè)為 1
void clear_bit(int nr, void *addr); 將addr的第nr位設(shè)為 0
void change_bit(int nr, void *addr); 將addr的第nr位取反
int test_bit(int nr, void *addr); 如果addr的第nr位為1則返回非0值,否則返回0
int test_and_set_bit(int nr, void *addr); 將addr的第nr位設(shè)為 1,設(shè)置之前該位為1則返回非0值,否則返回0
int test_and_clear_bit(int nr, void *addr); 將addr的第nr位設(shè)為 0,設(shè)置之前該位為1則返回非0值,否則返回0
int test_and_change_bit(int nr, void *addr);將addr的第nr位設(shè)取反,設(shè)置之前該位為1則返回非0值,否則返回0
總結(jié):
整型原子操作和位原子操作都是圍繞一個(gè)變量的操作做為原子操作。
可以用來(lái)限定設(shè)備能被幾個(gè)進(jìn)程操作,和作為計(jì)數(shù)器使用。
實(shí)例:(例如操作打印機(jī))
#define DevNumber 1
atomic_t v = ATOMIC_INIT(DevNumber); // 限定1個(gè)
// 打開打印機(jī)設(shè)備
int OpenPrinterDevice(unsigned char *buf, unsigned int size)
{
// 將v減1后,判斷v是否為0
if(atomic_dec_and_test(v))
{
// v 為 0,表示成功得到操作權(quán)限
return 0;
}
else
{
// 表示 設(shè)備已經(jīng)被占用。
return -EBUSY;
}
}
// 釋放打印機(jī)設(shè)備
void ClosePrinterDevice(void)
{
atomic_set(&v, DevNumber);
}
二、自旋鎖
原子操作可以讓指定變量的操作是原子的。很多時(shí)候我們?cè)谔幚硪恍⿺?shù)據(jù)執(zhí)行某些動(dòng)作的時(shí)候要保證執(zhí)行過程中
不能被中斷,要求是原子的,而整型、位原子操作要實(shí)現(xiàn)這種需求就會(huì)比較復(fù)雜一些。而使用自旋鎖則簡(jiǎn)單很多。
示例:
/* 一般定義為全局變量 */
spinlock_t lock; // 定義一把自旋鎖
spin_lock_init(&lock); // 初始化這把自旋鎖
或者使用宏來(lái)定義并初始化 DEFINE_SPINLOCK(lock)
void MyLock()
{
/* 使用場(chǎng)合:中斷下半部與中斷服務(wù)程序不會(huì)進(jìn)入臨界區(qū) */
spin_lock(&lock); // 獲取并上鎖
// ... <--- 關(guān)閉了內(nèi)核的搶占,但仍受硬中斷和中斷下半部的影響
// 臨界區(qū)代碼
// ...
spin_unlock(&lock); // 釋放解鎖,恢復(fù)內(nèi)核的搶占
}
若中斷處理函數(shù)中需要訪問上面的臨界區(qū),當(dāng)lock鎖未被釋放,同時(shí)中斷產(chǎn)生:
void MyIRQ(void) // 產(chǎn)生中斷
{
spin_lock(&lock); // 由于該鎖未被釋放,所以中斷服務(wù)參數(shù)就會(huì)一直自旋(雙重請(qǐng)求)
// ... // 而中斷服務(wù)未退出 就無(wú)法退回MyLock(),就無(wú)法釋放鎖造成死鎖
// 臨界區(qū)代碼
// ...
spin_unlock(&lock); // 釋放解鎖,恢復(fù)內(nèi)核的搶占
}
這種情況下需要采用以下的方式上鎖:
void MyLockIrq()
{
/*使用場(chǎng)合:
1、中斷服務(wù)函數(shù)與中斷下半部都需要進(jìn)入該臨界區(qū)
2、中斷服務(wù)函數(shù)需要進(jìn)入該臨界區(qū)
*/
spin_lock_irq(&lock); // 獲取并上鎖 臨界區(qū)的內(nèi)容可能會(huì)被
// ... <--- 關(guān)閉了內(nèi)核的搶占以及硬件中斷響應(yīng),軟中斷依賴硬件中斷,自然也不生效。
// 臨界區(qū)代碼
// ...
spin_unloc_irq(&lock); // 釋放解鎖,恢復(fù)內(nèi)核的搶占以及硬件中斷,中斷下半部也有效
}
如果訪問臨界區(qū)的資源的代碼不是放在中斷服務(wù)函數(shù)中,而是放在中斷下半部也會(huì)出現(xiàn)相似的情況,
即在MyLock()上鎖之后,產(chǎn)生一個(gè)硬件中斷,當(dāng)執(zhí)行完中斷服務(wù)函數(shù)之后就可能會(huì)繼續(xù)執(zhí)行中斷下
半部的代碼,因?yàn)樗梢該屨歼M(jìn)程上下文,而低半部要獲取的鎖已經(jīng)被MyLock()上鎖,形成死鎖。
這種情況也可以用void MyLockIrq()這種方式,但是最好用一下方式,更快:
void MyLockBh()
{
/* 使用場(chǎng)合:中斷下半部與進(jìn)程上下文都需要進(jìn)入該臨界代碼 */
spin_lock_bh(&lock); // 獲取并上鎖
// ... <--- 關(guān)閉了內(nèi)核的搶占以及中斷下半部,但受硬件中斷影響
// 臨界區(qū)代碼
// ...
spin_unloc_bh(&lock); // 釋放解鎖,恢復(fù)內(nèi)核的搶占以及中斷下半部
}
對(duì)于一個(gè)CPU的機(jī)器來(lái)說:當(dāng)有A、B進(jìn)程都要執(zhí)行臨界區(qū)的代碼時(shí),假設(shè)A先獲得鎖之后,B進(jìn)程不會(huì)被調(diào)度,
系統(tǒng)呈現(xiàn)假死狀態(tài), 只有當(dāng)A釋放鎖之后,B進(jìn)程才會(huì)被調(diào)度再去獲取鎖,此時(shí)A已經(jīng)釋放鎖,所以B也就順利得到鎖。
對(duì)于兩個(gè)CPU的機(jī)器來(lái)說:當(dāng)有A、B進(jìn)程都要執(zhí)行臨界區(qū)的代碼時(shí),假設(shè)A先獲得鎖之后,B進(jìn)程也會(huì)去獲取鎖,
但是鎖已經(jīng)被A得到,那么B進(jìn)程則會(huì)一直不停的循環(huán)檢測(cè)鎖是否被釋放,此時(shí)系統(tǒng)會(huì)呈現(xiàn)假死狀態(tài)。
(這些現(xiàn)象可以在VM虛擬機(jī)上驗(yàn)證,VM虛擬機(jī)可以調(diào)整CPU個(gè)數(shù))
A進(jìn)程:
spin_lock(&lock); // 獲取并上鎖
// ...
// 臨界區(qū)代碼 <--- A在執(zhí)行臨界區(qū)代碼 ---cpu0
// ...
spin_unlock(&lock); // 釋放解鎖
B進(jìn)程:
spin_lock(&lock); // <--- 阻塞這里,一直在spin_lock內(nèi)部不停的循環(huán)等待 ---cpu1
// ...
// 臨界區(qū)代碼
// ...
spin_unlock(&lock); // 釋放解鎖
也就說,只有一個(gè)進(jìn)程能進(jìn)入臨界區(qū),其他進(jìn)程要想進(jìn)入臨界區(qū)只能自己在原地循環(huán)旋轉(zhuǎn)等待。
使用注意事項(xiàng):
1、自旋鎖實(shí)際上是忙等待,因?yàn)樵诘却i的時(shí)候是在不停的循環(huán)等待,長(zhǎng)時(shí)間占用鎖會(huì)極大降低系統(tǒng)性能。
2、要避免在臨界區(qū)中調(diào)用可能會(huì)產(chǎn)生睡眠的函數(shù),因?yàn)榇藭r(shí)搶占、中斷已經(jīng)關(guān)閉,無(wú)法被喚醒導(dǎo)致無(wú)法解鎖。
3、若數(shù)據(jù)被軟中斷共享,也需要加鎖,因?yàn)樵诓煌幚砥魃洗嬖谲浿袛嗤瑫r(shí)執(zhí)行問題。
4、注意避免死鎖,例如上述例子,A進(jìn)程獲得了鎖之后,又繼續(xù)獲取該鎖,因?yàn)樵撴i已經(jīng)被A獲取,
所以該鎖無(wú)法再次被A獲取,A就會(huì)一直循環(huán)打轉(zhuǎn)等待,A沒有機(jī)會(huì)釋放該鎖,該CPU被鎖死,
對(duì)于多顆CPU來(lái)說,其他進(jìn)程又無(wú)法釋放該鎖,形成死循環(huán),導(dǎo)致死機(jī)。
A進(jìn)程:
spin_lock(&lock); // 獲取并上鎖 關(guān)閉了內(nèi)核的搶占
spin_lock(&lock); // <--- 阻塞這里,一直在spin_lock內(nèi)部不停的循環(huán) cpu被鎖死
// ...
// 臨界區(qū)代碼 <--- 無(wú)法得到執(zhí)行
// ...
spin_unlock(&lock); // 沒有機(jī)會(huì)釋放
|
|