阻塞和非阻塞
广泛上的区别就是 应用程序如果非阻塞那读取不到数据就应该马上有返回值
阻塞的话就是在应用程序去read数据,但是设备驱动没有数据,就一直卡住,直到有数据再继续往下
补充阻塞知识,应用层大部分都是阻塞
如果要非阻塞 ,应用程序在打开设备节点的时候填写int fd = open(“/dev/key0”, O_RDWR|O_NONBLOCK);,这里指定了
希望对这个节点后续的操作都是非阻塞
此时会把这个标志位传到驱动层,驱动层应该根据上面的标志位,使用正确的方法来返回值
所以read时候阻塞还是非阻塞取决于驱动的实现了
等待队列
等待队列是进程打开这个文件指定了阻塞,应用程序调用read()
read的时候因为没有数据,驱动把这个进程放入等待队列
驱动初始化应该建立一个等待队列列表,每个访问设备的进程都是一个队列项
设备不可用的时候就要将这些进程对应的等待队列项添加到等待队列里面
添加到等待队列头中以后进程才能进入休眠态
最后中断产生了数据,使用void wake_up_interruptible() 唤醒队列
队列中所有存的进程继续之前休眠前的代码
poll
可以理解poll对应的就是非阻塞了
应用程序,可以打开多个设备,使用poll进行查询数据,如果有数据
再调用read(),主打一个调用read()马上有结果
所以poll算是一个监听者
注意的点,关于休眠
阻塞是应用程序调用驱动的read,驱动发现没有数据返回给read
那驱动使用 wait_event_interruptible(key_dev->wq_head, key_dev->have_data);
上面这个函数相当于 下面三个函数
__set_current_state(TASK_INTERRUPTIBLE); //设置当前进程为休眠可中断
add_wait_queue(wait_queue_head p,wait_queue_t wait);//但是要把这个进程的信息放入等待队列中
schedule();//开始调度
而在poll中,调用到驱动的poll
执行 poll_wait(filp, &key_dev->wq_head, pts); 只是为了把当前进入放入等待队列中,不会触发休眠
休眠时第一次执行完驱动poll后,回到VFS层,vfs层帮进程休眠的
问:为啥poll还有个等待队列?
答:上面vfs层帮你休眠了,那数据来了,我怎么让你这个进程醒过来,还是得唤醒等待队列啊
poll的流程
第一步需要初始化并且创建一个等待队列,放在驱动中
第二步: app打开一些文件节点,现在这些文件节点就是我们要用poll监控的节点,
把需要监控的数据内容(POLL_IN,POLL_OUT)等和节点组成数组pds[]
应用先 poll(pfds, 2, 5000); //5s没有数据就超时 加pfds表
第三步
应用程序在while中调用poll
系统调用 ->内核的vfs poll ->驱动poll
第四步:
触发了软中断,调到vfs层,这里vfs贴心的提供了一个for循环,会第一次调用到驱动的中的poll
第五步:
到驱动层
此时调用了 poll_wait(filp, &key_dev->wq_head, pts);
把驱动中注册的等待队列头,和pts poll的一个表格进行关联,这时候等待队列中就存放了当前的进程了
假设不放入队列里,那以后发生中断时,中断服务程序去哪里找到你嘛?
注意此时还没有休眠,还得继续往走回到VFS层的
第6步:
此时自己的驱动程序返回为0,说明这个时候没有数据,回到VFS层
开始休眠
从现在开始分成两种情况
1:休眠超时情况,刚刚传入到vfs层中说应用程序只等待5s
那5s到了,在vfs层中的for循环开始新的一轮,又回到了第四步
此时是第二次进入
因为这个进程放进去过一次等待队列了,这次就不继续放了
此时还是没有数据,需要返回了
返回给应用层,没有数据,应用层继续往下执行把
第二种情况有中断产生,能返回数据
第7步,中断发生,中断里有函数wake_up_interrupt(wq_head)
这时候所有在 等待队列里的进程都被唤醒,继续往下执行
回到第4步骤,图中的黄色部分,因为被中断唤醒,进程继承从这个地方执行
再次回到for循环
进入第五步
再次调用到驱动中的poll,这时候检测到右数据,那么驱动poll,会返回POLL_IN这些掩码
传输到VFS层再到应用层
最后到第8步
再次调用到驱动中的poll,这时候检测到右数据,那么驱动poll,会返回POLL_IN这些掩码
传输到VFS层再到应用层,应用层的poll返回了有数据,根据返回值判断是哪个驱动有数据,并且是什么数据
应用层继续调用read读取数据