LINUX device driver - race, blocking, reentrant code

  
 

1. About races

If there are two processes that open the device for data write operation at the same time, when process A writes data, it will apply for a new device memory and append one to the device dev data link table. The new quantum, which points the pointer to this new device memory block, and the same operation in the process B write operation, so if no driver modification is done, because the device is opened by both processes at the same time, the two processes have the same device data link table. The information will be modified to the same data. Obviously, the data created by the first operation will be overwritten by the subsequent process, which is the result of the race.

However, the less positive argument is that this does not happen on a single processor because the code running in the kernel is non-preemptive, meaning that the processor can only process one code at a time. But a multiprocessor system can happen.

LINUX provides a race solution:

1) semaphore semaphore, used for mutual exclusion

#include

It is very simple, that is A tag needs to be defined in the data block that needs to be avoided. When a process is used, the flag is set to 0, indicating that the signal is already occupied and can no longer be used. All processes must first check the tag if they want to access the data block. If it is 0, it means that a process is occupying, you must wait.

Therefore there is a semaphore tag in the data structure Scull_Dev of scull0.

But the semaphore is handled by the kernel, because we want the process to be semaphore, if the semaphore is used, the process should be handed over to the kernel, waiting, not looped by ourselves. Check and wait for semaphores.

ok, since it has to be handed over to the kernel management, the semaphore must be initialized.

sema_init(&scull_devices.sem, 1); //; Register a semaphore, initialized to 1, indicating that it is available.

When you need to get a semaphore, call down_interruptable(&sem), release the semaphore up(&sem), up and wake up the process that is waiting for the semaphore.

if (down_interruptable(&dev->sem)) return -ERESTARTSYS; //; If it fails, return directly, can't call up(&sem)//;data operations...

up(&dev->sem);

Note that you should be careful with the use of semaphores, visible if a process holds a semaphore and it fails when it releases the semaphore If you do, other processes will block.

In addition, because the semaphore will cause the process to sleep, the semaphore cannot be applied in the interrupt processing.

2) Lock

As you can see, using a semaphore, if one process holds a semaphore, another process will go to sleep. In many cases, the process does not need to wait for sleep. For example, interrupt processing is not allowed to enter sleep, or in some cases, it is simply to test whether public data is occupied by other processes. If it is occupied, it will be retested until it can be used. Use a spinlock. Of course, when the spin lock is used, the processor is occupied, so the spin lock is suitable for holding the data for a relatively short time, and it is absolutely impossible to go to sleep when the lock is held.

#include

spinlock_t my_lock = SPIN_LOCK_UNLOCKED; or spin_lock_init(&my_lock); declare/create a lock

spin_lock(spinlock_t *my_lock); get the given lock If the lock is occupied, it spins until the lock is available. When the spin_lock returns, the calling function holds the lock until the spin_unlock (spinlock_t *my_lock) is released; the lock is released

2 About blocking and non-blocking < Br>

2.1 About blocking

There is a problem with the read call. When the device has no data to read, there are two ways to solve it. One is to prevent the direct read failure from jumping out. The second is to block the read operation, the process goes to sleep, and wakes up when there is data.

Discuss the blocking IO here to handle sleep and wake up.

Sleep is when a process needs to wait for an event, it should be temporarily suspended, let out the CPU, and then wake up after the event arrives.

One way to handle sleep is to add a process to the wait queue:

1) First you need to declare and initialize a wait queue entry.

#include

wait_queue_head_t my_queue;

init_waitqueue_head(&my_queue);

If you are awaiting a static global wait queue, you can use the above two definitions instead of

DECLARE_WAIT_QUEUE_HEAD( My_queue); //static declaration will be automatically initialized at compile time

2) use initialized wait queue item

call interrupt30_and_on(&my_queue) when it needs to join the kernel wait queue ; or sleep_on(&my_queue)

When wakeup is required, call wake_up_interruptible(&my_queue); or wake_up(&my_queue)

3)interruptible_sleep_on() defect

a. The resulting race:

To understand the possible race state caused by interruptible_sleep_on() and other sleep_on functions, you need to know more about the implementation of interruptible_sleep_on().

The waiting queue is actually a queue list. The data in the linked list is of type wait_queue_t. The simplified internal interruptible_sleep_on() is probably like this:

#include

wait_queue_t wait; //; Define a wait queue

init_wait_queue_entry(&wait, current); //;Initialize

current->state = TASK_INTERRUPTILBE; //; Set to sleep state, will enter Sleep

add_wait_queue(&my_queue, &wait); //; Add the wait queue item we defined to this wait queue

schedule(); //; actually go to sleep

remove_wait_queue(&my_queue, &wait); //; Event arrives, schedule() returns

The race happens in current->state = TASK_INTERRUPTIBLE and schedule() In some cases, when the driver is ready to go to sleep, that is, when the current->state has been set, there may be just data arrival. At this time, wake_up will not wake up the process that has not actually gone to sleep, which may result in The process has been sleeping because it has not responded to wake up, thus generating this competition. This race is very prone. The solution is to not use interruptible_sleep_on(), but instead use its internal implementation directly.

Example:

#include

wait_queue_t wait; //;Define a wait queue

init_wait_queue_entry(&wait, current); //; Initialize

add_wait_queue(&my_queue, &wait); //; Add the wait queue item we defined to this wait queue

while(1){

current->state = TASK_INTERRUPTILBE; //; set to sleep, will go to sleep

if (short_head != short_tail) break; //; test if there is data arriving, if any, jump out

schedule(); //;Real to sleep

}

set_current_state(TASK_RUNNING);

remove_wait_queue(&my_queue, &wait); //; Event arrives, schedule() returns

In fact, we can do these complicated things without the kernel. The kernel defines a macro

wait_event_interruptible(wq, condition); or wait_event(wq, Condition) condition is the condition of the test

Copyright © Windows knowledge All Rights Reserved