节拍
系统定时器以某种频率自行触发,这个频率是静态预处理定义的的,称为节拍率,也称为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);
该方法不能保证睡眠时间正好等于指定的延时时间,只能尽量接近。