现在的位置: 首页 > 综合 > 正文

初始化同步与互斥环境

2013年11月08日 ⁄ 综合 ⁄ 共 4633字 ⁄ 字号 评论关闭

5.1 初始化同步与互斥环境

要了解本节的内容,需要补充一下“疯狂内核之同步与互斥”的预备知识。

 

5.1.1 屏蔽中断

 

void lockdep_init(void)

{

       int i;

 

       /*

        * Some architectures have their own start_kernel()

        * code which calls lockdep_init(), while we also

        * call lockdep_init() from the start_kernel() itself,

        * and we want to initialize the hashes only once:

        */

       if (lockdep_initialized)

              return;

 

       for (i = 0; i < CLASSHASH_SIZE; i++)

              INIT_LIST_HEAD(classhash_table + i);

 

       for (i = 0; i < CHAINHASH_SIZE; i++)

              INIT_LIST_HEAD(chainhash_table + i);

 

       lockdep_initialized = 1;

}

 

该函数用于启动Lock dependency validator,本质上是建立两个散列表classhash_tablechainhash_table,并初始化全局变量lockdep_initialized。对Linux内核的链表和散列表不熟悉的同志可以看看博客“Linux内核入门(三)—— C语言基本功的相关内容。

 

好了,继续跟进,540行,debug_objects_early_init()函数,用于内核的对象调试。由于我们.config文件中没有配置CONFIG_DEBUG_OBJECTS,所以这个函数为空函数。545行,调用boot_init_stack_canary()函数,同样在也.config文件中没有配置CONFIG_CC_STACKPROTECTOR,所以这个函数也为空。547行,CONFIG_CGROUPS也没有配置,所以cgroup_init_early()函数也为空。

 

549行,local_irq_disable(),终于不为空了,在include/linux/irqflags.h61行定义:

 

#define local_irq_disable() /

       do { raw_local_irq_disable(); trace_hardirqs_off(); } while (0)

 

注意,这个定义必须是CONFIG_TRACE_IRQFLAGS_SUPPORT编译选项存在于.config文件,该宏才有效。我查了查.config,是存在的,所以又定位到raw_local_irq_disabletrace_hardirqs_off两个宏。先看第一个,raw_local_irq_disable,全内核四个地方有定义,取决于一个重要的CONFIG_PARAVIRT编译选项是否被配置。我们看到.config中根本不存在这个编译选项,所以根本不用考虑arch/x86/include/asm/paravirt.h里面的内容。而剩下的定义来自哪儿,取决于宏__ASSEMBLY__

 

是该讲讲__ASSEMBLY__宏了,这个宏的作用是避免gcc在进行汇编编译的时候,不定义后续相关内容。什么意思?我们看到顶层Makefile354行有一个:

354 KBUILD_AFLAGS   := -D__ASSEMBLY__

 

这个宏变量实际上是在编译汇编代码的时候,由编译器使用-D这样的参数加进去的,KBUILD_AFLAGS这个变量也定义了这个变量,gcc会把这个宏定义为1。我们知道,gcc编译一个c程序大致分为三步:编译c文件生成临时汇编程序;编译汇编程序生成.o对象文件;链接对象文件生成c程序。而KBUILD_AFLAGS指定__ASSEMBLY__,是为了编译汇编程序时,不会用到类似于“# define”定义的属性(注意!不是宏定义#define#后面有一个空格,Linux内核中定义了很多这样的定义,在编译c文件时,就相当于#define),因为这样的属性是在进行C语言编译的时候加的,通过__ASSEMBLY__来避免在C语言编译成汇编程序后,不在编译这些汇编代码时候的引用,从而避免不必要的宏或函数在编译汇编代码时候的引用。

 

虽然我们顶层Makefile设置了__ASSEMBLY__,但是,我们现在分析的是c语言下的代码,__ASSEMBLY__暂没定义,所以来到arch/x86/include/asm/irqflags.h74行:

 

static inline void raw_local_irq_disable(void)

{

       native_irq_disable();

}

 

native_irq_disable来自哪儿?同一文件的37行:

 

static inline void native_irq_disable(void)

{

       asm volatile("cli": : :"memory");

}

 

调用cli指令取消eflagsIF标志位,屏蔽可屏蔽中断。有关可屏蔽中断、非屏蔽中断和异常的基本概念请参考博文“中断的分类”。后面由于我们没有设置CONFIG_TRACE_IRQFLAGSCONFIG_IRQSOFF_TRACER编译选项,所以trace_hardirqs_off()函数为空,回到start_kernel中。

 

上面说了,没有配置CONFIG_TRACE_IRQFLAGS,所以550行的early_boot_irqs_off()函数也为空。而CONFIG_GENERIC_HARDIRQS是配置了的,所以551early_init_irq_lock_class函数就要被调用了,来自kernel/irq/handle.c543行:

 

void early_init_irq_lock_class(void)

{

       struct irq_desc *desc;

       int i;

 

       for_each_irq_desc(i, desc) {

              lockdep_set_class(&desc->lock, &irq_desc_lock_class);

       }

}

 

for_each_irq_desc是一个宏,我们设置了CONFIG_GENERIC_HARDIRQS,所以来自include/linux/irqnr.h29行:

 

# define for_each_irq_desc(irq, desc)                               /

       for (irq = 0, desc = irq_to_desc(irq); irq < nr_irqs;            /

            irq++, desc = irq_to_desc(irq))                        /

              if (!desc)                                    /

                     ;                                         /

              else

 

我们没有配置CONFIG_SPARSE_IRQ,所以来到kernel/irq/handle.c275行:

 

struct irq_desc *irq_to_desc(unsigned int irq)

{

       return (irq < NR_IRQS) ? irq_desc + irq : NULL;

}

 

好了,至此,最著名的NR_IRQS变量就出现了。要说清楚这个问题,就要大致学学APIC的知识了。APIC称为高级可编程中断控制器(Advanced Programmable Interrupt ControllerAPIC),是通过扩充组合的方式来为多处理器系统驱动中断控制器。在目前的x86体系中,系统的每一个部份都是经由APIC总线连接的。“本地 APIC”为系统的一部份,负责传递 Interrupt至指定的处理器。

 

举例来说,当一台机器上有一个四核处理器,则它必须相对的要有四个本地APIC。自 1994 年的Pentium P54c开始Intel已经将本地APIC内嵌在它们的处理器中。我们当前的 Intel 处理器的电脑就已经包含了 APIC 系统的部份。

 

系统中另一个重要的部份为 I/O APIC。系统中最多可拥有 8 I/O APIC。它们会收集来自 I/O 装置的 Interrupt 讯号且在当那些装置需要 interrupt 时传送讯息至本地 APIC。每个 I/O APIC 有一个专有的 interrupt 输入号码,就是我们所谓的IRQ号。Intel 过去与目前的 I/O APIC 通常有NR_IRQS个输入,具体多少个我们稍后分析。而且,有些机器拥有数个 I/O APIC,每一个分别有自己的输入号码,加起来一台机器上会有上百个 IRQ 可供装置 Interrupt 使用。

 

然而,系统中若没有 I/O APIC,那本机 APIC 就没有用处。像这样的状况下,编译配置中的CONFIG_X86_IO_APIC编译选项就不会设置,还会还原使用 8259 PIC,仅仅拥有16IRQ,其中一个还用来级联。

 

好了,有关APIC的知识我们就暂时介绍到这里,想深入了解的请查看博客“中断处理。由于我们配置了IO APIC但没有配置CONFIG_SPARSE_IRQ编译选项,所以NR_IRQS就定义在arch/x86/include/asm/irq_vectors.h166行:

 

#  define NR_IRQS                                  /

       (CPU_VECTOR_LIMIT < IO_APIC_VECTOR_LIMIT ?   /

              (NR_VECTORS + CPU_VECTOR_LIMIT)  :   /

              (NR_VECTORS + IO_APIC_VECTOR_LIMIT))

 

NR_VECTORS的值是256CPU_VECTOR_LIMIT 的值等于CONFIG_NR_CPUS,我们配置文件中的值是255IO_APIC_VECTOR_LIMIT被定义为( 32 * MAX_IO_APICS ),表示Linux支持的追到IO APIC的数量。MAX_IO_APICS32x86体系中被定义为64。因此CPU_VECTOR_LIMIT超过了最大限额,NR_IRQS的值就是256+64=320

 

所以,for_each_irq_desc宏的意思就是遍历每个IO APICSIRQ,并调用lockdep_set_class宏。由于我们没有CONFIG_LOCKDEP,所以这个宏是个空宏,没有任何内容。是不是觉得前面说了这么多,但没有实际的代码,有点灰心?没关系,毕竟我们学到新东西了。如果配置了CONFIG_LOCKDEP,那么lockdep_set_class

抱歉!评论已关闭.