嵌入式linux设备驱动,嵌入式Linux设备驱动工作原理的研究

摘要:计算机软件和集成电路技术的发展,为嵌入式产业带来了巨大的机遇和挑战,Linux以其稳定、高效、易定制、硬件广泛支持等特点,迅速崛起为当今计算机领域的一匹黑马。文章通过对与嵌入式Linux设备驱动程序相关内核源码进行分析,从设备驱动的体系结构和内核环境两方面入手,对嵌入式Linux设备驱动程序的工作原理进行剖析和阐述。

关键词: Linux;嵌入式系统;设备驱动;内核环境

0. 引 言

设备驱动程序在Linux内核中占有极其重要的位置,它是内核用于完成对物理设备的控制操作的功能模块。除了CPU、内存以及其他很少的几个部分以外,所有的设备控制操作都必须由与被控设备相关代码——驱动程序来完成。否则设备就无法在Linux下正常工作,这就是驱动程序开发成为Linux内核开发的主要工作的原因。

然而,在嵌入式Linux 系统中,内核提供保护机制,用户空间的进程一般不能直接访问硬件。进行嵌入式系统的开发,很大的工作量是为各种设备编写驱动程序,Linux 设备驱动程序在Linux 内核源代码中占有60%以上,从2.0、2.2 到2.4 版本的内核,源代码的长度日益增加,其实主要是设备驱动程序在增加。

文章通过对与设备驱动相关内核源码进行分析,从设备驱动的内部结构和内核环境两方面入手,对设备驱动程序的工作原理进行深入地剖析和阐述。

1. Linux I/O子系统的体系结构

1.1 I/O子系统的层次结构

Linux的I/O子系统分为上下两个层次:其下层是与设备有关的,即设备驱动程序,它直接控制设备完成具体的I/O操作,并且向上层提供一组访问接口;上层部分是与设备无关的,它根据进程的I/O请求设备驱动程序接口与设备进行通信。由于Linux把设备作为文件管理,所以I/O子系统的上层实际上就是实现文件管理功能的虚拟文件系统VFS,进程的I/O请求经过VFS的转换完成对设备的各种操作,而这些操作的具体实现是由设备驱动来完成的。(如图1所示)

1.2 I/O驱动软件的总体目标

I/O驱动软件的总体目标是将软件组织成一种层次结构,底层软件用来屏蔽具体设备硬件的细节;高层软件则为用户提供一个简洁的界面,从而实现I/O设计的设备无关性。(如图1所示)

嵌入式linux设备驱动,嵌入式Linux设备驱动工作原理的研究_第1张图片

2. 文件操作例程登记表

2.1 文件系统调用接口

[cpp] view plaincopy

structfile_operations IOdriver_fops =

{

read  : IOdriver_read,

write : IOdriver_write,

};

2.2基本入口点函数结构

我们用以globar_read和read函数为例来对驱动底层软件结构是如何屏蔽设备硬件细节进行剖析。

[cpp] view plaincopy

staticssize_t globar_read(structfile *file,char*buf,size_tlen, loff_t *off)

{

if(__copy_to_user(buf,&globar_var,sizeof(int)))

{

return–EFAULT;

}

returnsizeof(int);

}

上述函数的任务是实现将数据从设备复制到用户空间,也就是将内核空间缓冲区里的数据复制其工作原理如图2所示:

嵌入式linux设备驱动,嵌入式Linux设备驱动工作原理的研究_第2张图片

用户程序读操作的入口点到用户空间的缓冲区中,

嵌入式linux设备驱动,嵌入式Linux设备驱动工作原理的研究_第3张图片

read和global_read函数之间的通信是靠字符设备驱动接口中的file_operations结构,基于Linux-2.4.20的的定义如下:

[cpp] view plaincopy

structfile_operations {

structmodule *owner;

/* 指向拥有本结构的module,用于内核维护模块的引用技术 */

loff_t (*llseek) (structfile *, loff_t,int);

/* 改变当前文件的读/写位置并返回新位置,错误返回负数 */

ssize_t (*read) (structfile *,char*,size_t, loff_t);

/* 用来从设备接收数据,返回的非负数表示成功读取的字节数 */

ssize_t (*write) (structfile *,constchar*,size_t, loff_t *);

/* 用来往设备发数据,返回的非负数表示成功写入的字节数 */

int(readdir))(structinode *,structfile *,void*, filldir_t);

/* 仅用于文件系统的目录读取,不用于设备驱动 */

int(*select)(structinode *,structfile *,int, select table *);

/* 用于查询设备是否可读、可写或处于特殊的状态 */

int(*ioctl)(structinode *,structfile *, unsignedint, unsignedint);

/* 用于给设备发送命令的接口函数。如驱动不提供,则所有调用失败,返回ENOTTY */

int(*mmap)(structinode *,structfile *,structvm_area_struct *);

/* 用于请求将设备内存映射到进程空间。如果驱动不提供,则所有调用失败,返回ENODEV */

int(*open)(structinode *,structfile *);

/* 打开设备,通常是对设备的第一个操作函数 */

void(*release)(structinode *,structfile *);

/* 关闭设备。只有当设备文件的所有备份都被释放时,才进行release调用,而不是每次调用close时都执行。 */

int(*fsync)(structinode *,structfile *);

/* 是系统调用fsync的背后支撑,用户可调用fsync来刷新缓存数据 */

};

3. Linux驱动工作原理

3.1 驱动程序工作原理图(如图3所示)

嵌入式linux设备驱动,嵌入式Linux设备驱动工作原理的研究_第4张图片

3.2 驱动程序工作原理剖析

3.2.1注册驱动程序

设备驱动是通过insmod命令加载到系统内核中,在内核里由加载模块Init_module()调用注册函数register_chrdev()来完成(如图3所示)。

注册函数格式为:

[cpp] view plaincopy

intregister_chrdev(unsignedintmajor,constchar*name,structfile_operation *fops);

 major :主设备号

 name :设备名

 fops   :驱动程序结构体的地址

当注册函数在执行时,首先从字符设备注册表chrdev[ ]的底部(实际是倒数第二个表项)开始向上依次查询各个表项(如图4所示),查到表项的成员项fops为null时,说明它是一个空表项。这时把参数给出的设备名和驱动程序指针集合分别赋予该表项device_struct结构体(也被称为设备描述符,在fs/device.c中)的成员项name和fops,如下所示:

嵌入式linux设备驱动,嵌入式Linux设备驱动工作原理的研究_第5张图片

[cpp] view plaincopy

if(major == 0)

for(major=MAX_CHRDEV-1; major>0; major--)

{if(chrdev[major].fops == NULL)

{chrdev[major].name = name;

chrdev[major].fops = fops;

returnmajor;}

return–EBUSY;

}

3.2.2注销驱动程序

驱动程序的注销是由rmmod命令调用cleanup_module卸载模块,此时注销函数unregister_chrdev()函数就会被调用执行(如图3所示)。当注销函数的参数给出了要注销设备的主设备号major和设备名name后,根据参数major检查注册表项中注册的设备名与参数给出的名字是否一致,若不同则不能注销并返回错误值;如果一致则把该表项的两个成员项都置为null然后返回0值,如下所示:

[cpp] view plaincopy

if(strcmp(chrdev[major].name, name))

reture –EINVAL;

else

{ chrdev[major].name = NULL;

chrdev[major].fops = NULL;

return0;}

在Linux中由于设备管理是由内核实现的,所以设备管理的数据结构在内存的内核区,设备管理使用的函数都是内核函数。

4. 结束语

操作系统是通过各种驱动程序来驾驭硬件设备的,它为用户屏蔽了各种各样的设备,驱动硬件是操作系统最基本的功能,并且提供统一的操作方式。设备驱动程序是内核的一部分,硬件驱动程序是操作系统最基本的组成部分,因此熟悉驱动的编写是很重要的。

你可能感兴趣的:(嵌入式linux设备驱动)