world leader in high performance signal processing
Trace: » semaphores

Critical Areas

The example device driver was not designed to allow more than a single read/ write access to the data buffer. It was attempting to simply demonstrate the mechanics of the Open/Close and Read/Write functions.

In a real world driver the resource being driven would need to have more sophisticated access control.

In the previous example each task using the device had its own position pointer and was allowed to write to the data buffer no matter what was happening with any other driver trying to access the same device.

In order to improve the example driver example the data structure needs to have the data buffer positional pointers added to it. As each task then accesses the data buffer some protection is required to prevent any other task interrupting any process using these pointers.

Consider the following sequence:

Task 1 Task 2
Opens the driver for write
fetches the buffer pointer
starts to write data to the buffer
reads data pointer
blocks waiting for user data
Runs while Task 1 is blocked
Opens the driver for write
fetches the buffer pointer
Writes data to the buffer
completes and updates the buffer pointer
Task 1 Unblocks
Completes the data write
Updates the buffer pointer
This task's data is in the buffer This task's data has been “lost”

To stop this kind of operation some mechanism needs to be provided to prevent any other task accessing the device data pointers or the buffer while one task is trying to use the same device.

The device data buffer and its pointers is known as a Critical Area. Once one task has access to this Critical Area other tasks need to be suspended of held until the first task has completed.

Linux offers spinlocks and semaphores to provide this sort of protection.

Since the access to user data can block, you need to use a semaphore. Spinlocks can only be used where a task will not be blocked they are normally used on SMP systems or where data is shared with interrupt handlers.

Semaphores in Linux are defined in <asm/semaphore.h>. They have a type of

 struct semaphore.

Semaphores must be initialized prior to use by passing a numeric argument to the sema_init function. A 1 value will initialize the semaphore as full. In this state it can be taken by a call to down or down_interruptible. A 0 value will initialize the semaphore as empty. In this state it can be given by a call to up.

An example of semaphore initialization.

        #include <asm/semaphore.h>                            
        struct semaphore mysem;                               
        sema_init(&mysem, 1); /* init the semaphore as full */

Use down or down_interruptible to take a semaphore. If the semaphore value is 0, the function will sleep after adding itself to a list of tasks interested in any wake up call sent to the semaphore. A task using the up function (on the same semaphore) will cause a sleeping task waiting for the semaphore to be woken up.

The down_interruptible function can be interrupted by a signal. (for example pressing control C on the keyboard). If the function down_interruptible was interrupted by a signal it will return a non zero value. This can be used, within the driver, to allow the kernel to handle the signal or pass the control back to the user task with the signal condition flagged.

     if (down_interruptible(&mysem))
        return -ERESTARTSYS;        

Put or release the semaphore with the up function:

     up (&mysem);

Here is a typical code example:

  #include <asm/semaphore.h>                               
  static struct semaphore mysem;                           
     sema_init(&mysem, 1); /* init the semaphore as full */
  static int mydata_flag = 0;                              
        return -ERESTARTSYS;                               
     if ( mydata_flag == 0 )   {                           
        mydata_flag = 1;                                   
        /* ... init data or add to a list */