Seqlock
Categories:
12 minute read
Overview
A seqlock is a lock that improves upon the spinlock. It primarily distinguishes between read and write operations. A seqlock contains a spinlock and a sequence value. The seqlock divides critical sections into three types:
- Exclusive write: Any write operation on the seqlock critical section is exclusive. Each write operation first locks the spinlock, then increments the sequence value, and increments the sequence value again when leaving the critical section. In other words, the sequence value increments twice, so the number of increments is always even.
- Non-exclusive read: If multiple CPUs perform read-only operations, they can simultaneously enter the non-exclusive read critical section. CPUs entering the non-exclusive read critical section do not need to acquire the spinlock but must first check whether the sequence value is even and record the sequence value at that time. When exiting the read critical section, they need to read the sequence value again and compare it with the previously recorded value:
- If equal, the read result is valid;
- If not equal, it means another CPU performed a write operation during the read, and this read operation is invalid.
- Exclusive read: If you want the read critical section not to be invalidated by write operations, you can use the exclusive read mode. Exclusive read will exclude exclusive write and exclusive read operations on other CPUs, but will not exclude non-exclusive reads; other CPUs can still enter the non-exclusive read critical section.
Limitations
A seqlock has a flaw: when a writer writes data as a null pointer, it might cause a non-exclusive reader to dereference a null pointer and crash.
Using Seqlock
Seqlock Initialization
Like spinlocks, the seqlock structure is very small and does not provide dynamic creation and deletion methods. Users can initialize a seqlock via xwos_sqlk_init().
Multiple Locks
When using multiple spinlocks to protect write critical sections or exclusive read critical sections, the locking and unlocking order must be consistent, otherwise it will cause deadlock.
Non-exclusive Read Critical Section
If multiple CPUs perform read-only operations, they can simultaneously enter the non-exclusive read critical section. CPUs entering the non-exclusive read critical section do not need to acquire the spinlock, but must first check whether the sequence value is even and record the sequence value at that time. This operation can be done via xwos_sqlk_rd_begin().
When exiting the read critical section, the sequence value must be read again and compared with the previously recorded value. This operation can be done via xwos_sqlk_rd_retry().
- If equal, the read result is valid;
- If not equal, it means another CPU performed a write operation during the read, and this read operation is invalid.
do {
seq = xwos_sqlk_rd_begin(&lock); /* Get sequence value before entering critical section */
/* Non-exclusive read critical section */
} while (xwos_sqlk_rd_retry(&lock, seq)); /* Check if sequence value has changed */
Users can also read the sequence value via xwos_sqlk_get_seq() and compare it themselves.
Exclusive Read Critical Section
If you want the read critical section not to be invalidated by write operations, you can use the exclusive read mode. Exclusive read will exclude exclusive write and exclusive read operations on other CPUs, but will not exclude non-exclusive reads; other CPUs can still enter the non-exclusive read critical section.
Protecting Read Critical Sections Between Thread Contexts
- The critical section can be nested and is only safe for thread context.
xwos_splk_rdex_lock(&lock1);
/* Exclusive read critical section 1 */
xwos_splk_rdex_lock(&lock2);
/* Exclusive read critical section 2 */
xwos_splk_rdex_unlock(&lock2);
/* Exclusive read critical section 1 */
xwos_splk_rdex_unlock(&lock1);
- Within the critical section, only preemption is disabled. This can be understood as: at the thread level, operations within the critical section are atomic, and data within the critical section can only be accessed by thread context.
- Scheduling cannot occur within the critical section. Users must not use CAPI that cause sleep and blocking inside the critical section.
- CAPI
xwos_splk_rdex_lock(): Lock, disable preemptionxwos_splk_rdex_trylock(): Try to lock, disable preemptionxwos_splk_rdex_unlock(): Unlock, enable preemption
Protecting Read Critical Sections Between Any Context
- The critical section is safe for any context. However, the critical section must be single; nesting will cause the following error:
xwos_splk_rdex_lock_cpuirq(&lock1);
/* Exclusive read critical section 1 */
xwos_splk_rdex_lock_cpuirq(&lock2);
/* Exclusive read critical section 2 */
xwos_splk_rdex_unlock_cpuirq(&lock2);
/* Exclusive read critical section 1: ERROR! Interrupt is enabled */
xwos_splk_rdex_unlock_cpuirq(&lock1);
- Within the critical section, not only preemption is disabled, but CPU interrupts are also disabled. This can be understood as: operations within the critical section are atomic, and data within the critical section can be accessed by any context.
- Interrupts cannot occur within the critical section, and scheduling is also impossible. However, users still must not use CAPI that cause sleep and blocking inside the critical section.
- CAPI
xwos_splk_rdex_lock_cpuirq(): Lock, disable preemption and CPU interrupt (IRQ)xwos_splk_rdex_trylock_cpuirq(): Try to lock, disable preemption and CPU interrupt (IRQ)xwos_splk_rdex_unlock_cpuirq(): Unlock, enable preemption and CPU interrupt (IRQ)
Protecting Nested Read Critical Sections Between Any Context
- To solve the problem of nesting critical sections when interrupts are disabled, the CAPI for saving and restoring interrupt flags can be used.
xwos_splk_rdex_lock_cpuirqsv(&lock1, &cpuirq1);
/* Exclusive read critical section 1 */
xwos_splk_rdex_lock_cpuirqsv(&lock2, &cpuirq2);
/* Exclusive read critical section 2 */
xwos_splk_rdex_unlock_cpuirqrs(&loc2, cpuirq2);
/* Exclusive read critical section 1 */
xwos_splk_rdex_unlock_cpuirqrs(&loc1, cpuirq1);
- Within the critical section, not only preemption is disabled, but CPU interrupts are also disabled. This can be understood as: operations within the critical section are atomic, and data within the critical section can only be accessed by thread context.
- Interrupts cannot occur within the critical section, and scheduling is also impossible. However, users still must not use CAPI that cause sleep and blocking inside the critical section.
- CAPI
xwos_splk_rdex_lock_cpuirqsv(): Lock, disable preemption, save CPU interrupt (IRQ) flag and disablexwos_splk_rdex_trylock_cpuirqsv(): Try to lock, disable preemption, save CPU interrupt (IRQ) flag and disablexwos_splk_rdex_unlock_cpuirqrs(): Unlock, enable preemption, restore CPU interrupt (IRQ)
Protecting Exclusive Read Critical Sections Between Thread and Specified Interrupt (IRQ) Contexts
- The critical section is safe for thread and specified interrupt (IRQ) contexts. However, the critical section must be single.
xwos_splk_rdex_lock_irqs(&lock, irq_array, num);
/* Exclusive read critical section */
xwos_splk_rdex_unlock_irqs(&lock, irq_array, num);
- Within the critical section, only preemption and specified interrupts are disabled. This can be understood as: at the level of thread and specified interrupt functions, operations within the critical section are atomic, and data within the critical section can only be accessed by thread and specified peripheral interrupt (IRQ) contexts.
- Specified interrupts cannot occur within the critical section, and scheduling also cannot occur. However, users still must not use CAPI that cause sleep and blocking inside the critical section.
- CAPI
xwos_splk_rdex_lock_irqs(): Lock, disable preemption, disable partial interrupts (IRQ)xwos_splk_rdex_trylock_irqs(): Try to lock, disable preemption, disable partial interrupts (IRQ)xwos_splk_rdex_unlock_irqs(): Unlock, enable preemption, enable partial interrupts (IRQ)
Protecting Nested Read Critical Sections Between Thread and Specified Interrupt (IRQ) Contexts
- The critical section is safe for thread and specified interrupt (IRQ) contexts. The critical section can be nested.
xwos_splk_rdex_lock_irqssv(&lock1, irq_array, flag1_array, num);
/* Exclusive read critical section 1 */
xwos_splk_rdex_lock_irqssv(&lock2, irq_array, flag2_array, num);
/* Exclusive read critical section 2 */
xwos_splk_rdex_unlock_irqsrs(&lock2, irq_array, flag2_array, num);
/* Exclusive read critical section 1 */
xwos_splk_rdex_unlock_irqsrs(&lock1, irq_array, flag1_array, num);
- Within the critical section, only preemption and specified interrupts are disabled. This can be understood as: at the level of thread and specified interrupt functions, operations within the critical section are atomic, and data within the critical section can only be accessed by thread and specified peripheral interrupt (IRQ) contexts.
- Specified interrupts cannot occur within the critical section, and scheduling also cannot occur. However, users still must not use CAPI that cause sleep and blocking inside the critical section.
- CAPI
xwos_splk_rdex_lock_irqssv(): Lock, disable preemption, save partial interrupt (IRQ) flags and disablexwos_splk_rdex_trylock_irqssv(): Try to lock, disable preemption, save partial interrupt (IRQ) flags and disablexwos_splk_rdex_unlock_irqsrs(): Unlock, enable preemption, restore partial interrupts (IRQ)
Protecting Read Critical Sections Between Thread and Bottom Half (BH) Contexts
- The critical section is safe for thread and bottom half (BH) contexts. The critical section can be nested.
xwos_splk_rdex_lock_bh(&lock1);
/* Exclusive read critical section 1 */
xwos_splk_rdex_lock_bh(&lock2);
/* Exclusive read critical section 2 */
xwos_splk_rdex_unlock_bh(&lock2);
/* Exclusive read critical section 1 */
xwos_splk_rdex_unlock_bh(&lock1);
- Within the critical section, only preemption and bottom half (BH) are disabled. This can be understood as: at the level of thread and bottom half, operations within the critical section are atomic, and data within the critical section can only be accessed by thread and bottom half (BH) contexts.
- Scheduling cannot occur within the critical section. However, users still must not use CAPI that cause sleep and blocking inside the critical section.
- CAPI
xwos_splk_rdex_lock_bh(): Lock, disable preemption and bottom half (BH)xwos_splk_rdex_trylock_bh(): Try to lock, disable preemption and bottom half (BH)xwos_splk_rdex_unlock_bh(): Unlock, enable preemption and bottom half (BH)
Read-Write Critical Section
Any read-write operation on the seqlock critical section is exclusive. Each time entering the critical section, the spinlock is first locked, then the sequence value is incremented, and it is incremented again when leaving the critical section. In other words, the sequence value increments twice, so the number of increments is always even.
Protecting Read-Write Critical Sections Between Thread Contexts
- The critical section is only safe for thread context. The critical section can be nested.
xwos_splk_wr_lock(&lock1);
/* Critical section 1 */
xwos_splk_wr_lock(&lock2);
/* Critical section 2 */
xwos_splk_wr_unlock(&lock2);
/* Critical section 1 */
xwos_splk_wr_unlock(&lock1);
- Within the critical section, only preemption is disabled. This can be understood as: at the thread level, operations within the critical section are atomic, and data within the critical section can only be accessed by thread context.
- Scheduling cannot occur within the critical section. Users must not use CAPI that cause sleep and blocking inside the critical section.
- CAPI
xwos_splk_wr_lock(): Lock, disable preemption, enter critical sectionxwos_splk_wr_trylock(): Try to lock, disable preemption, try to enter critical sectionxwos_splk_wr_unlock(): Unlock, enable preemption, exit critical section
Protecting Read-Write Critical Sections Between Any Context
- The critical section is safe for any context. However, the critical section must be single; nesting will cause the following error:
xwos_splk_wr_lock_cpuirq(&lock1);
/* Critical section 1 */
xwos_splk_wr_lock_cpuirq(&lock2);
/* Critical section 2 */
xwos_splk_wr_unlock_cpuirq(&lock2);
/* Critical section 1: ERROR! Interrupt is enabled */
xwos_splk_wr_unlock_cpuirq(&lock1);
- Within the critical section, not only preemption is disabled, but CPU interrupts are also disabled. This can be understood as: operations within the critical section are atomic, and data within the critical section can be accessed by any context.
- Interrupts cannot occur within the critical section, and scheduling is also impossible. However, users still must not use CAPI that cause sleep and blocking inside the critical section.
- CAPI
xwos_splk_wr_lock_cpuirq(): Lock, disable preemption and CPU interrupt (IRQ), enter critical sectionxwos_splk_wr_trylock_cpuirq(): Try to lock, disable preemption and CPU interrupt (IRQ), try to enter critical sectionxwos_splk_wr_unlock_cpuirq(): Unlock, enable preemption and CPU interrupt (IRQ), exit critical section
Protecting Nested Read-Write Critical Sections Between Any Context
- To solve the problem of nesting critical sections when interrupts are disabled, the CAPI for saving and restoring interrupt flags can be used.
xwos_splk_wr_lock_cpuirqsv(&lock1, &cpuirq1);
/* Critical section 1 */
xwos_splk_wr_lock_cpuirqsv(&lock2, &cpuirq2);
/* Critical section 2 */
xwos_splk_wr_unlock_cpuirqrs(&loc2, cpuirq2);
/* Critical section 1 */
xwos_splk_wr_unlock_cpuirqrs(&loc1, cpuirq1);
- Within the critical section, not only preemption is disabled, but CPU interrupts are also disabled. This can be understood as: operations within the critical section are atomic, and data within the critical section can only be accessed by thread context.
- Interrupts cannot occur within the critical section, and scheduling is also impossible. However, users still must not use CAPI that cause sleep and blocking inside the critical section.
- CAPI
xwos_splk_wr_lock_cpuirqsv(): Lock, disable preemption, save CPU interrupt (IRQ) flag and disable, enter write critical sectionxwos_splk_wr_trylock_cpuirqsv(): Try to lock, disable preemption, save CPU interrupt (IRQ) flag and disable, try to enter write critical sectionxwos_splk_wr_unlock_cpuirqrs(): Unlock, enable preemption, restore CPU interrupt (IRQ), exit write critical section
Protecting Read-Write Critical Sections Between Thread and Specified Interrupt (IRQ) Contexts
- The critical section is safe for thread and specified interrupt (IRQ) contexts. However, the critical section must be single.
xwos_splk_wr_lock_irqs(&lock, irq_array, num);
/* Critical section */
xwos_splk_wr_unlock_irqs(&lock, irq_array, num);
- Within the critical section, only preemption and specified interrupts are disabled. This can be understood as: at the level of thread and specified interrupt functions, operations within the critical section are atomic, and data within the critical section can only be accessed by thread and specified peripheral interrupt (IRQ) contexts.
- Specified interrupts cannot occur within the critical section, and scheduling also cannot occur. However, users still must not use CAPI that cause sleep and blocking inside the critical section.
- CAPI
xwos_splk_wr_lock_irqs(): Lock, disable preemption, disable partial interrupts (IRQ), enter write critical sectionxwos_splk_wr_trylock_irqs(): Try to lock, disable preemption, disable partial interrupts (IRQ), try to enter write critical sectionxwos_splk_wr_unlock_irqs(): Unlock, enable preemption, enable partial interrupts (IRQ), exit write critical section
Protecting Nested Read-Write Critical Sections Between Thread and Specified Interrupt (IRQ) Contexts
- The critical section is safe for thread and specified interrupt (IRQ) contexts. The critical section can be nested.
xwos_splk_wr_lock_irqssv(&lock1, irq_array, flag1_array, num);
/* Critical section 1 */
xwos_splk_wr_lock_irqssv(&lock2, irq_array, flag2_array, num);
/* Critical section 2 */
xwos_splk_wr_unlock_irqsrs(&lock2, irq_array, flag2_array, num);
/* Critical section 1 */
xwos_splk_wr_unlock_irqsrs(&lock1, irq_array, flag1_array, num);
- Within the critical section, only preemption and specified interrupts are disabled. This can be understood as: at the level of thread and specified interrupt functions, operations within the critical section are atomic, and data within the critical section can only be accessed by thread and specified peripheral interrupt (IRQ) contexts.
- Specified interrupts cannot occur within the critical section, and scheduling also cannot occur. However, users still must not use CAPI that cause sleep and blocking inside the critical section.
- CAPI
xwos_splk_wr_lock_irqssv(): Lock, disable preemption, save partial interrupt (IRQ) flags and disable, enter write critical sectionxwos_splk_wr_trylock_irqssv(): Try to lock, disable preemption, save partial interrupt (IRQ) flags and disable, try to enter write critical sectionxwos_splk_wr_unlock_irqsrs(): Unlock, enable preemption, restore partial interrupts (IRQ), exit write critical section
Protecting Read-Write Critical Sections Between Thread and Bottom Half (BH) Contexts
- The critical section is safe for thread and bottom half (BH) contexts. The critical section can be nested.
xwos_splk_wr_lock_bh(&lock);
/* Critical section 1 */
xwos_splk_wr_lock_bh(&lock2);
/* Critical section 1 */
xwos_splk_wr_unlock_bh(&lock2);
/* Critical section 1 */
xwos_splk_wr_unlock_bh(&lock1);
- Within the critical section, only preemption and bottom half (BH) are disabled. This can be understood as: at the level of thread and bottom half, operations within the critical section are atomic, and data within the critical section can only be accessed by thread and bottom half (BH) contexts.
- Scheduling cannot occur within the critical section. However, users still must not use CAPI that cause sleep and blocking inside the critical section.
- CAPI
xwos_splk_wr_lock_bh(): Lock, disable preemption and bottom half (BH), enter critical sectionxwos_splk_wr_trylock_bh(): Try to lock, disable preemption and bottom half (BH), try to enter critical sectionxwos_splk_wr_unlock_bh(): Unlock, enable preemption and bottom half (BH), exit critical section