IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 系统运维 -> Linux IO复用技术与零拷贝 -> 正文阅读

[系统运维]Linux IO复用技术与零拷贝

前言:

1.内核空间和用户空间

??虚拟内存被操作系统划分成两块:内核空间和用户空间,内核空间是内核代码运行的地方用户空间是用户程序代码运行的地方。当进程运行在内核空间时就处于内核态,当进程运行在用户空间时就处于用户态。

??32位inux虚拟地址空间划分如下:
在这里插入图片描述
划分原因如下:

  1. 安全:内核有控制和分配所有硬件资源的权限,而对于普通应用程序来说,这是不必要的,也是不安全的。划分使得普通程序的错误不会影响整个系统的稳定性。
  2. 权限问题:内核空间的代码主要管理各种底层资源,而用户空间的代码主要实现业务逻辑。所以内核空间和用户空间在运行时,cpu的权限是不同的。

对于x86体系的cpu, 用户空间代码运行在Ring3模式(用户模式),内核空间代码运行Ring 0模式(特权模式);
对于arm体系的cpu,用户空间代码运行在usr模式(用户模式),内核空间代码运行在svc模式(特权模式);
用户模式只能正常执行程序,而特权模式才能访问外设、处理中断等。

  1. 核心代码和业务代码的解耦:内核代码偏重于管理系统资源,而用户代码侧重于业务逻辑,通过划分解耦二者,使得用户操作变得简单。

正是有了不同运行状态的划分,才有了上下文的概念。用户空间的应用程序,如果想要请求系统服务,比如操作一个物理设备,或者映射一段设备空间的地址到用户空间,就必须通过系统调用来(操作系统提供给用户空间的接口函数)实现。

所谓的进程上下文,就是一个进程在执行的时候,CPU的所有寄存器中的值、进程的状态以及堆栈中的内容,当内核需要切换到另一个进程时,它需要保存当前进程的所有状态,即保存当前进程的进程上下文,以便再次执行该进程时,能够恢复切换时的状态,继续执行。

进程、线程、上下文

我们都知道现在的操作系统都是多任务操作系统,但实际上对于单核心CPU来说,它在某个具体时间只能同时处理一个任务,而多任务的实现,很重要的就是进程的出现。由于在单线程阶段CPU只能处理一个任务经常会导致CPU处于空闲状态而浪费大量资源。进程出现后让计算机的性能得到了很大的提升,但是后来人们发现CPU会顺序处理进程中的每个任务,当一个进程有多个子任务的时候,顺序执行带来的效率问题就出现了,于是,线程的概念也就出现了。线程把进程的子任务进行分割,每个子任务对应一个线程,这样让进程内部的并发成为了可能。

那么进程线程是如何解决CPU同时只能处理一个任务从而解决多任务的问题的呢。这里谈谈我的理解。

(cpu的读取址相对于磁盘等硬件来说是非常快的,cpu的速度是纳秒级别,磁盘速度是毫秒)
首先,进程的是程序在运行时对应的特定内存空间,每个进程的内存空间相互独立互不干扰,保存着程序运行时每个时刻的状态,为进程切换提供可能。其次,CPU的运行相对于其他的硬件是非常快的,以至于CPU实际上长时间是出于空闲状态的。然后,在CPU开始执行一个程序的时候,与其相关的资源必须已经就位。(比如你去找人办事,办事的时候你得把你办事用的所有东西都准备好)这里除了CPU之外与程序相关的所有资源构成了程序的运行环境,也就是这个程序的上下文。

把这些串起来我们就可以得到下面的信息:我们之前提到CPU同时只能处理一个任务,那当有多个任务同时需要处理怎么办呢?轮着来,也就是如果有A、B两个程序同时运行,在CPU这里实际上是这样的:A→保存A上下文→读取B上下文→B→保存B上下文→读取A上下文→A……如此循环直到程序结束,由于CPU运行极快,我们并不能感知到A和B中间短暂的中止状态,于是在我们看来就像是在同时运行。

那么线程是什么呢,再举个例子,比如程序A在运行的时候有三个子任务A1、A2、A3由于CPU的特性这三个任务会被顺序执行,也就是如果在执行A1的时候我们需要A3的结果单线程的情况下意味着我们只能等待A1、A2、之行结束。这显然不符合我们的要求,于是线程的概念就出现了,线程把每个进程的子任务独立出来单个处理,当CPU在处理程序A的时候CPU的运行时间被分成更细小的片段执行,由于不是等待A1执行完在执行A2而是A1→A2→A3→A1→A2→A3……直到执行结束。如下图:

在这里插入图片描述

再加一句,CPU在工作的时候是按照时间片段进行的,靠这些分割的时间片段来回切换实现了多任务。所以也有的人会说线程和进程描述的是CPU时间段。(线程把进程的CPU时间继续分割来解决进程的并发问题)

缓存IO

缓存 IO 又被称作标准 IO,大多数文件系统的默认 IO 操作都是缓存 IO。在 Linux 的缓存 IO 机制中,操作系统会将 IO 的数据缓存在文件系统的页缓存( page cache )中,也就是说,数据会先被拷贝到操作系统内核的缓冲区中,然后才会从操作系统内核的缓冲区拷贝到应用程序的地址空间。
缓存 IO 的缺点:
数据在传输过程中需要在应用程序地址空间和内核进行多次数据拷贝操作,这些数据拷贝操作所带来的 CPU 以及内存开销是非常大的。

以read为例,数据会先被拷贝到操作系统内核的缓冲区中(DMA),然后才会从操作系统内核的缓冲区拷贝到应用程序的地址空间(CPU)。(缓冲区是作为IO中介的内存块,用于协调输入输出两端的速度)
具体如下:
当应用程序访问某块数据时,操作系统首先会检查,是不是最近访问过此文件,文件内容是否缓存在内核缓冲区,如果是,操作系统则直接根据read系统调用提供的buf地址,将内核缓冲区的内容拷贝到buf所指定的用户空间缓冲区中去。如果不是,操作系统则首先将磁盘上的数据拷贝的内核缓冲区,这一步目前主要依靠DMA来传输,然后再把内核缓冲区上的内容拷贝到用户缓冲区中。
接下来,write系统调用再把用户缓冲区的内容拷贝到网络堆栈相关的内核缓冲区中(DMA),最后socket再把内核缓冲区的内容发送到网卡上(CPU)

在这里插入图片描述
从上图中可以看出,共产生了四次数据拷贝,即使使用了DMA来处理了与硬件的通讯,CPU仍然需要处理两次数据拷贝,与此同时,在用户态与内核态也发生了多次上下文切换,无疑也加重了CPU负担。

又如:
在这里插入图片描述

实现零拷贝后,如下图:
在这里插入图片描述
即:

  1. 通过 DMA,从硬盘直接读到操作系统内核的读缓冲区里面。
  2. 根据 Socket 的描述符信息,直接从读缓冲区里面,写入到网卡的缓冲区里面。
    在这个方法里面,我们没有在内存层面去“复制(Copy)”数据,所以这个方法,也被称之为零拷贝

DMA

当用户进程想要执行IO操作时(比如想要读取磁盘数据),由于用户进程工作在用户模式下,它没有执行这些操作的权限。需要发起系统调用切换到内核态。这里会产生一次进程的上下文切换。
当内核进程帮忙执行IO操作时,由于IO操作相比于cpu很慢,在等待期间cpu会切换到其他进程上执行其他任务,又会设计到一次上下文切换。

DMA(直接存储器存取)技术,需要硬件支持,即需要主板上一块独立的芯片,即DMAC(协处理器),在进行IO操作时,不再需要CPU频繁切换,而是直接通过DMA控制数据传输。在DMA方式中,内存和IO设备中有一条专门的数据总线用于数据传输,因此不再需要占用CPU。
在这里插入图片描述

关于IO多路复用
引用知乎答主柴小喵:

下面举一个例子,模拟一个tcp服务器处理30个客户socket。假设你是一个老师,让30个学生解答一道题目,然后检查学生做的是否正确,你有下面几个选择:1. 第一种选择:按顺序逐个检查,先检查A,然后是B,之后是C、D。。。这中间如果有一个学生卡主,全班都会被耽误。这种模式就好比,你用循环挨个处理socket,根本不具有并发能力。2. 第二种选择:你创建30个分身,每个分身检查一个学生的答案是否正确。 这种类似于为每一个用户创建一个进程或者线程处理连接。3. 第三种选择,你站在讲台上等,谁解答完谁举手。这时C、D举手,表示他们解答问题完毕,你下去依次检查C、D的答案,然后继续回到讲台上等。此时E、A又举手,然后去处理E和A。。。 这种就是IO复用模型,Linux下的select、poll和epoll就是干这个的。将用户socket对应的fd注册进epoll,然后epoll帮你监听哪些socket上有消息到达,这样就避免了大量的无用操作。此时的socket应该采用非阻塞模式。
这里分身的意思是开辟多个进程/线程处理。
但是进程/线程的创建是有成本的,如下:
1.创建成本
2.切换成本
3.竞争系统资源成本

I/O多路复用可以用来在单个进程/线程中处理多个事件流。

通过上面的例子再来看下面两句话就比较容易理解了。
1.I/O复用技术是为了解决某个进程或者线程阻塞在某一I/O调用 而出现的技术。
2.I/O复用技术可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作,从而不会使程序阻塞在单个I/O上了。

Linux系统下IO模型

linux系统提供下列五种IO处理模型

1.阻塞IO

最为简单且常用的IO模型,表示在发起一次IO操作后,需要一直等待其成功或者失败后才返回,期间程序不能做其他事情。阻塞IO操作只能对单个文件描述符进行操作,例如read和write。

2.非阻塞IO

通过将文件描述符的flags 设置为O_NONBLOCK 表示当前I/O操作为非阻塞式。系统会采用类似轮询的方式去判断当前IO成功或失败。一般来说,非阻塞IO是在循环里面,程序需要不断地询问内核数据是否就绪,成功则执行IO操作并退出,失败则继续询问。也就说非阻塞IO会一直占用CPU,导致CPU占用率高。

3.IO多路复用

同时监听多个描述符的IO事件,取出状态ready的描述符列表。相比前两种,改进之处在于不在主动查询事件状态,而是等待准备好的通知。

4.信号驱动IO

为IO事件绑定处理程序,当事件发生时触发处理程序。

上述4中方案可归类为同步IO,因为其都需要在读写事件就绪后自己负责进行读写。也就是说,在接收数据的过程中,(一般指执行io操作,读写过程中),依然是阻塞状态。

5.异步IO

利用Linux的信号机制,当IO事件发生时,触发信号处理程序。

在这里插入图片描述

实现I/O复用的三种方法

select

函数原型

#include <sys/select.h>  
#include <sys/time.h>  
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);  
    // 返回:若有就绪描述符则为其个数,超时为0,出错-1  

其中我们注意参数fd_set结构体包含一个整形数组,该数组中每一个元素的每一位标记一个文件描述符。当调用select函数时,由内核根据IO状态修改fd_set的内容,由此来通知执行select的进程,哪一个或者几个文件可操作。

换句话说,select实现多路复用的方式是,当前进程把所有的文件描述符都放入fd_set中,然后调用 select 函数将文件描述符集合拷贝到内核里,让内核通过轮询(遍历)的方式判断是否有多少个IO请求产生。找到并标记之后, 接着再把整个文件描述符集合拷贝回用户态里,然后用户态再通过遍历的方法找到前面标记过的文件描述符,然后操作。

可看到,使用select的好处是:
用户可以在同一进程中处理多个IO请求,而在其他同步IO模型中,只能通过多进程或线程的方式实现。

缺点如下:
(2次拷贝,2次遍历)

  1. 每次调用select,都需要把fd_set集合从用户态拷贝到内核态,如果fd_set集合很大时,那这个开销很大。
  2. select 方法仅仅知道有IO事件发生了,但不知道是哪几个流,(可能是一个或者多个),因此只能无差别的轮询所有流。所以select具有O(n)的无差别轮询复杂度,当fd_set集合太大,开销变大。
  3. 为了减少数据拷贝带来的性能损坏,内核对被监控的fd_set集合大小做了限制,并且这个是通过宏控制的,大小不可改变(限制为1024)

poll

poll机制类似于select,管理多个文件描述符也是采取轮询的方式。,但是 poll 没有最大文件描述符数量的限制,也就是说,poll 只解决了上面的问题 3,并没有解决问题 1,2 的性能开销问题。

int poll(struct pollfd *fds, nfds_t nfds, int timeout);

typedef struct pollfd {
        int fd;                         // 需要被检测或选择的文件描述符
        short events;                   // 对文件描述符fd上感兴趣的事件
        short revents;                  // 文件描述符fd上当前实际发生的事件
} pollfd_t;

poll函数对文件描述符的管理方式不再是集合了,而是采用链表fds的方式,而且链表的每个元素pollfd指针,没有使用select 直接传值的方法,因此没有连接数的限制。

epoll

在这里插入图片描述

  系统运维 最新文章
配置小型公司网络WLAN基本业务(AC通过三层
如何在交付运维过程中建立风险底线意识,提
快速传输大文件,怎么通过网络传大文件给对
从游戏服务端角度分析移动同步(状态同步)
MySQL使用MyCat实现分库分表
如何用DWDM射频光纤技术实现200公里外的站点
国内顺畅下载k8s.gcr.io的镜像
自动化测试appium
ctfshow ssrf
Linux操作系统学习之实用指令(Centos7/8均
上一篇文章      下一篇文章      查看所有文章
加:2021-08-26 12:31:17  更:2021-08-26 12:33:53 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年12日历 -2024/12/29 9:21:37-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码
数据统计