1. Linux hrtimer的實現方案
Linux hrtimer的實現是依賴硬體(通過可程式化定時器來實現)的支援的,而且此定時器有自己的專用寄存器, 硬中斷和頻率。比如我的板子上的對應參數如下:
Timer at Vir:0xE0100200 = Phy:0xE0100200, using Irq:27, at Freq:250000000,由此可見,其頻率為250MHz,所以其精度為:1/250000000=4ns,比系統時鐘jiffy(HZ=100,精度為10ms)的精度高得太多了。可是支援此高精度timer是需要付出硬體成本的。即它是一個硬體時鐘。這裡所說的硬體時鐘特指的是硬體計時器時鐘。
2. 硬體時鐘 資料結構
和硬體計時器(本文又稱作硬體時鐘,區別於軟體時鐘)相關的資料結構主要有兩個:
struct clocksource :對硬體裝置的抽象,描述時鐘源資訊
struct clocksource {/* * First part of structure is read mostly */char *name;struct list_head list;int rating;cycle_t (*read)(struct clocksource *cs);int (*enable)(struct clocksource *cs);void (*disable)(struct clocksource *cs);cycle_t mask;u32 mult;u32 shift;u64 max_idle_ns;unsigned long flags;cycle_t (*vread)(void);void (*suspend)(struct clocksource *cs);void (*resume)(struct clocksource *cs);#ifdef CONFIG_IA64void *fsys_mmio; /* used by fsyscall asm code */#define CLKSRC_FSYS_MMIO_SET(mmio, addr) ((mmio) = (addr))#else#define CLKSRC_FSYS_MMIO_SET(mmio, addr) do { } while (0)#endif/* * Second part is written at each timer interrupt * Keep it in a different cache line to dirty no * more than one cache line. */cycle_t cycle_last ____cacheline_aligned_in_smp;#ifdef CONFIG_CLOCKSOURCE_WATCHDOG/* Watchdog related data, used by the framework */struct list_head wd_list;cycle_t wd_last;#endif};
struct clock_event_device :時鐘的事件資訊,包括當硬體時鐘中斷髮生時要執行那些操作(實際上儲存了相應函數的指標)。本文將該結構稱作為“時鐘事件裝置”。
/** * struct clock_event_device - clock event device descriptor * @name:ptr to clock event name * @features:features * @max_delta_ns:maximum delta value in ns * @min_delta_ns:minimum delta value in ns * @mult:nanosecond to cycles multiplier * @shift:nanoseconds to cycles divisor (power of two) * @rating:variable to rate clock event devices * @irq:IRQ number (only for non CPU local devices) * @cpumask:cpumask to indicate for which CPUs this device works * @set_next_event:set next event function * @set_mode:set mode function * @event_handler:Assigned by the framework to be called by the low *level handler of the event source * @broadcast:function to broadcast events * @list:list head for the management code * @mode:operating mode assigned by the management code * @next_event:local storage for the next event in oneshot mode * @retries:number of forced programming retries */struct clock_event_device {const char*name;unsigned intfeatures;u64max_delta_ns;u64min_delta_ns;u32mult;u32shift;intrating;intirq;const struct cpumask*cpumask;int(*set_next_event)(unsigned long evt, struct clock_event_device *);void(*set_mode)(enum clock_event_mode mode, struct clock_event_device *);void(*event_handler)(struct clock_event_device *);void(*broadcast)(const struct cpumask *mask);struct list_headlist;enum clock_event_modemode;ktime_tnext_event;unsigned longretries;};
上述兩個結構核心原始碼中有較詳細的註解,分別位於檔案 clocksource.h 和 clockchips.h 中。需要特別注意的是結構 clock_event_device 的成員 event_handler ,它指定了當硬體時鐘中斷髮生時,核心應該執行那些操作,也就是真正的時鐘中斷處理函數。
Linux 核心維護了兩個鏈表,分別儲存了系統中所有時鐘源的資訊和時鐘事件裝置的資訊。這兩個鏈表的表頭在核心中分別是 clocksource_list 和 clockevent_devices 。
3. hrtimer是如何?的呢?
下文就為之一一描述。
3.1 初始化hrtimer硬體定時器
3.1.1 設定硬體中斷
前面已經看到,它有一個硬體中斷,為了使此硬體中斷能正常工作,肯定需要設定一個硬體中斷,其參考代碼如下:
static unsigned long my_timer_irqnbr = 25; //硬體中斷號static struct irqaction my_timer_irqaction = {.name= "My HrTimer",.flags= IRQF_DISABLED | IRQF_TIMER | IRQF_IRQPOLL,.handler= my_timer_interrupt_handler, //中斷處理函數};setup_irq(my_timer_irqnbr, &my_timer_irqaction);
設定中斷之後,中斷處理函數也有了。
3.1.2 初始化硬體時鐘相關寄存器並註冊此硬體時鐘到系統中
static struct clocksource myclocksource = {.name= "my_hrtimer_src",.rating = 300,.read= my_get_cycles, //讀取COUNT寄存器以擷取cycle value.mask= CLOCKSOURCE_MASK(64),.flags= CLOCK_SOURCE_IS_CONTINUOUS,};static void __init my_clocksource_init(void){unsigned long ctrl = 0;unsigned long count = (my_timer_freq / HZ); ...writel(count, my_timer_vaddr + MY_TIMER_COMPARATOR_LOW);writel(count, my_timer_vaddr + MY_TIMER_AUTO_INCREMENT);ctrl = (MY_TIMER_CTRL_IRQ_ENA | MY_TIMER_CTRL_COMP_ENA | MY_TIMER_CTRL_TIMER_ENA | MY_TIMER_CTRL_AUTO_INC);writel(ctrl, my_timer_vaddr + MY_TIMER_CONTROL); ...clocksource_calc_mult_shift(&myclocksource, my_timer_freq, 4); //向系統註冊我的硬體時鐘,即把它加入clocksource_listclocksource_register(&myclocksource);}
3.1.3 初始化時鐘事件裝置並註冊到系統中
static struct clock_event_device myclockevent = {.name= "my_timer_evt",.features= CLOCK_EVT_FEAT_PERIODIC,.set_mode= my_set_mode, //通過寫寄存器設定clock_event_mode.set_next_event= my_set_next_event, // 通過寫寄存器寫下一個事件.rating= 300,.cpumask= cpu_all_mask,};static void __init my_clockevents_init(unsigned int timer_irq){ myclockevent.irq = timer_irq; clockevents_calc_mult_shift(&myclockevent, my_timer_freq, 4); myclockevent.max_delta_ns = clockevent_delta2ns(0xffffffff, &myclockevent); myclockevent.min_delta_ns = clockevent_delta2ns(0xf, &myclockevent); //註冊我的時鐘事件裝置,即把它加入clockevent_devices鏈表 clockevents_register_device(&myclockevent);}
3.2 硬體中處理函數my_timer_interrupt_handler
static irqreturn_t my_timer_interrupt_handler(int irq, void *dev_id){struct clock_event_device *evt = &myclockevent;/* clear the interrupt */writel(value, register_addr);evt->event_handler(evt);return IRQ_HANDLED;}
硬體中斷處理函數很簡單,它直接調用clockevent的event_handler函數。前面的初始化中並沒有初始化此event_handler,很顯然是在使用過程中進行動態初始化的。下面看看hrtimer中是如何初始化此event_handler的。
4. hrtimer如何初始化clock_event_device的event_handler?
hrtimer的中斷處理函數,很自然地想到了hrtimer_interrupt,哪這個東東與clock_event_device有關係嗎?
此非強制中斷TIMER_SOFTIRQ在run_local_timers函數中通過調用raise_softirq(TIMER_SOFTIRQ);來觸發。(注:raise_softirq->raise_softirq_irqoff->__raise_softirq_irqoff)
init_timers(中調用open_softirq(TIMER_SOFTIRQ, run_timer_softirq);)
run_timer_softirq->
hrtimer_run_pending(Called from timer softirq every jiffy, expire hrtimers,check如果hrtimer_hres_enabled is on<=1>,則執行下面的代碼切換到高精度模式)->
hrtimer_switch_to_hres->
tick_init_highres->
tick_switch_to_oneshot(hrtimer_interrupt)
<把hrtimer_interrupt賦值給dev->event_handler,即dev->event_handler = handler;>
看到沒有?在每一次時鐘非強制中斷處理函數中,都會嘗試把hrtimer切換到高精度模式,如果滿足條件,就切換,切換之後高精度模式就被啟用了,在hrtimer_run_pending檢查是否被啟用,如果被啟用了,下面的代碼就不用執行了。
5. hrtimer高精度模式下真正的中斷處理函數
hrtimer_interrupt
6. hrtimer高精度式的觸發過程
以下以nanosleep為例:
SYSCALL_DEFINE2(nanosleep, struct timespec __user *, rqtp, struct timespec __user *, rmtp)->
hrtimer_nanosleep->
do_nanosleep->
hrtimer_start_expires->
hrtimer_start_range_ns->
__hrtimer_start_range_ns->
enqueue_hrtimer(insert into rb_tree) then hrtimer_enqueue_reprogram-> hrtimer_reprogram->
tick_program_event->
tick_dev_program_event->
clockevents_program_event->
dev->set_next_event((unsigned long) clc, dev)<調用my clock_event_device的set_next_event方法設定register>