内存与IO、网络IO到netty

内存与IO、网络IO

Kernel:操作系统内核
VFS:虚拟文件系统,树结构,为什么是虚拟文件系统?因为VFS只是一个目录树,映射到物理地址,挂载
在这里插入图片描述

用df命令可以看出,我的根目录 / 是挂在/dev/mapper/centos_bogon-root(物理地址)目录下的,而/boot是挂在/dev/sda1目录下的,所以VFS只是一个虚拟的目录,这个目录的结构是趋于稳定的,和真实文件的位置不对等,这就是虚拟内存的概念。
FD:文件描述符,Linux系统在两个程序访问同一个文件的时候,,文件只会在内存加载一次,两个程序共享同一份,OS会为两个程序各自维护一个文件描述符,这个文件描述符指向文件的具体位置,每个文件描述符中都有一个seek指针,这个指针代表偏移量。
在这里插入图片描述

Seek偏移量,你每读取一个字节,seek加一,0t0 ->0t1,
每一个文件都有0:标准输入,1:标准输出,2:报错输出。

Dirty:脏,什么是脏?我们知道存放在内存(RAM)中的数据关机时是不会保存的,所以要把内存中的数据刷写到磁盘(ROM),这个还没有刷写的状态就是脏,这个刷写过程肯定不是实时的,它采用的是LRU算法,他可以根据规定的时间进行刷写或者当前数据在内存中所占的比例,超过这个比例也会刷写,那么当程序加载一个文件到内存并修改,这个时候断电,内存中的数据是不会刷写的,就会丢失数据,这是系统内核的刷写方法,每个程序都可以定义自己的刷写方法,例如Redis中的刷写策略。

ln连接命令
硬连接:两个虚拟地址同时指向一个物理地址,一个文件在硬链接状态,删除其中一个引用,对文件没有影响。
软连接:一个虚拟地址指向另一个虚拟地址,另一个虚拟地址指向物理地址,删除被指向的地址,指向的地址报错。
重定向:把你将要进行的输入输出操作重定向到其他操作,和管道不同,管道是传递数据。
输入:<
输出:>
管道:|
当我们在Linux系统中使用管道时,如果管道两端的命令优先级低于管道的优先级,进程先执行管道命令,会在管道的两端各启动一个子进程,而子进程和父进程之间有进程隔离,所以下面的给a赋值的操作会在子进程中执行,子进程执行完后关闭,在父进程中是看不到的,所以才需要配置环境变量,配置环境变量后父子进程都会看到这个文件。
在这里插入图片描述

PageCache:缓存页,
为什么使用PageCache?
减少硬件IO调用,让程序优先使用内存
一个程序处于运行状态,如果它想要读取一段数据,会先去内存的PageCache中读取,如果没有,就会触发缺页中断,OS保护现场,CPU中的缓存数据重新写入内存,从用户态切换到内核态,由协处理器从磁盘上获取数据,这个时候程序处于挂起状态,不受操作系统调度,当数据获取完成又会触发一次中断,程序修改为活跃状态。
进程分配内存:注意:这里说的是虚拟空间。系统给进程分配内存会分配代码区内存、数据区内存、堆、栈,这里的堆是进程的堆,进程会根据你自己配置的Xmx来开辟JVM堆,堆内内存是JVM堆的堆内内存,堆外内存是JVM堆外,如果系统访问这个进程中的堆内存,访问堆外内存的速度比访问堆内内存速度快,为什么?因为访问堆内内存时,JVM会把堆内内存地址拷贝到堆外内存中,而堆外内存地址是可以直接访问的。

文件IO

普通IO和bufferedIO谁快,为什么?
BufferedIO快,因为使用BufferedIO时,JVM虚拟机会维护一块8kb的数组,一次写8kb,8kb满了触发中断,切换内核态,而普通IO是按照字节进行写操作,写完触发中断,写入磁盘,然后继续进行写操作,BufferedIO节省的时间是用户态和内核态切换的时间。
NIO
普通IO是通过系统调用,需要进行用户态和内核态的切换,而NIO不需要经过系统调用,当他调用FileChannel中的map方法(注意:只有文件可以调用map方法),会把文件通过字节流直接映射到一个进程和内存共享的空间,不需要进行用户态和内核态的切换,但是他还是会受到PageCache的约束,会丢数据。可以使用c程序员写的jni扩展库,使用Linux内核的Direct IO,他不会使用Linux系统的PageCache体系,允许你自己创建自己的PageCache,把PageCache私有化,其他进程通过Linux调度不会访问到你自己创建的PageCache,但是PageCache所有的毛病(丢数据/脏)他都有,不过需要你自己去处理。
mmap内存映射
mmap不是系统调用,不需要进行用户态和内核态的切换,换言之他不接受系统调度,直接映射到内存,但是他还是要受到PageCache约束,还是会丢数据。

网络IO

服务端启动时会启动一个监听服务,这个监听会监听你配置连接的端口号,三次握手是通过这个监听服务进行的,当客户端启动时,会随机分配一个端口号去连接服务端,为什么说TCP协议是面向连接的,因为在客户端与服务端三次握手成功后,操作系统会在客户端和服务端同时开辟资源,同时创建socket文件,这个文件就代表了连接。
什么是socket?
Socket是一个四元组,里面包括:客户端IP_客户端端口号+服务端IP_服务端端口号
如果我们写一段代码,把服务器启动后,让他等待键盘输入,这个时候服务端进入IO阻塞状态,客户端启动请求建立连接,这个时候是可以进行三次握手的,而且也可以进行数据的传输,传输完成后存放到缓冲区,缓冲区满了就会丢数据,
那么为什么服务端都已经阻塞住了,客户端还能向服务端建立连接发送数据呢?这是因为数据的传输是在内核态的,他是在内存中开辟资源,由操作系统进行调度,而IO阻塞等待键盘输入,是在用户态的,
它是属于进程自己管理的,所以传输数据没有影响,但是这个时候客户端访问的服务端内存中的文件还没有被分配文件描述符,只是建立了一个连接,并缓存了一些数据,但是程序在阻塞中,还没有主动去调用这个文件,只有程序用到这个文件,这个文件才会被分配文件描述符。

TCP拥塞:TCP协议三次握手的时候会协商一个队列大小,发送数据的时候,会把多个数据包打包发送,如果接受的一方队列满了,就产生数据拥塞,如果不处理,就会丢包,TCP协议会给另一方发数据告诉他队列满了,另一方暂停发送,等待他处理数据包,然后会再发一个数据包告诉他可以继续发送,这就是拥塞控制。

BIO模型(同步阻塞IO)
BIO 服务端启动后,主线程调用accept(系统调用)阻塞,等待客户端连接,当客户端连接时,三次握手完后,主线程再次进行系统调用(clone()),克隆一个子线程,客户端和服务端数据包传输通过子线程进行,传输完成后,子线程关闭,主线程再次进入阻塞状态,这个过程中经过了两次用户态和内核态的切换。循环等待,在客户端读取的时候,等待服务端传输数据也会进行用户态和内核态的切换并阻塞,想要提高效率,可以使用线程池。
BIO使用线程池,预先创建一定数量的线程放到线程池中,不需要频繁的进行系统调用创建线程,但是多个线程传输数据,线程切换还是会进行线程等待,频繁进行系统调用,线程多了之后,消耗CPU资源,所以就有了NIO。
NIO模型(同步非阻塞IO)
服务端启动后,如果没有客户端连接,他不会阻塞的去等待客户端的连接,内核会返回一个-1告诉进程当前没有客户端连接,可以去干别的事情,当客户端连接进来,内核在返回一个客户端的FD,当前线程就处理请求,不会创建新的线程,而BIO在accept方法中阻塞,没有返回值,所以不能继续执行,而在客户端读取的时候虽然不需要进行阻塞,但是还是会进行系统调用,占用CPU资源。
NIO问题:如果你利用线程池一次启动10000个线程,但是你需要发送的数据只用到了1000个线程,在NIO模型中,这一千个线程返回客户端的文件描述符,而剩余的线程会返回-1,他们都会进行系统调用,所以说你只用到了1000个线程,他却调用了10000个,还都是系统调用,切换内核态,消耗CPU资源,所以就有了多路复用器。

多路复用
客户端的程序使用一个多路复用器到服务端获取多个IO的状态,如果某个IO中存在数据包,那么多路复用器就会告诉程序这个IO的FD,然后由程序自己通过FD获取数据包。
NIO多路复用(Select(在所有系统通用)、Poll两种)
NIO模型获取IO状态是一个线程获取一个,他们之间是隔离的,而多路复用会在中间加一个多路复用器,他的作用就是统一获取IO状态,意思就是之前是10000个IO获取10000个线程去获取10000个数据,现在是使用一个多路复用器通过一个线程获取10000个数据。
弊端
每次获取状态都要往服务端发送一个fds数组,通过文件描述符获取文件状态,而且内核都要进行遍历获取状态,进阶EPOLL。
EPOLL也是利用多路复用器原理,使用一个线程获取状态。
EPOLL流程
服务器启动进入listen状态,然后调用epoll_cerate(这个方法只会调用一次)创建一个红黑树文件,调用epoll_ctl来存放文件描述符到红黑树,客户端获取状态时,触发IO中断,进行系统调用,会把红黑树的数据拷贝到List集合中,程序调用epoll_wait获取这个集合。
EPOLL在IO和数据处理上做了解耦,因为如果不做解耦的话,IO在读取数据时,读取一段处理一段,在处理阶段可能会发生阻塞,那么可不可以异步的进行处理,可以创建一个线程池专门处理发过来的数据包,IO拿到数据之后放到内存,交给其他线程处理,但是这种方法进行R/W操作时会产生多余的系统调用,也会浪费CPU的资源,所以Netty中处理Epoll多路复用时,会产生多个线程,这些线程会线性的分块处理整个系统的文件描述符,最好的分配的线程数是:CPU核数或者CPU核数的2倍,这里的负责多路复用器的线程被放在boss组,而正常R/W的线程放在worker组中,由主线程进行分配。
IO中断
首先明确,中断时一定会调用callback,所以可以在callback中加一些操作,EPOLL就是这样做的,在callback时,不仅会把文件描述符放到缓存,还会调用EPOLL的方法,把FD的状态放到一个List集合中,当客户端与服务端第一次建立连接时,通过网卡触发IO中断,执行回调函数,返回FD和连接状态,FD会有一个缓冲区存储这些状态,所以EPOLL之前的IO模型都需要遍历FD获取状态信息。
Linux系统默认使用EPOLL
四次分手
完整的四次分手,客户端关闭,发送一个FIN包表示想要关闭,服务端接收并向客户端发送FIN_ACK确认收到,然后服务端再向客户端发送FIN包表示我也想要关闭,客户端响应一个ACK确认收到,这个时候4次分手结束。
四次分手状态
在服务关闭的时候需要调用连接另一端的close方法,双方建立连接之后,一端关闭如果没有调用另一端的close,这个时候就是客户端(假设这里是客户端先发起的关闭)会向服务端发送一个FIN包表示想要关闭,服务端就会进入CLOSE_WAIT状态并向客户端发送一个FIN_ACK确认自己收到这个包,这时候客户端进入FIN_WAIT2状态等待服务端发送FIN包,但是因为没有调用服务端的close所以服务端不会给客户端发送FIN表示想要关闭。

如果调用了close方法,四次分手完成,这个时候发起关闭的一端会进入TIME_WAIT状态,另一端直接关闭,为什么四次握手成功会有一个TIME_WAIT状态,这是因为发起关闭的一端返回一个ACK确认包之后无法确定另一端有没有收到这个包,如果另一端因为网络等原因没有收到ACK确认包,那么会再次发送FIN包,这个时候如果客户端的资源已经清理了,那这个包就会被直接丢弃,服务端就会一直处于等待状态。TIME_WAIT时间一般时报文活跃时间的两倍。在TIME_WAIT状态没有结束前,会一直占用SOCKET四元组资源,同一组四元组不能建立新的连接,在这里第二次和第三次分手可以合并。

**AIO(异步非阻塞) **
Linux系统目前还没有成熟的AIO模型

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值