Chinaunix首页 | 论坛 | 博客
  • 博客访问: 800556
  • 博文数量: 124
  • 博客积分: 1927
  • 博客等级: 上尉
  • 技术积分: 932
  • 用 户 组: 普通用户
  • 注册时间: 2010-08-31 14:06
文章分类

全部博文(124)

文章存档

2018年(5)

2017年(2)

2016年(6)

2015年(4)

2014年(24)

2013年(7)

2012年(11)

2011年(13)

2010年(52)

我的朋友

分类: LINUX

2010-09-11 16:14:59

Linux系统中,设备驱动程序是操作系统内核的重要组成部分,在 与硬件设备之间
建立了标准的抽象接口。通过这个接口,用户可以像处理普通文件一样,对硬件设
备进行打开(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     //同kernel.h,最基本的内核模块头文件
#include  //同module.h,最基本的内核模块头文件
#include  //这里包含了进行正确性检查的宏
#include    //文件系统所必需的头文件
#include  //这里包含了内核空间与用户空间进行数据交换时的
函数宏
#include    //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 DEBUG
  printk("\nmy_read is called. User buffer is %p,size is %d\n",buf,size);
#endif
if (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 DEBUG
  printk("\nmy_write is called. User buffer is %p,size is %d\n",buf,size);
#endif
if (verify_area(VERIFY_WRITE,buf,size)==-EFAULT)
return -EFAULT;
    printk("\nData below is from user program:\n");
    for (i=0;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 DEBUG
  printk("\nmy_ioctl is called. Parameter is %p,size is %d\n",arg1);
#endif
    switch (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, unsigned
long);
                                        //响应应用程序中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两个函数,
装载和卸载设备时内核自动调用这两个函数。在init_module中,除了可以对硬件
设备进行检查和初始化外,还必须调用register_* 函数将设备登记到系统中。本
例中是通过register_chrdev来登记的,如果是块设备或网络设备则应该用
register_blkdev和register_netdev来登记。Register_chrdev 的主要功能是将设
备名和结构file_operations登记到系统的设备控制块中。
2) 与应用程序的数据交换
由于设备驱动程序工作在内核存储空间,不能简单地用"="、"memcpy"等方法与应
用程序交换数据。在头文件uaccess.h中定义了方法put_user(x, ptr)和
get_user(x, ptr),用于内核空间与用户空间的数据交换。值x的类型根据指针
ptr的类型确定,请参见源代码中的my_read与my_write函数。
3) 与硬件设备的接口
Linux中为设备驱动程序访问I/O端口、硬件中断和DMA提供了简便方法,相应的头
文件分别为io.h、irq.h、dma.h。由于篇辐限制,本例中只涉及到I/O端口访问。
Linux提供的I/O端口访问方法主要有:inb()、inw()、outb()、outw()、inb_p()
、inw_p()、outb_p()、outw_p()等。要注意的是,设备驱动程序在使用端口前,
应该先用check_region()检查该端口的占用情况,如果指定的端口可用,则再用
request_region()向系统登记。说明check_region()、request_region()的头文件
是ioport.h。
4) 内存分配
设备驱动程序作为内核的一部分,不能使用虚拟内存,必须利用内核提供的
kmalloc()与kfree()来申请和释放内核存储空间。Kmalloc()带两个参数,第一个
要申请的是内存数量,在早期的版本中,这个数量必须是2的整数幂,如128、256
。关于kmalloc()与kfree()的用法,可参考内核源程序中的malloc.h与slab.c程序
3、程序的编译和访问
本例在Linux 2.2.x.x中可以成功编译和运行。先用下面的命令行进行编译:
gcc -Wall -O2 -c MyDev.c
该命令行中参数-Wall告诉编译程序显示警告信息;参数-O2是关于代码优化的设置
,注意内核模块必须优化;参数-c规定只进行编译和汇编,不进行连接。
正确编译后,形成MyDev.o文件。可以输入命令insmod MyDev.o来装载此程序。如
果装配成功,则显示信息:Register Ok.major-number=xx。利用命令lsmod可以看
到该模块被装配到系统中。
为了访问该模块,应该用命令mknode来创建设备文件。下面的应用程序可以对创建
的设备文件进行访问。
#include
#include
#include
#define DEVICE_NAME MyDev
#define OPENSPK 1
#define CLOSESPK 2
char buf[128];
int main(){
        int f=open(DEVICE_NAME,O_RDRW);
        if (f==-1) return 1;
        printf("\nHit enter key to read device...");
   read(f,buf,128); printf(buf);
printf("\nHit enter key to write device ...");
write(f,"test",4);
printf("\nHit enter key to open PC's speaker...");
ioctl(f,OPENSPK);
printf("\nHit enter key to close PC's speaker...");
ioctl(f,CLOSESPK);
        close(f);
        }
 

本文来自CSDN博客,转载请标明出处:Linux系统中,设备驱动程序是操作系统内核的重要组成部分,在 与硬件设备之间
建立了标准的抽象接口。通过这个接口,用户可以像处理普通文件一样,对硬件设
备进行打开(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     //同kernel.h,最基本的内核模块头文件
#include  //同module.h,最基本的内核模块头文件
#include  //这里包含了进行正确性检查的宏
#include    //文件系统所必需的头文件
#include  //这里包含了内核空间与用户空间进行数据交换时的
函数宏
#include    //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 DEBUG
  printk("\nmy_read is called. User buffer is %p,size is %d\n",buf,size);
#endif
if (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 DEBUG
  printk("\nmy_write is called. User buffer is %p,size is %d\n",buf,size);
#endif
if (verify_area(VERIFY_WRITE,buf,size)==-EFAULT)
return -EFAULT;
    printk("\nData below is from user program:\n");
    for (i=0;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 DEBUG
  printk("\nmy_ioctl is called. Parameter is %p,size is %d\n",arg1);
#endif
    switch (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, unsigned
long);
                                        //响应应用程序中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两个函数,
装载和卸载设备时内核自动调用这两个函数。在init_module中,除了可以对硬件
设备进行检查和初始化外,还必须调用register_* 函数将设备登记到系统中。本
例中是通过register_chrdev来登记的,如果是块设备或网络设备则应该用
register_blkdev和register_netdev来登记。Register_chrdev 的主要功能是将设
备名和结构file_operations登记到系统的设备控制块中。
2) 与应用程序的数据交换
由于设备驱动程序工作在内核存储空间,不能简单地用"="、"memcpy"等方法与应
用程序交换数据。在头文件uaccess.h中定义了方法put_user(x, ptr)和
get_user(x, ptr),用于内核空间与用户空间的数据交换。值x的类型根据指针
ptr的类型确定,请参见源代码中的my_read与my_write函数。
3) 与硬件设备的接口
Linux中为设备驱动程序访问I/O端口、硬件中断和DMA提供了简便方法,相应的头
文件分别为io.h、irq.h、dma.h。由于篇辐限制,本例中只涉及到I/O端口访问。
Linux提供的I/O端口访问方法主要有:inb()、inw()、outb()、outw()、inb_p()
、inw_p()、outb_p()、outw_p()等。要注意的是,设备驱动程序在使用端口前,
应该先用check_region()检查该端口的占用情况,如果指定的端口可用,则再用
request_region()向系统登记。说明check_region()、request_region()的头文件
是ioport.h。
4) 内存分配
设备驱动程序作为内核的一部分,不能使用虚拟内存,必须利用内核提供的
kmalloc()与kfree()来申请和释放内核存储空间。Kmalloc()带两个参数,第一个
要申请的是内存数量,在早期的版本中,这个数量必须是2的整数幂,如128、256
。关于kmalloc()与kfree()的用法,可参考内核源程序中的malloc.h与slab.c程序
3、程序的编译和访问
本例在Linux 2.2.x.x中可以成功编译和运行。先用下面的命令行进行编译:
gcc -Wall -O2 -c MyDev.c
该命令行中参数-Wall告诉编译程序显示警告信息;参数-O2是关于代码优化的设置
,注意内核模块必须优化;参数-c规定只进行编译和汇编,不进行连接。
正确编译后,形成MyDev.o文件。可以输入命令insmod MyDev.o来装载此程序。如
果装配成功,则显示信息:Register Ok.major-number=xx。利用命令lsmod可以看
到该模块被装配到系统中。
为了访问该模块,应该用命令mknode来创建设备文件。下面的应用程序可以对创建
的设备文件进行访问。
#include
#include
#include
#define DEVICE_NAME MyDev
#define OPENSPK 1
#define CLOSESPK 2
char buf[128];
int main(){
        int f=open(DEVICE_NAME,O_RDRW);
        if (f==-1) return 1;
        printf("\nHit enter key to read device...");
   read(f,buf,128); printf(buf);
printf("\nHit enter key to write device ...");
write(f,"test",4);
printf("\nHit enter key to open PC's speaker...");
ioctl(f,OPENSPK);
printf("\nHit enter key to close PC's speaker...");
ioctl(f,CLOSESPK);
        close(f);
        }
 

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/imho888/archive/2009/01/13/3766731.aspx
阅读(968) | 评论(1) | 转发(0) |
给主人留下些什么吧!~~

chinaunix网友2010-09-13 22:17:21

很好的, 收藏了 推荐一个博客,提供很多免费软件编程电子书下载: http://free-ebooks.appspot.com