原子操作(atomic operation)

深入分析Volatile的實現原理


引言

在多線程併發編程中synchronized和Volatile都扮演着重要的角色,Volatile是輕量級的synchronized,它在多處理器開發中保證了共享變量的“可見性”。可見性的意思是當一個線程修改一個共享變量時,另外一個線程能讀到這個修改的值。

它在某些情況下比synchronized的開銷更小,本文將深入分析在硬件層面上Inter處理器是如何實現Volatile的,通過深入分析能幫助我們正確的使用Volatile變量。

術語定義

術語

英文單詞

描述

共享變量

 

在多個線程之間能夠被共享的變量被稱爲共享變量。共享變量包括所有的實例變量,靜態變量和數組元素。他們都被存放在堆內存中,Volatile只作用於共享變量。

內存屏障

Memory Barriers

是一組處理器指令,用於實現對內存操作的順序限制。

緩衝行

Cache line

緩存中可以分配的最小存儲單位。處理器填寫緩存線時會加載整個緩存線,需要使用多個主內存讀週期。

原子操作

Atomic operations

不可中斷的一個或一系列操作。

緩存行填充

cache line fill

當處理器識別到從內存中讀取操作數是可緩存的,處理器讀取整個緩存行到適當的緩存(L1,L2,L3的或所有)

緩存命中

cache hit

如果進行高速緩存行填充操作的內存位置仍然是下次處理器訪問的地址時,處理器從緩存中讀取操作數,而不是從內存。

寫命中

write hit

當處理器將操作數寫回到一個內存緩存的區域時,它首先會檢查這個緩存的內存地址是否在緩存行中,如果存在一個有效的緩存行,則處理器將這個操作數寫回到緩存,而不是寫回到內存,這個操作被稱爲寫命中。

寫缺失

write misses the cache

一個有效的緩存行被寫入到不存在的內存區域。

Volatile的官方定義

Java語言規範第三版中對volatile的定義如下: java編程語言允許線程訪問共享變量,爲了確保共享變量能被準確和一致的更新,線程應該確保通過排他鎖單獨獲得這個變量。Java語言提供了volatile,在某些情況下比鎖更加方便。如果一個字段被聲明成volatile,java線程內存模型確保所有線程看到這個變量的值是一致的。

爲什麼要使用Volatile

Volatile變量修飾符如果使用恰當的話,它比synchronized的使用和執行成本會更低,因爲它不會引起線程上下文的切換和調度。

Volatile的實現原理

那麼Volatile是如何來保證可見性的呢?在x86處理器下通過工具獲取JIT編譯器生成的彙編指令來看看對Volatile進行寫操作CPU會做什麼事情。

Java代碼:

instance = new Singleton();//instance是volatile變量

彙編代碼:

0x01a3de1d: movb $0x0,0x1104800(%esi);

0x01a3de24: lock addl $0x0,(%esp);

有volatile變量修飾的共享變量進行寫操作的時候會多第二行彙編代碼,通過查IA-32架構軟件開發者手冊可知,lock前綴的指令在多核處理器下會引發了兩件事情。

  • 將當前處理器緩存行的數據會寫回到系統內存。
  • 這個寫回內存的操作會引起在其他CPU裏緩存了該內存地址的數據無效。

處理器爲了提高處理速度,不直接和內存進行通訊,而是先將系統內存的數據讀到內部緩存(L1,L2或其他)後再進行操作,但操作完之後不知道何時會寫到內存,如果對聲明瞭Volatile變量進行寫操作,JVM就會向處理器發送一條Lock前綴的指令,將這個變量所在緩存行的數據寫回到系統內存。但是就算寫回到內存,如果其他處理器緩存的值還是舊的,再執行計算操作就會有問題,所以在多處理器下,爲了保證各個處理器的緩存是一致的,就會實現緩存一致性協議,每個處理器通過嗅探在總線上傳播的數據來檢查自己緩存的值是不是過期了,當處理器發現自己緩存行對應的內存地址被修改,就會將當前處理器的緩存行設置成無效狀態,當處理器要對這個數據進行修改操作的時候,會強制重新從系統內存裏把數據讀到處理器緩存裏。

這兩件事情在IA-32軟件開發者架構手冊的第三冊的多處理器管理章節(第八章)中有詳細闡述。

Lock前綴指令會引起處理器緩存回寫到內存。Lock前綴指令導致在執行指令期間,聲言處理器的 LOCK# 信號。在多處理器環境中,LOCK# 信號確保在聲言該信號期間,處理器可以獨佔使用任何共享內存。(因爲它會鎖住總線,導致其他CPU不能訪問總線,不能訪問總線就意味着不能訪問系統內存),但是在最近的處理器裏,LOCK#信號一般不鎖總線,而是鎖緩存,畢竟鎖總線開銷比較大。在8.1.4章節有詳細說明鎖定操作對處理器緩存的影響,對於Intel486和Pentium處理器,在鎖操作時,總是在總線上聲言LOCK#信號。但在P6和最近的處理器中,如果訪問的內存區域已經緩存在處理器內部,則不會聲言LOCK#信號。相反地,它會鎖定這塊內存區域的緩存並回寫到內存,並使用緩存一致性機制來確保修改的原子性,此操作被稱爲“緩存鎖定”,緩存一致性機制會阻止同時修改被兩個以上處理器緩存的內存區域數據

一個處理器的緩存回寫到內存會導致其他處理器的緩存無效。IA-32處理器和Intel 64處理器使用MESI(修改,獨佔,共享,無效)控制協議去維護內部緩存和其他處理器緩存的一致性。在多核處理器系統中進行操作的時候,IA-32 和Intel 64處理器能嗅探其他處理器訪問系統內存和它們的內部緩存。它們使用嗅探技術保證它的內部緩存,系統內存和其他處理器的緩存的數據在總線上保持一致。例如在Pentium和P6 family處理器中,如果通過嗅探一個處理器來檢測其他處理器打算寫內存地址,而這個地址當前處理共享狀態,那麼正在嗅探的處理器將無效它的緩存行,在下次訪問相同內存地址時,強制執行緩存行填充。

Volatile的使用優化

著名的Java併發編程大師Doug lea在JDK7的併發包裏新增一個隊列集合類LinkedTransferQueue,他在使用Volatile變量時,用一種追加字節的方式來優化隊列出隊和入隊的性能。

追加字節能優化性能?這種方式看起來很神奇,但如果深入理解處理器架構就能理解其中的奧祕。讓我們先來看看LinkedTransferQueue這個類,它使用一個內部類類型來定義隊列的頭隊列(Head)和尾節點(tail),而這個內部類PaddedAtomicReference相對於父類AtomicReference只做了一件事情,就將共享變量追加到64字節。我們可以來計算下,一個對象的引用佔4個字節,它追加了15個變量共佔60個字節,再加上父類的Value變量,一共64個字節。

/** head of the queue */
private transient final PaddedAtomicReference < QNode > head;

/** tail of the queue */

private transient final PaddedAtomicReference < QNode > tail;


static final class PaddedAtomicReference < T > extends AtomicReference < T > {

    // enough padding for 64bytes with 4byte refs 
    Object p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, pa, pb, pc, pd, pe;

    PaddedAtomicReference(T r) {

        super(r);

    }

}

public class AtomicReference < V > implements java.io.Serializable {

    private volatile V value;

    //省略其他代碼 }

爲什麼追加64字節能夠提高併發編程的效率呢? 因爲對於英特爾酷睿i7,酷睿, Atom和NetBurst, Core Solo和Pentium M處理器的L1,L2或L3緩存的高速緩存行是64個字節寬,不支持部分填充緩存行,這意味着如果隊列的頭節點和尾節點都不足64字節的話,處理器會將它們都讀到同一個高速緩存行中,在多處理器下每個處理器都會緩存同樣的頭尾節點,當一個處理器試圖修改頭接點時會將整個緩存行鎖定,那麼在緩存一致性機制的作用下,會導致其他處理器不能訪問自己高速緩存中的尾節點,而隊列的入隊和出隊操作是需要不停修改頭接點和尾節點,所以在多處理器的情況下將會嚴重影響到隊列的入隊和出隊效率。Doug lea使用追加到64字節的方式來填滿高速緩衝區的緩存行,避免頭接點和尾節點加載到同一個緩存行,使得頭尾節點在修改時不會互相鎖定。

那麼是不是在使用Volatile變量時都應該追加到64字節呢?不是的。在兩種場景下不應該使用這種方式。第一:緩存行非64字節寬的處理器,如P6系列和奔騰處理器,它們的L1和L2高速緩存行是32個字節寬。第二:共享變量不會被頻繁的寫。因爲使用追加字節的方式需要處理器讀取更多的字節到高速緩衝區,這本身就會帶來一定的性能消耗,共享變量如果不被頻繁寫的話,鎖的機率也非常小,就沒必要通過追加字節的方式來避免相互鎖定。



原子操作的實現原理



1. 引言

原子(atom)本意是“不能被進一步分割的最小粒子”,而原子操作(atomic operation)意爲"不可被中斷的一個或一系列操作" 。在多處理器上實現原子操作就變得有點複雜。本文讓我們一起來聊一聊在Intel處理器和Java裏是如何實現原子操作的。


2. 術語定義


術語 英文 解釋
緩存行 Cache line 緩存的最小操作單位
比較並交換 Compare and Swap CAS操作需要輸入兩個數值,一箇舊值(期望操作前的值)和一個新值,在操作期間先比較下舊值有沒有發生變化,如果沒有發生變化,才交換成新值,發生了變化則不交換。
CPU流水線 CPU pipeline CPU流水線的工作方式就象工業生產上的裝配流水線,在CPU中由5~6個不同功能的電路單元組成一條指令處理流水線,然後將一條X86指令分成5~6步後再由這些電路單元分別執行,這樣就能實現在一個CPU時鐘週期完成一條指令,因此提高CPU的運算速度。
內存順序衝突 Memory order violation 內存順序衝突一般是由假共享引起,假共享是指多個CPU同時修改同一個緩存行的不同部分而引起其中一個CPU的操作無效,當出現這個內存順序衝突時,CPU必須清空流水線。

3. 處理器如何實現原子操作

32位IA-32處理器使用基於對緩存加鎖或總線加鎖的方式來實現多處理器之間的原子操作。

3.1 處理器自動保證基本內存操作的原子性

首先處理器會自動保證基本的內存操作的原子性。處理器保證從系統內存當中讀取或者寫入一個字節是原子的,意思是當一個處理器讀取一個字節時,其他處理器不能訪問這個字節的內存地址。奔騰6和最新的處理器能自動保證單處理器對同一個緩存行裏進行16/32/64位的操作是原子的,但是複雜的內存操作處理器不能自動保證其原子性,比如跨總線寬度,跨多個緩存行,跨頁表的訪問。但是處理器提供總線鎖定和緩存鎖定兩個機制來保證複雜內存操作的原子性。

3.2 使用總線鎖保證原子性

第一個機制是通過總線鎖保證原子性。如果多個處理器同時對共享變量進行讀改寫(i++就是經典的讀改寫操作)操作,那麼共享變量就會被多個處理器同時進行操作,這樣讀改寫操作就不是原子的,操作完之後共享變量的值會和期望的不一致,舉個例子:如果i=1,我們進行兩次i++操作,我們期望的結果是3,但是有可能結果是2。如下圖

(例1)

原因是有可能多個處理器同時從各自的緩存中讀取變量i,分別進行加一操作,然後分別寫入系統內存當中。那麼想要保證讀改寫共享變量的操作是原子的,就必須保證CPU1讀改寫共享變量的時候,CPU2不能操作緩存了該共享變量內存地址的緩存。

處理器使用總線鎖就是來解決這個問題的。所謂總線鎖就是使用處理器提供的一個LOCK#信號,當一個處理器在總線上輸出此信號時,其他處理器的請求將被阻塞住,那麼該處理器可以獨佔使用共享內存。

3.3 使用緩存鎖保證原子性

第二個機制是通過緩存鎖定保證原子性。在同一時刻我們只需保證對某個內存地址的操作是原子性即可,但總線鎖定把CPU和內存之間通信鎖住了,這使得鎖定期間,其他處理器不能操作其他內存地址的數據,所以總線鎖定的開銷比較大,最近的處理器在某些場合下使用緩存鎖定代替總線鎖定來進行優化。

頻繁使用的內存會緩存在處理器的L1,L2和L3高速緩存裏,那麼原子操作就可以直接在處理器內部緩存中進行,並不需要聲明總線鎖,在奔騰6和最近的處理器中可以使用“緩存鎖定”的方式來實現複雜的原子性。所謂“緩存鎖定”就是如果緩存在處理器緩存行中內存區域在LOCK操作期間被鎖定,當它執行鎖操作回寫內存時,處理器不在總線上聲言LOCK#信號,而是修改內部的內存地址,並允許它的緩存一致性機制來保證操作的原子性,因爲緩存一致性機制會阻止同時修改被兩個以上處理器緩存的內存區域數據,當其他處理器回寫已被鎖定的緩存行的數據時會起緩存行無效,在例1中,當CPU1修改緩存行中的i時使用緩存鎖定,那麼CPU2就不能同時緩存了i的緩存行。

但是有兩種情況下處理器不會使用緩存鎖定。第一種情況是:當操作的數據不能被緩存在處理器內部,或操作的數據跨多個緩存行(cache line),則處理器會調用總線鎖定。第二種情況是:有些處理器不支持緩存鎖定。對於Inter486和奔騰處理器,就算鎖定的內存區域在處理器的緩存行中也會調用總線鎖定。

以上兩個機制我們可以通過Inter處理器提供了很多LOCK前綴的指令來實現。比如位測試和修改指令BTS,BTR,BTC,交換指令XADD,CMPXCHG和其他一些操作數和邏輯指令,比如ADD(加),OR(或)等,被這些指令操作的內存區域就會加鎖,導致其他處理器不能同時訪問它。

4. JAVA如何實現原子操作

在java中可以通過鎖和循環CAS的方式來實現原子操作。

4.1 使用循環CAS實現原子操作

JVM中的CAS操作正是利用了上一節中提到的處理器提供的CMPXCHG指令實現的。自旋CAS實現的基本思路就是循環進行CAS操作直到成功爲止,以下代碼實現了一個基於CAS線程安全的計數器方法safeCount和一個非線程安全的計數器count。

   
public class Counter {
    private AtomicInteger atomicI = new AtomicInteger(0);
    private int i = 0;
    public static void main(String[] args) {
        final Counter cas = new Counter();
        List<Thread> ts = new ArrayList<Thread>(600);
        long start = System.currentTimeMillis();
        for (int j = 0; j < 100; j++) {
            Thread t = new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i = 0; i < 10000; i++) {
                        cas.count();
                        cas.safeCount();
                    }
                }
            });
            ts.add(t);
        }

        for (Thread t : ts) {
            t.start();
        }
       // 等待所有線程執行完成
        for (Thread t : ts) {
            try {
                t.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        System.out.println(cas.i);
        System.out.println(cas.atomicI.get());
        System.out.println(System.currentTimeMillis() - start);

    }

    /**
     * 使用CAS實現線程安全計數器
     */
    private void safeCount() {
        for (;;) {
            int i = atomicI.get();
            boolean suc = atomicI.compareAndSet(i, ++i);
            if (suc) {
                break;
            }
        }
    }
    /**
     * 非線程安全計數器
     */
    private void count() {
        i++;
    }
}

在java併發包中有一些併發框架也使用了自旋CAS的方式來實現原子操作,比如LinkedTransferQueue類的Xfer方法。CAS雖然很高效的解決原子操作,但是CAS仍然存在三大問題。ABA問題,循環時間長開銷大和只能保證一個共享變量的原子操作。

  1. ABA問題。因爲CAS需要在操作值的時候檢查下值有沒有發生變化,如果沒有發生變化則更新,但是如果一個值原來是A,變成了B,又變成了A,那麼使用CAS進行檢查時會發現它的值沒有發生變化,但是實際上卻變化了。ABA問題的解決思路就是使用版本號。在變量前面追加上版本號,每次變量更新的時候把版本號加一,那麼A-B-A 就會變成1A-2B-3A。
    從Java1.5開始JDK的atomic包裏提供了一個類AtomicStampedReference來解決ABA問題。這個類的compareAndSet方法作用是首先檢查當前引用是否等於預期引用,並且當前標誌是否等於預期標誌,如果全部相等,則以原子方式將該引用和該標誌的值設置爲給定的更新值。
    public boolean compareAndSet
            (V      expectedReference,//預期引用
             V      newReference,//更新後的引用
            int    expectedStamp, //預期標誌
            int    newStamp) //更新後的標誌
    
  2. 循環時間長開銷大。自旋CAS如果長時間不成功,會給CPU帶來非常大的執行開銷。如果JVM能支持處理器提供的pause指令那麼效率會有一定的提升,pause指令有兩個作用,第一它可以延遲流水線執行指令(de-pipeline),使CPU不會消耗過多的執行資源,延遲的時間取決於具體實現的版本,在一些處理器上延遲時間是零。第二它可以避免在退出循環的時候因內存順序衝突(memory order violation)而引起CPU流水線被清空(CPU pipeline flush),從而提高CPU的執行效率。

  3. 只能保證一個共享變量的原子操作。當對一個共享變量執行操作時,我們可以使用循環CAS的方式來保證原子操作,但是對多個共享變量操作時,循環CAS就無法保證操作的原子性,這個時候就可以用鎖,或者有一個取巧的辦法,就是把多個共享變量合併成一個共享變量來操作。比如有兩個共享變量i=2,j=a,合併一下ij=2a,然後用CAS來操作ij。從Java1.5開始JDK提供了AtomicReference類來保證引用對象之間的原子性,你可以把多個變量放在一個對象裏來進行CAS操作。

4.2 使用鎖機制實現原子操作

鎖機制保證了只有獲得鎖的線程能夠操作鎖定的內存區域。JVM內部實現了很多種鎖機制,有偏向鎖,輕量級鎖和互斥鎖,有意思的是除了偏向鎖,JVM實現鎖的方式都用到的循環CAS,當一個線程想進入同步塊的時候使用循環CAS的方式來獲取鎖,當它退出同步塊的時候使用循環CAS釋放鎖。詳細說明可以參見文章Java SE1.6中的Synchronized。

5. 參考資料

  1. Java SE1.6中的Synchronized
  2. Intel 64和IA-32架構軟件開發人員手冊
  3. 深入分析Volatile的實現原理

作者介紹

方騰飛,花名清英,淘寶資深開發工程師,關注併發編程,目前在廣告技術部從事無線廣告聯盟的開發和設計工作。個人博客:http://ifeve.com 微博:http://weibo.com/kirals 歡迎通過我的微博進行技術交流。

http://www.infoq.com/cn/articles/atomic-operation


Linux 原子操作

所謂原子操作,就是該操作絕不會在執行完畢前被任何其他任務或事件打斷,也就說,它的最小的執行單位,不可能有比它更小的執行單位,因此這裏的原子實際是使用了物理學裏的物質微粒的概念。

 

  原子操作需要硬件的支持,因此是架構相關的,其API和原子類型的定義都定義在內核源碼樹的include/asm/atomic.h文件中,它們都使用彙編語言實現,因爲C語言並不能實現這樣的操作。

  原子操作主要用於實現資源計數,很多引用計數(refcnt)就是通過原子操作實現的。原子類型定義如下:

 

typedef struct  {  volatile int counter;  }  atomic_t;


  volatile修飾字段告訴gcc不要對該類型的數據做優化處理,對它的訪問都是對內存的訪問,而不是對寄存器的訪問。

 

  原子操作API包括:

atomic_read(atomic_t * v);


  該函數對原子類型的變量進行原子讀操作,它返回原子類型的變量v的值。

 

 

atomic_set(atomic_t * v, int i);


  該函數設置原子類型的變量v的值爲i。

 

 

void atomic_add(int i, atomic_t *v);


  該函數給原子類型的變量v增加值i。

 

 

atomic_sub(int i, atomic_t *v);


  該函數從原子類型的變量v中減去i。

 

 

int atomic_sub_and_test(int i, atomic_t *v);


  該函數從原子類型的變量v中減去i,並判斷結果是否爲0,如果爲0,返回真,否則返回假。

 

 

void atomic_inc(atomic_t *v);


  該函數對原子類型變量v原子地增加1。

 

 

void atomic_dec(atomic_t *v);


  該函數對原子類型的變量v原子地減1。

 

 

int atomic_dec_and_test(atomic_t *v);


  該函數對原子類型的變量v原子地減1,並判斷結果是否爲0,如果爲0,返回真,否則返回假。

 

 

int atomic_inc_and_test(atomic_t *v);


  該函數對原子類型的變量v原子地增加1,並判斷結果是否爲0,如果爲0,返回真,否則返回假。

 

 

int atomic_add_negative(int i, atomic_t *v);


  該函數對原子類型的變量v原子地增加I,並判斷結果是否爲負數,如果是,返回真,否則返回假。

 

 

int atomic_add_return(int i, atomic_t *v);


  該函數對原子類型的變量v原子地增加i,並且返回指向v的指針。

 

 

int atomic_sub_return(int i, atomic_t *v);


  該函數從原子類型的變量v中減去i,並且返回指向v的指針。

 

 

int atomic_inc_return(atomic_t * v);


  該函數對原子類型的變量v原子地增加1並且返回指向v的指針。

 

 

int atomic_dec_return(atomic_t * v);

  該函數對原子類型的變量v原子地減1並且返回指向v的指針。

  原子操作通常用於實現資源的引用計數,在TCP/IP協議棧的IP碎片處理中,就使用了引用計數,碎片隊列結構struct ipq描述了一個IP碎片,字段refcnt就是引用計數器,它的類型爲atomic_t,當創建IP碎片時(在函數ip_frag_create中),使用atomic_set函數把它設置爲1,當引用該IP碎片時,就使用函數atomic_inc把引用計數加1。

  當不需要引用該IP碎片時,就使用函數ipq_put來釋放該IP碎片,ipq_put使用函數atomic_dec_and_test把引用計數減1並判斷引用計數是否爲0,如果是就釋放IP碎片。函數ipq_kill把IP碎片從ipq隊列中刪除,並把該刪除的IP碎片的引用計數減1(通過使用函數atomic_dec實現)。


原子操作函數原型


 原子操作僅執行一次,在執行過程中不會中斷也不會休眠;是最小的執行單元;鑑於原子操作這些特性,可以利用它來解決競態問題。
 往後其他同步機制都是在原子操作的基礎上進行擴展的。
 原子操作有整型原子操作、64位原子操作以及位原子操作。

1 整型原子操作
(Atomic Integer Operations)
 要使用原子操作,需要定義一個原子變量,然後使用內核提供的接口對其進行原子操作。
 整型原子變量結構如下

  1. #include <linux/type.h>
  2. typedef struct {
  3.     int counter;
  4. } atomic_t;
  可以看出整型原子變量實質上是一個32位整型變量。
 整型原子變量操作接口,其實現方式與具體的架構有關。
  1. #include <asm/atomic.h>
  2. ATOMIC_INIT(int i)                           // 定義原子變量時,將其值賦爲i
  3. int atomic_read(atomic_t *v)                 // 讀v的值
  4. void atomic_set(atomic_t *v, int i)          // 設置v的值爲i
  5. void atomic_add(int i, atomic *v)            // v的值增加i
  6. void atomic_sub(int i, atomic *v)            // v的值減少i
  7. void atomic_inc(atomic *v)                   // v的值加1
  8. void atomic_dec(atomic *v)                   // v的值減1
  9. int atomic_sub_and_test(int i, atomic_t *v)  // v的值減少i,且結果爲0時返回true
  10. int atomic_add_negative(int i, atomic_t *v)  // v的值增加i,且結果爲負數時返回true
  11. int atomic_add_return(int i, atomic_t *v)    // v的值增加i,且返回結果
  12. int atomic_sub_return(int i, atomic_t *v)    // v的值減少i,且返回結果
  13. int atomic_inc_return(atomic_t *v)           // v的值加1,且返回結果
  14. int atomic_dec_return(atomic_t *v)           // v的值減1,且返回結果
  15. int atomic_dec_and_test(atomic_t *v)         // v的值減1,且結果爲0時返回true
  16. int atomic_inc_and_test(atomic_t *v)         // v的值加1,且結果爲0時返回true

2 64位原子操作(64-Bit Atomic Operations)
 64位原子變量結構
  1. typedef struct {
  2.     u64 __aligned(8) counter;
  3. } atomic64_t;
  64位原子變量操作接口與整型變量操作接口類似,只要將整型變量接口名稱的"atomic"改成"atomic64"即可。

3 位原子操作(Atomic Bitwise Operations)
 位原子操作接口
  1. #include <asm/bitops.h>
  2. void set_bit(int nr, void *addr)           // 將addr第nr位置1
  3. void clear_bit(int nr, void *addr)         // 將addr第nr位置0
  4. void change_bit(int nr, void *addr)        // 將addr第nr位值取反
  5. int test_and_set_bit(int nr, void *addr)   // 將addr第nr位置1,並將該位之前值返回
  6. int test_and_clear_bit(int nr, void *addr) // 將addr第nr位置0,並將該位之前值返回
  7. int test_and_change_bit(int nr, void *addr)// 將addr第nr位取反,並將該位之前值返回
  8. int test_bit(int nr, void *addr)           // 將addr第nr位的值返回
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章