|
-----------------------------------------------------------------
工作之后,比較少時間看書學習,也比較少時間做筆記。
買下這本書,雖然和自己預想的有些出入,但仍對自己有蠻大的幫助。
自己買了很多的書都只是看了一點就丟在一旁,浪費啊。。。這次可不能浪費了。。。
回首2014,發(fā)現(xiàn)自己獲得的很少,原因在于自己不懂充分利用好時間,所以今年需要充分利用好時間,更加的努力,吼吼吼,聚集正能量。。。
-----------------------------------------------------------------
花了一個星期上下班的時間,看到了RCU機制,原以為今天下午能寫完 原子操作、自旋鎖、讀寫鎖、順序鎖、RCU的筆記,沒想到當真正實際上機操作時,發(fā)現(xiàn)那些自認為已經(jīng)理解的知識點在機器上得不到證實。看來不能光看書,還得要多多實踐實踐。
-----------------------------------------------------------------
第三篇 Linux 驅(qū)動開發(fā)高級技術(shù)
----------------------------------------------------------------
第11章 Linux 驅(qū)動程序中的并發(fā)控制
驅(qū)動程序是為上層服務的,一個驅(qū)動程序可能會被多個應用進程同時使用。
一、原子操作:即不可再細分的操作,最小的執(zhí)行單位,在操作完之前都不會被任何事件中斷。
整型原子操作:對int類型的操作變成原子操作。
int i = 0;
i = i + 2; <--- 轉(zhuǎn)換為匯編時,不止一條語句,所以可能會被中斷。
數(shù)據(jù)類型:atomic_t 在 linux/types.h 中定義。
typedef struct
{
int counter;
}atomic_t;
atomic_t.counter的變量值變化就是原子的,當然我們不能直接去讀寫這個變量值,要使用一些函數(shù)才能對它進行操作,
這些函數(shù)都是圍繞著 atomic_t.counter 變量的修改、獲取而設(shè)計的。
示例:
/* 一般定義為全局變量 */
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 的值 此時 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í)行不會有問題,但是64位整型原子操作在32位系統(tǒng)下執(zhí)行會造成難以預料的后果。
為了讓自己的驅(qū)動程序通用,若非必要則盡量使用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é):
整型原子操作和位原子操作都是圍繞一個變量的操作做為原子操作。
可以用來限定設(shè)備能被幾個進程操作,和作為計數(shù)器使用。
實例:(例如操作打印機)
#define DevNumber 1
atomic_t v = ATOMIC_INIT(DevNumber); // 限定1個
// 打開打印機設(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;
}
}
// 釋放打印機設(shè)備
void ClosePrinterDevice(void)
{
atomic_set(&v, DevNumber);
}
二、自旋鎖
原子操作可以讓指定變量的操作是原子的。很多時候我們在處理一些數(shù)據(jù)執(zhí)行某些動作的時候要保證執(zhí)行過程中
不能被中斷,要求是原子的,而整型、位原子操作要實現(xiàn)這種需求就會比較復雜一些。而使用自旋鎖則簡單很多。
示例:
/* 一般定義為全局變量 */
spinlock_t lock; // 定義一把自旋鎖
spin_lock_init(&lock); // 初始化這把自旋鎖
或者使用宏來定義并初始化 DEFINE_SPINLOCK(lock)
void MyLock()
{
/* 使用場合:中斷下半部與中斷服務程序不會進入臨界區(qū) */
spin_lock(&lock); // 獲取并上鎖
// ... <--- 關(guān)閉了內(nèi)核的搶占,但仍受硬中斷和中斷下半部的影響
// 臨界區(qū)代碼
// ...
spin_unlock(&lock); // 釋放解鎖,恢復內(nèi)核的搶占
}
若中斷處理函數(shù)中需要訪問上面的臨界區(qū),當lock鎖未被釋放,同時中斷產(chǎn)生:
void MyIRQ(void) // 產(chǎn)生中斷
{
spin_lock(&lock); // 由于該鎖未被釋放,所以中斷服務參數(shù)就會一直自旋(雙重請求)
// ... // 而中斷服務未退出 就無法退回MyLock(),就無法釋放鎖造成死鎖
// 臨界區(qū)代碼
// ...
spin_unlock(&lock); // 釋放解鎖,恢復內(nèi)核的搶占
}
這種情況下需要采用以下的方式上鎖:
void MyLockIrq()
{
/*使用場合:
1、中斷服務函數(shù)與中斷下半部都需要進入該臨界區(qū)
2、中斷服務函數(shù)需要進入該臨界區(qū)
*/
spin_lock_irq(&lock); // 獲取并上鎖 臨界區(qū)的內(nèi)容可能會被
// ... <--- 關(guān)閉了內(nèi)核的搶占以及硬件中斷響應,軟中斷依賴硬件中斷,自然也不生效。
// 臨界區(qū)代碼
// ...
spin_unloc_irq(&lock); // 釋放解鎖,恢復內(nèi)核的搶占以及硬件中斷,中斷下半部也有效
}
如果訪問臨界區(qū)的資源的代碼不是放在中斷服務函數(shù)中,而是放在中斷下半部也會出現(xiàn)相似的情況,
即在MyLock()上鎖之后,產(chǎn)生一個硬件中斷,當執(zhí)行完中斷服務函數(shù)之后就可能會繼續(xù)執(zhí)行中斷下
半部的代碼,因為它可以搶占進程上下文,而低半部要獲取的鎖已經(jīng)被MyLock()上鎖,形成死鎖。
這種情況也可以用void MyLockIrq()這種方式,但是最好用一下方式,更快:
void MyLockBh()
{
/* 使用場合:中斷下半部與進程上下文都需要進入該臨界代碼 */
spin_lock_bh(&lock); // 獲取并上鎖
// ... <--- 關(guān)閉了內(nèi)核的搶占以及中斷下半部,但受硬件中斷影響
// 臨界區(qū)代碼
// ...
spin_unloc_bh(&lock); // 釋放解鎖,恢復內(nèi)核的搶占以及中斷下半部
}
對于一個CPU的機器來說:當有A、B進程都要執(zhí)行臨界區(qū)的代碼時,假設(shè)A先獲得鎖之后,B進程不會被調(diào)度,
系統(tǒng)呈現(xiàn)假死狀態(tài), 只有當A釋放鎖之后,B進程才會被調(diào)度再去獲取鎖,此時A已經(jīng)釋放鎖,所以B也就順利得到鎖。
對于兩個CPU的機器來說:當有A、B進程都要執(zhí)行臨界區(qū)的代碼時,假設(shè)A先獲得鎖之后,B進程也會去獲取鎖,
但是鎖已經(jīng)被A得到,那么B進程則會一直不停的循環(huán)檢測鎖是否被釋放,此時系統(tǒng)會呈現(xiàn)假死狀態(tài)。
(這些現(xiàn)象可以在VM虛擬機上驗證,VM虛擬機可以調(diào)整CPU個數(shù))
A進程:
spin_lock(&lock); // 獲取并上鎖
// ...
// 臨界區(qū)代碼 <--- A在執(zhí)行臨界區(qū)代碼 ---cpu0
// ...
spin_unlock(&lock); // 釋放解鎖
B進程:
spin_lock(&lock); // <--- 阻塞這里,一直在spin_lock內(nèi)部不停的循環(huán)等待 ---cpu1
// ...
// 臨界區(qū)代碼
// ...
spin_unlock(&lock); // 釋放解鎖
也就說,只有一個進程能進入臨界區(qū),其他進程要想進入臨界區(qū)只能自己在原地循環(huán)旋轉(zhuǎn)等待。
使用注意事項:
1、自旋鎖實際上是忙等待,因為在等待鎖的時候是在不停的循環(huán)等待,長時間占用鎖會極大降低系統(tǒng)性能。
2、要避免在臨界區(qū)中調(diào)用可能會產(chǎn)生睡眠的函數(shù),因為此時搶占、中斷已經(jīng)關(guān)閉,無法被喚醒導致無法解鎖。
3、若數(shù)據(jù)被軟中斷共享,也需要加鎖,因為在不同處理器上存在軟中斷同時執(zhí)行問題。
4、注意避免死鎖,例如上述例子,A進程獲得了鎖之后,又繼續(xù)獲取該鎖,因為該鎖已經(jīng)被A獲取,
所以該鎖無法再次被A獲取,A就會一直循環(huán)打轉(zhuǎn)等待,A沒有機會釋放該鎖,該CPU被鎖死,
對于多顆CPU來說,其他進程又無法釋放該鎖,形成死循環(huán),導致死機。
A進程:
spin_lock(&lock); // 獲取并上鎖 關(guān)閉了內(nèi)核的搶占
spin_lock(&lock); // <--- 阻塞這里,一直在spin_lock內(nèi)部不停的循環(huán) cpu被鎖死
// ...
// 臨界區(qū)代碼 <--- 無法得到執(zhí)行
// ...
spin_unlock(&lock); // 沒有機會釋放
|
|