Linux concurrency and competition (atomic/spinlock/semaphore/mutex understanding)

1. Reasons for linux concurrency

(1) Multi-threaded concurrent access,

(2) Preemptive concurrent access, process scheduling can preempt the running thread at any time, so as to run other threads

(3) Terminal program concurrent access

(4) Concurrent access between SMP (multi-core)

The consequence of concurrent access is competition. Generally, global variables and device structures must be protected. As for other data, it depends on the actual driver.

2. Atomic operations

(1) Reasons for atomic operations

For c language, a=3, when compiled into assembly language, it may be translated into the following assembly (only for understanding)

ldr r0, =0X30000000 /* variable a address */
ldr r1, = 3 /* value to write */
str r1, [r0] /* write 3 into variable a */

This can happen when concurrency occurs:

(2) Atomic operation api

, In order to avoid the above situation, the Linux kernel defines a structure called atomic_t to complete the atomic operation of integer data. In use, atomic variables are used to replace integer variables. This structure is defined in the include/linux/types.h file.

 typedef struct {
 int counter;
 } atomic_t;


typedef struct {
long long counter;
} atomic64_t;//64 operating system

Variables can be declared like this, atomic_t a;

Linux provides a number of functions that can operate on atomic variables,

3 spinlocks

Atomic operations can only protect integer variables or bits, but how can there be only such a simple critical section as integer variables or bits in the actual use environment. To give the simplest example, the variable of the device structure is not an integer variable. We must also ensure the atomicity of the operation of the member variables in the structure. During the use of the structure variable by thread A, other threads should be prohibited from accessing this variable. Structural variables, these work atomic operations are not competent, and a lock mechanism is required, which is a spin lock in the Linux kernel.

When a thread wants to access a shared resource, it must first acquire the corresponding lock. The lock can only be held by one thread. As long as the thread does not release the held lock, other threads cannot acquire the lock. For spin locks, if the spin lock is being held by thread A and thread B wants to acquire the spin lock, then thread B will be in a busy loop-spin-wait state, and thread B will not go to sleep or Instead of doing other processing, it will always be foolishly “circling around” waiting for the lock to be available.

From here we can see a disadvantage of the spin lock: the thread waiting for the spin lock will always be in the spin state, which will waste processor time and reduce system performance, so the holding time of the spin lock should not be too long . So spin locks are suitable for short-term lightweight locking.

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;

Spinlock interface function

The critical section protected by the spin lock must not call any API function that can cause sleep and blocking, otherwise it may cause deadlock. Examples of classic deadlocks:

Thread a enters dormancy while holding the lock and is scheduled out. At this time, thread b wants to acquire the lock, but the lock is held by thread a, and kernel preemption is prohibited. Thread b cannot be scheduled out and waits for the lock to be released. At this time, thread a cannot be scheduled to run again, and a deadlock occurs.

Also, when using locks in interrupts, you must also pay attention to the occurrence of deadlocks, for example

Thread A runs first and acquires the lock. When thread A runs functionA, an interrupt occurs, and the interrupt takes away the right to use the CPU. The interrupt service function on the right also acquires the lock, but this lock is occupied by thread A, and the interrupt will keep spinning, waiting for the lock to become valid.

The best solution is to close the local interrupt before acquiring the lock

DEFINE_SPINLOCK(lock) /* define and initialize a lock */

 /* Thread A */
 void functionA (){
 unsigned long flags; /* interrupt status */
 spin_lock_irqsave( & amp;lock, flags) /* is here to disable local interrupts and acquire locks */
 /* critical section */
 spin_unlock_irqrestore( & amp;lock, flags) /* release the lock */
 }

 /* Interrupt service function */
 void irq() {
 spin_lock( & amp;lock) /* acquire lock */
 /* critical section */
 spin_unlock( & amp;lock) /* release the lock */
 }

Precautions for using spin locks;

(1) Because it is in a “spin” state while waiting for the spin lock, the lock holding time cannot be too long, and it must be
Short, otherwise it will degrade system performance. If the critical section is relatively large and the running time is relatively long, you should choose other concurrent places.
management methods, such as semaphores and mutexes.

(2) In the critical section protected by the spin lock, any API function that may cause the thread to sleep cannot be called, otherwise it may
lead to deadlock.

(3) You cannot recursively apply for a spin lock, because once you recursively apply for a lock you are holding, then you will
Must “spin”

4. Semaphore

Compared with spin locks, semaphores can make threads go to sleep. For example, A shares a house with B and C. There is only one toilet in this house, which can only be used by one person at a time. One morning A went to the toilet, and after a while B also wanted to use the toilet, because A was in the toilet, so B had to wait until A used it before going in. Either B has been waiting at the door of the toilet, waiting for A to come out, this time is equivalent to a spin lock. B can also tell A to let A notify him after he comes out, and then B continues to go back to the room to sleep, which is equivalent to a semaphore at this time. Using a semaphore will improve the efficiency of the processor, but the overhead of the semaphore is greater than that of the spin lock, because the semaphore will switch the thread after the thread enters the dormant state, and there will be overhead for switching the thread.

Semaphore features:

(1) Because the semaphore can make the thread waiting for resources go to sleep, it is suitable for those places that occupy resources for a long time
combine.

(2) The semaphore cannot be used in interrupts, because the semaphore will cause sleep, and the interrupt cannot sleep.

(3) If the holding time of shared resources is relatively short, it is not suitable to use semaphores, because the overhead caused by frequent sleep and switching threads is far greater than the advantages brought by semaphores.

A semaphore generally has a semaphore value, which can be used to control the access quantity of azimuth shared resources. For example, there are 10 keys in a room, which is equivalent to a semaphore value of 10. If you want to enter the room, you need to obtain a key first, and the semaphore value is reduced by 1 until all 10 keys are taken away, and the semaphore is 0. , At this time, no one is allowed to enter the room. If someone comes out, then one person can be allowed to enter.

struct semaphore {
raw_spinlock_t lock;
unsigned int count;
struct list_head wait_list;
};

Semaphore API functions:

struct semaphore sem; /* define semaphore */
sema_init( & amp;sem, 1); /* Initialize the semaphore */
down( & amp;sem); /* apply for semaphore */
/* critical section */
up( & amp;sem); /* Release the semaphore */

5. Mutex

Set the value of the semaphore to 1 to use the semaphore for mutual exclusive access, but the mutex has a special structure,

struct mutex {
/* 1: unlocked, 0: locked, negative: locked, possible waiters */
atomic_t count;
spinlock_t wait_lock;
};

Features:

(1) Mutex can cause sleep, so mutex cannot be used in interrupt, and only spin lock can be used in interrupt.

(2) Like a semaphore, the critical section protected by a mutex can call API functions that cause blocking.

(3) Since only one thread can hold a mutex at a time, the mutex must be released by the holder of the mutex. and
And mutex cannot be locked and unlocked recursively.

The interface function of the mutex:

 struct mutex lock; /* define a mutex */
 mutex_init( & amp;lock); /* initialize mutex */

 mutex_lock( & amp;lock); /* lock */
 /* critical section */
 mutex_unlock( & amp;lock); /* unlock */