免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 938 | 回复: 0
打印 上一主题 下一主题

一个字符设备驱动程序的实例 [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2009-07-18 23:26 |只看该作者 |倒序浏览

11.4.3 一个字符设备驱动程序的实例
下面我们通过一个实例对字符设备以及编写驱动程序的方法进行说明,通过下面的分析我们可以了解一个设备驱动程序的编写过程以及注意事项。虽然这个驱动程序没有什么实用价值,但是我们也可以通过它对一个驱动程序的编写特别是字符设备驱动程序有一定的认识。
一个设备驱动程序在结构上是非常相似的,在 Linux 中, 驱动程序一般用C语言编写,有时也支持一些汇编和C++语言。
1.头文件、宏定义和全局变量
一个典型的设备驱动程序一般都包含有一个专用头文件,这个头文件中包含一些系统函数的声明、设备寄存器的地址、寄存器状态位和控制位的定义以及用于此设备驱动程序的全局变量的定义,另外大多数驱动程序还使用一些标准的头文件:
param.h    包含一些内核参数。  
dir.h      包含一些目录参数。  
user.h      用户区域的定义 。
tty.h       终端和命令列表的定义。
fs.h        其中包括 Buffer header 信息。

下面是一些必要的头文件

#include   
#include  
#if CONFIG_MODVERSIONS==1  /* 处理 CONFIG_MODVERSIONS */
#define MODVERSIONS
#include
#endif        

/* 下面是针对字符设备的头文件 */
#include        
#include   

/* 对于不同的版本我们需要做一些必要的事情*/
#ifndef KERNEL_VERSION
#define KERNEL_VERSION(a,b,c) ((a)*65536+(b)*256+(c))
#endif
#if LINUX_VERSION_CODE > KERNEL_VERSION(2,4,0)
#include >  /* for copy_to_user */
#endif
   
#define SUCCESS 0

/* 声明设备 */
/* 这是本设备的名字,它将会出现在 /proc/devices */
#define DEVICE_NAME "char_dev"

/* 定义此设备消息缓冲的最大长度 */
#define BUF_LEN  100

/* 为了防止不同的进程在同一个时间使用此设备,定义此静态变量跟踪其状态 */
static int Device_Open = 0;

/* 当提出请求的时候,设备将读写的内容放在下面的数组中 */
static char Message[BUF_LEN];

/* 在进程读取这个内容的时候,这个指针是指向读取的位置*/
static char *Message_Ptr ;

/* 在这个文件中,主设备号作为全局变量以便于这个设备在注册和释放的时候使用。*/
static int Major;

2.open ()函数
功能:无论一个进程何时试图去打开这个设备都会调用这个函数。

static int device_open(struct inode *inode,
              struct file *file)
{
  static int counter = 0;

#ifdef DEBUG
  printk ("device_open(%p,%p)\n", inode, file);
#endif

  printk("Device: %d.%d\n",
    inode->i_rdev >> 8, inode->i_rdev & 0xFF);

/* 这个设备是一个独占设备,为了避免同时有两个进程使用这一个设备我们需要采取一定的措施*/
  if (Device_Open)
    return -EBUSY;

  Device_Open++;

/* 下面是初始化消息 , 注意不要使读写内容的长度超出缓冲区的长度,特别是运行在内核模式时,否则如果出现缓冲上溢则可能导致系统的崩溃。*/
  sprintf(Message,
"If I told you once, I told you %d times - %s",
   counter++,
    "Hello, world\n");

  Message_Ptr = Message;

/*当这个文件被打开的时候,我们必须确认该模块还没有被移走并且增加此模块的用户数目。(在移走一个模块的时候会根据这个数字去决定可否移去,如果不是 0 则表明还有进程正在使用这个模块,不能移走。)*/
  MOD_INC_USE_COUNT;   

  return SUCCESS;
}

3.release ( ) 函数
功能: 当一个进程试图关闭这个设备特殊文件的时候调用这个函数。

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,0)
static int device_release(struct inode *inode,
             struct file *file)
#else
static void device_release(struct inode *inode,
              struct file *file)
#endif
{
#ifdef DEBUG
  printk ("device_release(%p,%p)\n", inode, file);
#endif

  /* 为下一个使用这个设备的进程做准备。*/
  Device_Open --;

/* 减少这个模块使用者的数目,否则一旦你打开这个模块以后,你永远都不能释放掉它。*/
  MOD_DEC_USE_COUNT;

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,0)
  return 0;
#endif
}

 4.read ( ) 函数
功能:当一个进程已经打开此设备文件以后并且试图去读它的时候调用这个函数。
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,0)
static ssize_t device_read(struct file *file,
    char *buffer,    /* 把读出的数据放到这个缓冲区*/
    size_t length,   /* 缓冲区的长度*/
    loff_t *offset)  /* 文件中的偏移 */
#else
static int device_read(struct inode *inode,
                       struct file *file,
    char *buffer,       int length)
#endif
{
  /* 实际上读出的字节数 */
  int bytes_read = 0;
  /* 如果读到缓冲区的末尾,则返回0 ,类似文件的结束。*/
  if (*Message_Ptr == 0)
    return 0;
  /* 将数据放入缓冲区中*/
  while (length && *Message_Ptr)  {
/* 由于缓冲区是在用户空间而不是内核空间,所以我们必须使用 copu_to_user()函数将内核空间中的数据拷贝到用户空间。*/
    copy_to_user(buffer++,*(Message_Ptr++), length--);
    bytes_read ++;
  }

#ifdef DEBUG
   printk ("Read %d bytes, %d left\n",
     bytes_read, length);
#endif

   /* Read 函数返回一个真正读出的字节数*/
  return bytes_read;
}

5. write ( )  函数
功能:当试图将数据写入这个设备文件的时侯,这个函数被调用
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,0)
static ssize_t device_write(struct file *file,
    const char *buffer,   
    size_t length,   
    loff_t *offset)  
#else
static int device_write(struct inode *inode,
                        struct file *file,
                        const char *buffer,
                        int length)
#endif
{
  int i;

#ifdef DEBUG
  printk ("device_write(%p,%s,%d)", file, buffer, length);
#endif

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,0)
    copy_from_user(Message, buffer,length);

  Message_Ptr = Message;

  /* 返回写入的字节数 */
  return i;
}
6.这个设备驱动程序提供给文件系统的接口
当一个进程试图对我们生成的设备进行操作的时候就利用下面这个结构,这个结构就是我们提供给操作系统的接口,它的指针保存在设备表中,在init_module()中被传递给操作系统。

struct file_operations Fops = {
  read:    device_read,
  write:    device_write,
  open:     device_open,
  release: device_release
  };

7. 模块的初始化和模块的卸载
这个函数用来初始化这个模块 —注册该字符设备。init_module ()函数调用module_register_chrdev,把设备驱动程序添加到内核的字符设备驱动程序表中,它返回这个驱动程序所使用的主设备号。

int init_module()
{
  /* 试图注册设备*/
  Major = module_register_chrdev(0,
                                 DEVICE_NAME,
                                 &Fops);

  /* 失败的时候返回负值*/
  if (Major
    printk ("%s device failed with %d\n",
       "Sorry, registering the character",
       Major);
    return Major;
  }

  printk ("%s The major device number is %d.\n",
          "Registeration is a success.",
          Major);
  printk ("If you want to talk to the device driver,\n");
  printk ("you'll have to create a device file. \n");
  printk ("We suggest you use:\n");
  printk ("mknod  c %d \n", Major);
  printk ("You can try different minor numbers %s",
          "and see what happens.\n");

  return 0;
}


这个函数的功能是卸载模块,主要是从  /proc 中 取消注册的设备特殊文件。
void cleanup_module()
{
  int ret;

  /* 取消注册的设备*/
  ret = module_unregister_chrdev(Major, DEVICE_NAME);

  /* 如果出错则显示出错信息 */
  if (ret
    printk("Error in unregister_chrdev: %d\n", ret);
}


本文来自ChinaUnix博客,如果查看原文请点:http://blog.chinaunix.net/u3/92646/showart_2000260.html
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP