iis建站安装wordpress,吉林省建设安全信息网站,山东微商网站建设,51个人网站文章目录操作系统和内存内核空间和用户空间应用程序的内核态和用户态网络 IO 和磁盘 IO简易的网络通信流程阻塞和非阻塞阻塞 IO 模型非阻塞 IO 模型IO 复用模型SelectPollEpoll小结信号驱动 IO 模型异步 IO 模型五种 IO 模型的对比IO 模型里的同步和异步5种 IO 模型分别是…
文章目录操作系统和内存内核空间和用户空间应用程序的内核态和用户态网络 IO 和磁盘 IO简易的网络通信流程阻塞和非阻塞阻塞 IO 模型非阻塞 IO 模型IO 复用模型SelectPollEpoll小结信号驱动 IO 模型异步 IO 模型五种 IO 模型的对比IO 模型里的同步和异步5种 IO 模型分别是
阻塞 IO 模型、
非阻塞 IO 模型、
IO 复用模型、
信号驱动 IO 模型和
异步 IO 模型。操作系统和内存
计算机由操作系统和硬件组成。
操作系统主要包含内核kernel和应用程序。
内核提供进程管理、内存管理、网络服务等底层功能也提供了与硬件交互的接口通过系统调用提供给上层的应用程序使用。
应用程序如浏览器、QQ、MySQL 等下面简称为程序要操作硬件如进行磁盘读写需要先与内核交互再由内核与硬件交互。
硬件包括 CPU、内存、硬盘、网卡、声卡、显卡等。
内核空间和用户空间
操作系统都是采用虚拟地址空间内核是操作系统的核心独立于普通的应用程序内核可以访问受保护的内存空间内核空间也有访问底层硬件设备的所有权限。
为了保证内核的安全操作系统将虚拟内存空间划分为内核空间和用户空间两个部分。它们是隔离的即使用户程序崩溃了内核也不受影响。
内核空间是操作系统的内核代码运行的地址空间是受保护的内存空间。也称内核内存。用户空间是普通的用户程序代码运行的内存地址空间。也称用户内存。
应用程序的内核态和用户态
早期的操作系统是不区分内核空间和用户空间的。应用程序能随意访问任意内存空间导致用户程序经常把系统搞崩溃。
后来就按照 CPU 指令的重要程度对指令进行了分级。
CPU 指令分为四个级别Ring0 ~ Ring3Linux 只使用了 Ring0 和 Ring3 两个运行级别。
程序进程运行 Ring3 级别的指令时运行在用户态只能访问用户空间。
程序进程运行 Ring0 级别的指令时被称为内核态可访问任意内存空间。
当程序进程线程运行在内核空间时它就处于内核态当程序进程线程运行在用户空间时它就处于用户态。
那么程序什么时候运行在内核空间什么时候运行在用户空间呢
当需要进行 IO 操作时比如读写磁盘文件、读写网卡数据时程序进程需切换到内核态否则无法操作。无论是从用户态切换到内核态还是从内核态切换到用户态都需要进行一次上下文的切换。一般情况下程序不能直接操作内核空间的数据需要把内核内存的数据拷贝到用户空间才能操作。
当程序进程执行系统调用而进入内核代码中执行时称进程处于内核运行态内核态。
除了系统调用可以实现用户态到内核态的切换软中断和硬中断也会切换用户态和内核态。
在内核态下程序进程线程运行在内核空间中此时 CPU 可以执行任何指令。运行的代码也不受任何的限制可以自由地访问任何有效地址也可以直接进行端口的访问。在用户态下程序进程线程运行在用户空间中被执行的代码要受到 CPU 的很多检查比如进程只能访问映射其地址空间的页表项中规定的在用户态下可访问的虚拟地址。
网络 IO 和磁盘 IO
IO 是 Input/Output 的缩写也就是计算机中的输入和输出。
由于应用程序和运行时数据是在内存中驻留的由 CPU 来执行各种操作涉及到数据交换的地方通常是内存、磁盘和网络等就需要 IO 接口。
通常程序完成 IO 操作会有 Input 和 Output 两个数据流。比如从 MySQL 读取数据到内存就有 Input 操作再把数据展现出来给我们看就有 Output 操作。
IO 操作是相对于内存而言的数据从外部设备进入内存就是 Input从内存取出数据输出到外部设备就是 Output。
用户进程无法直接操作 IO 设备必须通过系统调用请求内核来协助完成。内核会为每一个 IO 设备维护一个缓冲区。
通常用户进程完成一次完整的 IO 操作需要两个阶段用户进程空间与内核空间的交互、内核空间与设备空间硬盘、网卡的交互。
IO 从读取数据的来源分为内存 IO、 网络 IO 和磁盘 IO 三种通常说的 IO 是指网络 IO 和 磁盘 IO。因为内存 IO 的读写速度远大于网络 IO 和磁盘 IO
IO 按照设备来分可分为网络 IO 和磁盘 IO。网络 IO 就是通过网络进行数据的拉取和输出。 磁盘 IO 就是对磁盘进行的读写操作。
网络 IO等待网络数据到达网卡把网卡中的数据读取到内核缓冲区然后从内核缓冲区拷贝数据到用户进程空间。磁盘 IO把数据从磁盘读取到内核缓冲区然后从内核缓冲区拷贝数据到用户进程空间。
由于 CPU 和内存的运行速度远远高于外部设备网卡、磁盘等所以在 IO 编程中存在速度严重不匹配的问题。
简易的网络通信流程
以两个应用程序的通信为例程序 A 给程序 B 发送消息基本流程如下
A 把数据发送到 TCP 发送缓冲区。TCP 发送缓冲区再把数据发送出去经过网络传递后数据会发送到 B 所在服务器的 TCP 接收缓冲区。B 再从 TCP 接收缓冲区去读取属于自己的数据。
也就是说消息发送要经过应用层的程序 A、A 所在服务器的 TCP 发送缓冲区经过网络传输后消息发送到了另一个应用层的程序 B 所在服务器的 TCP 接收缓冲区最终 B 读取到消息。
阻塞和非阻塞
由于应用程序之间发送消息是间断性的那么当程序 B 所在服务器的 TCP 接收缓冲区还未接收到消息数据时此时 B 向 TCP 接收缓冲区发起读取申请TCP 接收缓冲区是应该马上告诉 B 现在没有你的数据还是让 B 继续等待直到有数据再交给 B。
对于应用程序 A 也是一样。A 在向 TCP 发送缓冲区发送数据时如果 TCP 发送缓冲区已经满了那么是立即告诉 A 现在没空间了还是让 A 等着等 TCP 发送缓冲区有空间了再把 A 的数据拷贝到发送缓冲区。
阻塞以读取数据为例当程序 B 发起读取申请时在内核把数据准备就绪之前B 一直处于等待状态其它什么也不做直到内核把数据准备好交给 B才结束。
基本流程
程序进程或线程向内核发起 recfrom 读取数据。内核准备数据。程序进程将数据从内核空间拷贝到用户空间。拷贝完成后返回成功提示。
非阻塞以读取数据为例当程序 B 发起读取申请时如果内核没有把数据准备好会立即告诉 B返回提示或错误不会让 B 一直等待。
基本流程
程序进程或线程向内核发起 recfrom 读取数据。内核没有把数据好时立即返回 EWOULDBLOCK 错误码。程序进程不断调用 recvfrom即向内核发起轮询请求。当数据准备就绪就进行下一步否则还是返回错误码。将数据从内核空间拷贝到用户空间。拷贝完成后返回成功提示。
IO 操作分为两个阶段步骤
内核进行数据准备的阶段。数据从内核空间拷贝到用户空间的阶段。
根据这两个阶段处理方式的不同IO 操作可细分为下面五种。
阻塞 IO 模型
阻塞 IO 模型是指当程序 B 发起 IO 请求时如果内核没有把数据准备就绪B 会一直处于等待状态直到内核把数据准备好了并交给 B 才结束。
优点开发相对简单在阻塞期间用户线程被挂起期间不会占用 CPU 资源。
缺点
连接利用率不高内核如果没有响应数据则该连接一直处于阻塞状态占用连接资源。一个线程维护一个 IO 资源当有大量并发请求时需要创建等价的线程来处理请求不适合高并发场景。
非阻塞 IO 模型
非阻塞 IO 模型是指当程序 B 发起 IO 请求时如果内核没有把数据准备就绪会立即告诉 B返回提示或错误码这样B 就不会一直等待而是每过一段时间发起轮询请求。
优点每次发起 IO 请求时在内核准备数据的过程中可以立即返回用户线程不会阻塞实时性较好。
缺点
当用户线程 B 没有获取到数据时需不断轮询占用大量 CPU 时间效率不高。和阻塞 IO 一样一个线程维护一个 IO 资源当有大量并发请求时需要创建等价的线程来处理请求不适合高并发场景。
IO 复用模型
思考一个问题
高并发的情况下有很多人向应用程序 B 发送消息此时程序 B 可能就要创建很多个线程每个线程都会调用 recvfrom 读取数据。B 是不知道什么时候 TCP 接收缓冲区里会有数据为了保证能及时读取到消息每个线程必须不断内核发起 recvfrom 请求。
问题在于大量线程不断调用 recvfrom实在太浪费系统资源。
于是有人便提出了一个解决思路
在程序 B 中采用 IO 复用器select监控多个网络请求fd 文件描述符句柄这样只需一个线程就可以完成数据状态的询问操作当内核空间中有数据准备就绪再分配其他的线程或自己去读取数据。而不用为每一个请求创建一个线程从而节省出大量的线程资源。这就是 IO 复用模型。
Linux 中 IO 复用的实现方式主要有 Select、Poll 和 Epoll。
IO 复用模型的思路是系统提供了一种函数可以同时监控多个 fd这个函数就是我们常说的 select、poll 或 epoll程序线程通过调用 select 函数就可以同时监控多个 fd监控的 fd 集合中只要有任何一个数据状态准备就绪了select 函数就会返回可读状态这时再去分配其他线程或自己发起 recvfrom 请求读取数据。
Select
水平触发Level Triggered它会无差别地遍历轮询整个被监听的 fd 文件描述符集合fd_set 。如果有哪一个 fd 准备就绪就返回这个活跃的连接。fd_set 的大小是受限制的由 Linux 内核的 FD_SETSIZE 定义。
事件集合通过3个参数分别可读、可写及异常等事件。内核通过对这些参数的在线修改来反馈其中的就绪事件使得每次调用 select 函数都要重置这 3 个参数。工作模式LT程序获得就绪 fd 的复杂度时间复杂度 O(n)支持最大的 fd 数量一般为 1024内核实现和复杂度轮询方式检测就绪事件时间复杂度O(n)
Poll
原理和 select 类似poll 底层需要分配一个 pollfd 结构数组维护在内核中不受 fd_set 大小的限制。
事件集合统一处理所有事件类型因此只需要一个事件集参数。用户通过 pollfd.events 传入要监听的事件内核通过修改 pollfd.revents 反馈其中就绪的事件。工作模式LT程序获得就绪 fd 的复杂度时间复杂度 O(n)支持最大的 fd 数量65535内核实现和复杂度轮询方式检测就绪事件时间复杂度O(n)
Epoll
边缘触发Edge Triggered采用事件驱动和回调函数。三大要素mmap、红黑树、链表。
事件集合内核通过一个事件表直接管理用户程序监听的所有事件。因此每次调用 epoll_wait 时无需反复传入事件。epoll_wait 系统调用的参数 events 仅用来反馈就绪的事件。工作模式ET程序获得就绪 fd 的复杂度时间复杂度 O(1)支持最大的 fd 数量65535内核实现和复杂度回调方式检测就绪事件时间复杂度O(1)
说明epoll 并不是在所有的场景都比 select 和 poll 高效很多尤其是当活动连接比较多的时候。epoll 特别适用于连接数量多但活动连接较少的场景。
小结
select、poll、epoll 都是 IO 多路复用的机制。
IO 多路复用就是通过一种机制监听多个 fd 文件描述符一旦某个 fd 准备就绪能够通知程序进行相应的读写操作。
但 select、poll、epoll 本质上都是同步 IO因为它们都需要在读写事件就绪后自己负责读写一个个的处理也就是说这个读写过程是阻塞的。而异步 IO 则无需自己负责读写异步 IO 的实现会负责把数据从内核空间拷贝到用户空间。
IO 复用的基本思想是通过 select、poll、epoll 来监控多个 fd 达到不必为每个 fd 创建一个对应的监控线程从而减少线程资源的创建。
IO 复用的优势并不是对于单个连接处理得多快而是在于能处理更多的连接。
IO 复用模型的优点系统不必创建和维护大量的线程只需要一个或几个线程便可同时处理成千上万个连接大大减少了系统的开销。
IO 复用模型的缺点本质上还是同步阻塞模式。
信号驱动 IO 模型
IO 复用模型实现了一个线程可以监控多个 fd但 select 是采用轮询的方式来监控多个 fd 的通过不断轮询 fd 的状态来判断是否有数据准备就绪这种无脑的轮询显得有些暴力因为大部分情况下的轮询都是无效的。
所以有人就想能不能不要让我总是去询问你是否有数据准备就绪能不能我发出请求后等你数据准备好了就通知我。于是就诞生了信号驱动 IO 模型。
信号驱动 IO 模型是指程序 B 通过系统调用 sigaction向内核空间注册一个信号处理回调函数就立即返回非阻塞当内核把数据准备就绪时会发送一个信号SIGIO通知 BB 再向内核调用 recvfrom 读取数据。
信号驱动 IO 模型解决了轮询询问数据状态的问题线程在发出信号监控后立即返回非阻塞因此一个线程也可以同时监控多个 fd。
信号驱动 IO 也可以看成是一种异步非阻塞 IO。
在内核进行数据准备期间程序进程线程不阻塞。但是当程序进程线程将数据从内核空间拷贝到用户空间期间是阻塞的。这是它和异步 IO 的本质区别。
异步 IO 模型
可以发现不管是 IO 复用还是信号驱动 IO要读取数据需发起两次请求第一次发起判断数据就绪状态的请求第二次发起 recvfrom 读取数据的请求。
为什么我们明明是想读取数据却非要先发起一个判断数据状态的请求然后再发起真正的读取请求。有没有一种方法只需发送一个请求告诉内核我要读取数据就什么都不管由内核去帮我完成所有的事情。
于是异步 IO 模型便诞生了。
异步 IO 模型是指程序 B 向内核发送一个 IO 请求比如 read告知内核我要读取数据便立即返回内核收到请求后会与之建立一个信号联系当数据准备就绪内核会主动把数据从内核空间复制到用户空间。所有操作完成之后内核会发送一个完成通知告知 B。
异步 IO 模型做到了真正的非阻塞。
异步 IO 模型与信号驱动 IO 模型的主要区别在于信号驱动 IO 只是让内核通知我们何时可以开始下一个 IO 操作而异步 IO 模型是让内核通知我们操作什么时候完成。
此模型和前 4 个模型最大的区别是前 4 个模型从内核空间拷贝数据到用户空间这一过程必须由程序自身来进行必然是阻塞的。 而异步 IO 模型在内核准备数据和数据从内核空间拷贝到用户空间这两个过程都不用等待完全非阻塞。
用户进程线程完全不需要关心实际的整个 IO 操作是如何进行的只需先发起一个请求当收到内核返回的成功信号时所有的 IO 操作都已完成。它是最理想的模型。
五种 IO 模型的对比
阻塞 IO在数据准备阶段和数据拷贝阶段都会阻塞。非阻塞 IO在数据准备阶段非阻塞但在数据拷贝阶段阻塞。IO 复用在数据准备阶段采用复用器轮询遍历多个 fd会阻塞在数据拷贝阶段阻塞。但是它用单一线程监听了多个连接减少了线程资源的创建。信号驱动 IO在数据准备阶段注册一个信号处理函数来接收内核准备数据的结果信号实现非阻塞在数据拷贝阶段阻塞。异步 IO在数据准备阶段和数据拷贝阶段都非阻塞。
IO 模型里的同步和异步
我们常常可以听到同步阻塞 IO、同步非阻塞 IO、异步 IO。
应用程序发起读取数据请求若数据还没准备就绪需要等待就是阻塞反之若立即返回就是非阻塞。
那么这里的同步和异步怎么理解呢
同步是指应用程序发起 IO 请求从发起请求到请求最终完成整个过程都需要参与其中与内核进行交互。
异步是指应用程序发起 IO 请求之后就不再参与后续的具体过程接收最终完成结果的通知由回调实现。
为什么只有异步非阻塞而没有异步阻塞因为异步 IO 模型下程序发送完请求指令后就立即返回了没有任何后续流程。因此它注定不会阻塞也就只会有异步非阻塞。
阻塞必然同步同步不一定阻塞。