基于嵌入式Linux的设备驱动程序设计

来源:本站
导读:目前正在解读《基于嵌入式Linux的设备驱动程序设计》的相关信息,《基于嵌入式Linux的设备驱动程序设计》是由用户自行发布的知识型内容!下面请观看由(电工技术网 - www.9ddd.net)用户发布《基于嵌入式Linux的设备驱动程序设计》的详细说明。
简介:本文介绍了基于嵌入式Linux的设备驱动程序设计

Linux是一个成熟而稳定的操作系统。将Linux植入嵌入式设备具有众多的优点,包括可裁剪和容易移植等,所以Linux操作系统在嵌入式领域获得了广泛的应用。嵌入式Linux一直是嵌入式领域的研究热点,与PC架构不同,嵌入式系统的硬件具有多样性和差异性,嵌入式系统的开发需要对特定系统进行硬件设计,同时还要针对这些硬件来编写驱动程序。Linux内核就是通过驱动程序来同外围设备打交道的,系统设计人员必须为每个设备编写驱动程序,否则设备无法在操作系统下正常工作。

1设备驱动程序设计的基本概念与模型

设备驱动程序是操作系统内核与机器硬件之间的接口,它为应用程序屏蔽了硬件的细节。在应用程序看来,硬件设备只是一个设备文件,应用程序可以像操作普通文件一样对硬件设备进行操作。设备驱动程序是内核的一部分,可以实现以下功能:

◇ 对设备初始化和释放;

◇ 把数据从内核传送到硬件,以及从硬件读取数据;

◇ 读取应用程序传送给设备文件的数据,以及回送应用程序请求的数据;

◇ 检测和处理设备出现的错误。

前面已经提到驱动程序的作用,而编写驱动程序就是构造一系列可供应用程序调用的函数(包括open、release、read、write、llseek、ioctl等)。在用户自己的驱动程序中,首先要根据驱动程序的功能,实现file_operations结构中的函数。不需要的函数接口可以直接在file_operations结构中初始化为NULL;file_operations变量会在驱动程序初始化时注册到系统内部。当操作系统对设备操作时,会调用驱动程序注册的file_operations结构中的函数指针。

以下是嵌入式Linux2.4设备驱动程序的最简模型。

#ifndef_KERNEL_

#define_KERNEL_ //定义要编译为在内核空间中运行

#endif

#ifndef MODULE

#define MODULE

#endif //定义要编译为模块形式,可以在要调用时加载

#include<linux/module.h>

//定义一系列的头文件以及用户自己定义的宏

#include<linux/sched.h>

#include<linux/kernel.h>

#include<linux/init.h>

MODULE_LICENSE("GPL") //定义该驱动程序的GPL许可

int test_init(void) //声明驱动程序中的所有函数

void test_cleanup(void)

static loff_t test_llseek()

static ssize_t test_read()

static ssize_t test_write()

static int test_open()

static int test_release()

static int test_ioctl()

#define MAJOR_NR 126

//定义主设备号(用户指定),该主设备号还可以通过在调用

//register_chrdev时设定参数major为0来实现自动分配

#define DEVICE_NAME "test" //定义设备名称

module_init(test_init); //定义驱动模块的入口函数

module_exit(test_cleanup); //定义驱动模块的出口函数

static struct file_operations test_fop= {

owner: THIS_MODULE,

read: test_read,

write: test_write,

ioctl: test_ioctl,

open: test_open,

release: test_release,

} //定义file_operations结构体test_fop

具体实现前面定义的函数时,需注意下面几点:

① 在test_init函数中要通过调用register_chrdev()函数来向内核注册字符设备驱动程序。如果是块设备,则还需调用mmmap()进行地址空间的映射,再调用register_blkdev()函数来向内核注册块设备驱动程序。在Linux系统中,对中断的处理是属于系统核心部分,因而如果设备与系统之间以中断方式进行数据交换,则必须把该设备的驱动程序作为系统核心的一部分。也就是说设备驱动程序要通过调用request_irq()函数来申请中断,通过free_irq()函数来释放中断(在test_cleanup中实现)。

② open()函数和release()函数的具体实现有着一定的对应性。在open()函数中主要是执行打开设备时的一些初始化代码。如果该驱动程序需要管理多个设备,那么还要获取从设备号,根据从设备号来判断需要操作的设备。其中,从设备号可通过调用函数MINOR(inode->i_rdev)来获得,然后再调用宏MOD_INC_USE_COUNT来使得驱动程序使用计数器加1。而在release()函数中则要进行相反的处理,即调用宏MOD_DEC_USE_COUNT来减小驱动程序使用计数器。

③ 归根结底,驱动函数的实现就是调用内核所支持的函数(包括内核提供的API和用户自己定义的寄存器操作函数)来完成对设备的操作。虽然嵌入式系统设备的种类众多,不同设备操作的具体实现方法不可能相同,但是Linux操作系统提供了一系列特殊API,为开发内核驱动程序带来了很大的方便。在调用这些API时需要注意的是: 通常情况下,应用程序是通过内核接口访问驱动程序的(这是驱动程序的主要使用方式),因此驱动程序需要与应用程序交换数据,但是操作系统内核和驱动程序在内核空间中运行,而用户程序在用户空间中运行,用户程序不能访问内核空间,操作系统内核和驱动程序也不能使用指针或memcpy()等常规的C库函数与用户空间传输数据。造成这种状况的主要原因是Linux操作系统使用了虚拟内存机制。使用了虚拟内存机制后,用户空间的内存可能被换出,当内核使用用户空间指针时,对应的页面可能已经不在内存中了。因此在使用调用函数时要注意:设备驱动程序在申请和释放内存时不是调用malloc()和free(),而调用kmalloc()和kfree();用于内核空间与用户空间进行数据拷贝的函数主要有access_ok()(检查某内存空间是否有权访问),copy_to_user()和put_usr()(内核函数向用户空间传输数据),copy_from_user()和get_user()(用户空间向内核空间传输数据)。关于内核空间与I/O空间的数据交换,不同体系结构的处理器对I/O的处理方式也不同。在x86系列处理器中,I/O与内存完全不同,它是分开编址的,访问它要使用专用的指令;而对ARM体系结构的处理器来说,则是不区分I/O和内存,统一编址的,可以使用同样的指令访问,在驱动程序中可以使用一系列函数来访问I/O口,如outb()、outw()、outl()、inb()、inw()、inl()、outsb()、outsw()、outsl()、insb()、insw()和insl()等。

2Linux2.6与2.4内核驱动程序的区别

为了彻底防止对正在被使用的内核模块进行错误操作,Linux2.6内核在加载和导出内核模块方面都较2.4内核有所改进,避免了用户执行将导致系统崩溃的操作(例如强制删除模块等)。同时,当驱动程序需要在多个文件中包含<linux/module.h>头文件时,不必定义宏来检查内核的版本。与2.4内核相比,2.6内核在可扩展性、吞吐率等方面有较大提升,其新特性主要包括: 使用了新的调度器算法;内核抢占功能显著地降低了用户交互式应用程序;多媒体应用程序等类似应用程序的延迟;改进了线程模型以及对NPTL的支持;显著改善了虚拟内存在一定程度负载下的性能;能够支持更多的文件系统;引进了内存池技术;支持更多的系统设备,在2.4内核中有约束大型系统的限制,其支持的每一类设备的最大数量为256,而2.6内核则彻底打破了这些限制,可以支持4 095种主要的设备类型,且每个单独的类型又可以支持超过一百万个的子设备;支持反向映射机制(reverse mapping),内存管理器为每一个物理页建立一个链表,包含指向当前映射页中每个进程的页表条目的指针。该链表叫PTE链,它极大地提高了找到那些映射某个页的进程的速度。

Linux操作系统的设备驱动程序是在内核空间运行的程序,其中涉及很多内核的操作。随着Linux内核版本的升级,驱动程序的开发必然也要做出相应的修改。总之,在Linux2.6内核上编写设备驱动程序时具体要注意以下几个方面:

① Linux2.6内核驱动程序必须由MODULE_LICENSE("Dual BSD/GPL")语句来定义许可证,而不能再用2.4内核的MODULE_LICENSE("GPL")。否则,在编译时会出现警告提示。

② Linux2.6内核驱动程序可以用int try_module_get(&module)来加载模块,用module_put()函数来卸载模块,而以前 2.4内核使用的宏MOD_INC_USE_COUNT和MOD_DEC_USE_COUNT则可不用。

③ 前面给出的字符型设备驱动程序模型中结构体file_operations的定义要采用下面的形式。这是因为在Linux内核中对结构体的定义形式发生了变化,不再支持原来的定义形式。

static struct file_operations test_ops = {

.open = test_open,

.read =test_read,

.write =test_write,

};

④ 就字符型设备而言,test_open()函数中向内核注册设备的调用函数register_chrdev()可以升级为int register_chrdev_region(dev_t from, unsigned count, char *name),如果要动态申请主设备号可调用函数int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, char *name)来完成;原来的注册函数还可以用,只是不能注册设备号大于256的设备。同理,对于块设备和网络设备的注册函数也有着相对应的代替函数。

⑤ 在声明驱动程序是否要导出符号表方面有着很大的变化。当驱动程序模块装入内核后,它所导出的任何符号都会变成公共符合表的一部分,在/proc/ksyms中可以看到这些新增加的符号。通常情况之下,模块只需实现自己的功能,不必导出任何符号,然而,如果有其他模块需要使用模块导出的符号时,就必须导出符号。只有显示的导出符号才能被其他模块使用,Linux2.6内核中默认不导出所有的符号,不必使用EXPORT_NO_SYMBOLS宏来定义;而在2.4内核中恰恰相反,它默认导出所有的符号,除非使用EXPORT_NO_SYMBOLS,因此在上面给出的范例中可以省略去该定义语句。

⑥ Linux内核统一了很多设备类型,同时也支持更大的系统和更多的设备。原来Linux2.4内核中的变量kdev_t已经被废除不可用,取而代之的是dev_t。它拓展到了32位,其中包括12位主设备号和20位次设备号。调用函数为unsigned int iminor(struct inode *inode)和unsigned int imajor(struct inode *inode),而不再用Linux2.4版本中的int MAJOR(kdev_t dev)和int MINOR(kdev_t dev)。

⑦ 所有的内存分配函数不再包含在头文件<linux/malloc.h>中,而是包含在<linux/slab.h>中,而原来的<linux/malloc.h>已经不存在。所以当在驱动程序中要用到函数kmalloc()或kfree()等内存分配函数时,就必须要定义头文件<linux/slab.h>而不是<linux/malloc.h>。同时,前面提到的申请内存和释放内存函数的具体参数也有了一定的改变,包括:分配标志GFP_BUFFER被取消,取而代之的是GFP_NOIO和 GFP_NOFS;新增了__GFP_REPEAT、__GFP_NOFAIL和__GFP_NORETRY分配标志等,使得内存操作更加方便。

⑧ 因为内核中有些地方的内存分配是不允许失败的,所以为了确保这种情况下的成功分配,Linux2.6版本内核中开发了一种称为“内存池”的抽象。内存池其实相当于后备的高速缓存,以便在紧急状态下使用。要使用内存池的处理函数时,必须包含头文件<linux/mempool.h>。内存池处理函数主要有以下几个:mempool_t *mempool_create()、void *mempool_alloc()、void mempool_free()、int mempool_resize();

⑨ 另外值得一提的是:2.6内核为了区别以.o为扩展名的常规对象文件,将内核模块的扩展名改为.ko,所以驱动程序最后是被编译为ko后缀的可加载模块,在应用程序中加载驱动程序模块时要注意。

结语

驱动程序的开发作为嵌入式Linux系统开发过程当中最重要的环节之一,与硬件特性和操作系统的内核有着紧密的联系。随着Linux内核版本的升级,内核驱动程序必然也要作出相应的改进。相信随着嵌入式Linux系统在各个领域中的广泛应用,具有可抢占实时性的Linux2.6内核必定会在嵌入式领域大显身手。本文会对广大的驱动程序开发人员有一定的帮助。

提醒:《基于嵌入式Linux的设备驱动程序设计》最后刷新时间 2024-03-14 01:01:05,本站为公益型个人网站,仅供个人学习和记录信息,不进行任何商业性质的盈利。如果内容、图片资源失效或内容涉及侵权,请反馈至,我们会及时处理。本站只保证内容的可读性,无法保证真实性,《基于嵌入式Linux的设备驱动程序设计》该内容的真实性请自行鉴别。