云计算网络技术分享 - VPP
type: channel
link: https://zhaozhanxu.com/tag/VPP/
items: [转]Cisco VPP BiHash分析
items: [转]Cisco VPP Feature机制
items: [转]Cisco VPP 路由分析下
items: [转]Cisco VPP 路由分析上
items: [转]Cisco VPP Node调度框架
items: [转]Cisco VPP CLI分析
items: [转]Cisco VPP 多线程分析
items: [转]Cisco VPP Ethernet Node分析
items: [转]Cisco VPP DPDK Node分析
items: [转]Cisco VPP内存管理

[转]Cisco VPP BiHash分析
type: item
date: 2017-02-14T12:00:00+08:00
description: 注：本文是转载，但不是100%的转载，可能稍微有些出入，原文地址点击这里基本概念VPP里的Bihash全名为Bounded-index extensible hash。它的最大特点是，在查找时是无锁并且线程安全的。修改操作之间会有互斥，但是修改操作时仍然可以进行查找操作。vpp里的Bihash优化成了两种，bihash_kv_8_8和bihash_kv_24_8,区别在于hash key是8字节还是24字 节。最大限度的利用SSE4.2指令集中的_mm_crc32_u64来进行hash计算。核心函数在bihash_template.c中。根据包含的头文件是bihash_8_8.h还是bihash_24_8.h，BV宏和BTV 宏将把名字做出对应扩展。例如:BV (clib_bihash_init)扩展为clib_bihash_init_8_8()或者clib_bihash_init_24_8()。BVT (clib_bihash)扩展为clib_bihash_8_8_t或者为clib_bihash_24_8_t。clib_bihash_bucket_thash桶typedef struct { union { struct { //这个桶中记录得kv对，在heap中的起始位置 u32 offset; u8 pad[3]; //这个桶中记录的kv对，一共占用了1 << log2_pages个page u8 log2_pages; }; u64 as_u64; }; } clib_bihash_bucket_t;page数据结构，中间包含了kv对。typedef struct BV (clib_bihash_value) { union { BVT (clib_bihash_kv) kvp[BIHASH_KVP_PER_PAGE]; struct BV (clib_bihash_value) * next_free; }; } BVT (clib_bihash_value);核心函数clib_bihash_init_8_8()和clib_bihash_init_24_8()初始化bihash，并分配一个独占的内存heap给它。void BV (clib_bihash_init)(BVT (clib_bihash) * h, char *name, u32 nbuckets, uword memory_size) { void *oldheap; //方便之后hash值映射到bucket，用&替代昂贵的%操作。 nbuckets = 1 << (max_log2 (nbuckets)); h->name = (u8 *) name; h->nbuckets = nbuckets; h->log2_nbuckets = max_log2 (nbuckets); h->mheap = mheap_alloc (0 /* use VM */ , memory_size); //常用操作，这样才能使用内存操作函数， //注意这里单核心上只能有一个线程独占此操作，不同核心可以并发 oldheap = clib_mem_set_heap (h->mheap); vec_validate_aligned (h->buckets, nbuckets - 1, CLIB_CACHE_LINE_BYTES); h->writer_lock = clib_mem_alloc_aligned (CLIB_CACHE_LINE_BYTES, CLIB_CACHE_LINE_BYTES); clib_mem_set_heap (oldheap); }clib_bihash_search_8_8()和clib_bihash_search_24_8()给定key，查找value。int BV (clib_bihash_search)(const BVT (clib_bihash) * h, BVT (clib_bihash_kv) * search_key, BVT (clib_bihash_kv) * valuep) { //hash计算，者利用了SSE4.2指令集特性。值得记住 hash = BV (clib_bihash_hash) (search_key); //hash值的低log2_nbuckets bit用来索引桶号 bucket_index = hash & (h->nbuckets - 1); b = &h->buckets[bucket_index]; if (b->offset == 0) return -1; hash >>= h->log2_nbuckets; //在heap中的offset字节开始，属于桶。 v = BV (clib_b…

[转]Cisco VPP Feature机制
type: item
date: 2017-01-29T13:00:00+08:00
description: 注：本文是转载，但不是100%的转载，可能稍微有些出入，原文地址点击这里基本概念早期的VPP本身的node框架比较固定，各个node之间逻辑连接已经固化。为此新版本增加了feature机制，每个feature是一个node，用户可以启用/停止某个或某些feature。用户也可以自己写插件，把自定义node（自己的业务逻辑）加入到指定位置。vnet_feature_arc_registration_t vpp将feature分成不同的组，每组feature称为一个arc。arc中的feature按照代码指定的顺序串接起来。arc结构将记录这组feature的起始node和结束node。系统初始化时完成每个feature的连接。VNET_FEATURE_ARC_INIT宏用来注册arc。在arc中指定的起始node中，必须调用vnet_feature_arc_start函数，才能正式进入feature机制业务流程，该函数会将下一跳强行指定为arc中的下一个feature。vnet_feature_registration_t一个feature等价于一个node，用户通过VNET_FEATURE_INIT宏定义自己的feature，指定需要加入哪个arc，以及在arc中的哪个相对位置。每个feature都可以通过外部命令行启用/停止。关键函数VNET_FEATURE_ARC_INIT和VNET_FEATURE_ARC_INIT宏将arc和feature保存到全局变量feature_main中。此时arc和feature还未被”加工”，只是基本的原料。Feature初始化clib_error_t *vnet_feature_init将把arc和feature初始化，并组织成最终的数据结构填充到vnet_feature_main_t feature_main中，代码比较简单。这里只对vnet_feature_main_t描述即可。typedef struct { // feature arc configuration list //VNET_FEATURE_ARC_INIT宏中注册的arc保存在这里，顺序不定，也没有意义。 vnet_feature_arc_registration_t *next_arc; /* * vnet_feature_init()中来初始化该成员， * 把arc按名字组织成hash表,为何要用双重指针？？？ * 博主认为是bug，后面明明是当hash表用么。又不是hash数组 */ uword **arc_index_by_name; /* * feature path configuration lists * VNET_FEATURE_ARC_INIT宏中注册的feature保存在这里，顺序不定，也没有意义。 */ vnet_feature_registration_t *next_feature; /* * vnet_feature_init()中来初始化该成员，把feature分配到不同arc中， * 并且根据before和after变量来决定feature顺序。 * 这里看作是一个数组，用arc的index做索引 */ vnet_feature_registration_t **next_feature_by_arc; /* * vnet_feature_init()中来初始化该成员，把feature按名字组织成hash表数组， * arc的index索引到对应hash表，node name来做hash表的key */ uword **next_feature_by_name; // feature config main objects vnet_feature_config_main_t *feature_config_mains; // Save partial order results for show command char ***feature_nodes; /* * bitmap of interfaces which have driver rx features configured * bitmap数组，arc的index索引对应bitmap表,决定该网卡是否启用了该arc中的feature */ uword **sw_if_index_has_features; // feature reference counts by interface i16 **feature_count_by_sw_if_index; /* * Feature arc index for device-input * 从收包开始就使用feature机制了，这个变量给收包驱动用。 * 追踪了下代码…

[转]Cisco VPP 路由分析下
type: item
date: 2017-01-10T07:00:00+08:00
description: 注：本文是转载，但不是100%的转载，可能稍微有些出入，原文地址点击这里基本概念VPP路由可以看作是思科iOS CEF的翻版。将路由分为路由表和转发表。路由表的实现基于hash，转发表的实现基于8-8-8-8 mtrie树。路由表存储了所有可能的路由表项(其中最优路由将安装到转发表中)，转发表存储了实际使用的路由项(数据包转发真正查找的是这个转发表)。VPP转发表查找的最后结果将得到一个DPO（data path object），DPO将指示数据包的下一步动作(分类，ARP查找，丢弃等等，请查看DPO_TYPES枚举类型)。以下皆以ipv4来分析，只分析了我看懂了的一部分。路由表typedef struct ip4_fib_t { //根据掩码大小对路由项作hash uword *fib_entry_by_dst_address[33]; //8-8-8-8 mtrie转发表 ip4_fib_mtrie_t mtrie; // Table ID (hash key) for this FIB. u32 table_id; // Index into FIB vector. u32 index; // flow hash configuration flow_hash_config_t flow_hash_config; // N-tuple classifier indices u32 fwd_classify_table_index; u32 rev_classify_table_index; } ip4_fib_t;路由表项hash链入fib_entry_by_dst_address中。转发表项安装在mtrie中，这是实际被数据平面使用的查找数据库。路由表的任何变化，将导致转发表的更新。路由表项typedef struct fib_entry_t_ { //所有路由关键数据结构都已这个打头，可以看做是他们的基类。 //用来将这些不同数据结构连在一起，又能用统一的接口来遍历。 fib_node_t fe_node; //可以理解为路由目的地址+掩码 const fib_prefix_t fe_prefix; //The index of the FIB table this entry is in u32 fe_fib_index; /* * The load-balance used for forwarding. * * We don't share the EOS and non-EOS even in case when they could be * because: * - complexity & reliability v. memory * determining the conditions where sharing is possible is non-trivial. * - separate LBs means we can get the EOS bit right in the MPLS label DPO * and so save a few clock cycles in the DP imposition node since we can * paint the header straight on without the need to check the packet * type to derive the EOS bit value. */ dpo_id_t fe_lb; // [FIB_FORW_CHAIN_MPLS_NUM]; /* * Vector of source infos. * Most entries will only have 1 source. So we optimise for memory usage, * which is preferable since we have many entries. * * 同一个路由目的，可以是由不同的来源添加的， * 所有来源类型查看fib_source_t_枚举类型，数值越小的来源， * 优先级越高，最高优先级的来源的路由将安装到转发表， * 低优先级来源的路由项默默地保存在最高优先级来源后面。 * 这个数组是有序的，添加删除都将导致排序，0号位置为最优路由。 */ fib_entry_src_t *fe_srcs; /* * the path-list for which this entry is a child. This is also the path-list * that is contributing forwarding for this entry. */ fib_node_index_…

[转]Cisco VPP 路由分析上
type: item
date: 2016-12-19T17:00:00+08:00
description: 注：本文是转载，但不是100%的转载，可能稍微有些出入，原文地址点击这里基本概念VPP作为思科开源项目，路由系统基本继承了Cisco快速转发（Cisco Express Forwarding，CEF）设计思路。作为理解VPP路由源码第一步，首先要掌握起理论基础。Cisco Express Forwarding内容摘自异步社区。虽然基于缓存的交换机制相对进程交换而言提高了转发的性能，但是它们的性能是不确定的。进程交换和基于缓存的交换都是数据驱动的（data-driven），或者说是需求驱动的（demand-driven）。换句话说，只有当数据包进入路由器的时候交换组件才会被布置到位，一旦这些数据包不被路由器转发，交换组件就会被清除。如果存在大量的不可预知流量模式的数据包，交换性能就会显著地下降。显然，在Internet的级别上，这些交换路线是不可扩展的。创建CEF是为了避免基于缓存交换机制所固有的问题。它的设计最好地适应了频繁变化的网络态势和流量特征，这些都是由于不断增长的较短持续时间的流所产生的。典型地，这些较短持续时间的流与基于Web的应用和交互式TCP会话相关联。CEF具有以下几个优点：可扩展性（Scalability）——CEF是拓扑驱动的（topology-driven），并与路由选择表紧密相关。当激活了分布式CEF（Distributed CEF）模式时，CEF在每一块线卡（line card）上也提供了全部的交换能力。CEF支持硬件辅助的（hardware-assisted）转发方式，这是在高容量的线卡上，提供线速交换（line rate switching）能力所必需的。增强的性能——CEF的CPU密集程度比路由缓存机制的CPU密集程度低。更多的CPU处理能力可以专注于第3层服务，例如，处理BGP更新。弹性（Resilience）——在大型动态网络中，CEF提供了更好的交换一致性和稳定性。在这样一些网络中，由于路由选择的变化，快速交换缓存表项会频繁地失效。这些变化导致路由器使用路由选择表进程交换流量，而不是使用路由缓存快速交换流量。由于CEF查找表包含了存在于路由选择表中所有已知的路由，因此它消除了路由缓存的维护需要，并使快速交换/进程交换转发不再适用。CEF能够比典型的按需缓存机制更有效地交换流量。注意：IP RIB中所有路由的表项不管是否被使用，它们都需要被维护，因而CEF可能比其他交换方式需要更多的内存。CEF是拓扑驱动的交换机制，它的转发表和路由选择表是紧密关联的。无论何时路由选择表发生变化，CEF转发表也将被更新。在表项被创建的时候，数据包被交换到更慢的交换路线上。CEF把路由缓存的功能分割成两部分：转发信息库(Forwarding Information Base，FIB)邻接表(ajdancency table)FIBFIB包含了来自于路由选择表的所有IP前缀。如果不同的路由选择表被维护着，例如在MPLS VPN环境中，那么每个VPN都有自己的FIB。FIB不是数据驱动的。更确切地说，它是通过路由选择表来创建和更新的。FIB子系统负责确保所有的递归路由（是指那些没有与直接下一跳相联系的路由）被解析。为了增强一致性和减少查找时间，FIB被组织成一个被称为mtrie的多路（multiway）数据结构。在mtrie数据结构中，树结构用来定位所要找的数据，但是数据本身存储在其他地方。相反，mtree数据结构在树结构本身中存储了实际的数据。例如，在最优交换的mtree缓存中，用来转发数据包的MAC头的数据就是实际存储于mtree中的。Cisco路由器通常使用两种类型的mtrie结构：8-8-8-8——这种格式也被称为256-way mtrie，因为4个八位组的IPv4地址被映射到4个8bit的结构中。因此，一条前缀的最大查找次数是4次。这种格式用在大多数Cisco路由器中。16-8-8——这是一个3级的mtrie，它的根级有65536个表项。因此，一条前缀的最大查找次数是3次。换句话说，第一次查找解析了前面的两个八位组，接着最多再需要2次查找就可以解析最后的两个八位组。这种格式只用于Cisco 12000系列路由器。mtrie的每一级都被称为一个节点（node）。最后的节点被称为叶节点（leaf）。叶节点指向邻接表（adjacency table），或者当到达相同目的地的多条路径存在时，指向另一个负载分担的结构。IP FIB的内容可以使用show ip cef命令来显示。下面列举了一些FIB表项：附接的（attached）——这种前缀被配置为可以通过接口直接到达，不需要由IP下一跳来创建邻接关系。这种前缀是指路由器本地接口所属的网络。连接的（connected）——这种接口是由IP addressaddres…

[转]Cisco VPP Node调度框架
type: item
date: 2016-11-15T21:00:00+08:00
description: 注：本文是转载，但不是100%的转载，可能稍微有些出入，原文地址点击这里vpp的功能逻辑被划分为一个个node，node之间通过下一跳传递处理完的数据包集合，从而组成整个业务图谱。本文将分析node调度框架源码。基本概念vlib_node_type_tnode分为四种类型：VLIB_NODE_TYPE_INTERNAL对数据包真正处理的业务node。VLIB_NODE_TYPE_INPUT收包逻辑node，比如：dpdk，pcap等。VLIB_NODE_TYPE_PRE_INPUT目前只有一个epoll node，对socket相关逻辑提供服务，主要使用在控制业务上。VLIB_NODE_TYPE_PROCESS该类型node可以被挂起也可以被恢复，有独立的分配在heap上的运行时栈。类似于在一个线程中实现了多任务调度机制。主要用来修改vpp node内部参数。vlib_node_main_t记录各种全局信息，比如各种数据结构集合，数据结构内存池之类。vlib_node_t注册node时将业务逻辑，几乎不怎么修改的参数，状态信息保存在这里。vlib_node_runtime_t这是调度框架实际频繁使用的结构，从vlib_node_t拷贝了部分信息，以及私有的频繁变动的信息。vlib_process_tVLIB_NODE_TYPE_PROCESS类型node专用结构，记录用于模拟task的基础结构：heap上的运行时栈，2种返回时寄存器备份，等。vlib_frame_t每个node都有一个对应的vlib_frame_t，用来保存供node使用的数据包集合。这是每个node最终处理数据的内存所在地。vlib_pending_frame_t当一个node处理完数据包，则填充该数据结构，并加入到全局链表，调度框架便能在下一次调度时找到需要接收该数据包的下一个node。vlib_next_frame_t主要是node内部逻辑使用，定位该node的下一条信息。关键结构vlib_node_main_ttypedef struct { //一块连续内存，头部是vec_header_t，数据部分是node指针数组 vlib_node_t **nodes; //node按名字组成hash表 uword *node_by_name; //目前只有VLIB_NODE_MAIN_RUNTIME_STARTED一个状态，暂时没啥用，忽略之。 u32 flags; #define VLIB_NODE_MAIN_RUNTIME_STARTED (1 << 0) /* * node有三种类型:VLIB_NODE_TYPE_INTERNAL,VLIB_NODE_TYPE_INPUT, * VLIB_NODE_TYPE_PRE_INPUT,按类型分类索引 */ vlib_node_runtime_t *nodes_by_type[VLIB_N_NODE_TYPE]; //以下三个用于类似于网卡napi收包模式，dpdk没用到，忽略之 u32 *pending_interrupt_node_runtime_indices; //假设node1有n1个下一跳，node2有n2个下一跳....共n1 + n2 +....+ni个下一跳 //连续保存在next_frames指向的vec中。 vlib_next_frame_t *next_frames; /* * 数据包从node输出到下一跳，那么下一跳node即是pending frame，会加入pending_frames指向的vec。 * 主循环会遍历该vec，对每个node来调用处理逻辑 */ vlib_pending_frame_t *pending_frames; //定时器，信号相关，源码及其恶心，博主暂时放弃阅读 timing_wheel_t timing_wheel; vlib_signal_timed_event_data_t *signal_timed_event_data_pool; /* * VLIB_NODE_TYPE_PROCESS类型的node，每个node有分配在heap上的运行栈， * 没错就是通常说的在顶部那种栈，但是这里分配在了heap上。 * 这里在单线程中模拟了类似多线程的效果，以后会详细分析。 * processes保存了所有VLIB_NODE_TYPE_PROCESS类型node的描述结构指针。 */ vlib_process_t **processes; //VLIB_NODE_TYPE_PROCESS类型的node,执行时都会把 //current_process_index赋值为本node的runtime_index u32 current_process_index; …

[转]Cisco VPP CLI分析
type: item
date: 2016-11-15T18:00:00+08:00
description: 注：本文是转载，但不是100%的转载，可能稍微有些出入，原文地址点击这里基本概念这个只是辅助部分，大多数人不关心，我就随便写写。核心函数//主线程会调用unix_cli_config，开始cli部分的初始化工作 VLIB_CONFIG_FUNCTION (unix_cli_config, "unix-cli");/** Handle configuration directives in the @em unix section. */ static clib_error_t *unix_cli_config (vlib_main_t * vm, unformat_input_t * input) { ... //确保unix_config执行且只执行了一次 if ((error = vlib_call_config_function (vm, unix_config))) return error; if (um->flags & UNIX_FLAG_INTERACTIVE) { //支持交互模式 //非阻塞模式，UNIX_CLI_STDIN_FD会交由VLIB_NODE_TYPE_PRE_INPUT类型的 //unix-epoll-input node来监视 if ((flags = fcntl (UNIX_CLI_STDIN_FD, F_GETFL, 0)) < 0) flags = 0; (void) fcntl (UNIX_CLI_STDIN_FD, F_SETFL, flags | O_NONBLOCK); //生成或者复用一个VLIB_NODE_TYPE_PROCESS类型node， //并调度激活node，等待来处理该cli session cf_index = unix_cli_file_add (cm, "stdin", UNIX_CLI_STDIN_FD); cf = pool_elt_at_index (cm->cli_file_pool, cf_index); cm->stdin_cli_file_index = cf_index; //下面是调整cli界面参数的，暂时不分析了。 if (isatty (UNIX_CLI_STDIN_FD) && um->cli_line_mode == 0) { ... } //一些欢迎信息拷贝到cf->output_vector中，更新epoll状态，等待时机输出。 unix_cli_file_welcome (cm, cf); } //下面是CLI socket支持 /* If we have socket config, LISTEN, otherwise, don't */ clib_socket_t *s = &um->cli_listen_socket; if (s->config && s->config[0] != 0) { /* CLI listen. */ unix_file_t template = { 0 }; s->flags = SOCKET_IS_SERVER; /* listen, don't connect */ //socket初始化，监听模式，代码不难 error = clib_socket_init (s); if (error) return error; template.read_function = unix_cli_listen_read_ready; template.file_descriptor = s->fd; //socket加入到epoll unix_file_add (um, &template); } /* Set CLI prompt. */ if (!cm->cli_prompt) cm->cli_prompt = format (0, "VLIB: "); return 0; }//该函数启动一个CLI session，关键是要提前理解VLIB_NODE_TYPE_PROCESS类型node运行机制 /** Store a new CLI session. * @param name The name of the session. * @param fd The file descriptor for the session I/O. * @return The session ID. */ static u32 unix_cli_file_add (unix_cli_main_t * cm, char *name, int fd) { ... //CLI退出时，会把处理该CLI的node放入这个复用链表池，以待继续使用 if (vec_len (cm->unused_cli_process_…

[转]Cisco VPP 多线程分析
type: item
date: 2016-11-12T10:00:00+08:00
description: 注：本文是转载，但不是100%的转载，可能稍微有些出入，原文地址点击这里基本概念VPP支持多线程模式，其中区分为主线程和工作线程。主线程可以调用所有类型的node，工作线程只能调用。VLIB_NODE_TYPE_INTERNAL和VLIB_NODE_TYPE_INPUT类型的node。VPP多线程之间同步采用的是自旋锁，如果滥用vlib_worker_thread_barrier_sync和vlib_worker_thread_barrier_release会导致性能下降。博主认为如果使用RCU锁会有更好的性能。核心函数start_workers建立多线程框架。static clib_error_t *start_workers (vlib_main_t * vm) { /* * VLIB_REGISTER_THREAD会增加vlib_thread_main_t->registrations中数量。 * cpu_config可以配置占用核心位图或者指定核心个数，这将增加 * vlib_thread_registration_t->count计数。 * 这两个值将在vlib_thread_init用于计算vlib_thread_main_t->n_vlib_mains */ u32 n_vlib_mains = tm->n_vlib_mains; //调用该函数的是在主线程中 u8 *main_heap = clib_mem_get_per_cpu_heap (); mheap_t *main_heap_header = mheap_header (main_heap); ... //不开启工作线程时，n_vlib_mains为1，否则，每个工作线程都要单独的一份vlib_mains if (n_vlib_mains > 1) { //确保vlib_mains中能保存tm->n_vlib_mains个数据结构，可以产生内存重新分配 vec_validate (vlib_mains, tm->n_vlib_mains - 1); _vec_len (vlib_mains) = 0; //主线程的配置信息 vec_add1 (vlib_mains, vm); //博主发现根本没用到这个队列。无视之。 vec_validate (vlib_frame_queues, tm->n_vlib_mains - 1); _vec_len (vlib_frame_queues) = 0; fq = vlib_frame_queue_alloc (FRAME_QUEUE_NELTS); vec_add1 (vlib_frame_queues, fq); //如果wait_at_barrier为1，则工作线程主循环会自旋等待。 vlib_worker_threads->wait_at_barrier = clib_mem_alloc_aligned (sizeof (u32), CLIB_CACHE_LINE_BYTES); /* * 记录有多少工作线程进入自旋等待状态，只有所有工作线程都自旋等待了， * vlib_worker_thread_barrier_sync才能返回。 * 该机制滥用的话，性能会下降很快，希望以后能用rcu锁替代自旋锁 */ vlib_worker_threads->workers_at_barrier = clib_mem_alloc_aligned (sizeof (u32), CLIB_CACHE_LINE_BYTES); ... //tm->registrations就是调用VLIB_REGISTER_THREAD个数 for (i = 0; i < vec_len (tm->registrations); i++) { ... //配置工作线程个数为0的话，自然不用管了 if (tr->count == 0) continue; //工作线程个数要么指定core bitmap，要么指定个数自动分配核心 for (k = 0; k < tr->count; k++) { vec_add2 (vlib_worker_threads, w, 1); //博主没有找到mheap_size赋值的地方，应该都是0，一脸懵逼 if (tr->mheap_size) w->thread_mheap = mheap_alloc (0 /* use VM */ , tr->mheap_size); else w->thread_mheap = main_heap; ... //VLIB_REGISTER_THREAD处指定该值。"stats"工作线程这里为1，"workers"工作线程为0 if (tr->no_data_struc…

[转]Cisco VPP Ethernet Node分析
type: item
date: 2016-11-10T13:00:00+08:00
description: 注：本文是转载，但不是100%的转载，可能稍微有些出入，原文地址点击这里核心函数ethernet_input_init初始化函数，主循环之前会调用。static clib_error_t *ethernet_input_init (vlib_main_t * vm) { //支持vlan，和qinq协议 ethernet_main_t *em = &ethernet_main; __attribute__ ((unused)) vlan_table_t *invalid_vlan_table; __attribute__ ((unused)) qinq_table_t *invalid_qinq_table; //只是给format_buffer，unformat_buffer赋值， //值得注意的是有对packet generate初始化，基本协议都有自己的pg实现。 ethernet_setup_node (vm, ethernet_input_node.index); ethernet_setup_node (vm, ethernet_input_type_node.index); ethernet_setup_node (vm, ethernet_input_not_l2_node.index); //初始化sparse_vec，用于根据3层协议来区分下一跳node这个目的。 next_by_ethertype_init (&em->l3_next); ... }ethernet_input_inline完成了该node业务逻辑功能。static_always_inline uword ethernet_input_inline (vlib_main_t * vm, vlib_node_runtime_t * node, vlib_frame_t * from_frame, ethernet_input_variant_t variant) { u32 cpu_index = os_get_cpu_number (); /* * ETHERNET_INPUT_VARIANT_ETHERNET_TYPE， * ETHERNET_INPUT_VARIANT_NOT_L2， * ETHERNET_INPUT_VARIANT_ETHERNET三种模式下， * 公用ethernet_input_node的error信息。 * 博主没有看出这里有什么特殊的含义。 */ if (variant != ETHERNET_INPUT_VARIANT_ETHERNET) error_node = vlib_node_get_runtime (vm, ethernet_input_node.index); else error_node = node; //返回frame尾部保存数据包信息内存的起始地址 from = vlib_frame_vector_args (from_frame); //frame中的数据包个数 n_left_from = from_frame->n_vectors; //上次数据包的下一跳这里直接使用，后面有机会修正 next_index = node->cached_next_index; stats_sw_if_index = node->runtime_data[0]; while (n_left_from > 0) { //获取传给下一跳node的保存数据包的缓存 vlib_get_next_frame (vm, node, next_index, to_next, n_left_to_next); while (n_left_from >= 4 && n_left_to_next >= 2) { //操作两个数据包，再预取两个数据包，以下省略，因为下面操作一个数据包和这个方法一致 ... } while (n_left_from > 0 && n_left_to_next > 0) { //预取下一个报文 if (n_left_from > 1) { vlib_buffer_t *p2; p2 = vlib_get_buffer (vm, from[1]); vlib_prefetch_buffer_header (p2, STORE); CLIB_PREFETCH (p2->data, CLIB_CACHE_LINE_BYTES, LOAD); } bi0 = from[0]; to_next[0] = bi0; from += 1; to_next += 1; n_left_from -= 1; n_left_to_next -= 1; b0 = vlib_get_buffer (vm, bi0); …

[转]Cisco VPP DPDK Node分析
type: item
date: 2016-11-07T12:00:00+08:00
description: 注：本文是转载，但不是100%的转载，可能稍微有些出入，原文地址点击这里VPP使用者几乎都会使用dpdk node作为收包驱动，本文将分析其源码。基本概念vlib_buffer_tdpdk收到的数据包用rte_mbuf结构描述。vpp为了兼容其它收包node（netmap，pcap等）改为使用vlib_buffer_t来描述数据包。vlib_buffer_t紧跟在rte_mbuf后面，headroom空间中。vlib_buffer_from_rte_mbuf和rte_mbuf_from_vlib_buffer完成相互转化。值得注意的是，该结构使用了三个CLIB_CACHE_LINE_ALIGN_MARK宏，把结构分成了3段，确保每段都是CLIB_CACHE_LINE_BYTES对齐。作为一个被频繁使用的结构，这样设计可以更好的更具需要进行内存预取。vlib_get_next_frame，vlib_put_next_frame几乎每个node中必定出现的一对好基友。vlib_get_next_frame获取传递给下一个node的数据包将驻留的内存结构。vlib_put_next_frame把传递给下一个node的数据包写入特定位置。这样下一个node将正式可以被调度框架调度，并处理传给他的数据包.EFDearly-fast-discard没有找到相关资料，估计也是一个性能优化特性，之后再补个人对作者意图的猜测。核心函数dpdk_device_input//use_efd指定是否启用early-fast-discard特性。 static inline u32 dpdk_device_input (dpdk_main_t * dm, dpdk_device_t * xd, vlib_node_runtime_t * node, u32 cpu_index, u16 queue_id, int use_efd) { u32 n_buffers; /* * 缺省情况下一跳是ethernet node，之后可以最终改变下一条， * 博主认为这里使用next_index = node->cached_next_index会更合理 */ u32 next_index = DPDK_RX_NEXT_ETHERNET_INPUT; ... //网卡必须up了 if (xd->admin_up == 0) return 0; //指定网卡的指定队列上收一批包，返回收到的包个数， //数据包地址存在xd->rx_vectors[queue_id]中。 n_buffers = dpdk_rx_burst (dm, xd, queue_id); if (n_buffers == 0) { //看来EFD是默认不使用EFD。 if (PREDICT_FALSE (use_efd && dm->efd.enabled)) { xd->efd_agent.last_poll_time = 0; xd->efd_agent.last_burst_sz = 0; } return 0; } /* * vlib_buffer有多个pool，可以用在自己构建的数据包， * 这里的fl仅仅用来作为模板初始化ret_mbuf后面的vlib_buffer结构。 */ fl = vlib_buffer_get_free_list (vm, VLIB_BUFFER_DEFAULT_FREE_LIST_INDEX); if (PREDICT_FALSE (use_efd && dm->efd.enabled)) { //一次收包到了VLIB_FRAME_SIZE个，如果这种情况次数很多， //说明发生了收包端拥塞，可以在后面直接丢弃数据包。 if (PREDICT_FALSE (xd->efd_agent.consec_full_frames_cnt >= dm->efd.consec_full_frames_hi_thresh)) { u32 device_queue_sz = rte_eth_rx_queue_count (xd->device_index, queue_id); if (device_queue_sz >= dm->efd.queue_hi_thresh) { /* dpdk device queue has reached the critical threshold */ xd->efd_agent.congestion_cnt++; /* apply EFD to packets from the burst */ efd_discard_burst = 1; } } } mb_index = 0; while (n_buffers > 0) { /…

[转]Cisco VPP内存管理
type: item
date: 2016-10-20T14:00:00+08:00
description: 注：本文是转载，但不是100%的转载，可能稍微有些出入，原文地址点击这里vec变长数组len是数组元素个数，不是字节长度。每个数组元素都看作是等大小。自定义头部后接vec 组合使用。组合vec将引申出其他内存管理结构。_vec_find(v)v指向数据部分，宏返回vec头部地址。_vec_round_size(s)s按sizeof (uword)大小，向上对齐。vec_header_bytes (uword header_bytes)自定义头部+vec 组合这种模型下，header_bytes代表自定义头部大小。函数返回自定义头部+vec总长度，按sizeof (vec_header_t)向上对齐。vec_header (void *v, uword header_bytes)自定义头部+vec 组合，按sizeof (vec_header_t)向上对齐这种模型下，v指向数据部分地址，header_bytes代表自定义头部大小，返回自定义头部地址。vec_header_end (void *v, uword header_bytes)自定义头部+vec 组合，按sizeof (vec_header_t)向上对齐这种模型下，v指向自定义头部地址，header_bytes代表自定义头部大小，返回数据部分地址。vec_aligned_header (void *v, uword header_bytes, uword align)自定义头部+vec 组合，按align向上对齐这种模型下，v指向数据部分地址，header_bytes代表自定义头部大小，返回自定义头部地址。vec_aligned_header_end (void *v, uword header_bytes, uword align)自定义头部+vec 组合，按align向上对齐这种模型下，v指向自定义头部地址，header_bytes代表自定义头部大小，返回数据部分地址。_vec_len(v)v指向数据部分，返回vec头部的vec_header_t->len。vec_len(v)v指向数据部分，v等于null时返回0，否则返回vec头部的vec_header_t->len。vec_reset_length(v)v指向数据部分，vec头部的vec_header_t->len置0。vec_bytes(v)v指向数据部分，返回数据部分总字节大小。vec_capacity(v,b)v指向data起始地址，b代表user_header大小，返回该mheap_elt_t字节大小，即vec的最大可占用内存。博主觉得该函数放在这里破坏了该头件的独立性。vec_max_len(v)类似vec_capacity(v,b)内存布局，v指向data起始地址，返回该vec可容纳最大数据块数目。vec_end(v)v指向data起始地址，返回vec数据部分末尾的下一个字节。vec_is_member(v,e)v指向data起始地址，返回e是否在该vec地址范围内。vec_elt_at_index(v,i)v指向data起始地址，返回第i个数据块的地址。vec_elt(v,i)v指向data起始地址，返回第i个数据块的内容。vec_foreach(var,vec) vec_foreach_backwards(var,vec) vec_foreach_index(var,v)迭代宏mheapmheap整体视图注意每个elt大小不一定相等。这里的vec_header_t把后面数据看成一个字节一个字节的元素，因此vec->len实际是整个数据部分字节长度。mheap_elt_t视图基本函数操作在mheap_bootstrap.h中。该头文件基本函数，结合上文内容很容易理解，这里不再详细叙述。mheap.c中会对上图基本结构更近一步索引管理，也是mheap的核心部分。mheap_maybe_lock (void *v) mheap_maybe_unlock (void *v)自旋锁加锁解锁函数，如果是持有锁的本CPU再次进入，可以无条件获取锁。mheap_get_aligned (void v, uword n_user_data_bytes, uword align, uword align_offset, uword offset_return)mheap内存分配接口函数。void *mheap_get_aligned (void *v, uword n_user_data_bytes, uword align, uword align_offset, uword * offset_return) { ... //align至少是mheap_elt_t大小，并且是2的幂 align = clib_ma…