This article is a continuation of the Series on Linux Device Drivers 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 EXPORT_SYMBOL in Linux Device Driver – Linux Device Driver Tutorial Part 29.
You can also read, Linux driver introduction, First Device driver, passing arguments to device driver, Signals, Procfs, and IOCTL in the Linux device driver.
When you are writing multiple drivers (modules) in the same device, you may want to use some of the functions from one module to another module. How will we do that? If we use only extern
then it won’t help you. We must have used some advanced things. So, We have to tell the kernel, that I want to share this function with other modules.
For example, take printk()
function. This function will be defined in source/kernel/printk/printk.c
. Then how can we able to access that printk()
in our driver?
In this article, we will see how to do it.
EXPORT_SYMBOL in Linux Device Driver
Introduction
In a programming language, a symbol is either a variable or a function. Or more generally, we can say, a symbol is a name representing space in the memory, which stores data (variable, for reading and writing) or instructions (function, for executing).
When you look at some kernel codes, you may find EXPORT_SYMBOL()
very often. Have you wondered any time what the heck is that?
In the Linux Kernel 2.4, all the non-static symbols are exported to the kernel space automatically. But later, in Linux Kernel 2.6 instead of exporting all non-static symbols, they wanted to export the only symbols which are marked by EXPORT_SYMBOL()
macro.
EXPORT_SYMBOL’s role
When some symbols (variables or functions) are using EXPORT_SYMBOL
macro (ex. EXPORT_SYMBOL(func_name)
), those symbols are exposed to all the loadable kernel drivers. You can call them directly in your kernel module without modifying the kernel code. In other words, It tells the kbuild
mechanism that the symbol referred to should be part of the global list of kernel symbols. That allows the kernel modules to access them.
Only the symbols that have been explicitly exported can be used by other modules.
Another macro is also available to export the symbols like EXPORT_SYMBOL
. That is EXPORT_SYMBOL_GPL()
.
EXPORT_SYMBOL
exports the symbol to any loadable module.
EXPORT_SYMBOL_GPL
exports the symbol only to GPL-licensed modules.
How to use EXPORT_SYMBOL?
- Declare and define the symbol (functions or variables) which you want to make it visible to other kernel modules. Then below the definition, use
EXPORT_SYMBOL(symbol name)
. Now it is visible to all loadable modules. - Now take the kernel driver who is gonna use the above-exported symbol. Declare the symbol using
extern
. Then use the symbol directly. - Finally, load the module first, who has the definition of the export symbol. Then load the caller module using
insmod
“.
Limitation
- That symbol should not be
static
orinline.
- Order of loading the driver is matter. ie. We should load the module which has the definition of the symbol, then only we can load the module that is using that symbol.
Driver Source Code – EXPORT_SYMBOL in Linux
First, I will explain to you the concept of driver code attached below.
In this tutorial, we have two drivers.
Driver 1 has one function called etx_shared_func
and one global variable called etx_count.
This function and variable have been shared among with all the loadable modules using EXPORT_SYMBOL.
Driver 2 will be using that variable and function which are shared by Driver 1. When we read Driver 2, then it will call the shared function and we can read that variable also.
Let’s see the source code below.
[Get the source code from GitHub]
driver1.c
/***************************************************************************//** * \file driver1.c * * \details Simple Linux device driver (EXPORT_SYMBOL) * * \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/err.h> dev_t dev = 0; static struct class *dev_class; static struct cdev etx_cdev; 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); /******************************************************/ int etx_count = 0; //Exported variable /* ** exported function */ void etx_shared_func(void) { pr_info("Shared function been called!!!\n"); etx_count++; } //EXPORT_SYMBOL_GPL(etx_shared_func); EXPORT_SYMBOL(etx_shared_func); EXPORT_SYMBOL(etx_count); //File operation structure static struct file_operations fops = { .owner = THIS_MODULE, .read = etx_read, .write = etx_write, .open = etx_open, .release = etx_release, }; /* ** 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("Data Read : Done!\n"); return 1; } /* ** 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("Data Write : Done!\n"); return len; } /* ** Module Init function */ static int __init etx_driver_init(void) { /*Allocating Major number*/ if((alloc_chrdev_region(&dev, 0, 1, "etx_Dev1")) <0){ pr_err("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_err("Cannot add the device to the system\n"); goto r_class; } /*Creating struct class*/ if(IS_ERR(dev_class = class_create(THIS_MODULE,"etx_class1"))){ pr_err("Cannot create the struct class\n"); goto r_class; } /*Creating device*/ if(IS_ERR(device_create(dev_class,NULL,dev,NULL,"etx_device1"))){ pr_err("Cannot create the Device 1\n"); goto r_device; } pr_info("Device Driver 1 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) { device_destroy(dev_class,dev); class_destroy(dev_class); cdev_del(&etx_cdev); unregister_chrdev_region(dev, 1); pr_info("Device Driver 1 Remove...Done!!!\n"); } module_init(etx_driver_init); module_exit(etx_driver_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("EmbeTronicX <[email protected]>"); MODULE_DESCRIPTION("EXPORT_SYMBOL Driver - 1"); MODULE_VERSION("1.25");
driver2.c
/***************************************************************************//** * \file driver2.c * * \details Simple Linux device driver (EXPORT_SYMBOL) * * \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/err.h> dev_t dev = 0; static struct class *dev_class; static struct cdev etx_cdev; 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); extern int etx_count; void etx_shared_func(void); //Function declaration is by defalut extern //File operation structure static struct file_operations fops = { .owner = THIS_MODULE, .read = etx_read, .write = etx_write, .open = etx_open, .release = etx_release, }; /* ** 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) { etx_shared_func(); pr_info("%d time(s) shared function called!\n", etx_count); pr_info("Data Read : Done!\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("Data Write : Done!\n"); return len; } /* ** Module Init function */ static int __init etx_driver_init(void) { /*Allocating Major number*/ if((alloc_chrdev_region(&dev, 0, 1, "etx_Dev2")) <0){ pr_err("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_err("Cannot add the device to the system\n"); goto r_class; } /*Creating struct class*/ if(IS_ERR(dev_class = class_create(THIS_MODULE,"etx_class2"))){ pr_err("Cannot create the struct class\n"); goto r_class; } /*Creating device*/ if(IS_ERR(device_create(dev_class,NULL,dev,NULL,"etx_device2"))){ pr_err("Cannot create the Device 1\n"); goto r_device; } pr_info("Device Driver 2 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) { device_destroy(dev_class,dev); class_destroy(dev_class); cdev_del(&etx_cdev); unregister_chrdev_region(dev, 1); pr_info("Device Driver 2 Remove...Done!!!\n"); } module_init(etx_driver_init); module_exit(etx_driver_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("EmbeTronicX <[email protected]>"); MODULE_DESCRIPTION("EXPORT_SYMBOL Driver - 2"); MODULE_VERSION("1.26");
MakeFile
obj-m += driver1.o obj-m += driver2.o KDIR = /lib/modules/$(shell uname -r)/build all: make -C $(KDIR) M=$(shell pwd) modules clean: make -C $(KDIR) M=$(shell pwd) clean
Compiling and Testing Driver
- Build the driver by using Makefile (
sudo make
) - After compiling, you can able to see the file named as “Module.symvers“. If you open that file, then our shared function and variable will be mentioned there.
0x1db7034a etx_shared_func /home/embetronicx/driver/driver1 EXPORT_SYMBOL 0x6dcb135c etx_count /home/embetronicx/driver/driver1 EXPORT_SYMBOL
- Load driver 1 using
sudo insmod driver1.ko
(Driver 1 should be loaded first. If you try to load Driver 2 first, then you will get an error like “insmod: ERROR: could not insert module driver2.ko: Unknown symbol in module“). - Load driver 2 using
sudo insmod driver2.ko
- Now check the
dmesg
[ 393.814900] Major = 246 Minor = 0 [ 393.818413] Device Driver 1 Insert...Done!!! [ 397.620296] Major = 245 Minor = 0 [ 397.629002] Device Driver 2 Insert...Done!!!
- Then do
cat /proc/kallsyms | grep etx_shared_func
orcat /proc/kallsyms | grep etx_count
to check whether our shared function and variable become part of the kernel’s symbol table or not. - Now we can read the driver by using
sudo cat /dev/etx_device2
- Now check the
dmesg
[ 403.739998] Device File Opened...!!! [ 403.740018] Shared function been called!!! [ 403.740021] 1 time(s) shared function called! [ 403.740023] Data Read : Done! [ 403.740028] Device File Closed...!!!
- Now we can see the print from the shared function and variable count also.
- Unload module 2 using
sudo rmmod driver2
(Driver 2 should be unloaded first. If you unload Driver 1 first, then you will get an error like “rmmod: ERROR: Module driver1 is in use by driver2“). - Unload module 1 using
sudo rmmod driver1
In our next tutorial, we will discuss atomic variables in the Linux device driver.
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!