Linux内核模块驱动加载与调试

1、理论基础

1.1、KO文件:是kernel object文件,也就是linux kernel下的模块加载文件

1.2、ko文件的作用:一般情况下,内核中会有许多已经加载进去的模块,而有些驱动模块并不需要一直加载到内核中,为了减少内核的压力,就将这些模块写好,当需要的时候再加载带内核中去

1.3、模块机制:(Linux提供的模块机制)

1.3.1、模块自身不被编译到内核映像中,从而不影响内核映像的大小

1.3.2、一旦模块被加载,模块和内核中的其他部分的功能完全一样

1.4、编译ko的方法

1.4.1、整编内核:以整个内核去编译该文件

1.4.2、单编ko:内核源码为基础编译一个外部模块(推荐速度快

理论参考:如何编译linux驱动ko - 知乎 (zhihu.com)

2、内核模块模型说明

1、驱动和一般应用程序的执行方式很大不同

  • 一般应用由main函数开始执行,流程基本由程序自身控制
  • 驱动程序没有main函数,由回调方式驱动运行

2、回调方式

先向内核注册函数,然后应用程序触发这些函数的执行(例如:驱动程序在初始化时,向内核注册处理某个设备写操作的函数。当应用程序使用write系统调用写该设备时,内核就会调用注册的上述函数)

3、内核模型常见的回调函数举例

  1. DriverInitialize

     驱动初始化函数,通过宏静态注册;

     insmod PrintModule.ko,安装驱动并触发该函数,通常会创建设备对象;

  2. DriverUninitialize

     驱动销毁函数,通过宏静态注册;

     $ rmmod PrintModule,卸载驱动并触发该函数;

  3. DriverOpen

     打开设备函数,动态注册;

     应用调用open函数打开设备对象时,会触发该函数;

  4. DriverRead

     读设备函数,动态注册;

     应用调用read函数读设备时,会触发该函数;

  5. DriverWrite

     写设备函数,动态注册;

     应用调用write函数写设备时,会触发该函数;

  6. DriverIOControl

     设备控制函数,动态注册;

     应用调用ioctl函数操作设备时,会触发该函数;

  7. DriverMMap

  设备内存映射函数,动态注册;

  应用调用mmap函数时,会触发该函数;

4、单个源文件

demo_hello.c驱动源程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <linux/kernel.h> /*Needed by all modules*/
#include <linux/module.h> /*Needed for KERN_* */
#include <linux/init.h> /* Needed for the macros */

MODULE_LICENSE("GPL");

static int year=2024;

static int hello_init(void)
{
printk(KERN_WARNING "Hello kernel, it's %d!\n",year); //加载成功显示的内容
return 0;
}


static void hello_exit(void)
{
printk("Bye, kernel!\n"); //删除成功显示的内容
}

/* main module function*/
module_init(hello_init);
module_exit(hello_exit);
4.1、Makefile编写:

1、文件名一定要是Makefile

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
.PHONY:    all  clean

#指定内核所在目录
KERNELDIR := /lib/modules/$(shell uname -r)/build
#指定源码所在位置
PWD := $(shell pwd)
#指定编译工具
CROSS_ARCH := /usr/local/arm/gcc-linaro-5.3.1-2016.05-x86_64_arm-linux-gnueabihf/bin/arm-linux-gnueabihf-gcc
# 指定要编译的模块对象文件
obj-m += hello.o
# 目标 'all': 编译模块
#“M=”参数的作用是以内核源码为基础编译一个外部模块。命令中“M=DIR”,程序会自动跳转到所指定的PWD目录中查找模块源码,编译生成ko文件。
all:
$(MAKE) $(CROSS_ARCH) -C $(KERNELDIR) M=$(PWD) modules

#C make进入linux源码所在文件夹,从而获得内核的顶层Makefile,从而利用kbuild进行外部模块的编译
#M make在执行目标前返回到模块源代码所在文件夹
#modules目标将会编译所有包含在obj-m变量下的module.o文件

# 目标 'clean': 清理构建生成的文件
clean:
$(MAKE) -C $(KERNELDIR) M=$(PWD) clean

2、在当前源文件所在目录下执行以下编译命令

1
2
3
4
5
6
7
#生成构建文件
make
#在内核中加载hello模块文件(执行)
sudo insmod ./hello.ko

#删除ko驱动模块文件
sudo rmmod ./hello.ko

3、验证方式一:输入以下命令查看程序执行结果(进入kern.log中查看程序运行的结果)

1
2
3
4
#进入目标日志文件目录
cd /var/log
#直接在命令行打印kern.log文件中的内容
tail /var/log/kern.log
示例图片

4、验证方式二:同时输入,查看该驱动的加载情况

1
lsmod
示例图片

实例参考链接:,头文件不存在等问题的解决方法 - NeroHwang - 博客园 (cnblogs.com)

5、多个源文件

test.c驱动源程序:

1
2
3
4
5
6
7
8
9
10
// test.c
// --------------------------------

#include "test.h"

void test(void)
{
printk("hello world!\n");
return;
}

test.h程序:

1
2
3
4
5
6
7
8
9
10
// test.h
// --------------------------------

#include <linux/init.h>
#include <linux/module.h>

MODULE_LICENSE("GPL");
MODULE_AUTHOR("z_mss");

void test(void);

main.c驱动源程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// main.c
// --------------------------------

#include "test.h"

static int hello_init(void)
{
printk("hello init!\n"); //加载成功显示的内容
test();
return 0;
}

static void hello_exit(void)
{
printk("hello exit!\n"); //删除成功显示的内容
return;
}

module_init(hello_init);
module_exit(hello_exit);
5.1、Makefile编写:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
.PHONY:    all  clean

MODULE_NAME := hello

#指定内核所在目录
KERNELDIR := /lib/modules/$(shell uname -r)/build
#指定源码所在位置
PWD := $(shell pwd)
#指定编译工具
CROSS_ARCH := /usr/local/arm/gcc-linaro-5.3.1-2016.05-x86_64_arm-linux-gnueabihf/bin/arm-linux-gnueabihf-gcc

# 指定要编译的模块对象文件
obj-m += $(MODULE_NAME).o
$(MODULE_NAME)-objs := main.o test.o

# 目标 'all': 编译模块
#“M=”参数的作用是以内核源码为基础编译一个外部模块。命令中“M=DIR”,程序会自动跳转到所指定的PWD目录中查找模块源码,编译生成ko文件。
all:
$(MAKE) $(CROSS_ARCH) -C $(KERNELDIR) M=$(PWD) modules

# 目标 'clean': 清理构建生成的文件
clean:
$(MAKE) -C $(KERNELDIR) M=$(PWD) clean

验证方式一:

示例图片

6、内核模块驱动调试方式

通过以下指令,将程序中printk打印的信息输出到log.txt文件中

1
dmesg >log.txt