Identifying what data specifically needs protection is vital. Because any data that can be accessed concurrently almost assuredly needs protection, it is often easier to identify what data does not need protection and work from there. Obviously, any data that is local to one particular thread of execution does not need protection, because only that thread can access the data. For example, local automatic variables (and dynamically allocated data structures whose address is stored only on the stack) do not need any sort of locking because they exist solely on the stack of the executing thread. Likewise, data that is accessed by only a specific task does not require locking (because a process can execute on only one processor at a time).
What does need locking? Most global kernel data structures do. A good rule of thumb is that if another thread of execution can access the data, the data needs some sort of locking; if anyone else can see it, lock it. Remember to lock data, not code.
CONFIG Options: SMP Versus UP
Because the Linux kernel is configurable at compile time, it makes sense that you can tailor the kernel specifically for a given machine. Most important, the CONFIG_SMP configure option controls whether the kernel supports SMP. Many locking issues disappear on uniprocessor machines; consequently, when CONFIG_SMP is unset, unnecessary code is not compiled into the kernel image. For example, such configuration enables uniprocessor machines to forego the overhead of spin locks. The same trick applies to CONFIG_PREEMPT (the configure option enabling kernel preemption). This was an excellent design decision—
the kernel maintains one clean source base, and the various locking mechanisms are used as needed. Different combinations of CONFIG_SMP and CONFIG_PREEMPT on different archi-tectures compile in varying lock support.
In your code, provide appropriate protection for the most pessimistic case, SMP with kernel preemption, and all scenarios will be covered.
3 You will also see that, barring a few exceptions, being SMP-safe implies being preempt-safe.
ptg Deadlocks 169
Whenever you write kernel code, you should ask yourself these questions:
n Is the data global? Can a thread of execution other than the current one access it?
n Is the data shared between process context and interrupt context? Is it shared between two different interrupt handlers?
n If a process is preempted while accessing this data, can the newly scheduled process access the same data?
n Can the current process sleep (block) on anything? If it does, in what state does that leave any shared data?
n What prevents the data from being freed out from under me?
n What happens if this function is called again on another processor?
n Given the proceeding points, how am I going to ensure that my code is safe from concurrency?
In short, nearly all global and shared data in the kernel requires some form of the synchronization methods, discussed in the next chapter.
Deadlocks
A deadlock is a condition involving one or more threads of execution and one or more resources, such that each thread waits for one of the resources, but all the resources are already held.The threads all wait for each other, but they never make any progress toward releasing the resources that they already hold.Therefore, none of the threads can con-tinue, which results in a deadlock.
A good analogy is a four-way traffic stop. If each car at the stop decides to wait for the other cars before going, no car will ever proceed, and we have a traffic deadlock.
The simplest example of a deadlock is the self-deadlock:4 If a thread of execution attempts to acquire a lock it already holds, it has to wait for the lock to be released. But it will never release the lock, because it is busy waiting for the lock, and the result is deadlock:
acquire lock acquire lock, again
wait for lock to become available ...
4 Some kernels prevent this type of deadlock by providing recursive locks. These are locks that a single thread of execution may acquire multiple times. Linux, thankfully, does not provide recursive locks. This is widely considered a good thing. Although recursive locks might alleviate the self-deadlock problem, they very readily lead to sloppy locking semantics.
ptg Similarly, consider n threads and n locks. If each thread holds a lock that the other
thread wants, all threads block while waiting for their respective locks to become avail-able.The most common example is with two threads and two locks, which is often called the deadly embrace or the ABBA deadlock:
Each thread is waiting for the other, and neither thread will ever release its original lock; therefore, neither lock will become available.
Prevention of deadlock scenarios is important.Although it is difficult to prove that code is free of deadlocks, you can write deadlock-free code.A few simple rules go a long way:
n Implement lock ordering. Nested locks must always be obtained in the same order.
This prevents the deadly embrace deadlock. Document the lock ordering so others will follow it.
n Prevent starvation. Ask yourself, does this code always finish? If foo does not occur, will bar wait forever?
n Do not double acquire the same lock.
n Design for simplicity. Complexity in your locking scheme invites deadlocks.
The first point is most important and worth stressing. If two or more locks are acquired at the same time, they must always be acquired in the same order. Let’s assume you have the cat, dog, and fox locks that protect data structures of the same name. Now assume you have a function that needs to work on all three of these data structures simul-taneously—perhaps to copy data between them.Whatever the case, the data structures require locking to ensure safe access. If one function acquires the locks in the order cat, dog, and then fox, then every other function must obtain these locks (or a subset of them) in this same order. For example, it is a potential deadlock (and hence a bug) to first obtain the fox lock and then obtain the dog lock because the dog lock must always be acquired prior to the fox lock. Here is an example in which this would cause a deadlock:
Thread 1 Thread 2
acquire lock A acquire lock B
try to acquire lock B try to acquire lock A
wait for lock B wait for lock A
Thread 1 Thread 2
acquire lock cat acquire lock fox
acquire lock dog try to acquire lock dog
try to acquire lock fox wait for lock dog
wait for lock fox —
ptg