Concurrency and competition occur in two types of systems:
- multiple CPUs with symmetric multiprocessor (SMP)
- A single CPU system that the kernel can preempt
The area of code that accesses a shared resource is called the critical section (critical Sections), and the critical section needs to be protected with some kind of mutex mechanism. in a driver, when multiple threads access the same resource at the same time (critical sections) (the global variable in the driver is a typical shared resource), it may throw " race ", so we have to have concurrency control over shared resources. The method of solving concurrency control in Linux kernel interrupts shielding, atomic operation, spin lock and Semaphore . (The main way behind)
Interrupt masking:
How to use
Local_irq_disable () //Shield Interrupt ... critical section//Critical area ... local_irq_enable () //on interrupt
Local_irq_disable/enable can only prohibit/enable the interrupt within the CPU, can not solve the SMP multi-CPU-induced race, it is not recommended to use, it is suitable for spin lock joint use.
Atomic operation:
atomic operations are a series of operations that cannot be interrupted. The Linux kernel provides a series of functions to implement atomic operations in the kernel, which are divided into 2 classes, which are atomic for bitwise and integer variables .
The steps to implement an integral atomic operation are as follows:
1. Define an atomic variable and set the value of the variable
void Atomic_set (atomic_t *v, int i); Set the atomic variable value to iatomic_t v = atomic_init (0); Defines the atomic variable V, initialized to 0
2. Get the value of an atomic variable
Atomic_read (atomic_t *v);
3. Atomic variable plus and minus operation
void Atomic_add (int i,atomic_t *v);//atomic variable plus ivoid atomic_sub (int i, atomic_t *v);//atomic variable minus I
4. Atomic variable self-increment/auto-decrement
void Atomic_inc (atomic_t *v);//self-increment 1void atomic_dec (atomic_t *v);//Auto minus 1
5. Operate and test: performs an auto-increment on an atomic variable, self-decrement (no addition) tests whether it is 0, or returns False if 0 returns true.
int atomic_inc_and_test (atomic_t *v); int atomic_dec_and_test (atomic_t *v); int atomic_sub_and_test (int i, atomic_t *v);
6. Operation and return
int Atomic_add_return (int i, atomic_t *v), int atomic_sub_return (int i, atomic_t *v), int atomic_inc_return (atomic_t * v); int Atomic_dec_return (atomic_t * v);
Implement bit atomic operations as follows:
Set the bit void Set_bit (nr, void *addr); Set the addr address of the NR bit, will write 1//clear bit void Clear_bit (nr, void *addr); Clears the addr address of the NR bit, will write 0//change bit void Change_bit (nr, void *addr); Take the counter//test bit test_bit (nr, void *addr) to the Nr position of the addr address; Returns the NR bit of the addr address//test and operation: equivalent to execute test_bit (nr, void *addr) and then execute Xxx_bit (nr, void *addr) int test_and_set_bit (nr, void *addr); int Test_and_clear_bit (nr, void *addr), int test_and_change_bit (nr, void *addr)
Here is an example of an atomic variable using an instance so that the device can only be opened by a single process:
static atomic_t xxx_available = Atomic_init (1); Define atomic variable static int xxx_open (struct inode *inode, struct file *filp) { ... if (!atomic_dec_and_test (&xxx_available)) { atomic_inc (&xxx_availble); Return-ebusy; Already open } ... return 0; Success} static int xxx_release (struct inode *inode, struct file *filp) { atomic_inc (&xxx_available); Release device return 0;}
I would like to focus on :
Spin lock VS Signal Volume
in the strict sense, the signal volume and the spin lock belong to different levels of mutual exclusion, the former implementation relies on the latter, in the multi- CPU need to spin lock to mutually exclusive. semaphores are process-level, used for mutual exclusion of resources between multiple processes, although also in the kernel, but the kernel execution path is the process's identity, representing the process to compete for resources. If the competition fails, it switches to the next process, and the current process goes to sleep, so the semaphore is a good choice when the process takes up a long resource time.
when the critical access time to be protected is short, it is very convenient to use a spin lock because it saves time for context switching. However, the CPU does not get the spin lock, theCPU will spin in place until the other execution units are unlocked, so the lock must not stay in the critical section for too long.
Operation Steps of Spin Lock:
1. Define the spin lock spinlock_t lock;2. Initialize the spin lock spin_lock_init (lock); This is a macro that is used to dynamically initialize the spin lock lock;3. Get spin lock Spin_lock (lock); If the lock can be acquired immediately, it can return immediately, otherwise, he will spin there until the spin lock is released by the holding person. Spin_trylock (lock); be able to obtain, then return True, otherwise return false, actually is not in situ circles just. 4. Release the spin lock Spin_unlock (lock);
The kernel preemption will be banned during spin lock hold. The spin lock ensures that the critical section is not disturbed by other CPUs and the preemption process within the CPU, but the code path to the lock can also be affected by interrupts and the bottom half (BH) when the critical section is being executed. To prevent this effect, a spin lock derivation is required:
SPIN_LOCK_IRQ () = Spin_lock () + local_irq_disable () spin_unlock_irq () = Spin_unlock () + local_irq_enable () spin_lock_ Irqsave () = Spin_lock () + Local_irq_save () spin_unlock_irqrestore () = Spin_unlock () + Local_irq_restore () spin_lock_bh () = Spin_lock () + local_bh_disable () spin_unlock_bh () = Spin_unlock () + local_bh_enable ()
Note: spin lock is actually busy waiting, only when the lock time is very short, the use of spin lock is reasonable spin lock may lead to deadlock: recursion using a spin lock or process to obtain a spin lock after blocking.
Example:
spinlock_t Lock;spin_lock_init (&lock); Spin_lock (&lock); Get spin lock, protect critical area .... Critical Zone Spin_unlock (&lock);//Release spin lock
The spin lock does not care how the locked critical section is executed. In fact, reading a shared resource should allow multiple execution units to be accessed at the same time, whether it is a read or write operation, so the spin lock has its drawbacks. A read-write lock is then derived. It retains the properties of the spin, but can allow multiple unit processes to operate simultaneously on the operation. Of course, reading and writing cannot be done at the same time.
Now there is a problem, if my first process to write a shared resource, the second process read, once written, then can not read, may write more things, but the second process is very small, then can the first process to write at the same time, my second process to read it?
Of course, that leads to the concept of sequential locking. is the same operation.
Read- Write spin lock (Rwlock) allows reading concurrency. In the case of write operations, there can be a maximum of one write process, while in the read operation, there may be more than one read execution unit. Of course, neither reading nor writing can be done at the same time.
//define and initialize read-write spin lock rwlock_t my_rwlock = rw_lock_unlocked; Static initialization of rwlock_t My_rwlock;rwlock) init (&my_rwlock); Dynamic initialization//Read lock: The read lock function should be called before reading the shared resource, and the read unlock function void Read_lock (rwlock_t *lock) is called after completion; void Read_lock_irqsave (rwlock_t * Lock, unsigned long flags), void Read_lock_irq (rwlock_t *lock), void Read_lock_bh (rwlock_t *lock); Read unlock void Read_unlock (rwlock_t *lock); void Read_unlock_irqrestore (rwlock_t *lock, unsigned long flags); void Read_ UNLOCK_IRQ (rwlock_t *lock); void Read_unlock_bh (rwlock_t *lock); Write Lock: Before writing to a shared resource, the write lock function should be called before the Write unlock function is called void Write_lock (rwlock_t *lock); void Write_lock_irqsave (rwlock_t *lock, unsigned long flags); void Write_lock_irq (rwlock_t *lock); void Write_lock_bh (rwlock_t *lock); int Write_trylock (rwlock_ T *lock); Write unlock void Write_unlock (rwlock_t *lock); void Write_unlock_irqsave (rwlock_t *lock, unsigned long flags); void Write_ UNLOCK_IRQ (rwlock_t *lock); void Write_unlock_bh (rwlock_t *lock);
General usage of read-write spin lock:
rwlock_t lock; Definition rwlockrwlock_init (&lock); Initialize Rwlock//read to acquire lock Read_lock (&lock); Critical Resource Read_unlock (&lock); Write to get lock Write_lock_irqsave (&lock, flags); Critical Resource Write_unlock_irqrestore (&lock, flags);
Sequential Lock (Seqlock):
Sequential lock is an optimization of read-write lock, if use sequential lock, read and write operation is not blocked, only the same operation, namely read and read/write and write operation.
The sequence of operations for the Write execution unit is as follows:
Get sequential lock void Write_seqlock (seqlock_t *s1); int Write_tryseqlock (seqlock_t *s1); Write_seqlock_irqsave (lock, flags) WRITE_SEQLOCK_IRQ (Lock) WRITE_SEQLOCK_BH (Lock)//release order lock void Write_sequnlock (seqlock_t *s1); write_sequnlock_ Irqrestore (lock, Flags) WRITE_SEQUNLOCK_IRQ (lock) WRITE_SEQUNLOCK_BH (lock)
The sequence of operations for the read execution unit is as follows:
Read Start unsinged read_seqbegin (const seqlock_t *S1); Read_seqbegin_irqsave (lock, flags)//reread, The read execution unit needs to call this function after it accesses the shared resource protected by sequential lock S1 to check if there is a write operation in the read operation, and if so, the read execution unit needs to be read anew. int reead_seqretry (const seqlock_t *S1, unsigned iv); Read_seqretry_irqrestore (lock, IV, Flags)
RCU (read-copy update read-copy-update) can be seen as a high-performance version of a read-write lock that allows multiple read execution units to simultaneously access the protected data while allowing multiple read execution units and multiple write execution units to access the protected data at the same time. But RCU cannot replace read-write locks. Because if the write operation is relatively long, the performance improvement of the read execution unit cannot compensate for the loss caused by the Write execution unit. Because when using RCU, the synchronization overhead between the write execution units is larger, it requires delaying the release of the data structure, copying the modified structures, and it must also use some kind of locking mechanism to synchronize the modification operations of the other write-execution units in parallel.
Specific operation: slightly
The use of semaphores
The semaphore (semaphore) is the same as the spin lock, and only a semaphore is obtained to perform the critical section code, but when the semaphore is not acquired, the process does not spin in place and goes into a dormant wait state.
Same point: Only the process that gets the semaphore can execute the critical section code. (Linux spin lock and Semaphore lock use "get Lock-access critical section-release lock", can be called "Mutual Exclusion Trilogy", actually exist in almost all multitasking operating system)
Different points: When the semaphore is not acquired, the process does not spin in place and goes into hibernation wait state.
Operation of the semaphore:
Semaphore structure struct semaphore sem;//initialize semaphore void Sema_init (struct semaphore *sem, int val) //Common below two forms of # define INIT_MUTEX ( SEM) sema_init (SEM, 1) #define INIT_MUTEX_LOCKED (SEM) sema_init (SEM, 0) ///The following is a shortcut to initialize the semaphore, the most commonly used Declare_mutex (name) //Initialize name with a semaphore of 1declare_mutex_locked (name)//initialize semaphore to 0//common Operation Declare_mutex (Mount_sem);d Own (&MOUNT_SEM); Get the semaphore ... critical section//Critical area ... up (&MOUNT_SEM); Release semaphore
Semaphores are used to wake up only one execution unit when synchronizing, while the completion amount (completion) is used for synchronization to wake up all waiting execution units.
The choice of spin lock and mutual exclusion lock
- When a lock cannot be acquired, the cost of using semaphores is the process context switch time TSW, the start of the spin lock is the time to wait for the spin lock, and if the TCS is small, a spin lock should be used, otherwise the semaphore
- The critical section of the semaphore lock protection can contain code that causes blocking, whereas a spin lock is likely to trigger a lock trap to avoid using a critical section code that contains blocking
- The semaphore exists in the process context, so if the protected shared resource needs to be used in the event of an interruption or soft interruption, only the spin lock can be selected between the semaphore and the spin lock. Of course, if the semaphore must be used, it can only be done through the down_trylock () and cannot be retrieved and returned immediately to avoid blocking.
All rights reserved, reprint please specify reprint address: http://www.cnblogs.com/lihuidashen/p/4435979.html
concurrency control in the ~linux device driver