[学习分享]嵌入式linux字符驱动详解(一)

       先浅谈一下什么是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文件夹也没有对应的设备,这是因为我们只是编写了驱动的出入口函数,没有向内核注册一个真正的设备(文件),下一节将描述如何在驱动程序中向内核注册一个设备。

原创文章 57 获赞 71 访问量 11万+

猜你喜欢

转载自blog.csdn.net/u013053268/article/details/105906435