Linux 设备驱动框架

来源:互联网 发布:7.16大连新港火灾知乎 编辑:程序博客网 时间:2024/04/29 20:46

(1)驱动框架

      Linux将所有外部设备看成是一类特殊文件,称之为“设备文件”,如果说系统调用是Linux内核和应用程序之间的接口,那么设备驱动程序则可以看成是Linux内核与外部设备之间的接口。设备驱动程序向应用程序屏蔽了硬件在实现上的细节,使得应用程序可以像操作普通文件一样来操作外部设备。在应用程序看来,硬件设备只是一个设备文件,应用程序可以象操作普通文件一样对硬件设备进行操作,如open ()、close ()、read ()、write () 等。
      Linux主要将设备分为三类:字符设备和块设备和网络设备。字符设备是指设备发送和接收数据以字符的形式进行,而且也不支持随机访问;而块设备则是利用一块系统内存作为缓冲区,以整个数据缓冲区的形式进行,块设备主要是针对磁盘等慢速设备设计的,其目的是避免耗费过多的CPU时间来等待操作的完成。

      每个设备文件对应有两个设备号:一个是主设备号,标识该设备的种类,也标识了该设备所使用的驱动程序;另一个是次设备号,标识使用同一设备驱动程序的不同硬件设备。设备文件的主设备号必须与设备驱动程序在登录该设备时申请的主设备号一致,否则用户进程将无法访问到设备驱动程序。

      Linux下的设备驱动程序可以按照两种方式进行编译,一种是直接静态编译成内核的一部分,另一种则是编译成可以动态加载的模块。如果编译进内核的话,会增加内核的大小,还要改动内核的源文件,而且不能动态地卸载,不利于调试,所有推荐使用模块方式。从本质上来讲,模块也是内核的一部分,它不同于普通的应用程序,不能调用位于用户态下的C或者C++库函数,而只能调用Linux内核提供的函数,在/proc/allksyms中可以查看到内核提供的所有函数。

      设备驱动程序结构

了解设备驱动程序的基本结构(或者称为框架),对开发人员而言是非常重要的,Linux的设备驱动程序大致可以分为如下几个部分:驱动程序的注册与注销、设备的打开与释放、设备的读写操作、设备的控制操作、设备的中断和轮询处理。

A,驱动程序的注册与注销
向系统增加一个驱动程序意味着要赋予它一个主设备号,这可以通过在驱动程序的初始化过程中调用register_chrdev( )或者register_blkdev( )来完成。而在关闭字符设备或者块设备时,则需要通过调用unregister_chrdev( )或unregister_blkdev( )从内核中注销设备,同时释放占用的主设备号。

B,设备的打开与释放
      打开设备是通过调用file_operations结构中的函数open( )来完成的,它是驱动程序用来为今后的操作完成初始化准备工作的。在大部分驱动程序中,open( )通常需要完成下列工作: 检查设备相关错误,如设备尚未准备好等。如果是第一次打开,则初始化硬件设备。识别次设备号,如果有必要则更新读写操作的当前位置指针f_ops。分配和填写要放在file->private_data里的数据结构。使用计数增1。
      释放设备是通过调用file_operations结构中的函数release( )来完成的,这个设备方法有时也被称为close( ),它的作用正好与open( )相反,通常要完成下列工作:使用计数减1。释放在file->private_data中分配的内存。如果使用计算为0,则关闭设备。
C,设备的读写操作
      字符设备的读写操作相对比较简单,直接使用函数read( )和write( )就可以了。但如果是块设备的话,则需要调用函数block_read( )和block_write( )来进行数据读写,这两个函数将向设备请求表中增加读写请求,以便Linux内核可以对请求顺序进行优化。由于是对内存缓冲区而不是直接对设备进行操作的,因此能很大程度上加快读写速度。如果内存缓冲区中没有所要读入的数据,或者需要执行写操作将数据写入设备,那么就要执行真正的数据传输,这是通过调用数据结构blk_dev_struct中的函数request_fn( )来完成的。

D,设备的控制操作
      除了读写操作外,应用程序有时还需要对设备进行控制,这可以通过设备驱动程序中的函数ioctl( )来完成。ioctl( )的用法与具体设备密切关联,因此需要根据设备的实际情况进行具体分析。

E,设备的中断和轮询处理

      对于不支持中断的硬件设备,读写时需要轮流查询设备状态,以便决定是否继续进行数据传输。如果设备支持中断,则可以按中断方式进行操作。

(2)字符设备和杂项设备

      首先了解下杂项设备(misc device),杂项设备也是在嵌入式系统中用得比较多的一种设备驱动。在Linux 内核的include/linux目录下有Miscdevice.h文件,要把自己定义的misc device从设备定义在这里。其实是因为这些字符设备不符合预先确定的字符设备范畴,所有这些设备采用主编号10 ,一起归于misc device,其实misc_register就是用主标号10调用register_chrdev()的(需要注意的是,当注册的从设备号是MISC_DYNAMIC_MINOR,要求的是系统自动分配从设备号),对应的反注册函数是misc_deregister。也就是说,misc设备其实也就是特殊的字符设备。 
      再来看字符设备,下面我们来假设一个非常简单的虚拟字符设备:这个设备的名字叫做"gobalvar"。驱动程序是内核的一部分,因此我们需要给其添加模块初始化函数,该函数用来完成对所控设备的初始化工作,我们用module_init( global_var)申明,并调用register_chrdev() 函数注册字符设备(老式的字符设备注册方式):
static int __init gobalvar_init(void)
{
   if (register_chrdev(MAJOR_NUM, " gobalvar ", &gobalvar_fops))
  {
    //…注册失败
   }
  else
  {
    //…注册成功
   }
}
      其中,register_chrdev函数中的参数MAJOR_NUM为主设备号,"gobalvar"为设备名,gobalvar_fops为包含基本函数入口点的结构体,类型为file_operations。当gobalvar模块被加载时,gobalvar_init被执行,它将调用内核函数register_chrdev,把驱动程序的基本入口点指针存放在内核的字符设备地址表中,在用户进程对该设备执行系统调用时提供入口地址。
      与模块初始化函数对应的就是模块卸载函数,需要调用register_chrdev()的"反函数" unregister_chrdev():
static void __exit gobalvar_exit(void)
{
      unregister_chrdev(MAJOR_NUM," gobalvar ");
}
      随着内核不断增加新的功能,file_operations结构体已逐渐变得越来越大,但是大多数的驱动程序只是利用了其中的一部分。对于字符设备来说,要提供的主要入口有:open ()、release ()、read ()、write ()、ioctl ()、llseek()、poll()等。使用register_chrdev(LED_MAJOR,DEVICE_NAME,&dev_fops)注册字符设备驱动程序时,如果有多个设备使用该函数注册驱动程序,LED_MAJOR不能相同,否则几个设备都无法注册。


参考原文:http://blog.csdn.net/O1O1O1o/archive/2008/08/19/2797974.aspx

参考原文:http://blog.csdn.net/lanmanck/archive/2009/10/23/4713978.aspx