Such situations are often resolved using callbacks.
Suppose module A exports the functions to register/unregister the callbacks. B and/or C use these functions and provide the appropriate callbacks to A. When it is needed, A checks if the callbacks are set and calls them.
Something like this (without error handling and locking for simplicity):
/* Module A */
struct a_ops /* Better to define struct a_ops in a header file */
{
void (*needed_func)(void);
void (*another_needed_func)(void);
};
...
struct a_ops ops = {
.needed_func = NULL;
.another_needed_func = NULL;
};
...
int a_register_needed_funcs(struct a_ops *a_ops)
{
ops.needed_func = a_ops->needed_func;
ops.another_needed_func = a_ops->another_needed_func;
}
EXPORT_SYMBOL(a_register_needed_funcs);
void a_unregister_needed_funcs()
{
ops.needed_func = NULL;
ops.another_needed_func = NULL;
}
EXPORT_SYMBOL(a_unregister_needed_funcs);
...
/* Call the specified callbacks when needed: */
void do_something(void)
{
if (ops.needed_func != NULL) {
ops.needed_func();
}
else {
/* the callback is not set, handle this: report error, ignore it or
* do something else */
...
}
}
...
/* Modules B and C */
/* Their code #includes the file where struct a_ops is defined.
* The module registers the callbacks, for example, in its init function
* and unregister in exit function. */
...
static void func(void)
{
...
}
static void another_func(void)
{
...
}
struct a_ops my_funcs = {
.needed_func = func;
.another_needed_func = another_func;
};
int __init my_module_init(void)
{
...
result = a_register_needed_funcs(&my_funcs);
...
}
void __exit my_module_exit(void)
{
...
a_unregister_needed_funcs();
...
}
This is similar to file operations and many other callback operations in the kernel. Suppose a user wants to read from, say, a character device maintained by a custom driver. The kernel proper (VFS, to be exact) receives the request but cannot handle it itself. It forwards the request to that custom driver that has registered its file operation callbacks for that device. In turn, the driver uses the functions exported by the kernel proper, like cdev_add()
, etc.