"); //-->
内核源码结构
arch目录包括了所有和体系结构相关的核心代码,对于每个构架的CPU。arch目录下面对应一个子目录,例如arch/arm、(arch/i386就是Intel CPU及与之相兼容体系结构的子目录)。
block目录块设备通用函数
crypto目录常用加密和散列算法,还有一些压缩和CRC校验算法。
drivers目录所有的设备驱动程序,每个不同的驱动占用一个子目录,比如drivers/block/为块设备驱动程序,drivers/char/为字符设备驱动程序,drivers/mtd/为NOR Flash、NAND Flash等存储设备的驱动程序。
Documentation目录关于内核各部分的通用解释和注释。
fs目录各种支持的文件系统,如fs/ext2、fs/ext3、fs/jffs2等
include目录内核头文件。有基本头文件(存放在include/linux/目录下)、各种驱动或功能部件的头文件(比如include/media/、include/mtd、include/net/)、各种体系相关的头文件(比如include/asm-arm/、include/asm-i386/)。当配置内核后,include/asm/是某个include/asm-xxx(比如include/asm-arm/的链接)。
init目录包含核心的初始化代码(不是系统的引导代码),有main.c文件中的start_kernel函数是内核引导后运行的第一个函数。
ipc目录进程间通信的代码。
kernel目录内核管理的核心代码,包括进程调度、定时器等,和平台相关的一部分代码放在arch/*/kernel目录下。
lib目录内核用到的一些库函数代码。,比如crc32.c、string.c。
mm目录内存管理代码,和平台相关的一部分代码放在arch/*/mm目录下。
net目录网络相关代码,每个子目录对应网络的一个方面。实现了各种常见的网络协议。
scripts目录用于配置、编译内核的脚本文件。
security目录安全、密钥相关的代码。主要是一个SELinux的模块。
sound目录常用音频设备的驱动程序等。
usr目录用来制作一个压缩的cpio归档文件:initrd的镜像,它可以作为内核启动后挂接(mount)的第一个文件系统(一般用不到)。
Linux Makefile分析
内核中的哪些文件被编译?它们是怎样被编译的?它们的连接时的顺序如何确定的?……这些都是通过Makefile来管理。从最简单的角度来总结Makefile的作用有以下三点。
1、决定编译哪些文件。
2、怎样编译这些文件。
3、怎样连接这些文件,最重要的是它们的顺序如何?
顶层的Makefile 它是所有Makefile文件的核心,从总体上控制着内核的编译、连接
.config 配置文件,在配置内核时产生。所有Makefile文件(包含顶层目录及各级子目录)都是根据.config来决定使用哪些文件
arch/*/Makefile 对应体系结构的Makefile,它用来决定哪些结构体系相关的文件参与内核的生成,并提供一些规则生成特定格式的内核映像
scripts/Makefile.* Makefile共用的通用规则、脚本等
kbuild Makefiles 各级子目录下的Makefile,它们相对简单,被上一层Makefile调用来编译当前目录下的文件
内核文档Documentation\kbuild\makefiles.txt讲得很透彻。
(1)决定编译哪些文件。
①顶层的Makefile决定内核根目录下哪些子目录将被编进内核。
②arch/*/Makefile决定arch/$(ARCH)目录下哪些文件、哪些目录将被编进内核。
③各级子目录下的Makefile决定所在目录下哪些文件将被编进内核,哪些文件将被编成模块(即驱动程序),哪些子目录继续调用它们的Makefile
①
init-y := init/
drivers-y := drivers/ sound/
net-y := net/
libs-y := lib/
core-y := usr/
可见,顶层Makefile将13个子目录分为5类:init-y、drivers-y、net-y、libs-y、core-y。
include $(srctree)/arch/$(ARCH)/Makefile
对于ARCH变量,可以执行make命令时传入,比如“make ARCH=arm ...”。另外,对于非x86平台,还需要制定交叉编译工具,这也可以在执行make命令时传入,比如“make COROSS_COMPILE=arm-linux- ...”。为了方便,常在Makefile中进行修改。
修改前:
ARCH ?= $(SUBARCH)
CROSS_COMPILE ?=
修改后:
ARCH ?= arm
CROSS_COMPILE ?= arm-linux
②
head-y := arch/arm/kernel/head$(MMUEXT).o arch/arm/kernel/init_task.o
...
core-y += arch/arm/kernel/ arch/arm/mm/ arch/arm/common/
core-y += $(MACHINE)
core-$(CONFIG_ARCH_S3C2410) += arch/arm/mach-s3c2400/
core-$(CONFIG_ARCH_S3C2410) += arch/arm/mach-s3c2412/
core-$(CONFIG_ARCH_S3C2410) += arch/arm/mach-s3c2440/
...
libs-y := arch/arm/lib/ $(libs-y)
除了前面的5类子目录外,又出现了另一类:head-y不过它直接以文件名出现。MMUEXT在/arch/arm/Makefile前面定义,对于没有MMU的处理器,MMUEXT的值为-nommu,使用头文件head-nommu.S;对于有MMU的处理器,MMUEXT的值为空,使用文件head.S。
编译内核时,将依次进入init-y、core-y、libs-y、drivers-y和net-y所列出的目录执行它们的Makefile,每个子目录都会生成一个built-in.o(libs-y所列目录下,有可能生成lib.a文件)。最后,head-y所表示的文件将和这些built-in.o一起被连接成内核印象文件vmlinux。
③
在配置内核的时候,生成配置文件.config。内核顶层Makefile使用如下语句间接包含.config文件,以后就根据.config中定义的各个变量决定编译哪些文件。之所以说“间接”包含,是因为包含的是include/config/auto.conf文件,而它只是将.config文件中的注释去掉,并根据顶层Makefile中的定义增加一些变量而已。
# Read in config
-include include/config/auto.conf
(2) 怎样编译这些文件。
即编译选项、连接选项是什么。
(3)怎样连接这些文件,最重要的是它们的顺序如何?
前面分析有哪些文件要编进内核时,顶层的Makefile和arch/$(ARCH)/Makefile定义了6类目录(或文件):head-y、init-y、drivers-y、net-y、libs-y、core-y。它们的初始值如下(以ARM体系为例)。
arch/arm/Makefile中:
#Default value
head-y := arch/arm/kernel/head$(MMUEXT).o arch/arm/kernel/init_task.o
……
core-y += arch/arm/kernel/ arch/arm/mm/ arch/arm/common/
core-y += $(MACHINE)
core-$(CONFIG_ARCH_S3C2410) += arch/arm/mach-s3c2400/
core-$(CONFIG_ARCH_S3C2410) += arch/arm/mach-s3c2412/
core-$(CONFIG_ARCH_S3C2410) += arch/arm/mach-s3c2440/
……
libs-y := arch/arm/lib/ $(libs-y)
顶层Makefile中:
init-y := init/
drivers-y := drivers/ sound/
net-y := net/
libs-y := lib/
core-y := usr/
可见除了head-y之外,其余的都是目录名。在顶层Makefile中,这些目录名的后面直接加上built-in.o或lib.a,表示要连接进内核的文件,如下:
init-y := $(patsubst %/, %/built-in.o, $(init-y))
core-y := $(patsubst %/, %/built-in.o, $(core-y))
drivers-y := $(patsubst %/, %/built-in.o, $(drivers-y))
net-y := $(patsubst %/, %/built-in.o, $(net-y))
libs-y1 := $(patsubst %/, %/lib.a, $(libs-y))
libs-y2 := $(patsubst %/, %/built-in.o, $(libs-y))
libs-y := $(libs-y1) $(libs-y2)
上面的patsubst是字符串处理函数,它的用法如下:
$(patsubst pattern, replacement, text)
表示查找“text”中符合格式“pattern”的字,用“replacement”替换它们。比如上面的“init-y”初值为“init/”,经过init-y := $(patsubst %/, %/built-in.o, $(init-y))的变换后,“init-y”变为了“init/built-in.o”。
顶层Makefile中,再往下看。
vmlinux-init := $(head-y) $(init-y)
vmlinux-main := $(core-y) $(libs-y) $(drivers-y) $(net-y)
vmlinux-all := $(vmlinux-init) $(vmlinux-main)
vmlinux-lds := arch/$(ARCH)/kernel/vmlinux.lds
vmlinux-all表示搜有构成内核映像文件vmlinux的目标文件,可知这些目标文件的顺序为:head-y、init-y、core-y、libs-y、drivers-y、net-y,即arch/arm/kernel/head.o、arch/arm/kernel/init_task.o、init/build-in.o、usr/built-in.o等。
vmlinux-lds := arch/$(ARCH)/kernel/vmlinux.lds表示连接脚本为arch/$(ARCH)/kernel/vmlinux.lds,它由arch/arm/kernel/vmlinux.lds.S文件生成,规则在scripts/Makefile.build中,如下所示:
$(obj)/%.lds: $(src)/%.lds.S FORCE
$(call if_changed_dep,cpp_lds_S)
现将生成的arch/arm/kernel/vmlinux.lds摘录如下:
使用make menuconfig ARCH=arm CROSS_COMPILE=arm-linux-,需要安装Ncurses libraries
以一个例子说明config条目格式,下面代码选自fs/Kconfig。
config JFFS2_FS_XATTR
bool "JFFS2 XATTR support (EXPERIMENTAL)"
depends on JFFS2_FS && EXPERIMENTAL
default n
help
Extended attributes are name:value pairs associated with inodes by
the kernel or by users (see the attr(5) manual page, or visit
<http://acl.bestbits.at/> for details).
If unsure, say N.
1.引导阶段代码分析
ENTRY(stext)
msr cpsr_c, #PSR_F_BIT | PSR_I_BIT | SVC_MODE @ ensure svc(管理) mode
@ and irqs disabled
mrc p15, 0, r9, c0, c0 @ get processor id,存入r9
bl __lookup_processor_type @ r5=procinfo r9=cpuid
movs r10, r5 @ invalid processor (r5=0)?
beq __error_p @ yes, error 'p'
bl __lookup_machine_type @ r5=machinfo
movs r8, r5 @ invalid machine (r5=0)?
beq __error_a @ yes, error 'a'
内核映像中,定义了若干个proc_info_list结构(它的结构原型在include /asm-arm/procinfo.h中定义),它表示支持的CPU。对于ARM架构的CPU,这些结构体的源码在arch/arm/mm/目录下,比如proc-arm920.S中的如下代码,它表示arm920CPU的proc_info_list结构。
.section ".proc.info.init", #alloc, #execinstr
.type __arm920_proc_info,#object
__arm920_proc_info:
.long 0x41009200
.long 0xff00fff0
……
不同的proc_info_list结构被用来支持不同的CPU,它们都是定义在.section ".proc.info.init"。在连接内核时,这些结构被组织在一起,开始地址为__proc_info_begin,结束地址为__proc_info_end,在arch/arm/kernel/vmlinux.lds.S
__proc_info_begin = .;
*(.proc.info.init)
__proc_info_end = .;
__lookup_processor_type函数就是根据前面读出的processor id(r9),从proc_info_list结构中找出匹配的,arch/arm/kernel/head-common.S
/*
* Read processor ID register (CP#15, CR0), and look up in the linker-built
* supported processor list. Note that we can't use the absolute addresses
* for the __proc_info lists since we aren't running with the MMU on
* (and therefore, we are not in the correct address space). We have to
* calculate the offset.
*
* r9 = cpuid
* Returns:
* r3, r4, r6 corrupted
* r5 = proc_info pointer in physical address space
* r9 = cpuid (preserved)
*/
__lookup_processor_type:
adr r3, 3f
ldmda r3, {r5 - r7}
sub r3, r3, r7 @ get offset between virt&phys
add r5, r5, r3 @ convert virt addresses to
add r6, r6, r3 @ physical address space
1: ldmia r5, {r3, r4} @ value, mask
and r4, r4, r9 @ mask wanted bits
teq r3, r4
beq 2f
add r5, r5, #PROC_INFO_SZ @ sizeof(proc_info_list)
cmp r5, r6
blo 1b
mov r5, #0 @ unknown processor
2: mov pc, lr
ENDPROC(__lookup_processor_type)
……
/*
* Look in <asm/procinfo.h> and arch/arm/kernel/arch.[ch] for
* more information about the __proc_info and __arch_info structures.
*/
.long __proc_info_begin
.long __proc_info_end
3: .long .
在arch/arm/mm/Makefile中,它表示需要配置CONFIG_CPU_ARM920T(配置菜单中,System Type->ARM system type)。
obj-$(CONFIG_CPU_ARM920T) += proc-arm920.o
内核中对于每种支持的开发板都会使用宏MACHINE_START、MACHINE_END来定义一个machine_desc结构,它定义了开发板相关的一些属性及函数。
MACHINE_START(S3C2440, "SMDK2440")
/* Maintainer: Ben Dooks <ben@fluff.org> */
.phys_io = S3C2410_PA_UART,
.io_pg_offst = (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc,
.boot_params = S3C2410_SDRAM_PA + 0x100,
.init_irq = s3c24xx_init_irq,
.map_io = smdk2440_map_io,
.init_machine = smdk2440_machine_init,
.timer = &s3c24xx_timer,
MACHINE_END
MACHINE_START和MACHINE_END在arch/arm/include/asm/mach/arch.h文件中定义,如下:
#define MACHINE_START(_type,_name) \
static const struct machine_desc __mach_desc_##_type \
__used \
__attribute__((__section__(".arch.info.init"))) = { \
.nr = MACH_TYPE_##_type, \
.name = _name,
#define MACHINE_END \
};
所以上一段代码展开来就是:
static const struct machine_desc __mach_desc_S3C2440 \
__used \
__attribute__((__section__(".arch.info.init"))) = { \
.nr = MACH_TYPE_S3C2440, \
.name = "SMDK2440",
……
};
其中的MACH_TYPE_S3C2440在arch/arm/tools/mach-types中定义,machine_desc的结果在arch/arm/include/asm/mach/arch.h中定义。所有的machine_desc结构都处于".arch.info.init"段中,在连接内核时,他们被组织在一起,开始地址为__arch_info_begin,结束地址为__arch_info_end。这可以从连接脚本文件/arch/arm/kernel/vmlinux.lds
__arch_info_begin = .;
*(.arch.info.init)
__arch_info_end = .;
__lookup_machine_type函数的代码如下:
3: .long .
.long __arch_info_begin
.long __arch_info_end
……
/*
* Lookup machine architecture in the linker-build list of architectures.
* Note that we can't use the absolute addresses for the __arch_info
* lists since we aren't running with the MMU on (and therefore, we are
* not in the correct address space). We have to calculate the offset.
*
* r1 = machine architecture number
* Returns:
* r3, r4, r6 corrupted
* r5 = mach_info pointer in physical address space
*/
__lookup_machine_type:
adr r3, 3b
ldmia r3, {r4, r5, r6}
sub r3, r3, r4 @ get offset between virt&phys
add r5, r5, r3 @ convert virt addresses to
add r6, r6, r3 @ physical address space
1: ldr r3, [r5, #MACHINFO_TYPE] @ get machine type
teq r3, r1 @ matches loader number?
beq 2f @ found
add r5, r5, #SIZEOF_MACHINE_DESC @ next machine_desc
cmp r5, r6
blo 1b
mov r5, #0 @ unknown machine
2: mov pc, lr
ENDPROC(__lookup_machine_type)
2.start_kernel函数部分代码分析
进入start_kernel函数(在init/main.c)之后,如果在串口上没有看到内核的启动信息,一般有两种原因:Bootloader传入的命令参数不对,或者是setup_arch函数(在arch/arm/kernel/setup.c中)针对开发板的设置不对。
(1)setup_arch函数分析
void __init setup_arch(char **cmdline_p)
{
struct tag *tags = (struct tag *)&init_tags;
struct machine_desc *mdesc;
char *from = default_command_line;
unwind_init();
setup_processor();
mdesc = setup_machine(machine_arch_type);
machine_name = mdesc->name;
if (mdesc->soft_reboot)
reboot_setup("s");
if (__atags_pointer)
tags = phys_to_virt(__atags_pointer);
else if (mdesc->boot_params)
tags = phys_to_virt(mdesc->boot_params);
/*
* If we have the old style parameters, convert them to
* a tag list.
*/
if (tags->hdr.tag != ATAG_CORE)
convert_to_tag_list(tags);
if (tags->hdr.tag != ATAG_CORE)
tags = (struct tag *)&init_tags;
if (mdesc->fixup)
mdesc->fixup(mdesc, tags, &from, &meminfo);
if (tags->hdr.tag == ATAG_CORE) {
if (meminfo.nr_banks != 0)
squash_mem_tags(tags);
save_atags(tags);
parse_tags(tags);
}
init_mm.start_code = (unsigned long) _text;
init_mm.end_code = (unsigned long) _etext;
init_mm.end_data = (unsigned long) _edata;
init_mm.brk = (unsigned long) _end;
memcpy(boot_command_line, from, COMMAND_LINE_SIZE);
boot_command_line[COMMAND_LINE_SIZE-1] = '\0';
parse_cmdline(cmdline_p, from);
paging_init(mdesc);
request_standard_resources(&meminfo, mdesc);
#ifdef CONFIG_SMP
smp_init_cpus();
#endif
cpu_init();
/*
* Set up various architecture-specific pointers
*/
init_arch_irq = mdesc->init_irq;
system_timer = mdesc->timer;
init_machine = mdesc->init_machine;
#ifdef CONFIG_VT
#if defined(CONFIG_VGA_CONSOLE)
conswitchp = &vga_con;
#elif defined(CONFIG_DUMMY_CONSOLE)
conswitchp = &dummy_con;
#endif
#endif
early_trap_init();
}
(2)paging_init函数分析
paging_init -> devicemaps_init -> mdesc->map_io()
static void __init smdk2440_map_io(void)
{
s3c24xx_init_io(smdk2440_iodesc, ARRAY_SIZE(smdk2440_iodesc));
s3c24xx_init_clocks(16934400);
s3c24xx_init_uarts(smdk2440_uartcfgs, ARRAY_SIZE(smdk2440_uartcfgs));
}
注意要修改s3c24xx_init_clocks,这个频率在YLP2440上是12MHz。应该改为12000000。
(3)console_init函数分析
void __init console_init(void)
{
initcall_t *call;
/* Setup the default TTY line discipline. */
tty_ldisc_begin();
/*
* set up the console device so that later boot sequences can
* inform about problems etc..
*/
call = __con_initcall_start;
while (call < __con_initcall_end) {
(*call)();
call++;
}
}
它调用地址范围__con_initcall_start至__con_initcall_end 之间的定义的每个函数,这些函数使用con_initcall宏来指定。
*博客内容为网友个人发布,仅代表博主个人观点,如有侵权请联系工作人员删除。