微波EDA网,见证研发工程师的成长!
首页 > 研发问答 > 嵌入式设计讨论 > 嵌入式系统设计讨论 > 驱动入门:一个简单的字符设备驱动

驱动入门:一个简单的字符设备驱动

时间:10-02 整理:3721RD 点击:

驱动入门:一个简单的字符设备驱动

          首先我要向大家推荐一下韦东山老师的视频,他在视频里讲解的非常的好,把代码分析的非常的透彻,而且他会在视频里现场写出每一个程序的代码,而不是从其他的地方拿一个程序过来分析就完事,所以我们可以跟着他一步一步的学习linux程序设计。

我就按照这几天在视频里从韦老师那儿学到的方法,讲一下写简单字符设备的流程,以在书上看到的globalmem这样的一个虚拟设备为例。这个设备的功能是在内核空间里分配4K字节的内存,在驱动中提供如何访问和操作这块内存的函数,以供用户空间的进程通过系统调用来访问这块内存。我们可以把它看成是最大容量为4K的普通文件。我们要能够像打开普通文件一样打开它,读取其中的内容,或向其中写入数据,定位到文件的某个位置处,清空其中的内容,然后关闭打开的文件。我们的应用程序只需要通过系统调用open()、read()、write()、lseek()、ioctl()、release()。而不用去管它是一个普通文件还是一个字符设备。

第一步、包含进文件中所需的头文件,和宏定义,声明全局变量,并定义一个设备结构体。

#include <linux/module.h>

#include <linux/types.h>

#include <linux/fs.h>

#include <linux/errno.h>

#include <linux/mm.h>

#include <linux/sched.h>

#include <linux/init.h>

#include <linux/cdev.h>

#include <asm/io.h>

#include <asm/system.h>

#include <asm/uaccess.h>

#define GLOBALMEM_SIZE  0x1000  /*全局内存最大4K字节*/

#define MEM_CLEAR 0x1  /*清0全局内存*/

#define GLOBALMEM_MAJOR 254    /*预设的globalmem的主设备号*/

static int globalmem_major = GLOBALMEM_MAJOR;

/*globalmem设备结构体*/

struct globalmem_dev                                    

{                                                        

  struct cdev cdev; /*cdev结构体*/                       

  unsigned char mem[GLOBALMEM_SIZE]; /*全局内存*/        

};

第二步、写出驱动框架。

static const struct file_operations globalmem_fops =

{

  .owner = THIS_MODULE,

  .llseek = globalmem_llseek,

  .read = globalmem_read,

  .write = globalmem_write,

  .ioctl = globalmem_ioctl,

  .open = globalmem_open,

  .release = globalmem_release,

};

file_operations结构体里面有很多的函数,但并非要实现其中所有的成员函数。要根据实际的需要向file_operations里添加成员函数,这里实现6个函数。

第三步、分别实现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");

到这里我们的这个简单的设备就写好了。

驱动这么复杂啊

太难了啊

确实要下功夫~

唉!我就一个学嵌入式的,学的ARM7,数电,模电,LINUX,就是找不到对口的嵌入式工作,在学校学的也算很努力了,天天加班看书做实验,写代码,最后居然没人要,要我的就2K到3K,现在都不知道还要不要在去坚持,都快毕业了,花了我三年去努力的学,最后尽比不上民工,想想有点后悔,当初大学四年还不如去混,去玩

这以前我也写过,唉,就快换行了,伤感

学习下~!

得坚持学习

学习学习!

驱动这么复杂啊

学习一下再说!

先学着吧啊,再看看

这位大哥(大姐),刚毕业都这样的嘛,尤其是嵌入式的,是个学习的过程,虽然我没毕业呢,但是我也知道刚毕业的都是要学习的,等你学好了公司不给你涨工资也不行了。不涨就跳槽

谢谢小编分享了

Copyright © 2017-2020 微波EDA网 版权所有

网站地图

Top