Repository navigation
This brief tutorial explains how to write portable code with URT. Some back-ends of URT support real-time applications in kernel space and URT tries to hide all differences regarding initialization and termination of applications as well as receiving parameters.
First, let's outline a possible general structure of an application in user space:
int main(void)
{
int ret = startup();
if (ret)
return ret;
body();
finish();
}This is a very simplistic view of the application. For example, there are no parameters and there is no signal handling. Here is a more complete view of this general scheme:
/* parameters of the application */
static int param1 = PARAM1_DEFAULT_VALUE;
static char param2[20] = PARAM2_DEFAULT_VALUE;
static unsigned int param2_count = PARAM2_DEFAULT_LEN;
/* the data used by the application, moved around the functions */
struct app_data
{
/* whatever data */
};
/* signal handling */
static volatile sig_atomic_t interrupted = 0;
static int done = 0;
static void signal_handler(int signum)
{
interrupted = 1;
}
int main(int argc, char **argv)
{
struct app_data data;
/* parse application arguments and store them in `param1`, `param2` etc */
parse_arguments(argc, argv);
/* setup signal handling */
struct sigaction sa = {
.sa_handler = signal_handler,
};
sigemptyset(&sa.sa_mask);
sigaction(SIGSEGV, &sa, NULL);
sigaction(SIGINT, &sa, NULL);
sigaction(SIGHUP, &sa, NULL);
sigaction(SIGTERM, &sa, NULL);
sigaction(SIGQUIT, &sa, NULL);
sigaction(SIGUSR1, &sa, NULL);
sigaction(SIGUSR2, &sa, NULL);
/* do the absolute necessary to make sure the application could run */
int ret = startup(&data);
if (ret)
return ret;
/* spawn real-time threads that do the actual work */
body(&data);
/* wait until interrupted */
while (!interrupted)
usleep(A_LITTLE);
/* wait until `body()` declares it is done */
while (!done)
usleep(A_LITTLE);
/* cleanup */
finish(&data);
return 0;
}The threads spawned by body() in the example above should either cancel themselves when interrupted, or when the
finish() function tells them to stop. In the above example, the done variable is not really necessary, since we
know that body() has already terminated. The use of this variable would be more clear when body() is run in a
thread in kernel space.
Speaking of kernel space, let's see how a possible general in-kernel application (disguised as a kernel module) would look like:
static int body_thread(void *arg)
{
body();
do_exit(0);
return 0;
}
static int __init app_init(void)
{
int ret = startup();
if (ret)
return ret;
if (kthread_run(body, NULL, "app_body") == ERR_PTR(-ENOMEM))
return ENOMEM;
return 0;
}
static void __exit app_exit(void)
{
finish();
}
module_init(app_init);
module_exit(app_exit);This example too is a simplistic view of the kernel module. The main difference here with respect to the user-space
application is that, besides running in kernel space, body() and finish() can now be run in parallel. This can
happen if the user rmmods the module while body() has not yet terminated. The done variable above exists
specifically to prevent this. As with the user-space application, the above example can be completed with handling
parameters and proper handling of rmmod:
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Shahbaz Youssefi")
MODULE_DESCRIPTION("Example module")
/* parameters of the application */
static int param1 = PARAM1_DEFAULT_VALUE;
static char param2[20] = PARAM2_DEFAULT_VALUE;
static unsigned int param2_count = PARAM2_DEFAULT_LEN;
module_param(param1, int, S_IRUGO);
MODULE_PARAM_DESC(param1, "This is parameter 1");
module_param_array(param2, char, ¶m2_count, S_IRUGO);
MODULE_PARAM_DESC(param2, "This is parameter 2");
/* the data used by the application, moved around the functions */
struct app_data
{
/* whatever data */
};
static struct data data;
/* rmmod handling */
static int interrupted = 0;
static int done = 0;
static int body_thread(void *arg)
{
/* spawn real-time threads that do the actual work */
body(&data);
do_exit(0);
return 0;
}
static int __init app_init(void)
{
/* do the absolute necessary to make sure the application could run */
int ret = startup(&data);
if (ret)
return ret;
/* spawn a thread for `body()`, so that `insmod` can quickly return */
if (kthread_run(body, NULL, "app_body") == ERR_PTR(-ENOMEM))
return ENOMEM;
return 0;
}
static void __exit app_exit(void)
{
/* tell the threads and `body()` that `rmmod` is called */
interrupted = 1;
/* wait until `body()` declares it is done */
while (!done)
msleep(A_LITTLE);
/* cleanup */
finish(&data);
}
module_init(app_init);
module_exit(app_exit);Now that the complete form of the Linux kernel module is also presented, it can be seen why body() didn't have a
return value in the user-space case, or what the done variable is for.
In kernel space, module parameter handling is done by the kernel. To make portable (between user-space and kernel-space) code, URT parses the command line arguments the same way the Linux kernel does, i.e., with the same format.
As you can see, there are fair amount of differences between the user-space and kernel-space application. URT thus
provides a macro that glues everything together based on the space it is being compiled for. For this macro, named
URT_GLUE, you would need to provide the names of the startup(), body() and finish() functions, as well as
the names of the interrupted and done variables and struct app_data data type (the names used above are just
examples). Furthermore, you would need to declare the application parameters somewhat similar to the Linux kernel,
which in kernel space directly translates to Linux's own macros.
The user-space and kernel-space applications above can be unified with URT as follows:
#include <urt.h>
URT_MODULE_LICENCE("GPL");
URT_MODULE_AUTHOR("Shahbaz Youssefi");
URT_MODULE_DESCRIPTION("Example URT Application");
/* application parameters */
static int param1 = PARAM1_DEFAULT_VALUE;
static char param2[20] = PARAM2_DEFAULT_VALUE;
static unsigned int param2_count = PARAM2_DEFAULT_LEN;
URT_MODULE_PARAM_START()
URT_MODULE_PARAM(param1, int, "This is parameter 1")
URT_MODULE_PARAM(param2, char, ¶m2, "This is parameter 2")
URT_MODULE_PARAM_END()
/* the data used by the application, moved around the functions */
struct app_data
{
/* whatever data */
};
static int startup(struct app_data *data);
static void body(struct app_data *data);
static void finish(struct app_data *data);
URT_GLUE(startup, body, finish, struct data, interrupted, done)The URT_GLUE macro would then take care of parsing application parameters (if in user space), setting up interrupt
handlers (if in user space) or setting interrupted when rmmoded (if in kernel space), and waiting for done when
necessary. That said, interrupted is a variable managed by URT and done is one managed by the application. With
interrupted, URT can tell the application that it needs to terminate and with done, the application tells URT that
it can go ahead with calling finish().
A sample implementation of startup(), body() and finish() functions could be as follows:
/* a function that cleans up the data as well as URT */
static void cleanup(struct app_data *data)
{
cleanup_stuff(data);
urt_exit();
}
static int startup(struct app_data *data)
{
/* give default values to data */
*data = (struct app_data){
/* default values */
};
/* sanity checks */
if (not_valid(param1))
param1 = PARAM1_DEFAULT_VALUE;
/* initialize URT */
if (urt_init())
goto exit_no_urt;
/* initialize everything else */
if (setup_other_stuff(data))
goto exit_no_other_stuff;
return 0;
exit_no_other_stuff:
/* on failure, make sure to cleanup at least URT */
cleanup(data);
exit_no_urt:
return EXIT_FAILURE;
}
static void body(struct app_data *data)
{
/* do everything that can take a long time in the `body()` and spawn real-time threads to do the work */
do_lengthy_things(data);
start_real_time_threads(data);
/* once all this is done, tell that to URT so it can know when it is safe to call `finish()` */
done = 1;
}
static void finish(struct app_data *data)
{
/* wait until interrupted */
while (!interrupted)
urt_sleep(A_LITTLE);
/* cleanup at least URT */
cleanup(data);
}In the example above, body() terminates on its own, so finish() would have to wait until interrupted, otherwise in
user-space the application would just go on and finish. In some applications, the body() itself could loop forever
until interrupted, in which case finish() doesn't need to wait for interrupted instead.
A variant of URT_GLUE exists, namely URT_GLUE_NO_INTERRUPT that leaves the interrupt handling to the user. This is
so that the user could perform more complicated signal handling if she so desires.
While URT tries to unify some things between the two spaces, such as bringing sig_atomic_t, EXIT_FAILURE and qsort
to kernel space or declaring urt_sleep() and urt_mem_new() that work in either space, a complete unification is
both impossible and unnecessary. That said, to write portable applications between user and kernel spaces, you may
eventually need to conditionally compile on whether __KERNEL__ is defined.
Feel free to raise an issue in URT at GitHub with any questions on URT.
Next: See more tutorials.