On the website of GNU, there is a simple example available that is supposed to demonstrate the problems appearing with non-atomic access. The example contains a small mistake, they have forgotten #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;
}
}
The idea is that the assignment memory = zeros;
or the memory = ones;
takes multiple cycles and thus the interrupt handler will be able to print "0 1" or "1 0" at some point in time.
However, interestingly for the x86-64 architecture, the assembly code produced by the GCC compiler looks as follows. It appears that the assignment is done within one single cycle by the movq instruction:
.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
How is it possible that two separate assignments are done within a single cycle? I would think that the assignment of two different ints has to happen to two different pieces of memory, but somehow it seems over here that they are written to the same place.
This example changes when instead of int, I would use double. In that case, the while loop in assembly becomes:
.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