Skip to main content

  | मौसम…

411 समाचार
Public archive edition · Attributed source reporting with original dates · AI drafts are excluded

411 REFERENCE · Wikibooks

The Linux Kernel/Multitasking/Synchronization

Reference material by Wikibooks contributors. This retains its source identity and is not 411 news reporting.

Synchronization Thread Synchronization (computer science) synchronization is defined as a mechanism which ensures that two or more concurrent processes or threads do not simultaneously execute some particular program segment known as mutual exclusion (mutex). When one thread starts executing the critical section (serialized segment of the program) the other thread should wait until the first thread finishes. If proper synchronization techniques are not applied, it may cause a race condition where, the values of variables may be unpredictable and vary depending on the timings of context switches of the processes or threads. User space synchronization POSIX Timers ⚲ APIs 2 timer_create – creates a POSIX timer 2 timer_settime – starts or modifies a timer 2 timer_gettime – retrieves the remaining time of a timer 2 timer_delete – deletes a POSIX timer 2 clock_nanosleep – suspends execution for a specified time ⚙️ Internals linux/posix-timers.h kernel/time/itimer.c – interval timer (setitimer/getitimer) kernel/time/posix-timers.c – POSIX clock and timer operations kernel/time/posix-cpu-timers.c – per-process/thread CPU timers posix_cpu_timer_set – function setting up CPU timers Futex A 2 futex ↪ do_futex (short for "Fast User space muTex") is a kernel system call that programmers can use to implement basic locking, or as a building block for higher-level locking abstractions such as semaphores and POSIX mutexes or condition variables. A futex consists of a kernel space wait queue that is attached to an aligned integer in user space. Multiple processes or threads operate on the integer entirely in user space (using atomic operations to avoid interfering with one another), and only resort to relatively expensive system calls to request operations on the wait queue (for example to wake up waiting processes, or to put the current process on the wait queue). A properly programmed futex-based lock will not use system calls except when the lock is contended; since most operations do not require arbitration between processes, this will not happen in most cases. The basic operations of futexes are based on only two central operations futex_wait and futex_wake though implementation has more operations for more specialized cases. WAIT (addr, val) checks if the value stored at the address addr is val, and if it is puts the current thread to sleep. WAKE (addr, val) wakes up val number of threads waiting on the address addr. ⚲ API uapi/linux/futex.h linux/futex.h ⚙️ Internals: kernel/futex 🔨 stress-ng --futex 📖 References Futex 7 futex Futex API reference kernel-hacking/locking.html#futex-api-reference kernel/syscalls futex File locking ⚲ API: 2 flock Semaphore 💾 History: Semaphore is part of System V IPC 7 sysvipc ⚲ API 2 semget 2 semctl 2 semop ⚙️ Internals: ipc/sem.c 🔨 stress-ng --sem Kernel space synchronization For kernel mode synchronization Linux provides three categories of locking primitives: sleeping, per CPU local locks and spinning locks. rust/kernel/sync.rs – Rust bindings for locking primitives Read-Copy-Update Common mechanism to solve the readers–writers problem is the read-copy-update (RCU) algorithm. Read-copy-update implements a kind of mutual exclusion that is wait-free (non-blocking) for readers, allowing extremely low overhead. However, RCU updates can be expensive, as they must leave the old versions of the data structure in place to accommodate pre-existing readers. ⚲ The core API in linux/rcupdate.h is quite small: rcu_read_lock marks an RCU-protected data structure so that it won't be reclaimed for the full duration of that critical section. rcu_read_unlock is used by a reader to inform the reclaimer that the reader is exiting an RCU read-side critical section. Note that RCU read-side critical sections may be nested and/or overlapping. synchronize_rcu blocks until all pre-existing RCU read-side critical sections on all CPUs have completed. Note that synchronize_rcu will not necessarily wait for any subsequent RCU read-side critical sections to complete. For example, consider the following sequence of events: CPU 0CPU 1CPU 21.rcu_read_lock()2.enters synchronize_rcu()3. rcu_read_lock()4.rcu_read_unlock()5.exits synchronize_rcu()6.rcu_read_unlock() thumb|upright=2|RCU API communications between the reader, updater, and reclaimer Since synchronize_rcu is the API that must figure out when readers are done, its implementation is key to RCU. For RCU to be useful in all but the most read-intensive situations, synchronize_rcu's overhead must also be quite small. Alternatively, instead of blocking, synchronize_rcu may register a callback to be invoked after all ongoing RCU read-side critical sections have completed. This callback variant is called call_rcu in the Linux kernel. rcu_assign_pointer - The updater uses this function to assign a new value to an RCU-protected pointer, in order to safely communicate the change in value from the updater to the reader. This function returns the new value, and also executes any Memory barrier instructions required for a given CPU architecture. Perhaps more importantly, it serves to document which pointers are protected by RCU. rcu_dereference - The reader uses this function to fetch an RCU-protected pointer, which returns a value that may then be safely dereferenced. It also executes any directives required by the compiler or the CPU, for example, a volatile cast for gcc, a memory_order_consume load for C/C++11 or the memory-barrier instruction required by the old DEC Alpha CPU. The value returned by rcu_dereference is valid only within the enclosing RCU read-side critical section. As with rcu_assign_pointer, an important function of rcu_dereference is to document which pointers are protected by RCU. The RCU infrastructure observes the time sequence of rcu_read_lock, rcu_read_unlock, synchronize_rcu, and call_rcu invocations in order to determine when (1) synchronize_rcu invocations may return to their callers and (2) call_rcu callbacks may be invoked. Efficient implementations of the RCU infrastructure make heavy use of batching in order to amortize their overhead over many uses of the corresponding APIs. ⚲ API rcu_nocbs – no-callback CPUs linux/rcupdate.h linux/rculist.h – RCU-protected linked list operations ⚙️ Internals kernel/rcu – read-copy-update implementation 📖 References Avoiding Locks: Read Copy Update kernel-hacking/locking.html?#avoiding-locks-read-copy-update RCU concepts RCU RCU initialization 📚 Further reading Reduce synchronize_rcu() latency Sleeping locks Mutexes ⚲ API linux/mutex.h linux/completion.h mutex has owner and usage constraints, easier to debug than semaphore rt_mutex blocking mutual exclusion locks with priority inheritance (PI) support ww_mutex Wound/Wait mutexes: blocking mutual exclusion locks with deadlock avoidance rw_semaphore readers–writer semaphores percpu_rw_semaphore completion - use completion for synchronization task with ISR and task or two tasks. wait_for_completion complete 🔨 stress-ng --mutex 💾 Historical semaphore - use mutex instead semaphore if possible linux/semaphore.h linux/rwsem.h 📖 References Completions - “wait for completion” barrier APIs scheduler/completion.html Mutex API reference kernel-hacking/locking.html#mutex-api-reference LWN: completion events per CPU local lock On normal preemptible kernel local_lock calls preempt_disable. On RT preemptible kernel local_lock calls migrate_disable and spin_lock. Any changes applied to spinlock_t also apply to local_lock. ⚲ API linux/local_lock.h local_lock, preempt_disable local_lock_irqsave, local_irq_save etc 📖 References local_lock locking/locktypes.html#local-lock PREEMPT_RT caveats: spinlock_t, rwlock_t, migrate_disable and local_lock locking/locktypes.html#spinlock-t-and-rwlock-t Proper locking under a preemptive kernel locking/preempt-locking.html Local locks in the kernel Spinning locks Spinlocks a spinlock is a lock which causes a thread trying to acquire it to simply wait in a loop ("spin") while repeatedly checking if the lock is available. Since the thread remains active but is not performing a useful task, the use of such a lock is a kind of busy waiting. Once acquired, spinlocks will usually be held until they are explicitly released, although in some implementations they may be automatically released if the thread being waited on (that which holds the lock) blocks, or "goes to sleep". Spinlocks are commonly used inside kernels because they are efficient if threads are likely to be blocked for only short periods. However, spinlocks become wasteful if held for longer durations, as they may prevent other threads from running and require rescheduling. 👁 For example kobj_kset_join uses spinlock to protect assess to the linked list. Enabling and disabling of kernel preemption replaced spinlocks on uniprocessor systems (disabled CONFIG_SMP). Most spinning locks becoming sleeping locks in the CONFIG_PREEMPT_RT kernels. ⚲ API linux/spinlock.h spinlock_t raw_spinlock_t linux/bit_spinlock.h bit_spin_lock 📖 References Introduction to spinlocks Queued spinlocks Seqlocks A seqlock (short for "sequential lock") is a special locking mechanism used in Linux for supporting fast writes of shared variables between two parallel operating system routines. It is a special solution to the readers–writers problem when the number of writers is small. It is a reader-writer consistent mechanism which avoids the problem of writer starvation. A seqlock_t consists of storage for saving a sequence counter seqcount_t/seqcount_spinlock_t in addition to a lock. The lock is to support synchronization between two writers and the counter is for indicating consistency in readers. In addition to updating the shared data, the writer increments the sequence counter, both after acquiring the lock and before releasing the lock. Readers read the sequence counter before and after reading the shared data. If the sequence counter is odd on either occasion, a writer had taken the lock while the data was being read and it may have changed. If the sequence counters are different, a writer has changed the data while it was being read. In either case readers simply retry (using a loop) until they read the same even sequence counter before and after. ⚲ API seqlock_t DEFINE_SEQLOCK, seqlock_init, read_seqlock_excl, write_seqlock seqcount_t seqcount_init, read_seqcount_begin, read_seqcount_retry, write_seqcount_begin, write_seqcount_end linux/seqlock.h 👁 Example: mount_lock, defined in fs/namespace.c 📖 References Sequence counters and sequential locks locking/seqlock.html SeqLock Spinning or sleeping locks {| class="wikitable" ! !! normal !! on preempt RT |- | spinlock_t, || raw_spinlock_t || rt_mutex_base, rt_spin_lock, sleeping |- | rwlock_t || spinning || sleeping |- | local_lock || preempt_disable|| migrate_disable, rt_spin_lock, sleeping |} Low level The compiler might optimize away or reorder writes to variables leading to unexpected behavior when variables are accessed concurrently by multiple threads. ⚲ API asm-generic/rwonce.h – prevent the compiler from merging or refetching reads or writes. linux/compiler.h barrier – prevents the compiler from reordering instructions around the barrier asm-generic/barrier.h – generic barrier definitions arch/x86/include/asm/barrier.h – force strict CPU ordering mb – ensures that all memory operations before the barrier are completed before any memory operations after the barrier are started ⚙️ Internals Atomics driver-api/basics.html#atomics linux/atomic.h – atomic operations wrapper asm-generic/atomic.h linux/atomic/atomic-instrumented.h atomic_dec_and_test ... 📚 Further reading Volatile_(computer_programming)#In_C_and_C++ volatile – prevents the compiler from optimizations Memory barrier – enforces an ordering constraint on memory operations 🔨 stress-ng --lockbus ... ⚙️ Locking internals linux/lockdep.h – runtime locking correctness validator linux/debug_locks.h lib/locking-selftest.c – locking correctness self-tests kernel/locking – locking primitives mutex_waiter, timer_list, wait_queue_head_t kernel/locking/locktorture.c – module-based torture test facility for locking 📖 Locking references locking locking Lock types and their rules locking/locktypes.html 😴 sleeping locks locking/locktypes.html#sleeping-locks mutex, rt_mutex, semaphore, rw_semaphore, ww_mutex, percpu_rw_semaphore on preempt RT: local_lock, spinlock_t, rwlock_t 😵‍💫 spinning locks locking/locktypes.html#spinning-locks: raw_spinlock_t, bit spinlocks on non preempt RT: spinlock_t, rwlock_t Unreliable Guide To Locking kernel-hacking/locking.html LKMPG: Synchronization Synchronization primitives