Linux并发与同步专题 (1)原子操作和内存屏障
關鍵詞:。
《Linux并發與同步專題 (1)原子操作和內存屏障》
《Linux并發與同步專題 (2)spinlock》
《Linux并發與同步專題 (3) 信號量》
《Linux并發與同步專題 (4) Mutex互斥量》
《Linux并發與同步專題 (5) 讀寫鎖》
《Linux并發與同步專題 (6) RCU》
《Linux并發與同步專題 (7) 內存管理中的鎖》
《Linux并發與同步專題 (8) 最新更新與展望》
?
1. 原子操作
?
1.1 一個原子操作例子思考
?
1.2 原子操作API
atomic_t數據結構表示原子變量,它的實現依賴于不同的體系結構。
typedef struct {int counter; } atomic_t;Linux提供了很多操作原子變量的API。以arch/arm/include/asm/atomic.h為例。
#define ATOMIC_INIT(i) { (i) }----------------------------------聲明一個原子變量并初始化為i。#define atomic_read(v) ACCESS_ONCE((v)->counter)----------------讀取原子變量的值。 #define atomic_set(v,i) (((v)->counter) = (i))------------------設置變量v的值為i。#define atomic_xchg(v, new) (xchg(&((v)->counter), new))-----------把new賦值給原子變量v,返回原子變量v的舊值。#define atomic_cmpxchg---------------------------------------------比較old和原子變量v的值,如果相等則把new賦值給v,返回原子變量v的舊值。
#define atomic_inc(v) atomic_add(1, v)----------------------原子地給v加1 #define atomic_dec(v) atomic_sub(1, v)----------------------原子地給v減1#define atomic_inc_and_test(v) (atomic_add_return(1, v) == 0)---原子地給v加1,并且返回最新v的值 #define atomic_dec_and_test(v) (atomic_sub_return(1, v) == 0)---原子地給v減1,并且返回最新v的值 #define atomic_inc_return(v) (atomic_add_return(1, v))----------原子地給v加1,結果為0返回true,否則返回false。 #define atomic_dec_return(v) (atomic_sub_return(1, v))----------原子地給v減1,結果為0返回true,否則返回fasle。 #define atomic_sub_and_test(i, v) (atomic_sub_return(i, v) == 0)---原子地給v減i,結果為0返回true,否則返回false。#define atomic_add_negative(i,v) (atomic_add_return(i, v) < 0)
?
上面雖然有多種API但是基礎的只有atomic_add()/atomic_sub()/atomic_add_return()/atomic_sub_return()四種。
他們通過ATOMIC_OPS定義,產生atomic_add()/atomic_sub()/atomic_add_return()/atomic_sub_return()四個函數。
ARM使用ldrex和strex指令來保證add操作的原子性,指令后綴ex表示exclusive。
ldrex Rt, [Rn] - 把Rn寄存器只想內存地址的內容加載到Rt寄存器中。
strex Rd, Rt, [Rn] - 把Rt寄存器的值保存到Rn寄存器指向的內存地址中,Rd保存更新的結果,0表示更新成功,1表示失敗。
?
GCC嵌入匯編的格式如下:
__asm__ __volatile__(指令部 : 輸出部 : 輸入部 : 損壞部)
__volatile__防止編譯器優化,@符號標識是注釋。
?
?
?
#define ATOMIC_OPS(op, c_op, asm_op) \ATOMIC_OP(op, c_op, asm_op) \ATOMIC_OP_RETURN(op, c_op, asm_op)ATOMIC_OPS(add, +=, add) ATOMIC_OPS(sub, -=, sub)#define ATOMIC_OP(op, c_op, asm_op) \ static inline void atomic_##op(int i, atomic_t *v) \ { \unsigned long tmp; \int result; \\prefetchw(&v->counter); \----------------------提前把原子變量的值加載到cache中,以便提高性能。__asm__ __volatile__("@ atomic_" #op "\n" \ "1: ldrex %0, [%3]\n" \----------------------ldrex指令把原子變量v->counter的值加載到result變量中,然后在result變量中增加i值,使用strex指令把result變量的值存放到原子變量v->result中,其中變量tmp保存著strex指令更新后的結果。 " " #asm_op " %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) \--------------------最后比較該結果是否為0,為0則表示strex指令更新成功。如果不為0,那么跳轉到標簽“1”處重新再來一次。: "cc"); \ } \#define ATOMIC_OP_RETURN(op, c_op, asm_op) \ static inline int atomic_##op##_return(int i, atomic_t *v) \ { \unsigned long tmp; \int result; \\smp_mb(); \prefetchw(&v->counter); \\__asm__ __volatile__("@ atomic_" #op "_return\n" \ "1: ldrex %0, [%3]\n" \ " " #asm_op " %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"); \\smp_mb(); \\return result; \ }?
?
除了上面的API還有atomic_xchg和atomic_cmpxchg()。
static inline unsigned long __xchg(unsigned long x, volatile void *ptr, int size) {extern void __bad_xchg(volatile void *, int);unsigned long ret; #ifdef swp_is_buggyunsigned long flags; #endif #if __LINUX_ARM_ARCH__ >= 6unsigned int tmp; #endifsmp_mb();prefetchw((const void *)ptr);switch (size) { #if __LINUX_ARM_ARCH__ >= 6case 1:asm volatile("@ __xchg1\n""1: ldrexb %0, [%3]\n"" strexb %1, %2, [%3]\n"" teq %1, #0\n"" bne 1b": "=&r" (ret), "=&r" (tmp): "r" (x), "r" (ptr): "memory", "cc");break;case 4:asm volatile("@ __xchg4\n""1: ldrex %0, [%3]\n"" strex %1, %2, [%3]\n"" teq %1, #0\n"" bne 1b": "=&r" (ret), "=&r" (tmp): "r" (x), "r" (ptr): "memory", "cc");break; #elif defined(swp_is_buggy) #ifdef CONFIG_SMP #error SMP is not supported on this platform #endifcase 1:raw_local_irq_save(flags);ret = *(volatile unsigned char *)ptr;*(volatile unsigned char *)ptr = x;raw_local_irq_restore(flags);break;case 4:raw_local_irq_save(flags);ret = *(volatile unsigned long *)ptr;*(volatile unsigned long *)ptr = x;raw_local_irq_restore(flags);break; #elsecase 1:asm volatile("@ __xchg1\n"" swpb %0, %1, [%2]": "=&r" (ret): "r" (x), "r" (ptr): "memory", "cc");break;case 4:asm volatile("@ __xchg4\n"" swp %0, %1, [%2]": "=&r" (ret): "r" (x), "r" (ptr): "memory", "cc");break; #endifdefault:__bad_xchg(ptr, size), ret = 0;break;}smp_mb();return ret; }#define xchg(ptr,x) \((__typeof__(*(ptr)))__xchg((unsigned long)(x),(ptr),sizeof(*(ptr))))static inline int atomic_cmpxchg(atomic_t *ptr, int old, int new) {int oldval;unsigned long res;smp_mb();prefetchw(&ptr->counter);do {__asm__ __volatile__("@ atomic_cmpxchg\n""ldrex %1, [%3]\n""mov %0, #0\n""teq %1, %4\n""strexeq %0, %5, [%3]\n": "=&r" (res), "=&r" (oldval), "+Qo" (ptr->counter): "r" (&ptr->counter), "Ir" (old), "r" (new): "cc");} while (res);smp_mb();return oldval; }?
?
?
1.3 ARM32如何保證原子性
?
2. 內存屏障
?
2.1 內存屏障3條指令DMB/DSB/ISB
ARM體系架構中常見的3條內存屏障指令:
數據內存屏障DMB:Data Memory Barrier,它可確保會先檢測到程序中位于DMB指令前的所有顯示內存訪問指令,然后再檢測到程序中位于DMB指令后的顯式內存訪問指令。它不影響其它指令在處理器上的執行順序。
?
數據同步屏障DSB:Data Synchronization Barrier,是一種特殊的內存屏障,只有當此指令執行完畢后,才會執行程序中位于此指令后的指令。
當滿足以下條件時,此指令才會完成:
- 位于此指令前的所有顯示內存訪問均完成。
- 位于此指令前的所有緩存、跳轉預測和TLB維護操作全部完成。
?
指令同步屏障ISB:Instruction Synchronization Barrier,可刷新處理器中的管道,因此可確保在ISB指令完成后,才從高速緩存或內存中提取位于該指令后的其他所有指令。
這可確保提取時間晚于ISB指令的指令,能夠檢測到ISB指令執行前就已經執行的上下文更改操作的執行效果。
?
2.2 內存屏障API
?
| API | 描述 | ? |
| barrier() | 編譯優化屏障,阻止編譯器為了性能優化而進行指令重排。 | ? |
| mb() | 內存屏障(包括讀和寫),用于SMP和UP。 | ? |
| rmb() | 讀內存屏障,用于SMP和UP。 | ? |
| wmb() | 寫內存屏障,用于SMP和UP。 | ? |
| smp_mb() | 用于SMP場合的內存屏障。對于UP不存在memory order的問題,在UP上就是一個優化屏障,確保匯編和C代碼的memory order一致。 | ? |
| smp_rmb() | 用于SMP場合的讀內存屏障。 | ? |
| smp_wmb() | 用于SMP場合的寫內存屏障。 | ? |
| smp_read_barrier_depends() | 讀依賴屏障。 | ? |
| ? | ? | ? |
?
#define isb(option) __asm__ __volatile__ ("isb " #option : : : "memory") #define dsb(option) __asm__ __volatile__ ("dsb " #option : : : "memory") #define dmb(option) __asm__ __volatile__ ("dmb " #option : : : "memory")#define mb() do { dsb(); outer_sync(); } while (0) #define rmb() dsb() #define wmb() do { dsb(st); outer_sync(); } while (0)--------------------------------ST:DSB operation that waits only for stores to complete#define smp_mb() dmb(ish)----------------------------------------------------------------ISH:DMB operation only to the inner shareable domain #define smp_rmb() smp_mb() #define smp_wmb() dmb(ishst)-------------------------------------------------------------ISHST:DMB operation that waits only for stores to complete, and only to the inner shareable domain#define smp_read_barrier_depends() do { } while(0)?
2.3 內存屏障例子
?
2.3.1 一個網卡驅動中發送數據包
網絡數據包寫入buffer后交給DMA負責發送,wmb()保證在DMA傳輸之前,數據被完全寫入到buffer中。
static netdev_tx_t rtl8139_start_xmit (struct sk_buff *skb,struct net_device *dev) { .../* Note: the chip doesn't have auto-pad! */if (likely(len < TX_BUF_SIZE)) {if (len < ETH_ZLEN)memset(tp->tx_buf[entry], 0, ETH_ZLEN);skb_copy_and_csum_dev(skb, tp->tx_buf[entry]);dev_kfree_skb_any(skb);} else {dev_kfree_skb_any(skb);dev->stats.tx_dropped++;return NETDEV_TX_OK;}spin_lock_irqsave(&tp->lock, flags);/** Writing to TxStatus triggers a DMA transfer of the data* copied to tp->tx_buf[entry] above. Use a memory barrier* to make sure that the device sees the updated data.*/wmb();------------------------------------------------------------------確保之前的數據已經進入buffer,將buffer操作和DMA操作隔開。RTL_W32_F (TxStatus0 + (entry * sizeof (u32)),tp->tx_flag | max(len, (unsigned int)ETH_ZLEN)); ... }?
2.3.2 睡眠喚醒API中的內存屏障
通常一個進程因為等待某系時間需要睡眠,調用wait_event()。
在wait_event()中有prepare_to_wait_event()調用了set_current_state()。
?
#define set_current_state(state_value) \set_mb(current->state, (state_value))#define set_mb(var, value) do { var = value; smp_mb(); } while (0)--------------此處smp_mb()確保current->state的值已經得到更新。?
喚醒者通常調用__set_task_cpu()來設置當前進程對應的調度實體
static inline void __set_task_cpu(struct task_struct *p, unsigned int cpu) {set_task_rq(p, cpu); #ifdef CONFIG_SMP/** After ->cpu is set up to a new value, task_rq_lock(p, ...) can be* successfuly executed on another CPU. We must ensure that updates of* per-task data have been completed by this moment.*/smp_wmb();-----------------------------------確保之前的的寫操作已經得到更新。task_thread_info(p)->cpu = cpu;p->wake_cpu = cpu; #endif }?
轉載于:https://www.cnblogs.com/arnoldlu/p/9236300.html
總結
以上是生活随笔為你收集整理的Linux并发与同步专题 (1)原子操作和内存屏障的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Protobuf在Unity中的通讯使用
- 下一篇: POJ 入门