2009年12月27日 星期日

Linux Modules(7.2)- tasklet


Tasklet和timer類似(基本上都是運作在Softirqs上面),但是不同於timer會在特定時間執行,tasklet會在下一次interrupt來臨時執行。Tasklet有兩種implement,分別為TASKLET_SOFTIRQ和HI_SOFTIRQ,這兩種的差別在於HI_SOFTIRQ筆TASKLET_SOFTIRQ早執行。另外Tasklet只在註冊的CPU上面執行,而且註冊的tasklet同一時間只會被某個CPU執行。

您可以dynamically或statically的建立tasklet,
DECLARE_TASKLET(task, func, data);
DECLARE_TASKLET_DISABLED(task, func, data);

tasklet_init(task, func, data);

宣告後,還必須呼叫tasklet_schedule(task)才會被執行,但如果是用
DECLARE_TASKLET_DISABLED()宣告成disabled狀態,那就還必須用tasklet_enable()將其狀態設成enabled才能被執行。您也可以透過tasklet_disabled() disabled某個tasklet。tasklet_kill()可以保證tasklet不會被schedule,如果已經在執行,就會等它執行結束。

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

MODULE_LICENSE("GPL");

static void f(unsigned long name);

// create tasklet statically
static DECLARE_TASKLET(t1, f, (unsigned long)"t1");
static DECLARE_TASKLET_DISABLED(t2, f, (unsigned long)"t2");

static struct tasklet_struct *t3;

static void f(unsigned long name)
{
    printk("%s(): on cpu %d\n", (char*)name, smp_processor_id());
}

static void f3(unsigned long name)
{
    static u32 c = 0;
    tasklet_schedule(t3);
    if (!(c++ % 2000000)) { // 每隔2000000次呼叫就印出訊息
        printk("%s(): on cpu %d\n", (char*)name, smp_processor_id());
    }
}

static int __init init_modules(void)
{
    // create tasklet dynamically
    t3 = kzalloc(sizeof(struct tasklet_struct), GFP_KERNEL);
    tasklet_init(t3, f3, (unsigned long)"t3");

    tasklet_schedule(&t1);
    tasklet_schedule(&t2);
    tasklet_schedule(t3);
    tasklet_enable(&t2); // 沒有enable就不會被啟動
    return 0;
}

static void __exit exit_modules(void)
{
    // remove module就應該要確保tasklet有被移除
    tasklet_kill(&t1);
    tasklet_kill(&t2);
    tasklet_kill(t3);
}

module_init(init_modules);
module_exit(exit_modules);


Based on Kernel Version:2.6.35

參考資料:
Linux Kernel Development 3rd.
Linux Device Driver 3rd, http://www.makelinux.net/ldd3/chp-7-sect-5.shtml



Linux Kernel(7.1)- timer


有時候我們希望能在某個時間點執行某些動作,這時候便可以使用timer,在使用timer有些規矩必須被遵守。因為不是user-space來喚起,所以不允許存取user-space,current也就沒有意義。不能休眠,也不准schedule()或者任何有可能休眠的動作都不准。
struct timer_list {
 struct list_head entry;
 unsigned long expires;

 void (*function)(unsigned long);
 unsigned long data;

 struct tvec_base *base;
#ifdef CONFIG_TIMER_STATS
 void *start_site;
 char start_comm[16];
 int start_pid;
#endif
#ifdef CONFIG_LOCKDEP
 struct lockdep_map lockdep_map;
#endif
};

timer_list必須初始化之後才能使用,您可以選擇init_timer()或TIMER_INITIALIZER(),接著就可以設定expires/callback function/data(參數),並且使用add_timer()將其加入timer中,或者使用del_timer()移除pending中的timer,也可以使用mod_timer()修改或者重新設定timer。
#include <linux/init.h>
#include <linux/module.h>
#include <linux/timer.h>

MODULE_LICENSE("GPL");

struct timer_list brook_timer;
static void callback(unsigned long);
struct data {
    int count;
};
static struct data data;

static void callback(unsigned long data)
{
    struct data *dp = (struct data*) data;
    printk("%s(): %d\n", __FUNCTION__, dp->count++);
    mod_timer(&brook_timer, jiffies + 5 * HZ);
}

static int __init init_modules(void)
{
    init_timer(&brook_timer);
    brook_timer.expires = jiffies + 5 * HZ;
    brook_timer.function = &callback;
    brook_timer.data = (unsigned long) &data;
    add_timer(&brook_timer);
    return 0;
}

static void __exit exit_modules(void)
{
    del_timer(&brook_timer);
}

module_init(init_modules);
module_exit(exit_modules);


kernel timer最短的間隔是1個jiffies,而且會受到硬體中斷,和其他非同步事件的干擾,所以不適合非常精密的應用。

Linux Kernel(7)- timing


kernel會定期產生timer interrupt,HZ定義每秒產生timer interrupt的次數,定義在linux/param.h,根據平台的不同從50~1200不等。
而jiffies每當發生一次timer interrupt就會遞增一次,jiffies定義於linux/jiffies.h,所以簡單的說,jiffies就等於1/HZ,不管在64bit或32bit上的機器,Linux kernel都使用64位元版的jiffies_64,而jiffies其實是jiffies_64的低32位元版,除了讀取外,我們都不應該直接修改jiffies/jiffies_64。
kernel提供幾組macro來比較時間的先後,time_after()/timer_before()/time_after_eq()/time_before_eq()。
/*
 * These inlines deal with timer wrapping correctly. You are 
 * strongly encouraged to use them
 * 1. Because people otherwise forget
 * 2. Because if the timer wrap changes in future you won't have to
 *    alter your driver code.
 *
 * time_after(a,b) returns true if the time a is after time b.
 *
 * Do this with "<0" and ">=0" to only test the sign of the result. A
 * good compiler would generate better code (and a really good compiler
 * wouldn't care). Gcc is currently neither.
 */
#define time_after(a,b)  \
 (typecheck(unsigned long, a) && \
  typecheck(unsigned long, b) && \
  ((long)(b) - (long)(a) < 0))
#define time_before(a,b) time_after(b,a)

#define time_after_eq(a,b) \
 (typecheck(unsigned long, a) && \
  typecheck(unsigned long, b) && \
  ((long)(a) - (long)(b) >= 0))
#define time_before_eq(a,b) time_after_eq(b,a)

另外,kernel中有兩種時間的structure,struct timeval和struct timespec。
#ifndef _STRUCT_TIMESPEC
#define _STRUCT_TIMESPEC
struct timespec {
 __kernel_time_t tv_sec;   /* seconds */
 long  tv_nsec;  /* nanoseconds */
};
#endif

struct timeval {
 __kernel_time_t  tv_sec;  /* seconds */
 __kernel_suseconds_t tv_usec; /* microseconds */
};
早期以timeval為主,後來因為精密度的需求,有了timespec的誕生。kernel也提供了和jiffies的轉換函數。更多的轉換可以參考linux/jiffies.h
unsigned long timespec_to_jiffies(const struct timespec *value);
void jiffies_to_timespec(const unsigned long jiffies,
    struct timespec *value);
unsigned long timeval_to_jiffies(const struct timeval *value);
void jiffies_to_timeval(const unsigned long jiffies,
          struct timeval *value);


熱門文章