linux中原子操作atomic_read、atomic_set、atomic_add、atomic_sub
原子操作
所謂原子操作,就是該操作絕不會在執(zhí)行完畢前被任何其他任務(wù)或事件打斷,也就說,它的最小的執(zhí)行單位,不可能有比它更小的執(zhí)行單位。因此這里的原子實際是使用了物理學(xué)里的物質(zhì)微粒的概念。
原子操作需要硬件的支持,因此是架構(gòu)相關(guān)的,其API和原子類型的定義都定義在內(nèi)核源碼樹的include/asm/atomic.h文件中,它們都使用匯編語言實現(xiàn),因為C語言并不能實現(xiàn)這樣的操作。
原子操作主要用于實現(xiàn)資源計數(shù),很多引用計數(shù)(refcnt)就是通過原子操作實現(xiàn)的。原子類型定義如下:
typedef struct? {?volatile int counter;? }? atomic_t;volatile修飾字段告訴gcc不要對該類型的數(shù)據(jù)做優(yōu)化處理,對它的訪問都是對內(nèi)存的訪問,而不是對寄存器的訪問。
atomic_inc(&v)對變量v用鎖定總線的單指令進行不可分解的"原子"級增量操作, 避免v的值由于中斷或多處理器同時操作造成不確定狀態(tài)。typedef struct { volatile int counter; } atomic_t;volatile修飾字段告訴gcc不要對該類型的數(shù)據(jù)做優(yōu)化處理,對它的訪問都是對內(nèi)存的訪問, 而不是對寄存器的訪問。?原子操作API包括:?atomic_read(atomic_t * v);該函數(shù)對原子類型的變量進行原子讀操作,它返回原子類型的變量v的值。?atomic_set(atomic_t * v, int i);該函數(shù)設(shè)置原子類型的變量v的值為i。?void atomic_add(int i, atomic_t *v);該函數(shù)給原子類型的變量v增加值i。?atomic_sub(int i, atomic_t *v);該函數(shù)從原子類型的變量v中減去i。?int atomic_sub_and_test(int i, atomic_t *v);該函數(shù)從原子類型的變量v中減去i,并判斷結(jié)果是否為0,如果為0,返回真,否則返回假。?void atomic_inc(atomic_t *v);該函數(shù)對原子類型變量v原子地增加1。?void atomic_dec(atomic_t *v);該函數(shù)對原子類型的變量v原子地減1。?int atomic_dec_and_test(atomic_t *v);該函數(shù)對原子類型的變量v原子地減1,并判斷結(jié)果是否為0,如果為0,返回真,否則返回假。?int atomic_inc_and_test(atomic_t *v);該函數(shù)對原子類型的變量v原子地增加1,并判斷結(jié)果是否為0,如果為0,返回真,否則返回假。?int atomic_add_negative(int i, atomic_t *v);該函數(shù)對原子類型的變量v原子地增加I,并判斷結(jié)果是否為負(fù)數(shù),如果是,返回真,否則返回假。?int atomic_add_return(int i, atomic_t *v);該函數(shù)對原子類型的變量v原子地增加i,并且返回指向v的指針。?int atomic_sub_return(int i, atomic_t *v);該函數(shù)從原子類型的變量v中減去i,并且返回指向v的指針。?int atomic_inc_return(atomic_t * v);該函數(shù)對原子類型的變量v原子地增加1并且返回指向v的指針。?int atomic_dec_return(atomic_t * v);該函數(shù)對原子類型的變量v原子地減1并且返回指向v的指針。? 原子操作通常用于實現(xiàn)資源的引用計數(shù),在TCP/IP協(xié)議棧的IP碎片處理中,就使用了引用計數(shù),碎片隊列結(jié)構(gòu)struct ipq描述了一個IP碎片,字段refcnt就是引用計數(shù)器,它的類型為atomic_t,當(dāng)創(chuàng)建IP碎片時(在函數(shù)ip_frag_create中),使用atomic_set函數(shù)把它設(shè)置為1,當(dāng)引用該IP碎片時,就使用函數(shù)atomic_inc把引用計數(shù)加1。?
?
當(dāng)不需要引用該IP碎片時,就使用函數(shù)ipq_put來釋放該IP碎片,ipq_put使用函數(shù)atomic_dec_and_test把引用計數(shù)減1并判斷引用計數(shù)是否為0,如果是就釋放IP碎片。函數(shù)ipq_kill把IP碎片從ipq隊列中刪除,并把該刪除的IP碎片的引用計數(shù)減1(通過使用函數(shù)atomic_dec實現(xiàn))。
Linux中的基本原子操作
| 宏或者函數(shù) | 說明 |
| atomic_read | 返回原子變量的值。 |
| atomic_set | 設(shè)置原子變量的值。 |
| atomic_add | 原子的遞增計數(shù)的值。 |
| atomic_sub | 原子的遞減計數(shù)的值。 |
| atomic_cmpxchg | 原子比較并交換計數(shù)值。 |
| atomic_clear_mask | 原子的清除掩碼。 |
除此以外,還有一組操作64位原子變量的變體,以及一些位操作宏及函數(shù)。這里不再羅列。
/*** 返回原子變量的值。* 這里強制將counter轉(zhuǎn)換為volatile int并取其值。目的就是為了避免編譯優(yōu)化。*/ #define atomic_read(v) ? (*(volatile int *)&(v)->counter)/*** 設(shè)置原子變量的值。*/ #define atomic_set(v,i) ? ?(((v)->counter) = (i))原子遞增的實現(xiàn)比較精妙,理解它的關(guān)鍵是需要明白ldrex、strex這一對指令的含義。/*** 原子的遞增計數(shù)的值。*/ static inline void atomic_add(int i, atomic_t *v) {unsigned long tmp;int result;/*** __volatile__是為了防止編譯器亂序。與"#define atomic_read(v) ? ? ? ? ?(*(volatile int *)&(v)->counter)"中的volatile類似。*/__asm__ __volatile__("@ atomic_add\n"/*** ldrex是arm為了支持多核引入的新指令,表示"排它性"加載。與mips的ll指令一樣的效果。* 它與"排它性"存儲配對使用。*/ "1: ? ?ldrex ? ? ? ? %0, [%3]\n"/*** 原子變量的值已經(jīng)加載到寄存器中,這里對寄存器中的值減去指定的值。*/ " ? ? ? add ?%0, %0, %4\n"/*** strex是"排它性"的存儲寄存器的值到內(nèi)存中。類似于mips的sc指令。*/ " ? ? ? strex ? ? ? ? %1, %0, [%3]\n"/*** 關(guān)鍵代碼是這里的判斷。如果在ldrex和strex之間,其他核沒有對原子變量變量進行加載存儲操作,* 那么寄存器中值就是0,否則非0.*/ " ? ? ? teq ? %1, #0\n"/*** 如果其他核與本核沖突,那么寄存器值為非0,這里跳轉(zhuǎn)到標(biāo)號1處,重新加載內(nèi)存的值并遞增其值。*/ " ? ? ? bne ?1b": "=&r" (result), "=&r" (tmp), "+Qo" (v->counter): "r" (&v->counter), "Ir" (i): "cc"); }atomic_add_return遞增原子變量的值,并返回它的新值。它與atomic_add的最大不同,在于在原子遞增前后各增加了一句:smp_mb();
這是由linux原子操作函數(shù)的語義規(guī)定的:所有對原子變量的操作,如果需要向調(diào)用者返回結(jié)果,那么就需要增加多核內(nèi)存屏障的語義。通俗的說,就是其他核看到本核對原子變量的操作結(jié)果時,本核在原子變量前的操作對其他核也是可見的。
理解了atomic_add,其他原子變量的實現(xiàn)也就容易理解了。這里不再詳述。
atomic_cmpxchg()函數(shù)實現(xiàn)了一個比較+交換的原子操作(原子就是說cpu要不就不做,要做就一定要做完某些操作才能干別的事情,對應(yīng)這里就是比較和交換要一次過做完)。
atomic_cmpxchg()比較kgdb_active->count的值是否等用-1,如果是則把cpu的值賦給kgdb_active->count,否則不修改它的值atomic_cmpxchg返回kgdb_active->count賦值前的值。kgdb_active是一個全局原子變量,定義在kernel/kgdb.c中,用來記錄當(dāng)前正在執(zhí)行kgdb代碼的cpu號,它起到一個鎖的作用,因為同一時間只能有一個cpu執(zhí)行kgdb的代碼,這是可以想象得到的,如果兩個cpu在兩個不同斷點被觸發(fā),那究竟是誰和遠端gdb通信呢?前一條命令被 cpu1拿了,后一條卻去了cpu2那里,那還得了。
kgdb_active的初始值為-1,-1表示當(dāng)前kgdb的處理函數(shù)并沒有被觸發(fā),相反如果kgdb已經(jīng)在運行,那么kgdb_active就有它自己的值,這些處理都是針對多cpu的,如果只有一個cpu,這個世界就簡單多了。這里是防止多個kgdb的實例在不同cpu被觸發(fā)引起互相干擾。考慮這種情況,在cpu1上有一個斷點讓kgdb起來,這時,kgdb_active還是-1,cpu1很順利就給kgdb_active賦值然后進入后面的操作。這時cpu2中kgdb也被觸發(fā)。它也想進入后面的操作,但是這時候kgdb_active已經(jīng)不再是-1,cpu2只能不斷地比較kgdb_active的值和執(zhí)行cpu_relax(),宏cpu_relax()可以簡化為一條pause匯編,通過引入一個很短的延遲,加快了緊跟在鎖后面的代碼的執(zhí)行并減少能源的消耗,實際上就是讓cpu2等。當(dāng)cpu1在退出kgdb_handle_exception()前會把 kgdb_active賦回-1, 這樣cpu2就可以進行后面的操作了。kgdb使用大量的原子操作來完成鎖的功能,后面還會看到。atomic操作加上cpu_relax()跟一個自旋鎖很相似。?
總結(jié)
以上是生活随笔為你收集整理的linux中原子操作atomic_read、atomic_set、atomic_add、atomic_sub的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 基于TI DLP技术的工业级DLP301
- 下一篇: 腾讯面试之总结