標籤:現代體繫結構unix 多核程式設計 儲存模型 強定序 完全儲存定序
最近在看《現代體繫結構上的UNIX系統——核心程式員的SMP和Caching技術》,這裡抄點東西作為筆記吧!
順序儲存模型強制儲存空間操作(load和store)都按照程式次序來執行,即這些指令是按照在隨程式執行的指令流中出現的順序次序來執行的。它也指定了,由不同處理器完成的load和store操作也要以某種順序、但又是非確定性方式排序。這種儲存模型應該是大家最容易理解的,甚至都認為實際MP也是這樣工作的。但是,這種儲存結構是非常落後的,現代處理器應該已經淘汰了這種結構。
書中講到了一個例子,如下;
| 處理器一 |
處理器二 |
| store %r1, A |
store %r1, B |
| load %r2, B |
load %r2, A |
在強定序(strong order)的系統中,一共有四種可能的儲存排序。但是可以肯定的是最後一條指令一定是load指令,也就是說至少有一條load指令會讀取由別的處理器儲存的新值。Dekker演算法就是利用了這樣的性質。Dekker演算法是一種用在硬體中沒有原子的讀-改-寫儲存空間操作(test-and-set或者xchg類似的指令)的情況下實現的臨界區的技術。
enum state {UNLOCKED, LOCKED };typedef struct { char status[2]; char turn;} lock_t;void initlock(lock_t *lock){ lock->status[0] = UNLOCK; lock->status[1] = UNLOCK; lock->turn = 0;}void lock(volatile lock_t *lock){ lock->status[CPUID()] = LOCKED; while(lock->status[othercpu()] == LOCKED) { if(lock->turn != cpuid()) { lock-status[cpuid()] = UNLOCKED; while(lock->turn == othercpu()) ; lock->status[cpuid()] = LOCKED; } }}voidunlock(loct_t *lock){ lock->status[cpuid()] = UNLOCKED; lock->turn = othercpu();}
TSO(完全儲存定序):
完全儲存定序相對強定序來說,對記憶體操作的順序規律弱化了。首先,一個來自儲存空間的load指令的任意序列,假定它們都使用獨立的寄存器,就能以任何次序來執行。所有儲存指令都要確定次序,因此命名為完全儲存定序(total store ordering)。完全儲存定序MP中,如SPARC提供一種atomic-swap指令,它把單個位置的載入和儲存操作當做一個不可分割的整體操作。
當一條atomic-swap指令發出時,他就被放到儲存緩衝區,相對於以前的store指令按FIFO次序進行處理。但是它會屏蔽更多的儲存空間操作,直到儲存緩衝區為空白、原子交換操作完成為止。
Dekker演算法在TSO下是不能工作的,這是因為第一張表中的指令的可能順序變多了,沒有了前面分析的規律。在TSO結構下,lock指令就需要以下面的方式實現。
void lock(void lock_t *lock_status){ while(swap_atomic(lock-status, 1) == 1) ;}
PSO(部分儲存定序):
PSO則相對更加寬鬆,store指令的順序也不一定以FIFO的順序執行了。但是可以保證store緩衝中對相同位置的store操作以程式順序執行。同時load指令仍然會查看store緩衝,看是否有命中,如果有的話則返回最近向那個位置執行的store指令所帶的資料。在PSO中也需要atomic_swap指令來實現鎖,其行和TSO時介紹的是一樣的。原子交換操作阻止了再進一步發出儲存空間操作,但是在原子交換操作執行的時候,store緩衝不保證為空白,也就是說原子操作會在前面的store指令完成前結束。但是由於指令是順序發射的(這個保證現在看來比較勉強),臨界段內的store和load指令都還沒有發射。所以,PSO的上鎖實現和TSO是基本一樣的。
那麼,解鎖的時候呢?考慮下面一段臨界區指令流:
atomic-swap /*獲得鎖*/load %r1, counter /*獲得原來的值*/add %r2, %r1, 1 /*累加計數器*/store %r2, counter /*儲存新值*/clear %r3store %r3, lock /*釋放自旋鎖*/
考慮倒數第一條指令和倒數第一條指令,他們都是store指令。在PSO結構中,對不同位置的store指令的順序是不確定的。倘若,釋放自旋鎖指令在儲存新值指令之前完成,則整個臨界區就被破壞了。因為絕對不允許這種情況出現,所以SPARC體繫結構包括了一種稱為store-barrier的指令,強製造成一定程度的順序。用《POSIX多線程程式設計》中的一句話,記憶體屏障就是一睹移動的“牆”。牆壁之間的指令順序是不一定的,但是牆壁兩側的指令是有相對順序規律的。
所以,PSO結構下釋放自旋鎖的方式如下:
voidunlock(volatile lock_t *lock_status){ store_barrier(); *lock_status();}
其實我看本書的目的是為了看懂《Is Parallel Programming Hard, And, if so, What Can You Do About It?》。這本書貌似只有電子版的,並發編程網有一個翻譯版本叫《深入理解並行編程V1.0》,個人覺得這兩本書配合起來看非常爽,因為他們側重點不同,可以互補理解。
Blog地址:intheworld的專欄
著作權聲明:本文為博主原創文章,未經博主允許不得轉載。
多核程式設計——儲存模型