In-depth understanding of LINUX kernel notes Chapter 5 kernel synchronization

spin lock

Linux Kernel Synchronization (2): Spinlock (Spinlock)

call process

spin_lock
->raw_spin_lock
	->__raw_spin_lock
		->LOCK_CONTENDED(lock, do_raw_spin_trylock, do_raw_spin_lock);
			->do_raw_spin_lock
				->arch_spin_lock

Back to the present: arch_spinlock_t

arch_spinlock_t in the ARM platform is defined as follows (little endian):

typedef struct {
    
     
    union {
    
     
        u32 slock; 
        struct __raw_tickets {
    
     
            u16 owner; 
            u16 next; 
        } tickets; 
    }; 
} arch_spinlock_t;

Spin lock, which I originally thought could be solved with a simple integer type variable, seems not that simple. To understand this data structure, you need to understand some concepts of ticket-based spin lock. If you have the opportunity to go to Jiu Mao Jiu and queue up to eat (disclaimer: I am not a Jiu Mao Jiu rice tray, I just like pasta and eat it often), you will understand the ticket-based spin lock. Probably because it’s cheap, every time I go to Jiu Mao Jiu, I can’t get in straight away. The pretty girl with a nice smile at the door will give you a ticket with the number 15 written on it, and also tell you that the current status is that the 10th has been seated and the 11th is in wait.

Back to arch_spinlock_t, the owner here is the number that is currently seated, and the next record is the next number to be distributed. The following description uses ordinary computer language and an example of dining at Jiu Mao Jiu (assuming that Jiu Mao Jiu only has one dining table). It is estimated that foodies will be more interested in reading. At the beginning, slock is assigned a value of 0, which means that owner and next are both 0, and owner and next are equal, indicating unlocked. When the first thread calls spin_lock to apply for a lock (the first person to dine), owner and next are equal, indicating unlocked. At this time, the thread holds the spin lock (the only dining table that can have nine cents). , and execute next++, that is, set next to 1 (the next person will be assigned the number 1 to let him wait for his meal). Maybe the thread executes very quickly (eats quickly), and spin_unlock is called when there are no other threads to compete (no one is waiting for dinner, business is bleak). At this time, owner++ is executed, which means the owner is set to 1 (indicating that the current hold is People with the number plate 1 can dine). No. 1, who came late, got the opportunity to eat directly, and next++ is equal to 2. This guy No. 1 eats very slowly, which is uncivilized (thread cannot hold spin lock for too long), but it exists. When another person comes to dine, the number 2 of the current next value is assigned. Of course, next++ will also be executed so that the next person or the number plate of 3 can be assigned. If people continue to come, number plates 3, 4, 5, and 6 will be assigned, and the next value will continue to increase, but the owner will not move until Qian Bian No. 1 finishes eating (calling spin_unlock) and releases the only resource of the dining table. After owner++ Equal to 2, it means that people holding the number plate of 2 can enter to dine.

ARM architecture arch_spin_lock interface implementation

Lock

Similarly, here is just to choose a typical API for analysis, others can be learned by yourself. We chose arch_spin_lock, and its ARM32 code is as follows:

static inline void arch_spin_lock(arch_spinlock_t *lock) 
{
    
     
    unsigned long tmp; 
    u32 newval; 
    arch_spinlock_t lockval;
 
    prefetchw(&lock->slock);------------------------(0) 
    __asm__ __volatile__( 
"1:    ldrex    %0, [%3]\n"-------------------------(1"    add    %1, %0, %4\n" --------------------------(2"    strex    %2, %1, [%3]\n"------------------------(3"    teq    %2, #0\n"----------------------------(4"    bne    1b" 
    : "=&r" (lockval), "=&r" (newval), "=&r" (tmp) 
    : "r" (&lock->slock), "I" (1 << TICKET_SHIFT) 
    : "cc");
 
    while (lockval.tickets.next != lockval.tickets.owner) {
    
    -------(5wfe();--------------------------------(6) 
        lockval.tickets.owner = ACCESS_ONCE(lock->tickets.owner);----(7}
 
    smp_mb();---------------------------------(8}

0) Operations related to preloading cache are mainly for performance considerations

(1) lockval = lock->slock (if lock->slock is not exclusively occupied by other processors, mark the current execution processor’s exclusive access to the lock->slock address; otherwise it will not affect)

(2)newval = lockval + (1 << TICKET_SHIFT)

(3) strex tmp, newval, [&lock->slock] (If the current execution processor does not have exclusive access to the lock->slock address, no storage will be performed, and 1 will be returned to temp; if the current processor has exclusive access to the lock->slock memory If accessed, write to the memory, return 0 to temp, and clear the exclusive mark) lock->tickets.next = lock->tickets.next + 1 // lock->tickets.next + 1 = newval (exclusive memory access instruction
: (1) The LDREX R1, [R0] instruction takes an exclusive word from the address pointed to by R1 and stores it in R0; (2) The STREX R2, R1, [R0] instruction uses R1 exclusively to Update the memory. If the exclusive access condition allows, the update is successful and returns 0 to R2, otherwise it fails and returns 1 to R2.)

(4) Check whether the write is successful lockval.tickets.next

(5) During initialization, lock->tickets.owner and lock->tickets.next are both 0. Assume that arch_spin_lock is executed for the first time, lockval = *lock, lock->tickets.next++, lockval.tickets.next is equal to lockval.tickets .owner, obtain the spin lock; the spin lock has not been released. During the second execution, lock->tickets.owner = 0, lock->tickets.next = 1. After copying to lockval, lockval.tickets.next != lockval.tickets.owner, wfe will be executed to wait for the spin lock to be released and awakened. When the spin lock is released, lock->tickets.owner++ will be executed, and lockval.tickets.owner will be reassigned.

(6) Temporarily interrupt pending execution. If the current state of the spin lock is locked, then call wfe to enter the waiting state. For more specific details, please refer to the description in the ARM WFI and WFE instructions .

(7) Other CPUs wake up the execution of this CPU, indicating that the owner has changed. The new own is assigned to lockval, and then continues to determine the status of the spin lock, which means returning to step 5.

(8) For the operation of memory barrier, please refer to the description in memory barrier for details.

Guess you like

Origin blog.csdn.net/a13821684483/article/details/128119844