(1)内核代码:设备驱动程序是内核的一部分,如果驱动程序出错,则可能导
致系统崩溃。
(2)内核接口:设备驱动程序必须为内核或者其子系统提供一个标准接口。比如,一个终端驱动程序必须为内核提供一个文件I/O接口;一个SCSI设备驱动程序应该为SCSI子系统提供一个SCSI设备接口,同时SCSI子系统也必须为内核提供文
件的I/O接口及缓冲区。
(3)内核机制和服务:设备驱动程序使用一些标准的内核服务,如内存分配等。
(4)可装载:大多数的Linux操作系统设备驱动程序都可以在需要时装载进内
核,在不需要时从内核中卸载。 (5)可设置:Linux操作系统设备驱动程序可以集成为内核的一部分,并可以根据需要把其中的某一部分集成到内核中,这只需要在系统编译时进行相应的设置即可。
(6)动态性:当系统启动且各个设备驱动程序初始化后,驱动程序将维护其控制的设备。如果该设备驱动程序控制的设备不存在也不影响系统的运行,此时的设备
驱动程序只是多占用了一点系统内存罢了。
二.设备驱动程序与外界的接口
每种类型的驱动程序,不管是字符设备还是块设备都为内核提供相同的调用接口,故内核能以相同的方式处理不同的设备。Linux为每种不同类型的设备驱动程序
维护相应的数据结构,以便定义统一的接口并实现驱动程序的可装载性和动态性。
Linux设备驱动程序与外界的接口可以分为如下三个部分: (1)驱动程序与操作系统内核的接口:这是通过数据结构file_operations来完成的。
(2)驱动程序与系统引导的接口:这部分利用驱动程序对设备进行初始化。
(3)驱动程序与设备的接口:这部分描述了驱动程序如何与设备进行交互,这
与具体设备密切相关。
可归结为如下图2:
操 数实 各据结构 接 初 接具设 接系进 驱动程图2 设备驱动程序与
三.设备驱动程序的组织结构
设备驱动程序有一个比较标准的组织结构,一般可以分为下面三个主要组成部
分:
(1)自动配置和初始化子程序
这部分程序负责检测所要驱动的硬件设备是否存在以及是否能正常工作。如果该设备正常,则对设备及其驱动程序所需要的相关软件状态进行初始化。这部分程序仅
在初始化时被调用一次。
(2)服务于I/O请求的子程序
该部分又可称为驱动程序的上半部分。系统调用对这部分进行调用。系统认为这部分程序在执行时和进行调用的进程属于同一个进程,只是由用户态变成了内核态,而且具有进行此系统调用的用户程序的运行环境。故可以在其中调用与进程运行环境
有关的函数。
(3)中断服务子程序
该部分又可称为驱动程序的下半部分。设备在I/O请求结束时或其它状态改变时产生中断。中断可以产生在任何一个进程运行时,因此中断服务子程序被调用时不能依赖于任何进程的状态,因而也就不能调用与进程运行环境有关的函数。因为设备驱动程序一般支持同一类型的若干设备,所以一般在系统调用中断服务子程序时都带有
一个或多个参数,以唯一标识请求服务的设备。
四.设备驱动程序的代码
设备驱动程序是一些函数和数据结构的集合,这些函数和数据结构是为实现管理设备的一个简单接口。操作系统内核使用这个接口来请求驱动程序对设备进行I/O操作。甚至,我们可以把设备驱动程序看成是一个抽象数据类型,它为计算机中的每个硬件设备都建立了一个通用函数接口。由于一个设备驱动程序就是一个模块,所以在内核内部用一个file结构来识别设备驱动程序,而且内核使用file_operatuions结
构来访问设备驱动程序中的函数。
了解设备驱动程序代码的如下几个部分:
◆ 驱动程序的注册与注销。 ◆ 设备的打开与释放。 ◆ 设备的读写操作。
◆ 设备的控制操作。 ◆ 设备的中断和轮询处理。 五、字符设备驱动程序的代码
1、了解什么是字符设备
2、了解字符设备的基本入口点
字符设备的基本入口点也可称为子程序,它们被包含在驱动程序的
file_operations结构中。
① open()函数;② release()函数;③ read()函数;④ write()函数;
⑤ ioctl()函数;⑥ select()函数。
3、字符设备的注册
设备驱动程序提供的入口点在设备驱动程序初始化时向系统登记,以便系统调
用。Linux系统通过调用register_chrdev()向系统注册字符型设备驱动程序。
register_chrdev()定义如下:
#include
#include
int register_chrdev(unsigned int major, const char *name, struct
file_operations *ops); 其中major时设备驱动程序向系统申请的主设备号。如果它为0,则系统为该驱动程序动态地分配第一个空闲的主设备号,并把设备名和文件操作表的指针置于chrdevs表的相应位置。name是设备名,ops是对各个调用入口点的说明。register_chrdev()函数返回0表示注册成功;返回-EINVAL表示申请的主设备号非法,一般主设备号大于系统所允许的最大设备号;返回-EBUSY表示所申请的主设备号正被其它设备驱动程序使用。如果动态分配主设备号成功,则该函数将返回所分配的主设备号。如果register_chrdev()操作成功,则设备名就会出现在/proc/devices文件
中。
字符设备注册以后,还必须在文件系统中为其创建一个代表节点。该节点可以是在/dev目录中的一个节点,这种节点都是文件节点,且每个节点代表一个具体的设备。不过要有主设备号和从设备号两个参数才能创建一个节点。还可以是在devfs设备文件目录下的一个节点,对于这种节点应根据主设备号给每一种设备都创建一个目录节
点,在这个目录下才是代表具体设备的文件节点。
【实验内容】
编写一个简单的字符设备驱动程序。要求该字符设备包括scull_open()、scull_write()、scull_read()、scull_ioctl()和scull_release()五个基本操作,
并编写一个测试程序来测试你所编写的字符设备驱动程序。
【实验指导】
先给出字符设备驱动程序要用到的数据结构定义:
struct device_struct{
const char *name;
struct file_operations *chops;
};
static struct device_struct chrdevs[MAX_CHRDEV];
typedef struct Scull_Dev {
void **data;
int quantum; // the current quantum size
int qset; // the current array size
unsigned long size;
unsigned int access_key; // used by sculluid and scullpriv
unsigned int usage; // lock the device while using it
struct Scull_Dev *next; // next listitem
} scull; 1、字符设备的结构
字符设备的结构即字符设备的开关表。当字符设备注册到内核后,字符设备的名字和相关操作被添加到device_struct结构类型的chrdevs全局数组中,称chrdevs为字符设备的开关表。下面以一个简单的例子说明字符设备驱动程序中字符设备结构的定义:(假设设备名为scull)
**** file_operation结构定义如下,即定义chr设备的_fops **** static int scull_open(struct inode *inode,struct file *filp); static int scull_release(struct inode *inode,struct file *filp); static ssize_t scull_write(struct inode *inode,struct file *filp,const char *buffer,int count);
static ssize_t scull_read(struct inode *inode,struct file *filp,char *buffer,int count);
static int scull_ioctl(struct inode *inode,struct file *filp,unsigned long int cmd,unsigned long arg);
struct file_operation chr_fops = { NULL, // seek scull_read, // read scull_write, // write NULL, // readdir NULL, // poll scull_ioctl, // ioctl NULL, // mmap scull_open, // open NULL, // flush
scull_release, // release NULL, // fsync NULL, // fasync
NULL, // check media change NULL, // revalidate NULL // lock };
2、字符设备驱动程序入口点
字符设备驱动程序入口点主要包括初始化字符设备、字符设备的I/O调用和中断。在引导系统时,每个设备驱动程序通过其内部的初始化函数init()对其控制的设备及其自身初始化。字符设备初始化函数为chr_dev_init(),包含在/linux/drivers/char/mem.c中,它的主要功能之一是在内核中登记设备驱动程序。具体调用是通过register_chrdev()函数。register_chrdev()函数定义如下:
#include
int register_chrdev(unsigned int major,const char *name,struct file_operation *fops);
其中major是为设备驱动程序向系统申请的主设备号。如果为0,则系统为此驱动程序动态地分配一个主设备号。name是设备名。fops是前面定义的file_operation结构的指针。在登记成功的情况下,如果指定了major,则register_chrdev()函数返回值为0;如果major值为0,则返回内核分配的主设备号。并且register_chrdev()函数操作成功,设备名就会出现在/proc/devices文件里;在登记失败的情况下,register_chrdev()函数返回值为负。
初始化部分一般还负责给设备驱动程序申请系统资源,包括内存、中断、时钟、I/O端口等,这些资源也可以在open()子程序或别的地方申请。在这些资源不用的时候,应该释放它们,以利于资源的共享。
用于字符设备的I/O调用主要有:open()、release()、read()、write()和ioctl()。
open()函数的使用比较简单,当一个设备被进程打开时,open()函数被唤醒: static int scull_open(struct inode *inode,struct file *filp) { ?? ??
MOD_INC_USE_COUNT; return 0; }