[Kernel driver] The first kernel module

00. Table of Contents

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_initthe and module_exitfunctions are declared in /include/linux/module.hthe file. In older versions of the kernel, these two functions are declared in /include/linux/init.hthe 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

Insert image description here

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]# 

10. Appendix

Guess you like

Origin blog.csdn.net/dengjin20104042056/article/details/132876961