next up previous
Next: Accessing files from kernel_threads Up: Kernel Threads Previous: communicating with rt-threads

buddy thread concept

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");
}


next up previous
Next: Accessing files from kernel_threads Up: Kernel Threads Previous: communicating with rt-threads
Der Herr Hofrat
2003-01-06