The mechanisms to prevent competing states include semaphore (semaphore), spinlock (spin lock), completion (Completion volume), and atomic operations.
The meaning of atomic operations: operations are inseparable.
1. What is concurrency?
More than 1.1 execution units are executed simultaneously and concurrently.
1.2 Competition:
1.2.1 concurrent execution units accessing shared resources can easily lead to competition.
1.2.2 shared resources: hardware resources, software global variables, static variables, etc.
2. What kind of competition will happen in the Linux kernel?
2.1 competition among multiple CPUs of symmetric multi-processor (SMP)
2.2 inter-process competition in a single CPU
2.3 The competition between the Process and the interruption (hard interrupt, Soft Interrupt, tasklet, and low half step.
3. How to Solve competitive issues
Mutual access to shared resources is guaranteed (that is, when an execution unit accesses shared resources, other execution units are not allowed to access)
4. mutually exclusive approaches available for Linux Device Drivers
4.1 interrupt shielding
4.2 atomic operations
4.3 spin lock
4.4 semaphores
4.5 completion (completions)
(1) interrupt shielding
(1.1) can solve the problem of interruption and concurrency between processes
(1.2) It can also solve the concurrency problem between kernel preemption processes.
(1.3) Main Functions
Local_irq_disable () // disable interruption
Local_irq_enable () // open interrupt
Local_irq_save (flags) // disable interrupt and save the interrupt register flags
Local_irq_restore (flags) // open the interrupt and restore the flags to the interrupt register.
Local_bh_disable () // only the lower half of the interrupt is prohibited
Local_bh_enable () // open
Note: Do not block interruptions for a long time. Because many important operations such as asynchronous I/O and process scheduling in Linux system depend on interruptions, all interruptions cannot be processed during the blocking process, therefore, it is very dangerous to shield the data for a long time, which may cause visual data or even system crashes.
Usage:
Local_irq_disable () // block interrupt
......
Critical section // critical section
......
Local_irq_enable () // open interrupt
(2) semaphores (semaphore)
(2.1) semaphores are essentially an integer.
(2.2) a pair of operation functions are usually called P and V
(2.3) entering the critical section
(2.3.1) Call P on related semaphores
(2.3.2) if the semaphore is greater than zero, the value is reduced by one, and the process can continue.
(2.3.3) if the semaphore value is zero (or smaller), the process must wait until other processes release the semaphore.
(2.4) Exit the critical section
(2.4.1) the semaphore is unlocked by calling v.
(2.4.2) This function increases the semaphore value.
(2.4.3) and wake up the waiting process when necessary.
(2.5) semaphores are used for mutex
(2.5.1) Avoid multiple processes running in one critical section at the same time
(2.5.2) the semaphore value should be initialized to 1.
Only one process or thread can own
A semaphore is also known as a mutex )"
It is short for mutual exclusion.
Almost all semaphores in the Linux kernel are used for mutual exclusion.
The value of the mutex can only be: 1, 0
(2.6) Initial semaphore
(2.6.1) the semaphore type is struct semaphore, which is defined in <Linux/semaphore. h>;
(2.6.2) semaphores can be declared and initialized in several ways.
(2.6.3) Dynamic initialization semaphore void sema_init (struct semaphore * SEM, int Val );
VAL: the initial value of the semaphore.
(2.6.4) Static declared mutex semaphores:
Declare_mutex (name); // declare the mutex semaphore "name" and initialize it to 1
Declare_mutex_locked (name); // declare the mutex semaphore "name" and initialize it to 0
(2.6.5) Dynamic initialization mutex semaphores:
Void init_mutex (struct semaphore * SEM );
Void init_mutex_locked (struct semaphore * SEM );
(2.7) obtain the semaphore
Void down (struct semaphore * SEM); // reduce the semaphore value. Wait until the semaphore is obtained.
Int down_interruptible (struct semaphore * SEM); // completes the same job as down (), but can operate interrupted
Int down_trylock (struct semaphore * SEM); // never sleep. If the semaphore is not available during the call, a non-zero value is immediately returned.
(2.8) Release semaphores
Void up (struct semaphore * SEM)
(2.9) use the semaphore Template
(2.10) Introduction to mutex instances
(2.11) Reader/writer Signal
(2.11.1) read operations are concurrent and write operations are mutually exclusive. That is, an rwsem allows a writer or an unlimited number of readers to own the semaphore.
(2.11.2) the writer has a higher priority. When a large number of writers compete for the semaphore, the reader will be "starved to death", that is, the reader's access will be rejected for a long time.
(2.11.3) this mechanism is rarely used by drivers. Rwsem is generally used when there is little write access and the writer only has a semaphore for a short period of time.
(2.11.4) Data Type: struct rw_semaphore;
(2.11.5) initialization: void init_rwsem (struct rw_semaphore * SEM );
(2.11.6) main functions:
Void down_read (structrw_semaphore * SEM );
Int down_read_trylock (structrw_semaphore * SEM );
Void up_read (structrw_semaphore * SEM );
Void down_write (structrw_semaphore * SEM );
Int down_write_trylock (structrw_semaphore * SEM );
Void up_write (structrw_semaphore * SEM );
(3) completion (number of completions)
(3.1) a lightweight Mechanism
(3.2) It allows one thread to tell another thread that a job has been completed.
(3.3) initialization:
Declare_completion (xxx_completion); // static Creation
Struct completion xxx_cmmpletion; // dynamically created
Init_completion (& xxx_completion); // Initialization
(3.4) Wait for completion
Void wait_for_completion (struct completion * C );
(3.5) trigger completed
Void complete (stuct completion * C); // wake up a waiting thread
Void complete_all (struct completion * C); // wake up all the waiting threads
If you use complete_all and want to reuse the completion structure, you must reinitialize the structure before reusing it. The red below can be used to quickly perform the lucky initialization:
Init_completion (struct completion C );
(4) spin lock
(4.1) concept: a spin lock is a mutually exclusive device with only two values: "locked" and "Unlocked ".
Generally, to implement a single bit in an integer, to obtain the code of a specific lock, test the relevant bit.
If the lock is available, the lock bit is set, and the code continues to enter the critical section.
If the lock is obtained by others, the Code enters the busy cycle and re-returns to see this lock until the lock is available. This cycle is the "choice" of the spin lock ".
(4.2) initialize the spin lock
(4.2.1) initialization at Compilation:
Spinlock_t xxx_lock = spin_lock_unlocked;
(4.2.2) runtime initialization:
Void spin_lock_init (spinlock_t * Lock );
(4.3) Lock function
Void spin_lock (spinlock_t * Lock );
// Interrupt should be prohibited (including Soft Interrupt and hard interrupt) before obtaining the spin lock)
Void spin_lock_irq (spinlock_t * Lock );
// Disable interrupt (including Soft Interrupt and hard interrupt) before obtaining the spin lock. The interrupt status is saved in the status word flags.
Void spin_lock_irqsave (spinlock_t * Lock, unsigned long flags );
// Obtain the strong static Soft Interrupt of the spin lock, and keep the hardware interrupt open.
Void spin_lock_bh (spinlock_t * Lock)
(4.4) release the spin lock:
Void spin_unlock (spinlock_t * Lock );
Void spin_unlock_irq (spinlock_t * Lock );
Void spin_unlock_irqrestore (spinlock_t * Lock, unsigned long flags );
Void spin_unlock_bh (spinlock_t * Lock );
(4.5) read/write spin lock:
Any number of readers can enter the critical section at the same time. The writer must access each other and the variable type is rwlock_t.
(4.5.1) initialization:
# Include <Linux/spinlock. h>
Rwlock_t xxx_rwlock = rw_lock_unlocked;
Or:
Rwlock_t xxx_rwlock;
Rwlock_int (& xxx_rwlock );
(4.5.2) obtained by the reader
Void read_lock (rwlock_t * Lock );
Void read_lock_irqsave (rwlock_t * Lock, unsigned long flags );
Void read_lock_irq (rwlock_t * Lock );
Void read_lock_bh (rwlock_t * Lock );
(4.5.3) the reader releases the lock.
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 );
(4.5.4) the writer acquires the lock.
Void read_lock (rwlock_t * Lock );
Void read_lock_irqsave (rwlock_t * Lock, unsigned long flags );
Void read_lock_irq (rwlock_t * Lock );
Void read_lock_bh (rwlock_t * Lock );
(4.5.5) the writer releases the lock.
Void write_unlock (rwlock_t * Lock );
Void write_unlock_irqrestore (rwlock_t * Lock, unsigned long flags );
Void write_unlock_irq (rwlock_t * Lock );
Void write_unlock_bh (rwlock_t * Lock );
(4.6) spin lock vs semaphore
(4.6.1) Cost:
The overhead of semaphores is the context switching time of the process.
The overhead of the spin lock is waiting to get the spin lock.
() Different wait mechanisms
Semaphores may cause blocking. Therefore, you cannot use a semaphore processing method that may reference blocking in codes that do not allow blocking.
The spin lock is busy waiting. (Exclusive processor)
(4.7) rules for using spin locks
(4.7.1) Any code with a spin lock must be atomic.
(4.7.2) if the interrupt handler function also requires a spin lock, then the driver must disable the spin lock when it has a spin lock.
(4.7.3) the spin lock must be possessed within a short time.
(4.8) Other Rules
(4.8.1) prevent a function that acquires the lock from calling other functions that attempt to obtain the lock. Otherwise, the code will be deadlocked.
(4.8.1) Neither semaphores nor spin locks allow the lock owner to obtain the lock for the second time. If you try to do this, the system will be suspended.
(4.9) lock sequence rules
(4.9.1) when multiple locks are required, they should always be obtained in the same order. This effectively avoids deadlocks.
(4.9.2) if you need to obtain a local lock and a lock that belongs to a more central position of the kernel, you should first obtain your own local lock.
(4.9.3) if we have a combination of semaphores and spin locks, we must first obtain the semaphores. Calling down (which can cause sleep) when we have a spin lock is a serious error.
(5) Atomic operations
(5.1) integer atomic operation
(5.1.1) type: atomic_t
(5.1.2) set the value of the atomic variable.
Void atomic_set (atomic_t * V, int I); // dynamic Initialization
Atomic_t v = atomic_init (0); // static initialization.
(5.1.3) Get the value of the atomic variable: int atomic_raad (atomic_t * V); // return the current value of v.
(5.1.4) addition and subtraction of atomic variables
Void atomic_add (int I, atomic_t * V); // * V accumulative I, no return value.
Void atomic_sub (int I, atomic_t * V); // * V decimal I, no return value.
(5.1.5) Atomic variable auto-increment/auto-Increment
Void atomic_inc (atomic_t * V );
Void atomic_dec (atomic_t * V );
(5.1.6) operate and Test
// If the atomic value is 0 after the operation ends, true is returned. Otherwise, false is returned.
Int atomic_inc_and_test (atomic_t * V );
Int atomic_dec_and_test (atomic_t * V );
Int atomic_sub_and_tes (int I, atomic_t * V)
(5.1.7) operation and return
// After the operation is completed, a new value is returned.
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 );
(5.1) Bit atomic operation
Void set_bit (NR, void * ADDR); // set the value of the second bit of Nr.
Void clear_bit (NR, void * ADDR); // clear the value of the second bit of NR
Void change_bit (NR, void * ADDR); // change the value of the second Nr bit.
Void test_bit (NR, void * ADDR); // checks whether nrbit is set
// First check and return the previous value.
Int test_and_set_bit (NR, void * ADDR );
Int test_and_clear_bit (NR, void * ADDR );
Int test_and_change_bit (NR, void * ADDR );
Statement: This article is not original. It is organized from application embedding.