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