当前位置:   article > 正文

Linux驱动开发 IO模型:阻塞IO_linux 读取 io文件阻塞

linux 读取 io文件阻塞

目录

1、阻塞IO模型是什么

2、应用程序

3、驱动程序

3.1 阻塞相关的步骤

3.2 阻塞相关的API

3.2.1 wait_event_interruptible()函数解析

3.3 阻塞驱动程序

4、将非阻塞打开的文件设置阻塞

5、一些知识点理解


1、阻塞IO模型是什么

当应用程序发起读取数据(read)的时候,如果数据没有准备好,就会阻塞等待(进程休眠),如果与硬件的数据准备好了,就会产生硬件中断,在中断处理函数中唤醒休眠的进程,然后将准备好的数据拷贝至用户空间。

2、应用程序

应用程序默认是以阻塞方式打开,所以不需要特别设置

open("/dev/mycdev",O_RDWR|O_NONBLOCK);

3、驱动程序

3.1 阻塞相关的步骤

1、定义等待队列头

2、初始化等待队列头

3、如果数据没有准备好,就进行休眠

4、如果数据准备好了,就唤醒

3.2 阻塞相关的API

  1. //1、定义等待队列头
  2. wait_queue_head_t wq;
  3. struct __wait_queue_head {
  4. spinlock_t lock;
  5. struct list_head task_list;
  6. };
  1. //2、初始化等待队列头
  2. init_waitqueue_head(&wq);
  1. //3.1、休眠操作,一个函数实现
  2. //如果数据没有准备好,就进行休眠
  3. wait_event(wq, condition); //进入不可中断的休眠态
  4. wait_event_interruptible(wq, condition) // 让进程进入可中断的休眠态
  5. //wq: 等待队列头
  6. //condition: 代表数据是否准备好,如果为真,代表数据准备好了,不需要休眠
  7. // 如果为假,代表数据没有准备好,进程需要休眠
  1. //3.2分步实现等待队列
  2. //等待队列项操作函数
  3. //定义并初始化一个等待队列项
  4. DECLARE_WAITQUEUE(name, tsk)
  5. //name:就是等待队列项的名字,
  6. //tsk:表示这个等待队列项属于哪个任务(进程),一般设置为current,
  7. //在Linux内核中 current相当于一个全局变量,表示当前进程。
  8. //因此宏DECLARE_WAITQUEUE就是给当前正在运行的进程创建并初始化了一个等待队列项。
  9. //等待队列项添加
  10. void add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait)
  11. //q:等待队列项要加入的等待队列头。
  12. //wait:要加入的等待队列项。
  13. //等待队列项移除
  14. void remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait)
  15. //q:要删除的等待队列项所处的等待队列头。
  16. //wait:要删除的等待队列项。
  17. struct __wait_queue {
  18. unsigned int flags;
  19. void *private;
  20. wait_queue_func_t func;
  21. struct list_head task_list;
  22. };
  1. //4、如果数据准备好了,就唤醒
  2. condition = 1; //设置为真,代表数据准备好了
  3. void wake_up(wait_queue_head_t *q)
  4. void wake_up_interruptible(wait_queue_head_t *q)

3.2.1 wait_event_interruptible()函数解析

这个函数做了上述自己定义等待队列项,添加等待队列项的操作,所以稍微解析一下

  1. wait_event_interruptible(wq, condition)
  2. ->
  3. __wait_event_interruptible(wq, condition);
  4. ->
  5. ___wait_event(wq, condition, TASK_INTERRUPTIBLE, 0, 0, schedule())
  6. {
  7. //定义了一个等待队列项
  8. wait_queue_t __wait;
  9. //初始化了一个等待队列项
  10. init_wait_entry(&__wait, exclusive ? WQ_FLAG_EXCLUSIVE : 0);
  11. for (;;) {
  12. //将等待队列项放在队列头的队列尾
  13. //将task_struct->state = TASK_INTERRUPTIBLE
  14. prepare_to_wait_event(&wq, &__wait, state);
  15. //等待数据到来,退出循环
  16. if (condition)
  17. break;
  18. //传入的cmd为schedule(),即主动放弃CPU
  19. cmd;
  20. }
  21. //删除等待队列项
  22. //将进程状态设置为运行态
  23. finish_wait(&wq, &__wait);
  24. }
  25. typedef struct __wait_queue wait_queue_t;
  26. void init_wait_entry(wait_queue_t *wait, int flags)
  27. {
  28. wait->flags = flags;
  29. wait->private = current; //current是当前进程的结构体
  30. wait->func = autoremove_wake_function; //唤醒的函数
  31. INIT_LIST_HEAD(&wait->task_list); //初始化队列的指针
  32. }
  33. long prepare_to_wait_event(wait_queue_head_t *q, wait_queue_t *wait, int state)
  34. {
  35. unsigned long flags;
  36. long ret = 0;
  37. spin_lock_irqsave(&q->lock, flags);
  38. //判断是否为信号引起的唤醒
  39. if (unlikely(signal_pending_state(state, current))) {
  40. list_del_init(&wait->task_list);
  41. ret = -ERESTARTSYS;
  42. } else {
  43. if (list_empty(&wait->task_list)) {
  44. if (wait->flags & WQ_FLAG_EXCLUSIVE)
  45. __add_wait_queue_tail(q, wait);
  46. else
  47. //将等待队列项加入等待队列
  48. __add_wait_queue(q, wait);
  49. }
  50. //设置传入状态TASK_INTERRUPTIBLE
  51. set_current_state(state);
  52. }
  53. spin_unlock_irqrestore(&q->lock, flags);
  54. return ret;
  55. }
  56. void finish_wait(wait_queue_head_t *q, wait_queue_t *wait)
  57. {
  58. unsigned long flags;
  59. //将进程状态设置为运行态
  60. __set_current_state(TASK_RUNNING);
  61. if (!list_empty_careful(&wait->task_list)) {
  62. spin_lock_irqsave(&q->lock, flags);
  63. //删除等待队列项
  64. list_del_init(&wait->task_list);
  65. spin_unlock_irqrestore(&q->lock, flags);
  66. }
  67. }

3.3 阻塞驱动程序

一般来说,都是在中断函数中去唤醒,但是写个中断又有点麻烦,所以这里采用的是另起一个进程,在write函数中去唤醒进程。

  1. //定义等待队列头
  2. wait_queue_head_t wq;
  3. int condition = 0;
  4. ssize_t mycdev_read(struct file *file, char __user * ubuf, size_t size, loff_t * offs)
  5. {
  6. ret = wait_event_interruptible(wq, condition);
  7. ret = copy_to_user(ubuf, kbuf, size);
  8. //将condition设置为假
  9. condition = 0;
  10. return size;
  11. }
  12. ssize_t mycdev_write(struct file *file, const char __user * ubuf, size_t size, loff_t * offs)
  13. {
  14. ret = copy_from_user(kbuf, ubuf, size);
  15. //唤醒
  16. condition = 1;
  17. wake_up_interruptible(&wq);
  18. return size;
  19. }
  20. const struct file_operations fops = {
  21. .read = mycdev_read,
  22. .write = mycdev_write,
  23. };
  24. static int __init mycdev_init(void)
  25. {
  26. //初始化等待队列头
  27. init_waitqueue_head(&wq);
  28. }

4、将非阻塞打开的文件设置阻塞

  1. int flags = fcntl(fd, F_GETFL);
  2. fcntl(fd, F_SETFL, flags & ~O_NONBLOCK); /* 阻塞方式 */

5、一些知识点理解

1、阻塞与非阻塞是对于文件而言的,而不是指read、write等的属性。

2、同步IO操作定义为导致进程阻塞直到IO完成的操作,反之则是异步IO

3、非阻塞IO,它只有是检查无数据的时候是非阻塞的,在数据到达的时候依然要等待复制数据到用户空间(等着水将水杯装满),因此它还是同步IO。

4、阻塞IO模型(进程在内核状态下等待)使用recv的默认参数一直等数据直到拷贝到用户空间,这段时间内进程始终阻塞。

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

闽ICP备14008679号