Module_Driver_printk – Basic Structure and Log Printing of Modules
1. Creating C and Makefile Files
Command to create a folder
mkdir <folder_name>
Creating a file
touch <file_name>
Deleting a folder (non-empty)
rm -r <folder_name>
-r option: Recursively delete, which means deleting all files and subdirectories under the directory.
Force delete (avoid confirmation prompt)
rm -rf <file_name>
-f option: Force delete, ignore non-existent files, and do not prompt for confirmation.
2. Basic Format of C Files
#include <linux/init.h> // Functions related to driver initialization
#include <linux/module.h> // Basic module functions
#include <linux/kernel.h> // Basic kernel functions (e.g., printk)
// Driver's exit function
static int __init demo_init(void)
{
// __init is used to modify the demo_init function to place this function in the init code section
// __init is defined in init.h #define __init __section(.init.text)
// The section attribute is used to tell the compiler (linux gcc) that the modified variable or function should be placed in a specific section when compiling the executable file
printk("hello Linux\n");
return 0;
}
// Driver's entry function (executed by rmmod)
static void __exit demo_exit(void)
{
printk("Bye Linux\n");
}
module_init(demo_init); // Modifies the entry function of the modular driver
module_exit(demo_exit); // Modifies the exit function of the modular driver
MODULE_LICENSE("GPL"); // Must declare GPL license
MODULE_AUTHOR("Your Name"); // Author information
MODULE_DESCRIPTION("Basic Character Device Driver Template"); // Description information
MODULE_VERSION("1.0"); // Version number
2.1 <span>static</span> (Static Function Modifier)
- • Function: Limits the scope of the function to be visible only within the current
<span>.c</span>file, avoiding name conflicts with functions in other files. - • Kernel Driver Requirement: The entry and exit functions in the driver, as well as internal helper functions, are usually modified with
<span>static</span>because they do not need to be called by other files and are only used internally in this driver. This is a basic specification for modularizing kernel code.
2.2 <span>int</span> (Function Return Value Type)
- • Function: Declares that the function returns an integer, which indicates the result of the driver loading.
- • Kernel Convention:
- • Returning
<span>0</span>indicates that the driver initialization was successful (<span>insmod</span>will execute successfully). - • Returning a negative number (such as
<span>ENOMEM</span>,<span>EIO</span>, etc. kernel error codes) indicates that initialization failed, and the kernel will abandon loading this driver and release any allocated resources. - • This is one of the core differences between the entry function and the exit function (
<span>void</span>type), as the kernel needs to determine whether to load the driver based on the return value of the entry function.
2.3 <span>__init</span> (Kernel Initialization Attribute Macro)
- • Function: Tells the kernel compiler that this function is executed only once when the driver is loaded (
<span>insmod</span>), and after execution, it can be released by the kernel to save memory. - • Underlying Implementation:
<span>__init</span>is a macro defined by the kernel (in<span><linux/init.h></span>), which places the function in the<span>.init.text</span>section (initialization code section) of the kernel image. After the kernel starts or the module is loaded, this section of memory will be released (for modules, it will not be released upon unloading, but will not be used again until the module is unloaded). - • Applicable Scenario: Only used for the entry function of the driver (the function registered with
<span>module_init</span>), as the entry function is executed only once during loading and does not need to be retained afterwards.
2.4. <span>__exit</span> (Kernel Exit Attribute Macro)
- • Function: Tells the kernel compiler that this function is executed only once when the driver is unloaded (
<span>rmmod</span>), and is a dedicated function for the module exit phase. - • Underlying Implementation:
<span>__exit</span>is a macro defined by the kernel (in<span><linux/init.h></span>), which places the function in the<span>.exit.text</span>section (a memory area specifically for exit code). - • Special Features:
- • For “module drivers” (
<span>.ko</span>files):<span>.exit.text</span>section will be loaded with the module and executed only during<span>rmmod</span>, and after unloading, it will be released along with the module’s memory. - • For “kernel built-in drivers” (drivers compiled into the kernel): these drivers cannot be unloaded, so functions modified with
<span>__exit</span>will be ignored by the compiler (to avoid wasting memory). - • Applicable Scenario: Only used for the exit function of the driver (the function registered with
<span>module_exit</span>), as the exit function is executed only once during unloading.
2.5 <span>module_init(demo_init);</span>
- • Function: Registers the function
<span>demo_init</span>as the driver’s entry function (initialization function). - • Trigger Timing: When loading the driver module (
<span>.ko</span>) via<span>insmod</span>or<span>modprobe</span>, the kernel will automatically call the<span>demo_init</span>function to complete the driver initialization work (such as registering devices, allocating resources, initializing hardware, etc.). - • Essence:
<span>module_init</span>is a macro defined by the kernel (in<span><linux/module.h></span>), which stores the function pointer of<span>demo_init</span>in the kernel’s module management linked list, ensuring it is called during loading.
2.6 <span>module_exit(demo_exit);</span>
- • Function: Registers the function
<span>demo_exit</span>as the driver’s exit function (cleanup function). - • Trigger Timing: When unloading the driver module via
<span>rmmod</span>, the kernel will automatically call the<span>demo_exit</span>function to complete the driver cleanup work (such as unregistering devices, releasing resources, restoring hardware state, etc.). - • Essence: Similar to
<span>module_init</span>, the<span>module_exit</span>macro registers the function pointer of<span>demo_exit</span>in the kernel, ensuring it is called during unloading.
2.7 Module Metadata Declaration (<span>MODULE_xxx</span> Series Macros)
These macros are used to declare the basic information of the module to the kernel, which will store this information in the module descriptor and can be viewed using the <span>modinfo</span> command (for example, <span>modinfo demo.ko</span>), while some information (such as license) will affect the loading of the module.
- 1.
<span>MODULE_LICENSE("GPL");</span>
- • Function: Declares the open-source license that the driver follows (must be specified).
- • Key Significance:
- • The Linux kernel is under the GPL license, and if the driver uses
<span>MODULE_LICENSE("GPL")</span>, it can call internal functions marked as<span>EXPORT_SYMBOL_GPL</span>in the kernel (these functions are only open to GPL-compatible modules). - • If not declared or declared as a non-GPL license (such as
<span>Proprietary</span>), the kernel will issue a warning during loading (<span>kernel taint</span>), and it will not be able to use GPL-exclusive kernel interfaces. - • Common Values:
<span>"GPL"</span>,<span>"GPL v2"</span>,<span>"MIT"</span>, etc. (must be compatible with the kernel license).
<span>MODULE_AUTHOR("Your Name");</span>- • Function: Declares the author of the driver (name or email), facilitating tracing back to the code’s responsible person.
<span>MODULE_DESCRIPTION("Basic Character Device Driver Template");</span>- • Function: Briefly describes the function of the driver (e.g., “Driver for controlling GPIO lights”, “I2C sensor data acquisition driver”), making it easier for users to understand the module’s purpose.
<span>MODULE_VERSION("1.0");</span>- • Function: Declares the version number of the driver, facilitating version management (e.g., subsequent updates can be marked as
<span>1.1</span>,<span>2.0</span>, etc.).
3. Basic Format of Makefile
# External Compilation
# Use the makefile in the kernel source to compile
# Variable definitions and module declarations
# First specify the current directory
PWD ?= $(shell pwd)
# Kernel source path
KERNELDIR:=/home/ubuntu/work/linux/tspi_linux_sdk_20230916/Release/kernel
# Variables recognized by the kernel Makefile, used to declare the kernel module to be compiled (.ko file)
obj-m += demo.o
# Execute make default target module
module:
make -C $(KERNELDIR) M=$(PWD) ARCH=arm64 modules
@# -C $(KERNELDIR) switches to the kernel source directory from the current directory and uses the kernel source makefile to perform make
@# M=$(PWD) compiles only the driver files in the current directory
@# ARCH=arm64 specifies the compilation architecture
# Execute make clean call
clean:
make -C $(KERNELDIR) M=$(PWD) ARCH=arm64 clean
3.1 <span>$(shell pwd)</span> in <span>$</span>
- •
<span>$(...)</span>is the syntax for referencing functions in Makefile, and<span>$</span>is the prefix that triggers this syntax. - • Here,
<span>shell</span>is a built-in function in Makefile (used to execute Shell commands), and<span>$(shell pwd)</span><code><span> means: "Call the Shell command </span><code><span>pwd</span>(get the current directory path), and return the output of the command as the return value of this function”. - •
<span>$</span>is part of the Makefile syntax used to distinguish between “ordinary text” and “variables/functions that need to be parsed”. For example: - • Writing
<span>shell pwd</span>directly would be treated as a normal string and would not execute the command; - • Adding
<span>$</span>and parentheses $(shell pwd),<span>make</span>will recognize it as “a function to be executed”.
3.2 <span>$(KERNELDIR)</span> in <span>$</span>
- •
<span>$</span>is the syntax prefix for referencing variables in Makefile, and<span>$(variable_name)</span>means “get the value of that variable”. - • Here,
<span>KERNELDIR</span>and<span>PWD</span>are variables defined in your Makefile: - •
<span>$(KERNELDIR)</span>will be replaced with the path of the kernel source (for example,<span>/home/ubuntu/work/linux/.../kernel</span>). - •
<span>$(PWD)</span>will be replaced with the path of the current driver directory (for example,<span>/home/jimmy/tsp_device/1_1module_driver</span>).
3.3 Why use <span>modules</span>?
The kernel Makefile supports various compilation targets (such as <span>vmlinux</span> to compile the kernel image, <span>bzImage</span> to generate a compressed kernel, <span>install</span> to install the kernel, etc.), while <span>modules</span> is specifically used for compiling standalone kernel modules (<span>.ko</span> files).
3.4 Compilation Logic
The kernel Makefile will identify the modules to be compiled based on the <span>obj-m</span> variable (you defined <span>obj-m += demo.o</span>), and then execute the following steps:
- 1. Compile the source file (such as
<span>demo.c</span>) to generate the object file (<span>demo.o</span>). - 2. Process module dependencies and linking information to generate auxiliary files (such as
<span>demo.mod.c</span>,<span>demo.mod.o</span>). - 3. Finally, link these files into a loadable kernel module (
<span>demo.ko</span>).
4. Cross-compiling and Loading/Unloading Modules
On the Linux platform, create C and Makefile files, and execute the make command in that directory
jimmy@ubuntu:~/tsp_device/1_1module_driver$ ls
demo.c Makefile
# Execute make command
jimmy@ubuntu:~/tsp_device/1_1module_driver$ make
make -C /home/jimmy/android/kernel M=/home/jimmy/tsp_device/1_1module_driver ARCH=arm64 modules
make[1]: Entering directory "/home/jimmy/android/kernel"
CC [M] /home/jimmy/tsp_device/1_1module_driver/demo.o
Building modules, stage 2.
MODPOST 1 modules
CC /home/jimmy/tsp_device/1_1module_driver/demo.mod.o
LD [M] /home/jimmy/tsp_device/1_1module_driver/demo.ko
make[1]: Leaving directory "/home/jimmy/android/kernel"
# Make execution completed, generating intermediate files and standalone kernel module files (.ko files)
jimmy@ubuntu:~/tsp_device/1_1module_driver$ ls
demo.c demo.ko demo.mod.c demo.mod.o demo.o Makefile modules.order Module.symvers
4.1 Compiling Intermediate Files
The generation order and dependency relationship of these files during the compilation process are:
<span>demo.c</span> → (compilation) → <span>demo.o</span>
<span>demo.mod.c</span> (automatically generated) → (compilation) → <span>demo.mod.o</span>
<span>demo.o</span> + <span>demo.mod.o</span> → (linking) → <span>demo.ko</span>
| File | Type / Source | Core Function |
|---|---|---|
<span>demo.c</span> |
Source code (manually written) | Core logic code of the driver module, including initialization, exit, and other function implementations. |
<span>Makefile</span> |
Compilation script (manually written) | Defines compilation rules, guiding the <span>make</span> tool to call the kernel source and toolchain to complete module compilation. |
<span>demo.o</span> |
Intermediate object file (compiled) | <span>demo.c</span> compiled binary file, containing the module’s machine instructions (not linked). |
<span>demo.mod.c</span> |
Automatically generated dependency file | Automatically generated by the kernel, records module metadata (dependency symbols, licenses, etc.), used for matching kernel interfaces during linking. |
<span>demo.mod.o</span> |
Intermediate object file (compiled) | <span>demo.mod.c</span> compiled binary file, carrying module dependency information, participating in the final linking. |
<span>demo.ko</span> |
Final product (linked) | Loadable kernel module, linked from <span>demo.o</span> and <span>demo.mod.o</span>, loaded and run via <span>insmod</span>. |
<span>modules.order</span> |
Auxiliary file (compiled) | Records the module compilation order, facilitating multi-module management. |
<span>Module.symvers</span> |
Auxiliary file (compiled) | Records the kernel symbols exported by the module and their checksums, ensuring compatibility with the kernel / other modules. |
In the Windows environment,<span>Win+R</span> enters the terminal
4.2 Using adb push
<span>adb push</span> is a commonly used command of the Android Debug Bridge (ADB) to upload (push) files/directories from the computer to the specified path on Android devices (phones, emulators, development boards, etc.). The following is the detailed usage:
4.2.1 Basic Syntax
<span>adb push <local_file/directory_path> <device_target_path></span>
- •
<span><local_file/directory_path></span>: The absolute path / relative path of the file or directory to be pushed from the computer. - •
<span><device_target_path></span>: The path on the Android device to receive the file (must specify the full path, and the device must have write permissions).
4.2.2 Prerequisites
- 1. Enable Device Debugging Mode: The Android device must enable “Developer Options” and “USB Debugging” (Path: Settings → About Phone → Tap Version Number repeatedly to activate Developer Options, then enter Developer Options to enable USB Debugging).
- 2. Connect Device: Connect the computer and device with a USB cable, and execute
<span>adb devices</span>in the computer terminal to confirm that the device is connected normally (displaying the device serial number indicates a successful connection).
adb push C:\demo.ko /data/local/tmp/
4.3 Using adb shell
When entering the Linux command line environment of the Android device using <span>adb shell</span> in the Windows command line, <span>/</span> and other system directories are read-only by default, and under normal permissions, it may not be possible to transfer files to the system partition, load kernel modules, or view the contents of certain directories (such as <span>/data</span>); at this time, you can execute <span>adb root</span> in the Windows cmd to obtain root permissions, thus resolving these permission restrictions.
adb root
Or use the su command in adb shell to gain permissions
su
Enter the /data directory to view the local directory
rk3566_tspi:/ # cd data
rk3566_tspi:/data # ls
adb app-private drm misc preloads system user_de
anr app-staging gsi misc_ce property system_ce vendor
apex backup incremental misc_de resource-cache system_de vendor_ce
app bootchart local nfc rollback tombstones vendor_de
app-asec cache lost+found ota rollback-observer tsp_driver
app-ephemeral dalvik-cache media ota_package server_configurable_flags unencrypted
app-lib data mediadrm per_boot ss user
Enter the tsp_driver directory
rk3566_tspi:/data/tsp_driver # ls
printf.ko
4.4 Using insmod to Load Modules
To conveniently view the print effect, you can check the logs using the following methods
4.4.1 Use <span>dmesg</span> Command to View Kernel Logs
This is the most common way, <span>dmesg</span> will print the contents of the kernel log buffer (including the output of <span>printk</span>):
# After loading the module, execute dmesg to view the latest logs
dmesg | tail # View the last few lines (latest logs)
Example: If the module contains <span>printk("tsp_driver loaded\n");</span>, executing <span>dmesg | tail</span> after loading will show something like:
[12345.678901] tsp_driver loaded
4.4.2 Real-time Monitoring of Kernel Logs (Using <span>dmesg -w</span>)
If you want to view the output of <span>printk</span> in real-time (requires root permissions), you can execute the following before loading the module:
dmesg -w # Continuously monitor kernel logs, new content will be displayed immediately after loading the module
Then load the module in another <span>adb shell</span> window, and the current window will output the content of <span>printk</span> in real-time.
Execute insmod in the tsp_driver directory
insmod demo.ko
to see the corresponding printk content in the <span>dmesg -w</span> window
[ 1372.277187] Hello, world!
rk3566_tspi:/data/tsp_driver # insmod printf.ko
rk3566_tspi:/data/tsp_driver # lsmod
Module Size Used by
printf 16384 0
bcmdhd 1175552 0
4.5 Using rmmod to Unload Modules
rk3566_tspi:/data/tsp_driver # lsmod
Module Size Used by
printf 16384 0
bcmdhd 1175552 0
rk3566_tspi:/data/tsp_driver # rmmod printf.ko
rk3566_tspi:/data/tsp_driver # lsmod
Module Size Used by
bcmdhd 1175552 0
to see the corresponding printk content in the <span>dmesg -w</span> window