大連模版網(wǎng)站seo服務(wù)優(yōu)化
嵌入式Linux應(yīng)用開發(fā)-驅(qū)動(dòng)大全-第一章同步與互斥②
- 第一章 同步與互斥②
- 1.3 原子操作的實(shí)現(xiàn)原理與使用
- 1.3.1 原子變量的內(nèi)核操作函數(shù)
- 1.3.2 原子變量的內(nèi)核實(shí)現(xiàn)
- 1.3.2.1 ATOMIC_OP在 UP系統(tǒng)中的實(shí)現(xiàn)
- 1.3.2.2 ATOMIC_OP在 SMP系統(tǒng)中的實(shí)現(xiàn)
- 1.3.3 原子變量使用案例
- 1.3.4 原子位介紹
- 1.3.4.1 原子位的內(nèi)核操作函數(shù)
第一章 同步與互斥②
1.3 原子操作的實(shí)現(xiàn)原理與使用
在上面的第 2個(gè)失敗例子里,問題在于對 valid變量的修改被打斷了。如果對 valid變量的操作不能被打斷,就解決這個(gè)問題了。
這可以使用原子操作,所謂“原子操作”就是這個(gè)操作不會(huì)被打斷。Linux有 2種原子操作:原子變量、原子位。
1.3.1 原子變量的內(nèi)核操作函數(shù)
原子變量的操作函數(shù)在 Linux內(nèi)核文件 arch\arm\include\asm\atomic.h中。
原子變量類型如下,實(shí)際上就是一個(gè)結(jié)構(gòu)體(內(nèi)核文件 include/linux/types.h):
特殊的地方在于它的操作函數(shù),如下(下表中 v都是 atomic_t指針):
1.3.2 原子變量的內(nèi)核實(shí)現(xiàn)
注意:SMP就是 Symmetric Multi-Processors,對稱多處理器;UP即 Uni-Processor,系統(tǒng)只有一個(gè)單核 CPU。
這些函數(shù)都是在 Linux內(nèi)核文件 arch\arm\include\asm\atomic.h中。
atomic_read,atomic_set這些操作都只需要一條匯編指令,所以它們本身就是不可打斷的。
問題在于 atomic_inc這類操作,要讀出、修改、寫回。
以 atomic_inc為例,在 atomic.h文件中,如下定義:
#define atomic_inc(v) atomic_add(1, v)
atomic_add又是怎樣實(shí)現(xiàn)的呢?用下面這個(gè)宏:
ATOMIC_OPS(add, +=, add)
把這個(gè)宏展開:
#define ATOMIC_OPS(op, c_op, asm_op) \ATOMIC_OP(op, c_op, asm_op) \ATOMIC_OP_RETURN(op, c_op, asm_op) \ATOMIC_FETCH_OP(op, c_op, asm_op)
從上面的宏可以知道,一個(gè) ATOMIC_OPS定義了 3個(gè)函數(shù)。比如“ATOMIC_OPS(add, +=, add)”就定義了這 3個(gè)函數(shù):
atomic_add
atomic_add_return
atomic_atomic_fetch_add 或 atomic_fetch_add_relaxed
我們以 ATOMIC_OP(add, +=, add)為例,看它是如何實(shí)現(xiàn) atomic_add函數(shù)的,對于 UP系統(tǒng)、SMP系統(tǒng),分別有不同的實(shí)現(xiàn)方法。
1.3.2.1 ATOMIC_OP在 UP系統(tǒng)中的實(shí)現(xiàn)
對于 ARMv6以下的 CPU系統(tǒng),不支持 SMP。原子變量的操作簡單粗暴:關(guān)中斷,中斷都關(guān)了,誰能來打斷我?代碼如下(arch\arm\include\asm\atomic.h):
1.3.2.2 ATOMIC_OP在 SMP系統(tǒng)中的實(shí)現(xiàn)
對于 ARMv6及以上的 CPU,有一些特殊的匯編指令來實(shí)現(xiàn)原子操作,不再需要關(guān)中斷,代碼如下(arch\arm\include\asm\atomic.h):
在 ARMv6及以上的架構(gòu)中,有 ldrex、strex指令,ex表示 exclude,意為獨(dú)占地。這 2條指令要配合使用,舉例如下:
① 讀出:ldrex r0, [r1]
讀取 r1所指內(nèi)存的數(shù)據(jù),存入 r0;并且標(biāo)記r1所指內(nèi)存為“獨(dú)占訪問”。
如果有其他程序再次執(zhí)行“l(fā)drex r0, [r1]”,一樣會(huì)成功,一樣會(huì)標(biāo)記 r1所指內(nèi)存為“獨(dú)占訪問”。 ② 修改 r0的值
③ 寫入:strex r2, r0, [r1]:
如果 r1的“獨(dú)占訪問”標(biāo)記還存在,則把 r0的新值寫入 r1所指內(nèi)存,并且清除“獨(dú)占訪問”的標(biāo)記,把 r2設(shè)為 0表示成功。
如果 r1的“獨(dú)占訪問”標(biāo)記不存在了,就不會(huì)更新內(nèi)存,并且把 r2設(shè)為 1表示失敗。
假設(shè)這樣的搶占場景:
① 程序 A在讀出、修改某個(gè)變量時(shí),被程序 B搶占了;
② 程序 B先完成了操作,程序 B的 strex操作會(huì)清除“獨(dú)占訪問”的標(biāo)記;
③ 輪到程序 A執(zhí)行剩下的寫入操作時(shí),它發(fā)現(xiàn)獨(dú)占訪問”標(biāo)記不存在了,于是取消寫入操作。 這就避免了這樣的事情發(fā)生:程序 A、B同時(shí)修改這個(gè)變量,并且都自認(rèn)為成功了。
舉報(bào)個(gè)例子,比如 atomic_dec,假設(shè)一開始變量值為 1,程序 A本想把值從 1變?yōu)?0;但是中途被程序B先把值從 1變成 0了;但是沒關(guān)系,程序 A里會(huì)再次讀出新值、修改、寫入,最終這個(gè)值被程序 A從 0改為-1。
在 ARMv6及以上的架構(gòu)中,原子操作不再需要關(guān)閉中斷,關(guān)中斷的花銷太大了。并且關(guān)中斷并不適合SMP多 CPU系統(tǒng),你關(guān)了 CPU0的中斷,CPU1也可能會(huì)來執(zhí)行些操作啊。
在 ARMv6及以上的架構(gòu)中,原子操作的執(zhí)行過程是可以被打斷的,但是它的效果符合“原子”的定義:一個(gè)完整的“讀、修改、寫入”原子的,不會(huì)被別的程序打斷。它的思路很簡單:如果被別的程序打斷了,那就重來,最后總會(huì)成功的。
1.3.3 原子變量使用案例
現(xiàn)在可以使用原子變量實(shí)現(xiàn):只能有一個(gè) APP訪問驅(qū)動(dòng)程序。代碼如下:
01 static atomic_t valid = ATOMIC_INIT(1);
02
03 static ssize_t gpio_key_drv_open (struct inode *node, struct file *file) 04 {
05 if (atomic_dec_and_test(&valid))
06 {
07 return 0;
08 }
09 atomic_inc(&valid);
10 return -EBUSY;
11 }
12
13 static int gpio_key_drv_close (struct inode *node, struct file *file)
14 {
15 atomic_inc(&valid);
16 return 0;
17 }
18
第 5行的 atomic_dec_and_test,這是一個(gè)原子操作,在 ARMv6以下的 CPU架構(gòu)中,這個(gè)函數(shù)是在關(guān)中斷的情況下執(zhí)行的,它確實(shí)是“原子的”,執(zhí)行過程不被打斷。
但是在 ARMv6及以上的 CPU架構(gòu)中,這個(gè)函數(shù)其實(shí)是可以被打斷的,但是它實(shí)現(xiàn)了原子操作的效果,如下圖所示:
1.3.4 原子位介紹
1.3.4.1 原子位的內(nèi)核操作函數(shù)
能操作原子變量,再去操作其中的某一位,不是挺簡單的嘛?不過不需要我們自己去實(shí)現(xiàn),內(nèi)核做好了。
原子位的操作函數(shù)在 Linux內(nèi)核文件 arch\arm\include\asm\bitops.h中,下表中 p是一個(gè) unsigned long指針。
1.3.4.2 原子位的內(nèi)核實(shí)現(xiàn)
在 ARMv6以下的架構(gòu)里,不支持 SMP系統(tǒng),原子位的操作函數(shù)也是簡單粗暴:關(guān)中斷。以 set_bit函數(shù)為例,代碼在內(nèi)核文件 arch\arm\include\asm\bitops.h中,如下
在 ARMv6及以上的架構(gòu)中,不需要關(guān)中斷,有 ldrex、strex等指令,這些指令的作用在前面介紹過。還是以 set_bit函數(shù)為例,代碼如下:
我不再使用原子位操作來寫代碼,留給你們練習(xí)吧。