********************************************
蛐蛐
http://qgjie456.blog.163.com/
MSN:qgjie@hotmail.com
本文适用于
linux-2.6.22.8
V 0.1
欢迎转载,但请保留作者信息
********************************************
在 star_kernel() 函数中调用 init_timers() 函数。
这个函数完成如下功能:
1)初始化本 CPU 上的定时器(timer)相关的数据结构
2)向 cpu_chain 通知链注册元素 timers_nb,该元素的回调函数用于初始化指定 CPU 上的定时器相关的数据结构。
3) 初始化时钟的软中断处理函数
参考《Linux内部的时钟处理机制全面剖析》。
参考《深入理解 linux 内核》。
参考《linux通知链表机制》。
==========================================================================
void __init init_timers(void)
{
初始当前 CPU 的定时器链表。
int err = timer_cpu_notify(&timers_nb, (unsigned long)CPU_UP_PREPARE,
(void *)(long)smp_processor_id());
如果没有定义 CONFIG_TIMER_STATS 这个宏,则这个 init_timer_stats() 函数为空函数。
init_timer_stats();
BUG_ON(err == NOTIFY_BAD);
在 CPU 管理的通知链表 cpu_chain 注册通知结构 timers_nb。
register_cpu_notifier(&timers_nb);
注册时钟软中断 TIMER_SOFTIRQ,它的处理函数为 run_timer_softirq()。
这个处理函数如下所示。
open_softirq(TIMER_SOFTIRQ, run_timer_softirq, NULL);
}
********************************************
这个函数是 注册到 CPU 子系统的通知链表的回调函数。
这个函数主要是在新的 CPU 开始工作或者停止工作(支持热插拔)时,通知 timer 管理器,
使 timer 管理器管理器知道,并进行相应的处理。
----------------------------------------
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;
#ifdef CONFIG_HOTPLUG_CPU
case CPU_DEAD:
case CPU_DEAD_FROZEN:
migrate_timers(cpu);
break;
#endif
default:
break;
}
return NOTIFY_OK;
}
********************************************
注册到通知链表的回调函数支持两类事件( UP 和 DEAD 类型的事件),
下面为 支持 CPU_UP_PREPARE 和 CPU_UP_PREPARE_FROZEN 类型事件的函数。
----------------------------------------
static int __devinit init_timers_cpu(int cpu)
{
int j;
tvec_base_t *base;
定义静态局部变量 tvec_base_done[] 数组,表示是否已经初始化了由索引号表示的 CPU。
static char __devinitdata tvec_base_done[NR_CPUS];
检测 CPU 的初始化是否已经做过,如果没有做过,继续,如果已经做过,则跳过。
if (!tvec_base_done[cpu]) {
定义静态全局变量,表示是否是第一个启动的 CPU 的初始化。
static char boot_done;
如果不是启动 CPU,则走这个路径。
if (boot_done) {
为每个 CPU 分配 struct tvec_t_base_s 结构的空间。
base = kmalloc_node(sizeof(*base), GFP_KERNEL,
cpu_to_node(cpu));
if (!base)
return -ENOMEM;
if (tbase_get_deferrable(base)) {
WARN_ON(1);
kfree(base);
return -ENOMEM;
}
把这个结构体空间清 0。
memset(base, 0, sizeof(*base));
设置 per_cpu 变量中的指针指向分配的空间。
per_cpu(tvec_bases, cpu) = base;
} else {
这个路径是在 boot 阶段,启动的第一个 CPU 的路径。
设置标志已经启动过了。
由于在启动阶段 per_cpu 数据没有准备好,所以使用静态定义的结构。
这是应为 per_cpu 数据是一个指针,尽管 per_cpu 中为指针预留了空间,
但是指针指向的空间,没有进行分配,都是执行 boot_tvec_bases 的。
boot_done = 1;
base = &boot_tvec_bases;
}
设置 CPU 已经设置过了。
tvec_base_done[cpu] = 1;
} else {
base = per_cpu(tvec_bases, cpu);
}
spin_lock_init(&base->lock);
lockdep_set_class(&base->lock, base_lock_keys + cpu);
初始化 tvec_t_base_s 结构中每个链表的头节点。
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);
base->timer_jiffies = jiffies;
return 0;
}
----------------------------------------
在这个 init_timers_cpu() 函数中,使用了全局变量 boot_tvec_bases。
它的定义如下所示:
typedef struct tvec_s {
struct list_head vec[TVN_SIZE];
} tvec_t;
typedef struct tvec_root_s {
struct list_head vec[TVR_SIZE];
} tvec_root_t;
struct tvec_t_base_s {
spinlock_t lock;
struct timer_list *running_timer;
unsigned long timer_jiffies;
tvec_root_t tv1;
tvec_t tv2;
tvec_t tv3;
tvec_t tv4;
tvec_t tv5;
} ____cacheline_aligned;
typedef struct tvec_t_base_s tvec_base_t;
tvec_base_t boot_tvec_bases;
----------------------------------------
lock spinlock_t 用于同步操作
----------------------------------------
running_timer struct timer_list * 正在处理的定时器
----------------------------------------
timer_jiffies unsigned long 当前正在处理的定时器到期时间
----------------------------------------
tv1 struct tvec_root 保存了到期时间从 timer_jiffies 到(2^8 -1)
之间(包括边缘值)的所有定时器
----------------------------------------
tv2 struct tvec 保存了到期时间从 timer_jiffies +(2^8)到
timer_jiffies + (2^14-1)之间(包括边缘值)的 所有定时器
----------------------------------------
tv3 struct tvec 保存了到期时间从 timer_jiffies +(2^14)到
timer_jiffies +(2^20-1)之间(包括边缘值)的所有定时器
----------------------------------------
tv4 struct tvec 保存了到期时间从 timer_jiffies +(2^20)到
timer_jiffies + (2^26-1)之间(包括边缘值)的所有定时器
----------------------------------------
tv5 struct tvec 保存了到期时间从 timer_jiffies +(2^16)到
timer_jiffies +(2^32-1)之间(包括边缘值)的所有定时器
----------------------------------------
********************************************
下面为 支持 CPU_DEAD 和 CPU_DEAD_FROZEN 类型事件的函数。
当没有配置这个宏 CONFIG_HOTPLUG_CPU 定义时,即系统不支持 CPU 热插拔时,
这个 migrate_timers() 为空函数;
当配置了这个宏定义时,这个函数如下所示。
----------------------------------------
static void __devinit migrate_timers(int cpu)
{
tvec_base_t *old_base;
tvec_base_t *new_base;
int i;
首先检查这个 CPU 是否在工作,如果仍然在工作,则发出警告信息。
BUG_ON(cpu_online(cpu));
这个 tvec_bases 是一个 per_cpu 变量,是 tvec_base_t 的指针。
这个 per_cpu() 函数取得这个 cpu 的私有变量,即tvec_base_t 的指针
old_base = per_cpu(tvec_bases, cpu);
这个 get_cpu_var()函数取得当前 cpu 的tvec_base_t 的指针 。
new_base = get_cpu_var(tvec_bases);
禁止当前 cpu 的中断,同样也禁止了内核抢占。
local_irq_disable();
根据需求锁定这两个自旋锁 new_base->lock, old_base->lock 。
double_spin_lock(&new_base->lock, &old_base->lock, smp_processor_id() < cpu);
如果参数 cpu 上有正在发生的定时器,则发出 bug 信息。
BUG_ON(old_base->running_timer);
把参数 cpu 上注册的 tv1 定时器链表上的定时器迁移到当前 cpu 的定时器链表上。
for (i = 0; i < TVR_SIZE; i++)
migrate_timer_list(new_base, old_base->tv1.vec + i);
把参数 cpu 上注册的 tv2、 tv3、tv4、tv5 定时器链表上的定时器迁移到当前 cpu 的定时器链表上。
for (i = 0; i < TVN_SIZE; i++) {
migrate_timer_list(new_base, old_base->tv2.vec + i);
migrate_timer_list(new_base, old_base->tv3.vec + i);
migrate_timer_list(new_base, old_base->tv4.vec + i);
migrate_timer_list(new_base, old_base->tv5.vec + i);
}
根据需求为这两个自旋锁 new_base->lock, old_base->lock 解锁。
double_spin_unlock(&new_base->lock, &old_base->lock, smp_processor_id() < cpu);
恢复本地 cpu 的中断。
local_irq_enable();
put_cpu_var(tvec_bases);
}
----------------------------------------
static void migrate_timer_list(tvec_base_t *new_base, struct list_head *head)
{
struct timer_list *timer;
如果定时器链表为 空链表,则直接退出。
while (!list_empty(head)) {
timer = list_first_entry(head, struct timer_list, entry);
detach_timer(timer, 0);
timer_set_base(timer, new_base);
internal_add_timer(new_base, timer);
}
}
********************************************
static inline void double_spin_lock(spinlock_t *l1, spinlock_t *l2,
bool l1_first)
__acquires(l1)
__acquires(l2)
{
使用标志 l1_first 判断先锁定这两个中的那个锁,以防止发生死锁现象。
if (l1_first) {
spin_lock(l1);
spin_lock(l2);
} else {
spin_lock(l2);
spin_lock(l1);
}
}
----------------------------------------
# define __acquires(x) __attribute__((context(x,0,1)))
********************************************
如果没没有定义这个 CONFIG_TIMER_STATS 宏,这个 init_timer_stats() 函数就是个空函数。
这个 CONFIG_TIMER_STATS 宏的作用是是否在 /proc 文件系统中生成 timer_stats 文件,
这个文件允许你查看Linux内核里使用定时器的常规事件一些信息。
通过查看这个文件,你可以看到那些常规事件使用定时器的次数最多,使用的频率是多少。
更详细的信息可以参考内核源码树下面的 Documentation/filesystems/proc.txt 文件。
如果定义了如下所示:
这个 init_timer_stats() 函数对于每个 CPU 来说初始化了 timer_stat 的自旋锁。
----------------------------------------
void __init init_timer_stats(void)
{
int cpu;
for_each_possible_cpu(cpu)
spin_lock_init(&per_cpu(lookup_lock, cpu));
}
********************************************
在 CPU 管理的通知链表 cpu_chain 注册通知结构 timers_nb。
static struct notifier_block __cpuinitdata timers_nb = {
.notifier_call = timer_cpu_notify,
};
这个通知结构的回调函数为 timer_cpu_notify() ,在上面也讲解过。
-----------------------------------------
这个 Raw 类型的通知链表 cpu_chain 是在 kernel/cpu.c 文件中定义的。
static __cpuinitdata RAW_NOTIFIER_HEAD(cpu_chain);
int __cpuinit register_cpu_notifier(struct notifier_block *nb)
{
int ret;
锁定 cpu_add_remove_lock 互斥锁。
mutex_lock(&cpu_add_remove_lock);
在 Raw 类型的通知链表 cpu_chain 上注册通知结构 nb。
ret = raw_notifier_chain_register(&cpu_chain, nb);
释放 cpu_add_remove_lock 互斥锁。
mutex_unlock(&cpu_add_remove_lock);
return ret;
}
********************************************
这个函数 run_timer_softirq() 是 TIMER_SOFTIRQ 的处理函数。
这个函数对当前 CPU 到期的定时器进行处理。
参考《深入理解 linux 内核》第六章。
-----------------------------------------
static void run_timer_softirq(struct softirq_action *h)
{
首先获得到本地 CPU 的定时器链表的 base 地址。
tvec_base_t *base = __get_cpu_var(tvec_bases);
这个函数和高精度时钟定时器有关,检测高精度时钟定时器是否 active,如果是 active 的就替换到
hres tick机制。参考《linux高精度时钟分析》。
hrtimer_run_queues();
检测如果 jiffies大于等于 timer_jiffies ,说明可能已经有软件时钟到期了,
此时就要进行软件时钟的处理,调用函数 __run_timers() 函数 进行处理。
如果 jiffies 小于 timer_jiffies ,表明没有软件时钟到期,则不用对软件时钟进行处理。函数返回。
if (time_after_eq(jiffies, base->timer_jiffies))
__run_timers(base);
}
********************************************
在 TIMER_SOFTIRQ 软中断的处理函数调用 __run_timers() 对到期的定时器进行处理。
-----------------------------------------
static inline void __run_timers(tvec_base_t *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)) {
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);
set_running_timer(base, timer);
detach_timer(timer, 1);
spin_unlock_irq(&base->lock);
{
int preempt_count = preempt_count();
fn(data);
if (preempt_count != preempt_count()) {
printk(KERN_WARNING "huh, entered %p "
"with preempt_count %08x, exited"
" with %08x?\n",
fn, preempt_count,
preempt_count());
BUG();
}
}
spin_lock_irq(&base->lock);
}
}
set_running_timer(base, NULL);
spin_unlock_irq(&base->lock);
}
-----------------------------------------
1. 获得 base 的同步锁
2. 如果 jiffies 大于等于 timer_jiffies (当前正要处理的软件时钟的到期时间,
说明可能有软件时钟到期了),就一直运行3~7,否则跳转至8
3. 计算得到 tv1 的索引,该索引指明当前到期的软件时钟所在 tv1 中的链表(结构参见3.2节),代码:
int index = base->timer_jiffies & TVR_MASK;
1. 调用 cascade 函数对软件时钟进行必要的调整(稍后会介绍调整的过程)
2. 使得 timer_jiffies 的数值增加1
3. 取出相应的软件时钟链表
4. 遍历该链表,对每个元素进行如下操作
* 设置当前软件时钟为 base 中正在运行的软件时钟(即保存当前软件时钟到 base-> running_timer 成员中)
* 将当前软件时钟从链表中删除,即卸载该软件时钟
* 释放锁,执行软件时钟处理程序
* 再次获得锁
1. 设置当前 base 中不存在正在运行的软件时钟
2. 释放锁
********************************************
问题:
1)在 init_timers_cpu() 函数中,为什么不直接使用 per_cpu() 函数定义的静态数组
而是动态分配呢?
在源码注释中,解释由于内存分配器没有工作,所以启动时使用静态分配的,但是在 RCU_init() 函数
中已经使用了 per_cpu() 函数了?
其实已经在 setup_per_cpu_areas() 函数为 per_cpu 数据分配了空间。
这是应为 per_cpu 数据是一个指针,尽管 per_cpu 中为指针预留了空间,
但是指针指向的空间,没有进行分配,都是执行 boot_tvec_bases 的。
参考《每CPU变量的数据组织和访问》。
2)这个 __acquires(x) 宏定义的意义?
3)与 timer_stat 相关的代码没有看?
阅读(1640) | 评论(0) | 转发(0) |