Linux设备驱动程序设计实例2007-03-03 23:09Linux系统中,设备驱动程序是操作系统内核的重要组成部分,在与硬件设备之间建立了标准的抽象接口。
通过这个接口,用户可以像处理普通文件一样,对硬件设备进行打开(open)、关闭(close)、读写(read/write)等操作。
通过分析和设计设备驱动程序,可以深入理解Linux系统和进行系统开发。
本文通过一个简单的例子来说明设备驱动程序的设计。
1、程序清单//MyDev.c 2000年2月7日编写#ifndef __KERNEL__#define __KERNEL__//按内核模块编译#endif#ifndef MODULE#define MODULE//设备驱动程序模块编译#endif#define DEVICE_NAME "MyDev"#define OPENSPK 1#define CLOSESPK 2//必要的头文件#include <linux/module.h>//同kernel.h,最基本的内核模块头文件#include <linux/kernel.h>//同module.h,最基本的内核模块头文件#include <linux/sched.h>//这里包含了进行正确性检查的宏#include <linux/fs.h> //文件系统所必需的头文件#include <asm/uaccess.h>//这里包含了内核空间与用户空间进行数据交换时的函数宏#include <asm/io.h>//I/O访问int my_major=0; //主设备号static int Device_Open=0;static char Message[]="This is from device driver";char *Message_Ptr;int my_open(struct inode *inode, struct file *file){//每当应用程序用open打开设备时,此函数被调用printk ("\ndevice_open(%p,%p)\n", inode, file);if (Device_Open)return -EBUSY;//同时只能由一个应用程序打开Device_Open++;MOD_INC_USE_COUNT;//设备打开期间禁止卸载return 0;}static void my_release(struct inode *inode, struct file *file){//每当应用程序用close关闭设备时,此函数被调用printk ("\ndevice_release(%p,%p)\n", inode, file);Device_Open --;MOD_DEC_USE_COUNT;//引用计数减1}ssize_t my_read (struct file *f,char *buf,int size,loff_t off){//每当应用程序用read访问设备时,此函数被调用int bytes_read=0;#ifdef DEBUGprintk("\nmy_read is called. User buffer is %p,size is %d\n",buf,size);#endifif (verify_area(VERIFY_WRITE,buf,size)==-EFAULT)return -EFAULT;Message_Ptr=Message;while(size && *Message_Ptr){if(put_user(*(Message_Ptr++),buf++))//写数据到用户空间return -EINVAL;size --;bytes_read++;}return bytes_read;}ssize_t my_write (struct file *f,const char *buf, int size,loff_t off){//每当应用程序用write访问设备时,此函数被调用int i;unsigned char uc;#ifdef DEBUGprintk("\nmy_write is called. User buffer is %p,size is %d\n",buf,size);#endifif (verify_area(VERIFY_WRITE,buf,size)==-EFAULT)return -EFAULT;printk("\nData below is from user program:\n");for (i=0;i<size;i++)if(!get_user(uc,buf++)) //从用户空间读数据printk("%02x ",uc);return size;}int my_ioctl(struct inode *inod,struct file *f,unsigned int arg1,unsigned int arg2){//每当应用程序用ioctl访问设备时,此函数被调用#ifdef DEBUGprintk("\nmy_ioctl is called. Parameter is %p,size is %d\n",arg1);#endifswitch (arg1){case OPENSPK:printk("\nNow,open PC's speaker.\n");outb(inb(0x61)|3,0x61); //打开计算机的扬声器break;case CLOSESPK:printk("\nNow,close PC's speaker.");outb(inb(0x61)&0xfc,0x61);//关闭计算机的扬声器break;}}struct file_operations my_fops = {NULL,/* lseek */my_read,my_write,NULL,NULL,my_ioctl,NULL,my_open,my_release,/* nothing more, fill with NULLs */};int init_module(void){//每当装配设备驱动程序时,系统自动调用此函数int result;result = register_chrdev(my_major,DEVICE_NAME,&my_fops);if (result < 0) return result;if (my_major == 0)my_major = result;printk("\nRegister Ok. major-number=%d\n",result);return 0;}void cleanup_module(void){//每当卸载设备驱动程序时,系统自动调用此函数printk("\nunload\n");unregister_chrdev(my_major, DEVICE_NAME);}2、设备驱动程序设计Linux设备分为字符设备、块设备和网络设备。
字符设备是不需要缓冲而直接读写的设备,如串口、键盘、鼠标等,本例就是字符设备驱动程序;块设备的访问通常需要缓冲来支持,以数据块为单位来读写,如磁盘设备等;网络设备是通过套接字来访问的特殊设备。
1) 设备驱动程序和内核与应用程序的接口无论哪种类型的设备,Linux都是通过在内核中维护特殊的设备控制块来与设备驱动程序接口的。
在字符设备和块设备的控制块中,有一个重要的数据结构file_operations,该结构中包含了驱动程序提供给应用程序访问硬件设备的各种方法,其定义如下(参见fs.h):struct file_operations {loff_t (*llseek) (struct file *, loff_t, int);//响应应用程序中lseek调用的函数指针ssize_t (*read) (struct file *, char *, size_t, loff_t *);//响应应用程序中read调用的函数指针ssize_t (*write) (struct file *, const char *, size_t, loff_t *);//响应应用程序中write调用的函数指针int (*readdir) (struct file *, void *, filldir_t); //响应应用程序中readdir调用的函数指针unsigned int (*poll) (struct file *, struct poll_table_struct *);//响应应用程序中select调用的函数指针int (*ioctl) (struct inode *, struct file *, unsigned int, unsignedlong);//响应应用程序中ioctl调用的函数指针int (*mmap) (struct file *, struct vm_area_struct *);//响应应用程序中mmap调用的函数指针int (*open) (struct inode *, struct file *);//响应应用程序中open调用的函数指针int (*flush) (struct file *);int (*release) (struct inode *, struct file *);//响应应用程序中close调用的函数指针int (*fsync) (struct file *, struct dentry *);int (*fasync) (int, struct file *, int);int (*check_media_change) (kdev_t dev);int (*revalidate) (kdev_t dev);int (*lock) (struct file *, int, struct file_lock *);};多数情况下,只需为上面结构中的少数方法编写服务函数,其他均设为NULL即可。
每一个可装配的设备驱动程序都必须有init_module和cleanup_module两个函数,装载和卸载设备时内核自动调用这两个函数。