大佬教程收集整理的这篇文章主要介绍了通过内联汇编锁定内存操作,大佬教程大佬觉得挺不错的,现在分享给大家,也给大家做个参考。
这个问题?
我在这里提供的代码是否实现了使内存操作既线程安全又可重入的目标?
>如果有效,为什么?
>如果它不起作用,为什么?
>还不够好?我应该在C中使用register关键字吗?
我只想做…
>在内存操作之前,锁定.
>内存操作后,解锁.
代码:
volatile int atomic_gate_memory = 0; static inline void atomic_open(volatilE int *gatE) { asm volatile ( "wait:\n" "cmp %[lock],%[gate]\n" "je wait\n" "mov %[lock],%[gate]\n" : [gate] "=m" (*gatE) : [lock] "r" (1) ); } static inline void atomic_close(volatilE int *gatE) { asm volatile ( "mov %[lock],%[gate]\n" : [gate] "=m" (*gatE) : [lock] "r" (0) ); }
然后像:
void *_malloc(size_t sizE) { atomic_open(&atomic_gate_memory); void *R_989_11845@em = malloc(sizE); atomic_close(&atomic_gate_memory); return mem; } #define malloc(sizE) _malloc(sizE)
..同样适用于calloc,realloc,free和fork(适用于linuX).
#ifdef _UNISTD_H int _fork() { pid_t pid; atomic_open(&atomic_gate_memory); pid = fork(); atomic_close(&atomic_gate_memory); return pid; } #define fork() _fork() #endif
为atomic_open加载堆栈帧后,objdump生成:
00000000004009a7 <wait>: 4009a7: 39 10 cmp %edx,(%raX) 4009a9: 74 fc je 4009a7 <wait> 4009ab: 89 10 mov %edx,(%raX)
另外,鉴于上面的拆卸;我可以假设我正在进行原子操作,因为它只是一条指令吗?
在现代优化编译器中,寄存器是一个毫无意义的暗示.
我认为一个简单的自旋锁在x86上没有任何真正重大/明显的性能问题就是这样的.当然,真正的实现将在旋转一段时间之后使用系统调用(如Linux futex
),并且解锁将必须检查是否需要通过另一个系统调用通知任何服务员.这个很重要;你不想永远浪费cpu时间(和能量/热量)无所事事.但从概念上讲,在你采用后备路径之前,这是自旋锁的旋转部分.这是light-weight locking实施的重要部分. (在调用内核之前只尝试锁定一次是一个有效的选择,而不是完全旋转.)
在内联asm中实现尽可能多的这个,或者最好使用C11 stdatomic,就像这个semaphore implementation一样.
;;; UNTESTED ;;;;;;;; ;;; TODO: **IMPORTANT** fall BACk to OS-supported sleep/wakeup after spinning some ; first arg in rdi,in the AMD64 SysV ABI ;;;;;void spin_lock (volatile char *lock) global spin_unlock spin_unlock: ;; debug: check that the old value was non-zero. double-unlocking is a nasty bug mov byte [rdi],0 ret ;; The store has release semantics,but not sequential-consistency (which you'd get from an xchg or something),;; because acquire/release is enough to protect a critical section (hence the Name) ;;;;;void spin_unlock(volatile char *lock) global spin_lock spin_lock: cmp byte [rdi],0 ; avoid wriTing to the cache line if we don't own the lock: should speed up the other thread unlocking jnz .spinloop mov al,1 ; only need to do this the first time,otherwise we kNow al is non-zero .retry: xchg al,[rdi] test al,al ; check if we actually got the lock jnz .spinloop ret ; no taken branches on the fast-path .spinloop: pause ; very old cpus decode it as REP NOP,which is fine cmp byte [rdi],0 ; To get a compiler to do this in C++11,use a memory_order_acquire load jnz .spinloop jmp .retry
如果您使用原子标记的位域,则可以使用锁定bts(测试和设置)相当于xchg-with-1.你可以旋转bt或测试.要解锁,你需要锁btr,而不仅仅是btr,因为它将是字节的非原子读 – 修改 – 写,甚至包含32位.
使用字节或字大小的锁定,您甚至不需要锁定操作来解锁; release semantics are enough. glibc的pthread_spin_unlock
与我的解锁功能相同:一个简单的商店.
如果我们看到它已被锁定,这可以避免写入锁定.这避免了在运行拥有它的线程的核心的L1中使高速缓存行无效,因此它可以在解锁期间返回到“已修改”(MESIF或MOESI),具有较少的高速缓存一致性延迟.
我们也不会在循环中使用锁定操作来充斥cpu.我不确定这一般会减慢多少,但是所有等待相同螺旋锁的10个线程都会使内存仲裁硬件保持相当繁忙.这可能会减慢持有锁的线程或系统上其他无关线程的速度,同时它们会使用其他锁或内存.
PAUSE也是必不可少的,以避免cpu对内存排序的错误推测.只有当您正在读取的内存被另一个内核修改时才退出循环.但是,我们不想在无争议的情况下暂停.在Skylake上,PAUSE等待的时间要长得多,比如~100cycles IIRC,所以你绝对应该将spinloop与初始检查分开进行解锁.
以上是大佬教程为你收集整理的通过内联汇编锁定内存操作全部内容,希望文章能够帮你解决通过内联汇编锁定内存操作所遇到的程序开发问题。
如果觉得大佬教程网站内容还不错,欢迎将大佬教程推荐给程序员好友。
本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
如您有任何意见或建议可联系处理。小编QQ:384754419,请注明来意。