Linux字符设备驱动模型(二)

淩亂°似流年 2022-04-17 03:59 388阅读 0赞

在上一节 中讨论了字符设备的基本模型,本节在上一节的基础上继续完善,本节将增加file_operations中的readwritellseek三个方法。

read和write

read和write方法完成的任务是相似的,即,拷贝数据到应用程序空间,或反过来从应用程序空间拷贝数据到内核空间,它们的原型也很相似,如下:

  1. ssize_t read(struct file *filp, char __user *buffer, size_t count, loff_t *ppos);
  2. ssize_t write(struct file *file, const char __user *buffer, size_t count, loff_t* ppos);

对于这两个方法的参数解读如下:

  • filep: 文件指针;
  • count: 请求传输的数据长度;
  • buffer: 指向用户空间的缓冲区,该地址是用户空间的地址,在内核不能直接使用,拷贝数据需要通过copy_from_usercopy_to_user两个函数来操作,其原型如下:

    unsigned long copy_to_user(void user to, const void from, unsigned long count);
    unsigned long copy_from_user(void* to, const void
    user* from, unsigned long count);

这两个函数在拷贝是会对地址做检查,返回实际拷贝的字节数。

  • ppos: 指明用户在文件中进行存取操作的位置。即,readwrite都要在偏移*ppos的基础上对文件进行操作。需要注意的是在读取或者写入文件内容后需要更新*ppos,之后内核会将文件位置的改变传播回file结构。
llseek

llseek的作用就是重新定位文件访问(read或者write)的偏移,函数原型为:

  1. loff_t char_driver_llseek(struct file *filp, loff_t offset, int whence);
  • filp: 文件指针;
  • offset: 需要偏移的距离,可以是正数,也可以是负数;
  • whence: 从何处开始偏移,一共有三种方式,分别是:
      a.从文件头部偏移;
      b.从当前访问位置偏移;
      c.从文件末尾偏移。

至此,本节的内容差不多了,下面给出完整的代码:
char_driver.c

  1. #include <linux/module.h>
  2. #include <linux/init.h>
  3. #include <linux/errno.h>
  4. #include <linux/poll.h>
  5. #include <linux/slab.h>
  6. #include <linux/cdev.h>
  7. #include <linux/miscdevice.h>
  8. #define MEM_SIZE 4096
  9. struct mem_dev
  10. {
  11. char *buf;
  12. unsigned long buf_size;
  13. //struct cdev cdev;
  14. };
  15. struct mem_dev *mem_devp;
  16. static int char_driver_open(struct inode *inode, struct file *filp)
  17. {
  18. filp->private_data = mem_devp; // 将全局变量mem_devp赋给filp->private_data,减少对全局变量的依赖
  19. printk("<0> open dev:%d\n", iminor(inode));
  20. return 0;
  21. }
  22. static ssize_t char_driver_read(struct file *filp, char __user *buffer, size_t count, loff_t *ppos)
  23. {
  24. int read_size;
  25. struct mem_dev *my_dev;
  26. my_dev = filp->private_data;
  27. if (count > my_dev->buf_size - *ppos) {
  28. read_size = my_dev->buf_size - *ppos;
  29. } else {
  30. read_size = count;
  31. }
  32. copy_to_user(buffer, my_dev->buf + *ppos, read_size);
  33. *ppos += read_size;
  34. //printk("<0> char_read to user:%s\n", buffer);
  35. return read_size;
  36. }
  37. static ssize_t char_driver_write(struct file *filp, const char __user *buffer, size_t count, loff_t* ppos)
  38. {
  39. struct mem_dev *my_dev;
  40. int write_size;
  41. my_dev = filp->private_data;
  42. if (count > my_dev->buf_size - *ppos) {
  43. write_size = my_dev->buf_size - *ppos;
  44. } else {
  45. write_size = count;
  46. }
  47. copy_from_user(my_dev->buf + filp->f_pos, buffer, write_size);
  48. *ppos += count;
  49. //printk("<0> char_write from user:%s ppos:%d\n", (struct mem_dev*)(file->private_data)->buf, (int)*ppos);
  50. return write_size;
  51. }
  52. static loff_t char_driver_llseek(struct file *filp, loff_t offset, int whence)
  53. {
  54. loff_t newpos;
  55. struct mem_dev *my_dev = filp->private_data;
  56. switch(whence) {
  57. case 0: /* SEEK_SET */
  58. newpos = offset;
  59. break;
  60. case 1: /* SEEK_CUR */
  61. newpos = filp->f_pos + offset;
  62. break;
  63. case 2: /* SEEK_END */
  64. newpos = my_dev->buf_size -1 + offset;
  65. break;
  66. default: /* can't happen */
  67. return -EINVAL;
  68. }
  69. if ((newpos < 0) || (newpos > MEM_SIZE)) {
  70. return -EINVAL;
  71. }
  72. filp->f_pos = newpos;
  73. return newpos;
  74. }
  75. static int char_driver_release(struct inode *inode, struct file *filp)
  76. {
  77. printk(KERN_EMERG"close dev:%d\n", MINOR(inode->i_rdev));
  78. return 0;
  79. }
  80. static struct file_operations char_driver_fops = {
  81. .owner = THIS_MODULE,
  82. .open = char_driver_open,
  83. .write = char_driver_write,
  84. .read = char_driver_read,
  85. .llseek = char_driver_llseek,
  86. .release = char_driver_release,
  87. };
  88. static struct miscdevice misc = {
  89. .minor = MISC_DYNAMIC_MINOR,
  90. .name = "char_driver",
  91. .fops = &char_driver_fops,
  92. };
  93. static struct mem_dev* alloc_mem_dev(void)
  94. {
  95. struct mem_dev* devp = kmalloc(sizeof(struct mem_dev), GFP_KERNEL);
  96. if (!devp) {
  97. /*申请失败*/
  98. return NULL;
  99. }
  100. memset(devp, 0, sizeof(struct mem_dev));
  101. devp->buf = kmalloc(MEM_SIZE, GFP_KERNEL);
  102. if (NULL == devp->buf) {
  103. kfree(devp);
  104. return NULL;
  105. }
  106. devp->buf_size = MEM_SIZE;
  107. return devp;
  108. }
  109. static void release_mem_dev(struct mem_dev** devp)
  110. {
  111. if (NULL == devp) {
  112. return;
  113. }
  114. kfree((*devp)->buf);
  115. (*devp)->buf = NULL;
  116. kfree(*devp);
  117. *devp = NULL;
  118. }
  119. static int __init char_driver_init(void)
  120. {
  121. int ret;
  122. mem_devp = alloc_mem_dev();
  123. if (!mem_devp) /*申请失败*/
  124. {
  125. printk(KERN_EMERG"alloc mem_dev error!\n");
  126. return - ENOMEM;
  127. }
  128. ret = misc_register(&misc);
  129. printk(KERN_EMERG"hello driver dev_init!\n");
  130. return ret;
  131. }
  132. static void __exit char_driver_exit(void)
  133. {
  134. release_mem_dev(&mem_devp);
  135. misc_deregister(&misc);
  136. printk(KERN_EMERG"hello driver dev_exit!\n");
  137. }
  138. MODULE_LICENSE("GPL");
  139. module_init(char_driver_init);
  140. module_exit(char_driver_exit);

app.c

  1. #include <sys/types.h>
  2. #include <sys/stat.h>
  3. #include <fcntl.h>
  4. #include <sys/ioctl.h>
  5. #include <stdio.h>
  6. #include <stdlib.h>
  7. #include <string.h>
  8. int main(int argc, char *argv[])
  9. {
  10. int fd;
  11. char buf[128] = {
  12. 0};
  13. fd = open("/dev/char_driver", O_RDWR);
  14. if(0 > fd){
  15. perror("can not open 6410_led:");
  16. }
  17. write(fd, "hello driver!", 13);
  18. lseek(fd, SEEK_SET, 0);
  19. read(fd, buf, 5);
  20. printf("%s\n", buf);
  21. memset(buf, 0, sizeof(buf));
  22. read(fd, buf, 10);
  23. printf("%s\n", buf);
  24. close(fd);
  25. return 0;
  26. }

Makefile和上一节的一样,本节不再列出。

模块测试

编译加载模块的过程不再列出来了,分别为一下步骤:

  1. #make
  2. #insmod char_driver.ko

测试驱动功能
由于本驱动模拟的是一个文件的读写,那么接下来看看是否能实现功能:
在这里插入图片描述
从上图结果可知,首先我写入了“hello driver!”13个字符,分两次读取,读取结果和预期相同,至于第二次读取的内容打印出来后面有乱码,是由于我在驱动中没有记录实际写入的字节数,如果有兴趣的话可以自己尝试实现。

发表评论

表情:
评论列表 (有 0 条评论,388人围观)

还没有评论,来说两句吧...

相关阅读

    相关 Linux字符设备驱动模型(一)

    从事Linux开发也有几年时间了,期间也写了一些比较简单的驱动,但一直没有做系统的整理,今天终于想静下心来做一些整理,首先就从最基本的字符设备驱动开始。先贴出一个简单的字符设备

    相关 Linux字符设备驱动基础(

    Linux字符设备驱动基础(二) 5 设备号相关操作 设备号由主设备号和次设备号组成。主设备号用来标识与设备文件相连的驱动程序,用以反映设备类型;次设备号是用于驱动