Select Page

The Hello World Linux Loadable Kernel Module

A kernel module is not an application — for a start there is no main() function! Some of the key differences are that kernel modules:

  • do not execute sequentially— a kernel module registers itself to handle requests using its initialization function, which runs and then terminates. The type of requests that it can handle are defined within the module code. This is quite similar to the event-driven programming model that is commonly utilized in graphical-user interface (GUI) applications.
  • do not have automatic cleanup — any resources that are allocated to the module must be manually released when the module is unloaded, or they may be unavailable until a system reboots.
  • do not have printf() functions — kernel code cannot access libraries of code that is written for the Linux user space. The kernel module lives and runs in kernel space, which has its own memory address space. The interface between kernel space and user space is clearly defined and controlled. We do however have a printk() function that can output information, which can be viewed from within user space.
  • can be interrupted — one conceptually difficult aspect of kernel modules is that they can be used by several different programs/processes at the same time. We have to carefully construct our modules so that they have a consistent and valid behavior when they are interrupted. The BeagleBone has a single-core processor (for the moment) but we still have to consider the impact of multiple processes accessing the module simultaneously.
  • have a higher level of execution privilege — typically, more CPU cycles are allocated to kernel modules than to user-space programs. This sounds like an advantage, however, you have to be very careful that your module does not adversely affect the overall performance of your system.
  • do not have floating-point support — it is kernel code that uses traps to transition from integer to floating-point mode for your user space applications. However, it is very difficult to perform these traps in kernel space. The alternative is to manually save and restore floating point operations — a task that is best avoided and left to your user-space code.

hello.c

  1. /**
  2.  * file    hello.c
  3.  * author  J. Shankarappa
  4.  * date    4 Nov 2019
  5.  * version 0.1
  6.  * An introductory "Hello World!" loadable kernel module (LKM) that can display a message
  7.  * in the /var/log/kern.log file when the module is loaded and removed. The module can accept an
  8.  * argument when it is loaded -- the name, which appears in the kernel log files.
  9. */
  10. 
    
  11. #include <linux/init.h>             /* Macros used to mark up functions e.g., __init __exit */
  12. #include <linux/module.h>           /* Core header for loading LKMs into the kernel */
  13. #include <linux/kernel.h>           /* Contains types, macros, functions for the kernel */
  14. 
    
  15. MODULE_LICENSE("GPL");              /* The license type -- this affects runtime behavior */
  16. MODULE_AUTHOR("J.Shankarappa");     /* The author -- visible when you use modinfo */
  17. MODULE_DESCRIPTION("A simple Linux driver for the BBB.");  /* The description -- see modinfo */
  18. MODULE_VERSION("0.1");              /* The version of the module */
  19. 
    
  20. static char *name = "World";        /* An example LKM argument -- default value is "world" */
  21. module_param(name, charp, S_IRUGO); /* Param desc. charp = char ptr, S_IRUGO can be read/not changed */
  22. MODULE_PARM_DESC(name, "The name to display in /var/log/kern.log");  /* parameter description */
  23. 
    
  24. /** The LKM initialization function
  25.  *  The static keyword restricts the visibility of the function to within this C file. The __init
  26.  *  macro means that for a built-in driver (not a LKM) the function is only used at initialization
  27.  *  time and that it can be discarded and its memory freed up after that point.
  28.  *  
  29.  * returns : 0 if successful
  30.  */
  31. static int __init helloBBB_init(void){
  32.    printk(KERN_INFO "EmSys: Hello %s from the BBB LKM!\n", name);
  33.    return 0;
  34. }
  35. 
    
  36. /** The LKM cleanup function
  37.  *  Similar to the initialization function, it is static. The __exit macro notifies that if this
  38.  *  code is used for a built-in driver (not a LKM) that this function is not required.
  39.  */
  40. static void __exit helloBBB_exit(void){
  41.    printk(KERN_INFO "EmSys: Goodbye %s from the BBB LKM!\n", name);
  42. }
  43. 
    
  44. /** A module must use the module_init() and module_exit() macros from linux/init.h, which
  45.  *  identify the initialization function at insertion time and the cleanup function (as
  46.  *  listed above)
  47.  */
  48. module_init(helloBBB_init);
  49. module_exit(helloBBB_exit);
  • Line 15: The statement MODULE_LICENSE(“GPL”) provides information (via modinfo) about the licensing terms of the module that you have developed, thus allowing users of your LKM to ensure that they are using free software. Since the kernel is released under the GPL, your license choice impacts upon the way that the kernel treats your module.
  • Line 20: The name (ptr to char) is declared as static and is initialized to contain the string “hello”. You should avoid using global variables in kernel modules — it is even more important than in application programming, as global variables are shared kernel wide. You should use the static keyword to restrict a variable’s scope to within the module. If you must use a global variable, add a prefix that is unique to the module that you are writing.
  • Line 21: The module_param(name, type, permissions) macro has three parameters: name (the parameter name displayed to the user and the variable name in the module), type (the type of the parameter — i.e., one of byte, int, uint, long, ulong, short, ushort, bool, an inverse Boolean invbool, or a char pointer charp), and permissions (this is the access permissions to the the parameter when using sysfs. A value of 0 disables the entry, but S_IRUGO allows read access for user/group/others — See the Mode Bits for Access Permissions Guide)
  • Line 30 and 39: The functions can have whatever names you like (e.g., helloBBB_init() and helloBBB_exit()), however, the same names must be passed to the special macros module_init() and module_exit() on lines 48 and 49.
  • Line 32: The printk() is very similar in usage to the printf() function that you should be familiar with, and you can call it from anywhere within the kernel module code. The only significant difference is that you should specify a log level when you call the function. The log levels are defined in linux/kern_levels.h as one of KERN_EMERG, KERN_ALERT, KERN_CRIT, KERN_ERR, KERN_WARNING, KERN_NOTICE, KERN_INFO, KERN_DEBUG, and KERN_DEFAULT. This header is included via the linux/kernel.h header file, which includes it via linux/printk.h.

Essentially, when this module is loaded the helloBBB_init() function will execute, and when the module is unloaded the helloBBB_exit() function will execute.

Building the Module Code

A Makefile is required to build the kernel module — in fact, it is a special kbuild Makefile. The kbuild Makefile required to build the kernel module is shown below:

Makefile

  1. MODULES := hello.o
  2. 
    
  3. #guest architecture
  4. ARCH := arm
  5. 
    
  6. CROSS_COMPILE := arm-linux-gnueabihf-
  7. obj-m := $(MODULES)
  8. 
    
  9. # path of the arm compiled kernel
  10. KDIR := /path/to/compiled/linux/kernel
  11. DESTDIR := /path/to/compiled/kernel/target
  12. 
    
  13. MAKEARCH := $(MAKE) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE)
  14. 
    
  15. all: modules
  16. modules:
  17. 	$(MAKEARCH) -C $(KDIR) M=${shell pwd} modules
  18. 
    
  19. install:
  20. 	$(MAKE) -C $(KDIR) M=$(PWD) INSTALL_MOD_PATH=$(DESTDIR) modules_install
  21. 
    
  22. clean:
  23. 	$(MAKEARCH) -C $(KDIR) M=${shell pwd} clean
  • Line 7 of this Makefile is called a goal definition and it defines the module to be built (hello.o). obj-m defines a loadable module goal, whereas obj-y indicates a built-in object goal.
  • Line 17: The -C option switches the directory to the kernel directory before performing any make tasks. The M=${shell pwd} variable assignment tells the make command where the actual project files exist.
  • The modules target is the default target for external kernel modules. An alternative target is modules_install which would install the module (the make command would have to be executed with superuser permissions and the module installation path is required).
$ make

Testing the LKM