Access Control on Device Files - Allow only one process to open at a time

Let's modify our /dev/kmsg driver to avoid multiple processes opening the device simultaneously to avoid any concurrency issue.

We will use atomic variables as a solution. You can learn more about atomic variables in my previous post.

Logic:

  • Initialize the atomic value with 1
  • In open function, decrement and check whether the value is zero, if zero then return success
  • If the value is not zero, then increment the value and return EBUSY
  • In release function, increment the value of atomic variable.

Code:


#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/kdev_t.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <asm/atomic.h>
MODULE_LICENSE("GPL");
dev_t device_number;
bool dynamic = true;
struct class *my_class;
static struct cdev my_cdev;
#define MAX_SIZE 1024
char buffer[MAX_SIZE];
static atomic_t device_available = ATOMIC_INIT(1);
static int msgdevice_open(struct inode *inode, struct file *file)
{
pr_info("%s\n", __func__);
//Returns 1 if the result is zero, else 0
if (!atomic_dec_and_test(&device_available)) {
atomic_inc(&device_available);
return -EBUSY;
}
return 0;
}
static int msgdevice_release(struct inode *inode, struct file *file)
{
pr_info("%s\n", __func__);
atomic_inc(&device_available);
return 0;
}
ssize_t msgdevice_read(struct file *file, char __user *user_buffer,
size_t count, loff_t *offset)
{
int retval = 0;
size_t bytes;
bytes = strlen(buffer) - (*offset); //how many bytes not yet sent?
bytes = bytes > count ? count: bytes;
if (bytes) {
retval = copy_to_user(user_buffer, buffer, strlen(buffer));
if (!retval) {
*offset = bytes;
}
else
return retval;
}
return bytes;
}
ssize_t msgdevice_write(struct file *file, const char __user *user_buffer,
size_t count, loff_t *offset)
{
int retval;
size_t bytes;
bytes = count;
if (bytes > (MAX_SIZE - 1))
bytes = MAX_SIZE - 1;
memset(buffer, 0, sizeof(buffer));
retval = copy_from_user(buffer, user_buffer, bytes);
if (!retval) {
buffer[bytes] = '\0';
pr_info("%s Data written:%s\n", __func__, buffer);
}
else {
pr_info("%s: copy_from_user failed\t retval:%d\n",
__func__, retval);
return retval;
}
return bytes;
}
struct file_operations fops = {
.owner = THIS_MODULE,
.open = msgdevice_open,
.release = msgdevice_release,
.read = msgdevice_read,
.write = msgdevice_write
};
static int msg_device_init(void)
{
int retval;
pr_info("%s: In init\n", __func__);
if (dynamic) {
retval = alloc_chrdev_region(&device_number, 0, 1, "embedded");
}
else {
device_number = MKDEV(180, 0);
retval = register_chrdev_region(device_number, 1, "embedded");
}
if (!retval) {
pr_info("%s: Major Number:%d\t Minor Number:%d\n",
__func__, MAJOR(device_number), MINOR(device_number));
my_class = class_create(THIS_MODULE, "my_driver_class");
cdev_init(&my_cdev, &fops);
retval = cdev_add(&my_cdev, device_number, 1);
if (retval) {
pr_info("%s: Failed in adding cdev to subsystem "
"retval:%d\n", __func__, retval);
}
else {
device_create(my_class, NULL, device_number, NULL, "msg");
}
}
else
pr_err("%s: Failed in allocating device number "
"Error:%d\n", __func__, retval);
return retval;
}
static void msg_device_exit(void)
{
cdev_del(&my_cdev);
device_destroy(my_class, device_number);
class_destroy(my_class);
unregister_chrdev_region(device_number, 5);
pr_info("%s: In exit\n", __func__);
}
module_init(msg_device_init);
module_exit(msg_device_exit);
view raw msg_device.c hosted with ❤ by GitHub

Sample Test Application:

#include <stdio.h>
#include <pthread.h>
#include <fcntl.h>
void *func(void *arg)
{
int fd;
fd = open("/dev/msg", O_RDWR);
if (fd < 0) {
printf("Thread id:%d failed to opened file\n",
pthread_self());
return NULL;
} else {
printf("Thread id:%d successfully opened file\n",
pthread_self());
}
sleep(5);
write(fd, "hello world", sizeof("hello world"));
close(fd);
return NULL;
}
int main(int argc, char *argv[])
{
pthread_t thread1, thread2;
pthread_create(&thread1,NULL, func, NULL);
pthread_create(&thread2,NULL, func, NULL);
pthread_join(thread1, NULL);
pthread_join(thread2, NULL);
return 0;
}
view raw testapp.c hosted with ❤ by GitHub



Output:



Notes:

You can see while one thread was using the device and when the other thread tried to access it failed.
Also, from dmesg output, open was called twice and release only once


Comments

Popular posts from this blog

bb.utils.contains yocto

Difference between RDEPENDS and DEPENDS in Yocto

PR, PN and PV Variable in Yocto