用户态和内核态
用户态
用来运行应用程序,不能直接对操作系统进行调用,而是需要切换到内核态对操作系统进行操作。
内核态
直接访问操作系统资源或运行操作系统程序。
例如程序要保存一个文件到硬盘,在程序执行的用户态,是直接操作磁盘的。只有切换到内核态才能真正去操作磁盘。
内核态运行操作系统程序,操作硬件,用户态运行用户程序;
当程序运行在3级特权上时,可以称之为运行在用户态,当程序运行在0级特权级上时,称为运行在内核态。
R0 相当于内核态,R3 相当于用户态。
IO(Input/Output)
计算机角度的IO
输入设备:向计算机输入数据和信息的设备,键盘,鼠标都属于输入设备;
输出设备:用于接收计算机数据的输出显示,一般显示 器、打印机属于输出设备;
操作系统角度的IO
操作系统负责计算机的资源管理和进程的调度,经过操作系统,才能做一些特殊操作,如磁盘文件读写、内存的读写等。
真正的IO是在操作系统上执行的,即应用程序的IO操作:
IO调用:IO调用是由进程发起的
IO执行:是操作系统内核的工作
应用程序发起的一次 IO 操作包含两个阶段:
IO调用:应用程序进程需要向操作系统内核发起调用
IO执行:操作系统内核完成IO操作
IO模型
阻塞IO
定义:应用程序发起IO调用,但是内核的没有准备好,应用程序就会进入阻塞等待,等待内核数据准备好。必须等待内核数据准备好,程序才继续向下执行,效率低,浪费资源
经典应用:阻塞socket、Java BIO等
缺点:如果内核数据一直没有准备好,那么用户线程将一直阻塞,浪费性能,,可以使用非阻塞IO优化。
非阻塞IO(NIO)
定义:如果内核数据还没准备好,可以先信息给用户线程,让它不需要等待,而是通过轮询的方式再来请求。
当应用程序发起调用后,操作系统如果没有准备好数据,返回错误状态码,应用程序轮询再次发起调用,直到操作系统数据准备好了,将数据返回到用户内核空间。
非阻塞IO的流程:
- 应用程序向操作系统内核发起recvfrom()读取请求
- 操作系统内核数据没有准备好,立即返回回 EWOULDBLOCK 错误码
- 应用程序进程轮询调用,继续向操作系统内核发起recvfrom读取请求
- 操作系统你和数据准备好了,从内核缓冲区拷贝到用户空间
- 完成调用,返回成功提示
recvfrom()用来接收远程主机经指定大的socket传来的数据,并把数据传到由参数buf指向的内存空空间。
优点:提高了性能,采用轮询。
缺点:频繁的轮询导致频繁的系统调用,消耗大量的CPU资源。考虑IO多路复用。
IO多路复用
定义:为了解决NIO的频繁轮询的问题,等待内核数据准备好了,主动通知应用程序去进行系统的调用。
核心思想:系统为我们提供一类函数(select、poll、epoll),它们可以同时监控多个fd的操作,任何一个返回内核数据就绪,应用程序再发起recvform系统调用
文件描述符fd(File Descriptor),它是计算机科学中的一个术语,形式上是一个非负整数。当程序打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件描述符。
select
应用程序发起一次调用之后,就由select()函数负责监听,应用程序不需要轮询,当有数据准备好了,那么就将数据向用户态返回。
好处:不同轮询。
缺点:监听的IO最大连接数有限,在Linux系统上一般为1024。需要遍历fdset,找到就绪的描述符fd。
poll
与select相同,但是解决了select连接数限制,但同样需要遍历
epoll
给每个监听的文件描述符(fd)绑定一个回调函数,当数据准备就绪后,触发回调函数处理。
优点:不需要遍历
总结select、poll、epoll 的区别
select | poll | epoll | |
---|---|---|---|
底层数据结构 | 数组 | 链表 | 红黑树和双链表 |
获取就绪的fd | 遍历 | 遍历 | 事件回调 |
事件复杂度 | O(n) | O(n) | O(1) |
最大连接数 | 1024 | 无限制 | 无限制 |
fd数据拷贝 | 每次调用select,需要将fd数据从用户空间拷贝到内核空间 | 每次调用poll,需要将fd数据从用户空间拷贝到内核空间 | 使用内存映射,不需要从用户空间频繁拷贝fd数据到内核空间 |
epoll 明显优化了 IO 的执行效率,但在进程调用 epoll_wait()时,仍然可能被阻塞。能不能不用我老是去问你数据是否准备就绪,等我发出请求后,你数据准备好了通知我就行了,这就诞生了信号驱动 IO 模型。
IO 模型之信号驱动模型
信号驱动:当内核态数据准备好以后,内核态向用户态发送信息,用户态发起请求获取数据。
异步IO(AIO)
是真正的非阻塞IO,用户进程只需要发起一次调用,在内核态就会一次性直接将数据封装好,然后返回。
一个经典生活的例子:
BIO
小明去吃同仁四季的椰子鸡,就这样在那里排队,等了一小时,轮到她了,然后 才开始吃椰子鸡。
NIO
小红也去同仁四季的椰子鸡,她一看要等挺久的,于是去逛会商场,每次逛一下, 就跑回来看看,是不是轮到她了。于是最后她既购了物,又吃上椰子鸡了。
AIO
小华一样,去吃椰子鸡,由于他是高级会员,所以店长说,你去商场随便逛会吧, 等下有位置,我立马打电话给你。于是小华不用干巴巴坐着等,也不用每过一会儿就跑回来看有没有等到,最后也吃上了美味的椰子鸡
Java中的IO
BIO(阻塞IO)例如ServerSocket、Scanner等都是阻塞式的,效率低,可以通过线程独立运行,实现不阻塞。
NIO 非阻塞IO
非阻塞IO核心3的组件:通道(Channels)、缓存(Buffers)、选择器(Selectors核心)
通道读写数据是面向缓存区的
选择器可以监听多个通道,一旦某个通道有事件(读、写、链接)发生,都会被选择器捕捉。一个选择器可以监听多个通道,是非阻塞的,通过一个线程,就可以处理多个通道任务。