范文健康探索娱乐情感热点
投稿投诉
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文

高性能的零拷贝技术原理你真懂吗?

  https://blog.biezhi.me/2019/01/zero-copy-user-mode-perspective.html什么是 "零拷贝" ?
  为了更好的理解这个问题,我们首先需要了解问题本身。来看一个网络服务的简单运行过程,在这个过程中将磁盘的文件读取到缓冲区,然后通过网络发送给客户端。下面是示例代码:read(file, tmp_buf, len);  write(socket, tmp_buf, len);
  这个例子看起来非常简单,你可能会认为只有两次系统调用不会产生太多的系统开销。实际上并非如此,在这两次调用之后,数据至少被拷贝了 4 次,同时还执行了很多次 用户态/内核态  的上下文切换。(实际上这个过程是非常复杂的,为了解释我尽可能保持简单)为了更好的理解这个过程,请查看下图中的上下文切换,图片上部分展示上下文切换过程,下部分展示拷贝操作。
  程序调用 read 产生一次用户态到内核态的上下文切换。DMA 模块从磁盘读取文件内容,将其拷贝到内核空间的缓冲区,完成第 1 次拷贝。数据从内核缓冲区拷贝到用户空间缓冲区,之后系统调用 read 返回,这回导致从内核空间到用户空间的上下文切换。这个时候数据存储在用户空间的 tmp_buf 缓冲区内,可以后续的操作了。程序调用 write 产生一次用户态到内核态的上下文切换。数据从用户空间缓冲区被拷贝到内核空间缓冲区,完成第 3 次拷贝。但是这次数据存储在一个和 socket 相关的缓冲区中,而不是第一步的缓冲区。write 调用返回,产生第 4 个上下文切换。第 4 次拷贝在 DMA 模块将数据从内核空间缓冲区传递至协议引擎的时候发生,这与我们的代码的执行是独立且异步发生的。你可能会疑惑:"为何要说是独立、异步?难道不是在 write 系统调用返回前数据已经被传送了?write 系统调用的返回,并不意味着传输成功——它甚至无法保证传输的开始。调用的返回,只是表明以太网驱动程序在其传输队列中有空位,并已经接受我们的数据用于传输。可能有众多的数据排在我们的数据之前。除非驱动程序或硬件采用优先级队列的方法,各组数据是依照FIFO的次序被传输的(上图中叉状的 DMA copy 表明这最后一次拷贝可以被延后)。mmap
  如你所见,上面的数据拷贝非常多,我们可以减少一些重复拷贝来减少开销,提升性能。作为一名驱动程序开发人员,我的工作围绕着拥有先进特性的硬件展开。某些硬件支持完全绕开内存,将数据直接传送给其他设备。这个特性消除了系统内存中的数据副本,因此是一种很好的选择,但并不是所有的硬件都支持。此外,来自于硬盘的数据必须重新打包(地址连续)才能用于网络传输,这也引入了某些复杂性。为了减少开销,我们可以从消除内核缓冲区与用户缓冲区之间的拷贝开始。
  减少数据拷贝的一种方法是将 read 调用改为 mmap。例如:tmp_buf = mmap(file, len);  write(socket, tmp_buf, len);
  为了方便你理解,请参考下图的过程。
  mmap 调用导致文件内容通过 DMA 模块拷贝到内核缓冲区。然后与用户进程共享缓冲区,这样不会在内核缓冲区和用户空间之间产生任何拷贝。write 调用导致内核将数据从原始内核缓冲区拷贝到与 socket 关联的内核缓冲区中。第 3 次数据拷贝发生在 DMA 模块将数据从 socket 缓冲区传递给协议引擎时。
  通过调用 mmap 而不是 read,我们已经将内核拷贝数据操作减半。当传输大量数据时,效果会非常好。然而,这种改进并非没有代价;使用 mmap + write 方式存在一些隐藏的陷阱。当内存中做文件映射后调用 write,与此同时另一个进程截断这个文件时。此时 write 调用的进程会收到一个 SIGBUS 中断信号,因为当前进程访问了非法内存地址。这个信号默认情况下会杀死当前进程并生成 dump 文件——而这对于网络服务器程序而言不是最期望的操作。有两种方式可用于解决该问题:
  第一种方法是处理收到的 SIGBUS 信号,然后在处理程序中简单地调用 return。通过这样做,write 调用会返回它在被中断之前写入的字节数,并且将全局变量 errno 设置为成功。我认为这是一个治标不治本的解决方案。因为收到 SIGBUS 信号表示程序发生了严重的错误,我不推荐使用它作为解决方案。
  第二种方式应用了文件租借(在Microsoft Windows系统中被称为"机会锁")。这才是解劝前面问题的正确方式。通过对文件描述符执行租借,可以同内核就某个特定文件达成租约。从内核可以获得读/写租约。当另外一个进程试图将你正在传输的文件截断时,内核会向你的进程发送实时信号——RT_SIGNAL_LEASE。该信号通知你的进程,内核即将终止在该文件上你曾获得的租约。这样,在write调用访问非法内存地址、并被随后接收到的SIGBUS信号杀死之前,write系统调用就被RT_SIGNAL_LEASE信号中断了。write的返回值是在被中断前已写的字节数,全局变量errno设置为成功。下面是一段展示如何从内核获得租约的示例代码。1if (fcntl(fd, F_SETSIG, RT_SIGNAL_LEASE) == -1) { 2    perror("kernel lease set signal"); 3    return -1; 4} 5/* l_type can be F_RDLCK F_WRLCK */ 6if (fcntl(fd, F_SETLEASE, l_type)) { 7    perror("kernel lease set type"); 8    return -1; 9}
  在对文件进行映射前,应该先获得租约,并在结束 write 操作后结束租约。这是通过在 fcntl 调用中指定租约类型为 F_UNLCK 来实现的。Sendfile
  在内核的 2.1 版本中,引入了 sendfile 系统调用,目的是简化通过网络和两个本地文件之间的数据传输。sendfile 的引入不仅减少了数据拷贝,还减少了上下文切换。可以这样使用它:1sendfile(socket, file, len);
  同样的,为了理解起来方便,可以看下图的调用过程。
  sendfile 调用会使得文件内容通过 DMA 模块拷贝到内核缓冲区。然后,内核将数据拷贝到与 socket 关联的内核缓冲区中。第 3 次拷贝发生在 DMA 模块将数据从内核 socket 缓冲区传递到协议引擎时。
  你可能想问当我们使用 sendfile 调用传输文件时有另一个进程截断会发生什么?如果我们没有注册任何信号处理程序,sendfile 调用只会返回它在被中断之前传输的字节数,并且全局变量 errno 被设置为成功。
  但是,如果我们在调用 sendfile 之前从内核获得了文件租约,那么行为和返回状态完全相同。我们会在sendfile 调用返回之前收到一个 RT_SIGNAL_LEASE 信号。
  到目前为止,我们已经能够避免让内核产生多次拷贝,但我们还有一次拷贝。这可以避免吗?当然,在硬件的帮助下。为了避免内核完成的所有数据拷贝,我们需要一个支持收集操作的网络接口。这仅仅意味着等待传输的数据不需要在内存中;它可以分散在各种存储位置。在内核 2.4 版本中,修改了 socket 缓冲区描述符以适应这些要求 - 在 Linux 下称为零拷贝。这种方法不仅减少了多个上下文切换,还避免了处理器完成的数据拷贝。对于用户的程序不用做什么修改,所以代码仍然如下所示:sendfile(socket, file, len);
  为了更好地了解所涉及的过程,请查看下图
  sendfile 调用会导致文件内容通过 DMA 模块拷贝到内核缓冲区。没有数据被复制到 socket 缓冲区。相反,只有关于数据的位置和长度信息的描述符被附加到 socket 缓冲区。DMA 模块将数据直接从内核缓冲区传递到协议引擎,从而避免了剩余的最终拷贝。
  因为数据实际上仍然是从磁盘复制到内存,从内存复制到总线,所以有人可能会认为这不是真正的零拷贝。但从操作系统的角度来看,这是零拷贝,因为内核缓冲区之间的数据不会产生多余的拷贝。使用零拷贝时,除了避免拷贝外,还可以获得其他性能优势,比如更少的上下文切换,更少的 CPU 高速缓存污染以及不会产生 CPU 校验和计算。
  现在我们知道了什么是零拷贝,把前面的理论通过编码来实践。你可以从 http://www.xalien.org/articles/source/sfl-src.tgz 下载源码。解压源码需要执行 tar -zxvf sfl-src.tgz,然后编译代码并创建一个随机数据文件 data.bin,接下来使用 make 运行。
  查看头文件:/* sfl.c sendfile example program   Dragan Stancevic <   header name                 function / variable   -------------------------------------------------*/   #include           /* printf, perror */   #include           /* open */   #include          /* close */   #include           /* errno */   #include          /* memset */ 	#include      /* socket */ 	#include      /* sockaddr_in */ 	#include    /* sendfile */ 	#include       /* inet_addr */ 	#define BUFF_SIZE (10*1024) /* size of the tmp buffer */
  除了 socket 操作需要的头文件  和  之外,我们还需要 sendfile 调用的头文件 - :/* are we sending or receiving */ if(argv[1][0] == "s") is_server++; /* open descriptors */ sd = socket(PF_INET, SOCK_STREAM, 0); if(is_server) fd = open("data.bin", O_RDONLY);
  同样的程序既可以充当 服务端/发送者 ,也可以充当 客户端/接受者 。这里我们接收一个命令提示符参数,通过该参数将标志 is_server 设置为以 发送方模式 运行。我们还打开了 INET 协议族的流套接字。作为在服务端运行的一部分,我们需要某种类型的数据传输到客户端,所以打开我们的数据文件(data.bin)。由于我们使用 sendfile 来传输数据,所以不用读取文件的实际内容将其存储在程序的缓冲区中。这是服务端地址:/* clear the memory */ memset(&sa, 0, sizeof(struct sockaddr_in)); /* initialize structure */ sa.sin_family = PF_INET; sa.sin_port = htons(1033); sa.sin_addr.s_addr = inet_addr(argv[2]);
  我们重置了服务端地址结构并分配了端口和 IP 地址。服务端的地址作为命令行参数传递,端口号写死为 1033,选择这个端口号是因为它是一个允许访问的端口范围。
  下面是服务端执行的代码分支:if(is_server){     int client; /* new client socket */     printf("Server binding to [%s] ", argv[2]);     if(bind(sd, (struct sockaddr *)&sa,                       sizeof(sa)) < 0){         perror("bind");         exit(errno);     } }
  作为服务端,我们需要为 socket 描述符分配一个地址。这是通过系统调用 bind 实现的,它为 socket 描述符(sd)分配一个服务器地址(sa):if(listen(sd,1) < 0){     perror("listen");     exit(errno); }
  因为我们正在使用流套接字,所以我们必须接受传入连接并设置连接队列大小。我将缓冲压队列设置为 1,但对于等待接受的已建立连接,一般会将缓冲值要设置的更高一些。在旧版本的内核中,缓冲队列用于防止 syn flood 攻击。由于系统调用 listen 已经修改为 仅为已建立的连接设置参数 ,所以不使用这个调用的缓冲队列功能。内核参数 tcp_max_syn_backlog 代替了保护系统免受 syn flood 攻击的角色:if((client = accept(sd, NULL, NULL)) < 0){     perror("accept");     exit(errno); }
  accept 调用从挂起连接队列上的第一个连接请求创建一个新的 socket 连接。调用的返回值是新创建的连接的描述符; socket 现在可以进行读、写或轮询/select 了:if((cnt = sendfile(client,fd,&off,                           BUFF_SIZE)) < 0){     perror("sendfile");     exit(errno); } printf("Server sent %d bytes. ", cnt); close(client);
  在客户端 socket 描述符上建立连接,我们可以开始将数据传输到远端。通过 sendfile 调用来实现,该调用是在 Linux 下通过以下方式原型化的:extern ssize_t sendfile (int __out_fd, int __in_fd, off_t *offset,           size_t __count) __THROW;前两个参数是文件描述符。第 3 个参数指向 sendfile 开始发送数据的偏移量。第四个参数是我们要传输的字节数。
  为了使 sendfile 传输使用零拷贝功能,你需要从网卡获得内存收集操作支持。还需要实现校验和的协议的校验和功能,通过 TCP 或 UDP。如果你的 NIC 已过时不支持这些功能,你也可以使用 sendfile 来传输文件,不同之处在于内核会在传输之前合并缓冲区。移植性问题
  通常,sendfile 系统调用的一个问题是缺少标准实现,就像开放系统调用一样。Linux、Solaris 或 HP-UX 中 的 Sendfile 实现完全不同。这对于想通过代码实现零拷贝的开发人员而言是个问题。
  其中一个实现差异是 Linux 提供了一个 sendfile 接口,用于在两个文件描述符(文件到文件)和(文件到socket)之间传输数据。另一方面,HP-UX 和 Solaris 只能用于文件到 socket 的提交。
  第二个区别是 Linux 没有实现向量传输。Solaris sendfile 和 HP-UX sendfile 有一些扩展参数,可以避免与正在传输的数据添加头部的开销。展望
  Linux 下的零拷贝实现离最终实现还有点距离,并且很可能在不久的将来发生变化。要添加更多功能,例如,sendfile 调用不支持向量传输,而 Samba 和 Apache 等服务器必须使用设置了 TCP_CORK 标志的多个sendfile 调用。这个标志告诉系统在下一个 sendfile 调用中会有更多数据通过。TCP_CORK 和TCP_NODELAY 不兼容,并且在我们想要在数据前添加或附加标头时使用。这是一个完美的例子,其中向量调用将消除对当前实现所强制的多个 sendfile 调用和延迟的需要。
  当前 sendfile 中一个相当令人不快的限制是它在传输大于2GB的文件时无法使用。如此大小的文件在今天并不罕见,并且在出路时复制所有数据相当令人失望。因为在这种情况下sendfile和mmap方法都不可用,所以sendfile64在未来的内核版本中会非常方便。
  英文原文:http://www.linuxjournal.com/article/6345

落后29分,落后31分!西部豪门现原形,王朝终结者作茧自缚NA常规赛开打数周,30支球队大展拳脚,竞争的残酷性在战绩与排名上体现得淋漓尽致。随着赛季中期交易大门开启日的临近,各队都该总结得与失,争取朝着既定目标迈进。对于绿军勇士雄鹿这些争数字化智能工厂智能制造体系顶层设计及应用系统详细解决方案WORD来源网络,旨在交流学习,如有侵权,联系速删,更多参考公众号优享智库一智能工厂设计思路智能工厂建设目标企业进行智能工厂建设后,从原料到产成品整条生产线实现调度集中管控,智能化扁平管理总台记者看世界丨海外唯一完整华文教育体系,马来西亚如何做到的?总台记者看世界!大家好,我是总台驻吉隆坡站记者闫术。说到马来西亚您一定不陌生,这里是经典出国旅游路线新马泰三国之一。多民族聚集,华人占本国人口约四分之一。比如大家耳熟能详的羽球名将猪肉鸡蛋蔬菜等农产品价格稳中回落消费需求得到充分保障央广网北京12月14日消息(记者吕红桥)据中央广播电视总台经济之声环球新财讯报道,临近年底,农产品供应情况和价格受到关注。记者从多地市场了解到,猪肉鸡蛋蔬菜等重要民生商品最近供应充薪酬化的年终奖不能以实物替代上海MA销售公司于2020年1月招聘了一名市场总监老刘。双方约定,其年薪由底薪考核绩效组成。其中,考核绩效是指年终考核对应发放的年终奖。老刘的每月底薪为2万元,年度考核绩效奖金目标迎合新需求,即时零售挑战传统电商线上下单门店发货商品小时级送达,满足消费者的新需求,即时零售正在挑战传统电商。迎合新需求,外卖送万物渐成现实外卖送万物渐成现实,消费者的需求,正从应急走向日常,从餐饮品类走向非餐。报告家族信托客群年轻化多元化,家族信托功能与需求仍存在错位21世纪经济报道记者杨希北京报道2012年是中国家族信托产业的元年,这一年已有信托公司进行家族信托业务的探索与尝试。十年之后,中国家族信托产业发展迅猛,呈现出了新特征。家族信托也因活在宋朝跟着国画去旅行(2)我家住在东京城老赵坐了天下老孟在东京城住了三十多年,今天就带大家先逛逛东京城。此东京城非你们现在日本的首都。这个东京就是河南的开封,是大宋的都城,还有个陪都在洛阳,开封在东边就称东京,洛阳在西边卡塔尔世界杯现场记者连续猝死!48小时爆两桩惨剧,目击者揭过程上周,世界杯到了最惊险刺激的8进4阶段,然而在卡塔尔的记者连续死亡事件,给这次盛会蒙上了不小的阴影。有人怀疑,卡塔尔豪掷10亿美元建设的豪华场馆只是一个充满安全隐患的面子工程,更有活在宋朝跟着国画去旅行(4)买卖繁荣元老今天带大家看看东京城都有啥买卖!汴京城里的市场有各种各样的买卖,专业分工很强你若养马,就有商家每天安排两人供应马所需的饲草你若养狗,就有商家供给饴糖渣你若养猫,就有商家专门卖猫历史上同名的两位奇女子,一位嫁了三个皇帝,一个却生了三个皇帝历史上有两位奇女子,她们俩同名异姓,巧合的是一位一生嫁了三个皇帝,另外一位一生却生了三个皇帝。你知道她们是谁吗?说到王昭君,想必大家并不陌生,她是古代四大美女之一,有着落雁之美誉。
AI,推翻人类的斯巴达?最近在研究chatgpt的东西,我忍不住产生了一个思考。人,到底是什么?在学术上,关于人和其他生物之间的区别在于,人类可以制造工具,而早年马克思的定义是人类可以制造和使用工具,为什宝山这个老年艺术团助力老宝贝们开启华彩人生中国老年群体比例越来越高,这群老宝贝们的老年生活也从一个侧面反映着整个社会的精神面貌。近日,宝山区杨行华彩艺术团开学授课,这个民间自发的老年艺术团让老宝贝们老有所乐老有所学老有所为禁止人类踏足的巴西蛇岛有多恐怖?近4000条剧毒蛇,蛇獴能生存吗在巴西的东南区域,距离圣保罗州海岸约33公里处,这里有一座和大连蛇岛美国蛇岛相匹敌的大凯马达岛蛇岛,又叫巴西蛇岛,岛上以盛产一种独有的金矛头蝮著名,是巴西最恐怖的岛屿之一,到了现在聊聊你心目中博卡青年的最佳11人博卡青年是南美赛场上最成功的球队之一,一直是该地区一些的青年才俊最想加盟的俱乐部。当你说博卡青年队时,你会想起迭戈马拉多纳。但阿根廷传奇并不是代表博卡青年唯一一个备受瞩目的名字。当辽宁队最新猛料!琦继合体,小郭艾伦53分库里附体饼王遭嘲讽辽宁队正紧张有序地备战3月1日开始的第三阶段的比赛,目前已经加大了训练量,由原先的一天两次训练到一天三次训练,除了上午和下午两练,晚上还要加个班。晚上主要练投篮,因为前两轮比赛辽宁我们对NikeKobe6Protro黑人历史月的了解分析科比布莱恩特第六款采用全新黑人历史月配色的签名耐克篮球鞋。除了成为有史以来最伟大的篮球运动员之一,科比布莱恩特还声称拥有有史以来最好的签名运动鞋系列。在这位洛杉矶湖人队传奇人物历史的影像在天地的百叶窗上逗留历史的影像在天地的百叶窗上逗留给我轻轻地拉上被角袒露的胸膛有了热量爆发,热之能白天和黑夜僵持着我知道,今夜群英荟萃要评选历史之骄子爆发,热之能胸膛里的能,胸膛里的热历经着孕育与分娩在李安导演的作品中,人物妆发设计与自我挣扎的关系发型和妆容都是人关于人体自身的装饰,是政治民族社会审美等元素在人体上具体的呈现方式,具有较强的文化内涵,展现了不同角色的性格气质。比起服装代表着社会外在对人的期待与定位,妆发在李安历史将如何评判网队的凯文凯里欧文时代?就在不久前,篮网队还在谈论杜兰特和欧文的组合终于看起来可以带来NBA总冠军了。现在,在经历了两次令人头晕目眩的交易之后,杜兰特被送太阳队,换取了一系列首轮选秀权。欧文被送到达拉斯小在五个一百中品读时代新图景近日,2022中国正能量五个一百网络精品征集评选展播活动进入报送阶段。一批以正能量引领亿万流量用主旋律奏响奋进乐章的精品佳作持续涌现。五个一百就像一本影集,记录着山乡巨变山河锦绣的日本棒球手加盟中国!系世界杯MVP,不会说中文,父子沟通靠翻译世界棒球经典赛WBC时隔五年将再次重启,2月10日,中国棒球国家队公布了30人大名单,这一次中国棒球国家队分别有美籍华裔阿兰卡特张宝树,韩籍华裔朱权日本华裔真砂勇介共计4位华裔归化