[关闭]
@zwh8800 2017-08-23T10:06:40.000000Z 字数 4449 阅读 191355

linux 设备驱动程序 (5) – 字符设备驱动例子

blog 归档 linux 驱动开发


linux 设备驱动程序 (5) – 字符设备驱动例子


这次给出一个例子, 比 (3) 中的例子稍微复杂一些. 在 (3) 中, 给出了一个字符设备的例子, 会保存最后一个写入的字节. 这次的例子使用一个链表保存写入的数据, 读取时从链表中移出数据:
image_1bl0atutt1kn7iv5t3b18q8fr49.png-5.9kB

因此这个驱动和 FIFO 和管道非常相似, 当用户写入数据时, write point 后移, 并且写入数据 (如果当前链表 node 空间用尽则创建新 node). 当用户读取数据时, read pointer 也后移 (如果 read pointer 指向后一个 node 则删除前一个 node)

所以首先写一个简单的单向链表:

  1. #define BYTE_PER_NODE 4096 /* 每个节点的数据size */
  2. struct linked_node
  3. {
  4. char *data; /* 指向一个数组, 数据保存在这里 */
  5. struct linked_node* next; /* 指向下一个节点 */
  6. };
  7. /* 创建一个新node */
  8. struct linked_node* create_node(void)
  9. {
  10. struct linked_node* ret;
  11. if ((ret = kmalloc(sizeof(struct linked_node), GFP_KERNEL)) == NULL)
  12. {
  13. DEBUG_LOG(KERN_WARNING, "no memory\n");
  14. goto err;
  15. }
  16. if ((ret->data = kmalloc(BYTE_PER_NODE, GFP_KERNEL)) == NULL)
  17. {
  18. DEBUG_LOG(KERN_WARNING, "no memory\n");
  19. goto err1;
  20. }
  21. return ret;
  22. err1:
  23. kfree(ret);
  24. err:
  25. return NULL;
  26. }
  27. /* 删除一个node */
  28. void free_node(struct linked_node* node)
  29. {
  30. kfree(node->data);
  31. kfree(node);
  32. }

因为我们的读写指针 (read pointer/write pointer) 需要保存指向哪个节点, 以及在节点中的哪个位置. 所以用一个结构体来保存这个指针:

  1. struct pos_pointer
  2. {
  3. struct linked_node* node;
  4. size_t pos;
  5. };
  6. struct chr_dev
  7. {
  8. struct pos_pointer read_ptr;
  9. struct pos_pointer write_ptr;
  10. struct semaphore sem;
  11. wait_queue_head_t queue;
  12. struct cdev cdev;
  13. };

在我们的设备结构体中使用 pos_pointer 保存读写指针.

另外发现多了一个 sem 成员, 这个是上篇中学习的信号量. queue 成员是一个” 等待队列头”, 用于当无数据可读时进行休眠 [下一篇讲述].

在 chr_init() 函数中应该设置我们的读写指针和信号量设施:

  1. /* in function chr_init() */
  2. /* 设置私有数据 */
  3. DEBUG_LOG(DEFAULT_LEVEL, "setup private data\n");
  4. if ((node = create_node()) == NULL)
  5. {
  6. goto err2;
  7. }
  8. chr_dev->read_ptr.node = node;
  9. chr_dev->read_ptr.pos = 0;
  10. chr_dev->write_ptr.node = node;
  11. chr_dev->write_ptr.pos = 0;
  12. DEBUG_LOG(DEFAULT_LEVEL, "success\n");
  13. /* 初始化信号量 */
  14. DEBUG_LOG(DEFAULT_LEVEL, "initialize semaphore\n");
  15. sema_init(&chr_dev->sem, 1);
  16. DEBUG_LOG(DEFAULT_LEVEL, "success\n");
  17. /* 初始化等待队列 */
  18. DEBUG_LOG(DEFAULT_LEVEL, "initialize wait queue\n");
  19. init_waitqueue_head(&chr_dev->queue);
  20. DEBUG_LOG(DEFAULT_LEVEL, "success\n");

在 chr_exit() 中也应该释放资源:

  1. p = chr_dev->read_ptr.node;
  2. chr_dev->read_ptr.node = NULL;
  3. chr_dev->write_ptr.node = NULL;
  4. while (p)
  5. {
  6. q = p->next;
  7. free_node(p);
  8. p = q;
  9. }
  10. cdev_del(&chr_dev->cdev);

写入函数:

  1. ssize_t write(struct file *filp, const char __user *buf, size_t count, loff_t *fpos)
  2. {
  3. struct chr_dev *dev = filp->private_data;
  4. size_t this_write, total_write = 0;
  5. char* p;
  6. if (down_interruptible(&dev->sem) != 0)
  7. return -ERESTARTSYS;
  8. DEBUG_LOG(DEFAULT_LEVEL, "write from user,pid=%d [%s]\n",
  9. current->pid, current->comm);
  10. while (count != 0)
  11. {
  12. this_write = MIN(BYTE_PER_NODE - dev->write_ptr.pos,
  13. count);
  14. p = &dev->write_ptr.node->data[dev->write_ptr.pos];
  15. if (copy_from_user(p, buf, this_write) != 0)
  16. {
  17. DEBUG_LOG(KERN_WARNING, "copy_from_user error\n");
  18. goto err;
  19. }
  20. count -= this_write;
  21. total_write += this_write;
  22. buf += this_write;
  23. dev->write_ptr.pos += this_write;
  24. if (dev->write_ptr.pos == BYTE_PER_NODE)
  25. {
  26. dev->write_ptr.node->next = create_node();
  27. dev->write_ptr.node = dev->write_ptr.node->next;
  28. dev->write_ptr.pos = 0;
  29. }
  30. }
  31. DEBUG_LOG(DEFAULT_LEVEL, "write success total_write=%d,pos=%d\n",
  32. total_write, dev->write_ptr.pos);
  33. up(&dev->sem);
  34. wake_up_interruptible(&dev->queue);
  35. return total_write;
  36. err:
  37. up(&dev->sem);
  38. return -EFAULT;
  39. }

我们使用循环将数据写入, 每次循环开始时检测当前 node 剩余空间 (BYTE_PER_NODE – dev->write_ptr.pos) 和用户要求写入的 count 谁更小, 取较小作为 this_write(这一次写入的数据). 写入 node 之后, 令 count-=this_write 和并且令写指针 +=this_write. 之后检测写指针是否到 node 的末尾, 如果是的话, create 一个新 node.

读取函数和写入很类似:

  1. ssize_t read(struct file *filp, char __user *buf, size_t count, loff_t *fpos)
  2. {
  3. struct chr_dev *dev = filp->private_data;
  4. size_t this_read, total_read = 0;
  5. char* p;
  6. if (down_interruptible(&dev->sem) != 0)
  7. return -ERESTARTSYS;
  8. DEBUG_LOG(DEFAULT_LEVEL, "read from user,pid=%d [%s]\n",
  9. current->pid, current->comm);
  10. while (dev->read_ptr.node == dev->write_ptr.node &&
  11. dev->read_ptr.pos == dev->write_ptr.pos)
  12. {
  13. up(&dev->sem);
  14. if (filp->f_flags & O_NONBLOCK)
  15. return -EAGAIN;
  16. DEBUG_LOG(DEFAULT_LEVEL, "[%s] going to sleep\n", current->comm);
  17. if (wait_event_interruptible(dev->queue, (dev->read_ptr.node != dev->write_ptr.node ||
  18. dev->read_ptr.pos != dev->write_ptr.pos) ) != 0)
  19. return -ERESTARTSYS;
  20. if (down_interruptible(&dev->sem) != 0)
  21. return -ERESTARTSYS;
  22. }
  23. while (count != 0)
  24. {
  25. if (dev->read_ptr.node == dev->write_ptr.node)
  26. {
  27. this_read = MIN(dev->write_ptr.pos - dev->read_ptr.pos,
  28. count);
  29. }
  30. else
  31. {
  32. this_read = MIN(BYTE_PER_NODE - dev->read_ptr.pos,
  33. count);
  34. }
  35. if (this_read == 0)
  36. break;
  37. p = &dev->read_ptr.node->data[dev->read_ptr.pos];
  38. if (copy_to_user(buf, p, this_read) != 0)
  39. {
  40. DEBUG_LOG(KERN_WARNING, "copy_to_user error\n");
  41. goto err;
  42. }
  43. count -= this_read;
  44. total_read += this_read;
  45. buf += this_read;
  46. dev->read_ptr.pos += this_read;
  47. if (dev->read_ptr.pos == BYTE_PER_NODE)
  48. {
  49. struct linked_node* node;
  50. node = dev->read_ptr.node;
  51. dev->read_ptr.node = dev->read_ptr.node->next;
  52. free_node(node);
  53. dev->read_ptr.pos = 0;
  54. }
  55. }
  56. DEBUG_LOG(DEFAULT_LEVEL, "read success total_read=%d,pos=%d\n",
  57. total_read, dev->read_ptr.pos);
  58. up(&dev->sem);
  59. return total_read;
  60. err:
  61. up(&dev->sem);
  62. return -EFAULT;
  63. }

[EOF]

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注