[关闭]
@zwh8800 2017-08-23T02:01:16.000000Z 字数 9665 阅读 190453

linux设备驱动程序(3) – 字符设备驱动(设备号 注册设备)

blog 归档 linux 驱动开发


这次我们学习最简单的一种设备, 字符设备驱动的开发. 最终写出一个字符设备, 用户可以进行打开和关闭, 并向他写入数据, 它会始终保存着最后一次写入的数据, 对它进行读取会读出最后一次写入的数据.


1.设备号

在linux中执行ls -l命令, 在日期之前可以看到两个用逗号隔开的数, 这个便是设备号, 逗号之前的是主设备号(major)后面的是次设备号(minor).

在内核中, 设备号使用dev_t来表示(<linux/types.h>). dev_t可以同时保存主设备号和次设备号, 当需要从dev_t中获取主设备号或次设备号时, 可以使用以下:

  1. MAJOR(dev_t dev);
  2. MINOR(dev_t dev);

相反, 如果需要用设备号构造出dev_t则使用:

  1. MKDEV(int major, int minor);

2.分配设备号

建立字符设备前, 驱动程序首先应该分配设备号, 有三个函数用来分配(注册)设备号和释放设备号(在<linux/fs.h>中):

  1. int register_chrdev_region(dev_t first, unsigned int count,
  2. char *name);
  3. int alloc_chrdev_region(dev_t *dev, unsigned int firstminor,
  4. unsigned int count, char *name);
  5. void unregister_chrdev_region(dev_t first, unsigned int count);

register_chrdev_region函数用在已知设备号的情况下向内核进行注册, alloc用在设备号不确定的情况下, 向内核动态分配设备号. first是申请的设备号的第一个, count是要连续申请的个数(次设备号的个数, 比如first是[10, 102], count是4, 则会申请[10,102][10,103][10,104][10,105]这四个). name是设备的名称, 将出现在/proc/devices和sysfs中. 如果出错返回负的错误号.

alloc函数成功后通过dev返回第一个设备号.

例子:

  1. DEBUG_LOG("", "allocating device number\n");
  2. /* 申请设备号 */
  3. if (chr_major != 0) /* 如果用户提供了设备号 */
  4. {
  5. dev = MKDEV(chr_major, chr_minor);
  6. ret = register_chrdev_region(dev, 1, DEV_NAME);
  7. }
  8. else
  9. {
  10. ret = alloc_chrdev_region(&dev, chr_minor, 1, DEV_NAME);
  11. chr_major = MAJOR(dev);
  12. }
  13. if (ret < 0)
  14. {
  15. DEBUG_LOG(KERN_WARNING, "cannot get major%d\n", chr_major);
  16. goto err;
  17. }
  18. DEBUG_LOG("", "success\n");
  19. DEBUG_LOG("", "chr_major=%d, chr_minor=%d\n", chr_major, chr_minor);

3.重要的数据结构

关于字符设备驱动, 有三个重要的数据结构, 他们都在<linux/fs.h>中.分别是:

  1. struct file_operations {
  2. struct module *owner;
  3. loff_t (*llseek) (struct file *, loff_t, int);
  4. ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
  5. ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
  6. ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
  7. ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
  8. int (*iterate) (struct file *, struct dir_context *);
  9. unsigned int (*poll) (struct file *, struct poll_table_struct *);
  10. long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
  11. long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
  12. int (*mmap) (struct file *, struct vm_area_struct *);
  13. int (*open) (struct inode *, struct file *);
  14. int (*flush) (struct file *, fl_owner_t id);
  15. int (*release) (struct inode *, struct file *);
  16. int (*fsync) (struct file *, loff_t, loff_t, int datasync);
  17. int (*aio_fsync) (struct kiocb *, int datasync);
  18. int (*fasync) (int, struct file *, int);
  19. int (*lock) (struct file *, int, struct file_lock *);
  20. ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
  21. unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
  22. int (*check_flags)(int);
  23. int (*flock) (struct file *, int, struct file_lock *);
  24. ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
  25. ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
  26. int (*setlease)(struct file *, long, struct file_lock **);
  27. long (*fallocate)(struct file *file, int mode, loff_t offset,
  28. loff_t len);
  29. int (*show_fdinfo)(struct seq_file *m, struct file *f);
  30. };
  1. struct file {
  2. /*
  3. * fu_list becomes invalid after file_free is called and queued via
  4. * fu_rcuhead for RCU freeing
  5. */
  6. union {
  7. struct list_head fu_list;
  8. struct llist_node fu_llist;
  9. struct rcu_head fu_rcuhead;
  10. } f_u;
  11. struct path f_path;
  12. #define f_dentry f_path.dentry
  13. struct inode *f_inode; /* cached value */
  14. const struct file_operations *f_op;
  15. /*
  16. * Protects f_ep_links, f_flags, f_pos vs i_size in lseek SEEK_CUR.
  17. * Must not be taken from IRQ context.
  18. */
  19. spinlock_t f_lock;
  20. #ifdef CONFIG_SMP
  21. int f_sb_list_cpu;
  22. #endif
  23. atomic_long_t f_count;
  24. unsigned int f_flags;
  25. fmode_t f_mode;
  26. loff_t f_pos;
  27. struct fown_struct f_owner;
  28. const struct cred *f_cred;
  29. struct file_ra_state f_ra;
  30. u64 f_version;
  31. #ifdef CONFIG_SECURITY
  32. void *f_security;
  33. #endif
  34. /* needed for tty driver, and maybe others */
  35. void *private_data;
  36. #ifdef CONFIG_EPOLL
  37. /* Used by fs/eventpoll.c to link all the hooks to this file */
  38. struct list_head f_ep_links;
  39. struct list_head f_tfile_llink;
  40. #endif /* #ifdef CONFIG_EPOLL */
  41. struct address_space *f_mapping;
  42. #ifdef CONFIG_DEBUG_WRITECOUNT
  43. unsigned long f_mnt_write_state;
  44. #endif
  45. };
  1. struct inode {
  2. ...
  3. dev_t i_rdev;
  4. union {
  5. struct pipe_inode_info *i_pipe;
  6. struct block_device *i_bdev;
  7. struct cdev *i_cdev;
  8. };
  9. ...
  10. };

对于一个字符设备, 应当满足linux对于字符设备的定义(既它可以进行字符设备的操作, 如打开,读取,写入,ioctl,关闭), 所以他应当自己定义这些操作的函数, 然后把函数指针保存在struct file_operations结构中传递给内核. 这样内核就可以在用户对设备调用这些函数时调用适当的驱动程序. 所以在struct file_operations中可以看到, 大部分的都是函数指针, 而有一个成员例外, struct module *owner成员应给它赋值THIS_MODULE.

例如:

  1. struct file_operations chr_fops =
  2. {
  3. .owner = THIS_MODULE,
  4. .open = open,
  5. .release = release,
  6. .read = read,
  7. .write = write,
  8. };

对于file和inode, 我的理解是, 用户每打开一个文件, 将产生一个file结构, 但是一个文件只有一个inode(硬盘上也保存着inode, 用户打开时将会读入内存). 所以看open和release函数的签名都有一个file和inode, 而其他函数只有file. 因为当打开文件时, 应当让file和inode建立联系, 关闭时应当解除联系. 所以内核的接口是这样设计的.

4.注册字符设备

刚刚只是分配了设备号了, 内核其实连你的设备是什么类型都不知道. 所以第二步应该注册设备. 字符设备的注册用到的结构体为struct cdev(<linux/cdev.h>, 刚刚在inode结构体中也看见这个结构了i_cdev)

cdev结构体的分配有两个函数:

  1. struct cdev *cdev_alloc(void);
  2. void cdev_init(struct cdev *cdev, struct file_operations *fops);

第一个函数会分配内存空间, 并进行初始化cdev, 第二个只是会初始化cdev.

cdev有两个重要的字段, owner和ops, owner应该设置成THIS_MODULE, ops应当设置成一个指向struct file_operations的指针.

cdev结构构造好之后通过这两个函数注册和移除字符设备:

  1. int cdev_add(struct cdev *dev, dev_t num, unsigned int count);
  2. void cdev_del(struct cdev *dev);

看看通过cdev_add我们传递给内核什么信息:

这样, 当有用户请求对num指定的设备号的设备进行操作时, 就会通过ops中的指针调用相应的函数了. 这就完成了字符设备的注册.cdev_add会返回错误代码

例:

  1. DEBUG_LOG("", "register char device\n");
  2. /* 注册字符设备 */
  3. chr_dev = kmalloc(sizeof(chr_dev), GFP_KERNEL);
  4. if (chr_dev == NULL)
  5. {
  6. DEBUG_LOG(KERN_WARNING, "no memory\n");
  7. ret = -ENOMEM;
  8. goto err1;
  9. }
  10. memset(chr_dev, 0, sizeof(*chr_dev));
  11. cdev_init(&chr_dev->cdev, &chr_fops);
  12. chr_dev->cdev.owner = THIS_MODULE;
  13. chr_dev->cdev.ops = &chr_fops;
  14. if ((ret = cdev_add(&chr_dev->cdev, dev, 1)) != 0)
  15. {
  16. DEBUG_LOG(KERN_WARNING, "Error %d adding chr\n", ret);
  17. goto err2;
  18. }
  19. DEBUG_LOG("", "success\n");

我们使用一个自己的结构来保存cdev和相关的信息chr_dev.

5.具体的驱动程序如何写

前面的所有操作讲的都是驱动程序的初始化操作, 都是应当写到module_init函数中的. 下面将驱动真正的事件处理函数应当怎么写.

我们的功能是这个设备可以打开,关闭,读取,写入. 所以我们只需实现这几个函数, 如果用户对设备调用其他的函数, 内核会有相应的默认操作.

1)打开

前面说了, 打开操作就是将inode和file结构体建立联系, 这两个结构体内核都会在用户打开/关闭文件时自动创建/销毁, 驱动程序不用照看他们的生命周期.

open函数会用参数传来inode和file, inode是内核根据用户打开的文件创建的, 因为文件系统保存着设备文件的设备号, 而刚刚我们通过cdev_add函数将设备号和cdev建立了联系所以这个inode中会有一个指向对应cdev的指针. 但是file中没有, 我们要做的就是让每个打开的file也保存起这个指针(通过保存在filp->private_data中).

但是我们的cdev保存在chr_dev结构中, 而且这个结构中还有另外一些我们感兴趣的东西, 所以不如直接保存chr_dev结构.

  1. int open(struct inode *inode, struct file *filp)
  2. {
  3. struct chr_dev *dev;
  4. DEBUG_LOG("", "open from user\n");
  5. dev = container_of(inode->i_cdev, struct chr_dev, cdev);
  6. filp->private_data = dev;
  7. if ((filp->f_flags & O_ACCMODE) == O_WRONLY)
  8. {
  9. dev->last_char = 0;
  10. }
  11. DEBUG_LOG("", "open success\n");
  12. return 0;
  13. }
2)关闭

当用户关闭文件时, 我们应当:

我们这两个工作都不用做.

3)读写

这个很简单了没什么要说的. 注意一点, 传来的buf指针是用户空间的指针(用__user修饰), 我们不能对这个指针进行解引用, 因为它根本不指向我们这个空间(内核空间)的数据. 而对它进行读写只能通过函数copy_from_user/copy_to_user进行(<linux/uaccess>).

另外, 如果在内核空间需要分配内存, 应使用kmalloc和kfree(<linux/stab.h>)

直接看最终代码吧

6.最终代码

  1. #include <linux/module.h>
  2. #include <linux/init.h> /* module_* */
  3. #include <linux/kernel.h> /* printk */
  4. #include <linux/moduleparam.h> /* module_param */
  5. #include <linux/types.h> /* dev_t */
  6. #include <linux/fs.h> /* reg*_chrdev */
  7. #include <linux/cdev.h> /* cdev_add */
  8. #include <asm/uaccess.h> /* copy_*_user */
  9. #include <linux/string.h> /* memset */
  10. #include <linux/slab.h> /* kmalloc */
  11. #define DEV_NAME "chr"
  12. #define _DEBUG
  13. #ifdef _DEBUG
  14. #define DEBUG_LOG(lvl, fmt, ...) \
  15. printk(lvl "%s: %s:%d <%s>: " fmt, \
  16. DEV_NAME, __FILE__, __LINE__, __func__, ##__VA_ARGS__)
  17. #else
  18. #define DEBUG_LOG(lvl, fmt, ...)
  19. #endif
  20. MODULE_LICENSE("Dual BSD/GPL");
  21. struct chr_dev
  22. {
  23. char last_char;
  24. struct cdev cdev;
  25. };
  26. int open(struct inode *inode, struct file *filp)
  27. {
  28. struct chr_dev *dev;
  29. DEBUG_LOG("", "open from user\n");
  30. dev = container_of(inode->i_cdev, struct chr_dev, cdev);
  31. filp->private_data = dev;
  32. if ((filp->f_flags & O_ACCMODE) == O_WRONLY)
  33. {
  34. dev->last_char = 0;
  35. }
  36. DEBUG_LOG("", "open success\n");
  37. return 0;
  38. }
  39. int release(struct inode *inode, struct file *filp)
  40. {
  41. DEBUG_LOG("", "release from user\n");
  42. DEBUG_LOG("", "release success\n");
  43. return 0;
  44. }
  45. ssize_t read(struct file *filp, char __user *buf, size_t count, loff_t *fpos)
  46. {
  47. struct chr_dev *dev = filp->private_data;
  48. char* kbuf = kmalloc(count, GFP_KERNEL);
  49. DEBUG_LOG("", "read from user\n");
  50. if (kbuf == NULL)
  51. {
  52. DEBUG_LOG(KERN_WARNING, "no memory\n");
  53. goto err1;
  54. }
  55. memset(kbuf, dev->last_char, count);
  56. if (copy_to_user(buf, kbuf, count) != 0)
  57. {
  58. DEBUG_LOG(KERN_WARNING, "copy_to_user error\n");
  59. goto err;
  60. }
  61. kfree(kbuf);
  62. DEBUG_LOG("", "read success count=%d\n", count);
  63. return count;
  64. err1:
  65. kfree(kbuf);
  66. err:
  67. return -EFAULT;
  68. }
  69. ssize_t write(struct file *filp, const char __user *buf, size_t count, loff_t *fpos)
  70. {
  71. struct chr_dev *dev = filp->private_data;
  72. DEBUG_LOG("", "write from user\n");
  73. if (copy_from_user(&dev->last_char, buf + count - 1, 1) != 0)
  74. {
  75. DEBUG_LOG(KERN_WARNING, "copy_from_user error\n");
  76. return -EFAULT;
  77. }
  78. DEBUG_LOG("", "write success count=%d\n", count);
  79. return count;
  80. }
  81. struct file_operations chr_fops =
  82. {
  83. .owner = THIS_MODULE,
  84. .open = open,
  85. .release = release,
  86. .read = read,
  87. .write = write,
  88. };
  89. int chr_major = 0;
  90. int chr_minor = 0;
  91. //module_param(chr_major, int, S_IRUGO);
  92. struct chr_dev* chr_dev;
  93. int chr_init(void)
  94. {
  95. int ret;
  96. dev_t dev;
  97. DEBUG_LOG("", "allocating device number\n");
  98. /* 申请设备号 */
  99. if (chr_major != 0) /* 如果用户提供了设备号 */
  100. {
  101. dev = MKDEV(chr_major, chr_minor);
  102. ret = register_chrdev_region(dev, 1, DEV_NAME);
  103. }
  104. else
  105. {
  106. ret = alloc_chrdev_region(&dev, chr_minor, 1, DEV_NAME);
  107. chr_major = MAJOR(dev);
  108. }
  109. if (ret < 0)
  110. {
  111. DEBUG_LOG(KERN_WARNING, "cannot get major%d\n", chr_major);
  112. goto err;
  113. }
  114. DEBUG_LOG("", "success\n");
  115. DEBUG_LOG("", "chr_major=%d, chr_minor=%d\n", chr_major, chr_minor);
  116. DEBUG_LOG("", "register char device\n");
  117. /* 注册字符设备 */
  118. chr_dev = kmalloc(sizeof(chr_dev), GFP_KERNEL);
  119. if (chr_dev == NULL)
  120. {
  121. DEBUG_LOG(KERN_WARNING, "no memory\n");
  122. ret = -ENOMEM;
  123. goto err1;
  124. }
  125. memset(chr_dev, 0, sizeof(*chr_dev));
  126. cdev_init(&chr_dev->cdev, &chr_fops);
  127. chr_dev->cdev.owner = THIS_MODULE;
  128. chr_dev->cdev.ops = &chr_fops;
  129. if ((ret = cdev_add(&chr_dev->cdev, dev, 1)) != 0)
  130. {
  131. DEBUG_LOG(KERN_WARNING, "Error %d adding chr\n", ret);
  132. goto err2;
  133. }
  134. DEBUG_LOG("", "success\n");
  135. return 0;
  136. err2:
  137. kfree(chr_dev);
  138. err1:
  139. unregister_chrdev_region(dev, 1);
  140. err:
  141. return ret;
  142. }
  143. void chr_exit(void)
  144. {
  145. DEBUG_LOG("", "unloading module\n");
  146. cdev_del(&chr_dev->cdev);
  147. kfree(chr_dev);
  148. unregister_chrdev_region(MKDEV(chr_major, chr_minor), 1);
  149. DEBUG_LOG("", "success\n");
  150. }
  151. module_init(chr_init);
  152. module_exit(chr_exit);
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注