高级IO,阻塞于非阻塞

1、1、非阻塞IO

1.阻塞与非阻塞

(1)阻塞:就是当前的函数要执行的话,需要某些条件,但是没有达到,就被阻塞住,内核挂起,当前进程暂停。CPU被拿去运行别的进程了。比如父进程执行wait这个阻塞函数,等待子进程结束后,去回收子进程剩余的8KB内存资源,如果这个时候子进程没有结束,父进程的wait就会被阻塞,因为条件没有达到,这个时候父进程就暂停了,内核就挂起了,CPU的时间就去执行别的进程了,不在父进程这里耗了,当子进程结束的时候,OS会给子进程的父进程发送一个SIGCHILD信号,这时父进程就被唤醒了,wait函数执行就将子进程回收掉了

(2)常见的阻塞:

wait、sleep、pasue等函数;

read或write写文件的时候

(3)阻塞的好处:有利于操作系统的性能的发挥。因为阻塞住了进程会暂停,CPU可以去做别的事情,并且要完成的任务在一定情况下也会被完成的。

(4)但是一个进程中,进行多路IO操作的时候,有时阻塞式的就不好了,比如你当前进程排在前面的IO,由于因为你被阻塞住了,导致你的当前进程陷入了休眠状态,虽然你将CPU的时间让给了别的进程了,但是对于你自己的进程来说,你自己进程的剩下那些IO操作就要等你前面的IO操作完了之后才能进行,这样就会降低了你当前的一个进程中的工作效率了,因为你排在前面的IO操作没有进行完,你排在后面的IO操作是不肯能得到运行的,都是在一个进程中的。这个时候阻塞就是不好的了,所以有时也需要非阻塞式的操作。

(5)非阻塞式的操作,当你一个进程中进行多路IO的操作的时候,你排在前面的IO操作,由于你没有用阻塞式的操作,而是非阻塞式的操作,所以即使你的条件没有达到你也不会让整个进程陷入沉睡暂停状态,而是直接返回,直接执行这个进程的下面的内容,这样对于当前进程的别IO来说,别的进程的执行效率就加大了,这种情况往往是在IO操作设备文件的时候,比如你在读鼠标这个设备文件,就因为你这读这个设备文件的时候被阻塞住了,而影响到了其他的设备文件的IO操作,这样可想而知,真的不好

(6)如何实现非阻塞呢,就是在打开文件的时候加上O_NONBLOCK这个标志属性和fcntl,fcntl可以读出一个文件的flag,然后加上O_NONBLOCK这个标志,在将这个flag放回去

2、用read读取键盘,来看阻塞的感觉

(1)因为键盘是标准输入设备,对应的文件描述符是0,在每个进程中都是默认被打开的,所以我们直接read(0, buf, 2);直接读这个键盘就行,读取两个字节放到buf中,我们执行这个时候,当我们键盘没有输入东西完毕按回车的时候(因为Linux的控制台是行缓冲的,你没有按下回车的时候,只是把你输入的东西放到控制台上显示,还没有送到read中,你按下回车后才会被输入进去。),当我们没有输入东西按回车的时候,发现是被阻塞住的,被卡住了,不动了,这就是阻塞的感觉,这个时候当前的进程被暂停了,因为没有等待条件,read读取不到东西,当我们输入东西按下回车的时候,当前的进程就被唤醒了,read读取到东西了

3、read读取鼠标

(1)首先鼠标这个设备文件是需要我们打开的,才能得到文件描述符fd,鼠标的设备文件在/dev/input/mouse0或者可能是/dev/input/mouse1

4、先读鼠标在键盘的情况

(1)我们程序在设计的时候,是先读取鼠标这个设备文件,在读取键盘这个标准输入文件,我们会发现,由于我们是阻塞式的,所以我们在鼠标没有读取成功的时候,键盘输入了东西敲回车也不好使,只有当鼠标读取完了之后,才会去读取键盘,这就是阻塞式的坏处,解决方法就是非阻塞

5、并发式IO的解决方案(就是一个进程中对多个IO进行操作时引发的问题,就是上面的4的问题)

(1)解决方案有三个

(2)非阻塞式IO

@1:首先将键盘这个文件设置成非阻塞式的,因为键盘这个标准输入,是在每一个进程都打开的,所以我们无法直接用open这个函数去打开这个文件,将其中的flag加上O_NONBLOCK这个标志属性

所以我们要用fcntl这个函数来去利用文件描述符fd,去将这个文件的属性进行改成非阻塞的。

int fcntl(int fd, int cmd, ... /* arg */ );

@2:第一个参数是文件描述符

@3:第二个参数是要的功能,我们的cmd用的宏是F_GETFD (void) F_SETFD (int),这两个。

@4:利用F_GETFD这个cmd宏,将fd这个文件的flag读取出来,之后用F_SETFD这个cmd宏将O_NONBLOCK这个非阻塞式的属性添加上。

@5:先flag = fcntl(0, F_GETFD);获取原来的这个文件的flag,然后 flag |= O_NONBLOCK;位或一个O_NONBLOCK。然后在fcntl(0, F_SETFD, flag);将属性在写到这个fd中

6、多路复用IO

(1)IO多路复用原理(IO multiplexing),作用和非阻塞IO有点像,是解决并发式IO的问题的

(2)什么时候用多路复用IO呢,在多路IO都是非阻塞式的情况下。为了应对多路非阻塞IO,CPU要一直不停的去轮询这些非阻塞IO,因为是轮询,所以必然要消耗时间,有空档。所以有了多路复用IO。

(3)多路复用IO涉及到的两个函数:select和poll

(4)IO多路复用的实现原理就是:外部阻塞式,内部非阻塞式自动轮询多路阻塞式IO。

@1:外部阻塞式的意思是说,select和poll这两个函数本身是阻塞式的

@2:内部非阻塞式,意思是说,select和poll内部在轮询几路IO的时候是非阻塞式的。

@3:select类似于一个监听的函数,比如,当A这个鼠标IO,和B这个键盘IO,我们用select去监听这两个IO,我们的鼠标IO,和键盘IO,都是阻塞式的,并没有设置成非阻塞式的,当我们调用select去监听的时候,当鼠标IO事件和键盘IO事件都没有发生的情况,select本身就是阻塞式的,select会一直在这里等待他监听的两个IO事件其中至少发生一个,select去监听这个两个IO的时候,是以非阻塞式的方式去轮询监听的,这就是外部阻塞式,内部非阻塞式自动轮询多路阻塞式IO。select函数表现为对外是阻塞式的,对内部监听的A和B这两个阻塞式IO表现为非阻塞式的方式去轮询A和B这两个阻塞式IO。只要A或者B有一个IO中来数据了,select就会被唤醒,select就会向外面汇报,在没数据时,select是阻塞式的,select所在的进程变成了暂停状态

(5)select函数

int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval

*timeout);

@1:第一个参数表示文件描述符的个数,这个值我们应该设置select内部要监听的多路IO中的那个文件描述符最大的那一个的fd+1,因为文件描述符是从0开始的,所以要加1。

@2:第二个参数中的fd_set是多个文件描述符的集合,是一个结构体。select这个函数在运行的时候,是区分文件描述符所要进行的操作的,所以第二个参数是传递了一个要读的文件描述符,是要对多路文件描述符中那些要读的文件描述符。第三个参数是传递多路文件描述符中要写的那些文件描述符。第四个参数是那些发生异常的文件描述符,都是输出型参数。第五个参数是设置超时时间的,因为select是阻塞式的,所以当监听的IO没有IO事件时,select就一直被阻塞了,卡住了,当前进程就一直暂停了,所以有时不想select一直被阻塞住,所以有了这个第五个参数,用来设置超时的,时间到了,select就会被唤醒返回

@3:返回值,当select成功的时候,就select监听的内部有IO事件发生的时候,select返回,这个返回值就是表示select内部有几个IO事件发生了,如果返回值是0就表示是超时了,没有监听到任何的IO事件发生自己超时后返回了,返回值是0,如果返回值是-1,就表示select函数运行错误了

@4:void FD_CLR(int fd, fd_set *set);//将fd,从fd_set中清除出去

int  FD_ISSET(int fd, fd_set *set);//判断fd,在fd_set中有没有被置位,发生了IO事件对应fd

//会被置位返回值是1,返回值0表示没有发生这个fd的IO

//事件

void FD_SET(int fd, fd_set *set);//将fd,添加到fd_set中

void FD_ZERO(fd_set *set); //是用来将fd_set这个集合中文件描述符置位的清除置位

这四个函数是用来操作fd_set这个文件描述符集合的

(6)poll函数

int poll(struct pollfd *fds, nfds_t nfds, int timeout);

@1:第一个参数是输出型参数,是文件描述符的集合,第二个参数是poll函数内部监听的多路IO的最大的文件描述符fd+1,第三个参数是超时时间,是毫秒级别的

@2: struct pollfd {

int   fd;         /* file descriptor */

short events;     /* requested events */

short revents;    /* returned events */

};

我们给poll传递第一个参数的时候,要设置这个结构体的fd和events,fd表示我们要监测哪个fd,events表示要监测这个fd的什么事件

@3:events如果是

POLLIN 表示监测的是读

POLLOUT 表示监测的是写

@4:revents是内核设置的,当监测的IO事件发生POLLIN或者POLLOUT,我们可以判断events和revents是否相等来确定是否发生我们想要的IO事件

@5:返回值,当poll成功的时候,返回值是一个正数,非零,表示内核帮我们设置了一个revents,表示有IO事件发生,有几路IO事件发生了,返回值就是几,超时时返回0,返回-1表示poll函数运行错误了,和select的返回值定义是一样的

7、多路复用IO的代码实战

(1)select函数实现的读取键盘和鼠标的IO多路复用

(2)poll函数实现的读取键盘和鼠标的IO多路复用

8、异步通知(异步IO)

(1)什么是异步IO,可以认为是操作系统用软件实现的一种软件中断响应系统

(2)异步IO的工作方法就是,当前进程在做自己该做的事情,当有一个异步IO的信号SIGIO的时候,就会去执行这个异步IO信号对应的信号处理函数。

(3)所以就是一个进程中向操作系统注册了一个异步IO的信号对应的信号处理函数,用signal或者sigaction注册了一个SIGIO对应的信号处理函数,当前进程可以做想做的事情,当有异步IO事件发生了的时候,操作系统会给当前进程发一个SIGIO的异步信号,进程接受到这个SIGIO信号后就会去执行这个信号对应的信号处理函数。就跟被中断了一样,去执行了中断处理函数一样,只不过这个中断是由信号来产生的。

(4)异步IO涉及到的函数:

fcntl(主要是设置一些异步通知,F_GETFL,F_SETFL,O_ASYNC,F_SETOWN)

@1:F_GETFL来获取flag属性信息

@2:F_SETFL来设置flag属性信息

@3:O_ASYNC来指示当前的文件描述符可以被接受异步通知,也是一个flag文件属性,将文件描述符添加这个属性

@4:F_SETOWN用这个告诉当异步IO事件发生的时候让OS通知谁,fcntl带上这个参数后,后面一个变参要加上当前进程的pid,可以getpid来获得,表示发生异步IO事件的时候OS通知当前的这个进程

signal或者sigaction(绑定SIGIO对应的信号处理函数的)

8、存储映射IO

(1)mmao函数

void *mmap(void *addr, size_t length, int prot, int flags,

int fd, off_t offset);

把一个文件和一个内存区域映射起来。

(2)在LCD显示的时候和IPC共享内存通信的时候会用到

比如,你的LCD要显示的图片的话,是和驱动层的一个LCD驱动中划分出来的一个物理内存中的显存区域,将这个内存区域设置到LCD控制器中的一个参数中,当这个内存区域有一个图片的数据的时候,硬件就会自动刷新到LCD屏幕去显示,但是我们现在是在应用层下,我们在文件系统中的一个打开了一个图片文件,得到了这个图片的数据,我们要将这个打开的图片的数据,也就是要将一块内存复制到LCD的显存的那一个内存区域中,这样每次都直接复制是很浪费CPU时间的,所以就可以mmap将LCD的显存内存区域和我们的这个打开这个文件时的内存区域映射起来,这样就相当于关联起来了,实际上就变成了一个内存区域了,

时间: 2024-12-24 16:29:15

高级IO,阻塞于非阻塞的相关文章

五种I/O 模式——阻塞(默认IO模式),非阻塞(常用语管道),I/O多路复用(IO多路复用的应用场景),信号I/O,异步I/O

五种I/O 模式——阻塞(默认IO模式),非阻塞(常用语管道),I/O多路复用(IO多路复用的应用场景),信号I/O,异步I/O 五种I/O 模式:[1]        阻塞 I/O           (Linux下的I/O操作默认是阻塞I/O,即open和socket创建的I/O都是阻塞I/O)[2]        非阻塞 I/O        (可以通过fcntl或者open时使用O_NONBLOCK参数,将fd设置为非阻塞的I/O)[3]        I/O 多路复用     (I/O

Java中的阻塞和非阻塞IO包各自的优劣思考

NIO 设计背后的基石:反应器模式,用于事件多路分离和分派的体系结构模式. 反应器(Reactor):用于事件多路分离和分派的体系结构模式 通常的,对一个文件描述符指定的文件或设备, 有两种工作方式: 阻塞 与非阻塞 .所谓阻塞方式的意思是指, 当试图对该文件描述符进行读写时, 如果当时没有东西可读,或者暂时不可写, 程序就进入等待 状态, 直到有东西可读或者可写为止.而对于非阻塞状态, 如果没有东西可读, 或者不可写, 读写函数马上返回, 而不会等待 . 一种常用做法是:每建立一个Socket

阻塞与非阻塞IO -- 网络编程随想

阻塞和非阻塞IO 阻塞IO指当进行IO操作时, 如果IO操作无法立即完成,当前线程进入阻塞状态,直到IO操作完成,IO函数返回. 非阻塞IO指当进行IO操作时,如果IO操作无法立即完成,IO函数立即返回,线程不会阻塞. 写与读操作对阻塞与非阻塞IO的语义 写操作,只有完成所有指定数据的写入时,写操作才算完成. 读操作,只要能读取到数据,读操作就算完成. 阻塞IO 写操作.len 为指定写入的数据量. 如果只写入部分数据,IO函数会阻塞直至写入数据或发生错误才返回. 以soket的send()为例

阻塞与非阻塞IO step by step

谈到IO,阻塞.非阻塞,异步.同步是绕不开的话题.说实话,我也没搞清楚,网上查了许多资料,大家众说纷纭,一种比较靠谱的说法是:”在处理 IO 的时候,阻塞和非阻塞都是同步 IO,使用使用了特殊的API才是异步IO“.知乎的回答相对来说可信度高点,大家姑且可以先看着: http://www.zhihu.com/question/19732473 这些资料大多说理,我还是想通过一些例子,以我们看到到摸得着的方式,慢慢搞懂阻塞.非阻塞以及异步同步间的关系.所以这一系列将是我的读书笔记,因为我也摸着石头

IO模式——同步(阻塞、非阻塞)、异步

不少人把同步.异步.阻塞.非阻塞放到一起讨论,很多时候难以区分. 这里从根上剖析下该怎么看待这几个概念. 首先,异步和同步是相对的,而同步情况下又有阻塞和非阻塞之分. 异步很容易理解.当用户程序需要进行IO的时候,发出IO请求,然后就立刻返回,可以继续做其它事情. 例如,从网络收包,当包抵达后放到内核某个缓存区,并且从内核空间放置到程序需要的用户空间后(一种是直接复制,比较费资源:一种是映射mmap),通知程序,程序之后就可以处理数据了. 这是最理想的模式,CPU干活效率最高. 同步情况则是,当

[Z] IO - 同步,异步,阻塞,非阻塞 (亡羊补牢篇)

原文链接:http://blog.csdn.net/historyasamirror/article/details/5778378 当你发现自己最受欢迎的一篇blog其实大错特错时,这绝对不是一件让人愉悦的事.<IO - 同步,异步,阻塞,非阻塞 >是我在开始学习epoll和libevent的时候写的,主要的思路来自于文中的那篇link .写完之后发现很多人都很喜欢,我还是非常开心的,也说明这个问题确实困扰了很多人.随着学习的深入,渐渐的感觉原来的理解有些偏差,但是还是没引起自己的重视,觉着

IO中同步异步,阻塞与非阻塞 -- 原理篇

再补一篇高手写的理论分析,便于更深刻理解 转自:http://blog.csdn.net/historyasamirror/article/details/5778378 ============================================================= 同步(synchronous) IO和异步(asynchronous) IO,阻塞(blocking) IO和非阻塞(non-blocking)IO分别是什么,到底有什么区别?这个问题其实不同的人给出的答

网络IO之阻塞、非阻塞、同步、异步总结

1.前言 在网络编程中,阻塞.非阻塞.同步.异步经常被提到.unix网络编程第一卷第六章专门讨论五种不同的IO模型,Stevens讲的非常详细,我记得去年看第一遍时候,似懂非懂,没有深入理解.网上有详细的分析:http://blog.csdn.net/historyasamirror/article/details/5778378.我结合网上博客和书总结一下,加以区别,加深理解. 2.数据流向 网络IO操作实际过程涉及到内核和调用这个IO操作的进程.以read为例,read的具体操作分为以下两个

简述linux同步与异步、阻塞与非阻塞概念以及五种IO模型

1.概念剖析 相信很多从事linux后台开发工作的都接触过同步&异步.阻塞&非阻塞这样的概念,也相信都曾经产生过误解,比如认为同步就是阻塞.异步就是非阻塞,下面我们先剖析下这几个概念分别是什么含义. 同步:所谓同步,就是在发出一个功能调用时,在没有得到结果之前,该调用就不返回.也就是必须一件一件事做,等前一件做完了才能做下一件事. 例如普通B/S模式(同步):提交请求->等待服务器处理->处理完毕返回 这个期间客户端浏览器不能干任何事 异步:异步的概念和同步相对.当一个异步过程