Related Concepts Competition conditions
Multiple threads of execution (process/thread/interrupt handler) concurrently (parallel) access to shared resources because the execution order is different, which results in a different situation, called a race condition (race condition)
Examples Show
#include<thread>usingnamespacestd;int 0;void thread1(){ //for(int x=0;x<100000;x++) i++;}void thread2(){ //for(int x=0;x<100000;x++) i++;}int main(){ std::thread t1(thread1); std::thread t2(thread1); t1.join(); t2.join(); printf(" i = %d\n",i);}
T1,t2 there is a possibility of a race condition when two threads are executed concurrently (of course, the probability of a competitive condition is very low, but if you remove the comment, the race condition is very easy to happen)
i++; This statement normally requires 3 machine instructions.
MOV EAX,iINC EAXMOV EAX,i
If executed in the following order, there is no problem, the printed I is 2
Thread 1 |
Thread 2 |
MOV Eax,i (i=0) |
|
INC EAX |
|
MOV Eax,i (I=1) |
|
|
MOV Eax,i (I=1) |
|
INC EAX |
|
MOV Eax,i (i=2) |
However, two threads are likely to be executed in the following order, where a race condition appears, and the printed I is 1
Thread 1 |
Thread 2 |
MOV Eax,i (i=0) |
|
INC EAX |
|
|
MOV Eax,i (i=0) |
MOV Eax,i (I=1) |
|
|
INC EAX |
|
MOV Eax,i (I=1) |
critical Sections (critical section) are snippets of code that access and manipulate shared resources, as in the case of a two-thread i++ statement
To avoid competitive conditions, the critical section must be executed atomically and cannot be interrupted until the execution is completed. It is common to lock the critical section, use the semaphore, condition variables, and so on, and the simple operations in the example can also use atomic variables.
The understanding of atomic nature
The interpretation of atomicity in general books cannot be interrupted until the end of execution. How is this sentence understood, my personal understanding is:
- The atomic variable is implemented by means of the lock bus, and several related instructions are executed continuously;
- Use a lock (spin lock/Sleep Lock), semaphore, condition variable/completion variable these, not that all the commands in the critical section execute continuously, may be executed in the critical section to half, because the scheduling preemption or interrupt, the CPU does not continue in the critical zone execution, that is, execution is interrupted.
- However, this situation is still guaranteed to avoid competition conditions, because if the process outside the interrupted process is dispatched, the process to access the critical section, the lock or semaphore, etc. "Stop", is not entering the critical area, until the original interrupted critical section execution, release lock/signal volume, etc. Other processes are only likely to enter the critical section.
Causes of concurrency
- Interrupts: Interrupts can occur asynchronously at almost any moment, and can interrupt code that is currently executing at any time
- Soft interrupts and Tasklet: The kernel can wake up or dispatch soft interrupts and tasklet at any time, interrupting code that is currently executing
- Kernel preemption: The Linux kernel is preemptive, so the kernel's task may be preempted by another task
- Sleep and user space synchronization: Processes that execute in the kernel may sleep and wake up the scheduler
Scheduler to schedule a new user process execution
- SMP, two or more processors can execute code at the same time
The synchronization method in the kernel locks
In order to avoid competition conditions, the kernel provides several mechanisms for synchronization, the basic idea is to lock, critical section mutually exclusive access, semaphore can also support more than one thread concurrent access.
Contention for Locks
When it is occupied, there are other threads trying to obtain the lock. The higher the contention of the lock, the lower the system performance (locking can degrade system performance, such as sleep lock, spin lock), but must be used to ensure correctness. To reduce the contention of locks, lock granularity should be as small as possible, that is, the critical area should be as small as possible.
Introduction to several synchronization methods
Synchronization Method |
Main interface |
Notes |
Atomic integer (32bit) operation |
atomic_t v;/*定义 v*/ |
|
|
atomic_t u = ATOMIC_INIT(0);/*定义u,初始化为0*/ |
|
|
atomic_set(&v, 4);/* v=4 */ |
|
|
atomic_add(2, &v);/* v=v+2 */ |
|
|
atomic_inc(&v);/* v=v+1 */ |
|
Atomic integer (64bit) operation |
Atomic64_t v; |
|
Atomic bit manipulation |
unsigned long word = 0; |
Working with normal pointers |
|
set_bit(0,&word); //第0位被设置 |
__set_bit in the form of non-atomic operations |
|
set_bit(1,&word); //第1位被设置 |
Non-atomic operations are faster if atomic operations are not required |
|
printk("%ul\n",word);//打印3 |
"Linux kernel design and implementation" P147 |
|
clear_bit(1,&word);//清空第1位 |
|
|
change_bit(0,&word);//反转第0位 |
|
Spin lock |
DEFINE_SPINLOCK(mr_lock); |
Spin locks can only be in the critical section at the same time by a single thread |
|
spin_lock(&mr_lock); |
Provides the protection mechanisms required for concurrent access for multiple processors |
|
/*临界区*/ |
Single processor, as a kernel preemption whether the switch is turned on, if the kernel preemption is forbidden, compile-time spin lock will be completely rejected the kernel |
|
spin_unlock(&mr_lock); |
Spin Lock not recursive |
Spin lock and Lower half |
Lower half and process context shared data/lower half and interrupt handler shared data, need to be locked |
The lower half can preempt the process context, and the interrupt handler can preempt the lower part |
Read/write Spin lock |
DEFINE_RWLOCK(mr_rwlock); |
|
|
read_lock(&mr_rwlock); |
shared by all readers |
|
/*临界区(只读)*/ |
|
|
read_unlock(&mr_rwlock); |
|
|
write_lock(&mr_rwlock); |
The writer is mutually exclusive, the writer and the reader are mutually exclusive |
|
/*临界区(读写)*/ |
|
|
write_unlock(&mr_rwlock); |
|
Signal Volume |
struct semophore name; |
The contention thread sleeps, so the lock can be held for a long time. |
|
sema_init(&name, count); |
Used in the context of a process |
|
static DECLARE_MUTEX(name); |
|
|
init_MUTEX(sem) |
Dynamic initialization of the mutex signal volume |
|
static DECLARE_MUTEX(mr_sem); |
|
|
if(down_interruptible(&mr_sem)){ /*信号被接收,信号量还未获取*/ } |
|
|
/*临界区*/ |
|
|
up(&mr_sem);//释放给定的信号量 |
|
Read/write signal volume |
static DECLARE_RWSEM(mr_rwsem); |
Statically defined |
|
init_rwsem(struct rw_semaphore *sem); |
Dynamic initialization |
|
down_read(&mr_rwsem); |
|
|
/*临界区(只读)*/ |
|
|
up_read(&mr_rwsem); |
|
|
down_write(&mr_rwsem); |
|
|
/*临界区(读写)*/ |
|
|
up_write(&mr_rwsem); |
|
Mutex |
DEFINE_MUTEX(name); |
A sleep lock for handling mutexes |
|
mutex_init(&mutex); |
Dynamic initialization |
|
mutex_lock(&mutex) |
Cannot be locked and unlocked in a recursive |
|
/*临界区*/ |
Can no longer be interrupted or executed in the lower half |
|
mutex_unlock(&mutex) |
|
Completion variables |
DECLARE_COMPLETION(mr_comp) |
Wait, know the task is done, send a signal |
|
init_completion(); |
Dynamically created |
Wait for task to be called |
wait_for_completion(); |
|
The task invocation that generated the event |
Complete ();//Send a signal to wake a specific event |
|
For large kernel lock blk, the contents of the sequential lock are shown in "Linux kernel design and implementation" P160
Prohibit preemption
Kernel preemption code uses spin locks as a token for non-preemption zones
You can disable preemption
A more concise solution to the data access problem on each processor, GET_CPU () Gets the processor number and shuts down the kernel preemption before returning the processor number
int cpu;cpu = get_cpu;//禁止内核抢占,将CPU设置为当前cpu/*对每一个cpu的数据进行操作*/*在给予内核抢占性,“cpu”可改变,所以不再有效*/put_up();
Order and barrier
Reason for order of guarantee
- When synchronizing problems between multiple processors, hardware devices, you need to read memory and write memory in the program code in the order specified.
- When interacting with hardware, it is often necessary to ensure that a given read operation precedes other read/write operations
- On multiple processors, you may need to read the data in the order in which it was written.
For efficiency, the compiler (compilation optimization) and the processor (disorderly execution) may reorder read and write.
- All CPUs that may be reordered read and write provide machine instructions that are executed sequentially.
- You can also instruct the compiler not to reorder the sequence of instructions around the given point.
e.g.
May reorder
A=1;
b=2;
Impossible to reorder
A=1;
B=a;
Explain the reasons for the Order of guarantee
a=1,b=2
Thread 1 |
Thread 2 |
A=3; |
– |
MB (); |
– |
b=4; |
C=b; |
– |
RMB (); |
– |
D=a; |
If there is no memory barrier on some processors, C may have accepted the new value of B 4,d accepted a original value of 1
Several methods
Full interface List
From the 10th chapter of Linux kernel design and implementation
Linux kernel design and implementation notes--Introduction to Kernel synchronization