《Linux内核设计与实现》读书笔记——定时器和时间管理

节拍

系统定时器以某种频率自行触发,这个频率是静态预处理定义的的,称为节拍率,也称为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);

该方法不能保证睡眠时间正好等于指定的延时时间,只能尽量接近。

 

猜你喜欢

转载自blog.csdn.net/jiangwei0512/article/details/106149746
今日推荐