第二章 Hello World驱动
2.1 概述
在本章中,我们将编写一个简单的Linux驱动程序,通常称为"Hello World"驱动程序。这个驱动程序将帮助我们理解Linux驱动程序的基本结构和工作流程。
2.2 环境准备
在开始编写驱动程序之前,我们需要准备开发环境。下面2种环境其中任意环境都能达到目的。
2.2.1 在X86电脑上面的交叉编译环境
1). 确保获取Neardi SDK, 并安装SDK所需要的环境.
2). 在SDK/rk3588-linux/kernel/drivers/char目录里创建hello.c, 如2.3节。
3). 更改SDK/rk3588-linux/kernel/drivers/char/Makefile, 如下:
diff --git a/kernel/drivers/char/Makefile b/kernel/drivers/char/Makefile
index ffce287ef..a3d9f062f 100644
--- a/kernel/drivers/char/Makefile
+++ b/kernel/drivers/char/Makefile
@@ -47,3 +47,4 @@ obj-$(CONFIG_PS3_FLASH) += ps3flash.o
obj-$(CONFIG_XILLYBUS) += xillybus/
obj-$(CONFIG_POWERNV_OP_PANEL) += powernv-op-panel.o
obj-$(CONFIG_ADI) += adi.o
+obj-m += hello.o
4). 编译kernel, 则会生成hello.ko。
neardi@ubuntu2004:/work/rk3588-linux$ ./build.sh kernel
processing option: kernel
============Start building kernel============
TARGET_ARCH =arm64
TARGET_KERNEL_CONFIG =rockchip_linux_defconfig
TARGET_KERNEL_DTS =rk3588-neardi-linux-lkd3588-f0
TARGET_KERNEL_CONFIG_FRAGMENT =
==========================================
#
# No change to .config
#
CALL scripts/atomic/check-atomics.sh
CALL scripts/checksyscalls.sh
CHK include/generated/compile.h
CC [M] drivers/char/hello.o
MODPOST modules-only.symvers
GEN Module.symvers
CC [M] drivers/char/hello.mod.o
LD [M] drivers/char/hello.ko
Image: resource.img (with rk3588-neardi-linux-lkd3588-f0.dtb logo.bmp logo_kernel.bmp) is ready
Image: boot.img (with Image resource.img) is ready
Image: zboot.img (with Image.lz4 resource.img) is ready
加载及卸载hello.ko请看2.6及2.7。
2.2.2 直接在Neardi设备上编译
如下步骤都是在Neardi 设备上运行。
1). 安装编译工具
sudo apt update
sudo apt install build-essential cmake
2). 下载Linux header文件
Linux header files
3). Install Linux header文件
neardi@LPA3588:~/drivers$ tar -zxvf neardi.linux.kernel.v5.10.110.tar.gz
header/
header/linux-libc-dev_5.10.110-8_arm64.deb
header/linux-image-5.10.110-dbg_5.10.110-8_arm64.deb
header/linux-image-5.10.110_5.10.110-8_arm64.deb
header/linux-headers-5.10.110_5.10.110-8_arm64.deb
neardi@LPA3588:~/drivers/header$ sudo dpkg -i *.deb
Selecting previously unselected package linux-headers-5.10.110.
(Reading database ... 106198 files and directories currently installed.)
Preparing to unpack linux-headers-5.10.110_5.10.110-8_arm64.deb ...
Unpacking linux-headers-5.10.110 (5.10.110-8) ...
Selecting previously unselected package linux-image-5.10.110.
Preparing to unpack linux-image-5.10.110_5.10.110-8_arm64.deb ...
Unpacking linux-image-5.10.110 (5.10.110-8) ...
Selecting previously unselected package linux-image-5.10.110-dbg.
Preparing to unpack linux-image-5.10.110-dbg_5.10.110-8_arm64.deb ...
Unpacking linux-image-5.10.110-dbg (5.10.110-8) ...
Preparing to unpack linux-libc-dev_5.10.110-8_arm64.deb ...
Unpacking linux-libc-dev:arm64 (5.10.110-8) over (5.4.0-147.164) ...
Setting up linux-headers-5.10.110 (5.10.110-8) ...
Setting up linux-image-5.10.110 (5.10.110-8) ...
Setting up linux-image-5.10.110-dbg (5.10.110-8) ...
Setting up linux-libc-dev:arm64 (5.10.110-8) ...
2.3 编写HelloWorld驱动代码
这是一个仅仅包含初始化功能的驱动, 代码如下:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Linx zhang");
MODULE_DESCRIPTION("A simple Hello World LKM");
static int __init hello_init(void) {
printk(KERN_INFO "Hello, World!\n");
return 0;
}
static void __exit hello_exit(void) {
printk(KERN_INFO "Goodbye!\n");
}
module_init(hello_init);
module_exit(hello_exit);
代码解释
• #include <linux/module.h>:包含模块相关的头文件。
• #include <linux/kernel.h>:包含内核相关的头文件。
• #include <linux/init.h>:包含初始化和清理函数的头文件。
• MODULE_LICENSE("GPL"):指定模块的许可证。
• MODULE_AUTHOR("Linx zhang"):指定模块的作者。
• MODULE_DESCRIPTION("A simple Hello World LKM"):描述模块的功能。当使用modinfo命令查看模块信息时,这个描述会被显示出来。 编译成功后, 可以使用如下命令查看:
neardi@LPA3588:~/drivers/helloworld$ modinfo hello.ko
filename: /home/neardi/drivers/helloworld/hello.ko
description: A simple Hello World LKM
author: Linx zhang
license: GPL
depends:
name: hello
vermagic: 5.10.110 SMP mod_unload aarch64
• static int __init hello_init(void):模块加载时调用的初始化函数。
• static void __exit hello_exit(void):模块卸载时调用的清理函数。
• module_init(hello_init):module_init宏用于指定模块的初始化函数。当模块被加载时,内核会调用这个初始化函数来执行模块的初始化操作
• module_exit(hello_exit):当模块被卸载时,内核会调用这个清理函数来执行模块的清理操作。
2.4 编写Makefile
接下来,我们需要编写一个Makefile来编译我们的驱动程序。创建一个名为Makefile的文件,并添加以下内容:
obj-m += hello.o
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
Makefile解释
• obj-m += hello.o:指定要编译的目标文件。
• all:编译模块。
• clean:清理编译生成的文件。
2.5 编译和加载驱动程序
在终端中运行以下命令来编译和加载驱动程序:
neardi@LPA3588:~/drivers/helloworld$ make
make -C /lib/modules/5.10.110/build M=/home/neardi/drivers/helloworld modules
make[1]: Entering directory '/usr/src/linux-headers-5.10.110'
CC [M] /home/neardi/drivers/helloworld/hello.o
MODPOST /home/neardi/drivers/helloworld/Module.symvers
CC [M] /home/neardi/drivers/helloworld/hello.mod.o
LD [M] /home/neardi/drivers/helloworld/hello.ko
make[1]: Leaving directory '/usr/src/linux-headers-5.10.110'
• 加载hello.ko驱动, 如下:
neardi@LPA3588:~/drivers/helloworld$ sudo insmod hello.ko
neardi@LPA3588:~/drivers/helloworld$ dmesg
[ 7724.895686] Hello, World!
加载驱动时, hello_init有被执行到。
2.6 卸载驱动程序
neardi@LPA3588:~/drivers/helloworld$ sudo rmmod hello
neardi@LPA3588:~/drivers/helloworld$ dmesg
[ 7724.895686] Hello, World!
[ 7737.404195] Goodbye!
可以看见, 卸载驱动时hello_exit有被执行。
2.7 小结
在本章中,我们编写了一个简单的Hello World驱动程序,并学习了如何编译、加载和卸载它。这是理解Linux驱动程序开发的第一步。在接下来的章节中,我们将深入探讨更复杂的驱动程序开发技术。