- 浏览: 1386524 次
- 性别:
- 来自: 火星
文章分类
最新评论
-
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 11956Receive packet steering简称rp ... -
内核中拥塞窗口初始值对http性能的影响分析
2010-07-11 00:20 9624这个是google的人提出的 ... -
linux 内核tcp拥塞处理(一)
2010-03-12 16:17 9491这次我们来分析tcp的拥塞控制,我们要知道协议栈都是很保守的, ... -
内核tcp协议栈SACK的处理
2010-01-24 21:13 12071上一篇处理ack的blog中我 ... -
内核tcp的ack的处理
2010-01-17 03:06 11069我们来看tcp输入对于ack,段的处理。 先是ack的处理, ... -
内核处理time_wait状态详解
2010-01-10 17:39 6705这次来详细看内核的time_wait状态的实现,在前面介绍定时 ... -
tcp协议栈处理各种事件的分析
2009-12-30 01:29 13560首先我们来看socket如何将一些状态的变化通知给对应的进程, ... -
linux内核sk_buff的结构分析
2009-12-25 00:42 47813我看的内核版本是2.6.32. 在内核中sk_buff表示一 ... -
tcp的输入段的处理
2009-12-18 00:56 8269tcp是全双工的协议,因此每一端都会有流控。一个tcp段有可能 ... -
内核协议栈tcp层的内存管理
2009-11-28 17:13 11920我们先来看tcp内存管理相关的几个内核参数,这些都能通过pro ... -
linux内核中tcp连接的断开处理
2009-10-25 21:47 10213我们这次主要来分析相关的两个断开函数close和shotdow ... -
linux内核tcp的定时器管理(二)
2009-10-05 20:52 5333这次我们来看后面的3个定时器; 首先是keep alive定 ... -
linux内核tcp的定时器管理(一)
2009-10-04 23:29 9763在内核中tcp协议栈有6种 ... -
linux 内核tcp接收数据的实现
2009-09-26 20:24 14387相比于发送数据,接收数据更复杂一些。接收数据这里和3层的接口是 ... -
linux 内核tcp数据发送的实现
2009-09-10 01:41 19660在分析之前先来看下SO_RCVTIMEO和SO_SNDTIME ... -
tcp connection setup的实现(三)
2009-09-03 00:34 5128先来看下accept的实现. 其实accept的作用很简单, ... -
tcp connection setup的实现(二)
2009-09-01 00:46 8376首先来看下内核如何处理3次握手的半连接队列和accept队列( ... -
tcp connection setup的实现(一)
2009-08-23 04:10 5745bind的实现: 先来介绍几个地址结构. struct ... -
linux内核中socket的实现
2009-08-15 04:38 21001首先来看整个与socket相关的操作提供了一个统一的接口sys ... -
ip层和4层的接口实现分析
2009-08-08 03:50 6125首先来看一下基于3层的ipv4以及ipv6实现的一些4层的协议 ...
相关推荐
主要是对linux内核中的定时器的使用编写的一个例子,代码中包括timer.c和makefile文件,还有一些已经编译的文件,其中有个timer.ko文件就是内核模块文件,在终端使用 insmod timer.ko就可以插入到linux内核(要与...
linux 内核定时器编程: 包括代码程序和makefile文件 my_timer定时器每个1s 打印1 和 second_timer定时器每隔2s打印2 将rar修改为.tar.bz2
这是一个Linux内核定时器的实例,可以学习使用
linux 内核定时器使用模板 摘自宋宝华书中
LINUX内核定时器的使用 LINUX内核定时器的使用
这是一个内核定时器程序,写出了内核定时器的基本骨架,实现每隔10秒打印一句话。
Linux内核定时器,适用于5.0以上的内核,4.0以下内核不适用
Linux 驱动内核定时器
一、Linux内核定时器初探 1.1、图形界面配置系统节拍率 中断周期性产生的频率就是系统频率,也叫做节拍率(tick rate),单位是 Hz。系统节拍率是可以设置的,在编译 Linux 内核的时候可以通过图形化界面设置系统...
LINux系统内核定时器机制的操作系统课程设计。内有LInux操作系统内核定时器机制的详细讲解
一种基于内核定时器和工作队列的Linux rootkit.pdf
本文介绍了Linux系统中内核定时器的应用。
论文《Linux内核中一种高精度定时器的设计与实现》
文档介绍了实时时钟(RTC),时间戳计时器(TSC),CPU本地定时器,高精度时间定时器(HPET),ACPI电源管理定时器,定时器的数据结构,定时插补,单处理器系统上的计时体系结构,时钟中断处理程序,多处理器系统上...
一份关于linux内核中动态定时器实现机制的详解。是学习linux kernel关于timer实现的不可多得的资料。
内核定时器 linux 实现源码 经过测试
linux下的C语言开发(定时器) 定时器是我们需要经常处理的一种资源。那linux下面的定时器又是怎么一回事呢?其实,在linux里面有一种进程中信息传递的方法,那就是信号。这里的定时器就相当于系统每隔一段时间给...
软件意义上的定时器最终依赖硬件定时器来实现, 内核在时钟中断发生后检测各定时器是否到期 , 到期后的定时器处理函数将作为软中断在底半部执行 。实质上,时钟中断处理程序会 换起TIMER_SOFTIRQ软中断 ,运行当前...
5. Linux内核定时器 第三天 1. Linux进程控制 2. Linux进程调度 3. Linux系统调用 4. Proc文件系统 5. Linux内核异常分析 第四天 1. 嵌入式Linux产品开发流程 2. 交叉工具链 3. 嵌入式Linux内核制作 4. 根文件系统...
本书填补了Linux内核理论和实践细节之间的鸿沟。Robed Love著,陈莉君翻译。 本书针对Linux 2.6内核,包括O(1)调度程序、抢占式内核、块I/O层以及I/O调度程序等。本书还包含了Linux内核开发者在开发时需要用到的很多...