節拍
系統定時器以某種頻率自行觸發,這個頻率是靜态預處理定義的的,稱為節拍率,也稱為HZ(赫茲)。
HZ值在不同的體系結構不同。
x86體系結構中HZ預設值是100(include\asm-generic\param.h),是以x86上時鐘中斷的頻率就是100Hz,即每秒觸發100次中斷。
#ifndef HZ
#define HZ 100
#endif
jiffies用來記錄自系統啟動以來産生的節拍的綜述(include\linux\jiffies.h),還有個64位的版本jiffies_64。
extern u64 __jiffy_data jiffies_64;
extern unsigned long volatile __jiffy_data jiffies;
jiffies一秒内增加的值就是HZ。
jiffies是32位的,是以可能存在溢出的情況。
通過幾個宏可以正确處理溢出導緻的回繞:
#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)
硬時鐘和定時器
實時時鐘(RTC)用來持久存放系統時間。
核心通過讀取RTC來初始化牆上時間,該時間存放在xtime變量(include\linux\time.h):
#ifndef _STRUCT_TIMESPEC
#define _STRUCT_TIMESPEC
struct timespec {
__kernel_time_t tv_sec; /* seconds */
long tv_nsec; /* nanoseconds */
};
#endif
extern struct timespec xtime;
tv_sec以秒位機關,存放着自19700101以來經過的時間;
tv_nsec記錄自上一秒開始經過的ns數;
x86會周期性地将目前時間值存回RTC。
系統定時器提供一種周期性觸發中斷的機制。
x86下還有APIC時鐘和時間戳計數(TSC)。
定時器是管理核心流逝時間的基礎。
定時器并不是周期性執行,它在逾時後自行撤銷。
定時器有結構體time_list表示:
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
};
定時器定義:
#define DEFINE_TIMER(_name, _function, _expires, _data) \
struct timer_list _name = \
TIMER_INITIALIZER(_function, _expires, _data)
定時器初始化:
#define init_timer(timer) \
do { \
static struct lock_class_key __key; \
init_timer_key((timer), #timer, &__key); \
} while (0)
#define init_timer(timer)\
init_timer_key((timer), NULL, NULL)
激活定時器:
extern void add_timer(struct timer_list *timer);
更改定時器:
extern int mod_timer(struct timer_list *timer, unsigned long expires);
停止定時器:
extern int del_timer(struct timer_list * timer);
對于多處理,定制定時器最好使用如下的:
#ifdef CONFIG_SMP
extern int del_timer_sync(struct timer_list *timer);
核心在時鐘中斷發生之後執行定時器,定時器作為軟中斷(TIMER_SOFTIRQ)在下半部上下文中執行。
時鐘中斷處理程式
時鐘中斷處理程式分為兩個部分,一部分與體系架構有關,另一部分與體系架構無關。
前一部分:
- 獲得xtime_lock鎖(是一個seq鎖),以便對方為jiffies_64和牆上時間xtime進行保護;
- 需要時應答或重新設定系統時鐘;
- 周期性地使用牆上時間更新實時時鐘;
- 調用體系架構無關的時鐘例程tick_periodic();
後一部分就是tick_periodic():
static void tick_periodic(int cpu)
{
if (tick_do_timer_cpu == cpu) {
write_seqlock(&xtime_lock);
/* Keep track of the next tick event */
tick_next_period = ktime_add(tick_next_period, tick_period);
do_timer(1);
write_sequnlock(&xtime_lock);
}
update_process_times(user_mode(get_irq_regs()));
profile_tick(CPU_PROFILING);
}
大緻的操作:
- 給jiffies_64變量增加1;
- 更新資源消耗的統計值;
- 執行已經到期的動态定時器;
- 執行sheduler_tick()函數;
- 更新牆上時間,該時間存放在xtime變量中;
- 計算平均負載值;
延時執行
延時有很多種方法:
忙等待:
static void
fore200e_spin(int msecs)
{
unsigned long timeout = jiffies + msecs_to_jiffies(msecs);
while (time_before(jiffies, timeout));
}
代碼等待時允許核心重新排程更重要的任務:
for (;;) {
smcr = ioread16(dev->base + SMCR);
/*
* Don't bother checking ACKE here, this and the reset
* are handled in highlander_i2c_wait_xfer_done() when
* waiting for the ACK.
*/
if (smcr & SMCR_IRIC)
return;
if (time_after(jiffies, timeout))
break;
cpu_relax();
cond_resched();
}
cond_resched()函數将排程一個新程式投入運作。它不能在中斷上下文中使用,隻能在程序上下文中使用。
短延時:
udelay(100);
睡眠到指定時間:
set_current_state(TASK_INTERRUPTIBLE);
schedule_timeout(HZ);
該方法不能保證睡眠時間正好等于指定的延時時間,隻能盡量接近。