- 浏览: 1403603 次
- 性别:
- 来自: 火星
文章分类
最新评论
-
aidd:
内核处理time_wait状态详解 -
ahtest:
赞一下~~
一个简单的ruby Metaprogram的例子 -
itiProCareer:
简直胡说八道,误人子弟啊。。。。谁告诉你 Ruby 1.9 ...
ruby中的类变量与类实例变量 -
dear531:
还得补充一句,惊群了之后,数据打印显示,只有一个子线程继续接受 ...
linux已经不存在惊群现象 -
dear531:
我用select试验了,用的ubuntu12.10,内核3.5 ...
linux已经不存在惊群现象
由于linux还不是一个实时的操作系统,因此如果需要更高精度,或者更精确的定时的话,可能就需要打一些实时的补丁,或者用商用版的实时linux,.
这里内的定时器最小间隔也就是1个tick.
这里还有一个要注意的,我这里的分析并没有分析内核新的hrt 定时器.这个定时器是Monta Vista加入到内核的一个高精度的定时器的实现.
先来看几个相关的数据结构.
///这个是一个最主要的数据结构,表示一个完整的定时器级联表
下面来看tvec和tvec_root的结构:
可以看到这两个结构也就是hash链表.每次通过超时jiffies来计算slot,然后插入到链表.这里链表是FIFO的.这里除了tv5外其他几个都是简单的与TVR_MASK按位与计算.
///定义了一个per cpu变量.这里要知道定时器的注册和触发执行一定是在相同的cpu上的.
struct tvec_base boot_tvec_bases;
static DEFINE_PER_CPU(struct tvec_base *, tvec_bases) = &boot_tvec_bases;
内核注册定时器最终都会通过调用internal_add_timer来实现.具体的工作方式是这样的:
1 如果定时器在接下来的0~255个jiffies中到期,则将定时器添加到tv1.
2 如果定时器是在接下来的256*64个jiffies中到期,则将定时器添加到tv2.
3 如果定时器是在接下来的256*64*64个jiffies中到期,则将定时器添加到tv3.
4 如果定时器是在接下来的256*64*64*64个jiffies中到期,则将定时器添加到tv4.
5 如果更大的超时,则利用0xffffffff来计算hash,然后插入到tv5(这个只会出现在64的系统).
看下面的图就能比较清晰了:
接下来看源码:
这里要知道内核中的软定时器是用软中断来实现的,软中断的注册以及实现可以看我前面的blog,这里就不介绍了.我们来看timer模块的初始化:
ok,接下来我们就来看timer_cpu_notify这个函数,其实这个函数还是定时器注册的cpu的notify chain的action:
其他的部分我们忽略,我们就发现定时器模块会调用init_timers_cpu来初始化.我们来分析这个函数.
这个函数最主要的功能就是初始化boot_tvec_bases,也就是全局的定时器表:
通过上面的定时器初始化函数我们知道定时器软中断所对应的action是run_timer_softirq,也就是当时钟中断到来,软中断启动时,就会调用这个函数,因此我们来看这个函数:
这个函数功能很简单,它的最关键就是调用__run_timers,这个函数才是真正处理定时器的函数.
__run_timers这个函数的主要功能是运行所有超时的定时器:
1
ok我们接下来来看下定时器超时的机制,关键在这段代码:
index为0就说明当前要处理的定时器不在base->tv1 中.因此我们需要cascade来进行调解.
可以看到定时器处理始终都是在处理tv1,如果tv1已经处理完了,则将tv2添加到tv1,以此类推.
而定时器软中断如何触发呢,是用update_process_times来触发的,这个函数比较简单,主要是调用run_local_timers来触发软中断:
这里内的定时器最小间隔也就是1个tick.
这里还有一个要注意的,我这里的分析并没有分析内核新的hrt 定时器.这个定时器是Monta Vista加入到内核的一个高精度的定时器的实现.
先来看几个相关的数据结构.
///这个是一个最主要的数据结构,表示一个完整的定时器级联表
struct tvec_base { ///自旋锁 spinlock_t lock; ///表示由本地cpu正在处理的定时器链表 struct timer_list *running_timer; ///这个表示当前的定时器级联表中最快要超时的定时器的jiffer unsigned long timer_jiffies; ///下面表示了5级的定时器级联表. struct tvec_root tv1; struct tvec tv2; struct tvec tv3; struct tvec tv4; struct tvec tv5; } ____cacheline_aligned;
下面来看tvec和tvec_root的结构:
struct tvec { struct list_head vec[TVN_SIZE]; }; struct tvec_root { struct list_head vec[TVR_SIZE]; };
可以看到这两个结构也就是hash链表.每次通过超时jiffies来计算slot,然后插入到链表.这里链表是FIFO的.这里除了tv5外其他几个都是简单的与TVR_MASK按位与计算.
struct timer_list { struct list_head entry; ///超时节拍数 unsigned long expires; ///定时器将要执行的回调函数 void (*function)(unsigned long); ///传递给回调函数的参数 unsigned long data; ///从属于那个base struct tvec_base *base; };
///定义了一个per cpu变量.这里要知道定时器的注册和触发执行一定是在相同的cpu上的.
struct tvec_base boot_tvec_bases;
static DEFINE_PER_CPU(struct tvec_base *, tvec_bases) = &boot_tvec_bases;
内核注册定时器最终都会通过调用internal_add_timer来实现.具体的工作方式是这样的:
1 如果定时器在接下来的0~255个jiffies中到期,则将定时器添加到tv1.
2 如果定时器是在接下来的256*64个jiffies中到期,则将定时器添加到tv2.
3 如果定时器是在接下来的256*64*64个jiffies中到期,则将定时器添加到tv3.
4 如果定时器是在接下来的256*64*64*64个jiffies中到期,则将定时器添加到tv4.
5 如果更大的超时,则利用0xffffffff来计算hash,然后插入到tv5(这个只会出现在64的系统).
看下面的图就能比较清晰了:
接下来看源码:
static void internal_add_timer(struct tvec_base *base, struct timer_list *timer) { ///取出超时jiffies unsigned long expires = timer->expires; ///得到定时器还有多长时间到期(这里是相比于最短的那个定时器) unsigned long idx = expires - base->timer_jiffies; struct list_head *vec; ///开始判断该把定时器加入到那个队列.依次为tv1到tv5 if (idx < TVR_SIZE) { int i = expires & TVR_MASK; vec = base->tv1.vec + i; } else if (idx < 1 << (TVR_BITS + TVN_BITS)) { int i = (expires >> TVR_BITS) & TVN_MASK; vec = base->tv2.vec + i; } else if (idx < 1 << (TVR_BITS + 2 * TVN_BITS)) { int i = (expires >> (TVR_BITS + TVN_BITS)) & TVN_MASK; vec = base->tv3.vec + i; } else if (idx < 1 << (TVR_BITS + 3 * TVN_BITS)) { int i = (expires >> (TVR_BITS + 2 * TVN_BITS)) & TVN_MASK; vec = base->tv4.vec + i; } else if ((signed long) idx < 0) { /* * Can happen if you add a timer with expires == jiffies, * or you set a timer to go off in the past */ vec = base->tv1.vec + (base->timer_jiffies & TVR_MASK); } else { int i; /* If the timeout is larger than 0xffffffff on 64-bit * architectures then we use the maximum timeout: */ if (idx > 0xffffffffUL) { idx = 0xffffffffUL; expires = idx + base->timer_jiffies; } i = (expires >> (TVR_BITS + 3 * TVN_BITS)) & TVN_MASK; vec = base->tv5.vec + i; } /* * Timers are FIFO: */ ///最终加入链表 list_add_tail(&timer->entry, vec); }
这里要知道内核中的软定时器是用软中断来实现的,软中断的注册以及实现可以看我前面的blog,这里就不介绍了.我们来看timer模块的初始化:
void __init init_timers(void) { ///主要是初始化boot_tvec_bases(如果是smp,则会初始化所有cpu上的boot_tvec_bases) int err = timer_cpu_notify(&timers_nb, (unsigned long)CPU_UP_PREPARE, (void *)(long)smp_processor_id()); init_timer_stats(); BUG_ON(err == NOTIFY_BAD); ///注册到cpu的notify chain(这个我前面的blog也有介绍) register_cpu_notifier(&timers_nb); ///注册软中断 open_softirq(TIMER_SOFTIRQ, run_timer_softirq); }
ok,接下来我们就来看timer_cpu_notify这个函数,其实这个函数还是定时器注册的cpu的notify chain的action:
static struct notifier_block __cpuinitdata timers_nb = { .notifier_call = timer_cpu_notify, }; static int __cpuinit timer_cpu_notify(struct notifier_block *self, unsigned long action, void *hcpu) { long cpu = (long)hcpu; switch(action) { case CPU_UP_PREPARE: case CPU_UP_PREPARE_FROZEN: ///模块初始化的时候就会调用这个函数 if (init_timers_cpu(cpu) < 0) return NOTIFY_BAD; break; .................................... return NOTIFY_OK; }
其他的部分我们忽略,我们就发现定时器模块会调用init_timers_cpu来初始化.我们来分析这个函数.
这个函数最主要的功能就是初始化boot_tvec_bases,也就是全局的定时器表:
static int __cpuinit init_timers_cpu(int cpu) { int j; struct tvec_base *base; ///可以看到这个是一个静态变量.它保存了每个cpu上的那个boot_tvec_bases. static char __cpuinitdata tvec_base_done[NR_CPUS]; ///如果为空,说明这个cpu上的定时器表还没有初始化,因此需要初始化 if (!tvec_base_done[cpu]) { /*这个也是一个静态变量.它表示了cpu是否初始化完毕.这个函数有一个宏__cpuinit,这个将 *这个函数放置到cpuinit这个段,因此也就是说这个函数会先在cpu初始化时调用,也就是第一**次会先给boot_done赋值,然后再调用这个函数才会进入kmalloc. */ static char boot_done; if (boot_done) { /* * The APs use this path later in boot */ ///malloc一个tvec_base base = kmalloc_node(sizeof(*base), GFP_KERNEL | __GFP_ZERO, cpu_to_node(cpu)); if (!base) return -ENOMEM; /* Make sure that tvec_base is 2 byte aligned */ if (tbase_get_deferrable(base)) { WARN_ON(1); kfree(base); return -ENOMEM; } ///由于在per cpu的变量中类型为tvec_bases的,只有boot_tvec_bases,因此,也就是将base这个指针付给boot_tvec_bases. per_cpu(tvec_bases, cpu) = base; } else { ///cpu初始化完毕后会进入这里,标记了cpu已经boot完毕.此时内存初始化完毕. boot_done = 1; base = &boot_tvec_bases; } tvec_base_done[cpu] = 1; } else { ///取出tvec_base付给base base = per_cpu(tvec_bases, cpu); } ///开始初始化 spin_lock_init(&base->lock); ///开始初始化5个定时器表 for (j = 0; j < TVN_SIZE; j++) { INIT_LIST_HEAD(base->tv5.vec + j); INIT_LIST_HEAD(base->tv4.vec + j); INIT_LIST_HEAD(base->tv3.vec + j); INIT_LIST_HEAD(base->tv2.vec + j); } for (j = 0; j < TVR_SIZE; j++) INIT_LIST_HEAD(base->tv1.vec + j); ///默认值为初始化时的jiffes base->timer_jiffies = jiffies; return 0; }
通过上面的定时器初始化函数我们知道定时器软中断所对应的action是run_timer_softirq,也就是当时钟中断到来,软中断启动时,就会调用这个函数,因此我们来看这个函数:
这个函数功能很简单,它的最关键就是调用__run_timers,这个函数才是真正处理定时器的函数.
static void run_timer_softirq(struct softirq_action *h) { struct tvec_base *base = __get_cpu_var(tvec_bases); ///这个函数应该是提供给2.6.31内核的新特性Performance Counters. perf_counter_do_pending(); ///处理hrt timer hrtimer_run_pending(); ///判断当前的jiffies是否大于等于最小的那个超时jiffies.是的话就进入定时器处理 if (time_after_eq(jiffies, base->timer_jiffies)) __run_timers(base); }
__run_timers这个函数的主要功能是运行所有超时的定时器:
1
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)) { ///这里的head和work_list其实表示的就是已经超时的定时器,也就是我们将要处理的定时器. struct list_head work_list; struct list_head *head = &work_list; ///从timer_jiffies得到所在index,其实也就是在tv1中的index int index = base->timer_jiffies & TVR_MASK; ///开始处理层叠定时器,这里的这个cascade是一个关键的函数,我们下面会分析,这里只需要知道这个函数其实也就是用来一层层的得到这个定时器处于哪个级别中. 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)); ///更新timer_jiffies. ++base->timer_jiffies; ///用work_list替换掉base->tv1.vec + index.这里因为上面的处理中,就算定时器不在base->tv1中,可是通过cascade的调节,会将base->tv2加入到base->tv1中,或者说base->tv3,以此类推. list_replace_init(base->tv1.vec + index, &work_list); ///如果这个值不为空说明有已经超时的定时器.这里head也就是work_list,也就是base->tv1 while (!list_empty(head)) { void (*fn)(unsigned long); unsigned long data; ///取出定时器. timer = list_first_entry(head, struct timer_list,entry); fn = timer->function; data = timer->data; timer_stats_account_timer(timer); ///设置当前正在处理的定时器为timer(这个主要是针对smp的架构),因为我们是在软中断中进行的,因此要防止多个cpu的并发. set_running_timer(base, timer); ///删除这个定时器. detach_timer(timer, 1); spin_unlock_irq(&base->lock); { int preempt_count = preempt_count(); lock_map_acquire(&lockdep_map); ///执行定时器回调函数 fn(data); ............................................. } spin_lock_irq(&base->lock); } } ///修改base->running_timer为空 set_running_timer(base, NULL); spin_unlock_irq(&base->lock); }
ok我们接下来来看下定时器超时的机制,关键在这段代码:
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));
index为0就说明当前要处理的定时器不在base->tv1 中.因此我们需要cascade来进行调解.
///得到在N级(也就是tv2,tv3...)的定时器表中的slot.这里可以对照我们前面的internal_add_timer加入定时器的情况. #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) { /* cascade all the timers from tv up one level */ struct timer_list *timer, *tmp; struct list_head tv_list; ///这里实例化tv_list为我们将要处理的链表.并将老的list重新初始化为空. list_replace_init(tv->vec + index, &tv_list); /* * We are removing _all_ timers from the list, so we * don't have to detach them individually. */ list_for_each_entry_safe(timer, tmp, &tv_list, entry) { BUG_ON(tbase_get_base(timer->base) != base); ///重新加入定时器,也就是加入到自己对应的位置 internal_add_timer(base, timer); } ///然后返回index,这里可以看到如果index为空则说明这个级别的定时器也已经都处理过了,因此我们需要再处理下一个级别. return index; }
可以看到定时器处理始终都是在处理tv1,如果tv1已经处理完了,则将tv2添加到tv1,以此类推.
而定时器软中断如何触发呢,是用update_process_times来触发的,这个函数比较简单,主要是调用run_local_timers来触发软中断:
void run_local_timers(void) { hrtimer_run_queues(); ///触发软中断. raise_softirq(TIMER_SOFTIRQ); softlockup_tick(); }
发表评论
-
Receive packet steering patch详解
2010-07-25 16:46 12156Receive packet steering简称rp ... -
内核中拥塞窗口初始值对http性能的影响分析
2010-07-11 00:20 9708这个是google的人提出的 ... -
linux 内核tcp拥塞处理(一)
2010-03-12 16:17 9579这次我们来分析tcp的拥塞控制,我们要知道协议栈都是很保守的, ... -
内核tcp协议栈SACK的处理
2010-01-24 21:13 12185上一篇处理ack的blog中我 ... -
内核tcp的ack的处理
2010-01-17 03:06 11163我们来看tcp输入对于ack,段的处理。 先是ack的处理, ... -
内核处理time_wait状态详解
2010-01-10 17:39 6822这次来详细看内核的time_wait状态的实现,在前面介绍定时 ... -
tcp协议栈处理各种事件的分析
2009-12-30 01:29 13638首先我们来看socket如何将一些状态的变化通知给对应的进程, ... -
linux内核sk_buff的结构分析
2009-12-25 00:42 47921我看的内核版本是2.6.32. 在内核中sk_buff表示一 ... -
tcp的输入段的处理
2009-12-18 00:56 8362tcp是全双工的协议,因此每一端都会有流控。一个tcp段有可能 ... -
内核协议栈tcp层的内存管理
2009-11-28 17:13 12089我们先来看tcp内存管理相关的几个内核参数,这些都能通过pro ... -
linux内核中tcp连接的断开处理
2009-10-25 21:47 10342我们这次主要来分析相关的两个断开函数close和shotdow ... -
linux内核tcp的定时器管理(二)
2009-10-05 20:52 5432这次我们来看后面的3个定时器; 首先是keep alive定 ... -
linux内核tcp的定时器管理(一)
2009-10-04 23:29 9837在内核中tcp协议栈有6种 ... -
linux 内核tcp接收数据的实现
2009-09-26 20:24 14529相比于发送数据,接收数据更复杂一些。接收数据这里和3层的接口是 ... -
linux 内核tcp数据发送的实现
2009-09-10 01:41 19795在分析之前先来看下SO_RCVTIMEO和SO_SNDTIME ... -
tcp connection setup的实现(三)
2009-09-03 00:34 5206先来看下accept的实现. 其实accept的作用很简单, ... -
tcp connection setup的实现(二)
2009-09-01 00:46 8439首先来看下内核如何处理3次握手的半连接队列和accept队列( ... -
tcp connection setup的实现(一)
2009-08-23 04:10 5827bind的实现: 先来介绍几个地址结构. struct ... -
linux内核中socket的实现
2009-08-15 04:38 21109首先来看整个与socket相关的操作提供了一个统一的接口sys ... -
ip层和4层的接口实现分析
2009-08-08 03:50 6211首先来看一下基于3层的ipv4以及ipv6实现的一些4层的协议 ...
相关推荐
主要是对linux内核中的定时器的使用编写的一个例子,代码中包括timer.c和makefile文件,还有一些已经编译的文件,其中有个timer.ko文件就是内核模块文件,在终端使用 insmod timer.ko就可以插入到linux内核(要与...
Linux内核定时器基于软中断(software interrupt)机制,由`hrtimer`(High Resolution Timers)或传统的`timer_list`结构来实现。这些定时器可以在内核空间中注册,当设定的时间到达时,它们会触发回调函数的执行。...
Linux内核定时器是操作系统核心中的重要组成部分,用于在特定时间点执行预定的任务。这个实例提供了深入了解和操作Linux内核定时器的机会。在Linux中,定时器被广泛应用于各种场景,如任务调度、I/O超时处理、系统...
Linux内核定时器模板提供了一种标准的接口,使得开发者能够方便地为自己的驱动程序或服务设置延迟或定期执行的任务。这里我们将深入探讨Linux内核定时器的工作原理、主要结构以及如何使用模板来创建和管理定时器。 ...
下面我们将详细探讨Linux内核定时器的工作原理、相关API以及`jiffies`计数器。 一、Linux内核定时器工作原理 1. **定时器结构体**:在Linux内核中,定时器通过`struct timer_list`结构体表示。它包含了定时器的...
Linux内核定时器是操作系统核心中的重要组成部分,用于在未来的某个特定时间点执行特定功能。这一机制基于jiffies,即Linux内核维护的一个系统时钟滴答计数器。内核定时器的实现主要在`<linux/timer.h>`头文件和`...
Linux内核定时器,适用于5.0以上的内核,4.0以下内核不适用
本文将深入探讨Linux内核定时器的应用及其相关概念。 首先,我们来看一个简单的内核模块示例,该模块创建了一个定时器并展示了其基本用法。在这个例子中,`DECLARE_WAIT_QUEUE_HEAD(wq)`定义了一个等待队列头,用于...
【嵌入式学习资料之Linux内核定时器笔记】涵盖了多个关键概念,这些概念对于理解嵌入式系统和Linux内核的工作原理至关重要。首先,我们要介绍的是实时时钟(RTC)。RTC是一种硬件设备,即使在系统关闭或无电源的情况...
《Linux内核设计与实现(原书第3版)》是由Robert Love所著,陈莉君和康华翻译的一本全面指导Linux内核设计与实现的专业书籍。该书基于Linux 2.6.34内核,详细介绍了Linux内核系统的设计与实现。内容涵盖了进程管理...
Linux 驱动内核定时器
内核定时器是Linux内核中的一个关键组件,用于在特定时间间隔后触发某些操作或事件。它们主要由以下几种类型构成: 1. **软定时器(Software Timers)**:这类定时器在用户空间或内核空间中创建,通过软件调度机制...
一、Linux内核定时器初探 1.1、图形界面配置系统节拍率 中断周期性产生的频率就是系统频率,也叫做节拍率(tick rate),单位是 Hz。系统节拍率是可以设置的,在编译 Linux 内核的时候可以通过图形化界面设置系统...
在Linux中,这通常涉及到对信号量、进程调度或者内核定时器的使用。这些机制可以帮助开发者创建延时操作、周期性任务或者超时处理等功能。 1. **最小堆实现**: 最小堆是一种自平衡的数据结构,常用于实现优先队列...
#### 三、Linux内核定时器的数据结构 为了支持动态定时器的实现,Linux内核定义了一个名为`timer_list`的数据结构,该结构用于描述一个定时器。其定义如下: ```c struct timer_list { struct list_head list; // ...
一种基于内核定时器和工作队列的Linux rootkit.pdf
标准的Linux内核定时器虽然在一般应用中表现良好,但在需要高精度定时的场景下,可能无法满足需求。本文提出的"ETimer(Enhanced Timer)"是一种针对嵌入式Linux的高性能定时器实现方法,它通过对标准Linux内核的...
本课件系列围绕Linux内核的设计与实现展开,旨在帮助学习者深入理解Linux内核的工作原理,尤其针对那些对Linux内核开发感兴趣的人。 首先,我们从“linux内核源代码分析-进程管理及调度.ppt”开始。在这个主题中,...
Linux内核定时器是操作系统核心中的重要组成部分,它允许开发者安排任务在未来某个特定时间执行一次。内核定时器主要用于各种延迟操作、超时检测、周期性任务和其他需要精确时间控制的场景。在Linux系统中,内核...