阻塞式io:在我个人看来这个io就是读写文件的意思,阻塞式io就是阻塞式读写文件,当一个App要读写内核驱动的文件时可以将其认为是一个用户进程。这个用户进程开始前App会向内核驱动发出读写请求,此时内核驱动会检查自身是否能够被读写,假如此时内核驱动被别的App占用或者还没有准备好要被读取的数据时,App就会一直等待内核驱动做好准备知道能够读写为止,这个等待的过程就是进程进入休眠,进入休眠的进程会释放cpu资源,也就是cpu的时间片调度不会运行该进程,cpu在执行多个进程时是时间片切换执行的,它们共用cpu的时间,当某个进程进入休眠,cpu就不会给它分配时间片,这就是阻塞式io。
阻塞式io首先要在设备结构体里面添加一个等待队列头,如下所示
然后需要对这个队列头做一个初始化,可以放在入口函数里面
接下来就是等待事件,这个函数有wait_event_interruptible和wait_event两个版本,它们的第一个参数是等待队列头,第二个参数是等待条件,当条件为真时用户进程不会进入休眠继续执行下面的程序,当为假时用户进程进入休眠,释放cpu资源,这两个版本的区别就是,一个可以被中断信号打断,例如用户输入Ctrl+c就可以打断用户进程的休眠状态,另一个则不能,该函数一般放在内核驱动的读写操作函数中。
最后就是唤醒进程wake_up,它可以把进入休眠的进程唤醒,一般是在判断等待事件的条件为真时使用该函数来唤醒进程,它同样有wake_up和wake_up_interruptoble两个版本,跟上面的两个等待事件成对使用
非阻塞式io:非阻塞式io就是进程不会发生阻塞,也就是进程不会进入休眠,它会直接向内核驱动进行读写操作,当内核驱动没准备好时,它就会获得一个错误码,然后继续进程接下来的程序,同时它也会再次询问内核驱动有没有准备好,也就是轮询检查内核驱动的io状态。
实现非阻塞式io要在文件操作集中添加poll函数,然后定义这个函数,这个函数的返回值就是事件标志,它用来告知用户进程可以进行读写操作。这里面的poll_wait函数是将当前进程放入等待队列,此时进程会进入休眠,按理来说这应该是阻塞了的,不知道为什么非阻塞式io会用到它。
在应用端需要以非阻塞的方式打开文件
所谓的轮询就就是用一个while循环去反复执行select函数,这个函数很复杂,还有文件描述符这样一个抽象的概念,对我来说目前实在很难理解,下面只是个人不准确的看法。每个打开的文件都有一个文件描述符,通过FD_SET把当前应用打开的内核文件的描述符添加到某个需要关注的描述符集里面,例如应用想要读取内核驱动数据就要关注readfds,这就是select函数中的readfds参数,需要就传入,不需要的就给它一个NULL,这个过程就是应用程序告诉内核驱动readfds里面的文件描述符的文件是应用程序需要读的,去问内核这些文件准备好了没有,通过while循环不停的去问,当这些文件准备就绪后内核驱动就会向应用程序返回这些文件描述符,应用程序载进行相应的读写操作,注意这个timeout的意思是应用程序在这个时间段内会等待内核驱动去返回就绪的文件描述符,如果都没有就会继续执行后续的程序。也不知道自己在讲什么,以后理解透了再改吧。这个select返回值0表示超时,-1表示错误,其他表示可进行文件操作。
异步通知:异步通知是指内核驱动准备就绪时就给应用发出信号,应用接收到信号后开启异步通知然后执行信号处理函数,这个过程类似中断。
实现异步通知首先要在设备结构体中添加一个fasync结构体指针
在文件操作集里添加fasync函数,并定义该函数,fasync_helper函数用来注册一个内核驱动与应用程序的异步通知。
内核驱动通过kill_fasync函数发送信号,这里面SIGIO就是普通文件读写信号,POLL_IN是事件类型标志,当内核驱动文件描述符可读时就会设置这样一个标志
最后在释放文件函数中调用fasync函数并传入下面的参数来注销异步通知
应用层就是使用signal函数来接收信号并定义一个信号处理函数,在执行信号处理函数前还需要使用fcntl函数来让当前进程接收对应的信号和开启异步通知
信号处理函数就是应用程序要执行的任务,这里的num是信号编号
应用场景:
阻塞式IO应用于实时性要求不高,程序简单的场景,优点是编程简单,缺点是多个并发连接时效率不高
非阻塞式IO应用于多个并发连接的场景,它的优点就是多个并发连接时效率高,但是编程复杂
异步通知应用于实时性要求高的场合,例如状态变化的实时响应,它的优点就是实时性高,不需要轮询和阻塞,缺点就是需要考虑并发与竞争