當前位置:編程學習大全網 - 編程語言 - 新手求教LINUX下的原子操作該怎麽寫

新手求教LINUX下的原子操作該怎麽寫

linux中關於原子操作

2016年08月02日

原子操作:就是在執行某壹操作時不被打斷。

linux原子操作問題來源於中斷、進程的搶占以及多核smp系統中程序的並發執行。

對於臨界區的操作可以加鎖來保證原子性,對於全局變量或靜態變量操作則需要依賴於硬件平臺的原子變量操作。

因此原子操作有兩類:壹類是各種臨界區的鎖,壹類是操作原子變量的函數。

對於arm來說,單條匯編指令都是原子的,多核smp也是,因為有總線仲裁所以cpu可以單獨占用總線直到指令結束,多核系統中的原子操作通常使用內存柵障(memory barrier)來實現,即壹個CPU核在執行原子操作時,其他CPU核必須停止對內存操作或者不對指定的內存進行操作,這樣才能避免數據競爭問題。但是對於load update store這個過程可能被中斷、搶占,所以arm指令集有增加了ldrex/strex這樣的實現load update store的原子指令。

但是linux種對於c/c++程序(壹條c編譯成多條匯編),由於上述提到的原因不能保證原子性,因此linux提供了壹套函數來操作全局變量或靜態變量。

壹.整型原子操作定義於#include<asm/atomic.h>分為 定義,獲取,加減,測試,返回。void atomic_set(atomic_t *v,int i); //設置原子變量v的值為iatomic_t v = ATOMIC_INIT(0); //定義原子變量v,並初始化為0;atomic_read(atomic_t* v); //返回原子變量v的值;void atomic_add(int i, atomic_t* v); //原子變量v增加i;void atomic_sub(int i, atomic_t* v); void atomic_inc(atomic_t* v); //原子變量增加1;void atomic_dec(atomic_t* v);?int atomic_inc_and_test(atomic_t* v); //先自增1,然後測試其值是否為0,若為0,則返回true,否則返回false;int atomic_dec_and_test(atomic_t* v); int atomic_sub_and_test(int i, atomic_t* v); //先減i,然後測試其值是否為0,若為0,則返回true,否則返回false;註意:只有自加,沒有加操作int atomic_add_return(int i, atomic_t* v); ? //v的值加i後返回新的值;int atomic_sub_return(int i, atomic_t* v); ?int atomic_inc_return(atomic_t* v); //v的值自增1後返回新的值;int atomic_dec_return(atomic_t* v);二.位原子操作定義於#include<asm/bitops.h>分為 設置,清除,改變,測試void set_bit(int nr, volatile void* addr); //設置地址addr的第nr位,所謂設置位,就是把位寫為1;void clear_bit(int nr, volatile void* addr); ?//清除地址addr的第nr位,所謂清除位,就是把位寫為0;void change_bit(int nr, volatile void* addr); //把地址addr的第nr位反轉;int test_bit(int nr, volatile void* addr); //返回地址addr的第nr位;int test_and_set_bit(int nr, volatile void* addr);//測試並設置位;若addr的第nr位非0,則返回true; 若addr的第nr位為0,則返回false;int test_and_clear_bit(int nr, volatile void* addr);//測試並清除位;int test_and_change_bit(int nr, volatile void* addr);//測試並反轉位;上述操作等同於先執行test_bit(nr,voidaddr)然後在執行xxx_bit(nr,voidaddr)

舉個簡單例子:為了實現設備只能被壹個進程打開,從而避免競態的出現static atomic_t scull_available = ATOMIC_INIT(1);?//init atomic在scull_open 函數和scull_close函數中:int scull_open(struct inode *inode, struct file *filp){ struct scull_dev *dev; // device information dev = container_of(inode->i_cdev, struct scull_dev, cdev); filp->private_data = dev;?// for other methods if(!atomic_dec_and_test(&scull_available)){ atomic_inc(&scull_available); return -EBUSY; } return 0; // success?}int scull_release(struct inode *inode, struct file *filp){ atomic_inc(&scull_available); return 0;}

假設原子變量的底層實現是由壹個匯編指令實現的,這個原子性必然有保障。但是如果原子變量的實現是由多條指令組合而成的,那麽對於SMP和中斷的介入會不會有什麽影響呢?我在看ARM的原子變量操作實現的時候,發現其是由多條匯編指令(ldrex/strex)實現的。在參考了別的書籍和資料後,發現大部分書中對這兩條指令的描訴都是說他們是支持在SMP系統中實現多核***享內存的互斥訪問。但在UP系統中使用,如果ldrex/strex和之間發生了中斷,並在中斷中也用ldrex/strex操作了同壹個原子變量會不會有問題呢?就這個問題,我認真看了壹下內核的ARM原子變量源碼和ARM官方對於ldrex/strex的功能解釋,總結如下:

壹、ARM構架的原子變量實現結構

對於ARM構架的原子變量實現源碼位於:arch/arm/include/asm/atomic.h

其主要的實現代碼分為ARMv6以上(含v6)構架的實現和ARMv6版本以下的實現。

該文件的主要結構如下:

#if?__LINUX_ARM_ARCH__?>=?6

......(通過ldrex/strex指令的匯編實現)

#else?/*?ARM_ARCH_6?*/

#ifdef CONFIG_SMP

#error?SMP?not?supported?on?pre-ARMv6 CPUs

#endif

......(通過關閉CPU中斷的C語言實現)

#endif?/*?__LINUX_ARM_ARCH__?*/

......?

#ifndef CONFIG_GENERIC_ATOMIC64

......(通過ldrexd/strexd指令的匯編實現的64bit原子變量的訪問)

#else?/*?!CONFIG_GENERIC_ATOMIC64?*/

#include?<asm-generic/atomic64.h>

#endif

#include?<asm-generic/atomic-long.h>

這樣的安排是依據ARM核心指令集版本的實現來做的:

(1)在ARMv6以上(含v6)構架有了多核的CPU,為了在多核之間同步數據和控制並發,ARM在內存訪問上增加了獨占監測(Exclusive monitors)機制(壹種簡單的狀態機),並增加了相關的ldrex/strex指令。請先閱讀以下參考資料(關鍵在於理解local monitor和Global monitor):

1.2.2.?Exclusive monitors

4.2.12.?LDREX?和?STREX

(2)對於ARMv6以前的構架不可能有多核CPU,所以對於變量的原子訪問只需要關閉本CPU中斷即可保證原子性。?

對於(2),非常好理解。

但是(1)情況,我還是要通過源碼的分析才認同這種代碼,以下我僅僅分析最具有代表性的atomic_add源碼,其他的API原理都壹樣。如果讀者還不熟悉C內嵌匯編的格式,請參考《ARM GCC?內嵌匯編手冊》

二、內核對於ARM構架的atomic_add源碼分析

/*

*?ARMv6 UP 和 SMP 安全原子操作。 我們是用獨占載入和

*?獨占存儲來保證這些操作的原子性。我們可能會通過循環

*?來保證成功更新變量。

*/

static inline void atomic_add(int?i,?atomic_t?*v)

{

unsigned long tmp;

int?result;

__asm__ __volatile__("@ atomic_add\n"

"1: ldrex %0, [%3]\n"

" add %0, %0, %4\n"

" strex %1, %0, [%3]\n"

" teq %1, #0\n"

" bne 1b"

:?"=&r"?(result),?"=&r"?(tmp),?"+Qo"?(v->counter)

:?"r"?(&v->counter),?"Ir"?(i)

:?"cc");

}

源碼分析:?

註意:根據內聯匯編的語法,result、tmp、&v->counter對應的數據都放在了寄存器中操作。如果出現上下文切換,切換機制會做寄存器上下文保護。

(1)ldrex %0, [%3]

意思是將&v->counter指向的數據放入result中,並且(分別在Local monitor和Global monitor中)設置獨占標誌。

(2)add %0, %0, %4

result = result + i

(3)strex %1, %0, [%3]

意思是將result保存到&v->counter指向的內存中,此時?Exclusive monitors會發揮作用,將保存是否成功的標誌放入tmp中。

(4)?teq %1, #0

測試strex是否成功(tmp == 0?)

(5)bne 1b

如果發現strex失敗,從(1)再次執行。

通過上面的分析,可知關鍵在於strex的操作是否成功的判斷上。而這個就歸功於ARM的Exclusive monitors和ldrex/strex指令的機制。以下通過可能的情況分析ldrex/strex指令機制。(請閱讀時參考4.2.12.?LDREX?和?STREX)

1、UP系統或SMP系統中變量為非CPU間***享訪問的情況?

此情況下,僅有壹個CPU可能訪問變量,此時僅有Local monitor需要關註。

假設CPU執行到(2)的時候,來了壹個中斷,並在中斷裏使用ldrex/strex操作了同壹個原子變量。則情況如下圖所示:

A:處理器標記壹個物理地址,但訪問尚未完畢

B:再次標記此物理地址訪問尚未完畢(與A重復)

C:進行存儲操作,清除以上標記,返回0(操作成功)

D:不會進行存儲操作,並返回1(操作失敗)?

也就是說,中斷例程裏的操作會成功,被中斷的操作會失敗重試。?

2、SMP系統中變量為CPU間***享訪問的情況

此情況下,需要兩個CPU間的互斥訪問,此時ldrex/strex指令會同時關註Local monitor和Global monitor。

(i)兩個CPU同時訪問同個原子變量(ldrex/strex指令會關註Global monitor。)

A:將該物理地址標記為CPU0獨占訪問,並清除CPU0對其他任何物理地址的任何獨占訪問標記。

B:標記此物理地址為CPU1獨占訪問,並清除CPU1對其他任何物理地址的任何獨占訪問標記。

C:沒有標記為CPU0獨占訪問,不會進行存儲,並返回1(操作失敗)。

D:已被標記為CPU1獨占訪問,進行存儲並清除獨占訪問標記,並返回0(操作成功)。

也就是說,後執行ldrex操作的CPU會成功。

(ii)同壹個CPU因為中斷,“嵌套”訪問同個原子變量(ldrex/strex指令會關註Local monito)

A:將該物理地址標記為CPU0獨占訪問,並清除CPU0對其他任何物理地址的任何獨占訪問標記。

B:再次標記此物理地址為CPU0獨占訪問,並清除CPU0對其他任何物理地址的任何獨占訪問標記。

C:已被標記為CPU0獨占訪問,進行存儲並清除獨占訪問標記,並返回0(操作成功)。

D:沒有標記為CPU0獨占訪問,不會進行存儲,並返回1(操作失敗)。

也就是說,中斷例程裏的操作會成功,被中斷的操作會失敗重試。

(iii)兩個CPU同時訪問同個原子變量,並同時有CPU因中斷“嵌套”訪問改原子變量(ldrex/strex指令會同時關註Local monitor和Global monitor)

雖然對於人來說,這種情況比較BT。但是在飛速運行的CPU來說,BT的事情隨時都可能發生。

A:將該物理地址標記為CPU0獨占訪問,並清除CPU0對其他任何物理地址的任何獨占訪問標記。

B:標記此物理地址為CPU1獨占訪問,並清除CPU1對其他任何物理地址的任何獨占訪問標記。

C:再次標記此物理地址為CPU0獨占訪問,並清除CPU0對其他任何物理地址的任何獨占訪問標記。

D:已被標記為CPU0獨占訪問,進行存儲並清除獨占訪問標記,並返回0(操作成功)。

E:沒有標記為CPU1獨占訪問,不會進行存儲,並返回1(操作失敗)。

F:沒有標記為CPU0獨占訪問,不會進行存儲,並返回1(操作失敗)。

當然還有其他許多復雜的可能,也可以通過ldrex/strex指令的機制分析出來。從上面列舉的分析中,我們可以看出:ldrex/strex可以保證在任何情況下(包括被中斷)的訪問原子性。所以內核中ARM構架中的原子操作是可以信任的。

  • 上一篇:java 二面壹般會面試什麽內容
  • 下一篇:《敢達爭鋒對決》新玩法“戰艦系統”指南
  • copyright 2024編程學習大全網