linux驱动学习记录
一、背景
在开始学习我的linux驱动之旅之前,先提一下题外话,我是一个c语言应用层开发工作人员,在工作当中往往会和硬件直接进行数据的交互,往往遇到数据不通的情况,常常难以定位,而恰巧驱动是处于应用层和硬件之间的一个重要的数据传输媒介,也是在这个背景下,驱动了我对驱动的好奇心,当抱着了解的心态来的时候,却不知不觉地对驱动的执行方式产生了兴趣,由此也开始了我学习的征途…
二、驱动开发
驱动的作用是用来操作硬件工作,使其应用开发人员能间接实现对功能的开发。驱动函数都是注册在内核当中,应用层代码也都是通过直接调用系统的函数来实现对驱动的数据传递和接收,实现流程如下图所示:
1、驱动环境搭建(内核编译的方式)
- 从官网获取,将获取的压缩包解压后放入/usr/src中
- 使用apt指令获取
sudo apt-cache search linux-source
sudo apt-get install linux-source-<version>
sudo tar jxvf linux-source-<version>.tar.bz2
- 编译源码
cd /usr/src/linux-source-<version>
#以下三选一
sudo make config #逐一选择kernel的配置形式
#或
sudo make oldconfig #按原有kernel配置配置kernel
#或
sudo make menuconfig #使用可视化环境配置kernel
#编译内核的过程
sudo make #编译内核,时间较长,可能会长达1小时及以上,如5.4.0用时约3-5个小时
#此时已完成了镜像编译和所有modules编译,如果不确定可以使用以下指令再编译
sudo make modules #编译内核驱动modules
sudo make bzImage #编译内核镜像
#加载modules的过程
sudo make modules_install #安装内核模块
#检查安装
cd /lib/modules
#如果安装正常这里面会出现以版本号为文件名的文件夹,就证明安装成功了,如5.4.xx或5.4.xx-xx-generic
2、驱动加载和卸载的指令
- lsmod :list moduel 把我们机器上所有的驱动打印出来,
- insmod:安装驱动
- rmmod:删除驱动
- modinfo:打印驱动信息
3、最简单的驱动开始示例
#include <linux/module.h> // module_init module_exit
#include <linux/init.h> // __init __exit
// 模块安装函数
static int __init chrdev_init(void)
{
printk(KERN_INFO "chrdev_init helloworld init\n");
return 0;
}
// 模块下载函数
static void __exit chrdev_exit(void)
{
printk(KERN_INFO "chrdev_exit helloworld exit\n");
}
module_init(chrdev_init);
module_exit(chrdev_exit);
// MODULE_xxx这种宏作用是用来添加模块描述信息
MODULE_LICENSE("GPL"); // 描述模块的许可证
注:
__init
宏告知编译器,将变量或函数放在一个特殊的区域,这个区域定义在vmlinux.lds
中。__init
将函数放在".init.text"
这个代码区中,__initdata
将数据放在".init.data"
这个数据区中。
标记为初始化的函数,表明该函数供在初始化期间使用。在模块装载之后,模块装载就会将初始化函数扔掉。这样可以将该函数占用的内存释放出来。
__exit
宏告知编译器,将函数放在".exit.text"
这个区域中。__exitdata
宏则告知编译器将数据放在".exit.data"
这个区域中。
exit.*区域仅仅对于模块是有用的:如果编译稳定的话,exit函数将永远不会被调用。只有当模块支持无效的时候,exit.*区域将被丢弃。这就是为什么定义中会出现ifdef。
printk是内核的日志函数,日志级别有八个0-7
,通过读写/proc/sys/kernel/printk
文件可以读取、修改控制台的日志级别,数字越小,级别越高,其实printk
始终能输出信息,只不过是在
/var/log/messages
文件中,可以通过dmesg
来进行查看。
#define KERN_EMERG 0
#define KERN_ALERT 1
#define KERN_CRIT 2
#define KERN_ERR 3
#define KERN_WARNING 4
#define KERN_NOTICE 5
#define KERN_INFO 6
#define KERN_DEBUG 7
4、驱动编译Makefile
的编写
前提: 一定要把源码树目录中主Makefile中ARCH、cross_compile变量的值更改了;
#ubuntu的内核源码树,如果要编译在ubuntu中安装的模块就打开这2个
#KERN_VER = $(shell uname -r)
#KERN_DIR = /lib/modules/$(KERN_VER)/build
# 开发板的linux内核的源码树目录
KERN_DIR = /root/driver/kernel
obj-m += module_test.o
all:
make -C $(KERN_DIR) M=`pwd` modules
cp:
cp *.ko /root/porting_x210/rootfs/rootfs/driver_test
.PHONY: clean
clean:
make -C $(KERN_DIR) M=`pwd` modules clean
注:
make -C $(KERN_DIR) M=`PWD` modules
这句话代码的作用就是到 KERN_DIR
这个文件夹中 make modules
把当前目录赋值给M,M
作为参数传到主目录的Makefile
中,实际上是主目录的makefile
中有目标modules
,下面有一定的规则来编译驱动;
#KERN_VER = $(shell uname -r)
#KERN_DIR = /lib/modules/$(KERN_VER)/build
我们在ubuntu
中编译内核的时候用这两句代码,因为在ubuntu
中为我们保留了一份linux
内核的源码树,我们编译的时候直接调用那个源码树的主Makefile
以及一些头文件、内核函数等;
了解规则以后,我们设置好KERN_DIR、obj-m
这两个变量以后直接make
就可以了;
这里注意vermagic 这个的1.8.0-41是你用的linux内核源码树的版本号,只有这个编译的版本号与运行的linux内核版本一致的时候,驱动程序才会被安装
注意license:GPL linux内核开元项目的许可证一般都是GPL这里尽量设置为GPL,否则有些情况下会出现错误;