内核中的时间

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);
}

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

Linux 驱动框架---驱动中的时间相关的更多相关文章

  1. Linux 驱动框架---驱动中的阻塞

    描述和API 阻塞IO和非阻塞IO的应用编程时的处理机制是不同的,如果是非阻塞IO在访问资源未就绪时就直接返回-EAGAIN,反之阻塞IO则会使当前用户进程睡眠直到资源可用.从应用场景来说两种方式分别 ...

  2. Linux 驱动框架---驱动中的异步

    异步IO是对阻塞和轮询IO的机制补充,所谓异步IO就是在设备数据就绪时主动通知所属进程进行处理的机制.之所以说是异步是相对与被通知进程的,因为进程不知道也无法知道什么时候会被通知:这一机制非常类似于硬 ...

  3. Linux 驱动框架---驱动中的中断

    在单片机开发中中断就是执行过程中发生了一些事件需要及时处理,所以需要停止当前正在运行的处理的事情转而去执行中断服务函数,已完成必要的事件的处理.在Linux中断一样是如此使用但是基于常见的中断控制器的 ...

  4. Linux 驱动框架---驱动中的并发

    并发指多个执行单元被同时.并行的执行,而并发执行的单元对共享资源的访问就容易导致竟态.并发产生的情况分为抢占和并行(多核)和硬抢占(中断).Linux为解决这一问题增加了一系列的接口来解决并发导致的竟 ...

  5. 驱动框架入门——以LED为例[【转】

    本文转载自;http://blog.csdn.net/oqqHuTu12345678/article/details/72783903 以下内容源于朱有鹏<物联网大讲堂>课程的学习,如有侵 ...

  6. I2C驱动框架(四)

    参考:I2C子系统之platform_driver初始化——I2C_adap_s3c_init() 在完成platform_device的添加之后,i2c子系统将进行platform_driver的注 ...

  7. Linux驱动框架之framebuffer驱动框架

    1.什么是framebuffer? (1)framebuffer帧缓冲(一屏幕数据)(简称fb)是linux内核中虚拟出的一个设备,framebuffer向应用层提供一个统一标准接口的显示设备.帧缓冲 ...

  8. 基于Linux 3.0.8 Samsung FIMC(S5PV210) 的摄像头驱动框架解读(一)

    作者:咕唧咕唧liukun321 来自:http://blog.csdn.net/liukun321 FIMC这个名字应该是从S5PC1x0開始出现的.在s5pv210里面的定义是摄像头接口.可是它相 ...

  9. Linux Framebuffer驱动剖析之二—驱动框架、接口实现和使用

    深入分析LinuxFramebuffer子系统的驱动框架.接口实现和使用. 一.LinuxFramebuffer的软件需求 上一篇文章详细阐述了LinuxFramebuffer的软件需求(请先理解第一 ...

随机推荐

  1. 代码托管从业者 Git 指南

    本文作者:李博文 - CODING 后端开发工程师 前言 六七年前,我机缘巧合进入了代码托管行业,做过基于 Git 支持 SVN 客户端接入.Git 代码托管平台分布式.Git 代码托管读写分离.Gi ...

  2. jackson学习之二:jackson-core

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...

  3. 研发过程及工具支撑 DevOps 工具链集成

    https://mp.weixin.qq.com/s/NYm63nkCymIV3DbL4O01dg 腾讯重新定义敏捷 |Q推荐 小智 InfoQ 2020-09-03 敏捷开发奠基人 Robert C ...

  4. Go Concurrency Patterns: Context At Google, we require that Go programmers pass a Context parameter as the first argument to every function on the call path between incoming and outgoing requests.

    小结: 1. Background is the root of any Context tree; it is never canceled: 2.     https://blog.golang. ...

  5. 一、JavaScript实现AJAX(只需四步)

    -----------------------------------------------一.JavaScript实现AJAX(只需四步)----------------------------- ...

  6. C++ Primer Plus读书笔记(五)循环和关系表达式

    1.前缀运算符的优先级是从右到左,例如: 1 *++p; //这句话的含义是先对p进行++,再取* 2.循环 c++11新增了一种循环方式,有点和python类似,如下: 1 array<int ...

  7. windows命令行关闭IE代理

    打开:reg add "HKCU\Software\Microsoft\Windows\CurrentVersion\Internet Settings" /v ProxyEnab ...

  8. Java Object类 和 String类 常见问答 6k字+总结

    写在最前面 这个项目是从20年末就立好的 flag,经过几年的学习,回过头再去看很多知识点又有新的理解.所以趁着找实习的准备,结合以前的学习储备,创建一个主要针对应届生和初学者的 Java 开源知识项 ...

  9. pycharm创建文件夹以及查看源文件存放位置(FOR MAC)

    1.首先我们打开pycharm软件,一般首次打开界面如下 2.我们点击creat new project,点击后效果如下,此时创建相应的文件夹 3.创建完成后如下,并可以查看存放文件夹的位置 4.完成 ...

  10. React中组件间通信的方式

    React中组件间通信的方式 React中组件间通信包括父子组件.兄弟组件.隔代组件.非嵌套组件之间通信. Props props适用于父子组件的通信,props以单向数据流的形式可以很好的完成父子组 ...