当前位置:   article > 正文

字符设备驱动框架

字符设备驱动框架

初始化/移除字符设备
Linux内核提供了两种方式来定义字符设备,如下所示:

//第一种方式:常见的变量定义
static struct cdev chrdev;
//第二种方式:内核提供的动态分配方式
struct cdev *cdev_alloc(void);
  • 1
  • 2
  • 3
  • 4

从内核中移除某个字符设备,则需要调用cdev_del函数,如下所示:

void cdev_del(struct cdev *p)

    分配/注销设备号
    Linux的各种设备都以文件的形式存放在/dev目录下,为了管理这些设备,系统为各个设备进行编号,每个设备号又分为主设备号和次设备号。主设备号用来区分不同种类的设备,如USB,tty等,次设备号用来区分同一类型的多个设备。我们可以通过命令”cat /proc/devices”查询内核分配的主设备号。
    内核提供了一种数据类型:dev_t,用于记录设备编号,该数据类型实际上是一个无符号32位整型,其中的12位用于表示主设备号,剩余的20位则用于表示次设备号。
    Linux内核为我们提供了生成设备号的宏定义MKDEV,用于将主设备号和次设备号合成一个设备号。除此之外,内核还提供了另外两个宏定义MAJOR和MINOR,可以根据设备的设备号来获取设备的主设备号和次设备号。

    #define MINORBITS 20
    #define MINORMASK ((1U << MINORBITS) - 1)
    #define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))	//通过设备号获取主设备号
    #define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))//过设备号获取次设备号
    #define MKDEV(ma,mi) (((ma) << MINORBITS) \| (mi))//通过主设备号和次设备号合成设备号
    • 1
    • 2
    • 3
    • 4

    创建一个新的字符设备之前,我们需要为新的字符设备注册一个新的设备号。内核提供了三种方式,来完成这项工作。

    (1) register_chrdev函数
    register_chrdev函数用于分配设备号。该函数是一个内联函数,它不仅支持静态申请设备号,也支持动态申请设备号,并将主设备号返回,函数原型如下所示。

    static inline int register_chrdev(unsigned int major, const char *name,
    const struct file_operations *fops)
    {
    return __register_chrdev(major, 0, 256, name, fops);
    }
    • 1
    • 2
    • 3
    • 4
    • major:用于指定要申请的字符设备的主设备号,等价于register_chrdev_region函数,当设置为0时,内核会自动分配一个未使用的主设备号。
    • name:用于指定字符设备的名称
    • fops:用于操作该设备的函数接口指针。

    使用register_chrdev函数向内核申请设备号,同一类字符设备(即主设备号相同),会在内核中申请了256个,通常情况下,我们不需要用到这么多个设备,这就造成了极大的资源浪费。

    (2)register_chrdev_region函数
    register_chrdev_region函数用于静态地为字符设备申请一个或多个设备编号。该函数在分配成功时,会返回0;失败则会返回相应的错误码,函数原型如下所示。

    int register_chrdev_region(dev_t from, unsigned count, const char *name)
      • from:dev_t类型的变量,用于指定字符设备的起始设备号,如果要注册的设备号已经被其他的设备注册了,那么就会导致注册失败。
      • count:指定要申请的设备号个数,count的值不可以太大,否则会与下一个主设备号重叠。
      • name:用于指定该设备的名称,我们可以在/proc/devices中看到该设备。

      (3)alloc_chrdev_region函数(推荐使用)
      使用register_chrdev_region函数时,需要编程者自行确定未使用的设备号, 并不方便。因此,内核又为我们提供了一种能够动态分配设备编号的方式:alloc_chrdev_region。
      调用alloc_chrdev_region函数,内核会自动分配给我们一个尚未使用的主设备号。我
      们可以通过命令”cat /proc/devices”查询内核分配的主设备号。

      int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)
        • dev:指向dev_t类型数据的指针变量,用于存放分配到的设备编号的起始值;
        • baseminor:次设备号的起始值,通常情况下,设置为0;
        • count、name:同register_chrdev_region类型,用于指定需要分配的设备编号的个数以及设备的名称。

        注销设备号:
        当我们删除字符设备时候,我们需要把分配的设备编号交还给内核.
        (1)unregister_chrdev函数
        使用register函数申请的设备号,则应该使用unregister_chrdev函数进行注销。

        static inline void unregister_chrdev(unsigned int major, const char *name)
        {
        __unregister_chrdev(major, 0, 256, name);
        }
        
        • 1
        • 2
        • 3
        • 4
        • major:指定需要释放的字符设备的主设备号,一般使用register_chrdev函数的返回值作为实参。
        • name:执行需要释放的字符设备的名称。

        (2)unregister_chrdev_region函数
        对于使用register_chrdev_region函数以及alloc_chrdev_region函数分配得到的设备编号,可以使用unregister_chrdev_region函数进行注销。

        void unregister_chrdev_region(dev_t from, unsigned count)
          • from:指定需要注销的字符设备的设备编号起始值,我们一般将定义的dev_t变量作为实参。
          • count:指定需要注销的字符设备编号的个数,该值应与申请函数的count值相等,通常采用宏定义进行管理。

          关联设备的操作方式
          编写一个字符设备最重要的事情,就是要实现file_operations这个结构体中的函数。实现之后,如何将该结构体与我们的字符设备结构相关联呢?内核提供了cdev_init函数,来实现这个工作。

          void cdev_init(struct cdev *cdev, const struct file_operations *fops)
            • cdev:struct cdev类型的指针变量,指向需要关联的字符设备结构体;
            • fops:file_operations类型的结构体指针变量,一般将实现操作该设备的结构体file_operations结构体作为实参。

            注册设备
            cdev_add函数用于向内核的cdev_map散列表添加一个新的字符设备,如下所示

            int cdev_add(struct cdev *p, dev_t dev, unsigned count)
              • p:struct cdev类型的指针,用于指定需要添加的字符设备;
              • dev:dev_t类型变量,用于指定设备的起始编号;
              • count:指定注册多少个设备。

              字符设备驱动框架

              #include <linux/init.h>
              #include <linux/module.h>
              #include <linux/fs.h>
              #include <linux/cdev.h>
              #include <linux/uaccess.h>
              #define DEV_NAME            "EmbedCharDev"
              #define DEV_CNT                 (1)
              #define BUFF_SIZE               128
              
              //定义字符设备的设备号
              static dev_t devno;
              //定义字符设备结构体chr_dev
              static struct cdev chr_dev;
              //数据缓冲区
              static char vbuf[BUFF_SIZE];
              
              static int chr_dev_open(struct inode *inode, struct file *filp);
              static int chr_dev_release(struct inode *inode, struct file *filp);
              static ssize_t chr_dev_write(struct file *filp, const char __user * buf, size_t count, loff_t *ppos);
              static ssize_t chr_dev_read(struct file *filp, char __user * buf, size_t count, loff_t *ppos);
              
              //定义初始化file_operation结构体
              static struct file_operations  chr_dev_fops = 
              {
                  .owner = THIS_MODULE,
                  .open = chr_dev_open,
                  .release = chr_dev_release,
                  .write = chr_dev_write,
                  .read = chr_dev_read,
              }
              
              static int chr_dev_open(struct inode *inode, struct file *filp)
              {
                  printk("\nopen\n");
                  return 0;
              }
              
              static int chr_dev_release(struct inode *inode, struct file *filp)
              {
                  printk("\nrelease\n");
                  return 0;
              }
              
              static ssize_t chr_dev_write(struct file *filp, const char __user * buf, size_t count, loff_t *ppos)
              {
                   unsigned long p = *ppos;
                   int ret;
                   int tmp = count ;
                   if(p > BUFF_SIZE)
                      return 0;
                   if(tmp > BUFF_SIZE - p)
                      tmp = BUFF_SIZE - p;
                   //读取用户空间的数据存入内核数据缓冲区
                   ret = copy_from_user(vbuf, buf, tmp);
                   *ppos += tmp;
                    return tmp;
              }
              
              static ssize_t chr_dev_read(struct file *filp, char __user * buf, size_t count, loff_t *ppos)
              {
                  unsigned long p = *ppos;
                  int ret;
                  int tmp = count ;
                  static int i = 0;
                  i++;
                  if(p >= BUFF_SIZE)
                       return 0;
                  if(tmp > BUFF_SIZE - p)
                       tmp = BUFF_SIZE - p;
                  //将内核数据缓冲区的中的数据发送给用户空间
                  ret = copy_to_user(buf, vbuf+p, tmp);
                  *ppos +=tmp;
                  return tmp;
              }
              
              //入口函数
              static int __init chrdev_init(void)
              {
                  int ret = 0;
                  printk("chrdev init\n");
                  //第一步
                  //采用动态分配的方式,获取设备编号,次设备号为0,
                  //设备名称为EmbedCharDev,可通过命令cat  /proc/devices查看
                  //DEV_CNT为1,当前只申请一个设备编号
                  ret = alloc_chrdev_region(&devno, 0, DEV_CNT, DEV_NAME);
                  if(ret < 0)
                  {
                      printk("fail to alloc devno\n");
                      goto alloc_err;
                  }
                  
                  //第二步
                  //关联字符设备结构体cdev与文件操作结构体file_operations
                  cdev_init(&chr_dev, &chr_dev_fops);
              
                  //第三步
                  //添加设备至cdev_map散列表中
                  ret = cdev_add(&chr_dev, devno, DEV_CNT);
                  if(ret < 0)
                  {
                      printk("fail to add cdev\n");
                      goto add_err;
                  }
                  return 0;
                  
              add_err:
                  //添加设备失败时,需要注销设备号
                  unregister_chrdev_region(devno, DEV_CNT);
              alloc_err:
                  return ret;
              }
              //出口函数
              static void __exit chrdev_exit(void)
              {
                  printk("chrdev exit\n");
                  unregister_chrdev_region(devno, DEV_CNT);
                  cdev_del(&chr_dev);
              }
              module_init(chrdev_init);
              module_exit(chrdev_exit);
              MODULE_LICENSE("GPL");
              • 1
              • 2
              • 3
              • 4
              • 5
              • 6
              • 7
              • 8
              • 9
              • 10
              • 11
              • 12
              • 13
              • 14
              • 15
              • 16
              • 17
              • 18
              • 19
              • 20
              • 21
              • 22
              • 23
              • 24
              • 25
              • 26
              • 27
              • 28
              • 29
              • 30
              • 31
              • 32
              • 33
              • 34
              • 35
              • 36
              • 37
              • 38
              • 39
              • 40
              • 41
              • 42
              • 43
              • 44
              • 45
              • 46
              • 47
              • 48
              • 49
              • 50
              • 51
              • 52
              • 53
              • 54
              • 55
              • 56
              • 57
              • 58
              • 59
              • 60
              • 61
              • 62
              • 63
              • 64
              • 65
              • 66
              • 67
              • 68
              • 69
              • 70
              • 71
              • 72
              • 73
              • 74
              • 75
              • 76
              • 77
              • 78
              • 79
              • 80
              • 81
              • 82
              • 83
              • 84
              • 85
              • 86
              • 87
              • 88
              • 89
              • 90
              • 91
              • 92
              • 93
              • 94
              • 95
              • 96
              • 97
              • 98
              • 99
              • 100
              • 101
              • 102
              • 103
              • 104
              • 105
              • 106
              • 107
              • 108
              • 109
              • 110
              • 111
              • 112
              • 113
              • 114
              • 115
              • 116
              • 117
              • 118
              • 119
              • 120

              改进:一个驱动程序驱动多个不同次设备号的设备文件

              ...
              //申请两个次设备号
              #define DEV_CNT                 (2)
              ...
              /*虚拟字符设备*/
              struct chr_dev {
              	struct cdev dev;
              	char vbuf[BUFF_SIZE];
              };
              //字符设备1
              static struct chr_dev vcdev1;
              //字符设备2
              static struct chr_dev vcdev2;
              • 1
              • 2
              • 3
              • 4
              • 5
              • 6
              • 7
              • 8
              • 9
              • 10
              • 11
              • 12

              入口函数修改:

              static int __init chrdev_init(void)
              {
              	int ret;
              	dev_t cur_dev;
              	printk("chrdev init\n");
              	ret = alloc_chrdev_region(&devno, 0, DEV_CNT, DEV_NAME);
              	if (ret < 0)
              		goto alloc_err;
              	//关联第一个设备:vdev1
              	cdev_init(&vcdev1.dev, &chr_dev_fops);
              	cur_dev = MKDEV(MAJOR(devno), MINOR(devno) + 0);
              	ret = cdev_add(&vcdev1.dev, cur_dev, 1);
              	if (ret < 0) {
              		printk("fail to add vcdev1 ");
              		goto add_err1;
              	}
              	//关联第二个设备:vdev2	
              	cdev_init(&vcdev2.dev, &chr_dev_fops);
              	cur_dev = MKDEV(MAJOR(devno), MINOR(devno) + 1);
              	ret = cdev_add(&vcdev2.dev, cur_dev, 1);
              	 if (ret < 0) {
              	 	printk("fail to add vcdev2 ");
               		goto add_err2;
               	}
               	return 0;
              add_err2:
              	cdev_del(&(vcdev1.dev));
              add_err1:
              	unregister_chrdev_region(devno, DEV_CNT);
              alloc_err:
              	return ret;
              }
              • 1
              • 2
              • 3
              • 4
              • 5
              • 6
              • 7
              • 8
              • 9
              • 10
              • 11
              • 12
              • 13
              • 14
              • 15
              • 16
              • 17
              • 18
              • 19
              • 20
              • 21
              • 22
              • 23
              • 24
              • 25
              • 26
              • 27
              • 28
              • 29
              • 30
              • 31

              出口函数修改:

              static void __exit chrdev_exit(void)
              {
              	printk("chrdev exit\n");
              	unregister_chrdev_region(devno, DEV_CNT);
              	cdev_del(&(vcdev1.dev));
              	cdev_del(&(vcdev2.dev));
              }
              • 1
              • 2
              • 3
              • 4
              • 5
              • 6

              open函数修改:
              文件节点inode中的成员i_cdev:为了方便访问设备文件,在打开文件过程中,内核将对应的字符设备结构体cdev自动保存到文件节点inode中的成员变量i_cdev中。
              Linux提供了一个宏定义container_of,该宏可以根据结构体的某个成员的地址,来得到该结构体的地址。该宏需要三个参数,分别是代表结构体成员的真实地址,结构体的类型以及结构体成员的名字。
              在chr_dev_open函数中,我们需要通过inode的i_cdev成员,来得到对应的虚拟设备结构体,并保存到文件指针filp的私有数据成员中。假如,我们打开虚拟设备1,那么inode->i_cdev便指向了vcdev1的成员dev,利用container_of宏,我们就可以得到vcdev1结构体的地址,也就可以操作对应的数据缓冲区了。

              static int chr_dev_open(struct inode *inode, struct file *filp)
              {
              	printk("open\n");
              	filp->private_data = container_of(inode->i_cdev, struct chr_dev, dev);
              	return 0;
              }
              • 1
              • 2
              • 3
              • 4
              • 5

              write函数的修改:
              通过filp->private_data获取到打开的字符设备所在的虚拟设备结构体变量的指针,同过该指针即可访问该虚拟设备的数据缓冲区vbuf。

              static ssize_t chr_dev_write(struct file *filp, const char __user * buf, size_t count, loff_t *ppos)
              {
              	unsigned long p = *ppos;
              	int ret;
              	//获取文件的私有数据
              	struct chr_dev *dev = filp->private_data;
              	char *vbuf = dev->vbuf;
              	int tmp = count ;
              	if (p > BUFF_SIZE)
              		return 0;
              	if (tmp > BUFF_SIZE - p)
              		tmp = BUFF_SIZE - p;
              	ret = copy_from_user(vbuf, buf, tmp);
              	*ppos += tmp;
              	return tmp;
              }
              • 1
              • 2
              • 3
              • 4
              • 5
              • 6
              • 7
              • 8
              • 9
              • 10
              • 11
              • 12
              • 13
              • 14
              • 15

              read函数的修改:
              与write函数的修改基本一致。

              static ssize_t chr_dev_read(struct file *filp, char __user * buf, size_t count, loff_t *ppos)
              {
              	unsigned long p = *ppos;
              	int ret;
              	int tmp = count ;
              	//获取文件的私有数据
              	struct chr_dev *dev = filp->private_data;
              	char *vbuf = dev->vbuf;
              	if (p >= BUFF_SIZE)
              		return 0;
              	if (tmp > BUFF_SIZE - p)
              		tmp = BUFF_SIZE - p;
              	ret = copy_to_user(buf, vbuf+p, tmp);
              	*ppos +=tmp;
              	return tmp;
              }
              • 1
              • 2
              • 3
              • 4
              • 5
              • 6
              • 7
              • 8
              • 9
              • 10
              • 11
              • 12
              • 13
              • 14
              • 15
              声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/不正经/article/detail/180456
              推荐阅读
              相关标签
                

              闽ICP备14008679号