Search

Using Semaphores in Linux

Semaphore as used in an operating system to restrict the access to resources by multiple processes at the same time. When a semaphore is used to restrict the access to only one process at a time, it is termed as mutex, as it serves the purpose of mutual exclusion.
The part of the program which accesses the shared resource is called as the critical section. Hence a semaphore restricts the execution of the critical section by multiple processes at the same time.

Traditionally semaphores have two functions associated with it, called "P" and "V" .
Every process before entering a critical section calls the function "P", if no other process is executing the critical section the semaphore is held by the process and the process is allowed to enter the critical section.
In case the semaphore is already held by some other process, that is some other process is executing the critical section, then "P" puts the current process to sleep.
The function "V" on the other hand releases the semaphore that is being held by the process.

In linux the equivalent calls for P and V are down() and up() respectively.

When a process calls down() the value of the semaphore is decremented and if value after decrementing is zero or greater than zero the process is allowed to enter the critical section. But on the other hand if the new value is negative then the process is put to sleep on a wait queue. 
On the other hand when a process calls up() the value of the semaphore is incremented by one.

When we want to use the semaphore as a mutex, the value of semaphore is initialized to 1. So at any give time only one process can execute the critical section.


To make use of a semaphore we need the header file <asm/semaphore.h>  and variable is of the type "struct semaphore".

Semaphore initialization:

struct semaphore name;
sema_init(&name, count);


More often than not semaphore is used as a mutex during module programming, hence kernel developers have provided a separate function calls to initialize a semaphore as a mutex.

static DECLARE_MUTEX(name);

To create it dynamically we can use
 
init_MUTEX(name);

Various kinds of down() :

down() : Will keep waiting for the semaphore unless it does not become available. Can not be interrupted.
down_interruptible(): Will keep waiting for the semaphore unless it does not become available but can be interrupted. This is always preferable over down().
down_trylock() : Will not put the process to sleep if semaphore is already being held, but returns immediately with non zero return value.

There are no variations of up().

Let us look at an example module to understand the working of semaphore better.
The module below is a character driver to control a virtual device that we create using an array.You can refer to "Writing a driver from scratch " for further details.
In the module we create two proc entries "hold" and "remove"
When "hold" is read it locks a semaphore and when "remove" is read it unlocks the same semaphore.

The same semaphore is also held when a user space application tries to access  the device. Hence to understand the working of semaphore we will lock the semaphore by reading "hold" first and then try to access the device, which should put the process to sleep until we call read on remove and release the semaphore.

***************************sema.c**********************************************
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h> // required for various structures related to files liked fops.
#include <asm/uaccess.h> // required for copy_from and copy_to user functions
#include <linux/semaphore.h>
#include <linux/cdev.h>
#include <linux/proc_fs.h>
#include <linux/sched.h>


static int Major;
char flag='y';
struct task_struct *task;
struct device {
    char array[100];
    struct semaphore sem;
}char_arr;

int open(struct inode *inode, struct file *filp)
{
   
    printk(KERN_INFO "Inside open \n");
    task = current;                 // Saving the task_struct for future use.
    if(down_interruptible(&char_arr.sem)) {
        printk(KERN_INFO " could not hold semaphore");
        return -1;
    }
    return 0;
}

int release(struct inode *inode, struct file *filp) {
    printk (KERN_INFO "Inside close \n");
    printk(KERN_INFO "Releasing semaphore");
    up(&char_arr.sem);
    return 0;
}

ssize_t read(struct file *filp, char *buff, size_t count, loff_t *offp) {
    unsigned long ret;
    printk("Inside read \n");
    ret = copy_to_user(buff, char_arr.array, count);
    return ret;
}

ssize_t write(struct file *filp, const char *buff, size_t count, loff_t *offp) {   
    unsigned long ret;
    printk(KERN_INFO "Inside write \n");
    ret =    copy_from_user(char_arr.array, buff, count);
    return ret;
}
int hold(char *buf,char **start,off_t offset,int count,int *eof,void *data )
{
int len=0;
down_interruptible(&char_arr.sem);   // holding the semaphore
printk(KERN_INFO "Inside hold");
return len;
}

int remove(char *buf,char **start ,off_t offset,int count,int *eof,void *data )
{
int len=0;
/* according to linux/sched.h the value of state and their meanings are
-1 unrunnable, 0 runnable, >0 stopped*/

printk(KERN_INFO "State before= %i", task->state);  // printing the state of user process
up(&char_arr.sem);
printk(KERN_INFO "Inside remove");
printk(KERN_INFO "State after = %i", task->state); // printing the state of user process

return len;
}


void create_new_proc_entry()
{
create_proc_read_entry("hold",0,NULL,hold,NULL);
create_proc_read_entry("remove",0,NULL,remove,NULL);
}

struct file_operations fops = {
    read:        read,
    write:        write,
    open:         open,
    release:    release
};


struct cdev *kernel_cdev;


int char_arr_init (void) {
    int ret;
    dev_t dev_no,dev;

    kernel_cdev = cdev_alloc();   
     kernel_cdev->ops = &fops;
    kernel_cdev->owner = THIS_MODULE;
    printk (" Inside init module\n");
     ret = alloc_chrdev_region( &dev_no , 0, 1,"chr_arr_dev");
    if (ret < 0) {
        printk("Major number allocation is failed\n");
        return ret;   
    }
   
    Major = MAJOR(dev_no);
    dev = MKDEV(Major,0);

    printk (" The major number for your device is %d\n", Major);
    ret = cdev_add( kernel_cdev,dev,1);
    if(ret < 0 )
    {
    printk(KERN_INFO "Unable to allocate cdev");
    return ret;
    }
    init_MUTEX(&char_arr.sem);

    create_new_proc_entry();
    return 0;
}

void char_arr_cleanup(void) {
    printk(KERN_INFO " Inside cleanup_module\n");
    remove_proc_entry("hold",NULL);
    remove_proc_entry("remove",NULL);
    cdev_del(kernel_cdev);
    unregister_chrdev_region(Major, 1);
}
MODULE_LICENSE("GPL");   
module_init(char_arr_init);
module_exit(char_arr_cleanup);
******************************************************************************

************************Makefile******************************************
ifneq ($(KERNELRELEASE),)
   obj-m := sema.o
else

KERNELDIR ?= /lib/modules/$(shell uname -r)/build

PWD := $(shell pwd)

default:
    $(MAKE) -C $(KERNELDIR) M=$(PWD) modules 
endif
clean:
    $(MAKE) -C $(KERNELDIR)  M=$(PWD) clean
************************************************************************

User application to access the device

********************user_app.c************************************
#include <stdio.h>
#include <fcntl.h>


main ( ) {
        int i,fd;
        char ch, write_buf[100], read_buf[100];

        fd = open("/dev/temp", O_RDWR);

        if (fd == -1)
        {
                printf("Error in opening file \n");
                exit(-1);
        }
        printf ("Press r to read from device or w to write the device ");
        scanf ("%c", &ch); 


        switch (ch) {
                case 'w':
                       printf (" Enter the data to be written into device");
                        scanf (" %[^\n]", write_buf);
                        write(fd, write_buf, sizeof(write_buf));
                        break;
                case 'r':

                        read(fd, read_buf, sizeof(read_buf));
                        printf ("The data in the device is %s\n", read_buf);
                        break;

                default:
                        printf("Wrong choice \n");
                        break;
        }
        close(fd);
}


***********************************************************************************




To see the functioning run the above code as follows

$ make
$ sudo insmod sema.ko
$ dmesg

/*You should see the major number allocated to the driver in the output of dmesg, we assume it is 250*/
$ sudo mknod /dev/temp c 250 0
$  cc use_semaphore.c
$  cat /proc/hold
$  sudo ./a.out

 /* The process should go to sleep now,as it is waiting for the semaphore to be released.
Open a new terminal and run the following command*?
$ cat /proc/remove
/*Now go back to the previous terminal, you should see the process should have waken up and entered the case statement in the code i.e. */
Press r to read from device or w to write the device

/* If you execute dmesg now you should see the state of the user_app being printed when remove  proc entry was read.
State before= 1 and
State after = 0
The state 1 means the process was stopped, that was when the process was waiting for the semaphore. After the semaphore is released the state change to "0" which means the process is running.*/

No comments:

Post a Comment