00. Table of Contents
Article directory
01. Overview
In Linux systems, device drivers will appear in the form of kernel modules. Learning Linux kernel module programming is a prerequisite for driver development.
Linux is a cross-platform operating system that supports many devices. More than 50% of the code in the Linux kernel source code is related to device drivers. Linux has a macro kernel architecture. If all functions are enabled, the kernel will become very bloated. A kernel module is a piece of kernel code that implements a certain function. During the running process of the kernel, this part of the code can be loaded into the kernel, thereby dynamically increasing the functionality of the kernel. Based on this feature, when we develop device drivers, we write device drivers in the form of kernel modules. We only need to compile the relevant driver code, without compiling the entire kernel.
The introduction of kernel modules not only improves the flexibility of the system, but also provides great convenience to developers. During the development process of device drivers, we can add or remove the driver under test to the kernel at will. Every time we modify the code of the kernel module, we do not need to restart the kernel. On the development board, we do not need to store the kernel module program or the ELF file of the device driver on the development board to avoid occupying unnecessary storage space. When you need to load the kernel module, you can load the kernel module stored in other devices onto the development board by mounting the NFS server. On some specific occasions, we can load/unload the system's kernel module as needed to better serve the current environment.
02. The first kernel module
test.c
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
static int __init hello_init(void)
{
printk(KERN_EMERG "Hello Module Init\n");
return 0;
}
static void __exit hello_exit(void)
{
printk(KERN_EMERG "Hello Module Exit\n");
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("uplooking");
MODULE_DESCRIPTION("hello module");
MODULE_ALIAS("test_module");
03. Code framework analysis
The code framework of the Linux kernel module usually consists of the following parts:
- Module loading function (required): When the kernel module is loaded through the insmod or modprobe command, the module's loading function will automatically be executed by the kernel to complete the initialization work related to this module.
- Module uninstall function (required): When the rmmod command is executed to uninstall a module, the module uninstall function will be automatically executed by the kernel to complete related cleanup work.
- Module license statement (required): The license statement describes the permissions of the kernel module. If the module is not declared, there will be a warning that the kernel is contaminated when the module is loaded.
- Module parameters: Module parameters are parameters that can be passed to the module when the module is loaded.
- Module exports symbols: Modules can export prepared variables or functions as symbols so that they can be called by other kernel modules.
- Other related information about the module: You can declare the module author and other information.
The hello module program in the above example only contains the above three necessary parts and other information declarations of the module.
The header file includes <linux/init.h> and <linux/module.h>. These two header files must be included when writing a kernel module. The module initialization function hello_init calls the printk function. During the running process of the kernel module, it cannot rely on the C library function, so the printf function cannot be used and a separate printout function printk needs to be used. The usage of this function is similar to the printf function. After completing the module initialization function, you need to call the macro module_init to tell the kernel to use the hello_init function for initialization. The module uninstall function also uses the printk function to print a string, and uses the macro module_exit to register the module's uninstall function in the kernel. Finally, it must be declared that the module is used under a license, here we set it to GPL2.
04. Header file
We have already been exposed to Linux application programming before and learned that Linux header files are stored in /usr/include. The header files required for writing kernel modules are not in the directory mentioned above, but in the include folder in the Linux kernel source code.
- #include <linux/module.h>: Contains declarations of kernel loading module_init()/unloading module_exit() functions and kernel module information related functions
- #include <linux/init.h>: Contains macro definitions for some kernel module related sections
- #include <linux/kernel.h>: Contains various functions provided by the kernel, such as printk
Header file path
deng@local:~/a72/x3399/kernel/include/linux$ pwd
/home/deng/a72/x3399/kernel/include/linux
deng@local:~/a72/x3399/kernel/include/linux$
The following two header files are often used when writing kernel modules: <linux/init.h> and <linux/module.h>. We can see that there is also a folder name linux in front of the header file, which corresponds to the linux folder under include. We go to the folder to see what the contents of these two header files are.
init.h header file (located in kernel source code/include/linux/init.h)
/* These macros are used to mark some functions or
* initialized data (doesn't apply to uninitialized data)
* as `initialization' functions. The kernel can take this
* as hint that the function is used only during the initialization
* phase and free up used memory resources after
*
* Usage:
* For functions:
*
* You should add __init immediately before the function name, like:
*
* static void __init initme(int x, int y)
* {
* extern int z; z = x * y;
* }
*
* If the function has a prototype somewhere, you can also add
* __init between closing brace of the prototype and semicolon:
*
* extern int initialize_foobar_device(int, int, int) __init;
*
* For initialized data:
* You should insert __initdata or __initconst between the variable name
* and equal sign followed by value, e.g.:
*
* static int init_variable __initdata = 0;
* static const char linux_logo[] __initconst = { 0x32, 0x36, ... };
*
* Don't forget to initialize data not at file scope, i.e. within a function,
* as gcc otherwise puts the data into the bss section and not into the init
* section.
*/
/* These are for everybody (although not all archs will actually
discard it in modules) */
#define __init __section(.init.text) __cold notrace __noretpoline
#define __initdata __section(.init.data)
#define __initconst __constsection(.init.rodata)
#define __exitdata __section(.exit.data)
#define __exit_call __used __section(.exitcall.exit)
#define __exit __section(.exit.text) __exitused __cold notrace
The init.h header file mainly contains some macro definitions used by the kernel module. Therefore, as long as we involve the programming of the kernel module, we need to add this header file.
module.h header file (located in kernel source code/include/linux/module.h)
/**
* module_init() - driver initialization entry point
* @x: function to be run at kernel boot time or module insertion
*
* module_init() will either be called during do_initcalls() (if
* builtin) or at module insertion time (if a module). There can only
* be one per module.
*/
#define module_init(x) __initcall(x);
/**
* module_exit() - driver exit entry point
* @x: function to be run when driver is removed
*
* module_exit() will wrap the driver clean-up code
* with cleanup_module() when used with rmmod when
* the driver is a module. If the driver is statically
* compiled into the kernel, module_exit() has no effect.
* There can only be one per module.
*/
#define module_exit(x) __exitcall(x);
/* Generic info of form tag = "info" */
#define MODULE_INFO(tag, info) __MODULE_INFO(tag, tag, info)
#define MODULE_ALIAS(_alias) MODULE_INFO(alias, _alias)
#define MODULE_LICENSE(_license) MODULE_INFO(license, _license)
#define MODULE_AUTHOR(_author) MODULE_INFO(author, _author)
The above code contains the declaration of the loading and unloading functions of the kernel module, and also lists some macro definitions in the module.h file. Some of these macro definitions are dispensable, but MODULE_LICENSE specifies the kernel module. A license is a must.
Note: In the 4.x version of the kernel used in this tutorial, module_init
the and module_exit
functions are declared in /include/linux/module.h
the file. In older versions of the kernel, these two functions are declared in /include/linux/init.h
the file.
05. Module loading/unloading function
5.1 module_init function
/**
* module_init() - driver initialization entry point
* @x: function to be run at kernel boot time or module insertion
*
* module_init() will either be called during do_initcalls() (if
* builtin) or at module insertion time (if a module). There can only
* be one per module.
*/
#define module_init(x) __initcall(x);
return value:
- 0: Indicates that the module is initialized successfully and a new directory with the module name will be created under /sys/module.
- Non-0: Indicates module initialization failed
The macro definition module_init is used to inform the kernel which function to use for initialization when initializing the module. It will add the function address to the corresponding section, so that the module can be automatically loaded when booting.
[root@rk3399:/]# cd /sys/module/
[root@rk3399:/sys/module]# ls
8250 firmware_class pstore sr_mod
8250_core ftdi_sio ramoops stmmac
asix fuse rcupdate sunrpc
auth_rpcgss hci_vhci rcutree suspend
bfusb hid rfcomm sysrq
block hidp rfkill tc35874x
bluetooth i2c_algo_bit rk817_battery tcp_cubic
brd i2c_hid rk817_charger tpm
btbcm input_polldev rk_vcodec tpm_i2c_infineon
btintel ipv6 rndis_wlan uinput
btmrvl kernel rng_core usb_storage
btmrvl_sdio keyboard rockchip_pwm_remotectl usbcore
btrtl libertas_tf rtc_pcf8563 usbhid
btusb lkdtm sbs_battery usbtouchscreen
cdc_ncm lockd scsi_mod uvcvideo
cec loop sdhci v4l2_mem2mem
cfg80211 mac80211 sierra video_rkisp1
configfs mali snd videobuf2_core
cpuidle midgard_kbase snd_pcm videobuf2_dma_sg
devres mmcblk snd_seq videobuf_core
dns_resolver module snd_seq_dummy vt
drm mpp_dev_common snd_seq_midi workqueue
drm_kms_helper nfs snd_soc_rk817 xhci_hcd
dynamic_debug ntfs snd_timer xz_dec
ehci_hcd nvme snd_usb_audio
elan_i2c pcie_aspm spidev
fiq_debugger printk spurious
[root@rk3399:/sys/module]#
Reference example
static int __init func_init(void)
{
}
module_init(func_init);
The func_init function also does initialization-related work in the kernel module.
In C language, the function of static keyword is as follows:
- Static local variables modified by static are not released until the program is finished running, extending the life cycle of local variables;
- Static modified global variables can only be accessed in this file and cannot be accessed in other files;
- Functions modified by static can only be called in this file and cannot be called by other files.
The code of the kernel module is actually part of the kernel code. If the function defined by the kernel module is repeated with a function in the kernel source code, the compiler will report an error, causing the compilation to fail. Therefore, we add If you use the static modifier, you can avoid this error.
__init, __initdata macro definition (located in kernel source code/include/linux/init.h)
#define __init __section(.init.text) __cold __latent_entropy __noinitretpoline
#define __initdata __section(.init.data)
In the __init and __initdata macro definitions of the above code (located in the kernel source code/linux/init.h), __init is used to modify functions, and __initdata is used to modify variables. With the __init modifier, it means that the function is placed in the __init section of the executable file. The content of this section can only be used in the initialization phase of the module. After the initialization phase is completed, the content of this part will be released.
5.2 module_exit
Contrary to the kernel loading function, the kernel module unloading function func_exit is mainly used to release the memory allocated during the initialization phase, the allocated device number, etc., which is the reverse process of the initialization process.
/**
* module_exit() - driver exit entry point
* @x: function to be run when driver is removed
*
* module_exit() will wrap the driver clean-up code
* with cleanup_module() when used with rmmod when
* the driver is a module. If the driver is statically
* compiled into the kernel, module_exit() has no effect.
* There can only be one per module.
*/
#define module_exit(x) __exitcall(x);
__exit, __exitdata macro definition (located in kernel source code/include/linux/init.h)
#define __exit __section(.exit.text) __exitused __cold notrace
#define __exitdata __section(.exit.data)
Analogous to the module loading function, __exit is used to modify functions, and __exitdata is used to modify variables. The macro definition module_exit is used to tell the kernel which function needs to be called when the module is unloaded.
Reference example
static void __exit func_exit(void)
{
}
module_exit(func_exit);
The difference from the function func_init is that the return value of this function is void type, and the modifiers are also different. The __exit used here means that the function is placed in the __exit section of the executable file. When the module uninstallation phase is completed, After that, the space in this area will be automatically released.
06. Module information
Table kernel module information declaration function
function | effect |
---|---|
MODULE_LICENSE() | Indicates the software license agreement accepted by the module code. The Linux kernel follows the GPL V2 open source agreement. The kernel module only needs to be consistent with the Linux kernel. |
MODULE_AUTHOR() | Author information describing the module |
MODULE_DESCRIPTION() | A brief introduction to the module |
MODULE_ALIAS() | Set an alias for the module |
6.1 License
Linux is a free operating system that adopts the GPL license, allowing users to modify its source code at will. The main content of the GPL agreement is that even if a library provided by a certain GPL agreement product is used in a software product and a new product is derived, the software product must adopt the GPL agreement, that is, it must be open source and free to use. It can be seen that the GPL agreement is contagious. sex. Therefore, we can use a variety of free software in Linux. In the process of learning Linux in the future, you may find that any software we install never has a 30-day trial period or requires an activation code.
/*
* The following license idents are currently accepted as indicating free
* software modules
*
* "GPL" [GNU Public License v2 or later]
* "GPL v2" [GNU Public License v2]
* "GPL and additional rights" [GNU Public License v2 rights and more]
* "Dual BSD/GPL" [GNU Public License v2
* or BSD license choice]
* "Dual MIT/GPL" [GNU Public License v2
* or MIT license choice]
* "Dual MPL/GPL" [GNU Public License v2
* or Mozilla license choice]
*
* The following other idents are available
*
* "Proprietary" [Non free products]
*
* There are dual licensed components, but when running with Linux it is the
* GPL that is relevant so this is a non issue. Similarly LGPL linked with GPL
* is a GPL combined work.
*
* This exists for several reasons
* 1. So modinfo can show license info for users wanting to vet their setup
* is free
* 2. So the community can ignore bug reports including proprietary modules
* 3. So vendors can do likewise based on their own policies
*/
#define MODULE_LICENSE(_license) MODULE_INFO(license, _license)
Kernel module licenses include "GPL", "GPL v2", "GPL and additional rights", "Dual SD/GPL", "Dual MPL/GPL", "Proprietary" and so on.
6.2 Author information
Kernel module author macro definition (located in kernel source code/include/linux/module.h)
/*
* Author(s), use "Name <email>" or just "Name", for multiple
* authors use multiple MODULE_AUTHOR() statements/lines.
*/
#define MODULE_AUTHOR(_author) MODULE_INFO(author, _author)
The "author" information in the module information printed out using modinfo earlier comes from the macro definition MODULE_AUTHOR. This macro definition is used to declare the author of the module.
6.3 Module description information
Module description information (located in kernel source code/include/linux/module.h)
/* What your module does. */
#define MODULE_DESCRIPTION(_description) MODULE_INFO(description, _description)
The "description" information in the module information comes from the macro MODULE_DESCRIPTION, which is used to describe the function of the module.
6.4 Module aliases
Kernel module alias macro definition (located in kernel source code/inlcude/linux/module.h)
/* For userspace: you can also call me... */
#define MODULE_ALIAS(_alias) MODULE_INFO(alias, _alias)
The "alias" information in the module information comes from the macro definition MODULE_ALIAS. This macro definition is used to alias the kernel module. Note that when using the module's alias, you need to copy the module to /lib/modules/kernel source/, and use the command depmod to update the module's dependencies. Otherwise, how does the Linux kernel know that this module has another name.
07. Output function
printk function
/**
* printk - print a kernel message
* @fmt: format string
*
* This is printk(). It can be called from any context. We want it to work.
*
* We try to grab the console_lock. If we succeed, it's easy - we log the
* output and call the console drivers. If we fail to get the semaphore, we
* place the output into the log buffer and return. The current holder of
* the console_sem will notice the new output in console_unlock(); and will
* send it to the consoles before releasing the lock.
*
* One effect of this deferred printing is that code which calls printk() and
* then changes console_loglevel may break. This is because console_loglevel
* is inspected when the actual printing occurs.
*
* See also:
* printf(3)
*
* See the vsnprintf() documentation for format string extensions over C99.
*/
asmlinkage __visible int printk(const char *fmt, ...)
- printf: printing function implemented by glibc, working in user space
- printk: The kernel module cannot use the glibc library function. It is a printf-like function implemented by the kernel itself, but the printing level needs to be specified.
- #define KERN_EMERG "<0>" is usually the information before the system crashes
- #define KERN_ALERT “<1>” Messages that need to be processed immediately
- #define KERN_CRIT “<2>” Critical condition
- #define KERN_ERR "<3>" error condition
- #define KERN_WARNING “<4>” problematic situations
- #define KERN_NOTICE “<5>” note
- #define KERN_INFO “<6>” Normal message
- #define KERN_DEBUG “<7>” Debug information
View the current system printk printing level: cat /proc/sys/kernel/printk
, corresponding from left to right, the current console log level, the default message log level, the minimum console level, and the default console log level.
[root@rk3399:/sys/module]# cat /proc/sys/kernel/printk
7 4 1 7
Print all kernel print information: dmesg. Note that the kernel log buffer size is limited and the buffer data may be overwritten.
08. Makefile
For the kernel module, it is a piece of code that belongs to the kernel, but it is not in the kernel source code. For this reason, we need to compile in the kernel source directory when compiling. The Makefile used to compile the kernel module is roughly the same as the Makefile we used to compile the C code earlier. This is due to the Kbuild system used to compile the Linux kernel. Therefore, when compiling the kernel module, we also need to specify the environment variables ARCH and CROSS_COMPILE. value.
KERNEL_DIR=/home/deng/a72/x3399/kernel
CROSS_COMPILE=aarch64-linux-gnu-gcc
obj-m := test.o
all:
$(MAKE) -C $(KERNEL_DIR) M=`pwd` modules
.PHONE:clean
clean:
$(MAKE) -C $(KERNEL_DIR) M=$(PWD) clean
The above code provides a Makefile for compiling the kernel module.
- Line 1: This Makefile defines the variable KERNEL_DIR to save the directory of the kernel source code.
- Line 2: Toolchain specified
- Line 3: The variable obj-m holds the name of the target file that needs to be compiled into a module.
- Line 4: '$(MAKE)modules' is actually the pseudo-target modules that execute the Linux top-level Makefile. The option '-C' allows the make tool to jump to the source directory to read the top-level Makefile. 'M=$(CURDIR)' indicates returning to the current directory, reading and executing the Makefile in the current directory, and starting to compile the kernel module. pwd is set to the current directory.
Compilation command description
Enter the following command to compile the driver module:
deng@local:~/code/test/1module$ make
make -C /home/deng/a72/x3399/kernel M=`pwd` modules
make[1]: 进入目录“/home/deng/a72/x3399/kernel”
CC [M] /home/deng/a72/code/1module/test.o
Building modules, stage 2.
MODPOST 1 modules
CC /home/deng/a72/code/1module/test.mod.o
LD [M] /home/deng/a72/code/1module/test.ko
make[1]: 离开目录“/home/deng/a72/x3399/kernel”
deng@local:~/code/test/1module$
After successful compilation, a driver module file named "test.ko" will be generated in the directory.
09. Kernel module loading and unloading
Load kernel module
[root@rk3399:/mnt/a72/code/1module]# insmod test.ko
[16099.893741] Hello Module Init
Uninstall kernel module
[root@rk3399:/mnt/a72/code/1module]# rmmod test
[16103.132570] Hello Module Exit
[root@rk3399:/mnt/a72/code/1module]#