На веб-сайте GNU имеется простой пример, который должен продемонстрировать проблемы, возникающие при неатомарном доступе.В примере содержится небольшая ошибка, они забыли #include <unistd.h>
:
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
struct two_words { int a, b; } memory;
static struct two_words zeros = { 0, 0 }, ones = { 1, 1 };
void handler(int signum)
{
printf ("%d,%d\n", memory.a, memory.b);
alarm (1);
}
int main (void)
{
signal (SIGALRM, handler);
memory = zeros;
alarm (1);
while (1)
{
memory = zeros;
memory = ones;
}
}
Идея состоит в том, что присваивание memory = zeros;
или memory = ones;
занимает несколько циклов и, таким образом, обработчик прерываний сможет печатать«0 1» или «1 0» в некоторый момент времени.
Однако, что интересно для архитектуры x86-64, код сборки, создаваемый компилятором gcc, выглядит следующим образом.Похоже, что назначение выполняется в течение одного цикла командой movq:
.file "interrupt_handler.c"
.text
.comm memory,8,8
.local zeros
.comm zeros,8,8
.data
.align 8
.type ones, @object
.size ones, 8
ones:
.long 1
.long 1
.section .rodata
.LC0:
.string "%d,%d\n"
.text
.globl handler
.type handler, @function
handler:
.LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
subq $16, %rsp
movl %edi, -4(%rbp)
movl 4+memory(%rip), %edx
movl memory(%rip), %eax
movl %eax, %esi
leaq .LC0(%rip), %rdi
movl $0, %eax
call printf@PLT
movl $1, %edi
call alarm@PLT
nop
leave
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size handler, .-handler
.globl main
.type main, @function
main:
.LFB1:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
leaq handler(%rip), %rsi
movl $14, %edi
call signal@PLT
movq zeros(%rip), %rax
movq %rax, memory(%rip)
movl $1, %edi
call alarm@PLT
.L3:
movq zeros(%rip), %rax
movq %rax, memory(%rip)
movq ones(%rip), %rax
movq %rax, memory(%rip)
jmp .L3
.cfi_endproc
.LFE1:
.size main, .-main
.ident "GCC: (Ubuntu 7.3.0-16ubuntu3) 7.3.0"
.section .note.GNU-stack,"",@progbits
Может кто-нибудь объяснить, как возможно, что два разных назначения выполняются в одном цикле?Потому что я думаю, что присвоение двух разных целочисленных значений должно происходить двум разным частям памяти, но здесь почему-то кажется, что они записаны в одном месте.
Этот пример меняется, когда вместо intЯ бы использовал двойной.Цикл while в сборке становится:
.L3:
movq zeros(%rip), %rax
movq 8+zeros(%rip), %rdx
movq %rax, memory(%rip)
movq %rdx, 8+memory(%rip)
movq ones(%rip), %rax
movq 8+ones(%rip), %rdx
movq %rax, memory(%rip)
movq %rdx, 8+memory(%rip)
jmp .L3