要了解本节的内容,需要补充一下“疯狂内核之同步与互斥”的预备知识。
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_table和chainhash_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.h的61行定义:
#define local_irq_disable() / do { raw_local_irq_disable(); trace_hardirqs_off(); } while (0) |
注意,这个定义必须是CONFIG_TRACE_IRQFLAGS_SUPPORT编译选项存在于.config文件,该宏才有效。我查了查.config,是存在的,所以又定位到raw_local_irq_disable和trace_hardirqs_off两个宏。先看第一个,raw_local_irq_disable,全内核四个地方有定义,取决于一个重要的CONFIG_PARAVIRT编译选项是否被配置。我们看到.config中根本不存在这个编译选项,所以根本不用考虑arch/x86/include/asm/paravirt.h里面的内容。而剩下的定义来自哪儿,取决于宏__ASSEMBLY__。
是该讲讲__ASSEMBLY__宏了,这个宏的作用是避免gcc在进行汇编编译的时候,不定义后续相关内容。什么意思?我们看到顶层Makefile的354行有一个:
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.h中74行:
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指令取消eflags的IF标志位,屏蔽可屏蔽中断。有关可屏蔽中断、非屏蔽中断和异常的基本概念请参考博文“中断的分类”。后面由于我们没有设置CONFIG_TRACE_IRQFLAGS和CONFIG_IRQSOFF_TRACER编译选项,所以trace_hardirqs_off()函数为空,回到start_kernel中。
上面说了,没有配置CONFIG_TRACE_IRQFLAGS,所以550行的early_boot_irqs_off()函数也为空。而CONFIG_GENERIC_HARDIRQS是配置了的,所以551行early_init_irq_lock_class函数就要被调用了,来自kernel/irq/handle.c的543行:
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.h的29行:
# 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.c的275行:
struct irq_desc *irq_to_desc(unsigned int irq) { return (irq < NR_IRQS) ? irq_desc + irq : NULL; } |
好了,至此,最著名的NR_IRQS变量就出现了。要说清楚这个问题,就要大致学学APIC的知识了。APIC称为高级可编程中断控制器(Advanced Programmable Interrupt Controller,APIC),是通过扩充组合的方式来为多处理器系统驱动中断控制器。在目前的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,仅仅拥有16个IRQ,其中一个还用来级联。
好了,有关APIC的知识我们就暂时介绍到这里,想深入了解的请查看博客“中断处理”。由于我们配置了IO APIC但没有配置CONFIG_SPARSE_IRQ编译选项,所以NR_IRQS就定义在arch/x86/include/asm/irq_vectors.h的166行:
# define NR_IRQS / (CPU_VECTOR_LIMIT < IO_APIC_VECTOR_LIMIT ? / (NR_VECTORS + CPU_VECTOR_LIMIT) : / (NR_VECTORS + IO_APIC_VECTOR_LIMIT)) |
NR_VECTORS的值是256,CPU_VECTOR_LIMIT 的值等于CONFIG_NR_CPUS,我们配置文件中的值是255,IO_APIC_VECTOR_LIMIT被定义为( 32 * MAX_IO_APICS ),表示Linux支持的追到IO APIC的数量。MAX_IO_APICS在32位x86体系中被定义为64。因此CPU_VECTOR_LIMIT超过了最大限额,NR_IRQS的值就是256+64=320。
所以,for_each_irq_desc宏的意思就是遍历每个IO APICS的IRQ,并调用lockdep_set_class宏。由于我们没有CONFIG_LOCKDEP,所以这个宏是个空宏,没有任何内容。是不是觉得前面说了这么多,但没有实际的代码,有点灰心?没关系,毕竟我们学到新东西了。如果配置了CONFIG_LOCKDEP,那么lockdep_set_class宏定义如下:
#define lockdep_set_class(lock, key) /
lockdep_init_map(&(lock)->dep_map, #key, key, 0)
那么上面遍历了IO APICS每个中断的中段描述符,并设置中段描述符的锁。