Board logo

标题: 驱动入门:一个简单的字符设备驱动(2) [打印本页]

作者: yuyang911220    时间: 2017-2-20 17:41     标题: 驱动入门:一个简单的字符设备驱动(2)

第三步、分别实现file_operations里的每个函数。
/*文件打开函数*/
int globalmem_open(struct inode *inode, struct file *filp)
{
  return 0;  /*这里只是为了讲解大概的流程,不做其他的工作,直接返回*/
}
/*文件释放函数*/
int globalmem_release(struct inode *inode, struct file *filp)
{
  return 0;  /*这里只是为了讲解大概的流程,不做其他的工作,直接返回*/
}
/*读函数,读取其中的内容并返回读取的大小*/
static ssize_t globalmem_read(struct file *filp, char __user *buf, size_t size,
  loff_t *ppos)
{
  unsigned long p =  *ppos;  /*当前指针所在的位置*/
  unsigned int count = size;   /*要读取的大小*/
  int ret = 0;              
  /*分析和获取有效的写长度*/
  if (p >= GLOBALMEM_SIZE)  /*如果当前指针已经到设备最尾端*/
    return count ?  - ENXIO: 0;  /*要读取的大小不为0,则返回出错信息*/
  if (count > GLOBALMEM_SIZE - p) /*如果还有可读取的数据不够conut大小*/
    count = GLOBALMEM_SIZE - p; /*返回conut个数据*/
/*内核空间->用户空间*/
  if (copy_to_user(buf, (void*)(dev->mem + p), count)) /*用户的地址空间和内核空间的不能直接传输数据,必须通过copy_to_user  copy_from_user来在两个地址空间中传递数据 */
  {
    ret =  - EFAULT;
  }
  else
  {
    *ppos += count;
    ret = count;
   
    printk(KERN_INFO "read %d bytes(s) from %d\n", count, p);
  }
  return ret;
}
/*写函数*/
static ssize_t globalmem_write(struct file *filp, const char __user *buf,
  size_t size, loff_t *ppos)
{
  unsigned long p =  *ppos;
  unsigned int count = size;
  int ret = 0;
  /*分析和获取有效的写长度*/
  if (p >= GLOBALMEM_SIZE)  /*没有可写的空间了*/
    return count ?  - ENXIO: 0;  /*如果要写非零个数据,则返回出错信息*/
  if (count > GLOBALMEM_SIZE - p)
    count = GLOBALMEM_SIZE - p;
   
  /*用户空间->内核空间*/
  if (copy_from_user(dev->mem + p, buf, count))
    ret =  - EFAULT;
  else
  {
    *ppos += count;
    ret = count;
   
    printk(KERN_INFO "written %d bytes(s) from %d\n", count, p);
  }
  return ret;
}
/* seek文件定位函数 */
static loff_t globalmem_llseek(struct file *filp, loff_t offset, int orig)
{
  loff_t ret = 0;
  switch (orig)
  {
    case 0:   /*相对文件开始位置偏移*/
      if (offset < 0)
      {
        ret =  - EINVAL;
        break;
      }
      if ((unsigned int)offset > GLOBALMEM_SIZE)
      {
        ret =  - EINVAL;
        break;
      }
      filp->f_pos = (unsigned int)offset;
      ret = filp->f_pos;
      break;
    case 1:   /*相对文件当前位置偏移*/
      if ((filp->f_pos + offset) > GLOBALMEM_SIZE)
      {
        ret =  - EINVAL;
        break;
      }
      if ((filp->f_pos + offset) < 0)
      {
        ret =  - EINVAL;
        break;
      }
      filp->f_pos += offset;
      ret = filp->f_pos;
      break;
    default:
      ret =  - EINVAL;
      break;
  }
  return ret;
}
/* ioctl设备控制函数 ,这里只是实现一个清空该内存的命令*/
static int globalmem_ioctl(struct inode *inodep, struct file *filp, unsigned
  int cmd, unsigned long arg)
{
  switch (cmd)
  {
    case MEM_CLEAR:
      memset(dev->mem, 0, GLOBALMEM_SIZE);      
      printk(KERN_INFO "globalmem is set to zero\n");
      break;
    default:
      return  - EINVAL;
  }
  return 0;
}
第四步、实现注册和卸载函数
到这里我们看似已经实现设备的功能了,但是应用程序还不能够使用它,因为还没有注册该设备,所以我们要把这个设备加载进内核,相对应的当我们不需要该设备了的时候就应该把它从内核卸载掉。在模块加载函数中完成的功能主要有:申请设备号、注册cdev设备结构体。相应的卸载函数中就应该卸载cdev设备结构体,释放设备号。
/*初始化并注册cdev*/
static void globalmem_setup_cdev(struct globalmem_dev *dev, int index)
{
  int err, devno = MKDEV(globalmem_major, index);
  cdev_init(&dev->cdev, &globalmem_fops);
  dev->cdev.owner = THIS_MODULE;
  dev->cdev.ops = &globalmem_fops;
  err = cdev_add(&dev->cdev, devno, 1);
  if (err)
    printk(KERN_NOTICE "Error %d adding LED%d", err, index);
}
/*设备驱动模块加载函数*/
int globalmem_init(void)
{
  int result;
  dev_t devno = MKDEV(globalmem_major, 0);
  /* 申请设备号*/
  if (globalmem_major)
    result = register_chrdev_region(devno, 1, "globalmem");
  else  /* 动态申请设备号 */
  {
    result = alloc_chrdev_region(&devno, 0, 1, "globalmem");
    globalmem_major = MAJOR(devno);
  }  
  if (result < 0)
    return result;
   
  /* 动态申请设备结构体的内存*/
  globalmem_devp = kmalloc(sizeof(struct globalmem_dev), GFP_KERNEL);
  if (!globalmem_devp)    /*申请失败*/
  {
    result =  - ENOMEM;
    goto fail_malloc;
  }
  memset(globalmem_devp, 0, sizeof(struct globalmem_dev));
  
  globalmem_setup_cdev(globalmem_devp, 0);
  return 0;
  fail_malloc: unregister_chrdev_region(devno, 1);
  return result;
}
/*模块卸载函数*/
void globalmem_exit(void)
{
  cdev_del(&globalmem_devp->cdev);   /*注销cdev*/
  kfree(globalmem_devp);     /*释放设备结构体内存*/
  unregister_chrdev_region(MKDEV(globalmem_major, 0), 1); /*释放设备号*/
}
第五步、声明一些必要的信息。
我们看看前面的加载和卸载函数,它们与另外的几个函数并没有什么特别之处,当我们加载驱动的时候,内核怎么知道要调用globalmem_init()函数,在卸载驱动时怎么知道调用globalmem_exit()呢?所以我们应该向内核指示它们就是入口和出口函数,这就宏module_init()和module_exit()的作用。
module_init(globalmem_init);
module_exit(globalmem_exit);
除此之外我们还必须声明我们的驱动遵循的license,不然会报错。
MODULE_LICENSE("Dual BSD/GPL");
到这里我们的这个简单的设备就写好了。




欢迎光临 电子技术论坛_中国专业的电子工程师学习交流社区-中电网技术论坛 (http://bbs.eccn.com/) Powered by Discuz! 7.0.0