【发布时间】:2016-09-11 12:18:48
【问题描述】:
我对低级的东西不熟悉,所以我完全不知道你在那里可能会遇到什么样的问题,我什至不确定我是否正确理解了“原子”这个词。现在我正在尝试通过扩展程序集围绕内存操作制作简单的原子锁。为什么?为了好奇。我知道我在这里重新发明轮子,并且可能过度简化了整个过程。
问题? 我在这里展示的代码是否实现了使内存操作既线程安全又可重入的目标?
- 如果有效,为什么?
- 如果不起作用,为什么?
- 不够好?例如,我应该在 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 *mem = 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 的 stackframe 后,objdump 生成:
00000000004009a7 <wait>:
4009a7: 39 10 cmp %edx,(%rax)
4009a9: 74 fc je 4009a7 <wait>
4009ab: 89 10 mov %edx,(%rax)
另外,鉴于上面的反汇编;我可以假设我正在执行原子操作,因为它只有一条指令吗?
【问题讨论】:
-
不,它不是线程安全的,因为两个线程可以同时运行
cmp并假设它们可以获取锁。 -
交错(多任务)也会导致同样的问题。在一个线程执行
cmp之后,下一个线程可能会获得cpu 并且也执行它的cmp。 -
显然有办法。推荐的是不使用汇编,但如果你确实想使用它,你应该使用原子指令,例如
lock cmpxchg -
为什么需要叉子周围的锁? malloc 也不需要它们,因为 Linux 上的 malloc 实现是线程安全的,因此已经有了必要的锁。 fork 系统调用不会更改进程中其他线程可见的任何状态,因此没有什么可以用锁保护。
-
虽然因为你对 fork 和 malloc 包装器使用相同的锁,但理论上如果你重新初始化子进程中的锁,它会让你在子进程中安全地调用 malloc,因为你知道 fork 没有中断另一个线程中的 malloc 调用。实际上,问题在于不仅您自己的代码可以调用 malloc,各种其他库函数都可以调用 malloc,并且这些调用不会受到您的包装器的保护。幸运的是,这一切在实践中都是不必要的,因为 Linux 的 glibc 使用 pthread_atfork 跨叉锁定自身。
标签: c assembly x86 locking spinlock