Synchronization of Linux kernel learning
[Critical section and competitive conditions]
The so-called critical section is the code segment for accessing and operating shared data. Concurrent access to the same resource by multiple execution threads is usually insecure. To avoid concurrent access in the critical section, coder must ensure atomic execution of the Code.
If two execution threads may be executed simultaneously in the same critical section, this is a bug in the program. If this happens, we call it race conditions ). Synchronization is used to avoid concurrency and prevent competition conditions ).
[Cause of concurrent execution]
The reason why the user space needs to be synchronized is that the user program will be preemptible and rescheduled by the scheduling program. There are similar causes for concurrent execution in the kernel:
Interrupt: the interrupt can occur asynchronously at almost any time, that is, interrupt the code currently being executed at any time;
Soft Interrupt and tasklet: the inner nuclear energy can wake up at any time or schedule Soft Interrupt and tasklet to interrupt the code currently being executed;
Kernel preemption: Because the kernel is preemptible, tasks in the kernel may be preemptible by another task;
Sleep and synchronization with user space: Processes executed in the kernel may sleep, which will wake up the scheduling program and lead to scheduling a new user process;
Symmetric processor: Two or more processors can execute code simultaneously.
[Code to be synchronized]
When writing the kernel code, you need to ask yourself the following questions:
Is this data global? Can other threads access the current thread?
Will this data be shared in process context and interrupt context? Does it need to be shared between two different interrupt handlers?
Can a process be preemptible when accessing data? Will the scheduled new program access the same data?
Does the current process sleep (congested) on some resources? If yes, what status does it make the shared data in?
How can we prevent data from getting out of control?
What will happen if this function is scheduled for another processing?
How can we ensure that the Code is away from the concurrency threat?
In short, almost all kernel global variables and shared data require some form of synchronization.
[Deadlock]
A deadlock requires one or more execution threads and one or more resources. Each thread is waiting for one of the resources, however, all resources are occupied. All threads are waiting for each other, but they will never release the occupied resources, so no resources can continue, which means a deadlock occurs.
Example: There are two threads and two locks.
Thread 1 thread 2
Get lock a get lock B
Trying to get lock B trying to get lock
Wait for lock B wait for lock
[Atomic operation]
Atomic operations can ensure that commands are executed in an atomic manner-the execution process is not interrupted. The kernel provides two sets of atomic operation interfaces: one for integer operations, and the other for separate bit operations.
Atomic Integer type
typedef struct { intcounter;} atomic_t;
[Spin lock]
Spin lock can be held by a maximum of one executable thread. If an execution thread tries to obtain a spin lock that has been held (that is, the so-called contention), the thread will continue to perform a busy loop-rotation-waiting for the lock to be available again.
Spinlock struct:
typedef struct spinlock {union {struct raw_spinlock rlock;#ifdef CONFIG_DEBUG_LOCK_ALLOC# define LOCK_PADSIZE (offsetof(struct raw_spinlock, dep_map))struct {u8 __padding[LOCK_PADSIZE];struct lockdep_map dep_map;};#endif};} spinlock_t;
A competing spin lock is a process that requests its threads to spin while waiting for the lock to be re-available, especially a waste of processing time. This behavior is the key point of the spin lock. So the spin lock should not be held for a long time. The time needed to hold the spin lock is preferably less than the time required to complete two context switches.
Spin locks can be used to interrupt the processing program, but semaphores are not allowed. semaphores may cause sleep.
When using the lock, you must take the right medicine and be targeted. You need to know that you need to protect data rather than code.
[Semaphores]
Semaphores in Linux are sleep locks. If a task tries to obtain an unavailable (occupied) semaphore, the semaphore will push it into a waiting queue and sleep it. In this case, the processor can regain freedom to execute other code. When the hold semaphores are available (released), the task in the waiting queue will be awakened and the semaphores will be obtained.
Semaphore struct:
struct semaphore {spinlock_tlock;unsigned intcount;struct list_headwait_list;};
Notes for using semaphores:
Because the processes that compete for semaphores will sleep while waiting for the lock to become available again, semaphores are suitable for cases where the lock will be held for a long time;
On the contrary, when a lock is held for a short time, it cannot be too suitable to use semaphores. Because the overhead of sleep, maintenance waiting queue, and wake-up may be longer than the full time occupied by the lock;
Because the execution thread will sleep when the lock is contention, the semaphore lock can only be obtained in the process context, because scheduling cannot be performed in the interrupt context;
You can sleep when holding the semaphore, because other processes attempt to obtain the same semaphore, it will not cause a deadlock.
You cannot use the spin lock while occupying the semaphore. Because you may sleep while waiting for the semaphore, and you are not allowed to sleep when holding the spin lock.
[Mutex]
In the latest Linux kernel, mutex is a specific sleep lock that implements mutex. Mutex corresponds to the data structure mutex in the kernel. Its behavior is similar to the use of a semaphore with a count of 1, but the operation interface is simpler, the implementation is more efficient, and the use of restrictions is stronger.
Mutex struct:
struct mutex {/* 1: unlocked, 0: locked, negative: locked, possible waiters */atomic_tcount;spinlock_twait_lock;struct list_headwait_list;#if defined(CONFIG_DEBUG_MUTEXES) || defined(CONFIG_SMP)struct task_struct*owner;#endif#ifdef CONFIG_DEBUG_MUTEXESconst char *name;void*magic;#endif#ifdef CONFIG_DEBUG_LOCK_ALLOCstruct lockdep_mapdep_map;#endif};
Mutex restrictions:
Only one task can hold mutex at any time, that is, the Count of mutex usage is always 1;
The mutex locker must be responsible for unlocking it. You cannot lock a mutex in one context, but unlock it in another context. This restriction makes mutex unsuitable for complicated synchronization scenarios with the kernel and user space. The most common method is to lock and unlock in the same context.
Recursive above-ground locks and unlocking are not allowed. That is to say, you cannot recursively hold the same lock, and you cannot unlock another unlocked mutex;
Mutex cannot be used in the interrupted or lower half;
Mutex can only be managed through official APIs
[Semaphores and mutex]
Mutex locks are similar to semaphores, and coexistence in the kernel is confusing. Fortunately, their standard usage has a very simple specification: unless a mutex constraint hinders your use, mutex is preferred for semaphores. When you write new code, you only need to use semaphores in special cases. Therefore, mutex is recommended.
[Spin lock and mutex]
Knowing when to use spin locks and when to use mutex or semaphores is very important for writing good code. However, in most cases, you do not need to think too much, because you can only use spin locks in the interrupt context, you can only use mutex During task sleep.
Requirement |
Recommended locking method |
Lock with low overhead |
Use spin lock first |
Short-term lock |
Use spin lock first |
Long-term lock |
Use mutex first |
Locks interrupt Context |
Use spin lock |
Lock lower half |
Use spin lock |
Sleep needed to hold the lock |
Use mutex |