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:
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:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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); |
Sample Test Application:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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; | |
} |
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
Post a Comment