久久国产成人av_抖音国产毛片_a片网站免费观看_A片无码播放手机在线观看,色五月在线观看,亚洲精品m在线观看,女人自慰的免费网址,悠悠在线观看精品视频,一级日本片免费的,亚洲精品久,国产精品成人久久久久久久

分享

理解內存屏障(四)

 mzsm 2015-12-27

作者:新浪微博(@NP等不等于P

計算機學習微信公眾號(jsj_xx)

先回顧一下,。

上次講過compiler相關的優(yōu)化屏障和ACCESS_ONCE(),。對于ACCESS_ONCE()粒度更細,重在理解ONCE字眼,,compiler需要其使用次數不是compiler優(yōu)化可能導致的多次也不是0次,,而是1次

2.3.1.2 cpu內存屏障

linux內核的8個內存屏障實現:(強制(MANDATORY)屏障可以用在UP系統(tǒng))

TYPE  MANDATORY    SMP CONDITIONAL

====== ========  =====

GENERAL  mb()  smp_mb()

WRITE  wmb()  smp_wmb()

READ  rmb()  smp_rmb()

DATA DEP read_barrier_depends()smp_read_barrier_depends()

除了數據依賴屏障,,其它屏障都內含優(yōu)化屏障,。看看數據依賴屏障,,它不需要優(yōu)化屏障的配合,,因為compiler會感知這種依賴關系。顯然,,在UP系統(tǒng)中, SMP內存屏障將退化為優(yōu)化屏障(因為內存屏障本質是在協(xié)調多cpu之間的操作),。而SMP系統(tǒng)上的共享內存的(有序)訪問必須使用內存屏障或者鎖

還有一些高級的函數,,封裝了特定的操作:(和ACCESS_ONCE()一樣,,這樣控制粒度更精細精準了)

(*) set_mb(var, value)

value賦值給var之后插入一個通用內存屏障。顯然,,在UP系統(tǒng),,插入的屏障退化為一個優(yōu)化屏障。

(*) smp_mb__before_atomic()/smp_mb__after_atomic();

用于沒有返回值的原子(加/減/清bit/置bit)操作,,一般用于引入計數,,該原子操作本身不含屏障,用該函數插入一個通用內存屏障,。比如:(先宣告死亡,,然后計數減1)

obj->dead = 1;

smp_mb__before_atomic();

atomic_dec(&obj->ref_count);

這樣,就保證了先宣告死亡后改變計數的有序順序,。

2.3.1.3 mmio寫屏障

對內存映射的io空間使用mmiowb()來保證寫順序,,后文的另一個部分會講,此處略過,。

2.3.2 內核隱式內存屏障

2.3.2.1 鎖

內核中的一些鎖結構都是基于ACQUIRE/RELEASE操作,,也就是隱含含有內存屏障的,比如:

(*) spin locks

(*) R/W spin locks

(*) mutexes

(*) semaphores

(*) R/W semaphores

(*) RCU

回憶一下之前講的ACQUIRE/RELEASE操作,,我們知道:

*A = a;

ACQUIRE M

RELEASE M

*B = b;

可能會有這樣的順序:

ACQUIRE M, STORE *B, STORE *A, RELEASE M

或者更狠的:(ACQUIRE和RELEASE操作的是兩個不相關的臨界區(qū))

*A = a;

RELEASE M

ACQUIRE N

*B = b;

可能會有這樣的執(zhí)行順序:

ACQUIRE N, STORE *B, STORE *A, RELEASE M

總結就是,,這兩個配合使用不是一個完全意義的內存屏障,但是確實對臨界區(qū)內的代碼有保護作用,。

2.3.2.2 睡眠和喚醒

考慮下睡眠和喚醒兩個操作,,它們之間需要有屏障的保護,。

睡眠代碼:

for (;;) {

set_current_state(TASK_UNINTERRUPTIBLE);

if (event_indicated)

break;

schedule();

}

喚醒代碼:

event_indicated = 1;

wake_up(&event_wait_queue);

此時,正確的使用是:

CPU 1                        CPU 2

=================            ============================

set_current_state();        STORE event_indicated

    set_mb();                  wake_up();

       STORE current->state        <write barrier>

       <general barrier>            STORE current->state

LOAD event_indicated

這樣才能保證cpu1在被喚醒之后能夠感知到條件變量event_indicated的最新值,。

2.4 cpu間的鎖和屏障的關系

cpu間的鎖,,比如自旋鎖,也需要考慮順序問題,,比如:

CPU 1                    CPU 2

====                   =========

ACCESS_ONCE(*A) = a;    ACCESS_ONCE(*E) = e;

ACQUIRE M                ACQUIRE Q

ACCESS_ONCE(*B) = b;  ACCESS_ONCE(*F) = f;

ACCESS_ONCE(*C) = c;     ACCESS_ONCE(*G) = g;

RELEASE M                RELEASE Q

ACCESS_ONCE(*D) = d;    ACCESS_ONCE(*H) = h;

cpu1和cpu2在各自的cpu上能夠保證各自的有序序列,,但是cpu3上看到的則是cpu1和cpu2的兩個有序序列的交叉。再看個交叉的例子:(對設備的io空間做寫操作)

CPU 1                CPU 2

========             ============

spin_lock(Q)

writel(0, ADDR)

writel(1, DATA);

spin_unlock(Q);

                 spin_lock(Q);

                 writel(4, ADDR);

                 writel(5, DATA);

                 spin_unlock(Q);

此時的如果cpu1和cpu2的寫操作交叉執(zhí)行就出錯,,解決交叉的方法是使用mmiowb()寫屏障:

CPU 1            CPU 2

===              =======

spin_lock(Q)

writel(0, ADDR)

writel(1, DATA);

mmiowb();

spin_unlock(Q);

               spin_lock(Q);

               writel(4, ADDR);

               writel(5, DATA);

               mmiowb();

               spin_unlock(Q);

2.5 哪里需要使用屏障

要喚醒一個等待進程, 信號量喚醒函數有這些處理步驟:

1)讀進程的waiter->list.next,記住下一個等待進程,;

2)讀進程的waiter->task,,記住task指針;

3)清進程的waiter->task指針,;(表示該進程已經獲得該信號量,,可以理解為:清完該進程就能調度運行了)

4)調用wakeup喚醒該進程;

5)釋放waiter->task的引用計數,;

用代碼表示就是:

LOAD waiter->list.next;

LOAD waiter->task;

STORE waiter->task;

CALL wakeup

RELEASE task

這里前兩個是讀操作,,第三個是寫操作,如果三者之間發(fā)生亂序:

CPU 1                    CPU 2

=====                      ===========

                                down_xxx()

                                Queue waiter

                                Sleep

up_yyy()

LOAD waiter->task;

STORE waiter->task;

                                Woken up by other event

<preempt>

                                Resume processing

                                down_xxx() returns

                                call foo()

                                foo() clobbers *waiter

</preempt>

LOAD waiter->list.next;

--- OOPS ---

cpu1上OOPS原因就是沒有讓兩個讀操作先于寫操作導致,,解決的方法是插入通用屏障:

LOAD waiter->list.next;

LOAD waiter->task;

smp_mb();

STORE waiter->task;

CALL wakeup

RELEASE task

2.6 內核中io屏障的作用

在操作io空間時,,需要使用適當的函數,比如mmiowb()寫屏障,。

2.7 執(zhí)行有序的最小假想模型

x86對程序執(zhí)行順序有較強約束,,但是我們也應該看到,很多其它cpu都是弱約束的:

Some CPUs (such as i386 or x86_64) are more constrained than others (such as powerpc or

frv), and so the most relaxed case (namely DEC Alpha) must be assumed outside

of arch-specific code.

 關于我們

新浪微博(@NP等不等于P

計算機學習微信公眾號(jsj_xx)

原創(chuàng)技術文章,,感悟計算機,,透徹理解計算機!

    本站是提供個人知識管理的網絡存儲空間,所有內容均由用戶發(fā)布,,不代表本站觀點,。請注意甄別內容中的聯(lián)系方式、誘導購買等信息,,謹防詐騙,。如發(fā)現有害或侵權內容,請點擊一鍵舉報,。
    轉藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多