concurrency control of Linux device drivers

Source: Internet
Author: User

One of the issues that must be addressed in Linux device drivers is the concurrent access of multiple processes to shared resources, and concurrent access leads to race.

Interrupt masking, atomic manipulation, spin locks, and semaphores are all mechanisms for solving concurrency problems. Interrupt masks are rarely used alone, atomic operations are only for integers, so spin locks and semaphores are most widely used.

A spin lock causes a dead loop that does not allow blocking during locking, so the critical section of the lock is required to be small. The signal volume allows the critical section to be blocked, which can be applied to large critical areas.

Read-Write spin locks and read and write semaphores are relaxed the conditions of spin locks and semaphores, which allow multiple execution units to read concurrently on shared resources.

Interrupt Masking

The area of code that accesses a shared resource is called a critical section ( critical sections), and A simple and easy way to avoid race in a single CPU range is to mask the interruption of the system before entering the critical section. Interrupt masking eliminates the concurrency between interrupts and processes, and the concurrency between kernel preemption processes is avoided due to the fact that operations such as the Linux kernel's process scheduling are dependent on interrupts.

Local_irq_disable (); /* Shielded Interrupt * /

...

Critical section/* critical area * /

...

Local_irq_enable (); /* Open Interrupt * /

but because Many important operations, such as asynchronous I/O, process scheduling, and so on, are dependent on interrupts, and long-time shielding interrupts are dangerous, and interrupt masking is only valid for interrupts within this CPU. Therefore, it is not possible to solve The competing state caused by SMP multi- CPU . Direct use is not recommended in practical applications and is suitable for use in conjunction with spin locks below.

Atomic operation

The Linux kernel provides a series of functions to implement atomic operations in the kernel, which are divided into two categories, which are atomic operations on bits and integer variables. What they have in common is that in any case the operation is atomic and the kernel code can safely invoke them without being interrupted.

Atomic operation of Integral type

Set the value of an atomic variable

#include <asm/atomic.h>

void Atomic_set (atomic_t *v, int i); /* Set the value of the atomic variable to i * /

atomic_t v = atomic_init (0); /* define atomic variable v and initialize to 0 * /

Get the value of an atomic variable

int Atomic_read (atomic_t *v); /* Returns the value of the atomic variable * /

atom variable plus / minus

void Atomic_add (int i, atomic_t *v); /* atom variable increase I/*

void Atomic_sub (int i, atomic_t *v); /* Atomic variable reduction I/*

void Atomic_inc (atomic_t *v); /* atom variable self-increment 1 * /

void Atomic_dec (atomic_t *v); /* atom variable auto minus 1 * /

/* Operation results ==0, return true * /

int atomic_inc_and_test (atomic_t *v);

int atomic_dec_and_test (atomic_t *v);

int atomic_sub_and_test (int i, atomic_t *v);

/* Operation results <0, return true * /

int atomic_add_negative (int i, atomic_t *v);

/* operation and return results * /

int Atomic_add_return (int i, atomic_t *v);

int Atomic_sub_return (int i, atomic_t *v);

int Atomic_inc_return (atomic_t *v);

int Atomic_dec_return (atomic_t *v);

Bit atomic manipulation

Bit atoms operate fairly quickly, generally requiring only one machine instruction, without shutting down interrupts.

Set/clear/toggle

#include <asm/bitops.h>

/* Change the nr bit of the data referred to by the pointer addr * /

void Set_bit (nr, void *addr);

void Clear_bit (nr, void *addr);

void Change_bit (nr, void *addr);

Test

int Test_bit (nr, void *addr); /* return to nr bit * /

Test and manipulate

/* operate the nr bit and return the value before the Operation * /

int Test_and_set_bit (nr, void *addr);

int Test_and_clear_bit (nr, void *addr);

int Test_and_change_bit (nr, void *addr);

Spin lock (spinlock)

Spin Lock (spinlock) is a typical means of mutually exclusive access to critical resources, and its name is derived from the way it works. In order to obtain a spin lock, the code running on a CPU needs to perform an atomic operation that tests and sets ( test-and-set) A memory variable, because it is an atomic operation, Therefore, it is not possible for other execution units to access this memory variable until the operation is complete. If the test results indicate that the lock has been idle, the program obtains the spin lock and continues execution, and if the test results indicate that the lock is still occupied, the program will repeat the "test and set" operation in a small loop, that is, the so-called "spin", in Layman's words "spinning in place." When the owner of the spin lock releases the spin lock by resetting the variable, a waiting test and set operation reports to its caller that the lock has been released.

Basic

define / Initialize

#include <linux/spinlock.h>

/* static initialization * /

spinlock_t my_lock = spin_lock_unlocked;

/* Dynamic initialization * /

void Spin_lock_init (spinlock_t *lock);

get / release

/* basic Operation * /

void Spin_lock (spinlock_t *lock);

void Spin_unlock (spinlock_t *lock);

/* save interrupt status and close = = Spin_lock () + local_irq_save () */

void Spin_lock_irqsave (spinlock_t *lock, unsigned long flags);

void Spin_unlock_irqsave (spinlock_t *lock, unsigned long flags);

/* ignore interrupt status before Operation * /

void Spin_lock_irq (spinlock_t *lock);

void Spin_unlock_irq (spinlock_t *lock);

/* Turn off the bottom of the interrupt (i.e. turn off the software interrupt, turn on the hardware interrupt, as explained in the following interrupt) * /

void Spin_lock_bh (spinlock_t *lock);

void Spin_unlock_bh (spinlock_t *lock);

/* non-blocking fetch, successful return of non 0 * /

int Spin_trylock (spinlock_t *lock);

int Spin_trylock_bh (spinlock_t *lock);

Reader/writer spinlocks

smaller particle size, can be more Reader reads at the same time, but Writer can only be alone, and can not read and write at the same time, for writing very rarely read a lot of situations.

define / Initialize

rwlock_t my_rwlock = rw_lock_unlocked; /* static initialization * /

rwlock_t My_rwlock;

Rwlock_init (&my_rwlock); /* Dynamic initialization * /

Read

void Read_lock (rwlock_t *lock);

void Read_lock_irqsave (rwlock_t *lock, unsigned long flags);

void Read_lock_irq (rwlock_t *lock);

void Read_lock_bh (rwlock_t *lock);

void Read_unlock (rwlock_t *lock);

void Read_unlock_irqrestore (rwlock_t *lock, unsigned long flags);

void Read_unlock_irq (rwlock_t *lock);

void Read_unlock_bh (rwlock_t *lock);

Write

void Write_lock (rwlock_t *lock);

void Write_lock_irqsave (rwlock_t *lock, unsigned long flags);

void Write_lock_irq (rwlock_t *lock);

void Write_lock_bh (rwlock_t *lock);

void Write_unlock (rwlock_t *lock);

void Write_unlock_irqrestore (rwlock_t *lock, unsigned long flags);

void Write_unlock_irq (rwlock_t *lock);

void Write_unlock_bh (rwlock_t *lock);

Seqlock

sequential Lock (seqlock) is an optimization of read-write lock, using the stress mechanism, read and write do not block each other.

define / Initialize

#include <linux/seqlock.h>

seqlock_t lock1 = seqlock_unlocked; /* static * /

seqlock_t Lock2;

Seqlock_init (&LOCK2); /* Dynamic * /

Read

/* Get a sequential number before reading it and compare it to the current sequence number, or reread if inconsistent.

unsigned int seq;

do {

Seq = Read_seqbegin (&the_lock);

/* Do-need to do * *

} while Read_seqretry (&the_lock, seq);

/* If this lock may appear in the interrupt program, then the off-interrupt version should be used here * /

unsigned int read_seqbegin_irqsave (seqlock_t *lock,unsigned long flags);

int Read_seqretry_irqrestore (seqlock_t *lock, unsigned int seq,unsigned long flags);

Write

void Write_seqlock (seqlock_t *lock);

void Write_seqlock_irqsave (seqlock_t *lock, unsigned long flags);

void Write_seqlock_irq (seqlock_t *lock);

void Write_seqlock_bh (seqlock_t *lock);

int Write_tryseqlock (seqlock_t *lock);

void Write_sequnlock (seqlock_t *lock);

void Write_sequnlock_irqrestore (seqlock_t *lock, unsigned long flags);

void Write_sequnlock_irq (seqlock_t *lock);

void Write_sequnlock_bh (seqlock_t *lock);

RCU (Read-copy-update)

for being RCU protected shared data structures, read execution units do not need to acquire any locks to access it, so the read execution unit does not have any synchronization overhead. A write execution unit that uses RCU will need to copy a copy before accessing it, then modify the copy, and finally use a callback mechanism to point the pointer to the original data at the appropriate time to the new modified data. when the CPU exits the operation of the shared data. The synchronization overhead of the write execution unit depends on the synchronization mechanism between the write execution units used. RCU is seldom used in the drive and is not detailed here.

Precautions

The spin lock is actually a busy lock, and when the lock is not available, The CPU keeps looping through "testing and setting" the lock until it is available, and the CPU waits for no useful work while waiting for a spin lock. Therefore, it is reasonable to use a spin lock only when the lock is occupied for a very short time. When the critical section is large, or there is a shared device, it takes a long time to occupy the lock, and the use of a spin lock can degrade the system's performance.

A spin lock can cause a system to deadlock. The most common case of this problem is recursive use of a spin lock, that is, if a CPU that already has a spin lock wants to acquire the spin lock for the second time, the CPU will deadlock.

a function that can cause hibernation to occur during a spin lock lock cannot be called during a process dispatch. If the process gets a spin lock and then blocks, such as calling Copy_from_user (), copy_to_user (), kmalloc () and the Msleep () such as functions, can cause the kernel to crash.

Signal Volume semaphore

It is used in a similar way to a spin lock, but when the semaphore is not acquired, the process does not spin in place and goes into a dormant wait state.

define / Initialize

#include <asm/semaphore.h>

struct semaphore sem;

void Sema_init (struct semaphore *sem, int val);

/* usually we put val value at 1, that is, using mutex mode * /

Declare_mutex (name);

declare_mutex_locked (name);

void Init_mutex (struct semaphore *sem);

void init_mutex_locked (struct semaphore *sem);

Get the signal volume

void down (struct semaphore * sem); /* The semaphore minus 1 will cause sleep and therefore cannot be used in the interrupt context * /

int down_interruptible (struct semaphore * sem); /* Unlike down , processes that go into sleep can be interrupted to return non- 0 * /

int Down_trylock (struct semaphore * sem); /* non-blocking version, get return 0, does not cause sleep, can be used in interrupt context * /

Release semaphore

void up (struct semaphore * sem);

Reader/writer semaphores

The relationship between read and write semaphores and semaphores is similar to read-write spin locks and spin locks, which can cause process blocking, but it allows N Read execution units access shared resources at the same time, with a maximum of 1 write execution units. Therefore, the read and write signal is a relatively relaxed condition of the granularity slightly larger than the number of mutual exclusion mechanism.

define / Initialize

#include <linux/rwsem.h>

struct Rw_semaphore;

void Init_rwsem (struct rw_semaphore *sem);

Read

void Down_read (struct rw_semaphore *sem);

int Down_read_trylock (struct rw_semaphore *sem);

void Up_read (struct rw_semaphore *sem);

Write

/* write higher than read priority, write all reading can only wait * /

void Down_write (struct rw_semaphore *sem);

int Down_write_trylock (struct rw_semaphore *sem);

void Up_write (struct rw_semaphore *sem);

amount of Completion completion

Lightweight, used for one execution unit to wait for another execution unit to finish doing something.

define / Initialize

#include <linux/completion.h>

/* static * /

Declare_completion (name);

/* Dynamic * /

struct completion my_completion;

Init_completion (struct completion *c);

Init_completion (struct completion c); /* Reinitialize already defined and used completion * /

Wait for completion

void Wait_for_completion (struct completion *c);

Complete signal

void complete (struct completion *c); /* Wake up 1 * /

void Complete_all (struct completion *c); /* Wake up all waiter * /

void Complete_and_exit (struct completion *c, long retval); /* Call complete () and exit (retval) */

Source: Blog Park

concurrency control of Linux device drivers

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.