Tuesday, May 10, 2011

Reader Writer Semaphores in Linux

In the last post, Semaphores in linux, we saw the use of semaphores in linux. Semaphores are used to restrict the access to the shared resources.
When the shared resource is only read and not written to, there is no change to the data and hence if multiple processes want to only read from the shared resource there should not be any problem in allowing access to all readers.
Linux provides a special kind of semaphore, reader/writer semaphore,that allows multiple readers into the critical section but only one writer at any given time.
The structure used for the semaphore is "struct rw_semaphore"

Initializing the semaphore :
void init_rwsem(struct rw_semaphore

To hold the semaphore for reading we can use one of the following

void down_read(struct rw_semaphore *sem) : sleeps if semaphore is not available.
int down_read_trylock(struct rw_semaphore *sem):  Returns with an error if semaphore is not available.

Releasing a semaphore held for reading :

void up_read(struct rw_semaphore *sem);

To hold a semaphore for writing :

void down_write(struct rw_semaphore *sem) : sleeps if semaphore is not available.
int down_write_trylock(struct rw_semaphore *sem):  Returns with an error if semaphore is not available.

Releasing a semaphore held for reading :

void up_write(struct rw_semaphore *sem);

Let us look at an example to understand the working.
We make use of the basic character driver written from "Writing an example driver from scratch"  .

Every time we enter the read function a read/write semaphore is held and a message is printed to indicate that the semaphore was held successfully.
In the write function too we try to hold the same read/write semaphore but now write, we will see that if there are processes reading from the device the read is put to sleep but any number of readers are allowed at a time.

*******************************rw_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>
#include <linux/wait.h>
#include <linux/time.h>

wait_queue_head_t queue;
static int Major;
char flag='n';
struct task_struct *task;

struct device {
    char array[100];
    struct rw_semaphore rwsem;
}char_arr;

int open(struct inode *inode, struct file *filp)
{
   
    printk(KERN_INFO "Inside open \n");
    task = current;

    return 0;
}

int release(struct inode *inode, struct file *filp) {
    printk (KERN_INFO "Inside close \n");
    return 0;
}

ssize_t read(struct file *filp, char *buff, size_t count, loff_t *offp) {
    unsigned long ret;
    down_read(&char_arr.rwsem);
    printk("Inside read \n");
    ret = copy_to_user(buff, char_arr.array, count);
    wait_event_timeout(queue,flag!='n',30*HZ);
    up_read(&char_arr.rwsem);
    return ret;
}

ssize_t write(struct file *filp, const char *buff, size_t count, loff_t *offp) {   
    unsigned long ret;
    down_write(&char_arr.rwsem);
    printk(KERN_INFO "Inside write \n");
    ret =    copy_from_user(char_arr.array, buff, count);
    up_write(&char_arr.rwsem);
    return ret;
}


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_rwsem(&char_arr.rwsem);
    init_waitqueue_head(&queue);
   
    return 0;
}

void char_arr_cleanup(void) {
    printk(KERN_INFO " Inside cleanup_module\n");
    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 := rw_sem.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 applicatoin:

*****************************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 output run the following commands

$ make
$ sudo insmod rw_sem.ko
$ cat /proc/devices | grep char_arr_dev
250 char_arr_dev    (Note the number might be different in your system.)

$ sudo mknod /dev/temp c 250 0
$ cc user_app.c
Now open 4 separate terminals

One terminal :
$sudo ./a.out 
Press r to read from device or w to write the device r   (Note press "r")



Second terminal
$sudo ./a.out 
Press r to read from device or w to write the device r   (Note press "r")

Third terminal
$sudo ./a.out 
Press r to read from device or w to write the device r   (Note press "w")

In the fourth terminal
$ dmesg
 Inside open  //open by first read
 Inside read  //  Entered read after holding semaphore
 Inside open  // Open by second read
 Inside read // Entered read after holding the same semaphore so two readers are present now                                            
 Inside open // Open by the write which is put to sleep when it tries to hold the semaphore.

Wait for a while till the two readers exit.

Now run dmesg again
$ dmesg

Inside close   // Close by first read
Inside close  // Close by first read
Inside write  //Writer is allowed to enter after both readers exit.

 




0 comments:

Post a Comment