In the journey of learning programming, whether it's your first encounter with C language or exploring other programming languages, we often start with the classic "Hello, World!" as our starting point to open the door to the code world. This simple yet ceremonial tradition not only symbolizes the first dialogue between a programmer and a computer but also carries the love and pursuit of countless developers for technology. Similarly, in the field of driver programming, we can continue this tradition with "Hello, World!" as our first driver program, embarking on a wonderful journey into low-level system development.
Next, we will start writing our first driver program - a simple driver module called "Hello, World!". This is not only our first step in driver development but also an important starting point for understanding Linux kernel mechanisms. Through this code, we will get a preliminary glimpse of the basic structure of drivers, loading and unloading processes, and how to interact with the kernel. Are you ready? Let's begin this challenging and enjoyable journey of driver development together!
1. Basic Kernel Framework
Linux kernel module (driver) development consists of three essential parts:
Loading Function (Module Entry Point)
- This is the initialization entry function for the module/driver.
- Its purpose is to complete resource allocation, hardware initialization, or data structure setup when the driver is loaded (via insmod).
- Usually defined with
static int __init xxx_init(void)and registered with themodule_init()macro. - Can return a non-zero value to abort module loading on failure.
Unloading Function (Module Exit Point)
- Automatically called when the driver is unloaded (via rmmod).
- Its purpose is to release resources, unregister devices, and clean up data structures to ensure no memory leaks or system instability.
- Defined with
static void __exit xxx_exit(void)and registered with themodule_exit()macro.
License Declaration
- Use
MODULE_LICENSE("GPL v2")or similar macro to declare the license. - Its purpose is to indicate which open source agreement your module follows and inform the kernel that your code meets open source requirements.
- If not declared, the kernel will perform security handling and mark the module as "tainted", also affecting some function calls.
- Use
Any simplest and most basic Linux kernel driver module must have:
- Clear loading and unloading processes (open-close principle, ensuring resource safety)
- Clear legal license declaration (ensuring compliance and kernel compatibility)
Other additions (can be added based on actual requirements):
- Module parameters: Allow parameters to be passed during module loading for improved flexibility
- Exported symbols: Enable function sharing between different modules/drivers
- Author and description information: Facilitates maintenance and module identification
2. Driver Folder Preparation
Create an 01_helloworld/ folder in your working directory to store our driver source code and Makefile:
mkdir 01_helloworld3. Writing Driver Source Code
3.1 Source Code
Create a hello_world.c file in the 01_helloworld/ folder and write the following code:
#include <linux/module.h> /* Module-related macros and functions */
#include <linux/kernel.h> /* printk logging function */
/* Loading function (driver entry), automatically executed when driver is loaded via insmod */
static int __init helloworld_init(void)
{
printk("helloworld_init\r\n"); // Kernel log print
return 0; // Return 0 indicates successful loading
}
/* Unloading function (driver exit), automatically executed when driver is unloaded via rmmod */
static void __exit helloworld_exit(void)
{
printk("helloworld_exit\r\n");
}
/* These two lines tell the kernel where the entry and exit functions are */
module_init(helloworld_init);
module_exit(helloworld_exit);
/* These 3 are module information declarations */
MODULE_LICENSE("GPL v2"); /* Module license */
MODULE_VERSION("1.0"); /* Module version, optional */
MODULE_DESCRIPTION("helloworld Driver");/* Module description, optional, usually displayed when using lsmod */2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
3.2 Function Explanation
- Header Files:
#include <linux/module.h> /* Module-related macros and functions */
#include <linux/kernel.h> /* printk logging function */2
linux/module.h: Provides macros and APIs for module initialization, exit, and information declarations. Must be included when writing kernel modules.linux/kernel.h: Contains commonly used kernel functions, such as theprintklogging output function.
- Loading Function (Driver Entry):
static int __init helloworld_init(void)
{
printk("helloworld_init\r\n"); // Kernel log print
return 0; // Return 0 indicates successful loading
}2
3
4
5
- static int: Declared as static, only visible within this file, returns an integer.
- __init: GCC attribute telling the kernel this is initialization code. After module loading, its memory can be reclaimed to optimize memory usage.
- helloworld_init: Module entry function.
- printk: Used in the kernel to print information to the system log (equivalent to printf in user space applications).
- return 0: Return 0 on success, otherwise the kernel refuses to load the module.
- Unloading Function (Driver Exit):
static void __exit helloworld_exit(void)
{
printk("helloworld_exit\r\n");
}2
3
4
- static void: Static, no return value.
- __exit: Tells the kernel this is unloading-related code. Automatically called when unloading the module.
- helloworld_exit: Module exit function (release resources, cleanup).
- printk: Prints unloading information to kernel log.
- Telling the Kernel the Locations of These Two Key Functions:
module_init(helloworld_init);
module_exit(helloworld_exit);2
- module_init: Registers the entry function. Automatically executes
helloworld_initwhen loading the module. - module_exit: Registers the exit function. Automatically executes
helloworld_exitwhen unloading the module.
- Module Information Declarations:
MODULE_LICENSE("GPL v2"); /* Module license */
MODULE_VERSION("1.0"); /* Module version, optional */
MODULE_DESCRIPTION("helloworld Driver");/* Module description, optional, usually displayed when using lsmod */2
3
- MODULE_LICENSE: Informs the kernel which agreement the module follows (must be filled in, otherwise the kernel will warn "tainted").
- MODULE_VERSION: Specifies a version number for your driver for easier maintenance and upgrades (optional).
- MODULE_DESCRIPTION: Describes the module's purpose for easier identification (optional, shown when using
lsmod).
3.3 Detailed printk Explanation
printk is the "print log" function in the Linux kernel, equivalent to printf in regular C programs, but it's specifically used in kernel space to output debugging information, status information, or error messages to developers.
In Linux driver development and kernel module development, printk is the main debugging tool.
Why use printk instead of printf?
printfcan only be used in user space (like in applications you write), not directly in the kernel, because the kernel and user space have different input/output mechanisms.- The kernel has no standard output window or terminal, so information can only be viewed through the log system (
dmesg). printkrecords your information in the kernel log buffer, which can then be read using thedmesgcommand or viewing/var/log/messages(different paths on different systems).
printk also supports log levels to specify the importance of messages:
| Level | Meaning |
|---|---|
KERN_EMERG | System may be paralyzed |
KERN_ALERT | Alert requiring immediate action |
KERN_CRIT | Critical error |
KERN_ERR | Common error |
KERN_WARNING | Warning (not a fatal error) |
KERN_NOTICE | Information requiring attention |
KERN_INFO | General information (success/process) |
KERN_DEBUG | Debugging information (code tracing) |
If no log level is specified, the default is
KERN_WARNINGlevel.
Example usage:
printk(KERN_INFO "Driver loaded successfully.\n"); // Process notification
printk(KERN_ERR "Can not open device!\n"); // Error notification
printk(KERN_DEBUG "x value is %d\n", x); // Normal debugging use2
3
The kernel saves logs with levels, and you can filter to only see high "priority" logs (e.g., only errors/warnings, not debug info) using
dmesgor log management tools, allowing you to focus on currently relevant issues.
4. Writing the Makefile
Create a Makefile file in the 01_helloworld/ folder and write the following code:
export ARCH=arm64
# Cross compiler absolute path prefix
export CROSS_COMPILE=/home/lckfb/TaishanPi-3-Linux/prebuilts/gcc/linux-x86/aarch64/gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu/bin/aarch64-none-linux-gnu-
# Consistent with source file name
obj-m += hello_world.o
# Kernel source directory
KDIR := /home/lckfb/TaishanPi-3-Linux/kernel-6.1
PWD ?= $(shell pwd)
all:
make -C $(KDIR) M=$(PWD) modules
clean:
make -C $(KDIR) M=$(PWD) clean2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
4.1 CROSS_COMPILE Explanation
This parameter specifies the cross-compiler included in our SDK. When compiling hello_world.c source code, this compiler is called for compilation. The CROSS_COMPILE value is the compiler prefix, ending with a hyphen (-).
Generally, with consistent SDK versions, the compiler path is:
TaishanPi-3-Linux/prebuilts/gcc/linux-x86/aarch64/gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu/bin/
4.2 .o Explanation
obj-m += hello_world.oPurpose: Specifies the object filename to be compiled
obj-mindicates the current module's object file (.o) needs to be compiled, followed by your source code filename (without extension plus.o).- For example, if you have
hello_world.c, writehello_world.ohere.
INFO
The actual compilation process first converts hello_world.c to hello_world.o, then the kernel framework generates the final module file hello_world.ko (Kernel Object).
4.3 KDIR Explanation
KDIR := /home/lckfb/TaishanPi-3-Linux/kernel-6.1Purpose: Tells the Makefile which kernel version's source code and header files to use for compilation and linking.
- Many device driver modules need to match the target kernel environment, otherwise they cannot load and run normally.
KDIR := /home/.../kernel-6.1specifies the main directory of the current Linux kernel source code.
5. Compilation
INFO
Special Note: Before compiling kernel driver modules, make sure the kernel has already been compiled, because some dependency files needed for compiling kernel modules are generated after kernel compilation.
How to compile the kernel: Reference Debian12 Kernel Compilation
Enter the 01_helloworld/ directory and compile using the following command:
makeThe final result is the generated .ko file. Simply copy this file to the development board for module loading:
6. Driver Testing
Copy hello_world.ko to the development board:
Run the following command to load the driver module:
sudo insmod hello_world.koSome users may notice that nothing happens after loading. This is because logs are automatically classified and hidden by the system. Run the following command to view logs related to hello_world:
dmesg | grep -E 'hello'At this point, our first hello world driver is complete.