4.2 设备驱动的结构
可以说,所有的设备驱动都具有相同的结构组织。这或许可以从用户层进行理解:所有的设备在用户层都以同一套标准的接口对用户可见。这些标准的用户请求经过层层传递,最后到达底层驱动,对于每个标准请求,底层驱动都必须有一个对应的响应函数,这就决定了几乎所有的底层驱动都具有相同的结构。然而对用户请求进行响应并非驱动全部,这仅仅是构成底层驱动被动的一面,其主动的一面则体现在对硬件的操作上。设备驱动被动和主动两个方面的特性决定了设备驱动基本的架构:对上提供一套内核标准接口响应函数,对下提供硬件驱动和硬件中断响应函数。
为了实现用户层调用的标准化,操作系统对每种类型设备的驱动都有一套标准的接口函数,底层驱动必须实现这些函数,并将这些函数的地址注册给操作系统,如此当用户请求某项服务时,操作系统可以调用底层驱动对应的函数进行响应。此时操作系统作为主动方主动调用底层驱动提供的函数,以达到用户与底层驱动的交互。另一方面,为了与实际硬件设备进行交互,底层驱动也需要一种通知机制,这种机制通常就是中断。一个用户的请求到来时,底层驱动某个函数被操作系统调用进行响应,该函数配置硬件相关寄存器服务该请求。硬件完成该请求后,必须反过来通知底层驱动表示服务已经完成。硬件通过的方式一般就是中断。故底层驱动必须有一个针对驱动硬件中断发生的中断响应函数。底层驱动中断响应函数一旦被调用,就表示用户请求的某项服务已经完成,大多数时候,这个完成的状态需要通知给用户,故从这个方面来看,内核本身也需要提供一个回调函数给底层驱动,从而完成底层驱动向用户的主动通知,用户在得到通知后可以采取下一步的操作。由此,我们可以总结设备驱动的通用架构:
● 一套需要提供给操作系统的标准操作函数。
● 一个硬件中断响应函数。
● 一个(或一套)由内核提供给底层驱动的回调函数指针,用以存储内核回调函数的地址。
● 设备驱动注册和初始化函数。
以上四点基本构成了所有设备驱动的结构组织。至于对前三个方面如何进行封装,则由具体的设备类型决定。对于第一个方面底层驱动提供给操作系统的标准操作函数,我们可以进一步细化,细化的基本依据就是用户层标准接口。这些函数包括:
● 设备打开函数:完成设备的初始化,注册底层驱动硬件中断响应函数,使能设备工作。
● 设备读写函数:完成与设备数据的交互。
● 设备关闭函数:禁止设备工作,注销底层驱动硬件中断响应函数。
● 设备控制函数:对设备的工作方式进行控制。
以上讨论中,将设备的初始化作为设备打开函数实现的一部分,这通常是不准确的。底层驱动是作为操作系统映像的一部分存在的,即其属于内核代码,设备初始化通常在操作系统启动过程中完成。换句话说,设备初始化将作为一个独立的函数被实现,在操作系统启动过程中作为启动的一部分被调用。此时设备打开函数实现中将剔除设备初始化的代码,一般只需要完成中断函数的注册和设备的使能。另外,很多开发者将中断注册也作为设备初始化的一部分,这并非是一个错误,但基于现在中断号一般都是共享的,故推荐只有在设备真正工作时,才注册中断处理函数。设备打开函数中注册中断基本上已经成为一个约定。
注意
以上列出的四个函数需要注册给内核,这个注册的过程以及设备节点的创建通常也是在设备初始化函数中完成的。这样操作系统启动完成后,用户才有机会通过这些函数对设备进行操作。所以基于这些考虑,我们还需要为设备驱动通用架构再加上一条:设备驱动注册和初始化函数,这即是上面第4点的由来。