require’s module load¶
The require module is essentially a library loader that loads EPICS modules on
the running IOC. This article will describe require’s core implementation:
self registering, module loading and module registry.
Self registration¶
The init.cpp
is the most important file in require. It implements the self registration
mechanism that allows require to offload dependency loading to the system
loader. It has a single function, __module_library_init(),
that at first will load the <module_name>.dbd from the libraries directory.
Then the module registers the record device driver by calling Registration().
It registers itself to require via register_module(), that sets up require’s
own PVs and environment variables about the loaded module. Finally
setup_db_path() updates the environment variables that point to the module’s
database files: module_DB, TEMPLATES and EPICS_DB_INCLUDE_PATH.
This file is embedded into every module build for e3 by driver.Makefile. Just
after creating the <module_name>_registerRecordDeviceDriver, the inclusion of
init.cpp replaces a direct call to Registration(). This is done to avoid
running into the C++ static initialization order fiasco. Basically, C++ cannot
guarantee the initialization order of any static code, so all initialization
must be done in a single function.
# Create file to fill registry from dbd file. Because of c++ static
# initialization order fiasco all static initialization needs to be
# in a single function. Therefore the last line of
# module_registerRecordDeviceDriver.cpp, that would run Registration()
# for initialization, is removed. Then init.cpp is appended and
# __module_library_init() is initializes the library and calls
# Registration().
${REGISTRYFILE}: ${MODULEDBD}
$(PERL) $(EPICS_BASE_HOST_BIN)/registerRecordDeviceDriver.pl $< ${PRJ_SYMBOL}_registerRecordDeviceDriver | grep -v 'iocshRegisterCommon();' > $@
sed -i'.bak' -E '/^.*= Registration\(\)\;$$/d' $@
echo "#include <init.cpp>" >> $@
Note
See C++ Static Initialization Order Fiasco for background on the workaround above.
Module loading¶
The modules built with e3 must declare all other modules they depend on as linked libraries,
see documentation.
The module calc depends on sscan and sequencer, which is defined in its Makefile:
USR_LIBS += sscan sequencer. When require calc is called, require will try to load calc using dlopen, and
that will automatically load the library dependencies, starting with sscan and sequencer.
Note
As we use conda for package dependency and version handling, require does not have to care about these.
The loading is done by require_priv, which also validates that the library is a require-compatible EPICS module:
static int require_priv(const char *module) {
void *lib_handle = NULL;
char lib[PATH_MAX] = {0};
void *symbol_address = NULL;
char *dlsym_error = NULL;
debug("Trying to load module=\"%s\".\n", module);
debug("Load the library if file exists.\n");
snprintf(lib, PATH_MAX, PREFIX "%s" EXT, module);
lib_handle = dlopen(lib, RTLD_NOW | RTLD_GLOBAL);
if (lib_handle == NULL) {
errlogPrintf("Error loading module: %s.\n", module);
dlsym_error = dlerror();
if (dlsym_error != NULL)
errlogPrintf("%s\n", dlsym_error);
return -1;
}
symbol_address = dlsym(lib_handle, E3_SYMBOL);
dlsym_error = dlerror();
if (dlsym_error != NULL || symbol_address == NULL) {
dlclose(lib_handle);
errlogPrintf(PREFIX "%s" EXT " is not an EPICS module.\n", module);
if (dlsym_error != NULL)
errlogPrintf("%s\n", dlsym_error);
return -1;
}
return 0;
}
Note
Notice that require will check for the __module_lib_version symbol to check
if the library is an e3 compatible EPICS module.
Module registry¶
require holds a linked list with information of every module loaded. The module
structure holds modules name, version, and path. This list of loaded modules and
their versions is also what later is exposed in PV form (See The require function).