This article is a continuation of the Series on Linux Device Driver and carries the discussion on Linux device drivers and their implementation. The aim of this series is to provide easy and practical examples that anyone can understand. This is the ProcFS in Linux kernel driver tutorial – Linux Device Driver Tutorial Part 9.
You can also read sysfs, procfs, Workqueue, Completion, Softirq, and threaded IRQ in the Linux device driver.
Introduction
In this tutorial, we will see Procfs in Linux.
Procfs in Linux
Introduction
Many or most Linux users have at least heard of proc. Some of you may wonder why this folder is so important.
On the root, there is a folder titled “proc”. This folder is not really on /dev/sda1
or where ever you think the folder resides. This folder is a mount point for the procfs (Process Filesystem) which is a filesystem in memory. Many processes store information about themselves on this virtual filesystem. ProcFS also stores other system information.
It 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 entry “meminfo
” gives the details of the memory being used in the system.
To read the data in this entry just run
cat /proc/meminfo |
Similarly the “modules
” entry gives details of all the modules that are currently a part of the kernel.
cat /proc/modules |
It gives similar information as lsmod
. Like this more, proc entries are there.
/proc/devices
— registered character and block major numbers/proc/iomem
— on-system physical RAM and bus device addresses/proc/ioports
— on-system I/O port addresses (especially for x86 systems)/proc/interrupts
— registered interrupt request numbers/proc/softirqs
— registered soft IRQs/proc/swaps
— currently active swaps/proc/kallsyms
— running kernel symbols, including from loaded modules/proc/partitions
— currently connected block devices and their partitions/proc/filesystems
— currently active filesystem drivers/proc/cpuinfo
— information about the CPU(s) on the system
Most proc files are read-only and only expose kernel information to user space programs.
proc files can also be used to control and modify kernel behavior on the fly. The proc files need to be writable in this case.
For example, to enable IP forwarding of iptable
, one can use the command below,
echo 1 > /proc/sys/net/ipv4/ip_forward
The proc file system is also very useful when we want to debug a kernel module. While debugging we might want to know the values of various variables in the module or maybe the data that the module is handling. In such situations, we can create a proc entry for ourselves and dump whatever data we want to look into in the entry.
We will be using the same example character driver that we created in the previous post to create the proc entry.
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.
Creating procfs directory
You can create the directory under /proc/* using the below API.
where,
|
Creating Procfs Entry
The creation of proc entries has undergone a considerable change in kernel version 3.10 and above. In this post, we will see one of the methods we can use in Linux kernel version 3.10. Let us see how we can create proc entries in version 3.10.
struct proc_dir_entry *proc_create ( const char *name, umode_t mode, struct proc_dir_entry *parent, const struct file_operations *proc_fops ) |
The function is defined in proc_fs.h.
Where,
<name
>: The name of the proc entry
<mode
>: The access mode for proc entry
<parent
>: The name of the parent directory under /proc. If NULL is passed as a parent, the /proc directory will be set as a parent.
<proc_fops
>: The structure in which the file operations for the proc entry will be created.
Note: The above proc_create is valid in the Linux Kernel v3.10 to v5.5. From v5.6, there is a change in this API. The fourth argument
const struct file_operations *proc_fops
is changed toconst struct proc_ops *proc_ops
.
For example to create a proc entry by the name “etx_proc” under /proc the above function will be defined as below,
proc_create("etx_proc",0666,NULL,&proc_fops);
This proc entry should be created in the Driver init function.
If you are using the kernel version below 3.10, please use the below functions to create proc entry.
create_proc_read_entry() create_proc_entry() |
Both of these functions are defined in the file linux/proc_fs.h.
The create_proc_entry is a generic function that allows creating both the read as well as the write entries.
create_proc_read_entry
is a function specific to create only read entries.
It is possible that most of the proc entries are created to read data from the kernel space that is why the kernel developers have provided a direct function to create a read proc entry.
Procfs File System
Now we need to create file_operations structure proc_fops in which we can map the read and write functions for the proc entry.
static struct file_operations proc_fops = { .open = open_proc, .read = read_proc, .write = write_proc, .release = release_proc };
This is like a device driver file system. We need to register our proc entry filesystem. If you are using the kernel version below 3.10, this will not work. If you are using the Linux kernel v5.6 and above, you should not use this structure. You have to use struct proc_ops
instead of struct file_operations
like below.
static struct proc_ops proc_fops = { .proc_open = open_proc, .proc_read = read_proc, .proc_write = write_proc, .proc_release = release_proc };
Next, we need to add all functions to the driver.
Open and Release Function
These functions are optional.
static int open_proc(struct inode *inode, struct file *file) { printk(KERN_INFO "proc file opend.....\t"); return 0; } static int release_proc(struct inode *inode, struct file *file) { printk(KERN_INFO "proc file released.....\n"); return 0; }
Write Function
The write function will receive data from the user space using the function copy_from_user
into an array “etx_array”.
Thus the write function will look as below.
static ssize_t write_proc(struct file *filp, const char *buff, size_t len, loff_t * off) { printk(KERN_INFO "proc file write.....\t"); copy_from_user(etx_array,buff,len); return len; }
Read Function
Once data is written to the proc entry we can read from the proc entry using a read function, i.e transfer data to the user space using the function copy_to_user function.
The read function can be as below.
static ssize_t read_proc(struct file *filp, char __user *buffer, size_t length,loff_t * offset) { printk(KERN_INFO "proc file read.....\n"); if(len) len=0; else{ len=1; return 0; } copy_to_user(buffer,etx_array,20); return length;; }
Remove Proc Entry
Proc entry should be removed in the Driver exit function using the below function.
void remove_proc_entry(const char *name, struct proc_dir_entry *parent);
Example:
remove_proc_entry("etx_proc",NULL);
And you can remove the complete parent directory using proc_remove(struct proc_dir_entry *parent)
.
Procfs in Linux – Complete Driver Source Code
As there are changes in the procfs file system in the Linux kernel 3.10 and 5.6, we have added a macro called LINUX_KERNEL_VERSION
. You have to mention your Linux kernel version. Based on that, we will control the APIs in this source code.
Note:
You can follow this format for this LINUX_KERNEL_VERSION
.
Example:
Your Linux Kernel version | LINUX_KERNEL_VERSION |
v3.10 | 310 |
v5.6 | 506 |
v5.10 | 510 |
[Get the Source code from the GitHub]
/***************************************************************************//** * \file driver.c * * \details Simple Linux device driver (procfs) * * \author EmbeTronicX * * \Tested with Linux raspberrypi 5.10.27-v7l-embetronicx-custom+ * * *******************************************************************************/ #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> //kmalloc() #include<linux/uaccess.h> //copy_to/from_user() #include <linux/ioctl.h> #include<linux/proc_fs.h> #include <linux/err.h> /* ** I am using the kernel 5.10.27-v7l. So I have set this as 510. ** If you are using the kernel 3.10, then set this as 310, ** and for kernel 5.1, set this as 501. Because the API proc_create() ** changed in kernel above v5.5. ** */ #define LINUX_KERNEL_VERSION 510 #define WR_VALUE _IOW('a','a',int32_t*) #define RD_VALUE _IOR('a','b',int32_t*) int32_t value = 0; char etx_array[20]="try_proc_array\n"; static int len = 1; dev_t dev = 0; static struct class *dev_class; static struct cdev etx_cdev; static struct proc_dir_entry *parent; /* ** Function Prototypes */ static int __init etx_driver_init(void); static void __exit etx_driver_exit(void); /*************** Driver Functions **********************/ 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 long etx_ioctl(struct file *file, unsigned int cmd, unsigned long arg); /***************** Procfs Functions *******************/ 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); /* ** File operation sturcture */ static struct file_operations fops = { .owner = THIS_MODULE, .read = etx_read, .write = etx_write, .open = etx_open, .unlocked_ioctl = etx_ioctl, .release = etx_release, }; #if ( LINUX_KERNEL_VERSION > 505 ) /* ** procfs operation sturcture */ static struct proc_ops proc_fops = { .proc_open = open_proc, .proc_read = read_proc, .proc_write = write_proc, .proc_release = release_proc }; #else //LINUX_KERNEL_VERSION > 505 /* ** procfs operation sturcture */ static struct file_operations proc_fops = { .open = open_proc, .read = read_proc, .write = write_proc, .release = release_proc }; #endif //LINUX_KERNEL_VERSION > 505 /* ** This function will be called when we open the procfs file */ static int open_proc(struct inode *inode, struct file *file) { pr_info("proc file opend.....\t"); return 0; } /* ** This function will be called when we close the procfs file */ static int release_proc(struct inode *inode, struct file *file) { pr_info("proc file released.....\n"); return 0; } /* ** This function will be called when we read the procfs file */ 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; } /* ** This function will be called when we read the Device file */ static ssize_t etx_read(struct file *filp, char __user *buf, size_t len, loff_t *off) { pr_info("Read function\n"); return 0; } /* ** This function will be called when we write the Device file */ 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; } /* ** This function will be called when we write IOCTL on the Device file */ static long etx_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { switch(cmd) { case WR_VALUE: if( copy_from_user(&value ,(int32_t*) arg, sizeof(value)) ) { pr_err("Data Write : Err!\n"); } pr_info("Value = %d\n", value); break; case RD_VALUE: if( copy_to_user((int32_t*) arg, &value, sizeof(value)) ) { pr_err("Data Read : Err!\n"); } break; default: pr_info("Default\n"); break; } return 0; } /* ** Module Init function */ static int __init etx_driver_init(void) { /*Allocating Major number*/ 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*/ if(IS_ERR(dev_class = class_create(THIS_MODULE,"etx_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; } /* ** Module exit function */ static void __exit etx_driver_exit(void) { /* Removes single proc entry */ //remove_proc_entry("etx/etx_proc", parent); /* 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"); } module_init(etx_driver_init); module_exit(etx_driver_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("EmbeTronicX <[email protected]>"); MODULE_DESCRIPTION("Simple Linux device driver (procfs)"); MODULE_VERSION("1.6");
MakeFile
obj-m += driver.o KDIR = /lib/modules/$(shell uname -r)/build all: make -C $(KDIR) M=$(shell pwd) modules clean: make -C $(KDIR) M=$(shell pwd) clean
Building and Testing Driver
- Build the driver by using Makefile (
sudo make
) - Load the driver using
sudo insmod driver.ko
- Check our procfs entry using ls in procfs directory
linux@embetronicx-VirtualBox:ls /proc/ filesystems iomem kallsyms modules partitions etx
- Now our procfs entry is there under the /proc directory.
- Now you can read the procfs variable using
cat
.
linux@embetronicx-VirtualBox: cat /proc/etx/etx_proc try_proc_array
- We initialized the etx_array with “try_proc_array”. That’s why we got “try_proc_array”.
- Now do proc write using
echo
command and check usingcat
.
linux@embetronicx-VirtualBox: echo "device driver proc" > /proc/etx/etx_proc linux@embetronicx-VirtualBox: cat /proc/etx/etx_proc device driver proc
- We got the same string that was passed to the driver using procfs.
This is a simple example of using procfs in the device drivers. This is just basic. I hope this might help you. In our next tutorial, we will discuss the waitqueue in the Linux device drivers.
Please find the other Linux device driver tutorials here.
You can also read the below tutorials.

Embedded Software | Firmware | Linux Devic Deriver | RTOS
Hi, I’m SLR. I am a tech blogger and an Embedded Engineer. I am always eager to learn and explore tech-related concepts. And also, I wanted to share my knowledge with everyone in a more straightforward way with easy practical examples. I strongly believe that learning by doing is more powerful than just learning by reading. I love to do experiments. If you want to help or support me on my journey, consider sharing my articles, or Buy me a Coffee! Thank you for reading my blog! Happy learning!