新闻中心

EEPW首页 > 嵌入式系统 > 业界动态 > [ARM笔记]设备驱动概述

[ARM笔记]设备驱动概述

作者:时间:2016-11-24来源:网络收藏

  1. 设备驱动和操作系统

本文引用地址:http://www.eepw.com.cn/article/201611/340660.htm

  1.1 无操作系统时的设备驱动

  在没有操作系统的情况下,设备驱动的接口直接提交给应用软件工程师,应用软件没有跨越任何层次就可以直接访问设备驱动的接口。驱动包含的接口函数也与硬件的功能直接吻合,没有任何附加功能。

  1.2 有操作系统时的设备驱动

  没有操作系统时,设备驱动直接被应用程序调用,不与任何操作系统关联。当系统中包含操作系统后,设备驱动会变得怎样?

  首先,无操作系统时设备驱动的硬件操作仍然是必不可少的,没有这一部分,设备驱动不可能与硬件打交道,也就是说在无操作系统时驱动所做的工作,在有操作系统时也是要做的。

  其次,我们还需要将设备驱动融入操作系统内核。应用程序是通过调用操作系统的API来实现对硬件的操作的,所以设备驱动需要融入到内核中。为了实现这种融合,必须在所有的设备驱动中设计面向操作系统内核的接口,这样的接口由操作系统规定,对一类设备而言结构一致,独立于具体的设备。不同的操作系统中定义的设备驱动架构是不一样的,要将设备驱动融入系统内核中,就需要按照操作系统给出的独立于设备的接口架构设计,如此这般,应用程序就可以使用统一的系统调用接口来访问各种设备。其中内核的API 包括并发/同步控制、阻塞/唤醒、中断底半部调度、内存和I/O 访问等。

  由此可见,当系统中存在操作系统时,设备驱动变成了链接硬件和内核的桥梁,操作系统的存在使得单一的“驱动硬件设备工作”变为操作系统与硬件交互的模块,它对外呈现为操作系统API,不再给应用软件工程师直接提供接口。因此,驱动工程师不仅需要牢固的硬件基础,如硬件的工作原理、寄存器设置等,还需要对驱动中所涉及的内核知识有良好的掌握,包括内核支持的API、内核驱动架构等,才能设计开发出好的设备驱动程序。也就是说设备驱动从无操作系统时的应用程序和硬件设备之间的桥梁转变成操作系统和硬件设备之间的沟通纽带。

  2. 设备驱动

  2.1 设备的分类及特点

  驱动针对的对象是存储器和外设(包括CPU内部集成的存储器和外设),而不是针对CPU核。系统中将存储器和外设分为3个基础大类:字符设备、块设备和网络设备。

  2.1.1 字符设备

  概括的讲,字符设备指那些必须以串行顺序依次进行访问的设备,如触摸屏、磁带驱动器、鼠标等。字符设备是一种可以当作一个字节流来存取的设备,字符驱动就负责实现这种行为。这样的驱动常常至少实现open,close,read,和write系统调用。字符驱动很好地展

  现了流的抽象,它通过文件系统结点来存取,也就是说,字符设备被当作普通文件来访问。字符设备和普通文件之间唯一的不同就是:你可以在普通文件中移来移去,但是大部分字符设备仅仅是数据通道,你只能顺序存取。然而,也存在看起来象数据区的字符设备,你可以在里面移来移去的访问数据。例如,frame grabber经常这样,应用程序可以使用mmap或者lseek 存取整个要求的图像。

  2.1.2 块设备

  块设备是可以用任意顺序访问,以块为单位进行操作,如硬盘、软驱等。一般来说,块设备和字符设备并没有明显的界限。如同字符设备,块设备也是通过文件系统结点进行存取。一个块设备是可以驻有一个文件系统的。Linux系统中允许应用程序读写一个块设备象一个字符设备一样,它允许一次传送任意数目的字节,当然也包括一个字节。块和字符设备的区别仅仅在内核在内部管理数据的方式上,如字符设备不经过系统的快速缓冲,而块设备经过系统的快速缓冲,并且在内核/驱动的软件接口上不同。虽然它们之间的区别对用户是透明的,它们都使用文件系统的操作接口open()、close()、read()、write()等函数进行访问,但是它们的驱动设计存在很大的差异。

  2.1.3 网络设备

  网络设备是面向数据包的接收和发送而设计的,它与字符设备、块设备不同,并不对应于文件系统中的节点。内核与网络设备的通信和内核与字符设备、块设备的通信方式可以说是完全不同的。任何网络事务都通过一个接口来进行,就是说,一个能够与其他主机交换数据的设备。通常,一个接口是一个硬件设备,但是它也可能是一个纯粹的软件设备,比如环回接口,因此网络设备也可以称为网络接口。在内核网络子系统的驱动下,网络设备负责发送和接收数据报文。网络驱动对单个连接一无所知,它只处理报文。

  既然网络设备不是一个面向流的设备,一个网络接口就不象字符设备、块设备那么容易映射到文件系统的一个结点上。Linux提供的对网络设备的存取方式仍然是通过给它们分配一个名字,但是这个名字在文件系统中没有对应的入口,其并不用read和write等函数,而是通过内核调用和报文传递相关的函数来实现。

  近年来,某些设备驱动类别也已经添加到Linux内核中,如FireWire驱动。与内核处理USB和SCSI驱动相同的方式,内核开发者集合了类别范围内的特性,并把它们输出给驱动实现者,以避免重复工作,因此简化和加强了编写类似驱动的过程。

  除了上面对设备的分类的方式之外,还有其他的划分方式,与上面的设备类型是正交的。通常,某些类型的驱动与给定类型设备其他层的内核支持函数一起工作。例如,你可以说USB模块,串口模块,SCSI模块等等。每个USB设备由一个USB模块驱动,与USB子系统一起工作,但是设备自身在系统中表现为一个字符设备(比如一个USB串口),一个块设备(一个USB内存读卡器),或者一个网络设备(一个USB以太网接口)。

  2.2 不同设备的驱动设计概述

  上述的三类设备,除了网络设备外,字符设备与块设备都被映射到Linux文件系统的文件和目录,通过文件系统的系统调用接口open()、write()、read()、close()等函数访问。块设备比字符设备复杂,在它上面会有一个磁盘/Flash文件系统,该文件系统对存储介质上的文件和目录进行规范化的组织。

  2.2.1 字符设备驱动

  Linux字符设备驱动的核心是file_operations结构体,驱动的主体是实现其中的read()、write()、ioctl()、open()、release()等方法,这些方法将完成系统需要对设备进行的操作功能。

  其结构形式如下所示:

  struct file_operations xxx_fops =

  {

  .owner = THIS_MODULE,

  .read = xxx_read,

  .write = xxx_write,

  .ioctl = xxx_ioctl,

  ...

  }

  open()方法:该方法提供给驱动程序初始化设备的能力,从而为以后的设备操作做好准备,主要完成如下工作:检查设备特定的错误(例如设备没准备好,或者类似的硬件错误);如果它第一次打开,初始化设备;如果需要,更新file_operations指针;分配并填充要放进filp->private_data的任何数据结构等。此外open操作一般还会递增使用计数,用以防止文件关闭前模块被卸载出内核。

  release()方法:与open方法相反,它主要是释放由open分配的filp->private_data中的所有内容;在最后一次关闭操作时关闭设备;使用计数减1等操作。

  read()和write()方法:read方法完成将数据从内核拷贝到应用程序空间,write方法相反,将数据从应用程序空间拷贝到内核。

  ioctl()方法:ioctl 方法主要用于对设备进行读写之外的其他控制,比如配置设备、进入或退出某种操作模式,这些操作一般都无法通过 read/write文件操作来完成。

  2.2.2 块设备驱动

  Linux块设备驱动并不直接实现file_operations成员函数,其主体变成处理实现block_device_operations成员函数以及处理上层下达的I/O请求。block_device_operations结构体中包含了ioctl()、open()、release()方法,因为字符设备和块设备的存取方法不同,其I/O处理请求可以看作是块设备中的read()和write()方法。块设备调用函数block_read( )和block_write( )来进行数据读写,这两个函数将向设备请求表中Linux块设备驱动并不直接实现file_operations成员函数,其主体变成处理实现block_device_operations成员函数以及处理上层下达的I/O请求。block_device_operations结构体中包含了ioctl()、open()、release()方法,因为字符设备和块设备的存取方法不同,其I/O处理请求可以看作是块设备中的read()和write()方法。块设备调用函数block_read( )和block_write( )来进行数据读写,这两个函数将向设备请求表中增加读写请求,以便Linux内核可以对请求顺序进行优化。由于是对内存缓冲区而不是直接对设备进行操作的,因此很大程度上加快了读写速度。如果内存缓冲区中没有所要读入的数据,或者需要执行写操作将数据写入设备,那么就要执行真正的数据传输。

  处理I/O请求的典型流程如下所示:

  static void xxx_request(request_queue_t *q)

  {

  struct request *req;

  while ((req = elv_next_request(q)) != NULL)

  {

  struct xxx_dev *dev = req->rq_disk->private_data;

  if (!blk_fs_request(req)) //不是文件系统请求

  {

  printk(KERN_NOTICE "Skip non-fs requestn");

  end_request(req, 0);//通知请求处理失败

  }

  continue;

  }

  xxx_transfer(dev, req->sector, req->current_nr_sectors, req->buffer,rq_data_dir(req)); //处理这个请求

  end_request(req, 1); //通知成功完成这个请求

  }


上一页 1 2 3 下一页

关键词: ARM Linux

评论


相关推荐

技术专区

关闭