Peter Jay Salzman, Michael Burian, Ori Pomerantz, Bob Mottram, Jim Huang
May 2, 2023
1 Introduction
The Linux Kernel Module Programming Guide is a free book; you may reproduce
and/or modify it under the terms of the Open Software License, version
3.0.
This book is distributed in the hope that it would be useful, but without any
warranty, without even the implied warranty of merchantability or fitness for a
particular purpose.
The author encourages wide distribution of this book for personal or commercial
use, provided the above copyright notice remains intact and the method adheres to
the provisions of the Open Software License. In summary, you may copy and
distribute this book free of charge or for a profit. No explicit permission is required
from the author for reproduction of this book in any medium, physical or
electronic.
Derivative works and translations of this document must be placed under the
Open Software License, and the original copyright notice must remain intact. If you
have contributed new material to this book, you must make the material and source
code available for your revisions. Please make revisions and updates available directly
to the document maintainer, Jim Huang
for the merging of updates and provide consistent revisions to the Linux
community.
If you publish or distribute this book commercially, donations, royalties, and/or
printed copies are greatly appreciated by the author and the Linux Documentation
Project (LDP). Contributing in this way shows your support for free software and
the LDP. If you have questions or comments, please contact the address
above.
1.1 Authorship
The Linux Kernel Module Programming Guide was originally written for the 2.2
kernels by Ori Pomerantz. Eventually, Ori no longer had time to maintain the
document. After all, the Linux kernel is a fast moving target. Peter Jay Salzman took
over maintenance and updated it for the 2.4 kernels. Eventually, Peter no longer had
time to follow developments with the 2.6 kernel, so Michael Burian became a
co-maintainer to update the document for the 2.6 kernels. Bob Mottram updated the
examples for 3.8+ kernels. Jim Huang upgraded to recent kernel versions (v5.x) and
revised the LaTeX document.
1.2 Acknowledgements
The following people have contributed corrections or good suggestions:
2011eric, 25077667, Arush Sharma, asas1asas200, Benno Bielmeier, Bob Lee,
Brad Baker, ccs100203, Chih-Yu Chen, Ching-Hua (Vivian) Lin,
ChinYikMing, Cyril Brulebois, Daniele Paolo Scarpazza, David Porter,
demonsome, Dimo Velev, Ekang Monyet, fennecJ, Francois Audeon,
gagachang, Gilad Reti, Horst Schirmeier, Hsin-Hsiang Peng, Ignacio Martin,
JianXing Wu, linD026, lyctw, manbing, Marconi Jiang, mengxinayan,
RinHizakura, Roman Lakeev, Stacy Prowell, Steven Lung, Tristan Lelong,
Tucker Polomik, VxTeemo, Wei-Lun Tsai, xatier, Ylowy.
1.3 What Is A Kernel Module?
So, you want to write a kernel module. You know C, you have written a few normal
programs to run as processes, and now you want to get to where the real action is, to
where a single wild pointer can wipe out your file system and a core dump means a
reboot.
What exactly is a kernel module? Modules are pieces of code that can be loaded
and unloaded into the kernel upon demand. They extend the functionality of the
kernel without the need to reboot the system. For example, one type of module is the
device driver, which allows the kernel to access hardware connected to the system.
Without modules, we would have to build monolithic kernels and add new
functionality directly into the kernel image. Besides having larger kernels, this has
the disadvantage of requiring us to rebuild and reboot the kernel every time we want
new functionality.
1.4 Kernel module package
Linux distributions provide the commands
modprobe
,
insmod
and
depmod
within a package.
On Ubuntu/Debian:
1sudo apt-get install build-essential kmod
On Arch Linux:
1sudo pacman -S gcc kmod
1.5 What Modules are in my Kernel?
To discover what modules are already loaded within your current kernel use the command
lsmod
.
1sudo lsmod
Modules are stored within the file /proc/modules, so you can also see them with:
1sudo cat /proc/modules
This can be a long list, and you might prefer to search for something particular.
To search for the fat module:
1sudo lsmod | grep fat
1.6 Do I need to download and compile the kernel?
For the purposes of following this guide you don’t necessarily need to do that.
However, it would be wise to run the examples within a test distribution running
on a virtual machine in order to avoid any possibility of messing up your
system.
1.7 Before We Begin
Before we delve into code, there are a few issues we need to cover. Everyone’s system
is different and everyone has their own groove. Getting your first “hello world”
program to compile and load correctly can sometimes be a trick. Rest assured, after
you get over the initial hurdle of doing it for the first time, it will be smooth sailing
thereafter.
- Modversioning. A module compiled for one kernel will not load if you boot
a different kernel unless you enableCONFIG_MODVERSIONS
in the kernel. We will not go into module versioning until later in this
guide. Until we cover modversions, the examples in the guide may notwork if you are running a kernel with modversioning turned on. However,
most stock Linux distribution kernels come with it turned on. If you are
having trouble loading the modules because of versioning errors, compile
a kernel with modversioning turned off. -
Using X Window System. It is highly recommended that you extract,
compile and load all the examples this guide discusses from a console. You
should not be working on this stuff in X Window System.Modules can not print to the screen like
printf()
can, but they can log information and warnings, which ends up being
printed on your screen, but only on a console. If youinsmod
a module from an xterm, the information and warnings will be logged, but
only to your systemd journal. You will not see it unless you look through
yourjournalctl
. See 4 for details. To have immediate access to this information, do all
your work from the console. -
SecureBoot. Many contemporary computers are pre-configured with UEFI
SecureBoot enabled. It is a security standard that can make sure the
device boots using only software that is trusted by original equipment
manufacturer. The default Linux kernel from some distributions have also
enabled the SecureBoot. For such distributions, the kernel module has to
be signed with the security key or you would get the “ERROR: could not
insert module” when you insert your first hello world module:1insmod ./hello-1.ko
And then you can check further with
dmesg
and see the following text:
Lockdown: insmod: unsigned module loading is restricted; see man kernel
lockdown.7If you got this message, the simplest way is to disable the UEFI SecureBoot
from the PC/laptop boot menu to have your “hello-1” to be inserted. Of course
you can go through complicated steps to generate keys, install keys to your
system, and finally sign your module to make it work. However, this is not
suitable for beginners. You could read and follow the steps in SecureBoot if you
are interested.
Before you can build anything you’ll need to install the header files for your
kernel.
On Ubuntu/Debian:
1sudo apt-get update 2apt-cache search linux-headers-`uname -r`
This will tell you what kernel header files are available. Then for example:
1sudo apt-get install kmod linux-headers-5.4.0-80-generic
On Arch Linux:
1sudo pacman -S linux-headers
3 Examples
All the examples from this document are available within the examples
subdirectory.
If there are any compile errors then you might have a more recent kernel version
or need to install the corresponding kernel header files.
4 Hello World
4.1 The Simplest Module
Most people learning programming start out with some sort of “hello world”
example. I don’t know what happens to people who break with this tradition, but I
think it is safer not to find out. We will start with a series of hello world
programs that demonstrate the different aspects of the basics of writing a kernel
module.
Here is the simplest module possible.
Make a test directory:
1mkdir -p ~/develop/kernel/hello-1 2cd ~/develop/kernel/hello-1
Paste this into your favorite editor and save it as hello-1.c:
1/* 2 * hello-1.c - The simplest kernel module. 3 */ 4#include/* Needed by all modules */ 5#include/* Needed for pr_info() */ 6 7int init_module(void) 8{ 9 pr_info("Hello world 1.n"); 10 11 /* A non 0 return means init_module failed; module can't be loaded. */ 12 return 0; 13} 14 15void cleanup_module(void) 16{ 17 pr_info("Goodbye world 1.n"); 18} 19 20MODULE_LICENSE("GPL");
Now you will need a Makefile. If you copy and paste this, change the indentation
to use tabs, not spaces.
1obj-m += hello-1.o 2 3PWD := $(CURDIR) 4 5all: 6 make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules 7 8clean: 9 make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
In Makefile, $(CURDIR) can set to the absolute pathname of the current working
directory(after all -C options are processed, if any). See more about CURDIR in GNU
make manual.
And finally, just run make directly.
1make
If there is no PWD := $(CURDIR) statement in Makefile, then it may not compile
correctly with sudo make. Because some environment variables are specified by
the security policy, they can’t be inherited. The default security policy is
sudoers. In the sudoers security policy, env_reset is enabled by default,
which restricts environment variables. Specifically, path variables are not
retained from the user environment, they are set to default values (For more
information see: sudoers manual). You can see the environment variable settings
by:
$ sudo -s # sudo -V
Here is a simple Makefile as an example to demonstrate the problem mentioned
above.
1all: 2 echo $(PWD)
Then, we can use -p flag to print out the environment variable values from the
Makefile.
$ make -p | grep PWD PWD = /home/ubuntu/temp OLDPWD = /home/ubuntu echo $(PWD)
The PWD variable won’t be inherited with sudo.
$ sudo make -p | grep PWD echo $(PWD)
However, there are three ways to solve this problem.
-
You can use the -E flag to temporarily preserve them.
1 $ sudo -E make -p | grep PWD 2 PWD = /home/ubuntu/temp 3 OLDPWD = /home/ubuntu 4 echo $(PWD)
-
You can set the env_reset disabled by editing the /etc/sudoers with
root and visudo.1 ## sudoers file. 2 ## 3 ... 4 Defaults env_reset 5 ## Change env_reset to !env_reset in previous line to keep all environment variables
Then execute env and sudo env individually.
1 # disable the env_reset 2 echo "user:" > non-env_reset.log; env >> non-env_reset.log 3 echo "root:" >> non-env_reset.log; sudo env >> non-env_reset.log 4 # enable the env_reset 5 echo "user:" > env_reset.log; env >> env_reset.log 6 echo "root:" >> env_reset.log; sudo env >> env_reset.log
You can view and compare these logs to find differences between
env_reset and !env_reset. -
You can preserve environment variables by appending them to env_keep
in /etc/sudoers.1 Defaults env_keep += "PWD"
After applying the above change, you can check the environment variable
settings by:$ sudo -s # sudo -V
If all goes smoothly you should then find that you have a compiled hello-1.ko
module. You can find info on it with the command:
1modinfo hello-1.ko
At this point the command:
1sudo lsmod | grep hello
should return nothing. You can try loading your shiny new module with:
1sudo insmod hello-1.ko
The dash character will get converted to an underscore, so when you again try:
1sudo lsmod | grep hello
you should now see your loaded module. It can be removed again with:
1sudo rmmod hello_1
Notice that the dash was replaced by an underscore. To see what just happened in
the logs:
1sudo journalctl --since "1 hour ago" | grep kernel
You now know the basics of creating, compiling, installing and removing modules.
Now for more of a description of how this module works.
Kernel modules must have at least two functions: a “start” (initialization) function
called init_module()
which is called when the module is
insmod
ed into the kernel, and an “end” (cleanup) function called
cleanup_module()
which is called just before it is removed from the kernel. Actually, things have
changed starting with kernel 2.3.13. You can now use whatever name you like for the
start and end functions of a module, and you will learn how to do this in Section 4.2.
In fact, the new method is the preferred method. However, many people still use
init_module()
and
cleanup_module()
for their start and end functions.
Typically, init_module()
either registers a handler for something with the kernel, or it replaces one of the kernel
functions with its own code (usually code to do something and then call the original function).
The cleanup_module()
function is supposed to undo whatever
init_module()
did, so the module can be unloaded safely.
Lastly, every kernel module needs to include
needed to include
pr_alert()
log level, which you’ll learn about in Section 2.
- A point about coding style. Another thing which may not be immediately
obvious to anyone getting started with kernel programming is that
indentation within your code should be using tabs and not spaces. It is
one of the coding conventions of the kernel. You may not like it, but you’ll
need to get used to it if you ever submit a patch upstream. - Introducing print macros. In the beginning there was
printk
, usually followed by a priority such as
KERN_INFO
orKERN_DEBUG
. More recently this can also be expressed in abbreviated form using a set of
print macros, such aspr_info
and
pr_debug
. This just saves some mindless keyboard bashing and looks a bit neater.
They can be found within include/linux/printk.h. Take time to read through
the available priority macros. -
About Compiling. Kernel modules need to be compiled a bit differently
from regular userspace apps. Former kernel versions required us to
care much about these settings, which are usually stored in Makefiles.
Although hierarchically organized, many redundant settings accumulated
in sublevel Makefiles and made them large and rather difficult to maintain.
Fortunately, there is a new way of doing these things, called kbuild, and
the build process for external loadable modules is now fully integrated into
the standard kernel build mechanism. To learn more on how to compile
modules which are not part of the official kernel (such as all the examples
you will find in this guide), see file Documentation/kbuild/modules.rst.Additional details about Makefiles for kernel modules are available in
Documentation/kbuild/makefiles.rst. Be sure to read this and the related
files before starting to hack Makefiles. It will probably save you lots of
work.Here is another exercise for the reader. See that comment above
the return statement ininit_module()
? Change the return value to something negative, recompile and
load the module again. What happens?
4.2 Hello and Goodbye
In early kernel versions you had to use the
init_module
and
cleanup_module
functions, as in the first hello world example, but these days you can name those anything you
want by using the module_init
and
module_exit
macros. These macros are defined in include/linux/module.h. The only requirement
is that your init and cleanup functions must be defined before calling the those
macros, otherwise you’ll get compilation errors. Here is an example of this
technique:
1/* 2 * hello-2.c - Demonstrating the module_init() and module_exit() macros. 3 * This is preferred over using init_module() and cleanup_module(). 4 */ 5#include/* Needed for the macros */ 6#include/* Needed by all modules */ 7#include/* Needed for pr_info() */ 8 9static int __init hello_2_init(void) 10{ 11 pr_info("Hello, world 2n"); 12 return 0; 13} 14 15static void __exit hello_2_exit(void) 16{ 17 pr_info("Goodbye, world 2n"); 18} 19 20module_init(hello_2_init); 21module_exit(hello_2_exit); 22 23MODULE_LICENSE("GPL");
So now we have two real kernel modules under our belt. Adding another module
is as simple as this:
1obj-m += hello-1.o 2obj-m += hello-2.o 3 4PWD := $(CURDIR) 5 6all: 7 make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules 8 9clean: 10 make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
Now have a look at drivers/char/Makefile for a real world example. As
you can see, some things got hardwired into the kernel (obj-y) but where
have all those obj-m gone? Those familiar with shell scripts will easily be
able to spot them. For those who are not, the obj-$(CONFIG_FOO) entries
you see everywhere expand into obj-y or obj-m, depending on whether the
CONFIG_FOO variable has been set to y or m. While we are at it, those were
exactly the kind of variables that you have set in the .config file in the
top-level directory of Linux kernel source tree, the last time when you said
make menuconfig
or something like that.
4.3 The __init and __exit Macros
The __init
macro causes the init function to be discarded and its memory freed once the init
function finishes for built-in drivers, but not loadable modules. If you think about
when the init function is invoked, this makes perfect sense.
There is also an __initdata
which works similarly to
__init
but for init variables rather than functions.
The __exit
macro causes the omission of the function when the module is built into the kernel, and
like __init
, has no effect for loadable modules. Again, if you consider when the cleanup function
runs, this makes complete sense; built-in drivers do not need a cleanup function,
while loadable modules do.
These macros are defined in include/linux/init.h and serve to free up kernel
memory. When you boot your kernel and see something like Freeing unused kernel
memory: 236k freed, this is precisely what the kernel is freeing.
1/* 2 * hello-3.c - Illustrating the __init, __initdata and __exit macros. 3 */ 4#include/* Needed for the macros */ 5#include/* Needed by all modules */ 6#include/* Needed for pr_info() */ 7 8static int hello3_data __initdata = 3; 9 10static int __init hello_3_init(void) 11{ 12 pr_info("Hello, world %dn", hello3_data); 13 return 0; 14} 15 16static void __exit hello_3_exit(void) 17{ 18 pr_info("Goodbye, world 3n"); 19} 20 21module_init(hello_3_init); 22module_exit(hello_3_exit); 23 24MODULE_LICENSE("GPL");
4.4 Licensing and Module Documentation
Honestly, who loads or even cares about proprietary modules? If you do then you
might have seen something like this:
$ sudo insmod xxxxxx.ko loading out-of-tree module taints kernel. module license 'unspecified' taints kernel.
You can use a few macros to indicate the license for your module. Some examples
are “GPL”, “GPL v2”, “GPL and additional rights”, “Dual BSD/GPL”, “Dual
MIT/GPL”, “Dual MPL/GPL” and “Proprietary”. They are defined within
include/linux/module.h.
To reference what license you’re using a macro is available called
MODULE_LICENSE
. This and a few other macros describing the module are illustrated in the below
example.
1/* 2 * hello-4.c - Demonstrates module documentation. 3 */ 4#include/* Needed for the macros */ 5#include/* Needed by all modules */ 6#include/* Needed for pr_info() */ 7 8MODULE_LICENSE("GPL"); 9MODULE_AUTHOR("LKMPG"); 10MODULE_DESCRIPTION("A sample driver"); 11 12static int __init init_hello_4(void) 13{ 14 pr_info("Hello, world 4n"); 15 return 0; 16} 17 18static void __exit cleanup_hello_4(void) 19{ 20 pr_info("Goodbye, world 4n"); 21} 22 23module_init(init_hello_4); 24module_exit(cleanup_hello_4);
4.5 Passing Command Line Arguments to a Module
Modules can take command line arguments, but not with the argc/argv you might be
used to.
To allow arguments to be passed to your module, declare the variables that will
take the values of the command line arguments as global and then use the
module_param()
macro, (defined in include/linux/moduleparam.h) to set the mechanism up. At runtime,
insmod
will fill the variables with any command line arguments that are given, like
insmod mymodule.ko myvariable=5
. The variable declarations and macros should be placed at the beginning of the
module for clarity. The example code should clear up my admittedly lousy
explanation.
The module_param()
macro takes 3 arguments: the name of the variable, its type and
permissions for the corresponding file in sysfs. Integer types can be signed
as usual or unsigned. If you’d like to use arrays of integers or strings see
module_param_array()
and
module_param_string()
.
1int myint = 3; 2module_param(myint, int, 0);
Arrays are supported too, but things are a bit different now than they were in the
olden days. To keep track of the number of parameters you need to pass a pointer to
a count variable as third parameter. At your option, you could also ignore the count and
pass NULL
instead. We show both possibilities here:
1int myintarray[2]; 2module_param_array(myintarray, int, NULL, 0); /* not interested in count */ 3 4short myshortarray[4]; 5int count; 6module_param_array(myshortarray, short, &count, 0); /* put count into "count" variable */
A good use for this is to have the module variable’s default values set, like a port
or IO address. If the variables contain the default values, then perform autodetection
(explained elsewhere). Otherwise, keep the current value. This will be made clear
later on.
Lastly, there is a macro function, MODULE_PARM_DESC()
, that is used to document arguments that the module can take. It takes two
parameters: a variable name and a free form string describing that variable.
1/* 2 * hello-5.c - Demonstrates command line argument passing to a module. 3 */ 4#include5#include /* for ARRAY_SIZE() */ 6#include7#include 8#include 9#include 10 11MODULE_LICENSE("GPL"); 12 13static short int myshort = 1; 14static int myint = 420; 15static long int mylong = 9999; 16static char *mystring = "blah"; 17static int myintarray[2] = { 420, 420 }; 18static int arr_argc = 0; 19 20/* module_param(foo, int, 0000) 21 * The first param is the parameters name. 22 * The second param is its data type. 23 * The final argument is the permissions bits, 24 * for exposing parameters in sysfs (if non-zero) at a later stage. 25 */ 26module_param(myshort, short, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP); 27MODULE_PARM_DESC(myshort, "A short integer"); 28module_param(myint, int, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); 29MODULE_PARM_DESC(myint, "An integer"); 30module_param(mylong, long, S_IRUSR); 31MODULE_PARM_DESC(mylong, "A long integer"); 32module_param(mystring, charp, 0000); 33MODULE_PARM_DESC(mystring, "A character string"); 34 35/* module_param_array(name, type, num, perm); 36 * The first param is the parameter's (in this case the array's) name. 37 * The second param is the data type of the elements of the array. 38 * The third argument is a pointer to the variable that will store the number 39 * of elements of the array initialized by the user at module loading time. 40 * The fourth argument is the permission bits. 41 */ 42module_param_array(myintarray, int, &arr_argc, 0000); 43MODULE_PARM_DESC(myintarray, "An array of integers"); 44 45static int __init hello_5_init(void) 46{ 47 int i; 48 49 pr_info("Hello, world 5n=============n"); 50 pr_info("myshort is a short integer: %hdn", myshort); 51 pr_info("myint is an integer: %dn", myint); 52 pr_info("mylong is a long integer: %ldn", mylong); 53 pr_info("mystring is a string: %sn", mystring); 54 55 for (i = 0; i < ARRAY_SIZE(myintarray); i++) 56 pr_info("myintarray[%d] = %dn", i, myintarray[i]); 57 58 pr_info("got %d arguments for myintarray.n", arr_argc); 59 return 0; 60} 61 62static void __exit hello_5_exit(void) 63{ 64 pr_info("Goodbye, world 5n"); 65} 66 67module_init(hello_5_init); 68module_exit(hello_5_exit);
I would recommend playing around with this code:
$ sudo insmod hello-5.ko mystring="bebop" myintarray=-1 $ sudo dmesg -t | tail -7 myshort is a short integer: 1 myint is an integer: 420 mylong is a long integer: 9999 mystring is a string: bebop myintarray[0] = -1 myintarray[1] = 420 got 1 arguments for myintarray. $ sudo rmmod hello-5 $ sudo dmesg -t | tail -1 Goodbye, world 5 $ sudo insmod hello-5.ko mystring="supercalifragilisticexpialidocious" myintarray=-1,-1 $ sudo dmesg -t | tail -7 myshort is a short integer: 1 myint is an integer: 420 mylong is a long integer: 9999 mystring is a string: supercalifragilisticexpialidocious myintarray[0] = -1 myintarray[1] = -1 got 2 arguments for myintarray. $ sudo rmmod hello-5 $ sudo dmesg -t | tail -1 Goodbye, world 5 $ sudo insmod hello-5.ko mylong=hello insmod: ERROR: could not insert module hello-5.ko: Invalid parameters
4.6 Modules Spanning Multiple Files
Sometimes it makes sense to divide a kernel module between several source
files.
Here is an example of such a kernel module.
1/* 2 * start.c - Illustration of multi filed modules 3 */ 4 5#include/* We are doing kernel work */ 6#include/* Specifically, a module */ 7 8int init_module(void) 9{ 10 pr_info("Hello, world - this is the kernel speakingn"); 11 return 0; 12} 13 14MODULE_LICENSE("GPL");
The next file:
1/* 2 * stop.c - Illustration of multi filed modules 3 */ 4 5#include/* We are doing kernel work */ 6#include/* Specifically, a module */ 7 8void cleanup_module(void) 9{ 10 pr_info("Short is the life of a kernel modulen"); 11} 12 13MODULE_LICENSE("GPL");
And finally, the makefile:
1obj-m += hello-1.o 2obj-m += hello-2.o 3obj-m += hello-3.o 4obj-m += hello-4.o 5obj-m += hello-5.o 6obj-m += startstop.o 7startstop-objs := start.o stop.o 8 9PWD := $(CURDIR) 10 11all: 12 make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules 13 14clean: 15 make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
This is the complete makefile for all the examples we have seen so far. The first
five lines are nothing special, but for the last example we will need two lines.
First we invent an object name for our combined module, second we tell
make
what object files are part of that module.
4.7 Building modules for a precompiled kernel
Obviously, we strongly suggest you to recompile your kernel, so that you can enable
a number of useful debugging features, such as forced module unloading
( MODULE_FORCE_UNLOAD
): when this option is enabled, you can force the kernel to unload a module even when it believes
it is unsafe, via a sudo rmmod -f module
command. This option can save you a lot of time and a number of reboots during
the development of a module. If you do not want to recompile your kernel then you
should consider running the examples within a test distribution on a virtual machine.
If you mess anything up then you can easily reboot or restore the virtual machine
(VM).
There are a number of cases in which you may want to load your module into a
precompiled running kernel, such as the ones shipped with common Linux
distributions, or a kernel you have compiled in the past. In certain circumstances you
could require to compile and insert a module into a running kernel which you are not
allowed to recompile, or on a machine that you prefer not to reboot. If you
can’t think of a case that will force you to use modules for a precompiled
kernel you might want to skip this and treat the rest of this chapter as a big
footnote.
Now, if you just install a kernel source tree, use it to compile your kernel module
and you try to insert your module into the kernel, in most cases you would obtain an
error as follows:
insmod: ERROR: could not insert module poet.ko: Invalid module format
Less cryptic information is logged to the systemd journal:
kernel: poet: disagrees about version of symbol module_layout
In other words, your kernel refuses to accept your module because version strings
(more precisely, version magic, see include/linux/vermagic.h) do not match. Incidentally,
version magic strings are stored in the module object in the form of a static string, starting
with vermagic:
. Version data are inserted in your module when it is linked against the kernel/module.o
file. To inspect version magics and other strings stored in a given module, issue the
command modinfo module.ko
:
$ modinfo hello-4.ko description: A sample driver author: LKMPG license: GPL srcversion: B2AA7FBFCC2C39AED665382 depends: retpoline: Y name: hello_4 vermagic: 5.4.0-70-generic SMP mod_unload modversions
To overcome this problem we could resort to the –force-vermagic option,
but this solution is potentially unsafe, and unquestionably unacceptable
in production modules. Consequently, we want to compile our module in
an environment which was identical to the one in which our precompiled
kernel was built. How to do this, is the subject of the remainder of this
chapter.
First of all, make sure that a kernel source tree is available, having exactly the same
version as your current kernel. Then, find the configuration file which was used to
compile your precompiled kernel. Usually, this is available in your current boot directory,
under a name like config-5.14.x. You may just want to copy it to your kernel source
tree: cp /boot/config-`uname -r` .config
.
Let’s focus again on the previous error message: a closer look at the version magic
strings suggests that, even with two configuration files which are exactly the same, a
slight difference in the version magic could be possible, and it is sufficient to prevent
insertion of the module into the kernel. That slight difference, namely the
custom string which appears in the module’s version magic and not in the
kernel’s one, is due to a modification with respect to the original, in the
makefile that some distributions include. Then, examine your Makefile,
and make sure that the specified version information matches exactly the
one used for your current kernel. For example, your makefile could start as
follows:
VERSION = 5 PATCHLEVEL = 14 SUBLEVEL = 0 EXTRAVERSION = -rc2
In this case, you need to restore the value of symbol EXTRAVERSION to
-rc2. We suggest to keep a backup copy of the makefile used to compile your kernel
available in /lib/modules/5.14.0-rc2/build. A simple command as following
should suffice.
1cp /lib/modules/`uname -r`/build/Makefile linux-`uname -r`
Here linux-`uname -r`
is the Linux kernel source you are attempting to build.
Now, please run make
to update configuration and version headers and objects:
$ make SYNC include/config/auto.conf.cmd HOSTCC scripts/basic/fixdep HOSTCC scripts/kconfig/conf.o HOSTCC scripts/kconfig/confdata.o HOSTCC scripts/kconfig/expr.o LEX scripts/kconfig/lexer.lex.c YACC scripts/kconfig/parser.tab.[ch] HOSTCC scripts/kconfig/preprocess.o HOSTCC scripts/kconfig/symbol.o HOSTCC scripts/kconfig/util.o HOSTCC scripts/kconfig/lexer.lex.o HOSTCC scripts/kconfig/parser.tab.o HOSTLD scripts/kconfig/conf
If you do not desire to actually compile the kernel, you can interrupt the build
process (CTRL-C) just after the SPLIT line, because at that time, the files you need
are ready. Now you can turn back to the directory of your module and compile it: It
will be built exactly according to your current kernel settings, and it will load into it
without any errors.
5 Preliminaries
5.1 How modules begin and end
A program usually begins with a main()
function, executes a bunch of instructions and terminates upon completion of those
instructions. Kernel modules work a bit differently. A module always begin with either
the init_module
or the function you specify with
module_init
call. This is the entry function for modules; it tells the kernel what functionality the
module provides and sets up the kernel to run the module’s functions when they
are needed. Once it does this, entry function returns and the module does
nothing until the kernel wants to do something with the code that the module
provides.
All modules end by calling either cleanup_module
or the function you specify with the
module_exit
call. This is the exit function for modules; it undoes whatever entry function did. It
unregisters the functionality that the entry function registered.
Every module must have an entry function and an exit function. Since there’s
more than one way to specify entry and exit functions, I will try my best to use the
terms “entry function” and “exit function”, but if I slip and simply refer to them as
init_module
and
cleanup_module
, I think you will know what I mean.
5.2 Functions available to modules
Programmers use functions they do not define all the time. A prime example of this
is printf()
. You use these library functions which are provided by the standard C
library, libc. The definitions for these functions do not actually enter
your program until the linking stage, which insures that the code (for
printf()
for example) is available, and fixes the call instruction to point to that
code.
Kernel modules are different here, too. In the hello world
example, you might have noticed that we used a function,
pr_info()
but did not include a standard I/O library. That is because
modules are object files whose symbols get resolved upon running
insmod
or
modprobe
. The definition for the symbols comes from the kernel itself; the only external
functions you can use are the ones provided by the kernel. If you’re curious about
what symbols have been exported by your kernel, take a look at /proc/kallsyms.
One point to keep in mind is the difference between library functions and system
calls. Library functions are higher level, run completely in user space and
provide a more convenient interface for the programmer to the functions
that do the real work — system calls. System calls run in kernel mode on
the user’s behalf and are provided by the kernel itself. The library function
printf()
may look like a very general printing function, but all it really does is format the
data into strings and write the string data using the low-level system call
write()
, which then sends the data to standard output.
Would you like to see what system calls are made by
printf()
? It is easy! Compile the following program:
1#include2 3int main(void) 4{ 5 printf("hello"); 6 return 0; 7}
with gcc -Wall -o hello hello.c
. Run the executable with
strace ./hello
. Are you impressed? Every line you see corresponds to a system call. strace is a
handy program that gives you details about what system calls a program is
making, including which call is made, what its arguments are and what it
returns. It is an invaluable tool for figuring out things like what files a program
is trying to access. Towards the end, you will see a line which looks like
write(1, "hello", 5hello)
. There it is. The face behind the
printf()
mask. You may not be familiar with write, since most people use library functions for file
I/O (like fopen
,
fputs
,
fclose
). If that is the case, try looking at man 2 write. The 2nd man section is devoted to system
calls (like kill()
and
read()
). The 3rd man section is devoted to library calls, which you would probably be more familiar
with (like cosh()
and
random()
).
You can even write modules to replace the kernel’s system calls, which we will do
shortly. Crackers often make use of this sort of thing for backdoors or trojans, but
you can write your own modules to do more benign things, like have the kernel
write Tee hee, that tickles! every time someone tries to delete a file on your
system.
5.3 User Space vs Kernel Space
A kernel is all about access to resources, whether the resource in question happens to
be a video card, a hard drive or even memory. Programs often compete for the same
resource. As I just saved this document, updatedb started updating the locate
database. My vim session and updatedb are both using the hard drive concurrently.
The kernel needs to keep things orderly, and not give users access to resources
whenever they feel like it. To this end, a CPU can run in different modes. Each mode
gives a different level of freedom to do what you want on the system. The Intel 80386
architecture had 4 of these modes, which were called rings. Unix uses only
two rings; the highest ring (ring 0, also known as “supervisor mode” where
everything is allowed to happen) and the lowest ring, which is called “user
mode”.
Recall the discussion about library functions vs system calls. Typically, you use a
library function in user mode. The library function calls one or more system calls,
and these system calls execute on the library function’s behalf, but do so in
supervisor mode since they are part of the kernel itself. Once the system call
completes its task, it returns and execution gets transferred back to user
mode.
5.4 Name Space
When you write a small C program, you use variables which are convenient and make
sense to the reader. If, on the other hand, you are writing routines which will be part
of a bigger problem, any global variables you have are part of a community of other
peoples’ global variables; some of the variable names can clash. When a program has
lots of global variables which aren’t meaningful enough to be distinguished, you get
namespace pollution. In large projects, effort must be made to remember reserved
names, and to find ways to develop a scheme for naming unique variable names and
symbols.
When writing kernel code, even the smallest module will be linked against the
entire kernel, so this is definitely an issue. The best way to deal with this is to declare
all your variables as static and to use a well-defined prefix for your symbols. By
convention, all kernel prefixes are lowercase. If you do not want to declare everything
as static, another option is to declare a symbol table and register it with the kernel.
We will get to this later.
The file /proc/kallsyms holds all the symbols that the kernel knows about and
which are therefore accessible to your modules since they share the kernel’s
codespace.
5.5 Code space
Memory management is a very complicated subject and the majority of O’Reilly’s
Understanding The Linux Kernel exclusively covers memory management!
We are not setting out to be experts on memory managements, but we do
need to know a couple of facts to even begin worrying about writing real
modules.
If you have not thought about what a segfault really means, you may be surprised
to hear that pointers do not actually point to memory locations. Not real
ones, anyway. When a process is created, the kernel sets aside a portion of
real physical memory and hands it to the process to use for its executing
code, variables, stack, heap and other things which a computer scientist
would know about. This memory begins with 0x00000000 and extends up to
whatever it needs to be. Since the memory space for any two processes do not
overlap, every process that can access a memory address, say 0xbffff978, would
be accessing a different location in real physical memory! The processes
would be accessing an index named 0xbffff978 which points to some kind of
offset into the region of memory set aside for that particular process. For
the most part, a process like our Hello, World program can’t access the
space of another process, although there are ways which we will talk about
later.
The kernel has its own space of memory as well. Since a module is code which
can be dynamically inserted and removed in the kernel (as opposed to a
semi-autonomous object), it shares the kernel’s codespace rather than having its own.
Therefore, if your module segfaults, the kernel segfaults. And if you start writing
over data because of an off-by-one error, then you’re trampling on kernel
data (or code). This is even worse than it sounds, so try your best to be
careful.
By the way, I would like to point out that the above discussion is true for any
operating system which uses a monolithic kernel. This is not quite the same thing as
“building all your modules into the kernel”, although the idea is the same. There are
things called microkernels which have modules which get their own codespace. The
GNU Hurd and the Zircon kernel of Google Fuchsia are two examples of a
microkernel.
5.6 Device Drivers
One class of module is the device driver, which provides functionality for hardware
like a serial port. On Unix, each piece of hardware is represented by a file located in
/dev named a device file which provides the means to communicate with the
hardware. The device driver provides the communication on behalf of a
user program. So the es1370.ko sound card device driver might connect the
/dev/sound device file to the Ensoniq IS1370 sound card. A userspace program like
mp3blaster can use /dev/sound without ever knowing what kind of sound card is
installed.
Let’s look at some device files. Here are device files which represent the first three
partitions on the primary master IDE hard drive:
$ ls -l /dev/hda[1-3] brw-rw---- 1 root disk 3, 1 Jul 5 2000 /dev/hda1 brw-rw---- 1 root disk 3, 2 Jul 5 2000 /dev/hda2 brw-rw---- 1 root disk 3, 3 Jul 5 2000 /dev/hda3
Notice the column of numbers separated by a comma. The first number is called
the device’s major number. The second number is the minor number. The major
number tells you which driver is used to access the hardware. Each driver is assigned
a unique major number; all device files with the same major number are controlled
by the same driver. All the above major numbers are 3, because they’re all controlled
by the same driver.
The minor number is used by the driver to distinguish between the various
hardware it controls. Returning to the example above, although all three devices are
handled by the same driver they have unique minor numbers because the driver sees
them as being different pieces of hardware.
Devices are divided into two types: character devices and block devices. The
difference is that block devices have a buffer for requests, so they can choose the best
order in which to respond to the requests. This is important in the case of storage
devices, where it is faster to read or write sectors which are close to each
other, rather than those which are further apart. Another difference is that
block devices can only accept input and return output in blocks (whose size
can vary according to the device), whereas character devices are allowed
to use as many or as few bytes as they like. Most devices in the world are
character, because they don’t need this type of buffering, and they don’t
operate with a fixed block size. You can tell whether a device file is for a block
device or a character device by looking at the first character in the output of
ls -l
. If it is ‘b’ then it is a block device, and if it is ‘c’ then it is a character device. The
devices you see above are block devices. Here are some character devices (the serial
ports):
crw-rw---- 1 root dial 4, 64 Feb 18 23:34 /dev/ttyS0 crw-r----- 1 root dial 4, 65 Nov 17 10:26 /dev/ttyS1 crw-rw---- 1 root dial 4, 66 Jul 5 2000 /dev/ttyS2 crw-rw---- 1 root dial 4, 67 Jul 5 2000 /dev/ttyS3
If you want to see which major numbers have been assigned, you can look at
Documentation/admin-guide/devices.txt.
When the system was installed, all of those device files were created by the
mknod
command. To create a new char device named coffee with major/minor number 12 and 2,
simply do mknod /dev/coffee c 12 2
. You do not have to put your device files into /dev, but it is done by convention.
Linus put his device files in /dev, and so should you. However, when creating a
device file for testing purposes, it is probably OK to place it in your working
directory where you compile the kernel module. Just be sure to put it in the right
place when you’re done writing the device driver.
I would like to make a few last points which are implicit from the above
discussion, but I would like to make them explicit just in case. When a device file is
accessed, the kernel uses the major number of the file to determine which driver
should be used to handle the access. This means that the kernel doesn’t really need
to use or even know about the minor number. The driver itself is the only thing that
cares about the minor number. It uses the minor number to distinguish between
different pieces of hardware.
By the way, when I say “hardware”, I mean something a bit more abstract
than a PCI card that you can hold in your hand. Look at these two device
files:
$ ls -l /dev/sda /dev/sdb brw-rw---- 1 root disk 8, 0 Jan 3 09:02 /dev/sda brw-rw---- 1 root disk 8, 16 Jan 3 09:02 /dev/sdb
By now you can look at these two device files and know instantly that they are
block devices and are handled by same driver (block major 8). Sometimes two device
files with the same major but different minor number can actually represent the same
piece of physical hardware. So just be aware that the word “hardware” in our
discussion can mean something very abstract.
6 Character Device drivers
6.1 The file_operations Structure
The file_operations
structure is defined in include/linux/fs.h, and holds pointers to functions defined by
the driver that perform various operations on the device. Each field of the structure
corresponds to the address of some function defined by the driver to handle a
requested operation.
For example, every character driver needs to define a function that reads from the
device. The file_operations
structure holds the address of the module’s function that performs that operation.
Here is what the definition looks like for kernel 5.4:
1struct file_operations { 2 struct module *owner; 3 loff_t (*llseek) (struct file *, loff_t, int); 4 ssize_t (*read) (struct file *, char __user *, size_t, loff_t *); 5 ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *); 6 ssize_t (*read_iter) (struct kiocb *, struct iov_iter *); 7 ssize_t (*write_iter) (struct kiocb *, struct iov_iter *); 8 int (*iopoll)(struct kiocb *kiocb, bool spin); 9 int (*iterate) (struct file *, struct dir_context *); 10 int (*iterate_shared) (struct file *, struct dir_context *); 11 __poll_t (*poll) (struct file *, struct poll_table_struct *); 12 long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long); 13 long (*compat_ioctl) (struct file *, unsigned int, unsigned long); 14 int (*mmap) (struct file *, struct vm_area_struct *); 15 unsigned long mmap_supported_flags; 16 int (*open) (struct inode *, struct file *); 17 int (*flush) (struct file *, fl_owner_t id); 18 int (*release) (struct inode *, struct file *); 19 int (*fsync) (struct file *, loff_t, loff_t, int datasync); 20 int (*fasync) (int, struct file *, int); 21 int (*lock) (struct file *, int, struct file_lock *); 22 ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int); 23 unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long); 24 int (*check_flags)(int); 25 int (*flock) (struct file *, int, struct file_lock *); 26 ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int); 27 ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int); 28 int (*setlease)(struct file *, long, struct file_lock **, void **); 29 long (*fallocate)(struct file *file, int mode, loff_t offset, 30 loff_t len); 31 void (*show_fdinfo)(struct seq_file *m, struct file *f); 32 ssize_t (*copy_file_range)(struct file *, loff_t, struct file *, 33 loff_t, size_t, unsigned int); 34 loff_t (*remap_file_range)(struct file *file_in, loff_t pos_in, 35 struct file *file_out, loff_t pos_out, 36 loff_t len, unsigned int remap_flags); 37 int (*fadvise)(struct file *, loff_t, loff_t, int); 38} __randomize_layout;
Some operations are not implemented by a driver. For example, a driver that handles
a video card will not need to read from a directory structure. The corresponding entries
in the file_operations
structure should be set to
NULL
.
There is a gcc extension that makes assigning to this structure more convenient.
You will see it in modern drivers, and may catch you by surprise. This is what the
new way of assigning to the structure looks like:
1struct file_operations fops = { 2 read: device_read, 3 write: device_write, 4 open: device_open, 5 release: device_release 6};
However, there is also a C99 way of assigning to elements of a structure,
designated initializers, and this is definitely preferred over using the GNU extension.
You should use this syntax in case someone wants to port your driver. It will help
with compatibility:
1struct file_operations fops = { 2 .read = device_read, 3 .write = device_write, 4 .open = device_open, 5 .release = device_release 6};
The meaning is clear, and you should be aware that any member of
the structure which you do not explicitly assign will be initialized to
NULL
by gcc.
An instance of struct file_operations
containing pointers to functions that are used to implement
read
,
write
,
open
, … system calls is commonly named
fops
.
Since Linux v3.14, the read, write and seek operations are guaranteed for thread-safe by
using the f_pos
specific lock, which makes the file position update to become the mutual
exclusion. So, we can safely implement those operations without unnecessary
locking.
Additionally, since Linux v5.6, the proc_ops
structure was introduced to replace the use of the
file_operations
structure when registering proc handlers. See more information in the 7.1
section.
6.2 The file structure
Each device is represented in the kernel by a file structure, which is defined
in include/linux/fs.h. Be aware that a file is a kernel level structure and
never appears in a user space program. It is not the same thing as a
FILE
, which is defined by glibc and would never appear in a kernel space
function. Also, its name is a bit misleading; it represents an abstract open
‘file’, not a file on a disk, which is represented by a structure named
inode
.
An instance of struct file is commonly named
filp
. You’ll also see it referred to as a struct file object. Resist the temptation.
Go ahead and look at the definition of file. Most of the entries you see, like struct
dentry are not used by device drivers, and you can ignore them. This is because
drivers do not fill file directly; they only use structures contained in file which are
created elsewhere.
6.3 Registering A Device
As discussed earlier, char devices are accessed through device files, usually located in
/dev. This is by convention. When writing a driver, it is OK to put the
device file in your current directory. Just make sure you place it in /dev for a
production driver. The major number tells you which driver handles which
device file. The minor number is used only by the driver itself to differentiate
which device it is operating on, just in case the driver handles more than one
device.
Adding a driver to your system means registering it with the kernel. This is synonymous
with assigning it a major number during the module’s initialization. You do this by
using the register_chrdev
function, defined by include/linux/fs.h.
1int register_chrdev(unsigned int major, const char *name, struct file_operations *fops);
Where unsigned int major is the major number you want to request,
const char *name
is the name of the device as it will appear in /proc/devices and
struct file_operations *fops
is a pointer to the
file_operations
table for your driver. A negative return value means the
registration failed. Note that we didn’t pass the minor number to
register_chrdev
. That is because the kernel doesn’t care about the minor number; only our driver
uses it.
Now the question is, how do you get a major number without hijacking
one that’s already in use? The easiest way would be to look through
Documentation/admin-guide/devices.txt and pick an unused one. That is a bad way
of doing things because you will never be sure if the number you picked will be
assigned later. The answer is that you can ask the kernel to assign you a dynamic
major number.
If you pass a major number of 0 to register_chrdev
, the return value will be the dynamically allocated major number. The
downside is that you can not make a device file in advance, since you do not
know what the major number will be. There are a couple of ways to do
this. First, the driver itself can print the newly assigned number and we
can make the device file by hand. Second, the newly registered device will
have an entry in /proc/devices, and we can either make the device file by
hand or write a shell script to read the file in and make the device file. The
third method is that we can have our driver make the device file using the
device_create
function after a successful registration and
device_destroy
during the call to
cleanup_module
.
However, register_chrdev()
would occupy a range of minor numbers associated with the given major. The
recommended way to reduce waste for char device registration is using cdev
interface.
The newer interface completes the char device registration in two distinct steps.
First, we should register a range of device numbers, which can be completed with
register_chrdev_region
or
alloc_chrdev_region
.
1int register_chrdev_region(dev_t from, unsigned count, const char *name); 2int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name);
The choice between two different functions depends on
whether you know the major numbers for your device. Using
register_chrdev_region
if you know the device major number and
alloc_chrdev_region
if you would like to allocate a dynamicly-allocated major number.
Second, we should initialize the data structure
struct cdev
for our char device and associate it with the device numbers. To initialize the
struct cdev
, we can achieve by the similar sequence of the following codes.
1struct cdev *my_dev = cdev_alloc(); 2my_cdev->ops = &my_fops;
However, the common usage pattern will embed the
struct cdev
within a device-specific structure of your own. In this case, we’ll need
cdev_init
for the initialization.
1void cdev_init(struct cdev *cdev, const struct file_operations *fops);
Once we finish the initialization, we can add the char device to the system by using
the cdev_add
.
1int cdev_add(struct cdev *p, dev_t dev, unsigned count);
To find a example using the interface, you can see ioctl.c described in section
9.
6.4 Unregistering A Device
We can not allow the kernel module to be
rmmod
’ed whenever root feels like it. If the device file is opened by a process and then we
remove the kernel module, using the file would cause a call to the memory location
where the appropriate function (read/write) used to be. If we are lucky, no
other code was loaded there, and we’ll get an ugly error message. If we are
unlucky, another kernel module was loaded into the same location, which
means a jump into the middle of another function within the kernel. The
results of this would be impossible to predict, but they can not be very
positive.
Normally, when you do not want to allow something, you return an error code
(a negative number) from the function which is supposed to do it. With
cleanup_module
that’s impossible because it is a void function. However, there is a counter
which keeps track of how many processes are using your module. You
can see what its value is by looking at the 3rd field with the command
cat /proc/modules
or
sudo lsmod
. If this number isn’t zero,
rmmod
will fail. Note that you do not have to check the counter within
cleanup_module
because the check will be performed for you by the system call
sys_delete_module
, defined in include/linux/syscalls.h. You should not use this counter directly, but
there are functions defined in include/linux/module.h which let you increase,
decrease and display this counter:
try_module_get(THIS_MODULE)
: Increment the reference count of current module.
module_put(THIS_MODULE)
: Decrement the reference count of current module.
module_refcount(THIS_MODULE)
: Return the value of reference count of current module.
It is important to keep the counter accurate; if you ever do lose track of the
correct usage count, you will never be able to unload the module; it’s now reboot
time, boys and girls. This is bound to happen to you sooner or later during a
module’s development.
6.5 chardev.c
The next code sample creates a char driver named chardev. You can dump its device
file.
1cat /proc/devices
(or open the file with a program) and the driver will put the number of times the
device file has been read from into the file. We do not support writing to the file (like
echo "hi" > /dev/hello
), but catch these attempts and tell the user that the operation is not supported.
Don’t worry if you don’t see what we do with the data we read into the buffer; we
don’t do much with it. We simply read in the data and print a message
acknowledging that we received it.
In the multiple-threaded environment, without any protection, concurrent access
to the same memory may lead to the race condition, and will not preserve the
performance. In the kernel module, this problem may happen due to multiple
instances accessing the shared resources. Therefore, a solution is to enforce the
exclusive access. We use atomic Compare-And-Swap (CAS) to maintain the states,
CDEV_NOT_USED
and
CDEV_EXCLUSIVE_OPEN
, to determine whether the file is currently opened by someone or not. CAS compares
the contents of a memory location with the expected value and, only if they are the
same, modifies the contents of that memory location to the desired value. See more
concurrency details in the 12 section.
1/* 2 * chardev.c: Creates a read-only char device that says how many times 3 * you have read from the dev file 4 */ 5 6#include7#include 8#include 9#include 10#include 11#include 12#include /* for sprintf() */ 13#include14#include 15#include 16#include /* for get_user and put_user */ 17 18#include19 20/* Prototypes - this would normally go in a .h file */ 21static int device_open(struct inode *, struct file *); 22static int device_release(struct inode *, struct file *); 23static ssize_t device_read(struct file *, char __user *, size_t, loff_t *); 24static ssize_t device_write(struct file *, const char __user *, size_t, 25 loff_t *); 26 27#define SUCCESS 0 28#define DEVICE_NAME "chardev" /* Dev name as it appears in /proc/devices */ 29#define BUF_LEN 80 /* Max length of the message from the device */ 30 31/* Global variables are declared as static, so are global within the file. */ 32 33static int major; /* major number assigned to our device driver */ 34 35enum { 36 CDEV_NOT_USED = 0, 37 CDEV_EXCLUSIVE_OPEN = 1, 38}; 39 40/* Is device open? Used to prevent multiple access to device */ 41static atomic_t already_open = ATOMIC_INIT(CDEV_NOT_USED); 42 43static char msg[BUF_LEN + 1]; /* The msg the device will give when asked */ 44 45static struct class *cls; 46 47static struct file_operations chardev_fops = { 48 .read = device_read, 49 .write = device_write, 50 .open = device_open, 51 .release = device_release, 52}; 53 54static int __init chardev_init(void) 55{ 56 major = register_chrdev(0, DEVICE_NAME, &chardev_fops); 57 58 if (major < 0) { 59 pr_alert("Registering char device failed with %dn", major); 60 return major; 61 } 62 63 pr_info("I was assigned major number %d.n", major); 64 65 cls = class_create(THIS_MODULE, DEVICE_NAME); 66 device_create(cls, NULL, MKDEV(major, 0), NULL, DEVICE_NAME); 67 68 pr_info("Device created on /dev/%sn", DEVICE_NAME); 69 70 return SUCCESS; 71} 72 73static void __exit chardev_exit(void) 74{ 75 device_destroy(cls, MKDEV(major, 0)); 76 class_destroy(cls); 77 78 /* Unregister the device */ 79 unregister_chrdev(major, DEVICE_NAME); 80} 81 82/* Methods */ 83 84/* Called when a process tries to open the device file, like 85 * "sudo cat /dev/chardev" 86 */ 87static int device_open(struct inode *inode, struct file *file) 88{ 89 static int counter = 0; 90 91 if (atomic_cmpxchg(&already_open, CDEV_NOT_USED, CDEV_EXCLUSIVE_OPEN)) 92 return -EBUSY; 93 94 sprintf(msg, "I already told you %d times Hello world!n", counter++); 95 try_module_get(THIS_MODULE); 96 97 return SUCCESS; 98} 99 100/* Called when a process closes the device file. */ 101static int device_release(struct inode *inode, struct file *file) 102{ 103 /* We're now ready for our next caller */ 104 atomic_set(&already_open, CDEV_NOT_USED); 105 106 /* Decrement the usage count, or else once you opened the file, you will 107 * never get rid of the module. 108 */ 109 module_put(THIS_MODULE); 110 111 return SUCCESS; 112} 113 114/* Called when a process, which already opened the dev file, attempts to 115 * read from it. 116 */ 117static ssize_t device_read(struct file *filp, /* see include/linux/fs.h */ 118 char __user *buffer, /* buffer to fill with data */ 119 size_t length, /* length of the buffer */ 120 loff_t *offset) 121{ 122 /* Number of bytes actually written to the buffer */ 123 int bytes_read = 0; 124 const char *msg_ptr = msg; 125 126 if (!*(msg_ptr + *offset)) { /* we are at the end of message */ 127 *offset = 0; /* reset the offset */ 128 return 0; /* signify end of file */ 129 } 130 131 msg_ptr += *offset; 132 133 /* Actually put the data into the buffer */ 134 while (length && *msg_ptr) { 135 /* The buffer is in the user data segment, not the kernel 136 * segment so "*" assignment won't work. We have to use 137 * put_user which copies data from the kernel data segment to 138 * the user data segment. 139 */ 140 put_user(*(msg_ptr++), buffer++); 141 length--; 142 bytes_read++; 143 } 144 145 *offset += bytes_read; 146 147 /* Most read functions return the number of bytes put into the buffer. */ 148 return bytes_read; 149} 150 151/* Called when a process writes to dev file: echo "hi" > /dev/hello */ 152static ssize_t device_write(struct file *filp, const char __user *buff, 153 size_t len, loff_t *off) 154{ 155 pr_alert("Sorry, this operation is not supported.n"); 156 return -EINVAL; 157} 158 159module_init(chardev_init); 160module_exit(chardev_exit); 161 162MODULE_LICENSE("GPL");
6.6 Writing Modules for Multiple Kernel Versions
The system calls, which are the major interface the kernel shows to the processes,
generally stay the same across versions. A new system call may be added, but
usually the old ones will behave exactly like they used to. This is necessary for
backward compatibility – a new kernel version is not supposed to break regular
processes. In most cases, the device files will also remain the same. On the other
hand, the internal interfaces within the kernel can and do change between
versions.
There are differences between different kernel versions, and if you want
to support multiple kernel versions, you will find yourself having to code
conditional compilation directives. The way to do this to compare the macro
LINUX_VERSION_CODE
to the macro
KERNEL_VERSION
. In version a.b.c of the kernel, the value of this macro would be
.
7 The /proc File System
In Linux, there is an additional mechanism for the kernel and kernel modules to send
information to processes — the /proc file system. Originally designed to allow easy
access to information about processes (hence the name), it is now used by every bit
of the kernel which has something interesting to report, such as /proc/modules
which provides the list of modules and /proc/meminfo which gathers memory usage
statistics.
The method to use the proc file system is very similar to the one used with device
drivers — a structure is created with all the information needed for the /proc file,
including pointers to any handler functions (in our case there is only one, the
one called when somebody attempts to read from the /proc file). Then,
init_module
registers the structure with the kernel and
cleanup_module
unregisters it.
Normal file systems are located on a disk, rather than just in memory (which is
where /proc is), and in that case the index-node (inode for short) number
is a pointer to a disk location where the file’s inode is located. The inode
contains information about the file, for example the file’s permissions, together
with a pointer to the disk location or locations where the file’s data can be
found.
Because we don’t get called when the file is opened or closed, there’s nowhere for
us to put try_module_get
and
module_put
in this module, and if the file is opened and then the module is removed, there’s no
way to avoid the consequences.
Here a simple example showing how to use a /proc file. This is the HelloWorld for
the /proc filesystem. There are three parts: create the file /proc/helloworld in the
function init_module
, return a value (and a buffer) when the file /proc/helloworld is read in the callback
function procfile_read
, and delete the file /proc/helloworld in the function
cleanup_module
.
The /proc/helloworld is created when the module is loaded with the function
proc_create
. The return value is a
struct proc_dir_entry
, and it will be used to configure the file /proc/helloworld (for example, the owner
of this file). A null return value means that the creation has failed.
Every time the file /proc/helloworld is read, the function
procfile_read
is called. Two parameters of this function are very important: the buffer
(the second parameter) and the offset (the fourth one). The content of the
buffer will be returned to the application which read it (for example the
cat
command). The offset is the current position in the file. If the return value of the
function is not null, then this function is called again. So be careful with this
function, if it never returns zero, the read function is called endlessly.
$ cat /proc/helloworld HelloWorld!
1/* 2 * procfs1.c 3 */ 4 5#include6#include 7#include 8#include 9#include 10 11#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0) 12#define HAVE_PROC_OPS 13#endif 14 15#define procfs_name "helloworld" 16 17static struct proc_dir_entry *our_proc_file; 18 19static ssize_t procfile_read(struct file *file_pointer, char __user *buffer, 20 size_t buffer_length, loff_t *offset) 21{ 22 char s[13] = "HelloWorld!n"; 23 int len = sizeof(s); 24 ssize_t ret = len; 25 26 if (*offset >= len || copy_to_user(buffer, s, len)) { 27 pr_info("copy_to_user failedn"); 28 ret = 0; 29 } else { 30 pr_info("procfile read %sn", file_pointer->f_path.dentry->d_name.name); 31 *offset += len; 32 } 33 34 return ret; 35} 36 37#ifdef HAVE_PROC_OPS 38static const struct proc_ops proc_file_fops = { 39 .proc_read = procfile_read, 40}; 41#else 42static const struct file_operations proc_file_fops = { 43 .read = procfile_read, 44}; 45#endif 46 47static int __init procfs1_init(void) 48{ 49 our_proc_file = proc_create(procfs_name, 0644, NULL, &proc_file_fops); 50 if (NULL == our_proc_file) { 51 proc_remove(our_proc_file); 52 pr_alert("Error:Could not initialize /proc/%sn", procfs_name); 53 return -ENOMEM; 54 } 55 56 pr_info("/proc/%s createdn", procfs_name); 57 return 0; 58} 59 60static void __exit procfs1_exit(void) 61{ 62 proc_remove(our_proc_file); 63 pr_info("/proc/%s removedn", procfs_name); 64} 65 66module_init(procfs1_init); 67module_exit(procfs1_exit); 68 69MODULE_LICENSE("GPL");
7.1 The proc_ops Structure
The proc_ops
structure is defined in include/linux/proc_fs.h in Linux v5.6+. In older kernels, it
used file_operations
for custom hooks in /proc file system, but it contains some
members that are unnecessary in VFS, and every time VFS expands
file_operations
set, /proc code comes bloated. On the other hand, not only the space,
but also some operations were saved by this structure to improve its
performance. For example, the file which never disappears in /proc can set the
proc_flag
as
PROC_ENTRY_PERMANENT
to save 2 atomic ops, 1 allocation, 1 free in per open/read/close sequence.
7.2 Read and Write a /proc File
We have seen a very simple example for a /proc file where we only read
the file /proc/helloworld. It is also possible to write in a /proc file. It
works the same way as read, a function is called when the /proc file
is written. But there is a little difference with read, data comes from
user, so you have to import data from user space to kernel space (with
copy_from_user
or
get_user
)
The reason for copy_from_user
or
get_user
is that Linux memory (on Intel architecture, it may be different under some
other processors) is segmented. This means that a pointer, by itself, does
not reference a unique location in memory, only a location in a memory
segment, and you need to know which memory segment it is to be able to use
it. There is one memory segment for the kernel, and one for each of the
processes.
The only memory segment accessible to a process is its own, so when
writing regular programs to run as processes, there is no need to worry about
segments. When you write a kernel module, normally you want to access
the kernel memory segment, which is handled automatically by the system.
However, when the content of a memory buffer needs to be passed between
the currently running process and the kernel, the kernel function receives
a pointer to the memory buffer which is in the process segment. The
put_user
and
get_user
macros allow you to access that memory. These functions handle
only one character, you can handle several characters with
copy_to_user
and
copy_from_user
. As the buffer (in read or write function) is in kernel space, for write function you
need to import data because it comes from user space, but not for the read function
because data is already in kernel space.
1/* 2 * procfs2.c - create a "file" in /proc 3 */ 4 5#include/* We 're doing kernel work */ 6#include/* Specifically, a module */ 7#include/* Necessary because we use the proc fs */ 8#include/* for copy_from_user */ 9#include10 11#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0) 12#define HAVE_PROC_OPS 13#endif 14 15#define PROCFS_MAX_SIZE 1024 16#define PROCFS_NAME "buffer1k" 17 18/* This structure hold information about the /proc file */ 19static struct proc_dir_entry *our_proc_file; 20 21/* The buffer used to store character for this module */ 22static char procfs_buffer[PROCFS_MAX_SIZE]; 23 24/* The size of the buffer */ 25static unsigned long procfs_buffer_size = 0; 26 27/* This function is called then the /proc file is read */ 28static ssize_t procfile_read(struct file *file_pointer, char __user *buffer, 29 size_t buffer_length, loff_t *offset) 30{ 31 char s[13] = "HelloWorld!n"; 32 int len = sizeof(s); 33 ssize_t ret = len; 34 35 if (*offset >= len || copy_to_user(buffer, s, len)) { 36 pr_info("copy_to_user failedn"); 37 ret = 0; 38 } else { 39 pr_info("procfile read %sn", file_pointer->f_path.dentry->d_name.name); 40 *offset += len; 41 } 42 43 return ret; 44} 45 46/* This function is called with the /proc file is written. */ 47static ssize_t procfile_write(struct file *file, const char __user *buff, 48 size_t len, loff_t *off) 49{ 50 procfs_buffer_size = len; 51 if (procfs_buffer_size > PROCFS_MAX_SIZE) 52 procfs_buffer_size = PROCFS_MAX_SIZE; 53 54 if (copy_from_user(procfs_buffer, buff, procfs_buffer_size)) 55 return -EFAULT; 56 57 procfs_buffer[procfs_buffer_size & (PROCFS_MAX_SIZE - 1)] = '