DPDK收发包流程分析(一) 男娘i 2022-11-01 05:39 362阅读 0赞 ## **一、 前言】** ## DPDK是intel工程师开发的一款用来快速处理数据包的框架,最初的目的是为了证明传统网络数据包处理性能低不是intel处理器导致的,而是传统数据的处理流程导致,后来随着dpdk的开源及其生态的快速发展,dpdk成为了高性能网络数据处理的优秀框架。本篇文章主要介绍DPDK接收与发送报文的流程,包括CPU与网卡DMA协同工作的整个交互流程、数据包在内存、CPU、网卡之间游走的过程。 ## **【二、场景】** ## DPDK从2013开始开源,经过前辈们的缝缝补补到现在为止DPDK框架比较成熟、使用比较方便,使得现在开发者在不需要深入了解底层数据包收发原理的情况下也可以做简单的项目开发。但是个人感觉做简单的项目尚且可以应付,如果需要做性能优化等类似的需求时就需要取全面的了解DPDK的收发包机制,因为收发包性能与驱动工作流程、前期初始化配置息息相关。话不多说,下面我们进入正题。 ## **【三、收发包处理流程】** ## ![20201012202936738.png][] * **数据包接收的大体流程:** 1. 数据包到达网卡 2. 网卡经过DMA操作将数据包从网卡拷贝到收包队列 3. DPDK应用从收包队列中取包 * **数据包发送的大体流程:** 1. DPDK应用将数据包送到发包队列 2. 网卡经过DMA操作将网卡队列中的数据包拷贝到网卡 3. 数据包从网卡发出 * **收发包流程中的关键操作,主要是网卡如何与DPDK应用交互:** 1. 网卡的初始化配置操作有哪些,因为网卡要想正常工作肯定需要进行初始化配置一些属性 2. 网卡的DMA操作怎么找到DMA 地址,进而将数据包拷贝到系统主存供DPDK读取 3. 网卡把数据包成功放到队列后如何通知DPDK应用去队列中读取 4. DPDK从队列中取完数据包后需要做哪些操作通知网卡为下一次收包做准备 5. DPDK将数据包送到发送队列中需要做哪些预操作 6. 网卡从发送队列中取包需要做哪些操作 ## **【四、收包软件处理流程】** ## DPDk在初始化阶段通过igb\_alloc\_rx\_queue\_mbufs 负责将描述符,mbuf, dma,接收队列给关联起来,如下图所示。初始化配置这块内容下篇讲 ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTQ3ODc4MTU_size_16_color_FFFFFF_t_70][] ## **1、模块/硬件介绍** ## **Network interface:** 指以太网卡,它工作在OSI的下两层(物理层、数据链路层),工作在物理层的芯片称为PHY,工作在数据链路层的芯片称为MAC控制器,即Media Access Control,即媒体访问控制子层协议.该协议位于OSI七层协议中数据链路层的下半部分,但是目前好多网卡是将MAC和PHY功能做到了一颗芯片中,但是MAC和PHY的机制还是单独存在的,只是对外体现为一颗芯片。MAC控制器的功能主要是数据帧的构建、数据差错检查、传送控制、向网络层提供标准的数据接口等功能;PHY芯片的主要功能是将从PHY来的并行数据转换为穿行流数据,再按照物理层的编码规则把数字信号进行编码,最后再转换为模拟信号把数据发出去。 **RX\_FIFO: **数据接收缓冲区 **TX\_FIFO: **数据发送缓冲区 **DMA Engine:**Direct Memory Access,即直接寄存器访问,是一种告诉的数据传输方式,允许在外部设备和存储器之间直接读写数据,数据的读写不消耗CPU资源,DMA控制器通过一组描述符环形队列与CPU互相操作完成数据包的收发。CPU通过操作DMA寄存器来与DMA控制器进行部分通信与初始化配置,主要寄存器有Base、Size、Tail、Head,head寄存器用于DMA往rx\_ring里插入时使用,tail是应用通过写寄存器通知给DMA控制器当前可用的最后一个描述符(head->next 为tail时表示当前rx\_ring存满了,再来报文会被记录rx\_missed\_error)。 **Rx\_queue: 收包队列结构体:**我们主要关注两个环形队列rx\_ring、sw\_ring **Rx\_ring:**一个地址连续环形队列,存储的是描述符,描述符中包含将来存放数据包的物理地址、DD标志(下面会介绍DD标志)等,上面图中只画了存放数据包的物理地址,物理地址供网卡DMA模块使用,也称为DMA地址(硬件使用物理地址,当网卡收到数据包后会通过DMA操作将数据包拷贝到物理地址,物理地址是通过虚拟地址转换得到的,下面分析源码时会介绍) **Sw\_ring: **存储的是将来存放数据包的虚拟地址,虚拟地址供应用使用(软件应用使用虚拟地址,应用往虚拟地址读写数据包) **DD标志:**用于标识一个描述符buf是否可用,无论网卡是工作在轮询方式下还是中断方式,判断数据包接收成功或者是否发送成功都需要检查描述符中的完成状态位(Description Done)DD,该状态位由DMA控制器在完成操作后进行回写 **Mbuf:**对应于Mbuf内存池中的元素,通过alloc或者free操作内存池获取或者释放mbuf对象,这里需要说的一点是mbuf池创建的时候是连续的,但是rx\_ring和sw\_ring里指向的数据地址不一定是连续的,下面分析收包流程时会介绍 **PCIE总线:**采用高速串行通信互联标准,自上而下分为事务传输层、数据链路层、物理层,网卡与CPU之间数据包的传输、CPU对网卡寄存器的MMIO操作都通过PCIE进行传输 **DMA寄存器:CPU配置网卡的操作通过操作网卡的寄存器,寄存器主要** ## **2、收包流程** ## **Rx\_ring收数据时的状态如下:** -------------------- DMA控制器收到数据后往head写,当head=tail时表示当前队列为空,head->bext = tail表示当前队列已存满,dpdk启动刚初始化完后如下图所示: ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTQ3ODc4MTU_size_16_color_FFFFFF_t_70 1][] -------------------- ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTQ3ODc4MTU_size_16_color_FFFFFF_t_70 2][] -------------------- 可以看出来cpu对tail寄存器的更新并不是在rx\_ring描述符中填充完dma地址后立马就执行,而是等dma可用描述符低于一定阈值时才执行写寄存器更新tail ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTQ3ODc4MTU_size_16_color_FFFFFF_t_70 3][] -------------------- ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTQ3ODc4MTU_size_16_color_FFFFFF_t_70 4][] -------------------- **具体详细流程如下:** (1)CPU填缓冲地址(mbuf中的data)到收接收侧描述符(在dpdk初始化时就会第一次填充),也就是上图中rx\_ring会指向 mbuf池中的 部分mbuf用于接收数据包;另外CPU通过操作网卡的base、size寄存器,将rx\_ring环形队列的起始地址和内 存卡大小告诉给DMA控制器,将描述符队列的物理地址写入到寄存器后,dma 通过读这个寄存器就知道了描述符队列的地 址,进而 dma收到报文后,会将报文保存到描述符指向的 mbuf 空间 (2)网卡读取rx\_ring队列里接收侧的描述符进而获取系统缓冲区地址 (3)从外部到达网卡的报文数据先存储到网卡本地的RX\_FIFO缓冲区 (4)DMA通过PCIE总线将报文数据写到系统的缓冲区地址 (5)网卡回写rx\_ring接收侧描述符的更新状态DD标志,置1表示接收完毕 (6)CPU读取描述符sw\_ring队列中元素的DD状态,如果为1则表示网卡已经接收完毕,应用可以读取数据包 (7)CPU从sw\_ring中读取完数据包后有个“狸猫换太子”的动作,重新从mbuf池中申请一个mbuf替换到sw\_ring的该描述符中、 将新分配的mbuf虚拟地址转换为物理地址更新到rx\_ring中该条目位置的dma物理地址、更新描述符rx\_ring队列里的DD标 志置0,这样网卡就可以持续往rx\_ring缓冲区写数据了 (8)CPU判断rx\_ring里可用描述符小于配置的阈值时更新tail寄存器,而不是回填一个mbuf到描述符就更新下tail寄存器(因为CPU高频率的操作寄存器是性能的杀手,所以改用此机制) (9)至此,应用接收数据包完毕 注意:这里有两个非常关键的队列rx\_ring、sw\_ring,rx\_ring描述符里存放的是mbuf里data区的起始物理地址供DMA控制器收到报文后往该地址写入(硬件DMA直接操作物理地址,不需要cpu参与);sw\_ring描述符里存放的是mbuf的起始虚拟地址供应用读取数据包 ## **3、代码的实现过程** ## 我们以e1000驱动类型网卡举例,来分析数据包接收相关的软件结构及接口 (1)接收队列的结构如下图,图中标出了两个重要的环形队列(上面已介绍): ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTQ3ODc4MTU_size_16_color_FFFFFF_t_70 5][] (2) 数据包的软件接收流程如下图 ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTQ3ODc4MTU_size_16_color_FFFFFF_t_70 6][] (3)分析下eth\_igb\_recv\_pkts接口源码,与第四章的收包内部流程相对应 > uint16\_t > eth\_igb\_recv\_pkts(void \*rx\_queue, struct rte\_mbuf \*\*rx\_pkts, > uint16\_t nb\_pkts) > \{ > struct igb\_rx\_queue \*rxq; > volatile union e1000\_adv\_rx\_desc \*rx\_ring; > volatile union e1000\_adv\_rx\_desc \*rxdp; > struct igb\_rx\_entry \*sw\_ring; > struct igb\_rx\_entry \*rxe; > struct rte\_mbuf \*rxm; > struct rte\_mbuf \*nmb; > union e1000\_adv\_rx\_desc rxd; > uint64\_t dma\_addr; > uint32\_t staterr; > uint32\_t hlen\_type\_rss; > uint16\_t pkt\_len; > uint16\_t rx\_id; > uint16\_t nb\_rx; > uint16\_t nb\_hold; > uint64\_t pkt\_flags; > > nb\_rx = 0; > nb\_hold = 0; > rxq = rx\_queue; > rx\_id = rxq->rx\_tail; > rx\_ring = rxq->rx\_ring; > sw\_ring = rxq->sw\_ring; > while (nb\_rx < nb\_pkts) \{ > /\*第一步:从描述符队列中找到被应用最后一次接收的那个文件描述符的位置\*/ > rxdp = &rx\_ring\[rx\_id\]; > staterr = rxdp->wb.upper.status\_error; > > /\*第二步:检查DD状态是否为1,为1则说明驱动已经将报文成功放到接收队列,否则直接退出\*/ > if (! (staterr & rte\_cpu\_to\_le\_32(E1000\_RXD\_STAT\_DD))) > break; > rxd = \*rxdp; > > > PMD\_RX\_LOG(DEBUG, "port\_id=%u queue\_id=%u rx\_id=%u " > "staterr=0x%x pkt\_len=%u", > (unsigned) rxq->port\_id, (unsigned) rxq->queue\_id, > (unsigned) rx\_id, (unsigned) staterr, > (unsigned) rte\_le\_to\_cpu\_16(rxd.wb.upper.length)); > > /\*第三步:从mbuf池中重新申请一个mbuf,为下面的填充做准备\*/ > > nmb = rte\_mbuf\_raw\_alloc(rxq->mb\_pool); > if (nmb == NULL) \{ > PMD\_RX\_LOG(DEBUG, "RX mbuf alloc failed port\_id=%u " > "queue\_id=%u", (unsigned) rxq->port\_id, > (unsigned) rxq->queue\_id); > rte\_eth\_devices\[rxq->port\_id\].data->rx\_mbuf\_alloc\_failed++; > break; > \} > > nb\_hold++; > > /\*第四步:找到了描述符的位置,也就找到了需要取出的mbuf\*/ > rxe = &sw\_ring\[rx\_id\]; > rx\_id++; > if (rx\_id == rxq->nb\_rx\_desc) > rx\_id = 0; > > /\* Prefetch next mbuf while processing current one. \*/ > rte\_igb\_prefetch(sw\_ring\[rx\_id\].mbuf); > > /\* > \* When next RX descriptor is on a cache-line boundary, > \* prefetch the next 4 RX descriptors and the next 8 pointers > \* to mbufs. > \*/ > if ((rx\_id & 0x3) == 0) \{ > rte\_igb\_prefetch(&rx\_ring\[rx\_id\]); > rte\_igb\_prefetch(&sw\_ring\[rx\_id\]); > \} > > rxm = rxe->mbuf; > > /\*第五步:给描述符sw\_ring队列重新填写新申请的mbuf\*/ > rxe->mbuf = nmb; > > /\*第六步:将新申请的mbuf的虚拟地址转换为物理地址,为rx\_ring的缓冲区填充做准备\*/ > dma\_addr = > rte\_cpu\_to\_le\_64(rte\_mbuf\_data\_iova\_default(nmb)); > > /\*第七步:将rx\_ring描述符中该条目的DD标志置0,表示允许DMA控制器操作\*/ > rxdp->read.hdr\_addr = 0; > > /\*第八步:重新填充rx\_ring描述符中该条目的dma地址\*/ > rxdp->read.pkt\_addr = dma\_addr; > > /\* > \* Initialize the returned mbuf. > \* 1) setup generic mbuf fields: > \* - number of segments, > \* - next segment, > \* - packet length, > \* - RX port identifier. > \* 2) integrate hardware offload data, if any: > \* - RSS flag & hash, > \* - IP checksum flag, > \* - VLAN TCI, if any, > \* - error flags. > \*/ > > /\*第九步:对获取到的数据包做部分封装,比如:报文类型、长度等\*/ > pkt\_len = (uint16\_t) (rte\_le\_to\_cpu\_16(rxd.wb.upper.length) - > rxq->crc\_len); > rxm->data\_off = RTE\_PKTMBUF\_HEADROOM; > rte\_packet\_prefetch((char \*)rxm->buf\_addr + rxm->data\_off); > rxm->nb\_segs = 1; > rxm->next = NULL; > rxm->pkt\_len = pkt\_len; > rxm->data\_len = pkt\_len; > rxm->port = rxq->port\_id; > > rxm->hash.rss = rxd.wb.lower.hi\_dword.rss; > hlen\_type\_rss = rte\_le\_to\_cpu\_32(rxd.wb.lower.lo\_dword.data); > > /\* > \* The vlan\_tci field is only valid when PKT\_RX\_VLAN is > \* set in the pkt\_flags field and must be in CPU byte order. > \*/ > if ((staterr & rte\_cpu\_to\_le\_32(E1000\_RXDEXT\_STATERR\_LB)) && > (rxq->flags & IGB\_RXQ\_FLAG\_LB\_BSWAP\_VLAN)) \{ > rxm->vlan\_tci = rte\_be\_to\_cpu\_16(rxd.wb.upper.vlan); > \} else \{ > rxm->vlan\_tci = rte\_le\_to\_cpu\_16(rxd.wb.upper.vlan); > \} > pkt\_flags = rx\_desc\_hlen\_type\_rss\_to\_pkt\_flags(rxq, hlen\_type\_rss); > pkt\_flags = pkt\_flags | rx\_desc\_status\_to\_pkt\_flags(staterr); > pkt\_flags = pkt\_flags | rx\_desc\_error\_to\_pkt\_flags(staterr); > rxm->ol\_flags = pkt\_flags; > rxm->packet\_type = igb\_rxd\_pkt\_info\_to\_pkt\_type(rxd.wb.lower. > lo\_dword.hs\_rss.pkt\_info); > > /\* > \* Store the mbuf address into the next entry of the array > \* of returned packets. > \*/ > > /\*第十步:将获取到的报文放入将要返回给用户操作的指针数组中\*/ > rx\_pkts\[nb\_rx++\] = rxm; > \} > rxq->rx\_tail = rx\_id; > > /\* > \* If the number of free RX descriptors is greater than the RX free > \* threshold of the queue, advance the Receive Descriptor Tail (RDT) > \* register. > \* Update the RDT with the value of the last processed RX descriptor > \* minus 1, to guarantee that the RDT register is never equal to the > \* RDH register, which creates a "full" ring situtation from the > \* hardware point of view... > \*/ > > /\*第十步:CPU判断rx\_ring里可用描述符小于配置的阈值时更新尾部寄存器供DMA控制器参考\*/ > nb\_hold = (uint16\_t) (nb\_hold + rxq->nb\_rx\_hold); > if (nb\_hold > rxq->rx\_free\_thresh) \{ > PMD\_RX\_LOG(DEBUG, "port\_id=%u queue\_id=%u rx\_tail=%u " > "nb\_hold=%u nb\_rx=%u", > (unsigned) rxq->port\_id, (unsigned) rxq->queue\_id, > (unsigned) rx\_id, (unsigned) nb\_hold, > (unsigned) nb\_rx); > rx\_id = (uint16\_t) ((rx\_id == 0) ? > (rxq->nb\_rx\_desc - 1) : (rx\_id - 1)); > E1000\_PCI\_REG\_WRITE(rxq->rdt\_reg\_addr, rx\_id); > nb\_hold = 0; > \} > rxq->nb\_rx\_hold = nb\_hold; > return nb\_rx; > \}** ** ## **【五、发包软件处理流程】** ## 发包流程与第四章的收包流程有些区别,DPDk在初始化阶段不会从mbuf池中获取缓冲区插到描述符队列空间里,只有在真正的发送过程中才会从mbuf池中获取mbuf,然后插入到描述符队列空间里。网卡的初始化配置部分下篇讲。 ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTQ3ODc4MTU_size_16_color_FFFFFF_t_70 7][] ## **1、模块/硬件介绍** ## **Tx\_queue: 发包队列结构体:**我们主要关注两个环形队列tx\_ring、sw\_ring **Tx\_ring:**一个地址连续环形队列,存储的是描述符,描述符中包含将来存放数据包的物理地址、DD标志(下面会介绍DD标志)等,上面图中只画了存放数据包的物理地址,物理地址供网卡DMA模块使用,也称为DMA地址(硬件使用物理地址,当网卡收到数据包后会通过DMA操作将数据包拷贝到物理地址,物理地址是通过虚拟地址转换得到的,下面分析源码时会介绍) ## **2、发包流程** ## **Tx\_ring发数据时的状态如下:** -------------------- dpdk初始化完成时tx\_ring的队列为空 ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTQ3ODc4MTU_size_16_color_FFFFFF_t_70 8][] -------------------- DMA控制器通过head去判断当前的DD状态,如果为0则可以执行发送动作 ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTQ3ODc4MTU_size_16_color_FFFFFF_t_70 9][] -------------------- ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTQ3ODc4MTU_size_16_color_FFFFFF_t_70 10][] -------------------- CPU需要对tx\_ring上网卡发送成功的描述符的缓存空间进行释放操作,待应用下次继续写入 ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTQ3ODc4MTU_size_16_color_FFFFFF_t_70 11][] -------------------- (1)CPU读取发送侧描述符tx\_ring队列,检查DD标志是否为1,为1则说明发送完毕 (2)针对发送完毕的描述符需要释放该描述符里对应的缓冲区 (3)CPU将准备发送的缓冲区mbuf的虚拟地址填充到描述符sw\_ring (4)CPU通过将准备发送的缓冲区mbuf的虚拟地址转换得到该mbuf里data数据部分的物理地址填充到发送测描述符tx\_ring队列 中,并将DD标志清0 (5)DMA控制器读取base寄存器,获取发送侧描述符,根据发送测描述符获取tx\_ring队列地址,读取head指针里的元素,判断DD标志释放为0则从描述符中获取数据缓存区地址,通过PCIE总线将数据拷贝到网卡硬件Tx\_FIFO缓存中往外发送数据 (6)DMA控制器回写该描述符中队列里DD的标志置1,通知CPU该缓存中数据已成功发送 (7)至此,应用发送数据包完毕 注意:这里有两个非常关键的队列tx\_ring、sw\_ring,tx\_ring描述符里存放的是mbuf里data区的起始物理地址供DMA控制器读取报文(硬件DMA直接操作物理地址,不需要cpu参与);sw\_ring描述符里存放的是mbuf的起始虚拟地址供应用写入数据包 ## **3、代码的实现过程** ## 我们以e1000驱动类型网卡举例,来分析数据包发送相关的软件结构及接口 (1)发送队列的结构如下图,图中标出了两个重要的环形队列(上面已介绍): ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTQ3ODc4MTU_size_16_color_FFFFFF_t_70 12][] (2)数据包的软件发送流程如下图 ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTQ3ODc4MTU_size_16_color_FFFFFF_t_70 13][] (3) eth\_igb\_xmit\_pkts接口源码,发包逻辑参考上面小节,这里不做补充了 ## ** 【六 、后续】** ## 到目前位置分析完了数据包在网卡、CPU、主存上游走的流程,可以看到DPDK收发包的流程设计的还是比较震撼的,充分考虑到了高性能收发数据,其中一些处理流程、数据结构的设计经验等值得我们在平时的开发项目中去运用和学习。网卡的初始化配置那块后面再分析。 本篇文章中的网卡的硬件组成部分参考了网上其他网友的思路,本篇文章纯属个人的理解,观点可能存在错误的地方,另外文章中的文字及框架图为个人纯手工写的,可能会存在一些语法不顺等其他问题,欢迎各位朋友针对文中不正确的地方给多多指教。 [20201012202936738.png]: /images/20221024/db66d5eb6f1845f79cc8083cf41b7c41.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTQ3ODc4MTU_size_16_color_FFFFFF_t_70]: /images/20221024/362b1193563b4e7cad009852bab7d55c.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTQ3ODc4MTU_size_16_color_FFFFFF_t_70 1]: /images/20221024/574c647213dc404c96f1e7e959eca37c.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTQ3ODc4MTU_size_16_color_FFFFFF_t_70 2]: /images/20221024/38d23dd022e54e008fd491f95e9ee816.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTQ3ODc4MTU_size_16_color_FFFFFF_t_70 3]: /images/20221024/8f40990cfaa248b5b4ce7fff1c781c5c.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTQ3ODc4MTU_size_16_color_FFFFFF_t_70 4]: /images/20221024/0ec0dfb70af84b999b71852db279a92e.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTQ3ODc4MTU_size_16_color_FFFFFF_t_70 5]: /images/20221024/7fde113f7e6b4e78b53e5bf6ffc409e5.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTQ3ODc4MTU_size_16_color_FFFFFF_t_70 6]: /images/20221024/c7f102aaa28f4d2e9c6519aa5a6696e1.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTQ3ODc4MTU_size_16_color_FFFFFF_t_70 7]: /images/20221024/654dfedfa1494e11a6c62570cac76723.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTQ3ODc4MTU_size_16_color_FFFFFF_t_70 8]: /images/20221024/24c3b1db8bee4efa8971878e532d7cb3.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTQ3ODc4MTU_size_16_color_FFFFFF_t_70 9]: /images/20221024/9a86a77180e54b96923989af74f61a71.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTQ3ODc4MTU_size_16_color_FFFFFF_t_70 10]: /images/20221024/207f36e82c93486a8e49ccca74cc59bf.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTQ3ODc4MTU_size_16_color_FFFFFF_t_70 11]: /images/20221024/8c04934c33284c31adcde76128c47ab6.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTQ3ODc4MTU_size_16_color_FFFFFF_t_70 12]: /images/20221024/d3a8d5cd07ab46b2aa0a9be6e2cb920c.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTQ3ODc4MTU_size_16_color_FFFFFF_t_70 13]: /images/20221024/be2d4304349a4ae2a130ffae061ad972.png
相关 App启动流程分析(一) 一、App启动涉及到的三个进程 1、Launcher进程:负责接收用户点击屏幕的事件,它其实就是一个Activity,屏幕上的各种Icon就是这个Activity中的Bu 落日映苍穹つ/ 2023年10月12日 22:00/ 0 赞/ 62 阅读
相关 DPDK QOS 5 -- Enqueue && Dequeue详细流程分析 入队流程 我们先看看,官网上是怎么说的: Enqueue Pipeline The sequence of steps per packet: Access t 矫情吗;*/ 2023年07月24日 05:52/ 0 赞/ 14 阅读
相关 Linux 网络层收发包流程及 Netfilter 框架浅析 1. 前言 本文主要对 Linux 系统内核协议栈中网络层接收,发送以及转发数据包的流程进行简要介绍,同时对 Netfilter 数据包过滤框架的基本原理以及使用方式进行简 朱雀/ 2023年07月17日 07:08/ 0 赞/ 177 阅读
相关 Linux网络收发包流程 关于linux网络包的收发流程,网上随便一搜都可以搜一桶,但自己不动手永远都搞不原理。最近在家比较闲,对网络这一块也不太了解,小编习惯熟悉内核子系统原理从低版本内核开始. 墨蓝/ 2023年07月17日 06:50/ 0 赞/ 5 阅读
相关 linux收发包内核进程名称,Linux内核IP Queue机制的分析(一)——用户态接收数据包... 序 笔者将会通过包括本文在内的三篇文章,对IP Queue机制从用户态的应用到内核态的模块程序设计进行分析。三篇文章的题目分别是: Linux内核IP Queue机制的分析 缺乏、安全感/ 2023年01月19日 15:57/ 0 赞/ 287 阅读
相关 盘点一款Python发包收包利器——scapy 点击上方“Python爬虫与数据挖掘”,进行关注 回复“书籍”即可获赠Python从入门到进阶共10本电子书 今 日 鸡 汤 潮平两岸阔,风正一帆悬。 大 待我称王封你为后i/ 2023年01月17日 14:57/ 0 赞/ 189 阅读
相关 DPDK收发包流程分析(一) 一、 前言】 DPDK是intel工程师开发的一款用来快速处理数据包的框架,最初的目的是为了证明传统网络数据包处理性能低不是intel处理器导致的,而是传统 男娘i/ 2022年11月01日 05:39/ 0 赞/ 363 阅读
相关 DPDK中文-DPDK工具 dpdk在app目录下其实提供给我们去查询dpdk信息的工具 翻译自 [http://dpdk.org/doc/guides/tools/index.html][http_d 古城微笑少年丶/ 2022年07月13日 15:55/ 0 赞/ 292 阅读
相关 rabbitMq一收一发 1.生产者(producer) Producing 发送消息的程序就叫做生产者。 2.队列 消息通过你的应用程序和RabbitMQ进行传输,它们能够只存储在一个 ゞ 浴缸里的玫瑰/ 2022年06月11日 02:10/ 0 赞/ 203 阅读
还没有评论,来说两句吧...