Linux 驱动框架---驱动中的时间相关

内核中的时间       

        Linux 系统内核对于时间的管理依赖于硬件,硬件按一定的周期产生中断,周期由内核的一个配置值HZ决定在系统启动时会将定时器配置为HZ值指定的频率产生中断;同时内核维护一个64位(X86和X64都是64位)的计数器变量jiffies(jiffies_64)。在系统启动时这个值为0之后每次发生一次定时器中断这个值就会加1 ,所以他代表系统连续运行的嘀嗒次数。在内核中使用这个变量只需要引用头文件include<linux/jiffies.h>这个头文件又经常被linux/sched.h包含。

常用的比较两个jiffies时间的工具宏有

#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)

在用户空间表示时间常常用两个结构体来表示但是精度不同,头文件为#include <linux/time.h>。

表示秒和纳秒的

struct timespec {
time_t    tv_sec; /* seconds */
long    tv_nsec; /* nanoseconds */
};

表示秒和微妙

struct timeval {
time_t        tv_sec; /* seconds */
    suseconds_t    tv_usec; /* microseconds */
};

同时还提供了用户空间的时间和jiffies的相互转化的工具函数

unsigned long timespec_to_jiffies(struct timespec *value);
void jiffies_to_timespec(unsigned long jiffies, struct timespec *value);
unsigned long timeval_to_jiffies(struct timeval *value);
void jiffies_to_timeval(unsigned long jiffies, struct timeval *value);

这个接口也可以说明在用户空间struct timespec 和struct timeval 也是常常被用来表示相对时间而不是真实时间(壁钟时间)的。在32操作系统上直接访问jiffies_64 是无法保证原子性的,所以内核需要提供接口

#include <linux/jiffies.h>
u64 get_jiffies_64(void);

当需要测量的时间非常精确时只有硬件提供特殊的机制时才能实现否则,时间的精度会有上限。像X86的平台就提供了一个64bits的寄存器用来记录CPU的时钟级精度的时间刻度。除此之外内核内部还有一个全局的时间计数器xtime它是struct timeval 类型的变量,记录从标准时间基准1970-01-01 00:00:00到当前的相对秒数值。它由中断底半部分来维护,因为内核底半部执行时间具有不确定性所以同时内核还维护了一个xtim的更新时刻值他是和jiffies同类型的变量,每次跟新xtime时都把当前的jiffies赋值给他。

内核中的延时

 短延时

#include <linux/delay.h>
void ndelay(unsigned long nsecs);
void udelay(unsigned long usecs);
void mdelay(unsigned long msecs);

这三个延迟函数均是忙等待函数,在延时过程中无法运行其他任务。如果不浪费CPU的处理能力并能够容忍比所请求延迟更长的延迟,则应当使用schedule_timeout、sleep或者ssleep,这几个接口会发生任务切换。

void msleep(unsigned int msecs)
unsigned long msleep_interruptible(unsigned int msecs)
void ssleep(unsigned int seconds)

长延时

第一种实现方式

#define cpu_relax()            barrier()
while (time_before(jiffies, j1))
    cpu_relax();

这个方式是忙等待的,cpu_relax通常是什么都不做,如果在这之前禁用了抢占并关闭中断则系统内核就停止运行了,除了重启别无他法。这样的延时对系统性的负面影响非常大所以有了下面的版本。这时候系统执行其他处理或执行进程0进入低能耗。

while (time_before(jiffies, j1)) {
    schedule();

内核等待

这些接口都会导致当前线程睡眠。

#include <linux/wait.h>
//在一个队列上等待某些事件直到超时或事件发生
long wait_event_timeout(wait_queue_head_t q, condition, long timeout);
//在一个队列上等待某些事件直到超时或事件发生或者中断发生。
long wait_event_interruptible_timeout(wait_queue_head_t q, condition, long timeout);
/*这些函数在给定队列上睡眠, 但是它们在超时(以 jiffies 表示)到后返回。如果超时,函数返回 0;
所以我盟可以定义一个等待队列头,没有人往这个头上添加事件,则这个上面的函数就编程对应的单纯延时的函数。同时内核也提供了这种需
求的接口,以实现进程在超时到期时被唤醒而又不等待特定事件避免多余声明一个等待队列头。*/
#include <linux/sched.h>
signed long schedule_timeout(signed long timeout);
/*timeout 是要延时的 jiffies 数。除非这个函数在给定的 timeout 流失前返回,否则返回值是 0 。
schedule_timeout 要求调用者首先设置当前的进程状态。为获得一个不可中断的延迟, 可使用 TASK_UNINTERRUPTIBLE 代替。
如果你忘记改变当前进程的状态, 调用 schedule_time 如同调用 shcedule,建立一个不用的定时器。一个典型调用如下:*/
set_current_state(TASK_INTERRUPTIBLE);
schedule_timeout (delay);

 内核中的定时器

内核中用来描述一个定时器实例对象的数据结构定义如下,其中expires为到期时间,data为定时器处理接口传入的参数,function就是定时器到期时执行的处理接口,其余的接口都是内核管理定时器需要的辅助成员。

struct timer_list {
    /*
     * All fields that change during normal runtime grouped to the
     * same cacheline
     */
    struct list_head entry;
    unsigned long expires;
    struct tvec_base *base;

    void (*function)(unsigned long);
    unsigned long data;

    int slack;

#ifdef CONFIG_TIMER_STATS
    int start_pid;
    void *start_site;
    char start_comm[16];
#endif
#ifdef CONFIG_LOCKDEP
    struct lockdep_map lockdep_map;
#endif
};

常见的使用方法是静态定义或动态申请一个定时器数据结构,然后初始化定时器,之后就是将定时器添加到内核定时器管理的链表中,定时器就会在到期时间执行指定的处理接口。

初始化定时器

//初始化定时器 带标志设置
#define __TIMER_INITIALIZER(_function, _expires, _data, _flags) 
{ \
        .entry = { .prev = TIMER_ENTRY_STATIC },    \
        .function = (_function),            \
        .expires = (_expires),                \
        .data = (_data),                \
        .base = (void *)((unsigned long)&boot_tvec_bases + (_flags)), \
        .slack = -1,                    \
        __TIMER_LOCKDEP_MAP_INITIALIZER(        \
            __FILE__ ":" __stringify(__LINE__))    \
    }
//初始化定时器 不带flags设置
#define TIMER_INITIALIZER(_function, _expires, _data)        \
    __TIMER_INITIALIZER((_function), (_expires), (_data), 0)
//定义并初始化 带 TIMER_DEFERRABLE标识
#define TIMER_DEFERRED_INITIALIZER(_function, _expires, _data)    \
    __TIMER_INITIALIZER((_function), (_expires), (_data), TIMER_DEFERRABLE)
//定义并初始化 标志位全0
#define DEFINE_TIMER(_name, _function, _expires, _data)        \
    struct timer_list _name =                \
        TIMER_INITIALIZER(_function, _expires, _data)
#define init_timer(timer)                        \
    __init_timer((timer), 0)

#define __init_timer(_timer, _flags)                    \
    init_timer_key((_timer), (_flags), NULL, NULL)

void init_timer_key(struct timer_list *timer, unsigned int flags,
            const char *name, struct lock_class_key *key)
{
    debug_init(timer);
    do_init_timer(timer, flags, name, key);
}
#define setup_timer(timer, fn, data)                    \
    __setup_timer((timer), (fn), (data), 0)

添加定时器到内核

void add_timer(struct timer_list* timer);

定义好定时器只有加入到内核定时器管理链表上才能被内核定时器线程管理,从而在超时后执行。也有需求需要在定时器添加后删除定时器则只需要调用del_timer()接口即可 

int del_timer(struct timer_list * timer)

添加好之后又想修改超时时间可以吗,当然是可以的,如下

int mod_timer(struct timer_list* timer,unsigned long expires)

这就是Linux内核定时器的简单使用记录部分,后面了解一下定时器的标识flags参数的具体可取值和特性。

//从内核的处理过程来看这个标志在处理时没有进行任何特殊处理
#define
TIMER_DEFERRABLE 0x1LU // 中断安全,意味着执行这个定时器处理接口要关闭中断
#define TIMER_IRQSAFE 0x2LU //掩码 #define TIMER_FLAG_MASK 0x3LU

内核处理timer过程:

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);
            //处理TIMER_IRQSAFE
            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);
}

以上就是内核定时器的使用简单了解内容,具体的实现有时间了放在内核透视部分深入看看。

posted @ 2020-09-06 12:41  Little_Village  阅读(486)  评论(0编辑  收藏  举报