先谈一下什么是Linux驱动:驱动,顾名思义就是提供一组程序,能让处理器访问读写该设备信息。比如一个led灯,我们用GPIO来控制,那么led驱动就是配置GPIO属性,能让用户通过读写GPIO的信息来控制led灯。在裸机程序或者简单的单片机程序中,我们只需要调用库函数或者操作寄存器,实现一系列接口供应用程序调用就可以了。在Linux驱动开发中,也无非是这么回事,通过读写寄存器配置好GPIO属性,就可以实现对led灯的控制。只不过Linux是个庞大的操作系统,提供了标准的驱动程序模板,供我们参考如何把我们的驱动加入到Linux里面,然后应用程序或者称用户程序,就可以使用标准的Linux操作方法来操作led灯(open、read、write等)。“在Linux里面,一切皆文件”,这句话应该都不会陌生。编写Linux设备驱动,可以理解为是把设备描述成文件的一个过程。
Linux里面对设备设计了几个分类:
1、字符设备
2、块设备
3、网络设备
字符设备可以理解为可以按字节顺序访问的设备,比如LED、按键、IIC设备、LCD等,都属于字符设备的范畴;块设备则是按块来访问,如内存等,一般归属为块设备;网络设备则是网络相关的;需要注意,没有规定说一个设备只能归属到一个类里面,一个设备可以同时是块设备和网络设备;至于设备分类方面,目前只了解大概的概念,没有去深究,后面学习过程中遇到会回来更新这部分的描述。
编写Linux驱动程序,需要用到Linux的一些程序文件(头文件),因此在编写驱动程序之前需要先下载一份Linux源码,我用的版本是4.1.15。下载后解压到自己的目录里面。
下面以一个hellloworld的例子来认识一下Linux字符设备驱动的程序模板:
创建一个helllo.c文件,输入以下代码:
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
static int __init hello_init(void)
{
printk("hello_world init!\r\n");
return 0;
}
static void __exit hello_exit(void)
{
printk("hello_world exit!\r\n");
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("weymin");
接下来需要把刚刚写的hello_world驱动模块编译成内核模块,这里需要用到linux源码里的文件,因此Makefile里面需要指定Linux内核源码的路径。Makefile文件内容如下:
KERNELDIR := /home/weymin/workdir/kernel/linux
CURRENT_PATH := $(shell pwd)
obj-m := hello.o
build: kernel_modules
kernel_modules:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
前面我们创建的.c文件名字是hello.c,所以Makefile里面obj-m参数的文件是hello.o
执行编译命令:make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf-
编译成功后当前目录会生成一个.ko文件和一系列其他中间文件,这个.ko就是需要加载的内核驱动模块。
把.ko文件传到开发板上,执行模块加载命令 insmod hello.ko,可以看到提示“hello_world init!”,执行模块卸载命令,可以看到打印提示“hello_world exit!”。
到这里说明内核已经可以成功地加载和卸载我们编写的驱动模块。
但此时,我们并没有做任何处理,查看根文件目录下的dev文件夹也没有对应的设备,这是因为我们只是编写了驱动的出入口函数,没有向内核注册一个真正的设备(文件),下一节将描述如何在驱动程序中向内核注册一个设备。