2013年8月11日 星期日

Linux Kernel Timers

Kernel依據硬體所提供的不同timer來提供時間相關的服務,例如busy-waiting與sleep-waiting。如果process使用busy-waiting延時等待會浪費掉CPU的時間,反之sleep-waiting可以讓process在等待的這段時間進入睡眠,並交出CPU讓其他的process使用,等時間到了,這process會被叫醒繼續執行。首先,先瞭解一些重要的Kernel Timers變數含義,如jiffies, HZ與xtime。

HZ:
每隔固定週期Linux Kernel會發出timer interrupt,HZ定義每一秒有幾次timer interrupts,而HZ變數是儲存在Kernel變數中,在編譯Kernel之前,可以透過make menuconfig設定此值,設定路徑Processor type and features → Timer frequency (250 HZ):
如果適當調高HZ值,可以提升系統performance,讓timer interrupts次數變高,相對tasks之間的切換頻率也會變高,對於server而言,在回應clients時間會縮短。但如果HZ值設定太大,會導致系統資源與電源消耗,因為更多的CPU cycles會耗在timer interrupts。可以透過下列指令,檢查系統上的HZ值:
cat /boot/config-`uname -r` | grep '^CONFIG_HZ='
Jiffies:
用來紀錄系統自開幾以來,已經過多少的tick(jiffy),每發生一次timer interrupt,Jiffies變數會被加一。

底下是一個範例,透過Linux timer再搭配jffies與HZ使用,讓console每五秒印一次訊息。
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/timer.h>
#include <linux/jiffies.h>

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Renee Ciou");
MODULE_DESCRIPTION("Timer Module");
MODULE_ALIAS("timer module");

struct timer_list timer;
unsigned long ticks, count;

void timer_func(unsigned long data)
{
    mod_timer(&timer, jiffies + (5 * HZ)); /* 修改或者重新設定timer */
    printk("Timer rescheduled %lu times in %lu seconds\n",
        count++, (jiffies - ticks)/HZ);
}


int timer_module_init(void)
{
    init_timer(&timer); /* 初始化timer struct */
    timer.data = 10; /* 丟給time_func的參數 */
    timer.expires = jiffies + (5 * HZ); /* 5秒後timeout */
    timer.function = timer_func; /* 計時5秒timeout後執行time_func */
    add_timer(&timer); /* 啟動timer */
    ticks = jiffies; /* 紀錄當前的jiffies */

    printk("Timer module loaded");
    return 0;
}


void timer_module_exit(void)
{
    del_timer(&timer); /* 移除timer */
    printk("Timer module unloaded");
}

module_init(timer_module_init);
module_exit(timer_module_exit);
如果要在x86平台上執行,請用下面的Makefile:
ifneq ($(KERNELRELEASE),)

obj-m := timer.o

else

KDIR := /lib/modules/$(shell uname -r)/build
all:
        make -C $(KDIR) M=$(PWD) modules
clean:
        rm -f *.ko *.o *.mod.o *.mod.c *.symvers

endif
如果要在ARM平台上執行,請用下面的Makefile:
ifneq ($(KERNELRELEASE),)

obj-m := timer.o

else

KDIR := /lib/modules/$(shell uname -r)/build
all:
        make -C $(KDIR) M=$(PWD) modules ARCH=arm CROSS_COMPILE=arm-linux-
clean:
        rm -f *.ko *.o *.mod.o *.mod.c *.symvers

endif
Long Delays
在kernel的世界中,透過jiffies達成的時間延遲,通常被視為long delays。long delays可以用兩種方式實現,分別為busy-looping與sleep-waiting,這兩種方式延遲的優缺點,前面已經解釋過了。底下的範例,3, 4行code實現1秒busy-looping的long delays:
void timer_func(unsigned long data)
{
    unsigned long timeout = jiffies + HZ;
    while (time_before(jiffies, timeout)) continue;
    mod_timer(&timer, jiffies + (5 * HZ));
    printk("Timer rescheduled %lu times in %lu seconds\n",
        count++, (jiffies - ticks)/HZ);
}
上面透過busy-looping實現的long delays會浪費掉1秒CPU時間,不是很好的方式。不過,下面的範例,3, 4行code則是由sleep-waiting實現1秒的long delays,程式在等待過程中,會將CPU讓給其他的process使用。
void timer_func(unsigned long data)
{
    unsigned long timeout = HZ;
    schedule_timeout(timeout);
    mod_timer(&timer, jiffies + (5 * HZ));
    printk("Timer rescheduled %lu times in %lu seconds\n",
        count++, (jiffies - ticks)/HZ);
}
Short Delays
在kernel世界中,小於tick(jiffy)的時間延遲,被視為short delays。這種延遲普遍用於process與interrupt執行環境,但是無法基於jiffy實現此種極短且精準的延遲,由於jiffy單位不夠小。此外,sleep-waiting也不能用於short delays,由於interrupt handlers不允許被shedule或進入sleep,所以short delays只能採用busy-looping方式實現。在kernel API中,已經實現short delays,分別提供 mdelay(), udelay(), 和 ndelay() 這三個函式來達到極短且精準的延遲,而這三個函式是透過loops_per_jiffy實現,去計算需要多少個loop operation來得到精確的delay。

沒有留言:

張貼留言