Concurrency and competition control for Linux-driven Learning

Source: Internet
Author: User
Document directory
  • 1.1 Definition
  • 1.2 operation functions
  • 1.3 Reader/writer flag

Semaphore and mutex for Linux driver Learning

In the driver, when multiple threads simultaneously access the same resource (global variables or hardware resources), it may lead to a race. Therefore, we must control the concurrency of shared resources. The most common methods to solve Concurrency Control in Linux kernel are spin locks and semaphores.

Semaphores

In 1965, Dijkstra, a Dutch scholar, proposed to use the semaphore mechanism to solve the process synchronization problem. The semaphore was officially an effective process synchronization tool, currently, semaphores are widely used in single-processor, multi-processor systems, and computer networks.
Semaphore s is an integer. When S is greater than or equal to zero, the number of resource entities available for concurrent processes in the table. When S is less than zero, it indicates the number of processes waiting to use the critical section.
Dijkstra also proposes PV primitives for semaphore operations.
The P primitive operation is as follows:
(1) s minus 1;
(2) If s minus 1 is still greater than or equal to zero, the process continues to run;
(3) If s minus 1 is less than zero, the process is blocked and enters the queue corresponding to the signal, and then forwards the process to process scheduling.
The V primitive operation is as follows:
(1) s plus 1;
(2) If the sum result is greater than zero, the process continues to execute;
(3) If the sum result is less than or equal to zero, a waiting process is awakened from the waiting queue of the signal, and then the original process is returned for further execution or scheduling.
PV operations can be performed only once for each process and must be used in pairs. Interruption is not allowed during PV primitive execution.
The PV primitive processes the synchronization and mutex between processes through the operation semaphores. Its core is to process an inseparable program. The signal value 0 indicates that no idle resources are available, while the positive value indicates the number of idle resources. The absolute value of the negative value indicates the number of processes waiting to enter the critical section. The semaphore is maintained by the operating system, user processes can only be accessed through initialization and two standard primitives (P and V.

1.1 Definition

/* Initialize at definition */declare_mutex (name); // The initial signal value is 1declare_mutex_locked (name ); // The initial signal value is 0/* first defined and then initialized */struct semaphore SEM; // define void init_mutex (struct semaphore * SEM ); // The initial signal value is 1 void init_mutex_locked (struct semaphore * SEM); // The initial signal value is 0 void sema_init (struct semaphore * SEM, int Val ); // The initial signal value is Val.

1.2 operation functions

The P function is void down (struct semaphore * SEM)./* When the signal value is less than or equal to 0, it causes sleep and cannot be killed, */INT down_interruptible (struct semaphore * SEM);/* is similar to down, but the operation is interrupted, this function returns a non-zero value, the caller does not own the semaphore. After the return value is returned, you must check the return value to make a corresponding response. */INT down_trylock (struct semaphore * SEM);/* never sleep. If the semaphore is not available during call, a non-zero value is returned. */V function: void up (struct semaphore * SEM);/* any thread that obtains the semaphore must call it once (and only once) call up to release the semaphore */
1.3 Reader/writer flag

In some cases, multiple threads are allowed to read only one thread, because the write will change the resource status, but the read will not affect each other. For example, if three people read the same book, they can read it together. A wants to flip the page soon. A must wait for B and C to finish reading it before changing the resource status, if another person D wants to see it in the process of a and so on, if I want to show it to D, isn't it necessary to wait for D to finish reading this page? A is more aggressive and cannot allow D to read it, if no one wants to flip the page, other people may want to join the page.

Initialization: void init_rwsem (struct rw_semaphore * SEM); read-only access interface for protected resources: void down_read (struct rw_semaphore * SEM ); // The Calling process may be set to an uninterrupted sleep int down_read_trylock (struct rw_semaphore * SEM); // If read access is unavailable, it will not wait; if it is granted access, it will return non-zero, otherwise it is 0 void up_read (struct rw_semaphore * SEM); // release read access write interface: void down_write (struct rw_semaphore * SEM ); // The Calling process may be set to an uninterrupted sleep. When multiple threads request a write request, only one write is allowed, and other threads wait for him to finish writing. Only when all read threads release read access will the system wake up. The first reader is not allowed to join the waiting process. Int down_write_trylock (struct rw_semaphore * SEM); // If write access is unavailable, it will not wait. If it is allowed to write access, it will return a non-zero value, otherwise it is 0 void up_write (struct rw_semaphore * SEM); // release write access void downgrade_write (struct rw_semaphore * SEM ); // The writer becomes the reader, which is equivalent to up_write. After the writer is released, it becomes the reader down_read. In the end, you need to use up_read to release the writer.

Note that when a thread tries to obtain write access (writing is not started yet), it is not allowed to add new readers, that is, down_read will sleep.
Binary mutex

It cannot be used in the interrupt context. After obtaining the mutex, you can only get it from its thread and release it. The task may not exit without first unlocking the mutex. also, kernel memory where the mutex resides mutex must not be freed with the mutex still locked.

Define_mutex (mutexname) is initialized during definition; struct mutex mutexname; mutex_init (& mutexname); obtain the mutex void inline _ sched mutex_lock (struct mutex * Lock ); // The lock cannot be interrupted. If the lock fails, the current process int _ sched mutex_lock_interruptible (struct mutex * Lock) will be blocked. // If the lock fails, the current process int _ sched mutex_trylock (struct mutex * Lock) will be blocked; // attempts to lock will return immediately without blocking the process, success 1 failure 0 release mutex void _ sched mutex_unlock (struct mutex * Lock); // unlock
Usage of three completions

Completion is a lightweight mechanism used by tasks: allows one thread to tell another that the job has been completed. To use completion, your code must contain <Linux/completion. h>

1. declare_completion (my_completion) is initialized during definition; struct completion my_compeltion; init_completion (& my_completion) is initialized after definition; 2. void wait_for_completion (struct completion * C); 4. void completion (struct completion * C); // wake up only one waiting Execution Unit void completion_all (struct completion * C); // wake up all waiting execution units, you need to reinitialize it. Otherwise, wait_for_completion is returned directly.

A completion is normally a single-device. however, it is possible to take the correct measures to re-use the completion structure. if complete_all is not used, there is no problem to re-use a completion structure, as long as there is no blur on the events that are sent. if you use complete_all, however, you must re-initialize the completion structure before re-use. macro definition:
Init_completion (struct completion C );
For a typical application, after the kernel thread has created a subthread, it must wait for a certain action of the subthread to be executed before the main thread can continue.

Four spin locks

The spin lock ensures that only one thread enters the critical zone at the same time, and the access time of the critical resource to be protected is relatively short. It is very convenient and efficient to use the spin lock. The self-selected lock has the following features:
1. kernel code holds a spin lock any time, preemption is prohibited on the relevant processor.
2. If the time required to access critical resources is long, use the semaphore; otherwise, use the spin lock.
3. The code in the critical section must be atomic. It is illegal to schedule, preempt, and wait for the queue to sleep after the spin lock.
4. In the interrupt processing function, you can only use the spin lock. You can choose to hold the lock to prohibit interruption.

APIS related to spin locks mainly include:

Spinlock_t my_lock = spin_lock_unlocked;/* initialize the spinlock during compilation */void spin_lock_init (spinlock_t * Lock);/* initialize the spinlock during runtime */obtain the spin lock spin_lock (LOCK) this macro is used to obtain the spin lock. If the lock can be obtained immediately, it will return immediately. Otherwise, it will spin there until the lock holder is released; spin_trylock (LOCK) this macro tries to get the spin lock. If it can get the lock immediately, it gets the lock and returns the true value. Otherwise, it immediately returns the false value. In fact, it no longer "in the original position"; spin_unlock (LOCK) release the spin lock, which is used in pairs with the spin_trylock or the spin_lock;/* all the waiting conditions of the spin lock are essentially non-disruptive. Once the spin lock is called, always in the spin state */void spin_lock (spinlock_t * Lock) before obtaining the lock;/* Get the spinlock */void spin_lock_irqsave (spinlock_t;/* Lock, unsigned long flags ); /* Get the spinlock, disable local CPU interruption, and save the interrupt mark on flags */void spin_lock_irq (spinlock_t * Lock);/* Get the spinlock, disable local CPU interruption */void spin_lock_bh (spinlock_t * Lock);/* obtain the spinlock and Disable software interruption, the corresponding lock release function */void spin_unlock (spinlock_t * Lock); void spin_unlock_irqrestore (spinlock_t * Lock, unsigned long flags ); void spin_unlock_irq (spinlock_t * Lock); void spin_unlock_bh (spinlock_t * Lock);/* the following non-blocking spin lock function is obtained successfully and a non-zero value is returned; otherwise, zero */INT spin_trylock (spinlock_t * Lock) is returned; int spin_trylock_bh (spinlock_t * Lock );

/* The new kernel contains more functions */
The kernel provides the reader/writer form of a spin lock, which directly imitates the reader/writer flag we saw earlier in this chapter. these locks allow any number of readers to enter the critical section at the same time, but the writer must have exclusive access.
Reader/writer spin lock:

Rwlock_t my_rwlock = unlock;/* initialization at compilation */rwlock_t my_rwlock; rwlock_init (& my_rwlock);/* initialization at runtime */void read_lock (rwlock_t * Lock ); void unlock (rwlock_t * Lock, unsigned long flags); void unlock (rwlock_t * Lock); void read_lock_bh (rwlock_t * Lock); void read_unlock (rwlock_t * Lock ); void Merge (rwlock_t * Lock, unsigned long flags); void Merge (rwlock_t * Lock); void read_unlock_bh (rwlock_t * Lock); void write_lock (rwlock_t * Lock ); void Merge (rwlock_t * Lock, unsigned long flags); void Merge (rwlock_t * Lock); void write_lock_bh (rwlock_t * Lock); void write_unlock (rwlock_t * Lock ); void Merge (rwlock_t * Lock, unsigned long flags); void Merge (rwlock_t * Lock); void write_unlock_bh (rwlock_t * Lock); int write_trylock (rwlock_t * Lock );

Five atomic variables

The complete lock mechanism is a waste for a simple integer. The kernel provides an atomic Integer type called atomic_t, which is defined in. Atomic variable operations are very fast, because they are compiled into a single machine command at any time possible.
The following are their interface functions:

Atomic_t v = atomic_init (0);/* use a macro to define atomic_init to initialize the atomic value during compilation. */void atomic_set (atomic_t * V, int I);/* set the atomic variable V to the integer I. */INT atomic_read (atomic_t * V);/* returns the current value of v. */void atomic_add (int I, atomic_t * V);/* Add I to the atomic variable pointed by V. the returned value is void */void atomic_sub (int I, atomic_t * V)./* subtract I from * v. */void atomic_inc (atomic_t * V); void atomic_dec (atomic_t * V);/* increments or decreases an atomic variable. */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);/* perform a specific operation and test result; if the atomic value is 0 after the operation, the return value is true. Otherwise, the return value is false. note that there is no atomic_add_and_test. */INT atomic_add_negative (int I, atomic_t * V);/* Add the integer variable I to V. if the result is a negative value, the returned value is true, otherwise it is false. */INT partition (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 );

Functions like atomic_add and similar to it, except that they return New Values of atomic variables to the caller. atomic_t data items must be accessed through these functions. operations that require multiple atomic_t variables still require some other types of locks.

Six-Digit Variable

The kernel provides a set of functions to modify or test a single bit atomically. Atomic bit operations are very fast because they use a single machine command to perform operations.

Void set_bit (NR, void * ADDR);/* set the NR bit in the data item pointed to by ADDR. */Void clear_bit (NR, void * ADDR);/* clears the unsigned long data located at the ADDR. */void change_bit (NR, void * ADDR);/* flip the NR bit. */INT test_bit (NR, void * ADDR);/* this function is the only bit operation that does not need to be an atom. It simply returns the current value of this bit. * // * the following atomic operations are as listed above. Besides, they also return the previous values of this bit. */INT test_and_set_bit (NR, void * ADDR); int test_and_clear_bit (NR, void * ADDR); int test_and_change_bit (NR, void * ADDR );

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.