linux 定时器原理
内核定时器:
unsigned long timeout = jiffies + (x * HZ);
while(1) {
// Check the condition.
// Take a schedule.
if (time_after(jiffies, timeout)) {
printk("Timeout\n");
break;
}
}
转换到秒:
s = (jiffies - last_jiffies)/HZ;
jiffies(约50天溢出)为jiffies_64的后32位,因此直接读取jiffies_64不具备原子性,使用get_jiffies_64,
函数原理:[平台为32位则需要保护读取,否则直接读取]
顺序锁: 读读/读写并发,写写互斥
读写自旋锁: 读读并发,读写/写写互斥
自旋锁: 不允许任何操作并发
u64 get_jiffies_64(void)
{
unsigned long seq;
u64 ret;
do {
seq = read_seqbegin(&xtime_lock);
ret = jiffies_64;
} while (read_seqretry(&xtime_lock, seq)); // 若读的过程中发生写,则重读
return ret;
}
这里涉及一个顺序锁的读写规则:
读不会更改lock的seq,写会++,这里就会发现到值被写覆盖,于是重新读。
write_seqlock(&lock);
...... //写操作代码
write_sequnlock(&lock);
顺序锁的使用场景是必须默认保持写互斥后,才能使用顺序锁.
睡眠延时:
这种睡眠再调度的精度要低于jiffies的精度:
schedule_timeout(xx);
函数原理:
expire = timeout + jiffies; // 超时截至时间
setup_timer_on_stack(&timer, process_timeout, (unsigned long)current);
__mod_timer(&timer, expire, false, TIMER_NOT_PINNED);
schedule();
del_singleshot_timer_sync(&timer);
destroy_timer_on_stack(&timer);
timeout = expire - jiffies;
out:
return timeout < 0 ? 0 : timeout;
从这里看出,这个函数实际上是基于timer来实现的
标准的延时调度接口 timer_list:[如果需要循环调度,则在timer_func中递归init/add]
struct timer_list my_timer;
init_timer(&my_timer);
my_timer.expire = jiffies + n*HZ;
my_timer.function = timer_func;
my_timer.data = func_parameter;
add_timer(&my_timer);
短延时(基于指令级忙等,不基于jiffy机制的方法):
mdelay/udelay/ndelay
基于一个全局的变量:loops_per_jiffy,变量初始化位于:
calibrate_delay()
基本原理是,先从4096 以*2的倍数找到第一个范围 4096*x < t < 4096*2x
然后逐渐开始细化,从4096*x 开始,逐渐递增4096*x>>2(而不是减半),直到到达对应的精度要求
10000000
11000000
10100000
....
static inline void __udelay(unsigned long usecs)
{
unsigned long loops_per_usec;
loops_per_usec = (loops_per_jiffy * HZ) / 1000000; //一秒中能够执行的指令数目/1000000(ms)
__delay(usecs * loops_per_usec); //Delay 几毫秒的可以执行的指令
}
注:rdtsc这个指令是得到CPU自启动以后的运行周期,不适合超线程和多核CPU
墙上时钟:RTC
static struct timeval curr_time;
do_gettimeofday(&curr_time);
timer_list原理:
初始化timer时,首先取一个cpu变量【timer在哪个cpu注册,就在哪个cpu触发】
作为本cpu上所有timer的控制结构,根据超时程度将timers进行分级管理,其中base->timer_jiffies为最短的那个计时器的时间:
0 - 1<<8 tv1 index = expires
- 1<<(8+6) tv2 index = expires >> 8()
- 1<<(8+2*6) tv3 index = expires >> 8+6()
- 1<<(8+3*6) tv4 index = expires >> 8+2*6()
... tv5 [不是直接根据索引来决定在数组的地方,因为数组的地方是有限的]
在tv2中,需要把 2^8 - 2^14之间的timers均匀放到一个2^6的数组中,只能2^8对齐,每个数组链中最多放置2^8个timers.
往后类推..
tv1
1 2 3 4 5 6 .... vec
a1 b1 c1 d1 e1 f1
a2 b2 c2
a3 c3
a4
最后一步,添加timer到目标队列的尾部.
timer_list执行调度:
初始化:init_timers_cpu,主要是分配cpu变量,(除了启动cpu0是固定的静态空间),后再初始化tv1-tv5所有的timer header.
调用:这里从时钟irq中开始执行,增加jiffies.
run_timer_softirq(timer.c)
-> __run_timers
在函数update_process_times调用run_local_timers后触发软中断:
raise_softirq(TIMER_SOFTIRQ);
遍历过程:
static inline void __run_timers(struct tvec_base *base)
{
struct timer_list *timer;
spin_lock_irq(&base->lock);
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; //取其索引
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, &work_list); //清空这个列表,并处理
while (!list_empty(head)) { //遍历这个列表下的所有timer
void (*fn)(unsigned long);
unsigned long data;
bool irqsafe;
timer = list_first_entry(head, struct timer_list,entry);//取出timer
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脱链
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);
}
核心的降级处理函数:
#define INDEX(N) ((base->timer_jiffies >> (TVR_BITS + (N) * TVN_BITS)) & TVN_MASK)
static int cascade(struct tvec_base *base, struct tvec *tv, int index)
{
struct timer_list *timer, *tmp;
struct list_head tv_list;
list_replace_init(tv->vec + index, &tv_list); //获取
list_for_each_entry_safe(timer, tmp, &tv_list, entry) {
__internal_add_timer(base, timer);
}
return index;
}
index=0 说明当前的tv1已经为空,这个时候base->timer_jiffies应该已经 >256, INDEX(N)的作用就是减去基数获取实际所在的
链表位置,在tv2中timer_jiffies逐渐增加,每次取tv2的一个数组链表然后释放到tv1中(256),逐渐释放,当tv2结束时,同理从tv3
释放到tv2.
linux 定时器原理的更多相关文章
- Linux进程调度原理
Linux进程调度原理 Linux进程调度机制 Linux进程调度的目标 1.高效性:高效意味着在相同的时间下要完成更多的任务.调度程序会被频繁的执行,所以调度程序要尽可能的高效: 2.加强交互性能: ...
- Linux 定时器应用【转】
Linux 定时器应用 实验目的 阅读 Linux 相关源代码,学习 Linux 系统中的时钟和定时器原理,即,ITIMER_REAL实时计数,ITIMER_VIRTUAL 统计进程在用户模式执行的时 ...
- 2017-2018-1 20179205《Linux内核原理与设计》第七周作业
<Linux内核原理与设计>第七周作业 视频学习及操作分析 创建一个新进程在内核中的执行过程 fork.vfork和clone三个系统调用都可以创建一个新进程,而且都是通过调用do_for ...
- 2017-2018-1 20179205《Linux内核原理与设计》第四周作业
<Linux内核原理与分析> 视频学习及实验操作 Linux内核源代码 视频中提到了三个我们要重点专注的目录下的代码,一个是arch目录下的x86,支持不同cpu体系架构的源代码:第二个是 ...
- 2017-2018-1 20179209《Linux内核原理与分析》第七周作业
一.实验 1.1task_struct数据结构 Linux内核通过一个被称为进程描述符的task_struct结构体来管理进程,这个结构体包含了一个进程所需的所有信息.它定义在linux-3.18.6 ...
- [转载]Linux进程调度原理
[转载]Linux进程调度原理 Linux进程调度原理 Linux进程调度的目标 1.高效性:高效意味着在相同的时间下要完成更多的任务.调度程序会被频繁的执行,所以调度程序要尽可能的高效: 2.加强交 ...
- JavaScript定时器原理分析
.header { cursor: pointer } p { margin: 3px 6px } th { background: lightblue; width: 20% } table { t ...
- 20169212《Linux内核原理与分析》课程总结
20169212<Linux内核原理与分析>课程总结 每周作业链接汇总 第一周作业:完成linux基础入门实验,了解一些基础的命令操作. 第二周作业:学习MOOC课程--计算机是如何工作的 ...
- linux基础-第十四单元 Linux网络原理及基础设置
第十四单元 Linux网络原理及基础设置 三种网卡模式图 使用ifconfig命令来维护网络 ifconfig命令的功能 ifconfig命令的用法举例 使用ifup和ifdown命令启动和停止网卡 ...
随机推荐
- 机器学习实战(Machine Learning in Action)学习笔记————09.利用PCA简化数据
机器学习实战(Machine Learning in Action)学习笔记————09.利用PCA简化数据 关键字:PCA.主成分分析.降维作者:米仓山下时间:2018-11-15机器学习实战(Ma ...
- 数据库小组与UI小组第一次对接
时间:2018.6.1,21:30 ~ 23:00 人员:除黄志鹏外全体成员,因为黄志鹏临时有事 工作内容: 主要为数据库小组与UI第二组对接,并将成果汇总到github仓库.另外UI第一组重构了代码 ...
- 【SPL标准库专题(4)】 Datastructures:SplDoublyLinkedList
简述 双链表是一种重要的线性存储结构,对于双链表中的每个节点,不仅仅存储自己的信息,还要保存前驱和后继节点的地址. 类摘要 SplDoublyLinkedList implements Iterato ...
- Eclipse+Weblogic 12开发简单的Enterprise Application
学到EJB方面的内容,遇到了很多问题,翻阅了无数遍Java EE和Weblogic的官方文档,在google上进行了无数次搜索都没有答案,可能我要找的答案太冷门.这一切都起源于Java EE官方文档里 ...
- python的学习之路day2
1.什么是常量: 常量在程序中是不变的量 但是在python中所有的变量都可以改 注意:为了防止区分错误,所以python中常量使用大写命名 例如: MYSQL_CONNECTION = '192.1 ...
- 实现CTreeCtrl父子节点的联动选择
本文实现了下面的功能: 当选中父节点时, 其所有子节点全部选中. 当取消选中父节点时, 其所以子节点全部取消选中. 点击子节点时, 根据子节点与其兄弟节点的选中状态, 自动设置父节点的选中状态. 通过 ...
- easyui学习笔记14-拓展的基本验证规则
/** * 扩展的基本校验规则, */ $.extend($.fn.validatebox.defaults.rules, { minLength : { // 判断最小长度 validator : ...
- 如何弹出QQ临时对话框实现不添加好友在线交谈效果
如何不添加好友弹出QQ临时对话框实现在线交谈效果,这样的一个需求,我们真的是太需要了,实现起来也很简单,一行代码即可搞定,需要的朋友可以参考下 其实这个很简单,在img我们加入一个a标签,然后 < ...
- (十九)ArcGIS JS 加载WMS服务(超图示例)
前言 在工作中,需要在ArcGIS API for JavaScript中加载超图服务,因为超图的rest服务只可以用于Leaflet .openlayers3 (with MVT) .MapboxG ...
- Drupal使用
首先到https://www.drupal.org/download去下载Drupal 更好的办法是使用composer,这个先放着,了解后再添加 然后将解压后的文件夹整个复制到设置的部署路径下,因为 ...