Chinaunix首页 | 论坛 | 博客
  • 博客访问: 325085
  • 博文数量: 100
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 665
  • 用 户 组: 普通用户
  • 注册时间: 2015-02-02 12:43
文章分类

全部博文(100)

文章存档

2015年(100)

我的朋友

分类: LINUX

2015-05-19 23:09:44

********************************************


                                           蛐蛐

                    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 相关的代码没有看?
阅读(1629) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~