==== //depot/user/attilio/attilio_smpng/locking.9#3 - /home/wkoszek/p4/locking.9/locking.9 ==== @@ -1,41 +1,43 @@ -The FreeBSD syncronization primitives set is based on the idea to supply a +//depot/user/attilio/attilio_smpng/locking.9#3 - edit change 119624 (text+ko) +The FreeBSD synchronization primitives set is based on the idea to supply a rather huge number of different primitives in a way that the better one can be used for every particular, appropriate situation. -To an high level point of view you can consider three kinds of syncronization +To an high level point of view you can consider three kinds of synchronization primitives in the FreeBSD kernel: - atomic operations / memory barriers - locks - scheduling barriers Below there are descriptions for the 4 families. -For every lock, you should really check the linked manpage (where possible) -for more detailed explanatories. +For every lock, you should really check the linked manual page (where possible) +for more detailed explanations. + * Atomic operations / memory barriers Atomic operations are implemented through a set of functions performing simple -aritmetics on memory operands in an atomic way respect external events +arithmetics on memory operands in an atomic way respect external events (interrupts, preemption, etc). Atomic operations can guarantee atomicity just on small data types (in the magnitude order of the 'long' architecture C data type), so should be rarely used in end-level code directly, if not only for very simple operations (like -flag setting in a bitmap, for example). Infact, it is rather simple and common +flag setting in a bitmap, for example). In fact, it is rather simple and common to write down a wrong semantic just based on atomic operations (usually referred as lock-less). -FreeBSD kernel offers a way to perform atomic operations in conjuction with a +FreeBSD kernel offers a way to perform atomic operations in conjunction with a memory barrier. The memory barriers will guarantee that an atomic operation will happen following some specified ordering respect other memory accesses. For example, if we need that an atomic operation happens just after all other pending writes (in terms of instructions reordering buffers activities) are -completed, we need to explicitly use a memory barrier in conjuction to this +completed, we need to explicitly use a memory barrier in conjunction to this atomic operation. So it is simple to understand why memory barriers play a -key role for higher-level locks building (just as refcounts, mutexes, etc.). +key role for higher-level locks building (just as reference counts, mutexes, etc.). XXX: adding discussion on aligned memory atomic operations & volatile For a detailed explanatory on atomic operations, please refer to atomic(9). It is far, however, noting that atomic operations (and memory barriers too) should, ideally, only be used for building front-ending locks (as mutexes). -* Refcounts +* Reference counters (refcounts). Refcounts are interfaces for handling reference counters. They are implemented through atomic operations and are intended to be used just for cases where the reference counter is the only one thing to be @@ -43,12 +45,12 @@ the refcount interface for structures where a mutex is alredy used is often wrong since we should probabilly close the reference counter in some alredy protected paths. -A manpage discussing refcount doesn't exist currently, just check +A manual page discussing refcount doesn't exist currently, just check sys/refcount.h for an overview of the existing API. * Locks FreeBSD kernel has an huge class of locks. -Every lock is defined by some peculiar properties, but probabilly the most +Every lock is defined by some peculiar properties, but probably the most important is the event linked to contesting holders (or, in other terms, the behaviour of threads unable to acquire the lock). FreeBSD locking scheme presents 3 different behaviours for contenders: @@ -81,12 +83,12 @@ Sleep locks let waiters to be descheduled and fall asleep until the lock holder doesn't drop it and wakes up one or more waiters. Since sleep locks are intended to protect large paths of code and to - cater asyncronous events, they don't do any form of priority + cater asynchronous events, they don't do any form of priority propagation. They must be implemented through the sleepqueue(9) interface. The order used to acquire locks is very important, not only for the -possibility to deadlock due at lock order reversals, but even beacause lock +possibility to deadlock due at lock order reversals, but even because lock acquisition should follow specific rules linked to locks natures. If you give a look at the table above, the practical rule is that if a thread holds a lock of level n (where the level is the number listed close to the @@ -128,14 +130,14 @@ - sched_bind - sched_pin Generally, these should be used only in particular context and even if they can -often replace locks, they should be avoided beacause don't let diagnose +often replace locks, they should be avoided because don't let diagnose simply eventual problems with locking debugging tools (as WITNESS). - Critical sections FreeBSD kernel has been made preemptive basically in order to deal - with interrupt threads. Infact, in order to avoid a too high + with interrupt threads. In fact, in order to avoid a too high interrupt latency, time-sharing priority threads can be preempted by - interrpt threads (that, in this way, don't need to wait to be + interrupt threads (that, in this way, don't need to wait to be scheduled as the normal path previews). Preemption, however, introduces new racing points that need to be handled as well. @@ -174,11 +176,11 @@ APPENDIX B: Safe allocation for structure members One possible sleeping point, in every FreeBSD kernel, is malloc() when called with M_WAITOK flag high. -In this case, infact, the calling thread will sleep if the malloc'ing +In this case, in fact, the calling thread will sleep if the malloc'ing request cannot be immediately satisfied. From a locking point of view, this means that malloc(..., M_WAITOK) cannot be called with any lock held. This can often be a big problem, in particular in members allocation of -data structures. Let's take an example. Immagine you have the following code: +data structures. Let's take an example. Imagine you have the following code: struct foo { struct mtx foo_mtx; @@ -210,7 +212,7 @@ Now you need to do a function foo_buffer_alloc() which needs to check for the foo_flags, see that the state is not in FOO_BUFFERED state and than allocate the buffer. Please also note, that a memory allocation failure is -not tollerated in this function and that foo_flags is protected by foo_mtx. +not tolerated in this function and that foo_flags is protected by foo_mtx. Since you need to first check for foo_flags and than to possibly allocate with M_WAITOK, it means that you will have a lock mismatch (possible sleeping while holding a blocking lock) so you need to handle that. @@ -238,9 +240,9 @@ What happens here is that just before to allocate the new object the blocking lock is dropped in order to be reacquired once the allocation is -succedeed. But at this point, the thread could have lost the race, that +succeeded. But at this point, the thread could have lost the race, that the first mtx_lock() let it win instead, against another thread, so we need -to recheck the state of protected members and if another thread alredy +to recheck the state of protected members and if another thread already successfully allocated the buffer (or, in other words, won the race) just free allocated memory and go on.