Related Concepts
Competitive Conditions
Multiple thread of execution (process/thread/interrupt handler) accesses a shared resource concurrently (in parallel) because the execution order is different, resulting in a different result, called a competition condition (race condition)
Give an example to explain
#include <thread>
using namespace std;
int i = 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 Two threads may have a competitive condition when executed concurrently (of course the probability of a competitive condition is low, but if you remove the annotation, the competition condition is very easy to happen)
i++: This statement typically requires 3 machine instructions
mov eax,i
INC EAX
mov 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, and there will be competitive conditions, 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) |
The critical area (critical section) is a code snippet that accesses and operates a shared resource, in the example of a two-thread i++ statement
to avoid competitive conditions, the critical zone must be executed atomically and cannot be interrupted until the execution is completed. Usually lock the critical area, use the semaphore, the condition variable, and so on, the simple operation in the example can also use the atomic variable. The understanding of atomic nature
The interpretation of atomicity in the general book cannot be interrupted until the execution is completed. How to understand this sentence, my personal understanding is: Atomic variable this, implemented using a lock bus, a number of related instructions are executed continuously; using a lock (spin lock/Sleep Lock), semaphore, condition variable/completion variable these are not to say that all the instructions in the critical section are executed continuously, and may be executed in the critical section half way, Because the scheduler is preempted or interrupted, the CPU does not continue to execute within the critical zone, which means the execution is interrupted.
But that still guarantees the avoidance of competitive conditions, the reason is that if a process outside of the interrupted process is scheduled, the process will have to access the critical section, the lock or semaphore, and so on "stop", is not into the critical area, until the original interrupted critical section execution, release lock/signal volume, etc. It is possible for other processes to enter the critical section. Cause of concurrency disruption: Interrupts can occur almost at any time asynchronously, and can interrupt currently executing code soft interrupts and Tasklet: The kernel can wake or schedule soft interrupts and tasklet at any time, interrupting code that is currently executing Kernel preemption: The Linux kernel is preemptive, so the kernel's tasks may be preempted by another task and synchronized with user space: processes that are executing in the kernel may sleep, waking the Scheduler
Sequence, thus dispatching a new user process to execute SMP, where two or more processors can simultaneously execute synchronization in the code kernel lock
In order to avoid competition conditions, the kernel provides several mechanisms for synchronization, the basic idea is to lock, the critical area mutually exclusive access, the semaphore can also support more than one thread concurrent access. Contention for Locks
The other thread is trying to acquire the lock when it is occupied. The higher the contention of the lock, the lower the performance of the system (locking reduces system performance, such as sleep locks, spin locks), but must be used to ensure correctness. To reduce the lock contention, the 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;/* Definition v*/ |
|
|
atomic_t u = atomic_init (0);/* define U, initialize to 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 operation |
unsigned long word = 0; |
To manipulate a normal pointer |
|
Set_bit (0,&word); The No. 0 bit is set |
__set_bit in the form of a non-atomic operation |
|
Set_bit (1,&word); The 1th bit is set |
Non-atomic operations are faster if atomic operations are not required |
|
PRINTK ("%ul\n", word);//Print 3 |
"Linux kernel design and implementation" P147 |
|
Clear_bit (1,&word);/Clear 1th Place |
|
|
Change_bit (0,&word)/reverse No. 0 Position |
|
Spin lock |
Define_spinlock (Mr_lock); |
A spin lock can be located in a critical section only by one thread at a time |
|
Spin_lock (&mr_lock); |
The protection mechanisms that are required to provide concurrent access for multiple processors |
|
/* Critical Area * * |
Single processor, as a kernel to preempt whether the switch is turned on, if the kernel preemption is prohibited, compile-time spin lock will be completely removed from the kernel |
|
Spin_unlock (&mr_lock); |
Self-rotating lock is not recursive |
Spin lock and lower half part |
Lower half and process context share data/lower half and interrupt handler share data, need to lock |
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 |
|
/* Critical section (read only) * * |
|
|
Read_unlock (&mr_rwlock); |
|
|
Write_lock (&mr_rwlock); |
The writer is mutually exclusive, the writer and the reader are mutually exclusive |
|
/* Critical section (read/write) * * |
|
|
Write_unlock (&mr_rwlock); |
|
Signal Volume |
struct Semophore name; |
The contention thread sleeps, so the situation that the lock will be occupied for a long time |
|
Sema_init (&name, Count); |
Using in the process context |
|
Static Declare_mutex (name); |
|
|
Init_mutex (SEM) |
Dynamic initialization of mutex signals |
|
Static Declare_mutex (MR_SEM); |
|
|
if (down_interruptible (&mr_sem)) {/* signal is received, semaphore not yet acquired/} |
|
|
/* Critical Area * * |
|
|
Up (&mr_sem);//releasing the given semaphore |
|
Read and write signal volume |
Static Declare_rwsem (MR_RWSEM); |
Static definition |
|
Init_rwsem (struct rw_semaphore *sem); |
Dynamic initialization |
|
Down_read (&mr_rwsem); |
|
|
/* Critical section (read only) * * |
|
|
Up_read (&mr_rwsem); |
|
|
Down_write (&mr_rwsem); |
|
|
/* Critical section (read/write) * * |
|
|
Up_write (&mr_rwsem); |
|
Mutex |
Define_mutex (name); |
Sleep locks for handling mutexes |
|
Mutex_init (&mutex); |
Dynamic initialization |
|
Mutex_lock (&mutex) |
No recursive locking and unlocking. |
|
/* Critical Area * * |
No more interruptions or lower half execution |
|
Mutex_unlock (&mutex) |
|
Complete variable |
Declare_completion (Mr_comp) |
Wait, know the mission is done, send a signal |
|
Init_completion (); |
Dynamic creation |
Wait for task to call |
Wait_for_completion (); |
|
The task call that generated the event |
Complete ()//Send a signal to wake a specific event |
|
About the large kernel lock blk, the content of sequential lock See "Linux Kernel Design and implementation" P160 prohibit preemption
Kernel preemption code uses a spin lock as a token for a non-preemption zone
You can disable preemption
More concise solution to the data access problem on each processor, GET_CPU () get the processor number, before returning the processor number will first shut down the kernel preemption
int CPU;
CPU = get_cpu;//Prohibit kernel preemption, set CPU to current CPU
//* For each CPU data operation */*
in giving kernel preemption, "CPU" can be changed, so no longer effective * * *
put_up ();
Order and Barriers
To ensure the order of the reason multiprocessor, hardware device synchronization problems, need to be in the program code in the specified order to read memory and write memory. When interacting with hardware, it is often necessary to ensure that given read operations are on multiple processors before other read/write operations, you may need to read data in the order in which they are written.
For efficiency, compilers (compiler Optimizations) and processors (unordered execution) may reorder read and write. All CPUs that may be reordered read-write are provided with machine instructions to ensure that they are executed sequentially. You can also instruct the compiler not to reorder the sequence of instructions around the point.
e.g.
May reorder
A=1;
b=2;
Impossible to reorder
A=1;
B=a; Explain the reason of the guarantee order by example
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 accept a new value of B 4,d accepted a original value of 1 several methods
Full Interface List
Screenshot from "Linux Kernel Design and implementation" chapter 10th