1. Introduction
This chapter explains the basic elements for writing a kernel module. By end of this chapter readers can
* Write a kernel module
* Pass arguments to kernel module
* Export from kernel modules
For executing the sample codes, I suggest to use ubuntu machine with Linux kernel version > 2.6
Note ::
All examples in this post are executed and tested with
1.1 Simplest kernel module
#include <linux/module.h> /* Needed by all modules */
#include <linux/kernel.h> /* Needed for KERN_ALERT */
MODULE_LICENSE("GPL"); /* Setting Licence to GPL */
int __init ourinitmodule(void) /* Will be called during insmod <module>.ko */
{
printk(KERN_ALERT "\n Welcome to sample kernel module.... \n");
return 0;
}
void __exit ourcleanupmodule(void) /* Will be called during rmmod <module> */{
printk(KERN_ALERT "\n Thanks....Exiting. \n");
return;
}
/* Macros for init and cleanup module */
module_init(ourinitmodule);
module_exit(ourcleanupmodule);
Refer the following github example for ready-to-run code.
https://github.com/jeyaramvrp/kernel-module-programming/tree/master/helloworld
1.2 Passing command line arguments to a kernel module
#include <linux/module.h>
#include <linux/kernel.h>
/* Added for module_param */
#include <linux/moduleparam.h>
MODULE_LICENSE("GPL");
/* Module param : Integer data */
static int data;
module_param(data, int, S_IRUSR|S_IWUSR);
/* Module param : String data */
static char *mystr = "Default";
module_param(mystr, charp, 0);
/* Module param : Integer Array data */
static int myarray[10] = {-1};
static int count = 0;
module_param_array(myarray, int,&count, 0);
int ourinitmodule(void)
{
int tmp = 0;
/* Print data - Default values will be printed if No argument is passed. */
printk(KERN_ALERT "\n Demo for Passing arguments to kernel module \n");
printk(KERN_ALERT "\n data:%d\n", data);
printk(KERN_ALERT "\n mystr:%s\n", mystr);
for(tmp = 0; tmp < count ; tmp++)
printk(KERN_ALERT "\n myarray[%d]:%d", tmp, myarray[tmp]);
return 0;
}
void ourcleanupmodule(void)
{
printk(KERN_ALERT "\n Thanks....Exiting Passing args sample.. \n");
}
module_init(ourinitmodule);
module_exit(ourcleanupmodule);
http://stackoverflow.com/questions/10994576/passing-an-array-as-command-line-argument-for-linux-kernel-modulehttps://github.com/jeyaramvrp/kernel-module-programming/tree/master/passingargs
http://lxr.free-electrons.com/source/include/linux/moduleparam.h?a=arm#L112
1.3 Exporting a function to another kernel module(Module dependency)
#include <linux/module.h>
#include <linux/kernel.h>
#include "CommonHeader.h"
MODULE_LICENSE("GPL");
int __init ourinitmodule(void)
{
printk(KERN_ALERT "\n sample -1 init.... \n");
return 0;
}
void __exit ourcleanupmodule(void)
{
printk(KERN_ALERT "\n sample -1 Exit.... \n");
}
int sample1func()
{
printk(KERN_ALERT "\n sample -1 Exported Function .... \n");
return 0;
}
EXPORT_SYMBOL(sample1func);
module_init(ourinitmodule);
module_exit(ourcleanupmodule);
In the above kernel module(module - 1), samplefunc() is exported by this module. i.e It can be used by other kernel modules. For exporting a function/global variable1. It should not be "static"
2. Explicitly exported using EXPORT_SYMBOL() macro
There are other variants of EXPORT_SYMBOL(). It is up to reader's interest.
http://lxr.free-electrons.com/source/include/linux/export.h#L68
Lets see how other kernel module(module -2) uses the exported samplefunc()
#include <linux/module.h>
#include <linux/kernel.h>
#include "CommonHeader.h"
MODULE_LICENSE("GPL");
int __init ourinitmodule(void)
{
printk(KERN_ALERT "\n sample -2 init.... \n");
/* Making two.c to depend on one.c */
sample1func();
return 0;
}
void __exit ourcleanupmodule(void)
{
printk(KERN_ALERT "\n sample -2 Exit.... \n");
}
module_init(ourinitmodule);
module_exit(ourcleanupmodule);
Refer here for full code & Make files.https://github.com/jeyaramvrp/kernel-module-programming/tree/master/depmod-export-sym-demo
module-2 which uses samplefunc() requires module-1 loaded already. Refer the following screenshot which explains the sequence of operation.
2. Device Driver Programming
This chapter guides a kernel programmer to understand the device driver especially char device driver. By end of this chapter readers will have understanding of
* Writing a char device driver
* Using char driver at user space
Device drivers are kernel modules specifically written to handle particular hardware. Since the kernel is responsible managing the hardware, piece of code is written to handle it.
The Linux kernel provides APIs for writing the device driver so that the device seamlessly integrated in to our system. If the particular device is vendor specific(i.e Not available in market), mostly it needs driver to be written. Otherwise Linux kernel itself has lot of drivers for most of the devices available in market today.
To make the kernel generic as much as possible, built-in drivers which is already written by kernel community is controlled by configurations.
The Linux kernel drivers can be categorized in to two major types
* Character device driver
* Block device driver
Char device drivers usually deals with read/write byte on device. For example keyboard or mouse drivers. There is no much involvement of large data to handle unlike hard disk.
Block devices usually involves large data to handle such as hard disk, SD card.
The sources for character devices are kept in drivers/char/, and the sources for block devices are kept in drivers/block/. They have similar interfaces, and are very much alike, except for reading and writing. Block devices will be covered later.
2.3 Character device driver
This section will explain Char device drivers with a sample.
2.3.1 Major and Minor Number
The Linux kernel refers each driver with a number called Major number. This major number is either allocated by kernel or specified by driver developer. While the developer specifies the major number, he should be careful enough to use unused number.
Refer http://lxr.free-electrons.com/source/include/uapi/linux/major.h for already allocated major numbers.
Minor numbers represents the devices. For example, if two devices of same type is connected to system(say two MS USB mouse), then each device will be assigned a number. But these two devices will be handled by single driver.
Lets see the following screen for clearing understanding of Minor and Major number.
The Linux kernel assigns 4 as the Major number for TTY driver(Refer Here). Lets see the following screen for clearing understanding of Minor and Major number.
So each tty device is assigned with unique minor number.
Did you notice each row starts with "c" ??. This is meant for char driver. For block driver it will be "b".
2.3.2 file_operations structure
The file_operations structure is define in <linux/fs.h>. This structure is a collection of function pointers which is implemented in our drivers.
In the following driver example, only minimal operations are implemented. Just have a look at how the file operation is filled. At the end of example, you will get idea of file_operations struct.
static const struct file_operations sample_fops = {
.owner = THIS_MODULE,
.unlocked_ioctl = sample_ioctl,
.open = sample_open,
.release = sample_close
}; 2.3.3 Register and un-register device driver
The Linux kernel provides APIs for register and un register of your driver. In this registration, driver obtains major number and the list of operations supported by driver will be intimated to kernel.
major = register_chrdev(0, "samplechar", &sample_fops);
The first argument as 0 allows the kernel to allocate a major number which is available. If you want specific number, pass here instead of 0. If that number is not allocated already then it will be assigned to this driver. Best practice is to pass 0.Second argument represents the driver name which is displayed in /proc/devices
Third argument is the pointer to file_operations structure which we filled earlier.
Usually this registration is done at the init function of kernel module(i.e Driver's init module)
The un register operation is done at clean up function.
unregister_chrdev(major, "samplechar");
The major number and driver name is need for un register.2.3.4 ioctl()
ioctl() is very important function in device driver. The commands specific to devices are issued to driver through ioctls. From user space, the command is received at ioctl() and the corresponding action will be taken by driver.
Lets write a calculator with device driver APIs to demonstrate. The ioctl() function will be similar to
static long sample_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
switch(cmd)
{
case SAMPLE_IOCTL_ADD:
{
// Addition related operation
return 0;
}
case SAMPLE_IOCTL_SUB:
{
// Subtraction related operation
return 0;
}
case SAMPLE_IOCTL_MUL:
{
//Multiplication related operation
return 0;
}
case SAMPLE_IOCTL_DIV:
{
//Division related operation
return 0;
}
default:
return -EINVAL;
}
}
Based on the ioctl command from user space, driver performs operations and returns results to user space if necessary. The functions copy_to_user() and copy_from_user() is responsible for copying the information to/from user space.2.3.5 Sample driver with user space application
Refer here for the sample char driver which implements basic calculator functionality.
After compiling the driver, insert in to kernel by using insmod.
# insmod sampledrv.ko
Create the device node of type "c" with major number(say 58) in /dev
#mknod /dev/samplechar c 58 1
Now driver is ready to be used by applications. Lets write application which uses this calc driver.
#include <stdio.h> #include <stdlib.h> | |
#include <sys/ioctl.h> | |
#include <fcntl.h> | |
#include "../sampleioctl.h" | |
int main() | |
{ | |
struct calc dat; | |
int opt; | |
int fd, err=0; | |
fd = open("/dev/samplechar", O_RDWR); | |
if(fd < 0) | |
{ | |
perror("open:"); | |
return -1; | |
} | |
while(1) | |
{ | |
printf("\n 1.Add\n2.Sub\n3.Mul\n4.Div\n \ | |
5.ExitApp\nChoose Operation:" ); | |
scanf("%d", &opt); | |
if(opt == 5) | |
break; | |
if(opt < 1 || opt > 4) | |
{ | |
printf("\n Invalid Option... "); | |
continue; | |
} | |
printf("\n Enter Data1:"); | |
scanf("%d", &dat.data1); | |
printf("\n Enter Data2:"); | |
scanf("%d", &dat.data2); | |
switch(opt) | |
{ | |
case 1: | |
err=ioctl(fd, SAMPLE_IOCTL_ADD, &dat); | |
break; | |
case 2: | |
err=ioctl(fd, SAMPLE_IOCTL_SUB, &dat); | |
break; | |
case 3: | |
err=ioctl(fd, SAMPLE_IOCTL_MUL, &dat); | |
break; | |
case 4: | |
err= ioctl(fd, SAMPLE_IOCTL_DIV, &dat); | |
break; | |
} | |
printf("\n Result is :%d", dat.result); | |
} | |
close(fd); | |
return 0; | |
} |
Based on user's selection, the application fills the operands in structure and send to kernel for processing through ioctl(). Since the sample driver is registered for that device node, the ioctl() in driver will be invoked.
The ioctl() in kernel performs the corresponding operation and returns the result to the user space application.
2.3.6 Wrappers for drivers in user space
Usually driver developers will hide the complexity of opening device file, remembering the ioctl commands. If the application is developed by third party, it is better to write a wrapper which exposes only functionality.
The wrapper is written as shared library(.so) which takes care of handling driver related stuff and exposes only functionality.
The following is the source code for shard library.
#include <stdio.h> #include <stdlib.h> | |
#include <sys/ioctl.h> | |
#include <fcntl.h> | |
#include <unistd.h> | |
#include "../sampleioctl.h" | |
#include "mylib.h" | |
int fd; | |
struct calc dat; | |
int OpenDev() | |
{ | |
fd = open("/dev/samplechar", O_RDWR); | |
if(fd < 0) | |
{ | |
perror("open:"); | |
return -1; | |
} | |
return 0; | |
} | |
int Add(int data1, int data2) | |
{ | |
int err=0; | |
dat.data1 = data1; | |
dat.data2 = data2; | |
err=ioctl(fd, SAMPLE_IOCTL_ADD, &dat); | |
if(err < 0) | |
{ | |
perror("ioctl:"); | |
return -1; | |
} | |
return dat.result; | |
} | |
int Sub(int data1, int data2) | |
{ | |
int err=0; | |
dat.data1 = data1; | |
dat.data2 = data2; | |
err=ioctl(fd, SAMPLE_IOCTL_SUB, &dat); | |
if(err < 0) | |
{ | |
perror("ioctl:"); | |
return -1; | |
} | |
return dat.result; | |
} | |
int Mul(int data1, int data2) | |
{ | |
int err=0; | |
dat.data1 = data1; | |
dat.data2 = data2; | |
err=ioctl(fd, SAMPLE_IOCTL_MUL, &dat);if(err < 0) | |
{ | |
perror("ioctl:"); | |
return -1; | |
} | |
return dat.result; | |
} | |
int Div(int data1, int data2) | |
{ | |
int err=0; | |
dat.data1 = data1; | |
dat.data2 = data2; | |
err= ioctl(fd, SAMPLE_IOCTL_DIV, &dat); | |
if(err < 0) | |
{ | |
perror("ioctl:"); | |
return -1; | |
} | |
return dat.result; | |
} | |
int CloseDev() | |
{ | |
close(fd); | |
return 0; | |
} |
Use the following commands to generate the shared library.
gcc -fPIC -g -c -Wall mylib.c
gcc -shared -W1,-soname, -o libmylib.so mylib.o -lc
Then link this library with the application.Here is the sample application which uses this library.
#include <stdio.h> #include <stdlib.h> | |
#include <sys/ioctl.h> | |
#include <fcntl.h> | |
#include "../mylib.h" | |
int main() | |
{ | |
int data1, data2; | |
int opt; | |
int err=0; | |
err = OpenDev(); | |
if(err < 0) | |
{ | |
printf("\n open: Error in file open... "); | |
return -1; | |
} | |
while(1) | |
{ | |
printf("\n 1.Add\n2.Sub\n3.Mul\n4.Div\n \ | |
5.ExitApp\nChoose Operation:" ); | |
scanf("%d", &opt); | |
if(opt == 5) | |
break; | |
if(opt < 1 || opt > 4) | |
{ | |
printf("\n Invalid Option... "); | |
continue; | |
} | |
printf("\n Enter Data1:"); | |
scanf("%d", &data1); | |
printf("\n Enter Data2:"); | |
scanf("%d", &data2); | |
switch(opt) | |
{ | |
case 1: | |
err = Add(data1, data2); | |
break; | |
case 2: | |
err=Sub(data1, data2); | |
break; | |
case 3: | |
err= Mul(data1, data2); | |
break; | |
case 4: | |
err= Div(data1, data2); | |
break; | |
} | |
printf("\n Result is :%d", err); | |
} | |
CloseDev(); | |
return 0; | |
} |
Just compare the app with wrapper and with out wrapper. This gives you some clear understanding of creating wrapper for drivers.
Here is the git hub which provides all necessary make files and source/header for execution & testing.
This is really an amazing post, thanks for sharing such a valuable information with us,
ReplyDeleteDevOps Training
DevOps Classroom Training in Hyderabad
ReplyDeleteIt was great experience after reading this. thanks for sharing such good stuff with us.
Linux Training in Delhi
i like it that page
ReplyDeletePHP Training in Chennai | Certification | Online Training Course | Machine Learning Training in Chennai | Certification | Online Training Course | iOT Training in Chennai | Certification | Online Training Course | Blockchain Training in Chennai | Certification | Online Training Course | Open Stack Training in Chennai |
Certification | Online Training Course
great work admin keep it up....
ReplyDelete5 Instant Approval Site (DoFollow Backlink)
Great, thanks for sharing this post.Much thanks again. Awesome.
ReplyDeleteData Science training
linux training
Wondering how to fix Pinnacle Game Profiler not opening on Windows 10? Try the fixes like update the Pinnacle game profiler or run as admin.Pinnacle Game Profiler Crashes
ReplyDeleteSynthesia Key is the full latest version is connected each point run working main user keyboard have to be show on the screen in application. Synthesia Full Version
ReplyDeleteYou are spreading a Knowledge . it help a people to Know about embeded system
ReplyDeleteJTAG
This comment has been removed by the author.
ReplyDeleteImpressive Article. Thanks for sharing.
ReplyDeleteAzure DevOps Training Online
Azure DevOps Online Training
Azure DevOps Online Training in Hyderabad
Azure DevOps Course Online
Microsoft Azure DevOps Online Training
Azure DevOps Training in Hyderabad
Azure DevOps Training
Azure DevOps Training in Ameerpet
At APTRON Solutions, we believe in learning by doing. Our Linux Course in Noida provides ample opportunities for hands-on practice in a simulated environment. You'll work on real-world projects and assignments, allowing you to apply theoretical concepts to practical scenarios. Our experienced trainers offer personalized guidance and support, ensuring that you make the most out of your learning journey.
ReplyDelete