input & output
IO的过程:(1)等待资源就绪;(2)拷贝数据
例如:TCP_socket编程:recv(newsock, buf, sizeof(buf) - 1, 0);
(1) 从TCP的接收缓冲区当中拷贝数据,TCP的数据来源于TCP接收缓冲区。如果TCP接收缓冲区当中没有数据,调用recv函数就会阻塞等待。
(2)拷贝数据到buf当中
LINUX中进程无法直接操作I/O设备,其必须通过系统调用请求kernel来协助完成I/O动作;内核会为每个I/O设备维护一个缓冲区。
对于一个输入操作来说,进程IO系统调用后,内核会先看缓冲区中有没有相应的缓存数据,没有的话再到设备中读取,因为设备IO一般速度较慢,需要等待;内核缓冲区有数据则直接复制到进程空间。
所以,对于一个网络输入操作通常包括两个不同阶段:
(1)等待网络数据到达网卡→读取到内核缓冲区,数据准备好;
(2)从内核缓冲区复制数据到进程空间。
当发起一个IO调用的时候,如果IO资源没有准备好,则阻塞等待资源的到来(死等)。
阻塞IO 钓鱼的例子:人们在钓鱼的时候,将鱼钩抛入水中,眼睛一直盯着鱼漂,直到有鱼咬钩。
(1)一旦调用阻塞IO,阻塞等待的时长取决于内核
(2)在等待过程当中,对于等待的执行流(进程/线程)而言,CPU的利用率是极低的
(3)在等待数据就绪到拷贝数据之间,是非常实时的(及时响应每个操作)
(4)阻塞IO的代码比较简单,容易编写
(5)适用并发量小的网络应用开发,不适用并发量大的应用:因为一个请求IO会阻塞进程,所以,得为每个请求分配一个处理进程(线程)以及时响应,系统开销大。
当发起一个IO调用的时候,判断资源是否准备好
(1)资源准备好了,拷贝数据
(2)资源没有准备好,IO调用报错返回。IO调用返回了,并不一定代表IO操作成功了,需要搭配循环来使用。
(1)非阻塞IO相较于阻塞IO而言,CPU的利用率要高,因为进程轮询(重复)调用,消耗CPU的资源
(2)非阻塞IO由于增加了循环,非阻塞IO的代码会稍微复杂一点
(3)非阻塞IO在数据就绪到拷贝之间,并没有阻塞IO实时
(4)非阻塞IO需要搭配循环来使用
(5)适用并发量较小、且不需要及时响应的网络应用开发
第一个步骤:在代码当中自定义信号(29号信号(SIGIO))的处理方式
第二个步骤:当IO准备就绪之后,进程就会收到SIGIO这个信号,而回调自定义处理函数
第三个步骤:在自定义处理函数的内部发起IO调用
(1)代码更加复杂,牵扯到了信号自定义的处理函数,牵扯到回调
(2)相较于非阻塞IO而言,信号驱动在数据就绪到拷贝之间更加实时了
(3)相较于非阻塞IO而言,不需要循环调用,但是需要将IO调用放到回调函数当中
发起一个IO调用,作用是告诉操作系统内核,想要完成什么IO操作(想要什么样的数据),IO调用直接返回。意味着,IO调用函数就调用完毕了,并没有陷入到阻塞当中
内核针对用户提出来的IO请求,进行等待数据和拷贝数据。当内核等待到数据并且完成拷贝之后,向程序递交指定信号,用户就可以直接拿着数据继续操作了。
由内核在数据拷贝完成时,通知应用程序(而信号驱动是告诉应用程序何时可以开始拷贝数据)
当用户发起一个异步调用的时候,会注册一个回调函数。
当程序等待到数据,并且拷贝完成数据之后,回调注册的函数。用户就相当于拿到了IO调用想要的数据
只需要判断在资源不可用的情况下,发起的IO调用是否直接返回:
阻塞IO:进行死等,IO调用不返回
非阻塞IO:直接报错返回
当前说的同步,并不是多线程当中的同步,多线程当中的同步是为了保证执行流对资源的合理访问
而是只需要判断,当发起一个IO调用之后,数据是由谁拷贝的。
如果数据是由用户自己拷贝的,则表示同步
如果数据是由内核进行拷贝,拷贝完成之后,通知发起IO调用的调用者,则表示为异步。
异步读写调用的接口是aio_read、aio_wirte
(1)不阻塞,数据一步到位
(2)实现、开发应用难度大
(3)非常适合高性能高并发应用
本质上在做:可以一次性监控多个文件描述符,当有某个文件描述符发送了期望的IO事件的时候,则会通知给程序员。期望的IO事件:可读事件/可写事件
后面细讲select、poll、epoll~~~