Kernel module programming (13): semaphore, mutex lock, read/write semaphore, and completion volume

Source: Internet
Author: User
Document directory
  • Semaphores and mutex
  • Read/write semaphores
  • Completion (the Chinese name is unknown, which may be the number of completions, ^ _ ^)


This article is the second reading note of chapter 5 Concurrency and Race Conditions of LDD3 in Linux Device Drivers, but we are not limited to this content.

Semaphores (Semaphore) semaphores and mutex

  

Kernel provides different primitives to handle different situations. The most common method is to use semaphores. If a resource cannot be obtained, it enters the sleep state, waiting for the resource to be released, that is, the block side.
. Use the lock primitive to make the sleep. For example, in the scull write () example, kmalloc is suitable, but not all cases are suitable for sleep. Semaphores
Is a sleep mechanism. It contains an integer and a pair of functions P and V. If the process needs to enter, it will call P. If the semaphore value is greater than 0, this value is reduced by one and continues execution. If the semaphore is equal
Or less than 0, the process will wait for others to release the model. The Unlock semaphore is called V. It adds the semaphore value to 1 and wakes up the waiting process if possible.

  

If the semaphore user mutex (mutex: mutual exclusion), the semaphore value is initialized to 1, so that only one process or thread can be executed. In this case, semaphores also become mutex locks. In linux kernel, semaphores are essentially mutex locks.

  

In scull, we have used semaphores in the form of mutex locks, and there is a risk of Memory leakage caused by competition in scull_write:

If (! Dptr-> data [s_pos]) {

Dptr-> data [s_pos] = kmalloc (quantum, GFP_KERNEL );

If (dptr-> data [s_pos] = NULL)

Goto out;

... ...

}

  

The memory space kmalloc is allocated and the copy content is carried out. If multiple processes simultaneously trigger scull_write during execution, competition occurs. For example, A executes
When the second kmalloc statement is not executed, B executes the first statement. Therefore, B also needs to execute the second statement. Therefore, both processes require kmalloc space for data [s_pos,
And execute the copy content, leading to content confusion. As a result, the space allocated by a disappears and Its pointer disappears, so we cannot release kmalloc of A, which occupies a space in the memory, till System
Close, This is the memory leakage.

  

For examples of semaphore usage, see kernel module programming (5): device read/write

, The related operations have been highlighted and will not be repeated here. The usage is as follows:

  1. Header file loading:# Include <asm/semaphore. h>,

    The relevant data structure isStruct semaphore

    .

  2. Create semaphores:Void sema_init (struct semaphore *Sem

    , IntVal

    );

    When val is set to 1, it becomes a mutex lock. You can also use the following method for mutex lock:

    1. Declare and initialize a mutex lock:DECLARE_MUTEX (

      Name

      )

      OrDECLARE_MUTEX_LOCKED (

      Name

      ),

      Name is a variable of struct semaphore. In the latter mode, the starting state is locked. Other threads/processes need to wait for their unlocked to enter.

    2. If a mutex lock requires real-time initialization, that is, dynamic generation, use:Void int_MUTEX (struct semaphore *

      Sem

      )

      ; Or voidInit_MUTEX_LOCKED (struct semaphore *

      Sem

      );

  3. For semaphore acquisition, that is, the P function, which is called by the down operation, the semaphore is subtracted. If the semaphore does not meet the requirements, the caller is set to sleep until the resource is obtained. The method is as follows:
    1. VoidDown

      (Struct semaphore * sem); subtract one from the semaphore and wait until the resource is obtained.

    2. IntDown_interruptible

      (Struct semaphore * sem );
      Same as above, but interruptible is the most commonly used method. It allows users to interrupt a process in the user space waiting for the semaphore to be obtained. If non-
      The process cannot be killed during this period. We need to note that, in this way, if the user terminates the process while waiting, a non-zero value will be returned, so it must
      Determine the return value and process it accordingly. Generally-ERESTARTSYS

      Before returning, ensure all operations that the user can perceive before undo. If not-EINTR

      .

    3. IntDown_trylock (struct semaphore *

      Sem

      );

      This method does not sleep. If it fails, a non-zero value will be returned immediately.

  4. When a thread successfully calls down, that is, the obtained semahore, will enter this sensitive code zone. After execution, the semaphore must be released. Use up to call the V operation.Void up (struct semaphre *

    Sem)

    . Note that if return is required due to exceptions or errors during processing, the release of the semaphore must be ensured, that isThe semaphore must be released whether it is normal or abnormal to leave this sensitive code zone.

    . Otherwise, no thread/process can obtain the semaphore.

  

We need to note that we must ensure that the semaphore has been initialized before obtaining it. In the statement execution sequence, priority must be given.

Read/write semaphores

  

In scull

In this example, both read and write are protected by semaphores to prevent both write and read operations, at the same time, it does not allow two read operations.
The situation will not cause harm. Linux
Kernel provides a special rwsem semaphore for processing this situation, allowing multiple reads to exist at the same time to improve program processing capabilities. Read/write semaphores are rarely used in the driver, but sometimes
Very effective. The usage is as follows:

  • Header file loading:# Include
    <Linux/rwsem. h>

    , The corresponding data structure isStruct rw_semaphore

    ,

  • Initialization operation:Void init_rw_sem (struct rw_semaphore *

    Sem

    );

  • For read-only operations, the related functions are as follows:
    • Void down_read (struct rw_semaphore *

      Sem

      );

      Provides read-only access protection and other read-only operations. He will set the caller and non-interrupted sleep, which requires special attention, that is, if there is another write operation that causes sleep, non-interrupted, the user cannot interrupt the process at the moment.

    • Int down_read_trylock (struct rw_semaphore *

      Sem

      );

      Returns immediately. If it is readable, it returns non-zero, non-readable, and 0. Note that the return method is different from that of the general kernel function. The Return Method of semaphores is also different.

    • Void up_read (struct rw_semaphore *

      Sem

      );

      Release the read/write semaphores.

  • For write protection, the related functions are as follows:
    • Void down_write (struct rw_semaphore *

      Sem

      );

      Similar to down_read

    • Int down_write_trylock (struct rw_semaphore *

      Sem

      );

      Similar to down_read_trylock

    • Void up_write (struct rw_semaphore *

      Sem

      );

      Similar to up_read

    • Void downgrade_write (struct rw_semaphore *

      Sem

      );

      When a write protection is followed by a time-consuming read protection, we can use downgrade_write, which allows other read operations to immediately obtain the read/write model after you finish writing. The normal processing of the no side is to wait for the long read operation that follows.

  

A read/write semaphore can be obtained by a write user or an unlimited number of read users. Write users are given priority. When a write attempts to enter this key operation code, other readers cannot obtain the semaphore, wait until all the writes are completed. Therefore, it is suitable for the case where there are few write operations and the write process is relatively short. It is not suitable for the existence of a large number of writes, which will impede reading.

Completion (the Chinese name is unknown, which may be the number of completions, ^ _ ^)

  

Generally, the processing of semaphores is limited to a function, but sometimes function a is required to process function B. A must wait for processing by function B before it can continue, semaphores can be used for processing, but Linux Kernel provides the complete method. The usage is as follows:

  • Header file# Include

    , The data structure isStruct completion

    , InitializedInit_completion (struct completion *

    Comp

    )

    You can also directly useDECLARE_COMPLETION (

    Comp

    );

  • In function A, if you need to wait for other processing, useVoid wait_for_completion (struct completion *

    Comp

    );

    In this position, the user will wait for a non-interrupted sleep, that is, the relevant thread/process, and the user cannot kill it.

  • If function B has been processed, it can be processed by function A. There are two ways to do this:
    • Void complete (struct completion *

      Comp

      );

      If you want to execute A, you must wait for B to execute it first, and B to execute it again. If A needs to be executed again, make sure that the next execution of B is complete. If you execute B twice in A row, you can execute A twice. For the third time, A must wait until the third time.

    • Void complete_all (struct completion *

      Comp

      );

      As long as execution of B is complete, A can be executed no matter how many times. If you need to wait for the immediate response of B, you can useINIT_COMPLETION (struct completion *

      Comp

      )

      . Reinitialize the completion.

    • Void complete_and_exit (struct completion *

      Comp

      , Long

      Retval

      )

      In addition to the complete function, this processing will also terminate the thread/process that calls it. It can be used in some infinite loop scenarios. For example, after receiving the information of A cleaned up, the eknows that the user program is terminated and function A can be executed.

  

For our scull example, completion is not an appropriate scenario, but we can test it through it. We hope to complete the write operation before the scull read operation.

# Include <linux/completion. h>
......

DECLARE_COMPLETION (comp); // For the convenience of the test, each scull holds a completion volume, which should have been

Int scull_read (......){

Struct scull_qset * dptr;

......

Printk ("scull_read waiting for completed from write function./N ");

Wait_for_completion

(& Comp );

Printk ("scull_read awake for reading, continue.../N ");

.......

}

Int scull_write (......){

......

Complete

(& Comp );
//Complete_all

(& Comp );

//Complete_and_exit

(& Comp );

}

  

For scull0, we originally had a read/write test example in the user space, which was divided into read test and write test. When we call the read test, in the example of sleep, the read test is only available when the write test is called.
Continue. However, scull_read is used to find that the scull kernel module has crash, because before wait_for_completion,
Assign values to some variables, such as dptr, and these variables are changed during write. Therefore, an error occurs, which must be addressed after wait_for_completion.
Assign values to variables. This solves the crash problem. However, we found that after the write test, the read test can continue, but soon fell into the waiting state. The following is the code for the read test:

File = fopen ("DEV/scull0", "R ");

......

While (LEN = fread (read_buf, 1,512, file)> 0 ){

T

Otal_len

+ = Len;

Printf ("% s", read_buf );

Memset (read_buf, 0,512 );

}

  

Scull_read will be called until scull_read returns 0 or <0. Because the write operation has been performed, the length of the content returned by the first call will be the second call.
In this way, the program will be very messy. In fact, we write the test, because the write content is small, you can write it at one time, it is likely to be divided into multiple writes. Therefore, it is not appropriate to add the completion quantity here. It can be done in fopen.
Processing, that is, processing in scull_open, for example:

Int scull_open (....)

{

......

If (filp-> f_flags & O_ACCMODE) = O_RDONLY ){

Printk ("waiting for completed from write function./n ");

Wait_for_completion (& comp );

Printk ("awake for reading, continue.../n ");

}

......

}

  

This is a more reasonable approach. If the location of the complete is still in scull_write, we try three methods. Complete_and_exit (), which can be
Write a large amount of content into the test. We will find that only part of the content is written (the first time scull_write is called)
The test program exits. Scull is not an example of a proper completion. Completion may cause one or more problems. You need to carefully plan it.

Related links:
My articles related to the kernel module


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.