首页 > 编程知识 正文

局域网复制文件非常慢,并行传输和串行传输哪个快

时间:2023-05-03 13:38:22 阅读:119769 作者:2757

你知道当原始程序需要读取或写入数据时,CPU是如何操作我们的磁盘的吗? 首先,CPU必须向磁盘传递读写数据的命令。 该命令可以通过IO总线传递到磁盘。 这里有详细情况。 其实,人们常说磁盘不仅包括存储数据的介质,还包括接口。 我相信接口是众所周知的。 接口的意义不仅仅是为了连接到IO总线,其实这个接口有一种叫控制器的东西,控制器才是真正控制磁盘读写的东西。 CPU发出读写命令时,该命令其实以读取为例,控制器收到读取请求时,告诉磁盘“给我xx数据”,机械硬盘旋转、查找找到目标扇区后,将目标数据输入“哥哥,这是我想要的数据”。 控制器收到数据后,不会立即通知CPU。 因为需要读取的数据可能涉及多个扇区,所以每次读取一个扇区的数据时通知用户会更有效率

CPU :“控制器的弟弟,这是工作啊。 我很忙。 请在每次制作这点数据时通知我。 能帮我准备所有需要的数据吗? 请再通知我”。

“控制器”:“是的,中央处理器的哥哥”。

因此,在控制器内部制作缓冲器,将读取的数据暂时缓存,通知CPU来取数据,但又发生了问题.

CPU :“控制器的弟弟,数据准备好了,但是你给我的数据已经坏了。 请玩! ”

“控制器”:“CPU先生,我错了。 下次绝对不行”。

因此,控制器计算校验和以判断读取的数据是否损坏,如果校验和不通过,则不会通知CPU来取坏数据。

缓冲区快要满了,或者应该读的数据读完检查数据也OK时,控制器会发出中断。 “CPU先生,我有了想要的数据。 请来取”,CPU放屁来取数据。 当然那个也是分批拿的。 从控制器的缓冲区一个字节一个字节地取出,等待取完。 整个过程看起来不错,但存在严重的效率问题。 由于CPU每次获取数据的单位稍小(1字节),因此CPU会多次往返。 有解决这个问题的方法吗? 我们接下来往下看。

在谈论缓冲之前,让我们先知道当我们的程序发布read时,数据是如何返回的。 首先与设备交互时,必须启动系统调用。 系统调用进入内核状态。 然后CPU去读取数据,读取数据后,将数据返回用户程序。 此时,返回用户状态。

现在,让我们首先集中讨论从内核状态到用户状态的数据流。 如上所述,我们知道CPU会一个字节一个字节地读取数据。 CPU收到数据后,有几种选择:

每读一个字节发出一个中断,中断程序将每个字节传递给用户进程,在用户进程收到数据后,开始读取下一个字节。 这样,循环重复,直到数据读完。 此模式的问题是,每个字节都会触发进程,并且用户进程会继续阻止以等待下一个字节的到达。 又笨又没效率。

用户程序每次都可以读取很多数据。 例如,每次都告诉CPU“读取n字节”,CPU接到指令后去磁盘读取数据。 当然,这里并不是一个字节一个字节的初始化中断。 否则,和1没有区别。 因为一开始告诉CPU要读n字节,所以要读满n字节再开始中断,你怎么知道要读满n字节呢? 这需要缓冲区。 可以在用户空间中打开n字节的缓冲区。 缓冲区已满后,启动中断时,与前n次中断相比,这里只需要一次中断,不是会大大提高效率吗?

第二种方法解决了用户程序效率低下的问题,但不要忘记还有CPU。 CPU还在一个字节一个字节地向用户的缓冲区输送数据。 这样看CPU还是很辛苦,不仅需要读取数据,还需要将数据从内核空间低效地传输到用户空间。 请注意,在此内核空间和用户空间之间切换仍然需要时间。 因此,为了减少切换的开销,也需要在内核空间中创建缓冲区,等待缓冲区。

你会发现最后一个无疑是最有效率的。 这也是现代操作系统中常用的方法,但这种模式也不是100%完美。 看看相关的时序图吧。

在时序图中,首先以CPU为中心来看看。 控制器缓冲区已满后,可以看到CPU需要将数据复制到内核缓冲区。 然后,CPU将内核缓冲区中的数据复制到用户缓冲区中。 CPU不仅负责数据的读写,还负责数据的搬运。

高级-DMA“我堂堂正正地做CPU。 你能帮我为了慢磁盘而自卑吗? 和低等磁盘打交道的任务交给我吧。 还有很多其他进程在等待时间表”。 于是,设计师们意识到了这个问题,为了让CPU能够专注于调度、计算等工作,后来制作了DMA(directmemoryaccess )。 中文名字是直接访问,中文名字很抽象。 请不要着急。 接下来往下看。

首先,这个DMA内部也有寄存器。 这些寄存器可以存储什么? 答案是内存地址,严格来说是内核缓冲区的地址。 如果存在DMA,读取操作将从CPU传递到DMA,而不是从CPU传递到磁盘。 “现在,DMA先生有一个程序员要读取xx数据,请把xx数据放入内存地址为0x1234的内存中。” DMA收到老板的CPU的通知后:

“收到了老大,这种小事交给dmddy吧,你去忙吧”,到这里CPU就去忙别的事了,然后DMA就去通知我们的磁盘控制器了:“你先把xx数据的这一部分直接读到0x1234内存里去吧,读完告诉我一下,我这边还有xx数据的另一部分”,磁盘控制器:“好的,老大哥”,就这样每次控制器读完一部分数据之后就会通知DMA,然后DMA让它再读下一个数据,直至把需要读的数据读完,在读完了数据之后,肯定不能完事呀,这时得告诉老大哥CPU,于是DMA发出一个中断:“CPU大哥,数据已读取完毕,请享用~”,CPU收到通知后,发现数据已经在内核缓冲区了,不需要亲自干一个字节一个字节搬运的鸟事了,而且这期间CPU指挥了三次交通(调度)、扶了四个发嗲的便当过了马路(计算)。

CPU告诉DMA

DMA告诉磁盘

磁盘读完之后告诉DMA

DMA如果还需要读的话,会重复2,3步骤

DMA干完活之后通知CPU

DMA的出现无疑是帮助了CPU很多,特别是和IO设备打交道这块。

正常来说我们的程序在发起读数据后,需要等待数据的返回,因此需要CPU把内核缓冲区的数据再次COPY到用户缓冲区中,同时整个过程用户进程是阻塞的(因为要等数据),这一切看起来很合理,然而其实有这样一种场景:我们需要把读出的数据通过网络发出去,比如kafka,我们知道kafka是非常经典的消息引擎,当消费者需要消费消息的时候,kafka中的broker会把数据读出来,然后发给我们的消费者。

图中有两次看起来非常沙雕的操作,分别是第2步和第3步,关键这两步都需要CPU亲自参与搬运,并且涉及到内核态->用户态->内核态的上下文切换,这个上下文切换会导致什么呢?答案就是CPU需要进行现场保护(活干到一半就被打断了,等忙完了回来还要接着干),这个保护需要花费一定的开销,比如把当前的运行状态给保存下来,程序执行到哪了,寄存器该保存什么值...。

那有什么办法能省掉这次的开销呢?

升华 mmap + write

其实明眼人都看出来了,没必要把一份数据copy来copy去的,直接用内核态的缓冲区不就行了,这就是mmap(内存映射),我们还是先来看个例子,通过例子你就明白mmap的好处了:

现在有两个进程A和B,他们都需要读同一份数据,因此每个进程都要开辟一块用户态的缓冲区,即使数据是一样的,并且CPU还要发生两次copy,而且这只是两个进程,如果有更多的进程势必造成更多的内存空间浪费,于是就出现了mmap,有了mmap之后,不需要cpu copy数据了,并且进程A和进程B共享用户空间的一块内存,然后这块内存和内核空间的内存打通,注意这里并不是copy而是开启了一个映射,相当于开了一个VIP通道,有了VIP通道之后,同一份数据对于不同的进程不需要维护不同的内存空间了,因为大家共享一个公共的内存空间。

mmap只是打通了用户空间和内核空间之间的通路,可以说路是通了,接下来还要发数据呀,因此这时一般调用write把数据发出去,有了mmap+write ,我们再来看看这时数据是如何发出的:

首先肯定是程序发出mmap系统调用请求,然后DMA把数据copy到内核缓冲区去

DMA copy完之后,把内核缓冲区映射到用户缓冲区,注意映射和copy不一样,比copy的开销小

然后用户程序再次发起write请求

这时系统会把内核缓冲区的数据直接发到socket缓冲区

DMA copy socket缓冲区数据到网卡

通过 mmap + write 的方式可以发现少了一次CPU copy,但是系统调用并没有减少,有没有什么办法让系统调用再少些?

sendfile

没有什么能阻止进步的脚步,于是出现了sendfile,有了sendfile函数之后,首先它不需要进行两次系统调用,只需要一次系统调用,当我们sendfile之后等于告诉系统:“帮我把xx数据直接发出去吧,别再copy或者映射进来了,俺不需要,直接发出去就好”。

当我们发起sendfile之后,首先会切到内核态

然后DMA把数据copy到内核缓冲区

DMA把socket描述符等传到socket缓冲区

同时DMA把数据直接从内核缓冲区copy到网卡

可以发现这种方式是目前最优的方式了,通过sendfile+DMA技术可以实现真正的零拷贝,整个过程都不要cpu搬运数据,也没有上下文切换,kafka就是利用这种方式来提供吞吐的。

最后

微信搜一搜【假装懂编程】,关注牛逼知识抢先版,快人一步,胜人一筹,赢在起跑线上,另外这里给大准备了很多牛逼的book:

往期精彩:

程序员小扎-比特跳动公司的二面

程序员小扎-比特跳动公司的一面

内功大增!从机械硬盘和固态硬盘的结构来看IO

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。