需要了解嵌入式Linux设备驱动的工作原理

来源:
导读 大家好,我是本期栏目编辑小友,现在为大家讲解需要了解嵌入式Linux设备驱动的工作原理问题。 :计算机软件和集成电路技术的发展给嵌入式行...

大家好,我是本期栏目编辑小友,现在为大家讲解需要了解嵌入式Linux设备驱动的工作原理问题。

:计算机软件和集成电路技术的发展给嵌入式行业带来了巨大的机遇和挑战,而Linux凭借其稳定性、高效性、易定制性和广泛的硬件支持,迅速成为当今计算机领域的一匹黑马。通过分析嵌入式Linux设备驱动相关的内核源代码,从设备驱动架构和内核环境两个方面分析阐述了嵌入式Linux设备驱动的工作原理。

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

0.介绍

设备驱动程序在Linux内核中扮演着极其重要的角色,是内核用来控制物理设备的功能模块。除了CPU、内存等几个部分,所有的设备控制操作都必须由与被控设备相关的代码——驱动完成。否则设备在Linux下无法正常工作,这也是为什么驱动开发成为Linux内核开发的主要任务。

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

通过分析与设备驱动相关的内核源代码,从设备驱动的内部结构和内核环境两个方面深入分析和阐述了设备驱动的工作原理。

1.Linux输入输出子系统的体系结构。

1.1输入输出子系统层次结构。

Linux的I/O子系统分为两个层次:下层与设备相关,即设备驱动,直接控制设备完成特定的I/O操作,并向上层提供一套访问接口;上层与设备无关,根据进程的I/O请求与设备通信。因为Linux将设备作为文件来管理,所以输入/输出子系统的上层实际上是一个实现文件管理功能的虚拟文件系统VFS。进程的I/O请求由VFS转换完成设备上的各种操作,这些操作的具体实现由设备驱动完成。(如图1所示)

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

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

2.文件操作程序登记表。

2.1文件系统调用接口。

[CPP]查看平面图

struct file _ operationsiondriver _ fops=

{

read:IOdriver_read,

write:IOdriver_write,

};

2.2基本入口点功能结构。

我们以globar_read和read函数为例,分析驱动的底层软件结构如何屏蔽设备的硬件细节。

[CPP]查看平面图

staTIcssize _ tglobar _ read(structure file * file,char*buf,size_tlen,loff_t*off)

{

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

{

returnEFAULT;

}

returnsizeof(int);

}

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

用户程序读取操作的入口点在用户空间的缓冲器中,

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

[CPP]查看平面图

structfile_operaTIons{

structmodule *所有者;

/*指这种结构的模块,用于内核维护模块的参考技术*/

loff_t(* ll seek)(structure file *,loff _ t,int);

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

ssize_t(* read)(structure file *、char*、size _ t、loff _ t);

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

ssize_t(* write)(structure file *、constchar*、size _ t、loff _ t *);

/*随设备一起发送。

数据,返回的非负数表示成功写入的字节数 */   int(readdir))(struct inode *, struct file *, void *, filldir_t);   /* 仅用于文件系统的目录读取,不用于设备驱动 */   int (*select)(struct inode *, struct file *, int, select table *);   /* 用于查询设备是否可读、可写或处于特殊的状态 */   int (*ioctl)(struct inode *, struct file *, unsigned int, unsigned int );     /* 用于给设备发送命令的接口函数。如驱动不提供,则所有调用失败,返回ENOTTY */    int (*mmap)(struct inode *, struct file *, struct vm_area_struct *);   /* 用于请求将设备内存映射到进程空间。如果驱动不提供,则所有调用失败,返回ENODEV */   int (*open)(struct inode *, struct file *);     /* 打开设备,通常是对设备的第一个操作函数 */   void (*release)(struct inode *, struct file *);    /* 关闭设备。只有当设备文件的所有备份都被释放时,才进行release调用,而不是每次调用close时都执行。 */   int (*fsync)(struct inode *, struct file *);   /* 是系统调用fsync的背后支撑,用户可调用fsync来刷新缓存数据 */   };  

3. Linux驱动工作原理 3.1 驱动程序工作原理图(如图3所示)   3.2 驱动程序工作原理剖析 3.2.1注册驱动程序 设备驱动是通过insmod命令加载到系统内核中,在内核里由加载模块Init_module()调用注册函数register_chrdev()来完成(如图3所示)。 注册函数格式为:

[cpp] view plain copy   int register_chrdev(unsigned int major, const char *name, struct file_operaTIon *fops);    major :主设备号  name :设备名  fops   :驱动程序结构体的地址   当注册函数在执行时,首先从字符设备注册表chrdev[ ]的底部(实际是倒数第二个表项)开始向上依次查询各个表项(如图4所示),查到表项的成员项fops为null时,说明它是一个空表项。这时把参数给出的设备名和驱动程序指针集合分别赋予该表项device_struct结构体(也被称为设备描述符,在fs/device.c中)的成员项name和fops,如下所示:   [cpp] view plain copy   if (major == 0)   for (major=MAX_CHRDEV-1; major>0; major--)   {if (chrdev[major].fops == NULL)   {chrdev[major].name = name;    chrdev[major].fops = fops;    return major;}   return –EBUSY;   }   3.2.2注销驱动程序 驱动程序的注销是由rmmod命令调用cleanup_module卸载模块,此时注销函数unregister_chrdev()函数就会被调用执行(如图3所示)。当注销函数的参数给出了要注销设备的主设备号major和设备名name后,根据参数major检查注册表项中注册的设备名与参数给出的名字是否一致,若不同则不能注销并返回错误值;如果一致则把该表项的两个成员项都置为null然后返回0值,如下所示: [cpp] view plain copy   if (strcmp(chrdev[major].name, name))      reture –EINVAL;   else      { chrdev[major].name = NULL;        chrdev[major].fops = NULL;     return 0;}   在Linux中由于设备管理是由内核实现的,所以设备管理的数据结构在内存的内核区,设备管理使用的函数都是内核函数。

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

标签:

版权声明:转载此文是出于传递更多信息之目的。若有来源标注错误或侵犯了您的合法权益,请作者持权属证明与本网联系,我们将及时更正、删除,谢谢您的支持与理解。