当前位置:   article > 正文

linux设备驱动(2)--字符设备驱动框架_chrdevbase_open

chrdevbase_open

代码学习资料来源于:

第3.5讲 我的第一个Linux驱动-完善chrdevbase驱动_哔哩哔哩_bilibili

仅用于个人学习/复习,侵联删

1、字符设备驱动简介

字符设备是linux驱动中最基本的一类设备驱动,字符设备就是一个个字节,按照字节流来进行读写操作的设备,读写数据是分先后顺序的。比如我们最常见的点灯、按键、i2c、spi、lcd等都是字符设备,这些设备的驱动就叫做字符设备驱动。

linux内核文件 include/linux/fs.h中有个叫做file_operations的结构体,此结构体就是linux内核驱动操作函数集合。我们需要用到什么就用什么。常用的有open、write、read、release,poll,mmap等。

open:打开文件设备

read:读取文件设备

poll:轮询函数,用于查询设备是否可以进行非阻塞的读写

write:向设备文件写入设备

release:释放(关闭)文件设备,与应用程序中的close相对应

mmap:将设备的内存映射到进程空间中(也就是用户空间),一般帧缓冲设备会使用此函数,比如LCD驱动的显存,将帧缓冲映射到用户空间中以后应用程序就可以直接操作显存了,这样就可以不用在用户空间和内核空间之间来回复制。

  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 (*read_iter) (struct kiocb *, struct iov_iter *);
  7. ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);
  8. int (*iterate) (struct file *, struct dir_context *);
  9. int (*iterate_shared) (struct file *, struct dir_context *);
  10. __poll_t (*poll) (struct file *, struct poll_table_struct *);
  11. long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
  12. long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
  13. int (*mmap) (struct file *, struct vm_area_struct *);
  14. unsigned long mmap_supported_flags;
  15. int (*open) (struct inode *, struct file *);
  16. int (*flush) (struct file *, fl_owner_t id);
  17. int (*release) (struct inode *, struct file *);
  18. int (*fsync) (struct file *, loff_t, loff_t, int datasync);
  19. int (*fasync) (int, struct file *, int);
  20. int (*lock) (struct file *, int, struct file_lock *);
  21. ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
  22. unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
  23. int (*check_flags)(int);
  24. int (*flock) (struct file *, int, struct file_lock *);
  25. ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
  26. ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
  27. int (*setlease)(struct file *, long, struct file_lock **, void **);
  28. long (*fallocate)(struct file *file, int mode, loff_t offset,
  29. loff_t len);
  30. void (*show_fdinfo)(struct seq_file *m, struct file *f);
  31. #ifndef CONFIG_MMU
  32. unsigned (*mmap_capabilities)(struct file *);
  33. #endif
  34. ssize_t (*copy_file_range)(struct file *, loff_t, struct file *,
  35. loff_t, size_t, unsigned int);
  36. int (*clone_file_range)(struct file *, loff_t, struct file *, loff_t,
  37. u64);
  38. int (*dedupe_file_range)(struct file *, loff_t, struct file *, loff_t,
  39. u64);
  40. int (*fadvise)(struct file *, loff_t, loff_t, int);
  41. } __randomize_layout;

2、内核模块的载入和卸载

linux驱动程序可以编译到kernel里面,也就是zImage,也可以编译为模块.ko。测试的时候只需要加载.ko文件即可。

__init修饰函数表示此函数仅在初始化阶段使用,使用后所占用的内存资源会释放

模块代码有两种方式,一是静态编译链接进入内核,在系统启动过程中进行初始化;二是编译成可动态加载的module,通过insmod动态加载重定位到内核。这两种方式可以在Makefile中通过obj-y或obj-m选项进行选择

在调试的时候可以使用obj-y,在提交代码的时候为了规范要使用obj-m。

关于module_init和module_exit两个宏参考:

Linux内核模块分析(module_init宏)_阿基米东的博客-CSDN博客_module_init

加载驱动模块:insmod drv.ko或者modprobe drv.ko

insmod和modprobe两者之间的区别:

insmod命令不能解决模块的依赖问题,比如drv.ko依赖first.ko模块,就必须先insmod first.ko模块,而modprobe就比较智能,会到指定的路径下去查找依赖

卸载驱动模块:rmmod drv.ko或者modprobe -r drv.ko

查看驱动模块:lsmod

3、内核打印函数

内核打印函数为printk,printk存在八个等级,具体可以查看include/linux/kern_levels.h

  1. #define KERN_EMERG KERN_SOH "0" /* system is unusable */
  2. #define KERN_ALERT KERN_SOH "1" /* action must be taken immediately */
  3. #define KERN_CRIT KERN_SOH "2" /* critical conditions */
  4. #define KERN_ERR KERN_SOH "3" /* error conditions */
  5. #define KERN_WARNING KERN_SOH "4" /* warning conditions */
  6. #define KERN_NOTICE KERN_SOH "5" /* normal but significant condition */
  7. #define KERN_INFO KERN_SOH "6" /* informational */
  8. #define KERN_DEBUG KERN_SOH "7" /* debug-level messages */

0的优先级最高,7的优先级最低,如果不设置消息级别,printk会选择默认级别default_message_loglevel,为7。

可以通过指令对系统内核打印的等级进行设置:

echo 1       4       1      7 > /proc/sys/kernel/printk

同样有一些printk的封装函数:

pr_xxx

除了直接使用 printk 加消息级别的方式,在 <linux/printk.h> 中还定义了 pr_notice、pr_info、pr_warn、pr_err 等接口。使用这些 pr_xxx 接口,就可以省去指定消息级别的麻烦。

  1. #define pr_emerg(fmt, ...) printk(KERN_EMERG pr_fmt(fmt), ##__VA_ARGS__)
  2. #define pr_alert(fmt, ...) printk(KERN_ALERT pr_fmt(fmt), ##__VA_ARGS__)
  3. #define pr_crit(fmt, ...) printk(KERN_CRIT pr_fmt(fmt), ##__VA_ARGS__)
  4. #define pr_err(fmt, ...) printk(KERN_ERR pr_fmt(fmt), ##__VA_ARGS__)
  5. #define pr_warning(fmt, ...) printk(KERN_WARNING pr_fmt(fmt), ##__VA_ARGS__)
  6. #define pr_warn pr_warning
  7. #define pr_notice(fmt, ...) printk(KERN_NOTICE pr_fmt(fmt), ##__VA_ARGS__)
  8. #define pr_info(fmt, ...) printk(KERN_INFO pr_fmt(fmt), ##__VA_ARGS__)
  9. #define pr_devel(fmt, ...) printk(KERN_DEBUG pr_fmt(fmt), ##__VA_ARGS__)
  10. #define pr_debug(fmt, ...) printk(KERN_DEBUG pr_fmt(fmt), ##__VA_ARGS__)

需要注意的是,其他 pr_XXX() 函数能无条件地打印,但 pr_debug() 却不能。因为默认情况下它不会被编译,除非定义了 DEBUG 或设定了 CONFIG_DYNAMIC_DEBUG。

dev_xxx

对于驱动程序,在 <linux/device.h> 里也提供了一些驱动模型诊断宏,例如 dev_err、dev_warn、dev_info 等等。使用它们,不仅可以按标记的消息级别打印,还会打印对应的设备和驱动信息,这对于驱动调试来说相当重要。

  1. #define dev_emerg(dev, fmt, ...) _dev_emerg(dev, dev_fmt(fmt), ##__VA_ARGS__)
  2. #define dev_crit(dev, fmt, ...) _dev_crit(dev, dev_fmt(fmt), ##__VA_ARGS__)
  3. #define dev_alert(dev, fmt, ...) _dev_alert(dev, dev_fmt(fmt), ##__VA_ARGS__)
  4. #define dev_err(dev, fmt, ...) _dev_err(dev, dev_fmt(fmt), ##__VA_ARGS__)
  5. #define dev_warn(dev, fmt, ...) _dev_warn(dev, dev_fmt(fmt), ##__VA_ARGS__)
  6. #define dev_notice(dev, fmt, ...) _dev_notice(dev, dev_fmt(fmt), ##__VA_ARGS__)
  7. #define dev_info(dev, fmt, ...) _dev_info(dev, dev_fmt(fmt), ##__VA_ARGS__)

4、字符设备注册与注销

对于字符设备而言,当驱动模块加载成功以后需要注册字符设备,同样,卸载驱动模块的时候也需要注销掉字符设备。

  1. // 注册字符设备函数
  2. static inline int register_chrdev(unsigned int major, const char *name,
  3. const struct file_operations *fops)
  4. {
  5. return __register_chrdev(major, 0, 256, name, fops);
  6. }
  7. // 注销字符设备函数
  8. static inline void unregister_chrdev(unsigned int major, const char *name)
  9. {
  10. __unregister_chrdev(major, 0, 256, name);
  11. }

register_chrdev函数用于注册字符设备,此函数一共有三个参数,含义如下:

major:主设备号,linux下每一个设备都有一个设备号,设备号分为主设备号和次设备号两部分。

linux中主设备号表示某一个具体的驱动,次设备号表示使用这个驱动的各个设备,linux提供了一个名为dev_t的数据类型表示设备号,dev_t定义在include/linux/types.h里面,如下:

  1. typedef u32 __kernel_dev_t;
  2. typedef __kernel_dev_t dev_t;

综合起来看,其实dev_t类型就是unsigned int类型,是一个无符号的int类型的数据。

32位的前10位为主设备号,低20位为次设备号,因此Linux系统中主设备号范围为0-4095,所以在选择主设备号时一定不要超过这个范围,在include/linux/kdev_t.h中提供了几个关于设备号的宏:

  1. #define MINORBITS 20 // 次设备号位数
  2. #define MINORMASK ((1U << MINORBITS) - 1) // 次设备号掩码
  3. // 从设备号中获取主设备号,dev_t右移20位
  4. #define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))
  5. // 从设备号获取次设备号,取dev_t的低20位
  6. #define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))
  7. // 将给定的主设备号和次设备号组合成dev_t类型的设备号
  8. #define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))

可以使用cat /proc/devices命令来查看当前系统所有的设备,如下

可以查看到主设备号为50的没有使用,那么我们就可以用50的主设备号,例如:

register_chrdev(50, "chrdevbase"const struct file_operations *fops);

name:设备名称,指向一串字符串

fops:结构体file_operations类型指针,指向设备的操作函数集合变量

unregister_chrdev函数同理。

重要的几个结构体:

struct inode:静态文件

struct file:动态文件(打开的文件),file结构体里面有个叫做private_data的成员变量。

struct file_operations:描述设备的操作方法的

struct cdev:struct cdev结构体是用来描述一个字符设备的,每个字符设备都对应一个struct cdev结构体。

驱动中重要的三个结构体介绍:struct inode、struct file、struct file_operations_正在起飞的蜗牛的博客-CSDN博客

5、man使用

我们写完驱动程序之后,需要编写应用程序去测试所写的驱动程序是否正确。

man手册的调用如下(图片来源于网络):

比如open系统调用,我们就可以通过man 2 open来进行查看相关的头文件和注意事项

6、chrdevbase的驱动demo

进入 /dev 查看设备,以模块的名字进行命名的,我们可以先手动创建设备节点:

mknod /dev/chrdevbase c 200 0 (c表示字符设备,主设备号是200,次设备号是0)

chrdevbase.c

  1. #include <linux/module.h>
  2. #include <linux/init.h>
  3. #include <linux/sched/signal.h>
  4. #include <linux/device.h>
  5. #include <linux/ioctl.h>
  6. #include <linux/parport.h>
  7. #include <linux/ctype.h>
  8. #include <linux/poll.h>
  9. #include <linux/slab.h>
  10. #include <linux/major.h>
  11. #include <linux/ppdev.h>
  12. #include <linux/mutex.h>
  13. #include <linux/uaccess.h>
  14. #include <linux/compat.h>
  15. #define CHRDEVBASE_MAJOR 200
  16. #define CHRDEVBASE_NAME "chrdevbase"
  17. static int chrdevbase_open(struct inode *inode, struct file *file)
  18. {
  19. printk("chrdevbase_open start\n");
  20. return 0;
  21. }
  22. static int chrdevbase_release(struct inode *inode, struct file *file)
  23. {
  24. printk("chrdevbase_release start \n");
  25. return 0;
  26. }
  27. // count:读取的数据量大小
  28. static ssize_t chrdevbase_read(struct file *filp, char __user *buf, size_t count, loff_t *ppos)
  29. {
  30. printk("chrdevbase_read start\n");
  31. return 0;
  32. }
  33. static ssize_t chrdevbase_write(struct file *filp, const char __user *buf, size_t count, loff_t *ppos)
  34. {
  35. printk("chrdevbase_write start\n");
  36. return 0;
  37. }
  38. static struct file_operations chrdevbase_fops = {
  39. .owner = THIS_MODULE,
  40. .open = chrdevbase_open,
  41. .release = chrdevbase_release,
  42. .read = chrdevbase_read,
  43. .write = chrdevbase_write,
  44. };
  45. static int __init chrdevbase__init(void)
  46. {
  47. int ret = 0;
  48. printk("chrdevbase__init \n");
  49. // 返回值小于0就申请失败了
  50. ret = register_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME, &chrdevbase_fops);
  51. if(ret < 0) {
  52. printk("chrdevbase__init failed!\n");
  53. }
  54. printk("chrdevbase__init start\n");
  55. return 0;
  56. }
  57. static void __exit chrdevbase__exit(void)
  58. {
  59. unregister_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME);
  60. printk("chrdevbase__exit end\n");
  61. }
  62. module_init(chrdevbase__init);
  63. module_exit(chrdevbase__exit);

测试程序 chrdevbaseAPP.c

  1. #include <sys/types.h>
  2. #include <sys/stat.h>
  3. #include <fcntl.h>
  4. #include <stdio.h>
  5. #include <unistd.h>
  6. int main(int argc, int *argv[])
  7. {
  8. char *filename;
  9. int fd;
  10. int ret;
  11. filename = argv[1];
  12. char readbuf[100], writebuf[100];
  13. fd = open(filename, O_RDWR);
  14. if (fd < 0) {
  15. printf("open %s failed\n", filename);
  16. return -1;
  17. }
  18. ret = read(fd, readbuf, 50);
  19. if (ret < 0) {
  20. printf("read %s failed\n", filename);
  21. return -1;
  22. } else {
  23. }
  24. ret = write(fd, writebuf, 50);
  25. if (ret < 0) {
  26. printf("write %s failed\n", filename);
  27. return -1;
  28. } else {
  29. }
  30. ret = close(fd);
  31. if (ret < 0) {
  32. printf("close %s failed\n", filename);
  33. return -1;
  34. } else {
  35. }
  36. return 0;
  37. }

执行测试:

./chrdevbaseAPP /dev/chrdevbase

7、chrdevbase驱动的完善

驱动给应用传递数据的时候需要用到copy_to_user,应用程序向驱动传递数据的时候则使用copy_from_user,定义分别如下:

  1. static inline long copy_to_user(void __user volatile *to, const void *from,
  2. unsigned long n)
  3. static inline long copy_from_user(void *to, const void __user volatile *from,
  4. unsigned long n)
  5. 返回参数:成功返回0,失败返回负数

chrdevbase.c

  1. #include <linux/module.h>
  2. #include <linux/init.h>
  3. #include <linux/sched/signal.h>
  4. #include <linux/device.h>
  5. #include <linux/ioctl.h>
  6. #include <linux/parport.h>
  7. #include <linux/ctype.h>
  8. #include <linux/poll.h>
  9. #include <linux/slab.h>
  10. #include <linux/major.h>
  11. #include <linux/ppdev.h>
  12. #include <linux/mutex.h>
  13. #include <linux/uaccess.h>
  14. #include <linux/compat.h>
  15. #include <linux/fs.h>
  16. #include <linux/io.h>
  17. #define CHRDEVBASE_MAJOR 200
  18. #define CHRDEVBASE_NAME "chrdevbase"
  19. static char readbuf[100];
  20. static char writebuf[100];
  21. static char kerneldata[] = {"kernel data!"};
  22. static int chrdevbase_open(struct inode *inode, struct file *file)
  23. {
  24. printk("chrdevbase_open start\n");
  25. return 0;
  26. }
  27. static int chrdevbase_release(struct inode *inode, struct file *file)
  28. {
  29. printk("chrdevbase_release start \n");
  30. return 0;
  31. }
  32. // count:读取的数据量大小
  33. static ssize_t chrdevbase_read(struct file *filp, char __user *buf, size_t count, loff_t *ppos)
  34. {
  35. int ret = 0;
  36. memcpy(readbuf, kerneldata, sizeof(kerneldata));
  37. int ret = copy_to_user(buf, readbuf, count);
  38. if (ret == 0) {
  39. printk("copy to user success \n");
  40. } else {
  41. }
  42. return 0;
  43. }
  44. static ssize_t chrdevbase_write(struct file *filp, const char __user *buf, size_t count, loff_t *ppos)
  45. {
  46. int ret = 0;
  47. ret = copy_from_user(writebuf, buf, count);
  48. if (ret == 0) {
  49. printk("copy from user success, recedata = %s \n", writebuf);
  50. } else {
  51. }
  52. return 0;
  53. }
  54. static struct file_operations chrdevbase_fops = {
  55. .owner = THIS_MODULE,
  56. .open = chrdevbase_open,
  57. .release = chrdevbase_release,
  58. .read = chrdevbase_read,
  59. .write = chrdevbase_write,
  60. };
  61. static int __init chrdevbase__init(void)
  62. {
  63. int ret = 0;
  64. printk("chrdevbase__init \n");
  65. // 返回值小于0就申请失败了
  66. ret = register_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME, &chrdevbase_fops);
  67. if(ret < 0) {
  68. printk("chrdevbase__init failed!\n");
  69. }
  70. printk("chrdevbase__init start\n");
  71. return 0;
  72. }
  73. static void __exit chrdevbase__exit(void)
  74. {
  75. unregister_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME);
  76. printk("chrdevbase__exit end\n");
  77. }
  78. module_init(chrdevbase__init);
  79. module_exit(chrdevbase__exit);

chrdevbaseAPP.c

  1. #include <sys/types.h>
  2. #include <sys/stat.h>
  3. #include <fcntl.h>
  4. #include <stdio.h>
  5. #include <unistd.h>
  6. #include <stdlib.h>
  7. #include <string.h>
  8. /*
  9. * ./chrdevbaseAPP <filename> <1/2>
  10. * 1 读数据
  11. * 2 写数据
  12. * */
  13. int main(int argc, int *argv[])
  14. {
  15. char *filename;
  16. int fd;
  17. int ret;
  18. filename = argv[1];
  19. char readbuf[100], writebuf[100];
  20. if (argc < 3) {
  21. printf("usage error \n");
  22. }
  23. fd = open(filename, O_RDWR);
  24. if (fd < 0) {
  25. printf("open %s failed\n", filename);
  26. return -1;
  27. }
  28. if(atoi(argv[2]) == 1) {
  29. ret = read(fd, readbuf, 50);
  30. if (ret < 0) {
  31. printf("read %s failed\n", filename);
  32. return -1;
  33. } else {
  34. printf("APP read data: %s \n", readbuf);
  35. }
  36. }
  37. if(atoi(argv[2]) == 2) {
  38. memcpy(writebuf, usrdata, sizeof(userdata));
  39. ret = write(fd, writebuf, 50);
  40. if (ret < 0) {
  41. printf("write %s failed\n", filename);
  42. return -1;
  43. } else {
  44. printf("APP write data: %s \n", readbuf);
  45. }
  46. }
  47. ret = close(fd);
  48. if (ret < 0) {
  49. printf("close %s failed\n", filename);
  50. return -1;
  51. }
  52. return 0;
  53. }

执行测试:

./chrdevbaseAPP /dev/chrdevbase 1

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/羊村懒王/article/detail/180438
推荐阅读
相关标签
  

闽ICP备14008679号