Directory
Following is a list of the main directories in VPP source code, where basic information is given:
build-data
- contains makefile fragments which tell vpp build system how to build various components
- e.g.
/build-data/packages/vpp.mk
takes advantages ofauto_conf
files to install relevant dependencies
build-root
- rooted build system where all executable tools are built
- the main make file here is fairly complicated… (data-driven)
- normally the dependency should have no problem, it’s quite accurate
dpdk
- open source DPDK package
- nothing really touched, used directly
sample-plugin
- install engine development package
- configure and build the sample plug-in with installed libraries
svm
- library of shared virtual memory, used for control plane binary api between clients and vpp itself
vlib
- the vector library, including means by which VPP does vector processing
- definition of node types etc.
vlib-api
- the set of api infrustracture routines layered on top of shared virtual memory which allow clients to communicate with the overall process
vnet
- the network stack (the largest piece)
- a static library
vpp
- the container app
vppapigen
- the api compiler
vpp-api-test
- the canonical binary api client
vppinfra
- the fundation libraries
- e.g. dynamic arrays, vectors, spin maps, hashes (of various sorts), etc.
vpp-japi
- vpp java binding
Initialization
One of the highlights of VPP is that it naturally supports plugins. Here we are going to take a quick journey through the initialization of the plugins.
/vlib/init.h
Initialization is implemented on basis of constructor function, as is defined in src/vlib/init.h
, so that the plugins will be included just as main line codes.
/* Declaration is global (e.g. not static) so that init functions can
be called from other modules to resolve init function depend. */
#define VLIB_DECLARE_INIT_FUNCTION(x, tag) \
vlib_init_function_t * _VLIB_INIT_FUNCTION_SYMBOL (x, tag) = x; \
static void __vlib_add_##tag##_function_##x (void) \
__attribute__((__constructor__)) ; \
static void __vlib_add_##tag##_function_##x (void) \
{ \
vlib_main_t * vm = vlib_get_main(); \
static _vlib_init_function_list_elt_t _vlib_init_function; \
_vlib_init_function.next_init_function \
= vm->tag##_function_registrations; \
vm->tag##_function_registrations = &_vlib_init_function; \
_vlib_init_function.f = &x; \
}
#define VLIB_INIT_FUNCTION(x) VLIB_DECLARE_INIT_FUNCTION(x,init)
/vpp/vnet/main.c
Then, by default, main heap will be created and allocated to vnet and plugins. And then vlib_unix_main (argc, argv)
will be called.
defaulted:
/* Set up the plugin message ID allocator right now... */
vl_msg_api_set_first_available_msg_id (VL_MSG_FIRST_AVAILABLE);
/* Allocate main heap */
if (clib_mem_init (0, main_heap_size))
{
vm->init_functions_called = hash_create (0, /* value bytes */ 0);
vpe_main_init (vm);
return vlib_unix_main (argc, argv);
}
else
{
{
int rv __attribute__ ((unused)) =
write (2, "Main heap allocation failure!\r\n", 31);
}
return 1;
}
}
/vlib/unix/main.c
int
vlib_unix_main (int argc, char *argv[])
{
vlib_main_t *vm = &vlib_global_main; /* one and only time for this! */
unformat_input_t input;
clib_error_t *e;
int i;
vm->argv = (u8 **) argv;
vm->name = argv[0];
vm->heap_base = clib_mem_get_heap ();
ASSERT (vm->heap_base);
unformat_init_command_line (&input, (char **) vm->argv);
if ((e = vlib_plugin_config (vm, &input)))
{
clib_error_report (e);
return 1;
}
unformat_free (&input);
i = vlib_plugin_early_init (vm);
if (i)
return i;
unformat_init_command_line (&input, (char **) vm->argv);
if (vm->init_functions_called == 0)
vm->init_functions_called = hash_create (0, /* value bytes */ 0);
e = vlib_call_all_config_functions (vm, &input, 1 /* early */ );
if (e != 0)
{
clib_error_report (e);
return 1;
}
unformat_free (&input);
vlib_thread_stack_init (0);
__os_thread_index = 0;
vm->thread_index = 0;
i = clib_calljmp (thread0, (uword) vm,
(void *) (vlib_thread_stacks[0] +
VLIB_THREAD_STACK_SIZE));
return i;
}
/vlib/unix/plugin.c
In the vlib_unix_main
function, we can observe that there is a function called vlib_plugin_early_init
, which is defined in vlib/unix/plugin.c
and initialize plugin before main()
. The following chunk of code basically allows plugins being added via CLI.
int
vlib_plugin_early_init (vlib_main_t * vm)
{
plugin_main_t *pm = &vlib_plugin_main;
if (pm->plugin_path == 0)
pm->plugin_path = format (0, "%s%c", vlib_plugin_path, 0);
clib_warning ("plugin path %s", pm->plugin_path);
pm->plugin_by_name_hash = hash_create_string (0, sizeof (uword));
pm->vlib_main = vm;
return vlib_load_new_plugins (pm, 1 /* from_early_init */ );
}
unformat_init_command_line
These following lines basically take the command line and assign values to different variables. If necessary, configuration functions will be called.
unformat_init_command_line (&input, (char **) vm->argv);
if (vm->init_functions_called == 0)
vm->init_functions_called = hash_create (0, /* value bytes */ 0);
e = vlib_call_all_config_functions (vm, &input, 1 /* early */ );
if (e != 0)
{
clib_error_report (e);
return 1;
}
unformat_free (&input);
vlib_thread_stack_init
The following important concept is the thread stack. The following lines basically initialize the thread stack and assign the os thread as 0 index.
vlib_thread_stack_init (0);
__os_thread_index = 0;
vm->thread_index = 0;
clib_calljmp
i = clib_calljmp (thread0, (uword) vm,
(void *) (vlib_thread_stacks[0] +
VLIB_THREAD_STACK_SIZE));
This function will then lead us to our initialized thread, which is called now thread0
. If we take a closer look into this thread0
, it will finally lead us to an interesting function called vlib_main
, which located in /vlib/main.c
.
vlib_main
In this function, all static nodes are registered by vlib_register_all_static_nodes
, all subsystems are configured by vlib_call_all_config_functions
, and there is one last chance for some systems to do the last-minute setup and configuration in order to get ready and dive into the main loop with vlib_call_all_main_loop_enter_functions
.
And finally, this function leads to the vlib_main_loop
, which is the main loop for our thread0
.
/* Main function. */
int
vlib_main (vlib_main_t * volatile vm, unformat_input_t * input)
{
clib_error_t *volatile error;
vlib_node_main_t *nm = &vm->node_main;
vm->queue_signal_callback = dummy_queue_signal_callback;
clib_time_init (&vm->clib_time);
/* Turn on event log. */
if (!vm->elog_main.event_ring_size)
vm->elog_main.event_ring_size = 128 << 10;
elog_init (&vm->elog_main, vm->elog_main.event_ring_size);
elog_enable_disable (&vm->elog_main, 1);
/* Default name. */
if (!vm->name)
vm->name = "VLIB";
if ((error = unix_physmem_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;
}
/* Register static nodes so that init functions may use them. */
vlib_register_all_static_nodes (vm);
/* Set seed for random number generator.
Allow user to specify seed to make random sequence deterministic. */
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);
/* Initialize node graph. */
if ((error = vlib_node_main_init (vm)))
{
/* Arrange for graph hook up error to not be fatal when debugging. */
if (CLIB_DEBUG > 0)
clib_error_report (error);
else
goto done;
}
/* See unix/main.c; most likely already set up */
if (vm->init_functions_called == 0)
vm->init_functions_called = hash_create (0, /* value bytes */ 0);
if ((error = vlib_call_all_init_functions (vm)))
goto done;
/* Create default buffer free list. */
vlib_buffer_create_free_list (vm, VLIB_BUFFER_DEFAULT_FREE_LIST_BYTES,
"default");
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_len (nm->data_from_advancing_timing_wheel) = 0;
/* Create the process timing wheel */
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_len (vm->pending_rpc_requests) = 0;
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;
}
if ((error = vlib_call_all_config_functions (vm, input, 0 /* is_early */ )))
goto done;
/* Call all main loop enter functions. */
{
clib_error_t *sub_error;
sub_error = vlib_call_all_main_loop_enter_functions (vm);
if (sub_error)
clib_error_report (sub_error);
}
vlib_main_loop (vm);
done:
/* Call all exit functions. */
{
clib_error_t *sub_error;
sub_error = vlib_call_all_main_loop_exit_functions (vm);
if (sub_error)
clib_error_report (sub_error);
}
if (error)
clib_error_report (error);
return 0;
}
vlib_main_loop
There are two loop functions defined in /vlib/main.c
, namely, vlib_main_loop
and vlib_worker_loop
, whose difference is kind of obvious. Here, we are mainly talking about the former one, that is vlib_main_loop
.
static void
vlib_main_loop (vlib_main_t * vm)
{
vlib_main_or_worker_loop (vm, /* is_main */ 1);
}
void
vlib_worker_loop (vlib_main_t * vm)
{
vlib_main_or_worker_loop (vm, /* is_main */ 0);
}
If we dive inside the vlib_main_or_worker_loop
, we can observe the mainstream concepts inside a while(1)
loop one after another:
- Pre-Input Node
- epoll / pop …
- file io / interrupt …
- input
- getting packets into the graph
- queue_signal_pending
- shoulder tap the main thread
- service pending interrupts
- timer_wheel / processes
- Check if process nodes have expired from timing wheel
- pending frames
- 97% of vector processing is done in the loop here
- vector packets + dispatching function (can be interesting to look into cause we can see how a node is dispatched according to the
time_stamp
, how things can be different with differentCLI_DEBUG
value, and how each node is processing its received packets.)