把握linux内核设计思想(七):内核定时器和定时运行
前面章节说到了把工作推后到除如今以外的时间运行的机制是下半部机制,可是当你须要将工作推后到某个确定的时间段之后运行。使用定时器是非常好的选择。
上一节内核时间管理中讲到内核在始终中断发生运行定时器,定时器作为软中断在下半部上下文中运行。时钟中断处理程序会运行update_process_times函数,在该函数中运行run_local_timers()函数来标记一个软中断去处理全部到期的定时器。例如以下:
void update_process_times(int user_tick)
{
struct task_struct *p = current;
int cpu = smp_processor_id();
/* Note: this timer irq context must be accounted for as well. */
account_process_tick(p, user_tick);
run_local_timers();
rcu_check_callbacks(cpu, user_tick);
printk_tick();
scheduler_tick();
run_posix_cpu_timers(p);
}
void run_local_timers(void)
{
hrtimer_run_queues();
raise_softirq(TIMER_SOFTIRQ);
softlockup_tick();
}
在分析定时器的实现之前我们先来看一看使用内核定时器的一个实例。详细使用可查看这篇文章:http://blog.csdn.net/shallnet/article/details/17734571,示比例如以下:
#include <linux/module.h>
#include <linux/init.h>
#include <linux/version.h>
#include <linux/timer.h>
#include <linux/delay.h>
struct timer_list sln_timer;
void sln_timer_do(unsigned long l)
{
mod_timer(&sln_timer, jiffies + HZ);
printk(KERN_ALERT"param: %ld, jiffies: %ld\n", l, jiffies);
}
void sln_timer_set(void)
{
init_timer(&sln_timer);
sln_timer.expires = jiffies + HZ; //1s
sln_timer.function = sln_timer_do;
sln_timer.data = 9527;
add_timer(&sln_timer);
}
static int __init sln_init(void)
{
printk(KERN_ALERT"===%s===\n", __func__);
sln_timer_set();
return 0;
}
static void __exit sln_exit(void)
{
printk(KERN_ALERT"===%s===\n", __func__);
del_timer(&sln_timer);
}
module_init(sln_init);
module_exit(sln_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("allen");
该演示样例作用是每秒钟打印出当前系统jiffies的值。
内核定时器由结构timer_list表示,定义在文件<include/linux/timer.h>中。
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
};
如演示样例,内核提供部分操作接口来简化管理定时器,
第一步、定义一个定时器:
struct timer_list sln_timer;
第二步、初始化定时器数据结构的内部值。
init_timer(&sln_timer);//初始化定时器
#define init_timer(timer)\
init_timer_key((timer), NULL, NULL)
void init_timer_key(struct timer_list *timer,
const char *name,
struct lock_class_key *key)
{
debug_init(timer);
__init_timer(timer, name, key);
}
static void __init_timer(struct timer_list *timer,
const char *name,
struct lock_class_key *key)
{
timer->entry.next = NULL;
timer->base = __raw_get_cpu_var(tvec_bases);
#ifdef CONFIG_TIMER_STATS
timer->start_site = NULL;
timer->start_pid = -1;
memset(timer->start_comm, 0, TASK_COMM_LEN);
#endif
lockdep_init_map(&timer->lockdep_map, name, key, 0);
}
第三步、填充timer_list结构中须要的值:
sln_timer.expires = jiffies + HZ; //1s后运行
sln_timer.function = sln_timer_do; //运行函数
sln_timer.data = 9527;
sln_timer.expires表示超时时间,它以节拍为单位的绝对计数值。假设当前jiffies计数等于或大于sln_timer.expires的值,那么sln_timer.function所指向的处理函数sln_timer_do就会运行。而且该函数还要使用长整型參数sln_timer.dat。
void sln_timer_do(unsigned long l);
第四步、激活定时器:
add_timer(&sln_timer); //向内核注冊定时器
这样定时器就能够执行了。
add_timer()的实现例如以下:
void add_timer(struct timer_list *timer)
{
BUG_ON(timer_pending(timer));
mod_timer(timer, timer->expires);
}
add_timer()调用了mod_timer()。mod_timer()用于改动定时器超时时间。
mod_timer(&sln_timer, jiffies + HZ);
因为add_timer()是通过调用mod_timer()来激活定时器,所以也能够直接使用mod_timer()来激活定时器,假设定时器已经初始化但没有激活,mod_timer()也会激活它。
假设须要在定时器超时前停止定时器,使用del_timer()函数来完毕。
del_timer(&sln_timer);
该函数实现例如以下:
int del_timer(struct timer_list *timer)
{
struct tvec_base *base;
unsigned long flags;
int ret = 0;
timer_stats_timer_clear_start_info(timer);
if (timer_pending(timer)) {
base = lock_timer_base(timer, &flags);
if (timer_pending(timer)) {
detach_timer(timer, 1);
if (timer->expires == base->next_timer &&
!tbase_get_deferrable(timer->base))
base->next_timer = base->timer_jiffies;
ret = 1;
}
spin_unlock_irqrestore(&base->lock, flags);
}
return ret;
}
static inline void detach_timer(struct timer_list *timer,
int clear_pending)
{
struct list_head *entry = &timer->entry;
debug_deactivate(timer);
__list_del(entry->prev, entry->next);
if (clear_pending)
entry->next = NULL;
entry->prev = LIST_POISON2;
}
当使用del_timer()返回后,定时器就不会再被激活,但在多处理器机器上定时器上定时器中断可能已经在其它处理器上执行了,所以删除定时器时须要等待可能在其它处理器上执行的定时器处理I程序都退出,这时就要使用del_timer_sync()函数执行删除工作:
del_timer_sync(&sln_timer);
该函数不能再中断上下文中使用。
该函数具体实现例如以下:
int del_timer_sync(struct timer_list *timer)
{
#ifdef CONFIG_LOCKDEP
unsigned long flags;
local_irq_save(flags);
lock_map_acquire(&timer->lockdep_map);
lock_map_release(&timer->lockdep_map);
local_irq_restore(flags);
#endif
for (;;) { //一直循环。直到删除timer成功再退出
int ret = try_to_del_timer_sync(timer);
if (ret >= 0)
return ret;
cpu_relax();
}
}
int try_to_del_timer_sync(struct timer_list *timer)
{
struct tvec_base *base;
unsigned long flags;
int ret = -1;
base = lock_timer_base(timer, &flags);
if (base->running_timer == timer)
goto out;
ret = 0;
if (timer_pending(timer)) {
detach_timer(timer, 1);
if (timer->expires == base->next_timer &&
!tbase_get_deferrable(timer->base))
base->next_timer = base->timer_jiffies;
ret = 1;
}
out:
spin_unlock_irqrestore(&base->lock, flags);
return ret;
}
普通情况下应该使用del_timer_sync()函数取代del_timer()函数,由于无法确定在删除定时器时。他是否在其他处理器上执行。
为了防止这样的情况的发生,应该调用del_timer_sync()函数而不是del_timer()函数。否则,对定时器执行删除操作后。代码会继续执行。但它有可能会去操作在其他处理器上执行的定时器正在使用的资源,因而造成并发訪问,全部优先使用删除定时器的同步方法。
除了使用定时器来推迟任务到指定时间段执行之外。还有其它的方法处理延时请求。有的方法会在延迟任务时挂起处理器,有的却不会。实际上也没有方法可以保证实际的延迟时间刚好等于指定的延迟时间。
1. 最简单的 延迟方法是忙等待,该方法实现起来非常easy,仅仅须要在循环中不断旋转直到希望的时钟节拍数耗尽。比方:
unsigned long delay = jiffies+10; //延迟10个节拍
while(time_before(jiffies,delay))
;
这样的方法当代码等待时,处理器仅仅能在原地旋转等待。它不会去处理其他不论什么任务。最好在任务等待时,同意内核又一次调度其他任务运行。将上面代码改动例如以下:
unsigned long delay = jiffies+10; //10个节拍
while(time_before(jiffies,delay))
cond_resched();
看一下cond_resched()函数详细实现代码:
#define cond_resched() ({ \
__might_sleep(__FILE__, __LINE__, 0); \
_cond_resched(); \
}) int __sched _cond_resched(void)
{
if (should_resched()) {
__cond_resched();
return 1;
}
return 0;
} static void __cond_resched(void)
{
add_preempt_count(PREEMPT_ACTIVE);
schedule(); //终于还是调用schedule()函数来又一次调度其他程序执行
sub_preempt_count(PREEMPT_ACTIVE);
}
函数cond_resched()将又一次调度一个新程序投入执行。但它仅仅有在设置完need_resched标志后才干生效。换句话说。就是系统中存在更重要的任务须要执行。
再由于该方法须要调用调度程序,所以它不能在中断上下文中使用----仅仅能在进程上下文中使用。其实,全部延迟方法在进程上下文中使用。由于中断处理程序都应该尽可能快的执行。另外。延迟执行无论在哪种情况下都不应该在持有锁时或者禁止中断时发生。
2. 有时内核须要更短的延迟,甚至比节拍间隔还要短。
这时能够使用内核提供的ms、ns、us级别的延迟函数。
void udelay(unsigned long usecs); //arch/x86/include/asm/delay.h
void ndelay(unsigned long nsecs); //arch/x86/include/asm/delay.h
void mdelay(unsigned long msecs);
udelay()使用忙循环将任务延迟指定的ms后运行,其依靠运行数次循环达到延迟效果,mdelay()函数是通过udelay()函数实现,例如以下:
#define mdelay(n) (\
(__builtin_constant_p(n) && (n)<=MAX_UDELAY_MS) ? udelay((n)*1000) : \
({unsigned long __ms=(n); while (__ms--) udelay(1000);}))
#endif
udelay()函数仅能在要求的延迟时间非常短的情况下运行,而在快速机器中时间非常长的延迟会造成溢出。对于较长的延迟。mdelay()工作良好。
3. schedule_timeout()函数是更理想的延迟执行方法。该方法会让须要延迟执行的任务睡眠到指定的延迟时间耗尽后再又一次执行。
但该方法也不能保证睡眠时间正好等于指定的延迟时间,仅仅能尽量是睡眠时间接近指定的延迟时间。当指定的时间到期后,内核唤醒被延迟的任务并将其又一次放回执行队列。使用方法例如以下:
set_current_state(TASK_INTERRUPTIBLE); //将任务设置为可中断睡眠状态
schedule_timeout(s*HZ); //小睡一会儿,“s”秒后唤醒
唯一的參数是延迟的相对时间,单位是jiffies。上例中将对应的任务推入可中断睡眠队列。睡眠s秒。
在调用函数schedule_timeout之前。不要要将任务设置成可中断或不和中断的一种。否则任务不会休眠。
这个函数须要调用调度程序。所以调用它的代码必须保证可以睡眠。简而言之,调用代码必须处于进程上下文中。而且不能持有锁。
其实schedule_timeout()函数的实现就是内核定时器的一个简单应用。
signed long __sched schedule_timeout(signed long timeout)
{
struct timer_list timer;
unsigned long expire; switch (timeout)
{
case MAX_SCHEDULE_TIMEOUT:
/*
* These two special cases are useful to be comfortable
* in the caller. Nothing more. We could take
* MAX_SCHEDULE_TIMEOUT from one of the negative value
* but I' d like to return a valid offset (>=0) to allow
* the caller to do everything it want with the retval.
*/
schedule();
goto out;
default:
/*
* Another bit of PARANOID. Note that the retval will be
* 0 since no piece of kernel is supposed to do a check
* for a negative retval of schedule_timeout() (since it
* should never happens anyway). You just have the printk()
* that will tell you if something is gone wrong and where.
*/
if (timeout < 0) {
printk(KERN_ERR "schedule_timeout: wrong timeout "
"value %lx\n", timeout);
dump_stack();
current->state = TASK_RUNNING;
goto out;
}
} expire = timeout + jiffies; //下一行代码设置了超时运行函数process_timeout()。
setup_timer_on_stack(&timer, process_timeout, (unsigned long)current);
__mod_timer(&timer, expire, false, TIMER_NOT_PINNED); //激活定时器
schedule(); //调度其它新任务
del_singleshot_timer_sync(&timer); /* Remove the timer from the object tracker */
destroy_timer_on_stack(&timer); timeout = expire - jiffies; out:
return timeout < 0 ? 0 : timeout;
}
当定时器超时时。process_timeout()函数被调用:
static void process_timeout(unsigned long __data)
{
wake_up_process((struct task_struct *)__data);
}
当任务被又一次调度时。将返回代码进入睡眠前的位置继续运行,位置正好在schedule()处。
这样的情况下。代码能够简单的使用scedule_timeout()函数取代schedule()函数,这样一来。当希望指定时间到期后。任务都会被唤醒。当然。代码须要检查被唤醒的原因。有可能是被事件唤醒,也有可能是由于延迟的时间到期,还可能是由于接收到了信号。然后运行对应的操作。
把握linux内核设计思想(七):内核定时器和定时运行的更多相关文章
- 把握linux内核设计思想系列【转】
转自:http://blog.csdn.net/shallnet/article/details/47734053 版权声明:本文为博主原创文章,未经博主允许不得转载.如果您觉得文章对您有用,请点击文 ...
- 把握linux内核设计思想系列
[版权声明:尊重原创,转载请保留出处:blog.csdn.net/shallnet,文章仅供学习交流,请勿用于商业用途] 本专栏分析linux内核的设计实现,包含系统调用.中断.下半部机制.时间管理. ...
- Linux内核设计第七周 ——可执行程序的装载
Linux内核设计第七周 ——可执行程序的装载 第一部分 知识点总结 一.预处理.编译.链接和目标文件的格式 1.可执行程序是怎么得来的 编译链接的过程 预处理阶段 gcc -E -o XX.cpp ...
- 《深入理解Android内核设计思想》
<深入理解Android内核设计思想> 基本信息 作者: 林学森 出版社:人民邮电出版社 ISBN:9787115348418 上架时间:2014-4-25 出版日期:2014 年5月 开 ...
- 揭秘jbpm流程引擎内核设计思想及构架
揭秘jbpm流程引擎内核设计思想及构架 作者 胡长城(银狐999) 1 前言 2 阅读本篇的基础准备 2.1 概念的基础 2.2 环境的基础 3 什么是 ...
- 《深入理解Android内核设计思想》已陆续在全国各大书店及网上书店上市,感谢大家一直以来的支持~~
<深入理解Android内核设计思想>已陆续在全国各大书店上市,电子书店也在陆续上架中(不断添加): 1. China-Pub 2. 京东 3. s=books&ie=UTF8&a ...
- 《linux/unix设计思想》读后感
<linux/unix设计思想>这本书,觉得书的大部分内容都闲扯的太远了,以下简单的总结下本书的核心,帮助大家节省时间和金钱. linux/unix设计思想: 1) 程序应该小而专一,程序 ...
- Linux(centos 6.5) 调用java脚本以及定时运行的脚本实例及配置文件具体解释
Linux(centos 6.5) 调用java脚本以及定时运行的脚本实例 一.调用java程序脚本(默认已经搭建好了Java环境) 1.jdk 安装路径 /usr/jdk/jdk1.7/-- 2.j ...
- 把握linux内核设计思想(十三):内存管理之进程地址空间
[版权声明:尊重原创,转载请保留出处:blog.csdn.net/shallnet.文章仅供学习交流,请勿用于商业用途] 进程地址空间由进程可寻址的虚拟内存组成,Linux 的虚拟地址空间为0~4G字 ...
- 把握linux内核设计思想(五):下半部机制之工作队列及几种机制的选择
[版权声明:尊重原创.转载请保留出处:blog.csdn.net/shallnet,文章仅供学习交流,请勿用于商业用途] 工作队列是下半部的第二种将工作推后运行形式.和软中断.task ...
随机推荐
- assert用法总结
assert宏的原型定义在<assert.h>中,其作用是如果它的条件返回错误,则终止程序执行,原型定义:#include <assert.h>void assert( int ...
- nyoj 504 课程设计
课程设计 时间限制:3000 ms | 内存限制:65535 KB 难度:2 描述 新学期伊始,Gangster 老师又在为如何给学生分配课程设计题目而犯愁,Gangster老师老共有 N 名学生 ...
- securecrt5序列号
securecrt5序列号 Name: Apollo InteractiveCompany: Apollo InteractiveSerial Number: 03-50-02 ...
- Spring4.0系列9-websocket简单应用
http://wiselyman.iteye.com/blog/2003336 ******************************************* Spring4.0系列1-新特性 ...
- golang socket 实现分析(一)
socket:tcp/udp.ip构成了网络通信的基石,tcp/ip是面向连接的通信协议 要求建立连接时进行3次握手确保连接已被建立,关闭连接时需要4次通信来保证客户端和,服务端都已经关闭 在通信过程 ...
- 【Unity】状态机的状态改变及其回调
问:怎么知道状态机发生了改变?即如何得知从一个状态切换到了另一个状态? 答:Unity使用StateMachineBehaviours类来描述状态机的行为,当状态机处于不同的状态时,会触发不同的回调. ...
- 【C#/WPF】键盘事件
需求:按下回车键,触发事件. 搜MSDN时,看到的键盘事件是System.Windows.Forms里的,在WPF中没法用: https://msdn.microsoft.com/zh-tw/libr ...
- 二叉树、红黑树、伸展树、B树、B+树
好多树啊,程序猿砍树记,吼吼. 许多程序要解决的关键问题是:快速定位特定排序项的能力. 第一类:散列 第二类:字符串查找 第三类:树算法 树算法可以在辅助存储器中存储大量的数据. 二叉树.红黑树和伸展 ...
- VMware 14 Pro 安装 CentOS 7
今年准备好好学习一下.NET CORE了,那也是得学习Linux.然后又得学习更多,咬着牙干吧... 1.Vmware虚拟机安装 在windows平台,首先咱们得先安装Vmware虚拟机,步骤省略,一 ...
- osql.exe 批处理sql 文件
.bat文件内容 "C:\Program Files\Microsoft SQL Server\110\Tools\Binn\osql.exe" -U sa -P sa -S 19 ...