3. spin lock
The most common locks in Linux kernel are spin locks. A spin lock is a mutually exclusive device. It has only two values: "locked" and "Unlocked ". If the lock is available, the lock bit is set, and the code continues to enter the critical section. On the contrary, if the lock is contention by other processes, the Code enters the busy loop and checks the lock again, until the lock is available. This cycle is the "Spin" of the spin lock ". A spin lock can only be held by one executable thread at most. If an execution thread tries to obtain a contention spin lock, the thread will always be busy loop-Rotating-waiting for the lock to be available again. Note that the same lock can be used in multiple locations. Disadvantage: a competing spin lock makes the thread requesting it spin while waiting for the lock to re-available (especially wasting processor time ). Therefore, the spin lock should not be held for a long time. Of course, another method can be used to deal with lock contention: Let the request thread sleep until the lock is re-available and wake it up. However, there are two obvious context switches, and the blocked thread needs to be swapped in or out.Therefore, it is better to hold the spin lock for less time than the time required to complete two context switches.
Note:
1) if the kernel is prohibited from being preemptible, the spin lock will be removed from the kernel after compilation.
2) The spin lock implemented by the Linux kernel cannot be recursive. Be careful about self-locking.
3) debug the spin lock and add the configuration option config_debug_spinlock.
4) The spin lock can be used in the interrupt handler (semaphores cannot be used here because they will cause sleep). When using the spin lock in the interrupt handler, be sure to get the lock before, first, disable local interruption (Interrupt requests on the current processor). Otherwise, the interrupt handler interrupts the kernel code that is holding the lock, it is possible to try to use this already held spin lock.
5) Locking is not code for data.
6) The waiting for all spin locks is essentially non-disruptive.
3.1. Introduction to the spin lock API
The implementation of spin locks is closely related to the architecture and is defined in <Linux/spinlock. h>. Initialization of the spin lock during compilation:
Spinlock_t my_lock = spin_lock_unlocked;
Or at runtime:
Void spin_lock_init (spinlock_t * Lock );
Enter the critical section:
Spin_lock (& my_lock );
/* Access Data */
Spin_unlock (& my_lock );
The kernel provides an interface to prohibit interruption and request locks at the same time:
Spinlock_t my_lock = spin_lock_unlocked;
Unsigned long flags;
Spin_lock_irqsave (& my_lock, flags );
/* Access Data */
Spin_unlock_irqrestore (& my_lock, flags );
The spin_lock_irqsave function saves the current state of the interrupt and disables local interruption. Then, it obtains the specified lock. The function spin_unlock_irqrestore unlocks the specified lock and restores the interrupt to the status before the lock.
The spin lock interface provided by the kernel:
Void spin_lock_irq (spinlock_t * Lock );
Void spin_unlock_irq (spinlock_t * Lock );
Void spin_lock_bh (spinlock_t * Lock );
Void spin_unlock_bh (spinlock_t * Lock );
If you can ensure that there is no other Code to prohibit the interruption of the local processor, that is, you can ensure that the interruption should be enabled when the spin lock is released, which can use the spin_lock_irq function without the trace flag. The spin_lock_bh function prevents software interruption before obtaining the lock, but keeps the hardware interruption open.
Int spin_trylock (spinlock_t * Lock );
Int spin_trylock_bh (spinlock_t * Lock );
These two functions are non-obstructive. If the spin lock is obtained successfully, a non-zero value is returned. Otherwise, zero is returned.
3.2. spin lock and lower half
The core rules applicable to spin locks are:Any code with a spin lock must be atomic. It cannot be sleep. In fact, it cannot discard the processor for any reason, except service interruption.(This interrupted service does not access data that has obtained the spin lock). Kernel preemption is handled by the spin lock code itself. At any time, as long as the kernel code has a spin lock, preemption on the relevant processor will be disabled.
The spin_lock_bh function is used to obtain the specified lock. At the same time, it will disable the execution of all lower half. Because the lower half will seize the code in the process context, when the lower half shares data with the process context, the shared data in the process context must be protected. Therefore, the lower half must be locked and the execution of the lower half is prohibited.
Similar tasklets cannot run at the same time, so shared data of similar tasklets does not need to be protected. However, when data is shared by two different types of tasklets, You need to obtain the data before accessing the data in the lower half.A general spin lock. You do not need to disable the lower half because it will never be available on the same processor.TaskletMutual preemption.
For soft interruptions, whether or not data of the same type is shared by soft interruptions,Then it must be protected by a lock.Because even two soft interrupts of the same type can run on multiple processors of a system at the same time. However, a soft interrupt on the same processor will never seize another Soft Interrupt,Therefore, you do not need to disable the lower half..
3.3. read-write spin lock
When operations on a data structure can be clearly divided into two types: read and write, the read/write spin lock comes in handy. One or more read tasks can hold read locks concurrently. On the contrary, write locks can only be held by one write task, and concurrent read operations are not allowed. Multiple read tasks can safely obtain the same read lock. In fact, it is safe even if a thread recursively acquires the same read lock.
Initialize read/write locks:
Rwlock_t my_rwlock = rw_lock_unlocked;
On the code branch of the reading task:
Read_lock (& my_rwlock );
/* Read data */
Read_unlock (& my_rwlock );
On the code branch of the write task:
Write_lock (& my_rwlock );
/* Read data */
Write_unlock (& my_rwlock );
Note:A read lock cannot be upgraded to a write lock:
Read_lock (& my_rwlock );
Write_lock (& my_rwlock );
This will lead to deadlocks, because write locks will continue to spin, waiting for all the read locks to be released, including themselves.
The read and write locks implemented by the kernel are defined in <Linux/spinlock. h>.
When using the Linux read-write spin lock, you need to consider one thing. This mechanism takes care of reading a little more than taking care of writing. When the read lock is held, the write operation can only wait for mutex access, but the read task can continue to successfully occupy the read lock, in addition, write tasks waiting for spin cannot obtain the write lock before all read tasks release the lock.
4. semaphores
In Linux, semaphores are a sleep lock. If a task attempts to obtain an occupied semaphores, The semaphores will push them into a waiting queue and then sleep them. When the process holding the semaphore releases the semaphore, the task in the waiting queue will be awakened and the semaphore will be obtained. This provides better processor utilization than spin locks, because it takes time to wait, but semaphores have a higher overhead than spin locks.
The following are the sleep features of semaphores::
1) because the processes competing for semaphores will sleep while waiting for the lock to become available again, semaphores are suitable for Lock holding for a long time.
2) Because sleep, maintenance waiting queue, and wakeup may take longer than the full time occupied by the lock
3) because the execution thread will sleep when the lock is contention, the semaphore lock can only be obtained in the process context, and scheduling cannot be performed in the interrupt context.
4) processes holding semaphores can sleep, because other processes attempt to obtain the same semaphores lock will not be deadlocked, and processes holding semaphores will eventually continue to execute
5) processes holding semaphores cannot occupy the spin lock. Because waiting for the semaphore will sleep, while holding the spin lock is not allowed to sleep
When you need to synchronize data with the user space, your code will need to sleep. using semaphores is the only option. Unlike the spin lock, semaphores do not prohibit kernel preemption, so the code holding semaphores can be preemptible. This means that the semaphore will not have a negative impact on the scheduling wait time.
A major feature of semaphores, which can allow any number of holders at the same time, and a spin lock allows a task to hold it at most at a time. The number of holders allowed by the semaphore is specified when the semaphore is declared. This value is called the number of users. Generally, semaphores are the same as spin locks, and only one holder is allowed at a time. At this time, the count is equal to 1. Such a semaphores are called binary semaphores or mutex semaphores.
Semaphores support two atomic operations: P () and V (), named Dutch proberen (proberen) and vershogen (ADD ). Later, the system called the two operations down () and up (). The down operation is to subtract the semaphore count to request a semaphore. If the result is 0 or greater than 0, obtain the semaphore lock, and the task enters the critical section. If it is a negative number, the task will be placed in the waiting queue, and the processor will execute other tasks. The up operation is to release the semaphore and increase the Count value of the semaphore.
4.1. Create and initialize semaphores
The implementation of semaphores is related to the system (defined in <ASM/semaphore. h> ).
Static declared semaphores:
Static declare_semaphore (name, count );
Static declaration of mutex semaphores:
Static declare_mutex (name );
Dynamic initialization semaphores during runtime:
Sema_init (SEM, count );
Init_mutex (SEM );
4.2. Use semaphores
The down_interruptible () function tries to get the specified semaphore. If the acquisition fails, it will go to the sleep status of task_interruptible.That is, the task can be awakened by signals. If the process receives the signal while waiting to obtain the semaphore, the process will be awakened.. The function down _ interruptible () returns-eintr.
The function down () will make the process sleep in the task_uninterruptible state,The process no longer responds to the signal while waiting for the semaphore.
The down_trylock () function attempts to obtain the specified semaphore in blocking mode. When the semaphore is occupied, it immediately returns a non-0 value; otherwise, it returns 0.
To release the specified semaphore, you must call the up () function.
Example:
Static declare_mutex (my_sem );
If (down_interruptible (& my_sem ))
{
/* The signal is accepted, and the semaphore has not been obtained */
}
/* Access Shared data */
Up (my_sem );
4.3. read-write semaphore
The read-write semaphore is represented by the rw_semaphore structure in the kernel and is defined in <Linux/rwsem. h>.
Static declaration read-write semaphore:
Static declare_rwsem (name );
Dynamic initialization of read-write semaphores during runtime:
Init_rwsem (struct rw_semaphore * SEM );
All read-write semaphores are mutex semaphores. As long as there are no writers, the number of readers holding read locks is unlimited. Only the unique writer can obtain the write lock.All read-Neither sleep with write locks will be interrupted by signals.So he only has one version down operation.
Example:
Static declare _ rwsem (my_rwsem );
Down_read (& my_rwsem );
/* Access Shared data */
Up_read (my_sem );
Down_write (& my_rwsem );
/* Access Shared data */
Up_write (my_sem );
Like a standard semaphore, the read-write semaphore provides the down_read_trylock () and down_write_trylock () methods. The read-write semaphore has one more unique operation than the read-write spin lock: downgrade_writer (). This function can dynamically convert the obtained write lock into a read lock.
4.4. Spin locks and semaphores
Only spin locks can be used in the interrupt context, but semaphores can only be used during Task sleep. Comparison:
Requirement |
Recommended locking method |
Lock with low overhead |
Use spin lock first |
Short-term lock |
Use spin lock first |
Long-term lock |
Use semaphores first |
Locks interrupt Context |
Use spin lock |
Sleep needed to hold the lock |
Use semaphores |