Linux Device Driver Article 5: concurrency and final state in the driver, linux Article 5

Source: Internet
Author: User

Linux Device Driver Article 5: concurrency and final state in the driver, linux Article 5
Summary

In the previous article, I introduced the debugging method of the linux driver. This article describes the concurrency and status encountered in driver programming and how to handle concurrency and competition.

What is concurrency and final state first? Concurrency indicates that multiple execution units are executed simultaneously and concurrently. Concurrent Execution Unit access to shared resources (global and static variables in hardware resources and software) can easily lead to race conditions ). Concurrency and final state may be caused:

  • SMP (Symmetric Ric Multi-Processing), Symmetric Multi-Processing structure. SMP is a tightly coupled, shared storage system model. It features that multiple CPUs use the common system bus, so they can access the common peripherals and memory.
  • Interrupted. An interrupt can interrupt a running process. If the interrupt handler accesses the resources being accessed by the process, the competition also occurs. Interruptions may also be interrupted by new interruptions with higher priority. Therefore, multiple interruptions may also cause concurrency and lead to competition.
  • Kernel process preemption. Linux can be preemptible, so one kernel process may be preemptible by another high-priority kernel process. If the two processes access the shared resources together, the actual state will appear.

In the preceding three cases, only SMP is true parallelism, while others are macro parallelism and micro-serialization. However, it will lead to competition in critical sharing areas. The solution to the competition mode is to ensure mutex access to shared resources, that is, when an execution unit accesses shared resources, other execution units are forbidden to access. How can we achieve mutex access to shared resources in the Linux kernel? In linux driver programming, common methods to solve concurrency and final state include semaphores and mutex locks, Completions mechanisms, spin locks ), and some other implementation methods that do not use locks. The following is a one-to-one introduction.

Semaphores and mutex semaphores are actually an integer value. At the core of semaphores, a process that wants to enter the critical section calls P on the related semaphores. If the semaphores are greater than zero, this value decreases 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. unlock a semaphore by calling V. This function increments the semaphore value and, if necessary, wakes up the waiting process. When the initial semaphore value is 1, it becomes a mutex lock. Typical use of semaphores:
// Declare the semaphore struct semaphore sem; // initialize the semaphore void sema_init (struct semaphore * sem, int val) // The following two forms are commonly used # define init_MUTEX (sem) sema_init (sem, 1) # define init_MUTEX_LOCKED (sem) sema_init (sem, 0) // The following is the method to initialize the semaphore. The most common DECLARE_MUTEX (name) // The semaphores whose names are initialized are 1DECLARE_MUTEX_LOCKED (name) // The semaphores whose names are initialized are 0 // Common Operations DECLARE_MUTEX (mount_sem); down (& mount_sem); // obtain semaphores... critical section // critical section... up (& mount_sem); // release the semaphore
Common down operations include
// Similar to down (), processes that enter sleep cannot be interrupted by signals because of down (), while processes that enter sleep due to down_interruptible () can be interrupted by signals, // The signal will also cause this function to return, and the returned value is not 0int down_interruptible (struct semaphore * sem); // try to obtain the semaphores sem. If it is obtained immediately, it obtains the semaphore and returns 0. Otherwise, it returns non-0. it does not cause the caller to sleep. int down_trylock (struct semaphore * sem) can be used in the interrupt context );
The Completions mechanism completion provides a better synchronization mechanism than semaphores, which is used by one execution unit to wait for another execution unit to finish something.
// Define the completion quantity struct completion my_completion; // initialize completioninit_completion (& my_completion); // define and initialize the shortcut: DECLEAR_COMPLETION (my_completion ); // wait for a completion to be awakened void wait_for_completion (struct completion * c); // wake up the completion void cmplete (struct completion * c); void cmplete_all (struct completion * c );

 

If a process needs to access critical resources and test the idle lock, the process obtains the lock and continues to execute the lock. If the test result shows that the lock is occupied, the process repeats the "test and set" operation in a small loop to perform the so-called "Spin", waiting for the spin lock holder to release the lock. The spin lock is similar to the mutex lock, but the mutex lock cannot be used in code that may sleep, while the spin lock can be used in sleep-able code. A typical application can be used in interrupt processing functions. Operations related to spin locks:
// Define the spin lock spinlock_t spin; // initialize the spin lock spin_lock_init (lock); // obtain the spin lock: If the lock can be obtained immediately, it acquires the lock and returns it; otherwise, spin, until the lock holder releases the spin_lock (lock); // attempts to obtain the spin lock: If the lock can be obtained immediately, it obtains and returns the true result; otherwise, false is returned immediately, no longer spin the spin_trylock (lock); // release the spin lock: Use the spin_unlock (lock) pair with the spin_lock (lock) and the spin_trylock (lock); Use of the spin lock: // define a spin lock_t lock; spin_lock_init (& lock); spin_lock (& lock); // obtain the spin lock to protect the critical section... // key section spin_unlock (); // unlock

 

Kernel preemption during the spin lock hold will be disabled. The spin lock ensures that the critical zone is not disturbed by other CPU and the preemption process in the CPU. However, the code path of the lock may be affected by the interruption and the bottom half (BH) when executing the critical zone. To prevent this impact, spin lock derivatives are 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()

 

Other options are commonly used in linux driver programming. The following describes some other implementations in the kernel. Without locking algorithms, You can recreate your algorithms to completely avoid the need for locking .. Many readers/writers-if there is only one writer-can often work in this way .. If the writer carefully makes the data structure consistent with what the reader sees, it is possible to create an unlocked data structure. There is a general lockless circular buffer implementation in the linux kernel. For more information, see <linux/kfifo. h>. Atomic variables and bit operations atomic operations refer to operations that are not interrupted by other code paths during execution. Atomic variables and bit operations are atomic operations. The following describes the related operations.
// 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 ); // define the atomic variable v and initialize it to 0 // obtain the atomic variable value atomic_read (atomic_t * v ); // return the value of the atomic variable // Add/subtract void atomic_add (int I, atomic_t * v) for the atomic variable; // Add ivoid atomic_sub (int I, atomic_t * v); // atomic variable minus I // atomic variable auto-increment/auto-increment void atomic_inc (atomic_t * v ); // Add 1 void atomic_dec (atomic_t * v) to the atomic variable; // reduce the atomic variable by 1 // perform the following operations and test: perform auto-increment, auto-Subtract, and subtract operations on atomic variables (not added) to test whether the value is 0. If the value is 0, true is returned. Otherwise, falseint atomic_inc_and_test (atomic_t * v) is returned ); int atomic_dec_and_test (atomic_t * v); int atomic_sub_and_test (int I, atomic_t * v); // operation and return: Add/subtract and auto increment/decrease atomic variables, and return the new value 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 operation: // sets the bit void set_bit (nr, void * addr); // sets the nth nr bit of the addr address, about bit write 1 // clear bit void clear_bit (nr, void * addr); // clear the nr bit of the addr address, 0 // change the bit void change_bit (nr, void * addr); // reverse the nr bit of the addr address // test_bit (nr, void * addr); // return the nr bit of the addr address // test and perform the operation: equivalent to executing test_bit (nr, void * addr) and then executing xxx_bit (nr, void * addr) int test_and_set_bit (nr, void * addr); int test_and_clear_bit (nr, void * addr); int test_and_change_bit (nr, void * addr );

 

The seqlock (sequential lock) uses the seqlock. The read execution unit is not blocked by the write execution unit, that is, the read execution unit can continue to read the shared resources protected by the seqlock when the write Execution Unit writes, without waiting for the write Execution Unit to complete the write operation, the write execution unit does not need to wait for all read execution units to complete the read operation. The write execution units are mutually exclusive. If a write operation occurs during the read operation, you must re-read the data. The seqlock must require that the protected shared resources do not contain pointers.
// Obtain the sequence lock void write_seqlock (seqlock_t * sl); int write_tryseqlock (seqlock_t * sl); trim (lock, flags) write_seqlock_irq (lock) write_seqlock_bh () // release the sequence lock void write_sequnlock (seqlock_t * sl); unlock (lock, flags) write_sequnlock_irq (lock) write_sequnlock_bh () // The write Execution Unit uses the sequence lock mode as follows: write_seqlock (& seqlock_a );... // write operation code block write_sequnlock (& seqlock_a); read execution unit operation: // read start: return sequence lock sl current sequence number unsi Gned read_seqbegin (const seqlock_t * sl); read_seqbegin_irqsave (lock, flags) // re-read: the read Execution Unit needs to call this function after accessing the shared resources protected by the sequential lock sl to check whether there are write operations during the read access. If there is a write operation, re-read int read_seqretry (const seqlock_t * sl, unsigned iv); read_seqretry_irqrestore (lock, iv, flags) // The read Execution Unit uses the sequence lock mode as follows: do {seqnum = read_seqbegin (& seqlock_a); // read operation code block ...} while (read_seqretry (& seqlock_a, seqnum ));

 

Read-copy-Update (RCU) read-copy-Update (RCU) is an advanced mutex method, which can achieve high efficiency when appropriate. RCU can be viewed as a high-performance version of the read/write lock. Compared with the read/write lock, RCU allows multiple read execution units to access protected data at the same time, it also allows multiple read and write execution units to simultaneously access protected data. However, RCU cannot replace the read/write lock, because if there are too many writes, the performance improvement of the read execution unit cannot compensate for the loss caused by the write execution unit. Because there are few applications at ordinary times, do not say much. The above is the concurrency and competition content involved in linux driver programming. Below is a simple summary. The current processors are basically SMP type, and in the new kernel version, they basically support preemptible operations. In linux, many programs can be reentrant, to protect the data, you must use different lock mechanisms. In fact, the basic operation process of the lock mechanism is similar. Declare variables, lock, execute critical code, and then unlock. The difference is that there are different restrictions on allowed re-import, some of which allow unlimited re-import, some allow only the re-import of heterogeneous operations, and some do not allow re-import operations, some can be used in sleep code, and some cannot be used in sleep code. When considering the use of different lock mechanisms, we also need to consider the CPU processing efficiency. For different code lengths, different code execution times, selecting a good lock has a great impact on the good use of the CPU, otherwise it will cause a waste. Previously, I used linux Device Drivers in Article 3: writing a simple character device driver to introduce simple character device drivers. The next article will introduce some advanced operations in character device drivers.

Get a blog update reminder and share more technical information as soon as possible. Welcome to the personal public platform: coder_online, scan the QR code below or search for coder_online, read popular technical articles such as android and chrome.

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.