Linux device driver Fifth: Concurrency and the state of drive

Source: Internet
Author: User
Tags mutex semaphore

Review

In the previous article, we introduced the Linux driver debugging method, which describes the concurrency and the state of the drive programming and how to handle concurrency and contention.

First, what is concurrency and normality? concurrency (concurrency) refers to multiple execution units being executed concurrently and concurrently. The concurrent execution unit's access to shared resources (hardware resources and global, static variables on the software) can easily lead to race (race conditions). Situations that can cause concurrency and normality are:

    • SMP (symmetric multi-processing), symmetric multi-processing structure. SMP is a tightly coupled, shared storage system model that features multiple CPUs using a common system bus and therefore access to common peripherals and storage.

    • Interrupt. Interrupts can interrupt a process that is executing, and a race can occur if an interrupt handler accesses a resource that the process is accessing. Interrupts can also be interrupted by new higher-priority interrupts, so multiple interrupts can also cause concurrency and cause race.

    • Preemption of kernel processes. Linux is preemptive, so a kernel process can be preempted by another high-priority kernel process. If two processes share resources together, there will be a state of being.

The above three cases only SMP is the true meaning of parallelism, while the others are macroscopic on the parallel, microscopic on the serial. However, it will lead to the competition problem of the critical share area. The way to solve the race problem is to guarantee mutually exclusive access to the shared resource, that is, when an execution unit accesses the shared resource, the other execution units are forbidden to access. So how does the Linux kernel get mutually exclusive access to shared resources? In Linux driver programming, the commonly used methods to solve concurrency and the state of being are the semaphores and mutexes, the completions mechanism, the spin lock (Spin lock), and some other implementations that do not use locks. described below.

Semaphores and mutual exclusion lock

The semaphore is actually an integer value, the core of which is a process that wants to enter the critical region will call P on the relevant semaphore; If the value of the semaphore is greater than 0, the value is decremented by 1 and the process continues. Conversely, if the semaphore value is 0 (or smaller), the process must wait until someone else releases the semaphore. Unlocking a semaphore is done by calling V; This function increments the semaphore value, and, if necessary, wakes up the waiting process. When the initial value of the semaphore is 1, it becomes a mutex.

Typical use of semaphores:

Declare semaphore struct semaphore sem;//initialize semaphore void Sema_init (struct semaphore *sem, int val)//Common below two forms # define INIT_MUTEX (SEM) se Ma_init (SEM, 1) #define INIT_MUTEX_LOCKED (SEM) sema_init (SEM, 0)//The following is a shortcut to initialize the semaphore, the most commonly used Declare_mutex (name)//Initialize the name of the letter The number is 1declare_mutex_locked (name)//initialization semaphore is 0//common Operation Declare_mutex (Mount_sem);d Own (&MOUNT_SEM);    Get the semaphore ... critical section//Critical area ... up (&MOUNT_SEM); Release semaphore

The usual down operations are also

Similar to Down (), because down () the process of entering hibernation cannot be interrupted by the signal, and because the process of down_interruptible () into hibernation can be interrupted by a signal, the//signal will also cause the function to return, when the return value is not 0int Down_ interruptible (struct semaphore *sem);//attempts to obtain a semaphore, if obtained immediately, it obtains the semaphore and returns 0, otherwise, returns 0. It does not cause the caller to sleep and can be used in the interrupt context using int Down_trylock (struct semaphore *sem);
Completions mechanism

The completion amount (completion) provides a better synchronization mechanism than the semaphore, which is used for one execution unit to wait for another execution unit to finish doing something.

</pre></div><div><pre name= "code" class= "CPP" >//define the complete amount of the struct completion my_completion; Initialize Completioninit_completion (&my_completion); Define and Initialize shortcuts: declear_completion (my_completion); Wait for a completion to be awakened void wait_for_completion (struct completion *c); Wake-Up completion amount void Cmplete (struct completion *c); void Cmplete_all (struct completion *c);
Spin lock

If a process accesses a critical resource and the test lock is idle, the process obtains the lock and continues execution, and if the test results indicate that the lock is occupied, the process repeats the "test and set" operation in a small loop, making the so-called "spin" and waiting for the spin lock holder to release the lock. A spin lock is similar to a mutex, but a mutex cannot be used in code that might sleep, and a spin lock can be used in a sleep code, and a typical application can be used in an interrupt handler function. Spin Lock related operations:

Define spin lock spinlock_t spin; Initialize the spin lock spin_lock_init (lock);  To obtain a spin lock: If the lock is acquired immediately, it obtains the lock and returns, otherwise, spins until the lock holder releases Spin_lock (lock);  Try to get a spin lock: If you can get the lock immediately, it gets and returns true, otherwise immediately returns false, no longer spin spin_trylock (lock);    Release spin lock: Pair with Spin_lock (lock) and Spin_trylock (lock) using Spin_unlock (lock); Spin lock use://define a spin lock spinlock_t lock;spin_lock_init (&lock);  Spin_lock (&lock);  Get spin lock, protect critical zone ...//critical zone spin_unlock (); Unlock


The kernel preemption will be banned during spin lock hold. The spin lock ensures that the critical section is not disturbed by other CPUs and the preemption process within the CPU, but the code path to the lock can also be affected by interrupts and the bottom half (BH) when the critical section is being executed. To prevent this effect, a spin lock derivation is required:

SPIN_LOCK_IRQ () = Spin_lock () + local_irq_disable () spin_unlock_irq () = Spin_unlock () + local_irq_enable () spin_lock_ Irqsave () = Spin_lock () + Local_irq_save () spin_unlock_irqrestore () = Spin_unlock () + Local_irq_restore () spin_lock_bh () = Spin_lock () + local_bh_disable () spin_unlock_bh () = Spin_unlock () + local_bh_enable ()
A few other options

These are the locking mechanisms that are often used in Linux driver programming, and some of the other implementations of the kernel are described below.

No lock algorithm

Sometimes, you can re-build your algorithm to completely avoid the need for lock-up. Many reader/writer situations-if there is only one writer-are often able to work in this way. If the writer is careful to make the data structure, as the reader sees, is consistent, it is possible to create an unlocked data architecture. In the Linux kernel there is a universal lock-free ring buffer implementation, the specific content reference <linux/kfifo.h>.

Atomic variable and bit manipulation

Atomic manipulation refers to operations that are not interrupted by other code paths during execution. Atomic variables and bitwise operations are atomic operations. The following is a description of how it is related.

  Set the value of the atomic variable Void atomic_set (atomic_t *v, int i);  //  Set the value of the atomic variable to iatomic_t  v = atomic_init (0);  //  defines the atomic variable V and initializes the 0 //  to get the value of the atomic variable Atomic_read (atomic_ T&NBSP;*V);  //  Returns the value of the atomic variable  //  the atomic variable Plus/minus Void atomic_add (int i, atomic_t &NBSP;*V);  //  atomic variable plus ivoid atomic_sub (int i, atomic_t *v);   //   Atom variable i //  atomic variable self-increment/void atomic_inc (atomic_t *v);  //  atomic variable 1void  atomic_dec (atomic_t *v);  //  atomic variable reduces 1 //  operation and tests: after self-increment, decrement, and decrement of atomic variables (no addition) Tests whether it is 0, 0 returns True, otherwise returns Falseint atomic_inc_and_test (ATOMIC_T&NBSP;*V); Int atomic_dec_and_test (atomic_ T&NBSP;*V); Int atomic_sub_and_test (INT&NBSP;I,&NBSP;ATOMIC_T&NBSP;*V); //  operation and return:  to the atomic variable to add/ Subtract and increment/decrement operations, and return a new value Int atomic_add_return (INT&NBSP;I,&NBSP;ATOMIC_T&NBSP;*V); Int atomic_sub_return (int &NBSP;I,&NBSP;ATOMIC_T&NBSP;*V); Int atomic_inc_return (atomic_t *v); Int atomic_dec_return (atomic_t *v);   bit atomic operation:/ /  Set bit void set_bit (NR,&NBSP;VOID&NBSP;*ADDR);  //  set the addr address of the NR bit, will write the bit 1 //  Clear bit void clear_bit (NR,&NBSP;VOID&NBSP;*ADDR);  //  clears the NR bit of the addr address, the bit write 0 //  changes bit void  change_bit (NR,&NBSP;VOID&NBSP;*ADDR);  //  to the addr address of the NR bit of the counter  //  test bit test_bit (NR, &NBSP;VOID&NBSP;*ADDR); //  returns the Nr bit of the addr address  //  test and operation: equivalent to execute test_bit (NR,&NBSP;VOID&NBSP;*ADDR) After the execution of Xxx_bit (NR,&NBSP;VOID&NBSP;*ADDR) int test_and_set_bit (NR,&NBSP;VOID&NBSP;*ADDR); int test_and_ Clear_bit (NR,&NBSP;VOID&NBSP;*ADDR); Int test_and_change_bit (NR,&NBSP;VOID&NBSP;*ADDR);
Seqlock (Sequential Lock)

With the Seqlock lock, the read execution unit is not blocked by the write execution unit, that is, the read execution unit can continue to read while the write execution unit is writing to the shared resource protected by the Seqlock lock, without having to wait for the write execution unit to complete the write operation. The Write execution unit also does not need to wait for all read execution units to complete the read operation before the write operation. Write execution units are still mutually exclusive. If a write operation occurs during the read operation, the data must be read again. The Seqlock lock must require that the shared resource being protected does not contain pointers.

  Get Order Lock Void write_seqlock (SEQLOCK_T&NBSP;*SL); Int write_tryseqlock (SEQLOCK_T&NBSP;*SL); Write_ Seqlock_irqsave (lock, flags) WRITE_SEQLOCK_IRQ (lock) WRITE_SEQLOCK_BH ()  //  release sequence lock Void write_ Sequnlock (SEQLOCK_T&NBSP;*SL); Write_sequnlock_irqrestore (lock, flags) WRITE_SEQUNLOCK_IRQ (lock) Write_ SEQUNLOCK_BH ()  //  write execution units use sequential lock mode as follows: Write_seqlock (&AMP;SEQLOCK_A);...  //  write operation code block Write_ Sequnlock (&AMP;SEQLOCK_A);   read Execution unit operation://  Read start: Return order Lock SL Current order number Unsigned read_seqbegin (const  SEQLOCK_T&NBSP;*SL) Read_seqbegin_irqsave (lock, flags)  //  reread: the read execution unit needs to call this function to check after it accesses the shared resource protected by the sequential lock SL. Whether there is a write operation during read access. If there is write operation, reread Int read_seqretry (CONST&NBSP;SEQLOCK_T&NBSP;*SL,&NBSP;UNSIGNED&NBSP;IV); Read_seqretry_irqrestore ( lock, iv, flags)  //  read execution unit use sequential lock mode as follows: Do{    seqnum = read_ Seqbegin (&AMP;SEQLOCK_A);    //  read operation code block       ...} while (Read_seqretry (&AMp;seqlock_a, seqnum)); 
Read-copy-update (RCU)

Read-copy-update (RCU) is a high-level mutex method that can be very efficient at the right time. RCU can be seen as a high-performance version of a read-write lock, compared to a read-write lock, the RCU advantage is that it allows multiple read execution units to access the protected data simultaneously, while allowing multiple read execution units and multiple write execution units to access the protected data simultaneously. However, RCU cannot replace read-write locks, because the performance improvement of read execution units cannot compensate for the loss caused by write execution units if the write is relatively long. Because the usual application is less, so do not say long.

Summary

The above is the Linux driver programming involved in the concurrency and race of the content, below to do a simple summary.

Now the processor is basically SMP type, and in the new kernel version, basically support preemptive operation, many programs in Linux are reentrant, to protect this data, you have to use a different locking mechanism. The basic operation of the lock mechanism is actually similar, declaring variables, locking, executing the critical section code, and then unlocking. The difference is that you can re-enter the restrictions are different, some can be unrestricted re-entry, some only allow the operation of heterogeneous operations, and some are not allowed to re-enter the operation, some can be used in the sleep code, and some can not be used in the sleep code. In consideration of the use of different locking mechanisms, but also to consider the efficiency of CPU processing, for different code lengths, different code execution time, choose a good lock on the good use of the CPU has a great impact, otherwise it will cause waste.

Before the Linux device driver Third: Write a simple character device driver introduced in the simple character device driver, the next article will introduce some character device drivers in the advanced operation.

The first time to get blog update reminders, as well as more technical information sharing, welcome to the personal public platform: Programmer Interaction Alliance (coder_online), sweep the QR code below or search number Coder_online can pay attention to, read Android, A variety of popular technical articles such as Chrome.



Linux device driver Fifth: Concurrency and the state of drive

Related Article

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.