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);
}
添加定时器到内核
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 驱动框架---驱动中的时间相关的更多相关文章
- Linux 驱动框架---驱动中的阻塞
描述和API 阻塞IO和非阻塞IO的应用编程时的处理机制是不同的,如果是非阻塞IO在访问资源未就绪时就直接返回-EAGAIN,反之阻塞IO则会使当前用户进程睡眠直到资源可用.从应用场景来说两种方式分别 ...
- Linux 驱动框架---驱动中的异步
异步IO是对阻塞和轮询IO的机制补充,所谓异步IO就是在设备数据就绪时主动通知所属进程进行处理的机制.之所以说是异步是相对与被通知进程的,因为进程不知道也无法知道什么时候会被通知:这一机制非常类似于硬 ...
- Linux 驱动框架---驱动中的中断
在单片机开发中中断就是执行过程中发生了一些事件需要及时处理,所以需要停止当前正在运行的处理的事情转而去执行中断服务函数,已完成必要的事件的处理.在Linux中断一样是如此使用但是基于常见的中断控制器的 ...
- Linux 驱动框架---驱动中的并发
并发指多个执行单元被同时.并行的执行,而并发执行的单元对共享资源的访问就容易导致竟态.并发产生的情况分为抢占和并行(多核)和硬抢占(中断).Linux为解决这一问题增加了一系列的接口来解决并发导致的竟 ...
- 驱动框架入门——以LED为例[【转】
本文转载自;http://blog.csdn.net/oqqHuTu12345678/article/details/72783903 以下内容源于朱有鹏<物联网大讲堂>课程的学习,如有侵 ...
- I2C驱动框架(四)
参考:I2C子系统之platform_driver初始化——I2C_adap_s3c_init() 在完成platform_device的添加之后,i2c子系统将进行platform_driver的注 ...
- Linux驱动框架之framebuffer驱动框架
1.什么是framebuffer? (1)framebuffer帧缓冲(一屏幕数据)(简称fb)是linux内核中虚拟出的一个设备,framebuffer向应用层提供一个统一标准接口的显示设备.帧缓冲 ...
- 基于Linux 3.0.8 Samsung FIMC(S5PV210) 的摄像头驱动框架解读(一)
作者:咕唧咕唧liukun321 来自:http://blog.csdn.net/liukun321 FIMC这个名字应该是从S5PC1x0開始出现的.在s5pv210里面的定义是摄像头接口.可是它相 ...
- Linux Framebuffer驱动剖析之二—驱动框架、接口实现和使用
深入分析LinuxFramebuffer子系统的驱动框架.接口实现和使用. 一.LinuxFramebuffer的软件需求 上一篇文章详细阐述了LinuxFramebuffer的软件需求(请先理解第一 ...
随机推荐
- 30分钟带你理解 Raft 算法
为什么需要 Raft? Raft 是什么? Raft 的目标 前置条件:复制状态机 Raft 基础 Leader 选举(选举安全特性) 日志复制(Leader只附加.日志匹配) 安全 学习资料 使用 ...
- Docker数据目录迁移解决方案
场景 在docker的使用中随着下载镜像越来越多,构建镜像.运行容器越来越多, 数据目录必然会逐渐增大:当所有docker镜像.容器对磁盘的使用达到上限时,就需要对数据目录进行迁移. 如何避免: 1. ...
- 订单业务楼层化 view管理器和model管理器进行了model和view的全面封装处理 三端不得不在每个业务入口上线时约定好降级开关,于是代码中充满了各种各样的降级开关字段
京东APP订单业务楼层化技术实践解密 原创 杜丹 留成 博侃 京东零售技术 2020-09-29 https://mp.weixin.qq.com/s/2oExMjh70Kyveiwh8wOBVA 用 ...
- tcpdump 参数详解及使用案例
参数 -A 以ASCII码方式显示每一个数据包(不会显示数据包中链路层头部信息). 在抓取包含网页数据的数据包时, 可方便查看数据(nt: 即Handy for capturing web pages ...
- 有趣的css—隐藏元素的7种思路
css隐藏元素的7种思路 前言 display.visibility.opacity三个属性隐藏元素之间的异同点一直是前端面试面试的常考题. 属性 值 是否在页面上显示 注册点击事件是否有效 是否存在 ...
- JavaScript——事件
事件:是由访问Web页面的用户引起一系列操作. 事件的作用:用于浏览器和用户的交互 以下代码为相关试验代码: HTML事件: <script type="text/javascript ...
- Web APP和原生 APP的不同
我们现在手机中的APP,大部分都是混合APP,也就是既用到了原生APP的基础,又用到了Web APP的基础,混合的比例从0%到100%之间不等.更好的了解APP的类型,有助于我们学则合适的测试策略.今 ...
- labuladong 算法小抄
<labuladong的算法小抄官方完整版> 本书目前可以手把手带你解决 110 道 LeetCode 算法问题,而且在不断更 新,全部基于 LeetCode 的题目,涵盖了所有题型和技巧 ...
- C - 小希的迷宫
上次Gardon的迷宫城堡小希玩了很久(见Problem B),现在她也想设计一个迷宫让Gardon来走.但是她设计迷宫的思路不一样,首先她认为所有的通道都应该是双向连通的,就是说如果有一个通道连通了 ...
- CodeForces 1119D(差分+前缀和+二分)
题意:给你一个数组,数组每次每个数都+1,有q次查询每一查询+L到+R中出现的所有不重复的数字个数. +L到+R其实就相当于是0到+(R-L+1) 感觉自己写的好啰嗦,直接上代码加注释: 1 #inc ...