io模型及java中对应使用

一.linux基础知识讲解

1.32位操作系统为什么只能使用4G的内存

cpu的位是指一次性可处理的数据量是多少,1字节=8位,32位处理器可以一次性处理4个字节的数据量。如果用32位标识内存中的一个地址,那么就只能有2的32次方=4G地址了,所以32位的cpu只能使用4G的内存了。

2.内核空间和用户空间

为了保证用户进程不能直接操作内核,保证内核的安全,操心系统将4G内存划分为两部分,一部分为内核空间,一部分为用户空间。针对linux操作系统而言,将最高的1G字节(从虚拟地址0xC0000000到0xFFFFFFFF),供内核使用,称为内核空间,而将较低的3G字节(从虚拟地址0x00000000到0xBFFFFFFF),供各个进程使用,称为用户空间。每个进程可以通过系统调用进入内核,因此,Linux内核由系统内的所有进程共享。于是,从具体进程的角度来看,每个进程可以拥有4G字节的虚拟空间。

有了用户空间和内核空间,整个linux内部结构可以分为三部分,从最底层到最上层依次是:硬件–>内核空间–>用户空间。

3.数据操作流程

我们都知道,为了OS的安全性等的考虑,进程是无法直接操作I/O设备的,其必须通过系统调用请求内核来协助完成I/O动作,而内核会为每个I/O设备维护一个buffer。
整个请求过程为: 用户进程发起请求,内核接受到请求后,从I/O设备中获取数据到buffer中,再将buffer中的数据copy到用户进程的地址空间,该用户进程获取到数据后再响应客户端。

二.I/O模型

数据流入的两阶段:

  1. 等待数据准备阶段(数据从IO设备进入内核buffer)
  2. 数据从内核buffer被复制到用户空间

根据这两个阶段的阻塞情况,吧I/O划分为五种模型
在这里插入图片描述

1.阻塞I/O

在这里插入图片描述
当用户进程调用了recvfrom这个系统调用,内核就开始了IO的第一个阶段:等待数据准备。对于network io来说,很多时候数据在一开始还没有到达(比如,还没有收到一个完整的TCP包),这个时候内核就要等待足够的数据到来。而在用户进程这边,整个进程会被阻塞。当内核一直等到数据准备好了,它就会将数据从内核中拷贝到用户内存,然后内核返回结果,用户进程才解除block的状态,重新运行起来。
所以,blocking IO的特点就是在IO执行的两个阶段都被block了。

对应java实现:
ServerSocket.accept(), socket.read()

2.非阻塞I/O

在这里插入图片描述
当用户进程调用recvfrom时,系统不会阻塞用户进程,而是立刻返回一个ewouldblock错误,从用户进程角度讲 ,并不需要等待,而是马上就得到了一个结果。用户进程判断标志是ewouldblock时,就知道数据还没准备好,于是它就可以去做其他的事了,于是它可以再次发送recvfrom,一旦内核中的数据准备好了。并且又再次收到了用户进程的system call,那么它马上就将数据拷贝到了用户内存,然后返回。

只有第二阶段阻塞;

当一个应用程序在一个循环里对一个非阻塞调用recvfrom,我们称为轮询。应用程序不断轮询内核,看看是否已经准备好了某些操作,这通常是浪费CPU时间。

对应java实现:
java nio中不使用selecter的情况
需要ServerSocketChannel.configureBlocking(false)配置为非阻塞
serverSocketChannel.accept(),SocketChannel.read()

3.I/O多路复用

就是我们常说的select,poll,epoll
在这里插入图片描述
当用户进程调用了select,那么整个进程会被block,而同时,内核会“监视”所有select负责的socket,当任何一个socket中的数据准备好了,select就会返回。这个时候用户进程再调用read操作,将数据从内核拷贝到用户进程。

select

基本原理:select 函数监视的文件描述符分3类,分别是writefds、readfds、和exceptfds。调用后select函数会阻塞,直到有描述符就绪(有数据 可读、可写、或者有except),或者超时(timeout指定等待时间,如果立即返回设为null即可),函数返回。当select函数返回后,可以通过遍历fdset,来找到就绪的描述符。

缺点:
1、select最大的缺陷就是单个进程所打开的FD是有一定限制的,它由FDSETSIZE设置,32位机默认是1024个,64位机默认是2048。
一般来说这个数目和系统内存关系很大,”具体数目可以cat /proc/sys/fs/file-max察看”。32位机默认是1024个。64位机默认是2048.
2、对socket进行扫描时是线性扫描,即采用轮询的方法,效率较低。
当套接字比较多的时候,每次select()都要通过遍历FDSETSIZE个Socket来完成调度,不管哪个Socket是活跃的,都遍历一遍。这会浪费很多CPU时间。”如果能给套接字注册某个回调函数,当他们活跃时,自动完成相关操作,那就避免了轮询”,这正是epoll与kqueue做的。
3、需要维护一个用来存放大量fd的数据结构,这样会使得用户空间和内核空间在传递该结构时复制开销大

poll

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

它没有最大连接数的限制,原因是它是基于链表来存储的,但是同样有缺点

  1. 大量的fd的数组被整体复制于用户态和内核地址空间之间,而不管这样的复制是不是有意义。
  2. poll还有一个特点是“水平触发”,如果报告了fd后,没有被处理,那么下次poll时会再次报告该fd。

注意:从上面看,select和poll都需要在返回后,通过遍历文件描述符来获取已经就绪的socket。事实上,同时连接的大量客户端在一时刻可能只有很少的处于就绪状态,因此随着监视的描述符数量的增长,其效率也会线性下降。

epoll

基本原理:epoll支持水平触发和边缘触发,最大的特点在于边缘触发,它只告诉进程哪些fd刚刚变为就绪态,并且只会通知一次。还有一个特点是,epoll使用“事件”的就绪通知方式,通过epollctl注册fd,一旦该fd就绪,内核就会采用类似callback的回调机制来激活该fd,epollwait便可以收到通知。

epoll的优点:

  1. 没有最大并发连接的限制,能打开的FD的上限远大于1024(1G的内存上能监听约10万个端口)。
  2. 效率提升,不是轮询的方式,不会随着FD数目的增加效率下降。
    只有活跃可用的FD才会调用callback函数;即Epoll最大的优点就在于它只管你“活跃”的连接,而跟连接总数无关,因此在实际的网络环境中,Epoll的效率就会远远高于select和poll。
  3. 内存拷贝,利用mmap()文件映射内存加速与内核空间的消息传递;即epoll使用mmap减少复制开销。

对应java实现:
java nio中使用selecter的情况

4.信号驱动I/O

在这里插入图片描述

5.异步IO

在这里插入图片描述
当用户进程向内核发起某个操作后,会立刻得到返回,并把所有的任务都交给内核去完成(包括将数据从内核拷贝到用户自己的缓冲区),内核完成之后,只需返回一个信号告诉用户进程已经完成就可以了。

对应java实现:
java.nio.channels.AsynchronousChannel
标记一个channel支持异步IO操作。

java.nio.channels.AsynchronousServerSocketChannel
ServerSocket的aio版本,创建TCP服务端,绑定地址,监听端口等。

java.nio.channels.AsynchronousSocketChannel
面向流的异步socket channel,表示一个连接。

三.I/O模型对比

在这里插入图片描述
结果表明:前四个模型之间的主要区别是第一阶段,四个模型的第二阶段是一样的:过程受阻在调用recvfrom当数据从内核拷贝到用户缓冲区。然而,异步I/O处理两个阶段,与前四个不同。

四.参考网站

https://mp.weixin.qq.com/s?__biz=MzU0MzQ5MDA0Mw==&mid=2247483907&idx=1&sn=3d5e1384a36bd59f5fd14135067af1c2&chksm=fb0be897cc7c61815a6a1c3181f3ba3507b199fd7a8c9025e9d8f67b5e9783bc0f0fe1c73903&scene=21#wechat_redirect

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章