Kernel seminar – part 3

Top and bottom halves

Top half: section that executed within the ISR
Bottom half: section that executed after the interrupt
Both implemented as tasklets
each tasklet executed on one CPU after the interrupt is handled. The tasklets to be excited stored in a linked list.

Declare a tasklet:

DECLARE_TASKLET (module_tasklet, //Name
    module_do_tasklet,          //Function
    tasklet_data);                       // tasklet argument

To schedule a tasklet (in the interrupt handler):

tasklet_schedule(&module_tasklet); // tasklets at the end of the linked list of tasklets
tasklet_hi_schedule(&module_tasklet); // start of the linked list of tasklets

tasklet example

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/interrupt.h>

MODULE_LICENSE("GPL");

char my_tasklet_data[]="my_tasklet_function was called";

/* Bottom Half Function */
void my_tasklet_function( unsigned long data )
{
  printk( "%s\n", (char *)data );
  return;
}

DECLARE_TASKLET( my_tasklet, my_tasklet_function,
                (unsigned long) &my_tasklet_data );

int init_module( void )
{
  /* Schedule the Bottom Half */
  tasklet_schedule( &my_tasklet );
  return 0;
}

void cleanup_module( void )
{
  /* Stop the tasklet before we exit */
  tasklet_kill( &my_tasklet );
  return;
}

Deferring work

Work queues

another mechanism for deferring work
works on the same CPU
bigger latency than worklets

Work functions are in workqueue.h
Schedule work dynamically:

static inline bool schedule_work(struct work_struct *work)
static inline bool schedule_work_on(int cpu, struct work_struct *work)
static inline bool schedule_delayed_work(struct delayed_work *dwork,unsigned long delay)
static inline bool schedule_delayed_work_on(int cpu, struct delayed_work *dwork,unsigned long delay)

Schedule work statically with macros

 INIT_WORK(_work, _func)
 INIT_DELAYED_WORK(_work, _func)

Work queue example:

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/workqueue.h>
#include <linux/slab.h>

MODULE_LICENSE("GPL");

static struct workqueue_struct *my_wq;

typedef struct {
  struct work_struct my_work;
  int x;
} my_work_t;
my_work_t *work, *work2;

static void my_wq_function( struct work_struct *work)
{
  my_work_t *my_work = (my_work_t *)work;
  printk( "my_work.x %d\n", my_work->x );
  kfree( (void *)work );
  return;
}

int init_module( void )
{
  int ret;
  my_wq = create_workqueue("my_queue");
  if (my_wq) {
    /* Queue some work (item 1) */
    work = (my_work_t *)kmalloc(sizeof(my_work_t), GFP_KERNEL);
    if (work) {
      INIT_WORK( (struct work_struct *)work, my_wq_function );
      work->x = 1;
      ret = queue_work( my_wq, (struct work_struct *)work );
    }
    /* Queue some additional work (item 2) */
    work2 = (my_work_t *)kmalloc(sizeof(my_work_t), GFP_KERNEL);
    if (work2) {
      INIT_WORK( (struct work_struct *)work2, my_wq_function );
      work2->x = 2;
      ret = queue_work( my_wq, (struct work_struct *)work2 );
    }
  }
  return 0;
}

void cleanup_module( void )
{
  flush_workqueue( my_wq );
  destroy_workqueue( my_wq );
  return;
}

Time in the kernel

Timers

Timer interrupts occur every 1/HZ of a second (= 1 jiffy)
HZ is configurable (in ‘Processor type and features’):
* 100,250(i368 default), 300 or 1000 (other architechtures)
* see kernel/Kconfig.hz
Global variable jiffies represents the number of tick since machine started. It increments with each timer interrupt
Read jiffies with get_jiffies_64 function
Convert to msec with jiffies_to_msecs or to microsecs with jiffies_to_usecs
Requires #include <linux/jiffies.h>

Timers
The kernel provides asynchronous timers
Run in atomic kernel
require #include <linux/timer.h>
Timers API
static: TIMER_INITIALIZER(_function,_expires,_data)
dynamic:
void init_timer(struct timer_list *timer)
void setup_timer( struct timer_list *timer,void (*function)(unsigned long),unsigned long data)
add new timer:
void add_timer (struct timer_list *timer)
remove timer:
void del_timer (struct timer_list *timer)

Timer simple program example

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/timer.h>

MODULE_LICENSE("GPL");

static struct timer_list my_timer;

void my_timer_callback( unsigned long data )
{
  printk( "my_timer_callback called (%ld).\n", jiffies );
}

int init_module( void )
{
  int ret;

  printk("Timer module installing\n");

  // my_timer.function, my_timer.data
  setup_timer( &my_timer, my_timer_callback, 0 );

  printk( "Starting timer to fire in 200ms (%ld)\n", jiffies );
  ret = mod_timer( &my_timer, jiffies + msecs_to_jiffies(200) );
  if (ret) printk("Error in mod_timer\n");

  return 0;
}

void cleanup_module( void )
{
  int ret;

  ret = del_timer( &my_timer );
  if (ret) printk("The timer is still in use...\n");

  printk("Timer module uninstalling\n");

  return;
}

High resolution timers

Allow timers in resolution of nano seconds
Depends on CONFIG_HIGH_RES_TIMERS
High resolution timers there two time bases (in oppose to jiffies on normal timers):
1. CLOCK_REALTIME: jiffies, same as normal timers
2. CLOCK_MONOTONIC: wide clock measuring the time in seconds and nanoseconds since system boot. Cannot be modified, so can be used for accurate time measurement.

High resolution timers example

#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/hrtimer.h>
#include <linux/ktime.h>

MODULE_LICENSE("GPL");

#define MS_TO_NS(x)     (x * 1E6L)

static struct hrtimer hr_timer;

enum hrtimer_restart my_hrtimer_callback( struct hrtimer *timer )
{
  ktime_t now;
  ktime_t delay = ktime_set(0, MS_TO_NS(200));
  printk( "my_hrtimer_callback called (%ld).\n", jiffies );
  now = ktime_get();
  hrtimer_forward(&hr_timer, now, delay);

  return HRTIMER_RESTART;
}

int init_module( void )
{
  ktime_t ktime;
  unsigned long delay_in_ms = 200L;

  printk("HR Timer module installing\n");

  ktime = ktime_set( 0, MS_TO_NS(delay_in_ms) );

  hrtimer_init( &hr_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL );

  hr_timer.function = &my_hrtimer_callback;

  printk( "Starting timer to fire in %ldms (%ld)\n", delay_in_ms, jiffies );

  hrtimer_start( &hr_timer, ktime, HRTIMER_MODE_REL );

  return 0;
}

void cleanup_module( void )
{
  int ret;

  ret = hrtimer_cancel( &hr_timer );
  if (ret) printk("The timer was still in use...\n");

  printk("HR Timer module uninstalling\n");

  return;
}

Leave a Reply

Your email address will not be published. Required fields are marked *