procfs

  • In this program, you are going to learn

  • How to Communicate between the Userspace and Kernel Space ?

  • procfs can act as a bridge connecting the user space and the kernel space.

  • Userspace programs can use proc files to read the information exported by the kernel. Every entry in the proc file system provides some information from the kernel.

  • The proc entry can also be used to pass data to the kernel by writing into the kernel, so there can be two kinds of proc entries.

    • An entry that only reads data from the kernel space.

    • An entry that reads as well as writes data into and from kernel space.

  • Add the list of headers to refer the APIs used in the source code.

#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kdev_t.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/procfs.h>
#include <linux/kobject.h>
#include <linux/err.h>
  • Add the module macros which contains the information about macros such as author, description and license.

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Linux_usr");
MODULE_DESCRIPTION("Simple Linux Device Driver - procfs");
  • Initialize the functions and the variables which are used in the source code.

int32_t value;
char etx_array[20] = "try_proc_array\n";
static int len = 1;


dev_t dev;
static struct class *dev_class;
static struct cdev etx_cdev;
static struct proc_dir_entry *parent;


static int      __init etx_driver_init(void);
static void     __exit etx_driver_exit(void);


static int      etx_open(struct inode *inode, struct file *file);
static int      etx_release(struct inode *inode, struct file *file);
static ssize_t  etx_read(struct file *filp, char __user *buf, size_t len, loff_t *off);
static ssize_t  etx_write(struct file *filp, const char *buf, size_t len, loff_t *off);


static int      open_proc(struct inode *inode, struct file *file);
static int      release_proc(struct inode *inode, struct file *file);
static ssize_t  read_proc(struct file *filp, char __user *buffer, size_t length, loff_t *offset);
static ssize_t  write_proc(struct file *filp, const char *buff, size_t len, loff_t *off);

static const struct file_operations fops = {
        .owner          = THIS_MODULE,
        .read           = etx_read,
        .write          = etx_write,
        .open           = etx_open,
        .release        = etx_release,
};

static const struct file_operations proc_fops = {
        .open = open_proc,
        .read = read_proc,
        .write = write_proc,
        .release = release_proc
};
  • Add the module init function to execute once when the module is loaded.

static int __init etx_driver_init(void)
{
        if ((alloc_chrdev_region(&dev, 0, 1, "etx_Dev")) < 0) {
                pr_info("Cannot allocate major number\n");
                return -1;
        }
        pr_info("Major = %d Minor = %d\n", MAJOR(dev), MINOR(dev));


        /*Creating cdev structure*/
        cdev_init(&etx_cdev, &fops);

        /*Adding character device to the system*/
        if ((cdev_add(&etx_cdev, dev, 1)) < 0) {
                pr_info("Cannot add the device to the system\n");
                goto r_class;
        }

        /*Creating struct class*/
        dev_class = class_create(THIS_MODULE, "etx_class");
        if (IS_ERR(dev_class)) {
                pr_info("Cannot create the struct class\n");
                goto r_class;
        }

        /*Creating device*/
        if (IS_ERR(device_create(dev_class, NULL, dev, NULL, "etx_device"))) {
                pr_info("Cannot create the Device 1\n");
                goto r_device;
        }

        /*Create proc directory. It will create a directory under "/proc" */
        parent = proc_mkdir("etx", NULL);

        if (parent == NULL) {
                pr_info("Error creating proc entry");
                goto r_device;
        }

        /*Creating Proc entry under "/proc/etx/" */
        proc_create("etx_proc", 0666, parent, &proc_fops);

        pr_info("Device Driver Insert...Done!!!\n");
        return 0;

r_device:
        class_destroy(dev_class);
r_class:
        unregister_chrdev_region(dev, 1);
        return -1;
}
  • proc_mkdir using this we can create the directory under /proc/*

parent = proc_mkdir("etx", NULL);
  • proc_create used to create a proc entry by the name etx_proc under /proc. This proc entry should be created in the Driver init function.

proc_create("etx_proc", 0666, parent, &proc_fops);
  • Add the module exit function which is executed when the module is unloaded from the kernel.

static void __exit etx_driver_exit(void)
{
        /* remove complete /proc/etx */
        proc_remove(parent);

        device_destroy(dev_class, dev);
        class_destroy(dev_class);
        cdev_del(&etx_cdev);
        unregister_chrdev_region(dev, 1);
        pr_info("Device Driver Remove...Done!!!\n");
}
  • proc_remove used to remove the complete parent directory.

proc_remove(parent);
  • Add procfs APIs

static int open_proc(struct inode *inode, struct file *file)
{
        pr_info("proc file opened.....\t");
        return 0;
}


static int release_proc(struct inode *inode, struct file *file)
{
        pr_info("proc file released.....\n");
        return 0;
}


static ssize_t read_proc(struct file *filp, char __user *buffer, size_t length, loff_t *offset)
{
        pr_info("proc file read.....\n");
        if (len)
                len = 0;
        else {
                len = 1;
                return 0;
        }

        if (copy_to_user(buffer, etx_array, 20))
                pr_err("Data Send : Err!\n");

        return length;
}

// This function will be called when we write the procfs file

static ssize_t write_proc(struct file *filp, const char *buff, size_t len, loff_t *off)
{
        pr_info("proc file wrote.....\n");

        if (copy_from_user(etx_array, buff, len))
                pr_err("Data Write : Err!\n");

        return len;
}

// This function will be called when we open the Device file

static int etx_open(struct inode *inode, struct file *file)
{
        pr_info("Device File Opened...!!!\n");
        return 0;
}

// This function will be called when we close the Device file

static int etx_release(struct inode *inode, struct file *file)
{
        pr_info("Device File Closed...!!!\n");
        return 0;
}


static ssize_t etx_read(struct file *filp, char __user *buf, size_t len, loff_t *off)
{
        pr_info("Read function\n");
        return 0;
}


static ssize_t etx_write(struct file *filp, const char __user *buf, size_t len, loff_t *off)
{
        pr_info("Write Function\n");
        return len;
}
  • Add the mention init and exit function which is executed the module is loaded and unloaded.

module_init(etx_driver_init);
module_exit(etx_driver_exit);
  1#include <linux/kernel.h>
  2#include <linux/init.h>
  3#include <linux/module.h>
  4#include <linux/kdev_t.h>
  5#include <linux/fs.h>
  6#include <linux/cdev.h>
  7#include <linux/device.h>
  8#include <linux/slab.h>                 //kmalloc()
  9#include <linux/uaccess.h>              //copy_to/from_user()
 10#include <linux/ioctl.h>
 11#include <linux/proc_fs.h>
 12#include <linux/err.h>
 13
 14MODULE_LICENSE("GPL");
 15MODULE_AUTHOR("linux_usr");
 16MODULE_DESCRIPTION("Simple Linux device driver - Process Filesystem");
 17
 18int32_t value;
 19char etx_array[20] = "try_proc_array\n";
 20static int len = 1;
 21
 22
 23dev_t dev;
 24static struct class *dev_class;
 25static struct cdev etx_cdev;
 26static struct proc_dir_entry *parent;
 27
 28
 29static int      __init etx_driver_init(void);
 30static void     __exit etx_driver_exit(void);
 31
 32
 33static int      etx_open(struct inode *inode, struct file *file);
 34static int      etx_release(struct inode *inode, struct file *file);
 35static ssize_t  etx_read(struct file *filp, char __user *buf, size_t len, loff_t *off);
 36static ssize_t  etx_write(struct file *filp, const char *buf, size_t len, loff_t *off);
 37
 38
 39static int      open_proc(struct inode *inode, struct file *file);
 40static int      release_proc(struct inode *inode, struct file *file);
 41static ssize_t  read_proc(struct file *filp, char __user *buffer, size_t length, loff_t *offset);
 42static ssize_t  write_proc(struct file *filp, const char *buff, size_t len, loff_t *off);
 43
 44static const struct file_operations fops = {
 45	.owner          = THIS_MODULE,
 46	.read           = etx_read,
 47	.write          = etx_write,
 48	.open           = etx_open,
 49	.release        = etx_release,
 50};
 51
 52static const struct file_operations proc_fops = {
 53	.open = open_proc,
 54	.read = read_proc,
 55	.write = write_proc,
 56	.release = release_proc
 57};
 58
 59
 60static int open_proc(struct inode *inode, struct file *file)
 61{
 62	pr_info("proc file opened.....\t");
 63	return 0;
 64}
 65
 66
 67static int release_proc(struct inode *inode, struct file *file)
 68{
 69	pr_info("proc file released.....\n");
 70	return 0;
 71}
 72
 73
 74static ssize_t read_proc(struct file *filp, char __user *buffer, size_t length, loff_t *offset)
 75{
 76	pr_info("proc file read.....\n");
 77	if (len)
 78		len = 0;
 79	else {
 80		len = 1;
 81		return 0;
 82	}
 83
 84	if (copy_to_user(buffer, etx_array, 20))
 85		pr_err("Data Send : Err!\n");
 86
 87	return length;
 88}
 89
 90// This function will be called when we write the procfs file
 91
 92static ssize_t write_proc(struct file *filp, const char *buff, size_t len, loff_t *off)
 93{
 94	pr_info("proc file wrote.....\n");
 95
 96	if (copy_from_user(etx_array, buff, len))
 97		pr_err("Data Write : Err!\n");
 98
 99	return len;
100}
101
102// This function will be called when we open the Device file
103
104static int etx_open(struct inode *inode, struct file *file)
105{
106	pr_info("Device File Opened...!!!\n");
107	return 0;
108}
109
110// This function will be called when we close the Device file
111
112static int etx_release(struct inode *inode, struct file *file)
113{
114	pr_info("Device File Closed...!!!\n");
115	return 0;
116}
117
118
119static ssize_t etx_read(struct file *filp, char __user *buf, size_t len, loff_t *off)
120{
121	pr_info("Read function\n");
122	return 0;
123}
124
125
126static ssize_t etx_write(struct file *filp, const char __user *buf, size_t len, loff_t *off)
127{
128	pr_info("Write Function\n");
129	return len;
130}
131
132
133static int __init etx_driver_init(void)
134{
135	if ((alloc_chrdev_region(&dev, 0, 1, "etx_Dev")) < 0) {
136		pr_info("Cannot allocate major number\n");
137		return -1;
138	}
139	pr_info("Major = %d Minor = %d\n", MAJOR(dev), MINOR(dev));
140
141
142	/*Creating cdev structure*/
143	cdev_init(&etx_cdev, &fops);
144
145	/*Adding character device to the system*/
146	if ((cdev_add(&etx_cdev, dev, 1)) < 0) {
147		pr_info("Cannot add the device to the system\n");
148		goto r_class;
149	}
150
151	/*Creating struct class*/
152	dev_class = class_create(THIS_MODULE, "etx_class");
153	if (IS_ERR(dev_class)) {
154		pr_info("Cannot create the struct class\n");
155		goto r_class;
156	}
157
158	/*Creating device*/
159	if (IS_ERR(device_create(dev_class, NULL, dev, NULL, "etx_device"))) {
160		pr_info("Cannot create the Device 1\n");
161		goto r_device;
162	}
163
164	/*Create proc directory. It will create a directory under "/proc" */
165	parent = proc_mkdir("etx", NULL);
166
167	if (parent == NULL) {
168		pr_info("Error creating proc entry");
169		goto r_device;
170	}
171
172	/*Creating Proc entry under "/proc/etx/" */
173	proc_create("etx_proc", 0666, parent, &proc_fops);
174
175	pr_info("Device Driver Insert...Done!!!\n");
176	return 0;
177
178r_device:
179	class_destroy(dev_class);
180r_class:
181	unregister_chrdev_region(dev, 1);
182	return -1;
183}
184
185
186static void __exit etx_driver_exit(void)
187{
188	/* remove complete /proc/etx */
189	proc_remove(parent);
190
191	device_destroy(dev_class, dev);
192	class_destroy(dev_class);
193	cdev_del(&etx_cdev);
194	unregister_chrdev_region(dev, 1);
195	pr_info("Device Driver Remove...Done!!!\n");
196}
197
198module_init(etx_driver_init);
199module_exit(etx_driver_exit);
1obj-m += driver_proc.o
2all:
3	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
4clean:
5	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
  • Run Make to compile the module.

$make
make -C /lib/modules/5.4.0-150-generic/build M=$HOME/kernel_driver_proc modules
make[1]: Entering directory '/usr/src/linux-headers-5.4.0-150-generic'
  CC [M]  $HOME/kernel_driver_proc/driver_proc.o
  Building modules, stage 2.
  MODPOST 1 modules
  CC [M]  $HOME/kernel_driver_proc/driver_proc.mod.o
  LD [M]  $HOME/kernel_driver_proc/driver_proc.ko
make[1]: Leaving directory '/usr/src/linux-headers-5.4.0-150-generic'
  • Run ls to check if driver_proc.ko is generated or not.

$ ls -l
total 36
-rw-rw-r-- 1 test test  154 Feb 26 13:18 Makefile
-rw-rw-r-- 1 test test   47 Feb 26 13:18 modules.order
-rw-rw-r-- 1 test test    0 Feb 26 13:18 Module.symvers
-rw-rw-r-- 1 test test  820 Feb 26 13:18 driver_proc.c
-rw-rw-r-- 1 test test 5880 Feb 26 13:18 driver_proc.ko
-rw-rw-r-- 1 test test   47 Feb 26 13:18 driver_proc.mod
-rw-rw-r-- 1 test test  919 Feb 26 13:18 driver_proc.mod.c
-rw-rw-r-- 1 test test 3448 Feb 26 13:18 driver_proc.mod.o
-rw-rw-r-- 1 test test 3320 Feb 26 13:18 driver_proc.o
  • Run insmod to load the module.

$ sudo insmod ./driver_proc.ko
  • Once the module is loaded into kernel do read and write operations to procfs.

$ ls /proc/etx/
etx_proc

$ cat /proc/etx/etx_proc
try_proc_array

$ echo "device driver" > /proc/etx/etx_proc

$ cat /proc/etx/etx_proc
device driver
  • Run rmmod to unload the module.

$ sudo rmmod driver_proc
  • Check dmesg and we can clearly see the read and write operations are executed.

[ 9680.443352] Major = 239 Minor = 0
[ 9680.443468] Device Driver Insert...Done!!!
[ 9773.486681] proc file opened.....
[ 9773.486688] proc file read.....
[ 9773.536962] proc file read.....
[ 9773.537018] proc file released.....
[ 9791.229041] proc file opened.....
[ 9791.229055] proc file wrote.....
[ 9791.229059] proc file released.....
[ 9797.128045] proc file opened.....
[ 9797.128055] proc file read.....
[ 9797.142568] proc file read.....
[ 9797.142597] proc file released.....
[ 9823.179016] Device Driver Remove...Done!!!

API

Learning

proc_mkdir

To create the directory under /proc/*

proc_create

To create a proc entry

proc_remove

To remove the complete parent directory

See Also