学习目的
搭建驱动开发的环境,能够编译自己的模块
预备知识
如何编译自己的模块
1. 下载对应版本linux源码树,用于构建模块
2. 编写自己模块的makefile
需要了解的是linux的kbuild系统,这里简单介绍一下模块的makefile的通用写法
/* Makefile:单个源文件编译的模块 */
obj-m := hello.o
/* Makefile:多个源文件编译的模块 */
obj-m := module.o
module-objs := file1.o file2.o
/* shell 环境:编译指令 */
make -C /usr/src/linux-source-4.15.0 M=`pwd` modules //-C表示在make被执行的时候先进入/usr/src/linux-source-4.15.0目录执行顶层的Makefile,M表示在编译模块之前返回到当前目录
上面的方法比较麻烦,一般在自己编写驱动模块的时候会使用下面这种通用的Makefile,直接执行make就可以编译自己的模块了
/* 利用KERNELRELEASE变量 */
ifneq ($(KERNELRELEASE),)
obj-m := hello.o
else
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
default:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
endif
必须的要素
头文件
#include <linux/module.h>
#include <linux/init.h>
这两个头文件包含了模块编写时所需的一些必要的符号定义
许可
MODULE_LICENSE("GPL");
各个许可的含义在网上可以找到
错误处理
在内核空间中,注册的操作可能会失败,所以需要一直检查返回值,在发生错误之后,必须要清理错误,释放资源。
使用的错误码在<linux/errno.h>
中定义,注意引用该文件
下面有两种方法:
- 初始化的每一步发生错误使用goto语句跳转到对应的错误处理语句,原则是先分配的后释放,后分配的线释放。
这种方式写的代码,结构对称,有分配就有相应的错误处理,但是代码里面重复的部分就很多。
例子如下:
int __init my_init_function()
{
int err;
err = register_this(ptr1, "scull");
if (err)
goto fail_this;
err = register_that(ptr2, "scull");
if (err)
goto fail_that;
err = register_those(ptr3, "scull");
if (err)
goto fail_those;
fail_those:
unregister_that(ptr3, "scull");
fail_that:
unregister_this(ptr1, "scull");
fail_this:
return err;
}
- 为了解决分配操作多的时候带来的goto语句过多的问题,我们可以把处理语句封装在一个处理函数中.
例子如下:
struct something *item1;
struct somethingelse *item2;
int stuff_ok;
void my_cleanup()
{
if (item1)
release_thing1(item1);
if (item2)
release_thing2(item2);
if (stuff_ok)
unregister_stuff();
return;
}
int __init my_init(void) {
int err = -ENOMEM;
item1 = allocate_thing(arguments);
item2 = allocate_thing2(arguments2);
if (!item2 || !item2)
goto fail;
err = register_stuff(item1, item2);
if (!err)
stuff_ok = 1;
else
goto fail;
return 0; /* success */
fail:
my_cleanup();
return err;
}