概述
尝试了一下使用local_irq_disable好像不能将时钟中断屏蔽,不知道搞错没有
时钟中断上半部(rm vexpress,定时器SP804)
void __init __sp804_clockevents_init(void __iomem *base, unsigned int irq, struct clk *clk, const char *name)
{
struct clock_event_device *evt = &sp804_clockevent;
long rate;
if (!clk)
clk = clk_get_sys("sp804", name);
if (IS_ERR(clk)) {
pr_err("sp804: %s clock not found: %dn", name,
(int)PTR_ERR(clk));
return;
}
rate = sp804_get_clock_rate(clk);
if (rate < 0)
return;
clkevt_base = base;
clkevt_reload = DIV_ROUND_CLOSEST(rate, HZ);
evt->name = name;
evt->irq = irq;
evt->cpumask = cpu_possible_mask;
writel(0, base + TIMER_CTRL);
setup_irq(irq, &sp804_timer_irq);
clockevents_config_and_register(evt, rate, 0xf, 0xffffffff);
}
可以看到时钟中断的注册的处理函数是sp804_timer_interrupt
setup_irq(irq, &sp804_timer_irq);
static struct irqaction sp804_timer_irq = {
.name = "timer",
.flags = IRQF_TIMER | IRQF_IRQPOLL,
.handler = sp804_timer_interrupt,
.dev_id = &sp804_clockevent,
};
int x = 1;
static irqreturn_t sp804_timer_interrupt(int irq, void *dev_id)
{
struct clock_event_device *evt = dev_id;
/* clear the interrupt */
writel(1, clkevt_base + TIMER_INTCLR);
evt->event_handler(evt);
if (x == 1)
{
x = 0;
printk(KERN_EMERG "evt->event_handler %pn",evt->event_handler);
}
return IRQ_HANDLED;
}
evt->event_handler=tick_handle_periodic
void tick_handle_periodic(struct clock_event_device *dev)
{
int cpu = smp_processor_id();
/* 获取时钟中断下次执行的时间 */
ktime_t next = dev->next_event;
tick_periodic(cpu);
if (dev->mode != CLOCK_EVT_MODE_ONESHOT)
return;
for (;;) {
/*
* Setup the next period for devices, which do not have
* periodic mode:
*/
/* 计算下一次触发时间 */
next = ktime_add(next, tick_period);
/* 设置下一次触发时间, 0表示成功 */
if (!clockevents_program_event(dev, next, false))
return;
/*
* Have to be careful here. If we're in oneshot mode,
* before we call tick_periodic() in a loop, we need
* to be sure we're using a real hardware clocksource.
* Otherwise we could get trapped in an infinite
* loop, as the tick_periodic() increments jiffies,
* which then will increment time, possibly causing
* the loop to trigger again and again.
*/
if (timekeeping_valid_for_hres())
tick_periodic(cpu);
}
}
tick_handle_periodic-->tick_periodic
static void tick_periodic(int cpu)
{
/* 当前CPU负责更新时间 */
if (tick_do_timer_cpu == cpu) {
write_seqlock(&jiffies_lock);
/* Keep track of the next tick event */
tick_next_period = ktime_add(tick_next_period, tick_period);
do_timer(1);/* 更新jiffies_64+=1 */
write_sequnlock(&jiffies_lock);
/* 内核维护了另外一个wall time时间:xtime,
取决于用于对xtime计时的clocksource,它的精度甚至可以达到纳秒级别
因为xtime实际上是一个内存中的变量,它的访问速度非常快,
内核大部分时间都是使用xtime来获得当前时间信息。
xtime记录的是自1970年1月1日24时到当前时刻所经历的纳秒数。 */
update_wall_time();/* 更新墙上时间 */
}
/* 更新进程信息 */
update_process_times(user_mode(get_irq_regs()));
profile_tick(CPU_PROFILING);
}
void update_process_times(int user_tick)
{
struct task_struct *p = current;
int cpu = smp_processor_id();
/* Note: this timer irq context must be accounted for as well. */
/* */
/* 更新当前进程内核态和用户态的占用率 */
account_process_tick(p, user_tick);
/* 检查有没有定时器到期,有就运行到期定时器的处理 */
run_local_timers();
rcu_check_callbacks(cpu, user_tick);
#ifdef CONFIG_IRQ_WORK
if (in_irq())
irq_work_run();
#endif
/* 调度器的tick */
scheduler_tick();
run_posix_cpu_timers(p);
}
run_local_timer会主动触发软中断,将本cpu的软中断状态寄存器对应的bit为置上,然后在中断上半部退出irq_exit时,就可以去执行软中断,自然也能执行到定时器的软中断
void run_local_timers(void)
{
hrtimer_run_queues();
raise_softirq(TIMER_SOFTIRQ);
}
时钟中断下半部
void __init init_timers(void)
{
int err;
/* ensure there are enough low bits for flags in timer->base pointer */
BUILD_BUG_ON(__alignof__(struct tvec_base) & TIMER_FLAG_MASK);
err = timer_cpu_notify(&timers_nb, (unsigned long)CPU_UP_PREPARE,
(void *)(long)smp_processor_id());
BUG_ON(err != NOTIFY_OK);
init_timer_stats();
register_cpu_notifier(&timers_nb);
open_softirq(TIMER_SOFTIRQ, run_timer_softirq);
}
可以看到针对时钟中断的中断下半部是一个软中断,对应的处理函数是run_timer_softirq
open_softirq(TIMER_SOFTIRQ, run_timer_softirq);
static void run_timer_softirq(struct softirq_action *h)
{
struct tvec_base *base = __this_cpu_read(tvec_bases);
hrtimer_run_pending();/* 高精度定时器的处理 */
/* jiffies大于等于本cpu的timer_jiffies说明有软件时钟到期了 */
if (time_after_eq(jiffies, base->timer_jiffies))
__run_timers(base);
}
static inline void __run_timers(struct tvec_base *base)
{
struct timer_list *timer;
spin_lock_irq(&base->lock);
if (catchup_timer_jiffies(base)) {
spin_unlock_irq(&base->lock);
return;
}
while (time_after_eq(jiffies, base->timer_jiffies)) {
struct list_head work_list;
struct list_head *head = &work_list;
int index = base->timer_jiffies & TVR_MASK;
/*
* Cascade timers:
*/
if (!index &&
(!cascade(base, &base->tv2, INDEX(0))) &&
(!cascade(base, &base->tv3, INDEX(1))) &&
!cascade(base, &base->tv4, INDEX(2)))
cascade(base, &base->tv5, INDEX(3));
++base->timer_jiffies;
list_replace_init(base->tv1.vec + index, head);
while (!list_empty(head)) {
void (*fn)(unsigned long);
unsigned long data;
bool irqsafe;
timer = list_first_entry(head, struct timer_list,entry);
fn = timer->function;
data = timer->data;
irqsafe = tbase_get_irqsafe(timer->base);
timer_stats_account_timer(timer);
base->running_timer = timer;
detach_expired_timer(timer, base);
if (irqsafe) {
spin_unlock(&base->lock);
call_timer_fn(timer, fn, data);
spin_lock(&base->lock);
} else {
spin_unlock_irq(&base->lock);
call_timer_fn(timer, fn, data);
spin_lock_irq(&base->lock);
}
}
}
base->running_timer = NULL;
spin_unlock_irq(&base->lock);
}
1、看这样,定时器设置的回调函数是运行在中断上下文的
2、在调用定时器回调函数前使用了detach_expired_timer将定时器从tvec_base中删除。所以如果需要定时器被再次调用,则需要使用add_timer/mod_timer将定时器重新加进去才行
3、此外可以看到定时器是基于软中断实现的,同样也存在延时。即定时器回调函数被调用的时候,存在jiffies超过设置的超时时间的情况
detach_expired_timer(timer, base);
if (irqsafe) {
spin_unlock(&base->lock);
call_timer_fn(timer, fn, data);
spin_lock(&base->lock);
} else {
spin_unlock_irq(&base->lock);
call_timer_fn(timer, fn, data);
spin_lock_irq(&base->lock);
}
static void call_timer_fn(struct timer_list *timer, void (*fn)(unsigned long),
unsigned long data)
{
............
fn(data);/* 调用定时器设置的回调函数 */
trace_timer_expire_exit(timer);
lock_map_release(&lockdep_map);
if (count != preempt_count()) {
WARN_ONCE(1, "timer: %pF preempt leak: %08x -> %08xn",
fn, count, preempt_count());
/*
* Restore the preempt count. That gives us a decent
* chance to survive and extract information. If the
* callback kept a lock held, bad luck, but not worse
* than the BUG() we had.
*/
preempt_count_set(count);
}
}
定时器的回调函数运行在中断上下文验证:
struct timer_list mytimer;
void myCallback(void)
{
printk(KERN_EMERG "rnfunc %s, line %d in_interrupt() %lun", __FUNCTION__, __LINE__, in_interrupt());
msleep(10);
}
static int smsc911x_init(struct net_device *dev)
{
.................
init_timer(&mytimer);
mytimer.function = myCallback; /* 定时器处理函数*/
mytimer.expires = jiffies + msecs_to_jiffies(10000); /* 超时时间为2s */
mytimer.data = NULL; /* 自己的数据 */
add_timer(&mytimer); /* 启动定时器 */
}
在回调函数中调用了msleep进行休眠。会出现原子上下文不允许调度的错误
最后
以上就是慈祥唇彩为你收集整理的linux时钟中断的全部内容,希望文章能够帮你解决linux时钟中断所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
发表评论 取消回复