驱动开发的必要条件:
- 正常运行linux系统的开发板
- 内核源码树
- nfs挂载的rootfs,虚拟机安装nfs服务器
驱动开发步骤:
- 驱动源码和makefile编写,编译
- insmod安装模块,测试,rmmod卸载模块
常用的模块操作命令:
- lsmod,打印当前内核已经安装的模块
- insmod xxx.ko 安装xxx.ko模块
- modinfo xxx.ko 打印xxx.ko模块信息,注意模块版本和内核版本必须一致,否则安装报错:Invalid module format
- rmmod xxx 卸载xxx.ko模块
- 其他
最小模块源码及makefiel分析
makefile :
#ubuntu的内核源码树,如果要编译在虚拟机中安装的模块就用这个
KERN_VER = $(shell uname -r)
KERN_DIR = /lib/modules/$(KERN_VER)/build
# 开发板的linux内核的源码树目录,自定义
#KERN_DIR = /home/yt/drivers/kernel
#将module_test.c编译成一个模块
obj-m += module_test.o
#第一个命令
all:
make -C $(KERN_DIR) M=`pwd` modules
.PHONY: clean
clean:
make -C $(KERN_DIR) M=`pwd` modules clean
- KERN_DIR,变量的值就是我们用来编译这个模块的内核源码树的目录
- obj-m += module_test.o,这一行就表示我们要将module_test.c文件编译成一个模块
- make -C $(KERN_DIR) M=
pwd
modules 这个命令用来实际编译模块,工作原理就是:利用make -C进入到我们指定的内核源码树目录下,然后在源码目录树下借用内核源码中定义的模块编译规则去编译这个模块,编译完成后把生成的文件还拷贝到当前目录下,完成编译
总结:模块的编译要借助内核源码的体系来完成
module.c
#include <linux/module.h> // module_init module_exith宏在这里定义
#include <linux/init.h> // __init __exit宏在这里定义
// 模块安装函数
static int __init chrdev_init(void)
{
printk(KERN_INFO "chrdev_init helloworld init\n");
#设置打印级别为7
//printk("<7>" "chrdev_init \n");
return 0;
}
// 模块下载函数
static void __exit chrdev_exit(void)
{
printk(KERN_INFO "chrdev_exit \n");
}
module_init(chrdev_init);
module_exit(chrdev_exit);
// MODULE_xxx这种宏作用是用来添加模块描述信息
MODULE_LICENSE("GPL"); // 描述模块的许可证
MODULE_AUTHOR("klyer"); // 描述模块的作者
MODULE_DESCRIPTION("module test"); // 描述模块的介绍信息
MODULE_ALIAS("xxx"); // 描述模块的别名信息
- 头文件,头文件路径是内核源码树的 /include/…,比如#include <linux/module.h>是在/include/linux/module.h
- module_init宏和insmod命令绑定,即insmod----调用module_init宏----调用chrdev_init函数
- module_exit宏和rmmod命令绑定,即rmmod----调用module_exit宏----调用chrdev_exit函数
- __init,本质上是个宏定义,
#define __init __section(.init.text) __cold notrace
这个__init的作用就是将被他修饰的函数放入.init.text段中去(本来默认情况下函数是被放入.text段中)。
__exit和它差不多:
#define __exit __section(.exit.text) __exitused __cold
- 模块中常用宏,代码中已经注释,一般模块许可证不能少
- printk函数,和printf功能类似,区别是printf是应用层使用的C库函数,不能在内核中使用。printk函数是内核中使用的,并且多了打印级别设置的功能