foreword
I don’t know if you have ever thought that in a kernel module code, printk
a function will be used, and this function is not implemented by us, it is part of the kernel code, but why can we compile it?
The reason why our code can be compiled is because the compilation of the module is only compiled and not linked .
The compiled .ko
file is an ordinary ELF
file . Using file
commands and nm
commands, we can see related information:
# file vser.ko
vser.ko ELF 32-bit LSB relocatable, Intel 80386, vserion 1 (SYSV), BuildID[sha1]=0x09ca747e6f75c65v19a5da9102113v98d7cea24, not stripped
# nm vser.ko
......
00000004 d port
U printk
00000000 t vser_exit
00000000 t vser_init
vser_init
and vser_exit
are the entry function and exit function of the module respectively. nm
When you use the command to view the symbol information of the module object file, you can see that the symbol type of vser_exit
and is , indicating that they are functions .vser_init
t
And printk
the symbol type is U
, which means it is a pending symbol . It means that the address of this symbol is not known during the compilation phase, because it is defined in other files and is not compiled together with the module code.
How to solve the address problem of the printk function? The answer is to use EXPORT_SYMBOL
a macro to printk
export it.
EXPORT_SYMBOL export symbols
General principle: Use EXPORT_SYMBOL
macros to generate a specific structure and put it in ELF
a specific segment of the file. During the startup process of the kernel, the exact address of the symbol will be filled into a specific member of this structure .
When the module is loaded, the loader will process pending symbols, search for the name of the symbol in the special segment, and if found, fill the obtained address into the corresponding segment of the loaded module, so that the address of the symbol can be determined.
Using this method to process pending symbols is actually equivalent to postponing the linking process and performing dynamic linking, which is similar to the reason why ordinary applications use shared library functions . It can be found that the kernel will have a large number of symbol exports, providing a rich infrastructure for the module.
kernel module dependencies
Normally, a module only uses symbols exported by the kernel and does not export symbols itself. But if a module needs to provide global variables or functions for use by other modules, then these symbols need to be exported.
This is more common when one driver calls another driver's code, so that a dependency relationship is formed between modules, and the module that uses the exported symbol will depend on the module that exported the symbol.
To give a specific example, the following are two C files that vser.c
call dep.c
variables and functions in:
vser.c
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
extern int expval;
extern void expfun(void);
static int __init vser_init(void)
{
printk("vser_init\");
printk("expval:%d\n", expval);
expfun();
return 0;
}
static void __exit vser_exit(void)
{
printk("vser_exit\n");
}
module_init(vser_init);
module_exit(vser_exit);
dep.c
#include <linux/kernel.h>
#include <linux/module.h>
static int expval = 5;
EXPORT_SYMBOL(expval);
static void expfun(void)
{
printk("expfun");
}EXPORT_SYMBOL_GPL(expfun);
Key points of Makefile:
obj-m := vser.o
obj-m += dep.o
In the above code, dep.c defines a variable expval
and a function , which are used and exported expfun
respectively . And the variables and functions called in it , after compiling and installing:EXPORT_SYMBOL
EXPORT_SYMBOL_GPL
vser.c
dep.c
# modprobe vser
# dmesg
[58278.204677] vser_init
[58278.204683] expval:5
[58287.206464] expfun
As can be seen from the output information, the variables and functions vser.c
are correctly referenced .dep.c
Information through train: Linux kernel source code technology learning route + video tutorial kernel source code
Learning through train: Linux kernel source code memory tuning file system process management device driver/network protocol stack
Here are three important notes:
- If you use
insmod
the command to load the module, you must first load the dep module, and then load the vser module .
Because the vser module uses the dep module. It can be seen from this that modprobe
commands are superior to insmod
commands in that they can automatically load dependent modules. And this is thanks to depmod
the command, depmod
which generates the dependency information of the module and saves it in /lib/modules/5.10.111-64-generic/modules.dep
the file. Among them, 5.10.111-64-generic is the kernel source version. Viewing this file reveals the modules that the vser module depends on.
# cat /lib/modules/5.10.111-64-generic/modules.dep
......
extra/vser.ko: extra/dep.ko
extra/dep.ko:
- There is a dependency between the two modules. If you compile the two modules separately, a warning message similar to the following will appear, and even if the loading sequence is correct, the loading will not succeed :
WARNING: "expfun" [/home/ubuntu/driver/module/vser.ko] undefined!
WARNING: "expval" [/home/ubuntu/driver/module/vser.ko] undefined!
# sudo insmod dep.ko
# sudo insmod vser.ko
insmod:error inserting 'vser.ko': -1 Invalid parameters
This is because the entry for and vser
is not found in the kernel's symbol table when the module is compiled , and the module is completely unaware of the module's existence.expval
expfun
vser
dep
The solution to this problem is to compile the two modules together, or put the dep module in the kernel source code, first compile all the modules under the kernel source code, and then compile the vser module.
- When uninstalling a module, first uninstall the vser module, and then uninstall the dep module, otherwise the dep module will not be uninstalled because it is used by the vser module .
The kernel will create a link to the module's dependencies, and the module can only be unloaded if the linked list of dependencies on this module is empty.