I/O相关概念记录

2019-03-14  本文已影响0人  whupenger

I/O模式

对于一次IO访问(以read举例),数据会先被拷贝到操作系统内核的缓冲区中,然后才会从操作系统内核的缓冲区拷贝到应用程序的地址空间。所以说,当一个read操作发生时,它会经历两个阶段:

等待数据准备 (Waiting for the data to be ready)
将数据从内核拷贝到进程中 (Copying the data from the kernel to the process)
正式因为这两个阶段,linux系统产生了下面五种网络模式的方案。

阻塞I/O

在linux中,所有的socket默认都是blocking的


当用户进程调用了recvfrom这个系统调用,kernel就开始了I/O的第一个阶段:准备数据(对于网络IO来说,很多时候数据在一开始还没有到达。比如,还没有收到一个完整的UDP包。这个时候kernel就要等待足够的数据到来)。这个过程需要等待,也就是说数据被拷贝到操作系统内核的缓冲区中是需要一个过程的。而在用户进程这边,整个进程会被阻塞(当然,是进程自己选择的阻塞)。当kernel一直等到数据准备好了,它就会将数据从kernel中拷贝到用户内存,然后kernel返回结果,用户进程才解除block的状态,重新运行起来

非阻塞I/O(nonBlocking I/O)

在linux下,可以通过设置socket使其变成non-blocking。当对一个non-blocking socket执行读操作时,流程如下:


当用户进程发出read操作时,如果kernel中的数据还没有准备好,那么它并不会block用户进程,而是立刻返回一个error。从用户进程角度讲 ,它发起一个read操作后,并不需要等待,而是马上就得到了一个结果。用户进程判断结果是一个error时,它就知道数据还没有准备好,于是它可以再次发送read操作。一旦kernel中的数据准备好了,并且又再次收到了用户进程的system call,那么它马上就将数据拷贝到了用户内存,然后返回

I/O多路复用(I/O multiplexing)

IO多路复用指内核一旦发现进程指定的一个或者多个IO条件准备读取,他就通知该进程,有时候也被称为事件驱动I/O(event driven I/O)

IO多路复用适用于以下场景:

与多进程和多线程相比,IO多路复用的优势是系统开销小,系统不必创建线程或者进程,也不必维护这些线程或进程

IO多路复用就是通过一种机制,一个进程可以监控多个描述符,一旦某个描述符就绪(读/写就绪),能够通知程序进行相应的读写操作

目前支持I/O多路复用的系统调用有select,poll,epoll,pselect,本质上都是同步I/O,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的

select

select函数监视的文件描述符有3类,分别是writefds,readfds,exceptfds。调用后select函数会阻塞,直到有描述符就绪(有数据读、写、except)或者超时,函数返回。当select函数返回后,可以遍历fdset,来找到就绪的描述符。

select本质上是通过设置或检查描述符标志位的数据结构进行下一步处理,这样带来几个缺点:

select调度过程
poll

poll本质上和select没有区别,它将用户传入的数组拷贝到内核空间,然后查询每个fd对应的设备状态,如果设备就绪则在设备等待队列中加入一项并继续遍历,如果遍历完所有fd后没有发现就绪设备,则挂起当前进程,直到设备就绪或者主动超时,被唤醒后它又要再次遍历fd。这个过程经历了多次无谓的遍历。

epoll

epoll有3个系统调用:

int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event event);
int epoll_wait(int* epfd, struct epoll_event events, int* maxevents, int timeout)

epoll高效的地方还在于:epoll里面有个内核高速cache,被监控的socket在cache里面以红黑树的结构存储,同时,epoll还有个list链表,存储准备就绪的事件

在调用epoll_create时,内核会建立红黑树存储socket,建立链表存储准备就绪的事件;在调用epoll_wait时,仅仅观察这个链表有没有数据,没有数据就sleep,有数据就返回,等到timeout没数据也返回;在执行epoll_ctl时,如果增加socket,则检查红黑树是否存在,存在就立即返回,不存在就添加到红黑树,然后向内核注册回调函数,用于当中断事件来临时向准备就绪的链表中插入数据。

epoll水平触发和边缘触发的实现
epoll_wait返回用户态时是否清空链表,清空了就是边缘触发,未清空就是水平触发

select/poll和epoll的区别:

异步I/O(asynchronous I/O)


用户进程发起read操作之后,立刻就可以开始去做其它的事。而另一方面,从kernel的角度,当它受到一个asynchronous read之后,首先它会立刻返回,所以不会对用户进程产生任何block。然后,kernel会等待数据准备完成,然后将数据拷贝到用户内存,当这一切都完成之后,kernel会给用户进程发送一个signal,告诉它read操作完成了。

最后

就好比去买一件商品
  1)阻塞I/O:你自己跑去商店下单(只能一个一个来),等有了物品还要自己去拿回来
  2)非阻塞I/O:你可以网上下单了,而且网上看有没有货,有的话自己去拿回来
  3)I/O多路复用:和第一种情况差不多,但是这个商店下单窗口多,可以同时多个人跑来下单,谁的货到了,自己来取
  4)异步I/O:你只要网上下个单,其余就不管了。商家不仅发快递,快递小哥还把商品直接送到你家里

上一篇下一篇

猜你喜欢

热点阅读