今天我们介绍一下VPP的启动流程。如果把vpp看成一个应用程序,那么他是怎么工作的呢:
首先,vpp有许多插件so,每个插件下面有许多node,比如我们如果要新加一个功能为ip地址过滤,那么我们其实就是加一个node,当数据经过node时,调用我们提前注册的function函数就实现了我们的功能。接下来就理一下vpp从启动后到node生效的过程是怎样的。
开始之前先说下DPDK是怎么和vpp交互的。从vpp看来,dpdk就是一个插件,只不过这个插件运行得比较靠前,当数据包到来之后,dpdk将其收到rte_mbuf中,后面再将rte_mbuf转化为vlib_buf,后面的node就是从vlib_buf中取出数据再来处理,简单讲下rte_mbuf和vlib_buf的关系,以及他们是如何转化的:
#define rte_mbuf_from_vlib_buffer(x) (((struct rte_mbuf *)x) - 1)
#define vlib_buffer_from_rte_mbuf(x) ((vlib_buffer_t *)(x+1))
这两个宏定义用于在vlib buffer和DPDK的rte_mbuf之间进行转换。rte_mbuf 是DPDK中用于表示数据包的结构体,而 vlib_buffer_t 是VPP中用于表示数据包的结构体。以下是对这两个宏的解释:
rte_mbuf_from_vlib_buffer(x): 这个宏将一个 vlib_buffer_t 转换成对应的 rte_mbuf。vlib_buffer_t 中的数据的指针指向 rte_mbuf 的后一个字节,所以通过将 x 强制转换为 struct rte_mbuf *,然后减去1,就可以得到对应的 rte_mbuf。
vlib_buffer_from_rte_mbuf(x): 这个宏将一个 rte_mbuf 转换成对应的 vlib_buffer_t。由于上面的宏中的指针偏移关系,将 x 强制转换为 vlib_buffer_t * 之后,可以得到指向 vlib_buffer_t 数据部分的指针
初始化里面主要做了四件事
1、load so //加载静态库
2、register node //注册处理节点
3、function init (cmd init,feture init) //注册处理函数
先上个伪代码简单看下流程
int main(){
//VPP 程序的入口函数,它初始化全局状态、配置、插件以及其他运行时环境,并最终进入主循环以运行网络处理和其他功能
vlib_unix_main();
//执行了插件的早期初始化,设置了插件路径、日志等,并准备加载新的插件
vlib_plugin_early_init();
//加载新插件,并进行相应的管理和处理,确保只加载有效的插件,并进行了排序、覆盖等操作
vlib_load_new_plugins();
//加载插件并进行一系列的检查,确保插件可以正确加载和初始化
load_one_plugin();
//通过协程跳转到thread0执行
clib_calljmp();
//线程函数,作为独立线程的入口点
thread0();
//各种初始化操作,调用初始化函数、配置函数、主循环函数、以及一系列的清理操作
vlib_main();
//调用所有初始化函数
vlib_call_all_init_functions();
vlib_call_init_exit_functions
//遍历所有的节点注册(node_registrations 链表),并将这些注册的节点全部注册到 VLIB 中
vlib_register_all_static_nodes();
//
vlib_main_loop (vm);
//VLIB 主循环或工作线程循环的核心部分。它处理节点的调度和执行,以及一些循环计数和时间间隔的计算
vlib_main_or_worker_loop (vm, /* is_main */ 1);
//负责管理进程的调度、执行和暂停等。根据进程的状态和标志,它会判断是否继续执行进程,
//启动定时器等。执行完毕后,会更新性能计数器、事件日志以及进程的统计信息
dispatch_process();
//责将进程切换到新的堆栈并执行其启动函数
vlib_process_startup
//进程启动的具体逻辑,包括在进程的堆栈上调用进程函数并处理返回值
vlib_process_bootstrap
// 这里就调用到了我们注册一个node之后,真正的处理函数
n = node->function (vm, node, f);
}
接下来对上面伪代码中的函数进行详细分析下:
从main函数开始,main函数中其实都是配置和初始化VPP环境的相关东西,大概就是下面这样:
int main(){
//创建主堆,使用了之前设置的 main_heap_size 和 main_heap_log2_page_sz
main_heap = clib_mem_init_with_page_size(main_heap_size, main_heap_log2_page_sz);
//初始化VLIB库,初始化VPE(VPP Environment)库
//将一个VLIB主要实例传递给了 vpe_main_init() 函数,以便VPE库能够与VLIB库进行集成
vlib_main_init();
vpe_main_init(vlib_get_first_main());
//进入VLIB的主循环并开始执行网络处理程序。这是整个程序的核心部分,处理数据包、事件和各种网络功能
return vlib_unix_main(argc, argv);
}
vlib_unix_main函数解析:
int
vlib_unix_main (int argc, char *argv[])
{
//初始化时间库
clib_time_init (&vm->clib_time);
//初始化并启用事件日志,用于记录系统中的事件
elog_init (vlib_get_elog_main (), vgm->configured_elog_ring_size);
elog_enable_disable (vlib_get_elog_main (), 1);
//初始化插件的配置,通过调用插件的配置函数来加载和处理插件特定的配置信息
if ((e = vlib_plugin_config (vm, &input)))
{
clib_error_report (e);
return 1;
}
unformat_free (&input);
//这里是各种so插件初始化的地方
i = vlib_plugin_early_init (vm);
if (i)
return i;
//调用注册的配置函数,这些函数在插件中注册,用于配置程序的各种功能
e = vlib_call_all_config_functions (vm, &input, 1 /* early */ );
if (e != 0)
{
clib_error_report (e);
return 1;
}
unformat_free (&input);
//加载 ELF 格式文件的符号,用于处理信号处理程序和内存分配追踪
clib_elf_main_init (vgm->exec_path);
vec_validate (vlib_thread_stacks, 0);
vlib_thread_stack_init (0);
__os_thread_index = 0;
vm->thread_index = 0;
//启动主循环,将堆栈切换到主线程的堆栈,然后使用 clib_calljmp 调用主循环
vlib_process_start_switch_stack (vm, 0);
i = clib_calljmp (thread0, (uword) vm,
(void *) (vlib_thread_stacks[0] +
VLIB_THREAD_STACK_SIZE));
return i;
}
这里我们再看看插件是如何被加载的,通过vlib_plugin_early_init
int
vlib_plugin_early_init (vlib_main_t * vm)
{
plugin_main_t *pm = &vlib_plugin_main;
pm->logger =
vlib_log_register_class_rate_limit ("plugin", "load",
0x7FFFFFFF /* aka no rate limit */ );
//如果插件路径未设置,则使用默认的插件路径 vlib_plugin_path
if (pm->plugin_path == 0)
pm->plugin_path = format (0, "%s", vlib_plugin_path);
//如果有额外的插件路径附加,将其添加到插件路径中
if (pm->plugin_path_add)
pm->plugin_path = format (pm->plugin_path, ":%s", pm->plugin_path_add);
//在插件路径的末尾添加一个 null 字节,以便可以在字符串中找到插件路径的终止
pm->plugin_path = format (pm->plugin_path, "%c", 0);
PLUGIN_LOG_DBG ("plugin path %s", pm->plugin_path);
//创建两个哈希表,用于存储插件的信息和覆盖信息
pm->plugin_by_name_hash = hash_create_string (0, sizeof (uword));
pm->plugin_overrides_by_name_hash = hash_create_string (0, sizeof (uword));
//设置插件主结构体的 vlib_main 字段,以指向 VLIB 主结构体
pm->vlib_main = vm;
//加载新的插件
return vlib_load_new_plugins (pm, 1 /* from_early_init */ );
}
插件的加载就是vlib_load_new_plugins 函数,它做了哪些事呢:加载新插件,并进行相应的管理和处理,确保只加载有效的插件,并进行了排序、覆盖等操作。我们如果自己新定制了plugin插件,也是在这里处理
int
vlib_load_new_plugins (plugin_main_t * pm, int from_early_init)
{
DIR *dp;
struct dirent *entry;
struct stat statb;
uword *p;
plugin_info_t *pi;
u8 **plugin_path;
uword *not_loaded_indices = 0;
int i;
//根据插件路径字符串生成一个字符串数组,每个元素表示一个路径
plugin_path = split_plugin_path (pm);
for (i = 0; i < vec_len (plugin_path); i++)
{
dp = opendir ((char *) plugin_path[i]);
if (dp == 0)
continue;
//遍历插件路径数组,打开每个路径对应的目录,并遍历目录中的每个文件项
while ((entry = readdir (dp)))
{
u8 *plugin_name;
u8 *filename;
//如果设置了插件名过滤,将会检查文件名是否满足过滤条件。如果不满足,则跳过当前文件项
if (pm->plugin_name_filter)
{
int j;
for (j = 0; j < vec_len (pm->plugin_name_filter); j++)
if (entry->d_name[j] != pm->plugin_name_filter[j])
goto next;
}
//检查文件的扩展名是否是 .so,并且通过 stat 函数检查文件是否可读
filename = format (0, "%s/%s%c", plugin_path[i], entry->d_name, 0);
char *ext = strrchr ((const char *) filename, '.');
/* unreadable */
if (!ext || (strcmp (ext, ".so") != 0) ||
stat ((char *) filename, &statb) < 0)
{
ignore:
vec_free (filename);
continue;
}
/* 检查文件是否是一个常规文件,即普通文件,而不是目录或其他类型的文件 */
if (!S_ISREG (statb.st_mode))
goto ignore;
//如果文件是有效的插件文件,将其信息添加到插件信息数组中,并在哈希表中建立插件名称到插件信息的映射
plugin_name = format (0, "%s%c", entry->d_name, 0);
p = hash_get_mem (pm->plugin_by_name_hash, plugin_name);
if (p == 0)
{
/* No, add it to the plugin vector */
vec_add2 (pm->plugin_info, pi, 1);
pi->name = plugin_name;
pi->filename = filename;
pi->file_info = statb;
pi->handle = 0;
hash_set_mem (pm->plugin_by_name_hash, plugin_name,
pi - pm->plugin_info);
}
next:
;
}
closedir (dp);
vec_free (plugin_path[i]);
}
vec_free (plugin_path);
/*
根据插件名称对插件信息数组进行排序,确保插件按照名称的字典顺序排列
*/
vec_sort_with_function (pm->plugin_info, plugin_name_sort_cmp);
/*
遍历插件信息数组,并尝试加载每个插件,如果加载失败,则记录下来
*/
for (i = 0; i < vec_len (pm->plugin_info); i++)
{
pi = vec_elt_at_index (pm->plugin_info, i);
if (load_one_plugin (pm, pi, from_early_init))
{
/* Make a note of any which fail to load */
vec_add1 (not_loaded_indices, i);
}
}
/*
处理未能成功加载的插件,以及通过覆盖列表进行覆盖的插件
*/
for (i = 0; i < vec_len (pm->plugin_info); i++)
{
uword *p;
pi = vec_elt_at_index (pm->plugin_info, i);
p = hash_get_mem (pm->plugin_overrides_by_name_hash, pi->name);
/* Plugin overridden? */
if (p)
{
PLUGIN_LOG_NOTICE ("Plugin '%s' overridden by '%s'", pi->name,
p[0]);
vec_add1 (not_loaded_indices, i);
}
}
/*
删除未加载的插件信息,并对插件信息数组进行调整
*/
vec_sort_with_function (not_loaded_indices, index_cmp);
for (i = 0; i < vec_len (not_loaded_indices); i++)
{
if (i < vec_len (not_loaded_indices) - 1)
{
if (not_loaded_indices[i + 1] == not_loaded_indices[i])
{
vec_delete (not_loaded_indices, 1, i);
i--;
}
}
}
/* Remove plugin info vector elements corresponding to load failures */
if (vec_len (not_loaded_indices) > 0)
{
for (i = vec_len (not_loaded_indices) - 1; i >= 0; i--)
{
pi = vec_elt_at_index (pm->plugin_info, not_loaded_indices[i]);
hash_unset_mem (pm->plugin_by_name_hash, pi->name);
if (pi->handle)
{
dlclose (pi->handle);
PLUGIN_LOG_NOTICE ("Unloaded plugin: %s", pi->name);
}
vec_free (pi->name);
vec_free (pi->filename);
vec_delete (pm->plugin_info, 1, not_loaded_indices[i]);
}
vec_free (not_loaded_indices);
}
/* Recreate the plugin name hash */
hash_free (pm->plugin_by_name_hash);
pm->plugin_by_name_hash = hash_create_string (0, sizeof (uword));
重新创建插件名称哈希表,以反映加载成功的插件信息
for (i = 0; i < vec_len (pm->plugin_info); i++)
{
pi = vec_elt_at_index (pm->plugin_info, i);
hash_set_mem (pm->plugin_by_name_hash, pi->name, pi - pm->plugin_info);
}
return 0;
}
接下来看是怎么加载一个插件的load_one_plugin
static int
load_one_plugin (plugin_main_t * pm, plugin_info_t * pi, int from_early_init)
{
void *handle;
int reread_reg = 1;
clib_error_t *error;
elf_main_t em = { 0 };
elf_section_t *section;
u8 *data;
char *version_required;
vlib_plugin_registration_t *reg;
vlib_plugin_r2_t *r2;
plugin_config_t *pc = 0;
uword *p;
//使用 ELF 解析库读取插件文件,将文件内容解析为 ELF 结构
if (elf_read_file (&em, (char *) pi->filename))
return -1;
检查是否存在新的插件注册结构,如果存在,将解析该结构并进行处理
error = elf_get_section_by_name (&em, ".vlib_plugin_r2", §ion);
if (error == 0)
{
data = elf_get_section_contents (&em, section->index, 1);
r2 = (vlib_plugin_r2_t *) data;
reg = clib_mem_alloc (sizeof (*reg));
memset (reg, 0, sizeof (*reg));
reg->default_disabled = r2->default_disabled != 0;
error = r2_to_reg (&em, r2, reg);
if (error)
{
PLUGIN_LOG_ERR ("Bad r2 registration: %s\n", (char *) pi->name);
return -1;
}
if (pm->plugins_default_disable)
reg->default_disabled = 1;
reread_reg = 0;
goto process_reg;
}
else
clib_error_free (error);
//检查是否存在老版本的插件注册结构,如果不存在,则表示不是一个有效的插件
error = elf_get_section_by_name (&em, ".vlib_plugin_registration",
§ion);
if (error)
{
PLUGIN_LOG_ERR ("Not a plugin: %s\n", (char *) pi->name);
return -1;
}
//处理插件注册结构,检查大小是否匹配,并根据需要进行默认禁用
data = elf_get_section_contents (&em, section->index, 1);
reg = (vlib_plugin_registration_t *) data;
if (vec_len (data) != sizeof (*reg))
{
PLUGIN_LOG_ERR ("vlib_plugin_registration size mismatch in plugin %s\n",
(char *) pi->name);
goto error;
}
if (pm->plugins_default_disable)
reg->default_disabled = 1;
process_reg:
//根据插件名称在配置信息中查找是否需要禁用插
p = hash_get_mem (pm->config_index_by_name, pi->name);
if (p)
{
pc = vec_elt_at_index (pm->configs, p[0]);
if (pc->is_disabled)
{
PLUGIN_LOG_NOTICE ("Plugin disabled: %s", pi->name);
goto error;
}
if (reg->default_disabled && pc->is_enabled == 0)
{
PLUGIN_LOG_NOTICE ("Plugin disabled (default): %s", pi->name);
goto error;
}
}
else if (reg->default_disabled)
{
PLUGIN_LOG_NOTICE ("Plugin disabled (default): %s", pi->name);
goto error;
}
//检查插件所需的版本是否与应用版本匹配
version_required = str_array_to_vec ((char *) ®->version_required,
sizeof (reg->version_required));
if ((strlen (version_required) > 0) &&
(strncmp (vlib_plugin_app_version, version_required,
strlen (version_required))))
{
PLUGIN_LOG_ERR ("Plugin %s version mismatch: %s != %s",
pi->name, vlib_plugin_app_version,
reg->version_required);
if (!(pc && pc->skip_version_check == 1))
{
vec_free (version_required);
goto error;
}
}
处理当前插件覆盖的其他插件,记录覆盖关系
if (reg->overrides[0])
{
const char *overrides = reg->overrides;
u8 *override_name_copy, *overridden_by_name_copy;
u8 *sp, *ep;
uword *p;
sp = ep = (u8 *) overrides;
while (1)
{
if (*sp == 0
|| (sp >= (u8 *) overrides + ARRAY_LEN (reg->overrides)))
break;
if (*sp == ' ' || *sp == ',')
{
sp++;
continue;
}
ep = sp;
while (*ep && *ep != ' ' && *ep != ',' &&
ep < (u8 *) overrides + ARRAY_LEN (reg->overrides))
ep++;
if (*ep == ' ' || *ep == ',')
ep--;
override_name_copy = extract (sp, ep);
p = hash_get_mem (pm->plugin_overrides_by_name_hash,
override_name_copy);
/* Already overridden... */
if (p)
vec_free (override_name_copy);
else
{
overridden_by_name_copy = format (0, "%s%c", pi->name, 0);
hash_set_mem (pm->plugin_overrides_by_name_hash,
override_name_copy, overridden_by_name_copy);
}
sp = *ep ? ep + 1 : ep;
}
}
vec_free (version_required);
//使用 dlopen 函数加载插件的动态库,返回句柄
handle = dlopen ((char *) pi->filename,
RTLD_LAZY | (reg->deep_bind ? RTLD_DEEPBIND : 0));
if (handle == 0)
{
PLUGIN_LOG_ERR ("%s", dlerror ());
PLUGIN_LOG_ERR ("Failed to load plugin '%s'", pi->name);
goto error;
}
pi->handle = handle;
/*dlsym 函数用于从已加载的共享库中获取一个符号的地址,该符号代表了一个结构体,
其中包含了插件的注册信息。插件在加载时,通常会在共享库中定义一个名为
"vlib_plugin_registration" 的全局变量,该变量包含了插件的配置和信息*/
if (reread_reg)
reg = dlsym (pi->handle, "vlib_plugin_registration");
pi->reg = reg;
pi->version = str_array_to_vec ((char *) ®->version,
sizeof (reg->version));
//如果插件指定了早期初始化函数,调用该函数进行初始化
if (reg->early_init)
{
clib_error_t *(*ei) (vlib_main_t *);
void *h;
h = dlsym (pi->handle, reg->early_init);
if (h)
{
ei = h;
error = (*ei) (pm->vlib_main);
if (error)
{
u8 *err = format (0, "%s: %U%c", pi->name,
format_clib_error, error, 0);
PLUGIN_LOG_ERR ((char *) err);
clib_error_free (error);
dlclose (pi->handle);
pi->handle = 0;
goto error;
}
}
else
PLUGIN_LOG_ERR ("Plugin %s: early init function %s set but not found",
(char *) pi->name, reg->early_init);
}
//记录插件加载成功的信息
if (reg->description)
PLUGIN_LOG_NOTICE ("Loaded plugin: %s (%s)", pi->name, reg->description);
else
PLUGIN_LOG_NOTICE ("Loaded plugin: %s", pi->name);
//释放临时分配的内存,并返回成功加载的状态
vec_free (data);
elf_main_free (&em);
return 0;
//在加载失败的情况下,释放资源并返回失败的状态
error:
vec_free (data);
elf_main_free (&em);
return -1;
}
线程函数,作为独立线程的入口点 thread0
/**
* 线程函数,作为独立线程的入口点。
*
* @param arg 传递给线程函数的参数。在这个上下文中,应该是一个指向 vlib_main_t 结构的指针。
* @return vlib_main 函数的返回值。
*/
static uword thread0(uword arg) {
// 将参数转换为 vlib_main_t 指针
vlib_main_t *vm = (vlib_main_t *)arg;
// 获取全局 VLIB 主结构
vlib_global_main_t *vgm = vlib_get_global_main();
// 初始化一个用于命令行解析的输入结构
unformat_input_t input;
// 用于存储 vlib_main 函数的返回值
int i;
// 通知 VLIB 线程已完成切换到其堆栈
vlib_process_finish_switch_stack(vm);
// 使用全局 argv 初始化命令行输入
unformat_init_command_line(&input, (char **)vgm->argv);
// 调用 VLIB 主循环函数并存储其返回值
i = vlib_main(vm, &input);
// 释放命令行输入结构使用的资源
unformat_free(&input);
// 返回 vlib_main 函数的返回值
return i;
}
各种初始化操作,调用初始化函数、配置函数、主循环函数、以及一系列的清理操作vlib_main
/* 主函数 */
int vlib_main(vlib_main_t *volatile vm, unformat_input_t *input) {
vlib_global_main_t *vgm = vlib_get_global_main();
clib_error_t *volatile error;
vlib_node_main_t *nm = &vm->node_main;
// 设置队列信号回调为占位符函数
vm->queue_signal_callback = placeholder_queue_signal_callback;
/* 重新配置事件日志,这个日志在非常早期就被启用 */
if (vgm->configured_elog_ring_size &&
vgm->configured_elog_ring_size != vgm->elog_main.event_ring_size)
elog_resize(&vgm->elog_main, vgm->configured_elog_ring_size);
vl_api_set_elog_main(vlib_get_elog_main());
(void)vl_api_set_elog_trace_api_messages(1);
/* 默认名称 */
if (!vgm->name)
vgm->name = "VLIB";
if ((error = vlib_physmem_init(vm))) {
clib_error_report(error);
goto done;
}
if ((error = vlib_log_init(vm))) {
clib_error_report(error);
goto done;
}
if ((error = vlib_stats_init(vm))) {
clib_error_report(error);
goto done;
}
if ((error = vlib_buffer_main_init(vm))) {
clib_error_report(error);
goto done;
}
if ((error = vlib_thread_init(vm))) {
clib_error_report(error);
goto done;
}
// 注册节点 ifunction 变体
vlib_register_all_node_march_variants(vm);
// 注册静态节点以便初始化函数可以使用它们
vlib_register_all_static_nodes(vm);
// 设置随机数生成器的种子
if (!unformat(input, "seed %wd", &vm->random_seed))
vm->random_seed = clib_cpu_time_now();
clib_random_buffer_init(&vm->random_buffer, vm->random_seed);
// 初始化节点图
if ((error = vlib_node_main_init(vm))) {
// 在调试模式下,将图连接错误设置为非致命错误
if (CLIB_DEBUG > 0)
clib_error_report(error);
else
goto done;
}
// 初始化 vpe_api
if ((error = vpe_api_init(vm))) {
clib_error_report(error);
goto done;
}
if ((error = vlibmemory_init(vm))) {
clib_error_report(error);
goto done;
}
if ((error = map_api_segment_init(vm))) {
clib_error_report(error);
goto done;
}
// 检查 init_functions_called 是否为 0,如果是则创建哈希表
if (vgm->init_functions_called == 0)
vgm->init_functions_called = hash_create(0, /* value bytes */ 0);
// 调用所有的 init 函数
if ((error = vlib_call_all_init_functions(vm)))
goto done;
// 分配节点主循环的定时轮数据结构
nm->timing_wheel = clib_mem_alloc_aligned(sizeof(TWT(tw_timer_wheel)),
CLIB_CACHE_LINE_BYTES);
vec_validate(nm->data_from_advancing_timing_wheel, 10);
vec_set_len(nm->data_from_advancing_timing_wheel, 0);
// 创建进程定时轮
TW(tw_timer_wheel_init)((TWT(tw_timer_wheel) *)nm->timing_wheel,
0 /* no callback */,
10e-6 /* timer period 10us */,
~0 /* max expirations per call */);
vec_validate(vm->pending_rpc_requests, 0);
vec_set_len(vm->pending_rpc_requests, 0);
vec_validate(vm->processing_rpc_requests, 0);
vec_set_len(vm->processing_rpc_requests, 0);
// 如果配置了缓冲区分配错误注入,则设置默认参数
if (VLIB_BUFFER_ALLOC_FAULT_INJECTOR > 0) {
vm->buffer_alloc_success_seed = 0xdeaddabe;
vm->buffer_alloc_success_rate = 0.80;
}
// 调用所有的配置函数
if ((error = vlib_call_all_config_functions(vm, input, 0 /* is_early */)))
goto done;
/*
* 使用指数平滑法,半衰期为 1 秒
* reported_rate(t) = reported_rate(t-1) * K + rate(t)*(1-K)
*
* 每 20ms 进行一次采样,即每秒 50 个样本
* K = exp (-1.0/20.0);
* K = 0.95
*/
vm->damping_constant = exp(-1.0 / 20.0);
// 对 per-thread init 函数进行排序,然后启动线程
vlib_sort_init_exit_functions(&vgm->worker_init_function_registrations);
// 调用所有的 main loop enter 函数
{
clib_error_t *sub_error;
sub_error = vlib_call_all_main_loop_enter_functions(vm);
if (sub_error)
clib_error_report(sub_error);
}
switch (clib_setjmp(&vm->main_loop_exit, VLIB_MAIN_LOOP_EXIT_NONE)) {
case VLIB_MAIN_LOOP_EXIT_NONE:
vm->main_loop_exit_set = 1;
break;
case VLIB_MAIN_LOOP_EXIT_CLI:
goto done;
default:
error = vm->main_loop_error;
goto done;
}
// 开始主循环
vlib_main_loop(vm);
done:
vlib_worker_thread_barrier_sync(vm);
/* 调用所有的 exit 函数 */
{
clib_error_t *sub_error;
sub_error = vlib_call_all_main_loop_exit_functions(vm);
if (sub_error)
clib_error_report(sub_error);
}
vlib_worker_thread_barrier_release(vm);
// 如果有错误,报告错误
if (error)
clib_error_report(error);
return vm->main_loop_exit_status;
}
调用所有初始化函数vlib_call_all_init_functions
/**
* 调用所有初始化函数
*
* @param vm VLIB 主数据结构的指针
* @return clib_error_t 指向错误对象的指针,如果没有错误则为 NULL
*/
clib_error_t *
vlib_call_all_init_functions(vlib_main_t *vm) {
vlib_global_main_t *vgm = vlib_get_global_main();
// 调用占位符函数,确保纯静态模块被连接
// 这里使用 foreach_vlib_module_reference 宏来展开对各个模块的引用
#define _(f) vlib_##f##_reference();
foreach_vlib_module_reference;
#undef _
// 调用初始化/退出函数
return vlib_call_init_exit_functions(vm, &vgm->init_function_registrations,
1 /* call_once */, 1 /* is_global */);
}
初始化/退出/主循环进入函数的内部实现:实现了一个通用的函数调用机制,用于调用一组初始化、退出或者主循环进入函数。这个机制可以根据需要对函数链表进行排序,并且支持设置仅调用一次的标志。在遍历函数链表时,会检查是否已经调用过函数,如果设置为仅调用一次,并且函数未被调用过,则调用该函数。这样的机制可以方便地调用一组函数来完成初始化、退出或者进入主循环等操作。call_init_exit_functions_internal
/**
* @brief 调用一组初始化/退出/主循环进入函数
*
* @param vm vlib_main_t 结构体的指针
* @param headp 指向函数链表头指针的指针
* @param call_once 标志,指示是否仅调用一次
* @param do_sort 标志,指示是否对函数链表进行排序
* @param is_global 标志,指示是否为全局函数
* @return clib_error_t 指向错误对象的指针,如果没有错误则为 NULL
*/
static inline clib_error_t *
call_init_exit_functions_internal(vlib_main_t *vm,
_vlib_init_function_list_elt_t **headp,
int call_once, int do_sort, int is_global) {
vlib_global_main_t *vgm = vlib_get_global_main();
clib_error_t *error = 0;
_vlib_init_function_list_elt_t *i;
// 如果需要排序,进行排序操作
if (do_sort && (error = vlib_sort_init_exit_functions(headp)))
return (error);
i = *headp;
while (i) {
uword *h;
// 检查是否已经调用过这个函数
if (is_global)
h = hash_get(vgm->init_functions_called, i->f);
else
h = hash_get(vm->worker_init_functions_called, i->f);
// 如果设置为仅调用一次,并且函数未被调用过,则调用函数
if (call_once && !h) {
if (call_once) {
if (is_global)
hash_set1(vgm->init_functions_called, i->f);
else
hash_set1(vm->worker_init_functions_called, i->f);
}
error = i->f(vm);
if (error)
return error;
}
i = i->next_init_function;
}
return error;
}
注册所有静态节点(static nodes)的函数。静态节点是预定义的节点,其功能在整个系统的生命周期中保持不变vlib_register_all_static_nodes
void vlib_register_all_static_nodes(vlib_main_t *vm) {
vlib_global_main_t *vgm = vlib_get_global_main();
vlib_node_registration_t *r;
static char *null_node_error_strings[] = {
"blackholed packets",
};
static vlib_node_registration_t null_node_reg = {
.function = null_node_fn, // 空节点的处理函数
.vector_size = sizeof(u32),
.n_errors = 1,
.error_strings = null_node_error_strings,
};
// 注册一个名为 "null-node" 的节点
// 这是一个特殊的节点,用于处理被黑洞的数据包
vlib_register_node(vm, &null_node_reg, "null-node");
// 遍历所有节点注册
r = vgm->node_registrations;
while (r) {
// 注册每个节点
vlib_register_node(vm, r, "%s", r->name);
r = r->next_registration;
}
}
VLIB 主循环或工作线程循环的核心部分。它处理节点的调度和执行,以及一些循环计数和时间间隔的计算vlib_main_or_worker_loop:
static_always_inline void vlib_main_or_worker_loop(vlib_main_t * vm, int is_main) {
// 获取主循环的相关数据结构
vlib_node_main_t *nm = &vm->node_main;
vlib_thread_main_t *tm = vlib_get_thread_main();
uword i;
u64 cpu_time_now;
f64 now;
vlib_frame_queue_main_t *fqm;
u32 frame_queue_check_counter = 0;
// 初始化 pending_frames 数组,用于存储待处理的帧
if (is_main) {
vec_resize(nm->pending_frames, 32);
vec_set_len(nm->pending_frames, 0);
}
// 标记主循环开始的时间
if (is_main) {
cpu_time_now = vm->clib_time.last_cpu_time;
vm->cpu_time_main_loop_start = cpu_time_now;
} else
cpu_time_now = clib_cpu_time_now();
// 预分配中断运行时索引和锁
vec_alloc_aligned(nm->pending_interrupts, 1, CLIB_CACHE_LINE_BYTES);
// 预分配过期节点
if (!nm->polling_threshold_vector_length)
nm->polling_threshold_vector_length = 10;
if (!nm->interrupt_threshold_vector_length)
nm->interrupt_threshold_vector_length = 5;
// 获取当前 CPU ID 和 NUMA 节点信息
vm->cpu_id = clib_get_current_cpu_id();
vm->numa_node = clib_get_current_numa_node();
os_set_numa_index(vm->numa_node);
// 启动所有进程
if (is_main) {
uword i;
// 执行初始的屏障同步
vlib_worker_thread_initial_barrier_sync_and_release(vm);
nm->current_process_index = ~0;
for (i = 0; i < vec_len(nm->processes); i++)
cpu_time_now = dispatch_process(vm, nm->processes[i], /* frame */ 0, cpu_time_now);
}
while (1) {
vlib_node_runtime_t *n;
// 处理 RPC 请求
if (PREDICT_FALSE(_vec_len(vm->pending_rpc_requests) > 0)) {
if (!is_main)
vl_api_send_pending_rpc_requests(vm);
}
// 检查 worker 线程屏障
if (!is_main)
vlib_worker_thread_barrier_check();
// 检查帧队列是否有工作
if (PREDICT_FALSE(vm->check_frame_queues + frame_queue_check_counter)) {
u32 processed = 0;
vlib_frame_queue_dequeue_fn_t *fn;
if (vm->check_frame_queues) {
frame_queue_check_counter = 100;
vm->check_frame_queues = 0;
}
vec_foreach(fqm, tm->frame_queue_mains) {
fn = fqm->frame_queue_dequeue_fn;
processed += (fn)(vm, fqm);
}
// 没有找到帧队列的工作?
if (processed)
frame_queue_check_counter = 100;
else
frame_queue_check_counter--;
}
// 执行 worker 线程主循环回调函数
if (PREDICT_FALSE(vec_len(vm->worker_thread_main_loop_callbacks)))
clib_call_callbacks(vm->worker_thread_main_loop_callbacks, vm, cpu_time_now);
// 处理 pre-input 节点
cpu_time_now = clib_cpu_time_now();
vec_foreach(n, nm->nodes_by_type[VLIB_NODE_TYPE_PRE_INPUT])
cpu_time_now = dispatch_node(vm, n, VLIB_NODE_TYPE_PRE_INPUT, VLIB_NODE_STATE_POLLING, /* frame */ 0, cpu_time_now);
// 处理 input 节点
vec_foreach(n, nm->nodes_by_type[VLIB_NODE_TYPE_INPUT])
cpu_time_now = dispatch_node(vm, n, VLIB_NODE_TYPE_INPUT, VLIB_NODE_STATE_POLLING, /* frame */ 0, cpu_time_now);
// 如果是主循环且队列信号挂起,触发队列信号回调
if (PREDICT_TRUE(is_main && vm->queue_signal_pending == 0))
vm->queue_signal_callback(vm);
// 处理挂起的中断事件
if (__atomic_load_n(nm->pending_interrupts, __ATOMIC_ACQUIRE)) {
int int_num = -1;
*nm->pending_interrupts = 0;
while ((int_num = clib_interrupt_get_next(nm->interrupts, int_num)) != -1) {
vlib_node_runtime_t *n;
clib_interrupt_clear(nm->interrupts, int_num);
n = vec_elt_at_index(nm->nodes_by_type[VLIB_NODE_TYPE_INPUT], int_num);
cpu_time_now = dispatch_node(vm, n, VLIB_NODE_TYPE_INPUT, VLIB_NODE_STATE_INTERRUPT, /* frame */ 0, cpu_time_now);
}
}
// 处理 input 节点可能已经添加到 pending_frames 数组中的工作
for (i = 0; i < _vec_len(nm->pending_frames); i++)
cpu_time_now = dispatch_pending_node(vm, i, cpu_time_now);
// 重置 pending_frames 数组,准备下一轮迭代
vec_set_len(nm->pending_frames, 0);
if (is_main) {
// ELOG_TYPE_DECLARE 定义用于事件日志记录
ELOG_TYPE_DECLARE(es) = {
.format = "process tw start",
.format_args = "",
};
ELOG_TYPE_DECLARE(ee) = {
.format = "process tw end: %d",
.format_args = "i4",
};
struct {
int nready_procs;
} *ed;
// 检查是否有节点从定时轮中过期
ASSERT(nm->data_from_advancing_timing_wheel != 0);
if (PREDICT_FALSE(vm->elog_trace_graph_dispatch))
ed = ELOG_DATA(&vlib_global_main.elog_main, es);
nm->data_from_advancing_timing_wheel =
TW(tw_timer_expire_timers_vec)
((TWT(tw_timer_wheel) *)nm->timing_wheel, vlib_time_now(vm),
nm->data_from_advancing_timing_wheel);
ASSERT(nm->data_from_advancing_timing_wheel != 0);
if (PREDICT_FALSE(vm->elog_trace_graph_dispatch)) {
ed = ELOG_DATA(&vlib_global_main.elog_main, ee);
ed->nready_procs = _vec_len(nm->data_from_advancing_timing_wheel);
}
if (PREDICT_FALSE(_vec_len(nm->data_from_advancing_timing_wheel) > 0)) {
uword i;
for (i = 0; i < _vec_len(nm->data_from_advancing_timing_wheel); i++) {
u32 d = nm->data_from_advancing_timing_wheel[i];
u32 di = vlib_timing_wheel_data_get_index(d);
if (vlib_timing_wheel_data_is_timed_event(d)) {
vlib_signal_timed_event_data_t *te =
pool_elt_at_index(nm->signal_timed_event_data_pool, di);
vlib_node_t *n = vlib_get_node(vm, te->process_node_index);
vlib_process_t *p = vec_elt(nm->processes, n->runtime_index);
void *data;
data = vlib_process_signal_event_helper(nm, n, p,
te->event_type_index,
te->n_data_elts,
te->n_data_elt_bytes);
if (te->n_data_bytes < sizeof(te->inline_event_data))
clib_memcpy_fast(data, te->inline_event_data,
te->n_data_bytes);
else {
clib_memcpy_fast(data, te->event_data_as_vector,
te->n_data_bytes);
vec_free(te->event_data_as_vector);
}
pool_put(nm->signal_timed_event_data_pool, te);
} else {
cpu_time_now = clib_cpu_time_now();
cpu_time_now = dispatch_suspended_process(vm, di, cpu_time_now);
}
}
vec_set_len(nm->data_from_advancing_timing_wheel, 0);
}
}
// 增加主循环计数
vlib_increment_main_loop_counter(vm);
// 记录时间戳,以防没有启用的节点和上述调用未更新时间戳
cpu_time_now = clib_cpu_time_now();
vm->loops_this_reporting_interval++;
now = clib_time_now_internal(&vm->clib_time, cpu_time_now);
// 是否需要更新 loops_per_second?
if (PREDICT_FALSE(now >= vm->loop_interval_end)) {
// 下一次采样在 20 毫秒后结束
if (vm->loop_interval_start) {
f64 this_loops_per_second;
this_loops_per_second =
((f64)vm->loops_this_reporting_interval) / (now - vm->loop_interval_start);
vm->loops_per_second =
vm->loops_per_second * vm->damping_constant +
(1.0 - vm->damping_constant) * this_loops_per_second;
if (vm->loops_per_second != 0.0)
vm->seconds_per_loop = 1.0 / vm->loops_per_second;
else
vm->seconds_per_loop = 0.0;
}
// 新的间隔从现在开始,20 毫秒后结束
vm->loop_interval_start = now;
vm->loop_interval_end = now + 2e-4;
vm->loops_this_reporting_interval = 0;
}
}
}
负责管理进程的调度、执行和暂停等。根据进程的状态和标志,它会判断是否继续执行进程,启动定时器等。执行完毕后,会更新性能计数器、事件日志以及进程的统计信息 dispatch_process:
static u64
dispatch_process(vlib_main_t *vm,
vlib_process_t *p, vlib_frame_t *f, u64 last_time_stamp)
{
vlib_node_main_t *nm = &vm->node_main;
vlib_node_runtime_t *node_runtime = &p->node_runtime;
vlib_node_t *node = vlib_get_node(vm, node_runtime->node_index);
u32 old_process_index;
u64 t;
uword n_vectors, is_suspend;
// 检查节点状态和进程标志,判断是否可以继续执行
if (node->state != VLIB_NODE_STATE_POLLING ||
(p->flags & (VLIB_PROCESS_IS_SUSPENDED_WAITING_FOR_CLOCK |
VLIB_PROCESS_IS_SUSPENDED_WAITING_FOR_EVENT)))
return last_time_stamp;
// 设置进程标志,表示进程正在运行
p->flags |= VLIB_PROCESS_IS_RUNNING;
t = last_time_stamp;
// 记录事件日志,表示节点即将执行
vlib_elog_main_loop_event(vm, node_runtime->node_index, t,
f ? f->n_vectors : 0, /* is_after */ 0);
// 保存当前进程索引,以便在之后恢复
old_process_index = nm->current_process_index;
nm->current_process_index = node->runtime_index;
// 在进程启动之前更新性能计数器
vlib_node_runtime_perf_counter(vm, node_runtime, f, 0, last_time_stamp,
VLIB_NODE_RUNTIME_PERF_BEFORE);
// 调用进程的启动函数,获取处理的向量数目
n_vectors = vlib_process_startup(vm, p, f);
// 恢复之前的进程索引
nm->current_process_index = old_process_index;
// 判断进程的处理结果,可能是继续运行、暂停等
ASSERT(n_vectors != VLIB_PROCESS_RETURN_LONGJMP_RETURN);
is_suspend = n_vectors == VLIB_PROCESS_RETURN_LONGJMP_SUSPEND;
if (is_suspend) {
// 进程被暂停,处理暂停逻辑
vlib_pending_frame_t *pf;
n_vectors = 0;
// 从进程的暂停帧池中获取帧
pool_get(nm->suspended_process_frames, pf);
pf->node_runtime_index = node->runtime_index;
pf->frame = f;
pf->next_frame_index = ~0;
p->n_suspends += 1;
p->suspended_process_frame_index = pf - nm->suspended_process_frames;
if (p->flags & VLIB_PROCESS_IS_SUSPENDED_WAITING_FOR_CLOCK) {
// 如果进程等待时钟,则启动定时器
TWT(tw_timer_wheel) *tw = (TWT(tw_timer_wheel) *)nm->timing_wheel;
p->stop_timer_handle =
TW(tw_timer_start)(tw,
vlib_timing_wheel_data_set_suspended_process
(node->runtime_index) /* [sic] pool idex */,
0 /* timer_id */, p->resume_clock_interval);
}
} else {
// 进程未被暂停,清除运行标志
p->flags &= ~VLIB_PROCESS_IS_RUNNING;
}
t = clib_cpu_time_now();
// 记录事件日志,表示节点执行结束
vlib_elog_main_loop_event(vm, node_runtime->node_index, t, is_suspend,
/* is_after */ 1);
// 更新节点运行时的性能计数器
vlib_node_runtime_perf_counter(vm, node_runtime, f, n_vectors, t,
VLIB_NODE_RUNTIME_PERF_AFTER);
// 更新进程的统计信息
vlib_process_update_stats(vm, p,
/* n_calls */ !is_suspend,
/* n_vectors */ n_vectors,
/* n_clocks */ t - last_time_stamp);
return t;
}
通过调用 clib_setjmp 设置了一个返回点,然后通过条件判断选择不同的执行路径。如果返回值 r 是 VLIB_PROCESS_RETURN_LONGJMP_RETURN,说明进程还没有启动,那么会通过 vlib_process_start_switch_stack 切换到进程的新堆栈,然后调用 vlib_process_bootstrap 函数来执行进程的启动逻辑。如果 r 不是 VLIB_PROCESS_RETURN_LONGJMP_RETURN,说明进程已经启动过,此时会调用 vlib_process_finish_switch_stack 完成堆栈切换,然后返回 r。这样,通过 setjmp 和长跳转,进程可以在不同的堆栈上运行,实现了进程的启动与切换 vlib_process_startup:
/* 在主堆栈中调用。 */
static_always_inline uword
vlib_process_startup(vlib_main_t *vm, vlib_process_t *p, vlib_frame_t *f)
{
vlib_process_bootstrap_args_t a;
uword r;
// 设置参数
a.vm = vm;
a.process = p;
a.frame = f;
// 使用 setjmp 保存上下文,以便进行长跳转
r = clib_setjmp(&p->return_longjmp, VLIB_PROCESS_RETURN_LONGJMP_RETURN);
if (r == VLIB_PROCESS_RETURN_LONGJMP_RETURN) {
// 切换到进程的新堆栈,然后调用 vlib_process_bootstrap 函数
vlib_process_start_switch_stack(vm, p);
r = clib_calljmp(vlib_process_bootstrap, pointer_to_uword(&a),
(void *)p->stack + (1 << p->log2_n_stack_bytes));
} else {
// 完成堆栈切换
vlib_process_finish_switch_stack(vm);
}
return r;
}
从参数 _a 中还原了一个结构体 vlib_process_bootstrap_args_t,其中包含了所需的参数。然后,获取了进程的主要信息,如 vm、p(进程对象)、f(帧对象)、node(进程节点运行时对象)。接着,通过调用 node->function 来执行进程的主要逻辑,返回值 n 将用于表示进程的执行结果。
在进程函数执行完成后,代码通过 vlib_process_stack_is_valid 确保进程的堆栈仍然是有效的。然后,通过调用 vlib_process_start_switch_stack 切换回主堆栈,并使用 clib_longjmp 进行长跳转,将执行结果 n 返回给调用者,也就是 vlib_process_startup 函数。
总之,这段代码负责在进程的堆栈上执行进程函数,然后将执行结果返回给调用者。这种跳转和切换堆栈的方式使得进程能够在独立的堆栈上运行,实现了并发执行 vlib_process_bootstrap:
/* 在进程堆栈中调用。 */
static uword
vlib_process_bootstrap(uword _a)
{
vlib_process_bootstrap_args_t *a;
vlib_main_t *vm;
vlib_node_runtime_t *node;
vlib_frame_t *f;
vlib_process_t *p;
uword n;
// 从参数 _a 中还原参数结构体
a = uword_to_pointer(_a, vlib_process_bootstrap_args_t *);
// 获取参数
vm = a->vm;
p = a->process;
// 完成堆栈切换
vlib_process_finish_switch_stack(vm);
f = a->frame;
node = &p->node_runtime;
// 调用进程函数并获取返回值
n = node->function(vm, node, f);
// 确保进程堆栈是有效的
ASSERT(vlib_process_stack_is_valid(p));
// 切换回主堆栈,使用 clib_longjmp 返回结果
vlib_process_start_switch_stack(vm, 0);
clib_longjmp(&p->return_longjmp, n);
return n;
}
可以看到,这里就调用到了我们注册一个node之后,真正的处理函数,也就是我们新加node生效的地方。