/*
* Copyright (c) 2013-2014 Richard Braun.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*
*
* Lockless synchronization.
*
* The llsync module provides services similar to RCU (Read-Copy Update).
* As such, it can be thought of as an efficient reader-writer lock
* replacement. It is efficient because read-side critical sections
* don't use expensive synchronization mechanisms such as locks or atomic
* instructions. Lockless synchronization is therefore best used for
* read-mostly objects. Updating still requires conventional lock-based
* synchronization.
*
* The basic idea is that read-side critical sections are assumed to hold
* read-side references, and objects for which there may be read-side
* references must exist as long as such references may be held. The llsync
* module tracks special system events to determine when read-side references
* can no longer exist.
*
* Since read-side critical sections can run concurrently with updates,
* it is important to make sure that objects are consistent when being
* accessed. This is achieved with a publish/subscribe mechanism that relies
* on the natural atomicity of machine word updates in memory, i.e. all
* supported architectures must guarantee that, when updating a word, and
* in turn a pointer, other processors reading that word obtain a valid
* value, that is either the previous or the next value of the word, but not
* a mixed-up value. The llsync module provides the llsync_store_ptr() and
* llsync_load_ptr() wrappers that take care of low level details such as
* compiler and memory barriers, so that objects are completely built and
* consistent when published and accessed.
*
* As objects are published through pointers, multiple versions can exist at
* the same time. Previous versions cannot be deleted as long as read-side
* references may exist. Operations that must wait for all read-side references
* to be dropped can be either synchronous, i.e. block until it is safe to
* proceed, or be deferred, in which case they are queued and later handed to
* the work module. As a result, special care must be taken if using lockless
* synchronization in the work module itself.
*
* The two system events tracked by the llsync module are context switches
* and a periodic event, normally the periodic timer interrupt that drives
* the scheduler. Context switches are used as checkpoint triggers. A
* checkpoint is a point in execution at which no read-side reference can
* exist, i.e. the processor isn't running any read-side critical section.
* Since context switches can be very frequent, a checkpoint is local to
* the processor and lightweight. The periodic event is used to commit
* checkpoints globally so that other processors are aware of the progress
* of one another. As the system allows situations in which two periodic
* events can occur without a single context switch, the periodic event is
* also used as a checkpoint trigger. When all checkpoints have been
* committed, a global checkpoint occurs. The occurrence of global checkpoints
* allows the llsync module to determine when it is safe to process deferred
* work or unblock update sides.
*/
#ifndef _KERN_LLSYNC_H
#define _KERN_LLSYNC_H
#include
#include
#include
#include
#include
#include
/*
* Safely store a pointer.
*/
#define llsync_store_ptr(ptr, value) atomic_store(&(ptr), value, ATOMIC_RELEASE)
/*
* Safely load a pointer.
*/
#define llsync_load_ptr(ptr) atomic_load(&(ptr), ATOMIC_CONSUME)
/*
* Read-side critical section enter/exit functions.
*
* It is not allowed to block inside a read-side critical section.
*/
static inline void
llsync_read_enter(void)
{
int in_read_cs;
in_read_cs = thread_llsync_in_read_cs();
thread_llsync_read_inc();
if (!in_read_cs) {
thread_preempt_disable();
}
}
static inline void
llsync_read_exit(void)
{
thread_llsync_read_dec();
if (!thread_llsync_in_read_cs()) {
thread_preempt_enable();
}
}
/*
* Return true if the llsync module is initialized, false otherwise.
*/
bool llsync_ready(void);
/*
* Manage registration of the current processor.
*
* The caller must not be allowed to migrate when calling these functions.
*
* Registering tells the llsync module that the current processor reports
* context switches and periodic events.
*
* When a processor enters a state in which checking in becomes irrelevant,
* it unregisters itself so that the other registered processors don't need
* to wait for it to make progress. For example, this is done inside the
* idle loop since it is obviously impossible to enter a read-side critical
* section while idling.
*/
void llsync_register(void);
void llsync_unregister(void);
/*
* Report a context switch on the current processor.
*
* Interrupts and preemption must be disabled when calling this function.
*/
static inline void
llsync_report_context_switch(void)
{
llsync_checkin();
}
/*
* Report a periodic event on the current processor.
*
* Interrupts and preemption must be disabled when calling this function.
*/
void llsync_report_periodic_event(void);
/*
* Defer an operation until all existing read-side references are dropped,
* without blocking.
*/
void llsync_defer(struct work *work);
/*
* Wait for all existing read-side references to be dropped.
*
* This function sleeps, and may do so for a moderately long duration (a few
* system timer ticks).
*/
void llsync_wait(void);
#endif /* _KERN_LLSYNC_H */