Linux Device Driver Tutorial Part 29 – EXPORT_SYMBOL in Linux Device Driver

This is the Series on Linux Device Driver. The aim of this series is to provide easy and practical examples that anyone can understand. This is the Linux Device Driver Tutorial Part 29 – EXPORT_SYMBOL in Linux Device Driver.

Note: In this article, I assume that you already know the basic routines for kernel module development.

When you are writing multiple drivers (modules) in the same device, you may want to use some of the functions form 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 to 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 driver. 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 or inline.
  • 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 who 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 has 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 this 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 the GitHub]

driver1.c

/***************************************************************************//**
*  \file       driver1.c
*
*  \details    Simple Linux device driver (EXPORT_SYMBOL)
*
*  \author     EmbeTronicX
*
* *******************************************************************************/
#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>
 
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);
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;
void etx_shared_func(void)
{
        printk(KERN_INFO "Shared function been called!!!\n");
    etx_count++;
}
//EXPORT_SYMBOL_GPL(etx_shared_func);
EXPORT_SYMBOL(etx_shared_func);
EXPORT_SYMBOL(etx_count);
 
static struct file_operations fops =
{
        .owner          = THIS_MODULE,
        .read           = etx_read,
        .write          = etx_write,
        .open           = etx_open,
        .release        = etx_release,
};
 
static int etx_open(struct inode *inode, struct file *file)
{
        printk(KERN_INFO "Device File Opened...!!!\n");
        return 0;
}
 
static int etx_release(struct inode *inode, struct file *file)
{
        printk(KERN_INFO "Device File Closed...!!!\n");
        return 0;
}
 
static ssize_t etx_read(struct file *filp, char __user *buf, size_t len, loff_t *off)
{
        printk(KERN_INFO "Data Read : Done!\n");
        return 1;
}

static ssize_t etx_write(struct file *filp, const char __user *buf, size_t len, loff_t *off)
{
        printk(KERN_INFO "Data Write : Done!\n");
        return len;
}
 
static int __init etx_driver_init(void)
{
        /*Allocating Major number*/
        if((alloc_chrdev_region(&dev, 0, 1, "etx_Dev1")) <0){
                printk(KERN_INFO "Cannot allocate major number\n");
                return -1;
        }
        printk(KERN_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){
            printk(KERN_INFO "Cannot add the device to the system\n");
            goto r_class;
        }
 
        /*Creating struct class*/
        if((dev_class = class_create(THIS_MODULE,"etx_class1")) == NULL){
            printk(KERN_INFO "Cannot create the struct class\n");
            goto r_class;
        }
 
        /*Creating device*/
        if((device_create(dev_class,NULL,dev,NULL,"etx_device1")) == NULL){
            printk(KERN_INFO "Cannot create the Device 1\n");
            goto r_device;
        }
        printk(KERN_INFO "Device Driver 1 Insert...Done!!!\n");
        return 0;
 
r_device:
        class_destroy(dev_class);
r_class:
        unregister_chrdev_region(dev,1);
        return -1;
}
 
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);
        printk(KERN_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
*
* *******************************************************************************/
#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>
 
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);
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
 
static struct file_operations fops =
{
        .owner          = THIS_MODULE,
        .read           = etx_read,
        .write          = etx_write,
        .open           = etx_open,
        .release        = etx_release,
};
 
static int etx_open(struct inode *inode, struct file *file)
{
        printk(KERN_INFO "Device File Opened...!!!\n");
        return 0;
}
 
static int etx_release(struct inode *inode, struct file *file)
{
        printk(KERN_INFO "Device File Closed...!!!\n");
        return 0;
}
 
static ssize_t etx_read(struct file *filp, char __user *buf, size_t len, loff_t *off)
{
    etx_shared_func();
        printk(KERN_INFO "%d time(s) shared function called!\n", etx_count);
        printk(KERN_INFO "Data Read : Done!\n");
        return 0;
}

static ssize_t etx_write(struct file *filp, const char __user *buf, size_t len, loff_t *off)
{
        printk(KERN_INFO "Data Write : Done!\n");
        return len;
}
 
static int __init etx_driver_init(void)
{
        /*Allocating Major number*/
        if((alloc_chrdev_region(&dev, 0, 1, "etx_Dev2")) <0){
                printk(KERN_INFO "Cannot allocate major number\n");
                return -1;
        }
        printk(KERN_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){
            printk(KERN_INFO "Cannot add the device to the system\n");
            goto r_class;
        }
 
        /*Creating struct class*/
        if((dev_class = class_create(THIS_MODULE,"etx_class2")) == NULL){
            printk(KERN_INFO "Cannot create the struct class\n");
            goto r_class;
        }
 
        /*Creating device*/
        if((device_create(dev_class,NULL,dev,NULL,"etx_device2")) == NULL){
            printk(KERN_INFO "Cannot create the Device 1\n");
            goto r_device;
        }
        printk(KERN_INFO "Device Driver 2 Insert...Done!!!\n");
        return 0;
 
r_device:
        class_destroy(dev_class);
r_class:
        unregister_chrdev_region(dev,1);
        return -1;
}
 
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);
        printk(KERN_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 the driver 1 using sudo insmod driver1.ko(Driver 1 should be loaded first. If you try to load the Driver 2 first, then you will get an error like “insmod: ERROR: could not insert module driver2.ko: Unknown symbol in module“).
  • Load the driver 1 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_funcorcat /proc/kallsyms | grep etx_countto check whether our shared function and variable become the part of 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 shared function and variable count also.
  • Unload module 2 using sudo rmmod driver2(Driver 2 should be unloaded first. If you unload the 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.

5 1 vote
Article Rating
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
Ads Blocker Image Powered by Code Help Pro
Ad Blocker Detected!!!

It seems you are using adblocking software that prevents the page from fully loading.

The revenue we earn by advertisements is used to manage this website. Still, If you want to disable the ads, please become a premium member. Ads are disabled for Premium members.

Or please add www.embetronicx.com to your ad blocker whitelist or disable your adblocking software and reload the page.

Note: Please disable the ad blocker to access the membership pages also.

False Detection? Please write a mail to [email protected]

 
Refresh