From f0265a0d2b5da3214d5c69c5490ef78a90244a7b Mon Sep 17 00:00:00 2001 From: Richard Braun Date: Thu, 4 Jan 2018 14:59:41 +0100 Subject: thread: discuss compiler barriers --- src/cpu.h | 6 ++++++ src/thread.c | 45 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+) diff --git a/src/cpu.h b/src/cpu.h index 6326a58..0ca61d6 100644 --- a/src/cpu.h +++ b/src/cpu.h @@ -82,6 +82,9 @@ typedef void (*cpu_irq_handler_fn_t)(void *arg); /* * Enable/disable interrupts. + * + * These functions imply a compiler barrier. + * See thread_preempt_disable() in thread.c. */ void cpu_intr_enable(void); void cpu_intr_disable(void); @@ -90,6 +93,9 @@ void cpu_intr_disable(void); * Disable/restore interrupts. * * Calls to these functions can safely nest. + * + * These functions imply a compiler barrier. + * See thread_preempt_disable() in thread.c. */ uint32_t cpu_intr_save(void); void cpu_intr_restore(uint32_t eflags); diff --git a/src/thread.c b/src/thread.c index e361473..0d63be8 100644 --- a/src/thread.c +++ b/src/thread.c @@ -241,6 +241,48 @@ thread_preempt_disable(void) thread->preempt_level++; assert(thread->preempt_level != 0); + /* + * This is a compiler barrier. It tells the compiler not to reorder + * the instructions it emits across this point. + * + * Reordering is one of the most effective ways to optimize generated + * machine code, and the C specification allows compilers to optimize + * very aggressively, which is why C shouldn't be thought of as a + * "portable assembler" any more. + * + * Sometimes, especially when building critical sections, strong control + * over ordering is required, and this is when compiler barriers should + * be used. In this kernel, disabling preemption is one of the primary + * ways to create critical sections. The following barrier makes sure + * that no instructions from the critical section leak out before it. + * + * When using GCC or a compatible compiler such as Clang, compiler + * barriers are implemented with an inline assembly expression that + * includes the "memory" clobber. This clobber tells the compiler that + * memory may change in unexpected ways. All memory accesses started + * before the barrier must complete before the barrier, and all memory + * accesses that complete after the barrier must start after the barrier. + * See barrier() in macros.h. + * + * When calling assembly functions, there is usually no need to add + * an explicit compiler barrier, because the compiler, not knowing + * what the external function does, normally assumes memory may change, + * i.e. that the function has unexpected side effects. In C code however, + * the compiler has better knowledge about side effects. That knowledge + * comes from the amount of code in a single compilation unit. The + * traditional compilation unit when building with optimizations is the + * source file, but with link-time optimizations (LTO), this can be the + * whole program. This is why specifying barriers in C code is required + * for a reliable behaviour. + * + * Also note that compiler barriers only affect the machine code + * generated by the compiler. Processors also internally perform + * various kinds of reordering. When confined to a single processor, + * this can safely be ignored because the processor model guarantees + * that the state seen by software matches program order. This + * assumption breaks on multiprocessor systems though, where memory + * barriers are also required to enforce ordering across processors. + */ barrier(); } @@ -249,6 +291,7 @@ thread_preempt_enable_no_yield(void) { struct thread *thread; + /* See thread_preempt_disable() */ barrier(); thread = thread_self(); @@ -465,6 +508,8 @@ thread_runq_schedule(struct thread_runq *runq) * assume that memory may have changed in completely unexpected ways, * which is equivalent to the inline assembly expression used to * implement compiler barriers with GCC (see barrier() in macros.h). + * + * See thread_preempt_disable() for a description of compiler barriers. */ thread_switch_context(prev, next); } -- cgit v1.2.3