设备驱动程序是操作系统内核和机器硬件之间的接口,它为应用程序屏蔽硬件的细节,一般来说, Linux的设
备驱动程序需要完成如下功能:
.设备初始化、释放;
.提供各类设备服务;
.负责内核和设备之间的数据交换;
.检测和处理设备工作过程中出现的错误。
Linux下的设备驱动程序被组织为一组完成不同任务的函数的集合,通过这些函数使得 Linux的设备操作犹如
文件一般。在应用程序看来,硬件设备只是一个设备文件,应用程序可以象操作普通文件一样对硬件设备进行
操作,如 open ()、close ()、read ()、write () 等。
Linux主要将设备分为二类:字符设备和块设备。字符设备是指设备发送和接收数据以字符的形式进行;而块
设备则以整个数据缓冲区的形式进行。在对字符设备发出读 /写请求时,实际的硬件 I/O一般就紧接着发生了;
而块设备则不然,它利用一块系统内存作缓冲区,当用户进程对设备请求能满足用户的要求,就返回请求的数
据,如果不能,就调用请求函数来进行实际的 I/O操作。块设备主要针对磁盘等慢速设备。
1.内存分配
由于Linux驱动程序在内核中运行,因此在设备驱动程序需要申请 /释放内存时,不能使用用户级的 malloc/free
函数,而需由内核级的函数 kmalloc/kfree ()来实现,kmalloc()函数的原型为:
void kmalloc (size_t size ,int priority);
参数 size为申请分配内存的字节数, kmalloc最多只能开辟 128k的内存;参数 priority说明若 kmalloc()
不能马上分配内存时用户进程要采用的动作: GFP_KERNEL 表示等待,即等 kmalloc()函数将一些内存安排
到交换区来满足你的内存需要, GFP_ATOMIC 表示不等待,如不能立即分配到内存则返回 0 值;函数的返回
值指向已分配内存的起始地址,出错时,返回 0。
kmalloc ()分配的内存需用 kfree()函数来释放, kfree ()被定义为:
# define kfree (n) kfree_s( (n) ,0)
其中 kfree_s ()函数原型为:
void kfree_s (void * ptr ,int size);
参数 ptr为 kmalloc()返回的已分配内存的指针, size是要释放内存的字节数,若为 0 时,由内核自动确定内
存的大小。
2.中断
许多设备涉及到中断操作,因此,在这样的设备的驱动程序中需要对硬件产生的中断请求提供中断服务程序。
与注册基本入口点一样,驱动程序也要请求内核将特定的中断请求和中断服务程序联系在一起。在 Linux中,
用 request_irq()函数来实现请求:
120
int request_irq (unsigned int irq ,void( * handler) int ,unsigned long type ,char * name);
参数 irq为要中断请求号,参数 handler为指向中断服务程序的指针,参数 type 用来确定是正常中断还是快
速中断(正常中断指中断服务子程序返回后,内核可以执行调度程序来确定将运行哪一个进程;而快速中断是
指中断服务子程序返回后,立即执行被中断程序,正常中断 type取值为 0 ,快速中断 type 取值为
SA_INTERRUPT),参数 name是设备驱动程序的名称。
3.字符设备驱动
我们必须为字符设备提供一个初始化函数,该函数用来完成对所控设备的初始化工作,并调用
register_chrdev() 函数注册字符设备。假设有一字符设备 “exampledev”,则其 init函数为:
void exampledev_init(void)
{
if (register_chrdev(MAJOR_NUM, " exampledev ", &exampledev_fops))
TRACE_TXT("Device exampledev driver registered error");
else
TRACE_TXT("Device exampledev driver registered successfully");
…//设备初始化
}
其中, register_chrdev函数中的参数 MAJOR_NUM为主设备号 ,“exampledev”为设备名,
exampledev_fops为包含基本函数入口点的结构体,类型为 file_operations。当执行 exampledev_init时,
它将调用内核函数 register_chrdev,把驱动程序的基本入口点指针存放在内核的字符设备地址表中,在用户
进程对该设备执行系统调用时提供入口地址。
随着内核功能的加强, file_operations结构体也变得更加庞大。但是大多数的驱动程序只是利用了其中的一部
分,对于驱动程序中无需提供的功能,只需要把相应位置的值设为 NULL。对于字符设备来说,要提供的主要
入口有:open ()、release ()、read ()、write ()、ioctl ()等。
open()函数对设备特殊文件进行 open()系统调用时,将调用驱动程序的 open ()函数:
int (*open)(struct inode * inode,struct file *filp);
其中参数 inode为设备特殊文件的 inode (索引结点 ) 结构的指针,参数 filp是指向这一设备的文件结构的指
针。open()的主要任务是确定硬件处在就绪状态、验证次设备号的合法性 (次设备号可以用 MINOR(inode->
i_rdev)取得)、控制使用设备的进程数、根据执行情况返回状态码 (0表示成功,负数表示存在错误 ) 等;
release()函数当最后一个打开设备的用户进程执行 close ()系统调用时,内核将调用驱动程序的 release ()
函数:
void (*release) (struct inode * inode,struct file *filp) ;
release 函数的主要任务是清理未结束的输入 /输出操作、释放资源、用户自定义排他标志的复位等。
read()函数当对设备特殊文件进行 read()系统调用时,将调用驱动程序 read() 函数:
ssize_t (*read) (struct file * filp, char * buf, size_t count, loff_t * offp);
参数 buf是指向用户空间缓冲区的指针,由用户进程给出, count 为用户进程要求读取的字节数,也由用户给
出。
read()函数的功能就是从硬设备或内核内存中读取或复制 count个字节到 buf 指定的缓冲区中。在复制数据
时要注意,驱动程序运行在内核中,而 buf指定的缓冲区在用户内存区中,是不能直接在内核中访问使用的,
因此,必须使用特殊的复制函数来完成复制工作,这些函数在 include/asm/uaccess.h中被声明:
unsigned long copy_to_user (void * to, void * from, unsigned long len);
此外,put_user()函数用于内核空间和用户空间的单值交互(如 char、int、long)。
write( ) 函数当设备特殊文件进行 write () 系统调用时,将调用驱动程序的 write () 函数:
ssize_t (*write) (struct file *, const char *, size_t, loff_t *);
write ()的功能是将参数 buf 指定的缓冲区中的 count 个字节内容复制到硬件或内核内存中,和read()一样,
复制工作也需要由特殊函数来完成:
unsigned long copy_from_user(void *to, const void *from, unsigned long n);
此外,get_user()函数用于内核空间和用户空间的单值交互(如 char、int、long)。
ioctl() 函数该函数是特殊的控制函数,可以通过它向设备传递控制信息或从设备取得状态信息,函数原型
为:
int (*ioctl) (struct inode * inode,struct file * filp,unsigned int cmd,unsigned long arg);
参数 cmd为设备驱动程序要执行的命令的代码,由用户自定义,参数 arg 为相应的命令提供参数,类型可以
是整型、指针等。
同样,在驱动程序中,这些函数的定义也必须符合命名规则,按照本文约定,设备 “exampledev”的驱动程序
的这些函数应分别命名为 exampledev_open、 exampledev_ release、 exampledev_read、
exampledev_write、 exampledev_ioctl,因此设备 “exampledev”的基本入口点结构变量
exampledev_fops 赋值如下(对较早版本的内核):