About Linux's spinlock
The selection of mutex means is not based on the size of the critical section, but based on the nature of the critical section and
Which part of the code is competing for the kernel execution path.
Strictly speaking, semaphore and spinlock_xxx are mutually exclusive methods at different levels.
The implementation depends on the latter. This is a bit like the relationship between HTTP and TCP. Both are protocols, but the layers are different.
Semaphore is process-level, used for mutual exclusion of resources between multiple processes.
In the kernel, but the execution path of the kernel is a process, representing the process to compete for resources. If
No competition, there will be context switch, the process can sleep, but the CPU will not stop, will continue to run
Other execution paths. In terms of concept, this is not directly related to a single CPU or multiple CPUs, just in
To guarantee the atomicity of semaphore structure access
Use a spinlock to obtain mutual exclusion.
In the kernel, it is necessary to ensure that the data access between the various execution paths of the kernel is mutually exclusive, which is the most basic
Mutual Exclusion, that is, maintaining the atomicity of data modification. The implementation of semaphore also depends on this. In a single CPU
The main problems are interruptions and bottom_half. Therefore, the switch can be interrupted. In multiple CPUs,
Added the interference of other CPUs, so you need to use the spinlock to help. These two parts are combined,
It forms the spinlock_xxx. It features that once the CPU enters the spinlock_xxx, it will not
Wait until the lock is successful. Therefore, this determines
The critical zone locked by the spinlock_xxx cannot be stopped, or the context switch cannot be used. You need to access the data and hurry up.
So that other execution paths in the idling can obtain the spinlock. This is also the principle of spinlock.
. If context switch is required for the current execution path, it must be before schedule ().
Release the spinlock. Otherwise, it is prone to deadlocks. Because there is no context in the interrupt and BH, it cannot be performed
Context switch, which can only be idling and waiting for the spinlock. If your context switch is gone, who knows the Year of the Monkey?
Ma Yue can only return.
Because the original intention and purpose of spinlock is to ensure the atomicity of data modification, there is no reason
Stay in the locked critical section.
Spinlock_xxx has many forms, including
Spin_lock ()/spin_unlock (),
Spin_lock_irq ()/spin_unlock_irq (),
Spin_lock_irqsave/spin_unlock_irqrestore ()
Spin_lock_bh ()/spin_unlock_bh ()
Local_irq_disable/local_irq_enable
Local_bh_disable/local_bh_enable
So under what circumstances should we use it? It depends on the kernel execution path and the kernel
Execution paths are mutually exclusive. We know that the main execution paths in the kernel are:
1. There is a process context in the kernel state of the user process, which mainly indicates that the process is executing the system call.
.
2. interrupted, abnormal, or self-trapped. In terms of concept, there is no process context and it cannot be performed.
Context switch.
3 bottom_half. In terms of concept, there is no process context at this time.
4 At the same time, the same execution path may run on other CPUs.
In this way, considering these four factors, the data that we want to mutually exclusive will be identified by these four factors.
Which of the following statements can be used to access the system. If you only need
If it is mutually exclusive, you need to use spin_lock/spin_unlock. If it is mutually exclusive with IRQ and other CPUs, you need to use
Spin_lock_irq/spin_unlock_irq. If it is mutually exclusive with IRQ and other CPUs, it must be saved
For the eflag status, use the spin_lock_irqsave/spin_unlock_irqrestore. If
To be mutually exclusive with BH and other CPUs, use spin_lock_bh/spin_unlock_bh.
Other CPUs are mutually exclusive. If they are mutually exclusive with IRQ, use local_irq_disable/local_irq_enable,
If you do not need to be mutually exclusive with other CPUs, use local_bh_disable/local_bh_enable as long as you and BH are mutually exclusive,
And so on. It is worth noting that the mutex on the same data is in Different kernel execution paths,
The format may be different (see the example below ).
For example. In the interrupt section, there is an irq_desc_t type structure array variable irq_desc [],
Each member of the array corresponds to an IRQ description structure, which contains the IRQ response function.
There is a spinlock in the irq_desc_t structure to ensure the mutex of access (modification.
For an IRQ Member, irq_desc [IRQ] has two paths for accessing the kernel. One is
Setup_irq usually occurs during module initialization, or
The initialization phase of the system; the second is in the interrupt response function (do_irq ). The Code is as follows:
Int setup_irq (unsigned int IRQ, struct irqaction * New)
{
Int shared = 0;
Unsigned long flags;
Struct irqaction * Old, ** P;
Irq_desc_t * DESC = irq_desc + IRQ;
/*
* Some drivers like serial. c Use request_irq () heavily,
* So we have to be careful not to interfere with
* Running system.
*/
If (New-> flags & sa_sample_random ){
/*
* This function might sleep, we want to call it first,
* Outside of the atomic block.
* Yes, this might clear the entropy pool if the wrong
* Driver is attempted to be loaded, without actually
* Installing a new handler, but is this really a problem,
* Only the SysAdmin is able to do this.
*/
Rand_initialize_irq (IRQ );
}
/*
* The following block of code has to be executed atomically
*/
[1] spin_lock_irqsave (& desc-> lock, flags );
P = & desc-> action;
If (old = * P )! = NULL ){
/* Can't share interrupts unless both agree */
If (! (Old-> flags & New-> flags & sa_shirq )){
[2] spin_unlock_irqrestore (& desc-> lock, flags );
Return-ebusy;
}
/* Add new interrupt at end of IRQ queue */
Do {
P = & old-> next;
Old = * P;
} While (old );
Shared = 1;
}
* P = new;
If (! Shared ){
Desc-> depth = 0;
Desc-> Status & = ~ (Irq_disabled | irq_autodetect | irq_waiting );
Desc-> Handler-> startup (IRQ );
}
[3] spin_unlock_irqrestore (& desc-> lock, flags );
Register_irq_proc (IRQ );
Return 0;
}
Asmlinkage unsigned int do_irq (struct pt_regs regs)
{
/*
* We ack quickly, we don't want the IRQ Controller
* Thinking we're re snobs just because some other CPU has
* Disabled global interrupts (we have already done
* Int_ack cycles, it's too late to try to pretend to
* Controller that we aren't taking the interrupt ).
*
* 0 return value means that this IRQ is already being
* Handled by some other CPU. (or is disabled)
*/
Int IRQ = regs. orig_eax & 0xff;/* High bits used in ret_from _ code */
Int CPU = smp_processor_id ();
Irq_desc_t * DESC = irq_desc + IRQ;
Struct irqaction * action;
Unsigned int status;
Kstat. irqs [CPU] [IRQ] ++;
[4] spin_lock (& desc-> lock );
Desc-> Handler-> ack (IRQ );
/*
Replay is when Linux resends an IRQ that was dropped earlier
Waiting is used by probe to mark irqs that are being tested
*/
Status = desc-> Status &~ (Irq_replay | irq_waiting );
Status | = irq_pending;/* We _ want _ to handle it */
/*
* If the IRQ is disabled for whatever reason, we cannot
* Use the action we have.
*/
Action = NULL;
If (! (Status & (irq_disabled | irq_inprogress ))){
Action = desc-> action;
Status & = ~ Irq_pending;/* We commit to handling */
Status | = irq_inprogress;/* We are handling it */
}
Desc-> Status = status;
/*
* If there is no IRQ handler or it was disabled, exit early.
Since we set pending, if another processor is handling
A different instance of this same IRQ, the other processor
Will take care of it.
*/
If (! Action)
Goto out;
/*
* Edge triggered interrupts need to remember
* Pending events.
* This applies to any HW interrupts that allow a second
* Instance of the same IRQ to arrive while we are in do_irq
* Or in the handler. But the code here only handles the _ second _
* Instance of the IRQ, not the third or fourth. So it is mostly
* Useful for IRQ hardware that does not mask cleanly in
* SMP environment.
*/
For (;;){
[5] spin_unlock (& desc-> lock );
Handle_irq_event (IRQ, & regs, action );
[6] spin_lock (& desc-> lock );
If (! (Desc-> Status & irq_pending ))
Break;
Desc-> Status & = ~ Irq_pending;
}
Desc-> Status & = ~ Irq_inprogress;
Out:
/*
* The-> end () Handler has to deal with Interrupts which got
* Disabled while the handler was running.
*/
Desc-> Handler-> end (IRQ );
[7] spin_unlock (& desc-> lock );
If (softirq_pending (CPU ))
Do_softirq ();
Return 1;
}
In setup_irq (), because other CPUs may simultaneously run setup_irq () or setup_irq,
The local IRQ is interrupted. Run do_irq () to modify desc-> status. To prevent both
Interference with local IRQ interruptions, as shown in [1] [2] [3], using the spin_lock_irqsave/spin_unlock_irqrestore ()
In do_irq (), because do_irq () itself is interrupted, and no interrupt is enabled yet
Other CPUs may be running setup_irq () or interrupted,
There is no difference in the impact on the local do_irq (), and they all come from interference from other CPUs. Therefore, you only need to use the spin_lock/spin_unlock
,
See [4] [5] [6] [7. It is worth noting that in [5], the spinlock is released first and then the specific response function is called.
Another example:
Static void tasklet_hi_action (struct softirq_action *)
{
Int CPU = smp_processor_id ();
Struct tasklet_struct * List;
[8] local_irq_disable ();
List = tasklet_hi_vec [CPU]. List;
Tasklet_hi_vec [CPU]. List = NULL;
[9] local_irq_enable ();
While (list ){
Struct tasklet_struct * t = List;
List = List-> next;
If (tasklet_trylock (t )){
If (! Atomic_read (& T-> count )){
If (! Test_and_clear_bit (tasklet_state_sched, & T-> State ))
Bug ();
T-> func (t-> data );
Tasklet_unlock (t );
Continue;
}
Tasklet_unlock (t );
}
[10] local_irq_disable ();
T-> next = tasklet_hi_vec [CPU]. List;
Tasklet_hi_vec [CPU]. List = T;
_ Cpu_raise_softirq (CPU, hi_softirq );
[11] local_irq_enable ();
}
}
Here, the modifications to tasklet_hi_vec [CPU] do not involve competition between CPUs, because each CPU has its own data,
To prevent IRQ interference, use local_irq_disable/local_irq_enable, as shown in [8] [9] [10] [11 ].