One of the many traditional communication mechanisms are signals. As rt-threads are operating in kernel memory space and are not available via the linux kernel task-structure direct unix-signals from user-space applications to rt-threads are not possible. Possibilities shown in RTLinux examples where to install rt-handlers for FIFOs and trigger signals via these rt-FIFOs. In the following code an alternative concept that is intended to be expanded in the future is shown. This concept introduces a buddy-thread to each rt-thread that runs in kernel space as a kernel thread and thus is reachable directly from user-space via regular unix-signals. The signal is still a two hop job, a signal is set to the kthread identified by the pid of the kernel process and passed on to the rt-thread via directly modifying the pending signals mask of the rt-thread structure or by using the RTLinux-API pthread_kill and pthread_delete_np. This approach is also necessitated by the fact that the UNIX signal set and the RTLinux signal set are not the same - so the kernel_thread also remaps signals to something known in rt-context, in our case everything is a wakeup except a terminating signal which is a terminating rt-signal as well.
first we need some header files, the first for the RTLinux specific signal numbers (RTL_SIGNAL_WAKEUP in our case) and linux sched.h for the linux signal handling routines like flush_signals().
#include <rtl_signal.h> #include <linux/sched.h>
This time the kernel_thread will not terminate on its own on time - in our first example we simply assumed that the kernel_thread will have terminated before we can type rmmod khello.o so we did not bother weather the kernel_thread was still running or not. In this example we must take care of this and to do this we use a global variable state that allows cleanup_module to check and wait for the kernel_thread to terminate.
#define ACTIVE 1 #define TERMINATED 0 static int state=ACTIVE;
This rt-thread is no more than the /examples/hello/hello.c from RTLinux again, the only noteworthy difference its not a periodic thread but the thread suspends it self and waits until it is woken up by a signal again.
static void *
rtthread_code(void *arg)
{
struct sched_param p;
p . sched_priority = 1;
pthread_setschedparam (
pthread_self(),
SCHED_FIFO,
&p);
while (1) {
rtl_printf("RT-Thread woke up\n");
pthread_suspend_np(
pthread_self());
}
return 0;
}
The kernel thread was not created by a executable with a name that the kernel can use to name the process so we must explicitly fill out the process name in the process structure - so first we grab the process structure with a call to current (which is defined to get_current()) and clear the filed for the name.
static int
kthread_code( void *data )
{
struct task_struct *kthread=current;
char thread_name[NAME_LEN];
memset(thread_name,0,NAME_LEN);
We noted in the first example that the environment of the kernel_thread needs to be initialized explicitly, in this example we use a call to demonize() to make this thread inherit the environment from init (see kernel/exit.c for details). Following is a brute force synchronization using the global variable rt_thread_state to ensure that we don't enter the while(1) main loop of the kernel_thread before the rt-thread that we intend to send signals to is set up.
daemonize();
while (!rt_thread_state) {
current->state=TASK_INTERRUPTIBLE;
schedule_timeout(1);
}
This might seem a bit strange but here we take the address of the rt-thread as the unique name, its the simples solution as we are guaranteed that the rt-threads address is unique. And to make sure this kernel_thread does not simply eat up our CPU we set its nice value to the lowest possible value, by setting it directly in the task-structure, basically this is what renice does via a system call. After those setup steps we flush the signal mask - just to make sure there are no pending signals.
sprintf(thread_name,"rtl_%lx",
(unsigned long)&rt_thread);
strcpy(kthread->comm,
thread_name);
kthread->nice=20;
spin_lock_irq(&kthread->sigmask_lock);
sigemptyset(&kthread->blocked);
flush_signals(kthread);
recalc_sigpending(kthread);
spin_unlock_irq(&kthread->sigmask_lock);
This part is a more interesting now - this kernel_thread will receive signals and by periodically (but non-realtime - so very `soft` periodicity) checking if it has any pending signals - if there are and they are not a terminating signal, then wake up the rt-thread else terminate the rt-thread and then terminate it self. Note that pthread_delete_np basically simply ensures that the cancellation signal can be delivered by directly resetting threads blocked-signal mask and the calls pthread_cancel on the rt-thread.
while(1){
interruptible_sleep_on(&wait);
if(sigtestsetmask(
&kthread->pending.signal,
sigmask(SIGKILL))){
pthread_delete_np(rt_thread);
break;
}
else{
pthread_kill(rt_thread,
RTL_SIGNAL_WAKEUP);
spin_lock_irq(
&kthread->sigmask_lock);
sigemptyset(&kthread->blocked);
flush_signals(kthread);
recalc_sigpending(kthread);
spin_unlock_irq(
&kthread->sigmask_lock);
}
}
When the kernel_thread exits set state to TERMINATED so cleanup_module can proceed.
state=TERMINATED; return(0); }
Init module initializes the wait queue for the kernel_thread to wait on sets up the kernel_thread itself and then invokes pthread_create on the rt-thread function and uses the return value of pthread_create to `signal` that the rt-thread is active via the global variable rt_thread_state, so that the kernel_thread can continue initializing.
int
init_module(void)
{
init_waitqueue_head(&wait);
kthread_id=kernel_thread(
kthread_code,
NULL,
CLONE_FS|CLONE_FILES|CLONE_SIGHAND);
printk("rt_sig_thread (pid %d)\n",
kthread_id);
rt_thread_state = pthread_create(
&rt_thread,
NULL,
rtthread_code,
0);
return 0;
}
Cleanup module more or less reverses the process from init_module - we first delete the rt-thread, and then we kill the kernel_thread by sending it a SIGKILL, but before we can proceed on we need to wait for the kernel_thread to actually terminate - again `signaled` by a global variable state, to ensure that we would not wait for ever and hang up the system, we test 10 times and call the scheduler in between - if the kernel_thread did not exit on time and rmmod continues then we get a kernel oops.
void
cleanup_module(void)
{
int ret;
pthread_delete_np (rt_thread);
ret = kill_proc(kthread_id,
SIGKILL,
1);
if (!ret) {
int count = 10 * HZ;
while (state && --count) {
current->state=TASK_INTERRUPTIBLE;
schedule_timeout(1);
}
}
printk("rt_sig_thread exit\n");
}