一、概述
Linux操作系统通常是基于Linux内核,并结合GNU项目中的工具和应用程序而成。Linux操作系统支持多用户、多任务和多线程,具有强大的网络功能和良好的兼容性。本文主要讲述如何编译及加载linux驱动模块。
二、概念及原理
应用程序通过系统调用与内核进行交互,而驱动程序则提供了硬件设备的访问接口,内核本身则提供了系统调用、驱动框架等基础设施。
驱动开发:Linux 驱动开发是指为 Linux 内核开发各种设备驱动程序,用于控制和管理硬件设备。驱动程序运行在内核空间,直接与硬件进行交互。Linux 内核提供了丰富的接口和框架,开发者可以编写各种类型的设备驱动,包括网络设备、存储设备、输入设备等。驱动程序通过内核提供的接口与用户空间的应用程序进行通信。
应用开发:Linux 应用开发是指在 Linux 系统上开发各种类型的应用程序,包括命令行工具、图形界面应用、服务器端应用等。Linux 提供了丰富的开发环境和工具链,开发者可以使用各种编程语言和开发工具进行应用开发。应用程序运行在用户空间,通过系统调用与操作系统内核进行交互,执行各种任务和功能。
内核开发:Linux 内核开发是指对 Linux 内核本身进行开发和维护。Linux 内核是操作系统的核心,负责管理系统资源、调度任务、提供系统调用等功能。内核开发包括对内核功能的添加和修改,修复内核漏洞,优化性能等工作。内核开发人员通常会编写和维护内核的各种子系统和模块,包括调度器、文件系统、网络协议栈等。
模块加载与卸载:在 Linux 中,模块是指可以动态加载到内核中并扩展其功能的代码段。模块的加载意味着将模块的代码和数据加载到内核空间并使其在内核中运行,从而扩展内核的功能。模块的卸载则是将其从内核中移除,释放其占用的资源。模块的加载与卸载允许内核在运行时动态地添加或移除功能,这为系统提供了灵活性和可扩展性。
三、编译前准备
在 Ubuntu 系统中进行 Linux 内核驱动模块的编译,需要做一些准备工作:
-
安装必要的软件包:
确保你的系统已经安装了必要的软件包,包括编译工具链、内核源码和头文件等。你可以使用以下命令来安装这些软件包:sudo apt update sudo apt install build-essential linux-headers-$(uname -r)
-
确认内核源码路径:
确保你知道系统中内核源码的路径,通常位于/lib/modules/$(uname -r)/build
。这个路径在编译驱动模块时可能会用到。
四、代码实现及编译
- 驱动模块代码
新建一个名为helloworld.c
的文件,添加如下代码。
#include <linux/init.h>
#include <linux/module.h>
/* 定义模块的初始化函数 */
static int Helloworld_Init(void)
{
printk("I'm Chewie, Helloworld init ok!\n");
return 0;
}
/* 定义模块的退出函数 */
static void Helloworld_Exit(void)
{
printk("Bye bye!\n");
}
/* 注册模块的初始化和退出函数,这个是给内核识别的 */
module_init(Helloworld_Init);
module_exit(Helloworld_Exit);
/* 声明该模块符合GPL协议 */
MODULE_LICENSE("GPL");
- Makefile编写
新建一个名为Makefile
的文件(不需要后缀,必须是Makefile首字母大写,makefile识别不了,一开始不知道踩了个坑),这个 Makefile 文件主要是用于告诉 make 命令如何编译驱动程序模块,并提供了编译和清理的规则。在文件中添加如下内容。
obj-m := helloworld.o
KDIR := /lib/modules/$(shell uname -r)/build
all:
make -C $(KDIR) M=$(PWD) modules
clean:
make -C $(KDIR) M=$(PWD) clean
其中各部分的具体含义如下:
-
obj-m := helloworld.o
:obj-m
是一个特殊的变量,用于指定要编译成模块的目标文件。helloworld.o
是模块的目标文件名,这里是你要编译生成的驱动模块文件名。
-
KDIR := /lib/modules/$(shell uname -r)/build
:KDIR
是一个变量,用于指定内核源码的路径。/lib/modules/$(shell uname -r)/build
是一个动态获取当前系统内核版本并拼接出内核源码路径的命令。
-
all:
:all
是一个 Makefile 中的默认目标,表示默认情况下执行的规则。
-
make -C $(KDIR) M=$(PWD) modules
:make -C $(KDIR)
是在内核源码路径下执行 make 命令,M=$(PWD)
表示当前目录是模块源码的路径。modules
是指定了要编译生成模块对象文件的规则。
-
clean:
:clean
是一个用于清理的目标,用于清理编译产生的文件。
-
make -C $(KDIR) M=$(PWD) clean
:make -C $(KDIR)
是在内核源码路径下执行 make 命令,M=$(PWD)
表示当前目录是模块源码的路径。clean
是指定了要执行清理操作的规则。
然后在当前helloworld.c目录下,打开终端,输入以下命令进行编译。
make -C /lib/modules/$(uname -r)/build M=$(pwd) modules
五、模块加载及卸载
编译完模块后,需要把模块加载到内核才可正常使用。这里可以使用insmod
指令加载驱动模块。注意需要加上sudo
使用root权限。
sudo insmod helloworld.ko
那如何知道模块是否加载成功呢?这里需要使用lsmod | grep helloworld
检查模块加载情况。如果有如下显示则说明加载成功。
卸载模块时,则需要使用rmmod
进行卸载。同样需要加上sudo
使用root权限。
sudo rmmod helloworld
到这里可能有人要问了,模块初始化和退出函数里不是加了printk打印了一些信息吗,为什么终端里没有显示任何内容?是因为printk打印的信息是在内核空间中,而终端是属于用户空间的,所以不会显示内核的信息。如果想要显示这个打印内容,可以使用dmesg
打印内核日志(同样的要加上sudo
使用root权限),这样就能看到上面模块加载和卸载的信息及打印的相关信息。
如果想要实时查看内核日志,可以通过以下命令实时打印内核日志。这里建议单独开一个终端实时查看。
sudo tail -f /var/log/kern.log
六、相关链接
【学习记录】从0开始的Linux学习之旅——编译linux内核