next up previous
Next: Kernel Threads Up: Tasklets Previous: scheduling tasklets from rt-context

naive rt-allocator

As a second somewhat more interesting example of using a tasklet from rt-context a naive rt_allocator framework is presented. The tasklet is called from a rt-function that suspends the running thread rtl_malloc(size), this allocator will call a tasklet to do the actually memory allocation and then signal RTL_SIGNAL_WAKEUP back to the rt-thread when the allocator thread is done. The allocation thus is non-realtime and the realtime thread needs to check if memory actually was allocated successfully or not. Note that the call to kmalloc in the tasklet uses the flags GFP_ATOMIC which is necessary, if GFP_KERNEL were used the tasklet could sleep and thus the system would hang.

This allocator has a automatic initialized array of pointers to char set and will allocate a requested size of memory assigned to these pointers. These are globally available so the tasklet can signal a wakeup to the rt-thread by setting the appropriate bit in the threads pending signal mask. instead of setting the bit directly one could also call pthread_kill(rt_thread,RTL_SIGNAL_WAKEUP), if modules are split between kernel and rtl context it sometimes is a problem to include rtl API-calls that require rtl-header files so in those cases directly accessing the signal pending mask solves the problem.


...
#include <linux/slab.h>      /* kmalloc */

void allocator_function(unsigned long arg);
#define BUFFERS 128

This allocator has a static array of pointers for the buffers so the absolute memory that could be allocated is bounded by 128 pointers to kmalloc'ed areas of each maximum 128kByte.


static char *iptr[BUFFERS]; 
static int iptr_idx;
DECLARE_TASKLET(allocator_tasklet,i
  allocator_function,0);


void
allocator_function(unsigned long arg)
{
  struct timeval now;
  do_gettimeofday(&now);
  printk("alloc %ld at %ld,%ld\n",
    (unsigned long)arg,
    now.tv_sec,
    now.tv_usec);
  iptr[iptr_idx]=kmalloc(
    (unsigned long)arg,
    GFP_ATOMIC);
  if(iptr[iptr_idx] == NULL){
    printk("Allocation failed\n");
  }
  else{
    memset(iptr[iptr_idx],
      0,
      (unsigned long)arg);
    printk("Allocated 0'ed buffer %d\n",
      iptr_idx);
    iptr_idx++;
  }
If we get the memory that we wanted then wake up the rt-thread that requested memory, note that this is done here by directly flipping the bit in the signal vector of the thread - one could have used a pthread_kill() as well, but as we are in kernel space we have direct access as well.


  set_bit(RTL_SIGNAL_WAKEUP, 
    &rt_thread->pending); 
}

This is a wrapper function to call the tasklet that will then call kmalloc - basically it will schedule the tasklet and suspend itself - this is made to be a cancellation point as well by calling pthread_testcancel() at the end - which makes sense for functions that could theoretically be delayed an inappropriate time, causing a different thread to cancel this threads execution. Before actually calling the tasklet though, it first is checked if any buffer pointers are left. This hardly is really a usable allocator - but it should outline the basic resources that would be needed to build an application specific allocator.


unsigned long
rtl_kmalloc(unsigned long size)
{
  int idx;
  pthread_t self = pthread_self();
  RTL_MARK_SUSPENDED (self);
  rtl_printf("requesting %ld bytes\n",
    (unsigned long)size);
  idx = iptr_idx;
  if(idx < BUFFERS){
    allocator_tasklet.data=size;
    tasklet_hi_schedule(
      &allocator_tasklet);
    rtl_schedule();
    pthread_testcancel();
    if(iptr[idx] == NULL){
      return -1;
    }
    else{
      return idx;
    }
  }
  else{
    return -1;
  }
  return 0;
}

In the actual application thread, a period rt-thread with a period of half a second (500000000 nanoseconds), shown below, nothing useful is being done - only allocate all buffers until we have none left. Once we are out of buffers this thread simply prints an error to the kernel message ring-buffer and goes on. To few the messages again use dmesg.


void * 
start_routine(void *arg)
{
  struct sched_param p;
  int ret;
  unsigned long i,size,block;
  p . sched_priority = 1;
  pthread_setschedparam (
    pthread_self(), 
    SCHED_FIFO, 
    &p);

  pthread_make_periodic_np(
    pthread_self(), 
    gethrtime(), 
    500000000);

  size=0;
  block=128;
  i=1;
  while (1) {
    pthread_wait_np ();
    size=block*i++;
    rtl_printf("request %ld bytes\n", 
      size);
    ret=rtl_kmalloc(size);

This here is the actual problem for rt-threads with dynamic resources - you never can have the guarantee that you get what your requested - apps must check that they actually got something, and the problem is designing an exit strategy in case you get no resources that will not break your rt-application. So for any mission-critical task dynamic resources are a fundamental problem.


    if(ret == -1){
      rtl_printf("No more buffers available\n");
    }
    else{
      rtl_printf("allocated buffer %d\n",ret);
    }
  }
  return 0;
}

The mandatory init_module and cleanup_module, just get set all pointers to NULL and create the thread. In cleanup_module free all non-NULL buffers and delete the rt-thread.


int 
init_module(void) 
{
  int i;
  for(i=0;i<BUFFERS;i++){
    iptr[i] = NULL;
  }
  return pthread_create (
    &rt_thread,
    NULL, 
    start_routine, 
    0);
}

void 
cleanup_module(void) 
{
  int i;
  for(i=0;i<BUFFERS;i++){
    if(iptr[i] != NULL){
      kfree(iptr[i]);
      printk("Freeing buffer %d\n",i);
    }
  }
  pthread_delete_np (rt_thread);
}

next up previous
Next: Kernel Threads Up: Tasklets Previous: scheduling tasklets from rt-context
Der Herr Hofrat
2003-01-06