资源描述
Linux内核开发框架学习
一、 常见linux内核文件的区别
1、 vmlinux
编译出来的最原始的内核文件,未压缩。
2、 zImage
vmlinux经过gzip压缩后的文件。
3、 bzImage
bz表示“big zImage”,不是用bzip2压缩的。两者的不同之处在于,zImage解压缩内核到低端内存(第一个640K),bzImage解压缩内核到高端内存(1M以上)。如果内核比较小,那么采用zImage或bzImage都行,如果比较大应该用bzImage。
4、 uImage
U-boot专用的映像文件,它是在zImage之前加上一个长度为0x40的tag。
5、 vmlinuz
bzImage/zImage文件的拷贝或指向bzImage/zImage的链接。
6、 initrd
“initial ramdisk”的简写。一般被用来临时的引导硬件到实际内核vmlinuz能够接管并继续引导的状态。
二、 linux内核源码目录结构
1、 arch目录
包括了所有和体系结构相关的核心代码。它下面的每一个子目录都代表一种Linux支持的体系结构,例如i386就是Intel CPU及与之相兼容体系结构的子目录。PC机一般都基于此目录。
2、 include目录
包括编译核心所需要的大部分头文件,例如与平台无关的头文件在include/linux子目录下。
3、 init目录
包含核心的初始化代码(不是系统的引导代码),有main.c和Version.c两个文件。这是研究核心如何工作的好起点。
4、 mm目录
包含了所有的内存管理代码。与具体硬件体系结构相关的内存管理代码位于arch/*/mm目录下。
5、 drivers目录
是系统中所有的设备驱动程序。它又进一步划分成几类设备驱动,每一种有对应的子目录,如声卡的驱动对应于drivers/sound。
6、 ipc目录
包含了核心进程间的通信代码。
7、 modules目录
存放了已建好的、可动态加载的模块。
8、 fs目录
存放Linux支持的文件系统代码。不同的文件系统有不同的子目录对应,如ext3文件系统对应的就是ext3子目录。
9、 Kernel内核
管理的核心代码放在这里。同时与处理器结构相关代码都放在arch/*/kernel目录下。
10、 net目录
里面是核心的网络部分代码,其每个子目录对应于网络的一个方面。
11、 lib目录
包含了核心的库代码,不过与处理器结构相关的库代码被放在arch/*/lib/目录下。
12、 scripts目录
包含用于配置核心的脚本文件。
13、 documentation目录
该目录是对其它每个目录作用的具体说明。
三、 Uboot加载内核的流程
uboot通过执行bootm 0x81000000指令来启动linux。
int do_bootm (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
{
bootm_start(cmdtp, flag, argc, argv);
ret = bootm_load_os(images.os, &load_end, 1);
boot_fn = boot_os[images.os.os]; // 选择启动函数,即do_bootm_linux
boot_fn(0, argc, argv, &images); // 执行do_bootm_linux
}
int do_bootm_linux(int flag, int argc, char *argv[], bootm_headers_t *images)
{
int machid = bd->bi_arch_number;
void (*theKernel)(int zero, int arch, uint params);
// theKernel指向内核入口地址
theKernel = (void (*)(int, int, uint))images->ep;
// 在启动内核之前做一些清理工作
cleanup_before_linux ();
// 启动内核
theKernel (0, machid, bd->bi_boot_params);
}
在启动内核之前,uboot必须为uImage准备以下准备:
1. CPU 寄存器的设置:
R0=0;
R1=Machine ID(即Machine Type Number,定义在linux/arch/arm/tools/mach-types);
R2=内核启动参数在 RAM 中起始基地址;
2. CPU 模式:
必须禁止中断(IRQs和FIQs);
CPU 必须 SVC 模式;
3. Cache 和 MMU 的设置:
MMU 必须关闭;
指令 Cache 可以打开也可以关闭;
数据 Cache 必须关闭;
uImage中64字节的头信息结构体如下:
#define IH_NMLEN 32 /* Image Name Length */
typedef struct image_header {
uint32_t ih_magic; /* Image Header Magic Number */
uint32_t ih_hcrc; /* Image Header CRC Checksum */
uint32_t ih_time; /* Image Creation Timestamp */
uint32_t ih_size; /* Image Data Size */
uint32_t ih_load; /* Data Load Address */
uint32_t ih_ep; /* Entry Point Address */
uint32_t ih_dcrc; /* Image Data CRC Checksum */
uint8_t ih_os; /* Operating System */
uint8_t ih_arch; /* CPU architecture */
uint8_t ih_type; /* Image Type */
uint8_t ih_comp; /* Compression Type */
uint8_t ih_name[IH_NMLEN]; /* Image Name */
} image_header_t;
四、 linux内核的配置机制及其编译过程
1、 配置系统的基本结构
Linux内核的配置系统由三个部分组成,分别是:
1) Makefile:分布在 Linux 内核源代码根目录及各层目录中,定义 Linux 内核的编译规则;
2) 配置文件(config.in(2.4内核,2.6内核)):给用户提供配置选择的功能;
3) 配置工具:包括配置命令解释器(对配置脚本中使用的配置命令进行解释)和配置用户界面(提供基于字符界面、基于 Ncurses 图形界面以及基于 Xwindows 图形界面的用户配置界面,各自对应于 Make config、Make menuconfig 和 make xconfig)。
2、 Linux内核配置相关的文件
1) Linux内核根目录下的scripts文件夹:
scripts文件夹存放的是跟make menuconfig配置界面的图形绘制相关的文件,我们作为使用者无需关心这个文件夹的内容。
2) arch/$ARCH/Kconfig文件、各层目录下的Kconfig文件:
当我们执行make menuconfig命令出现上述蓝色配置界面以前,系统帮我们做了以下工作:首先系统会读取arch/$ARCH/目录下的Kconfig文件生成整个配置界面选项(Kconfig是整个linux配置机制的核心),默认生成的界面是所有参数都是没有值的。ARCH环境变量的值由linux内核根目录下的makefile文件决定的,在makefile下有此环境变量的定义:ARCH ?= arm或者通过 make ARCH=arm menuconfig命令来指定。
3) Linux内核根目录下的makefile文件、各层目录下的makefile文件:
4) Linux内核根目录下的的.config文件、arm/$ARCH/下的config文件:
默认配置选项存放在arch/$ARCH/configs下,对于arm来说就是arch/arm/configs文件夹,此文件夹中有许多选项,系统会读取哪个呢?内核默认会读取linux内核根目录下.config文件作为内核的默认选项,我们一般会根据开发板的类型从中选取一个与我们开发板最接近的系列到Linux内核根目录下。.config文件与我们的板子并不是完全匹配,这时我们可以选择直接修改.config文件然后执行make menuconfig命令读取新的选项。.config文件与我们的板子并不是完全匹配,这时我们可以选择直接修改.config文件然后执行make menuconfig命令读取新的选项。
5) Linux内核根目录下的 include/generated/autoconf.h文件:
当你保存make menuconfig选项时,系统会除了会自动更新.config外,还会将所有的选项以宏的形式保存在Linux内核根目录下的 include/generated/autoconf.h文件下。内核中的源代码就都会包含以上.h文件,跟宏的定义情况进行条件编译。当我们需要对一个文件整体选择如是否编译时,还需要修改对应的makefile文件。
最后我们会发现,整个linux内核配置过程中,留给用户的接口其实只有各层Kconfig、makefile文件以及对应的源文件。
比如我们如果想要给内核增加一个功能,并且通过make menuconfig控制其生成过程:
首先需要做的工作是:修改对应目录下的Kconfig文件,按照Kconfig语法增加对应的选项;
其次执行make menuconfig选择编译进内核或者不编译进内核,或者编译为模块,.config文件和autoconf.h文件会自动生成;
再次修改对应目录下的makefile文件完成编译选项的添加;
最后执行make zImage命令进行编译。
3、 Kconfig文件分析
分布在各目录下的Kconfig构成了一个分布式的内核配置数据库,每个Kconfig分别描述了所属目录源文件相关的内核配置菜单。在内核配置make menuconfig(或xconfig等)时,从Kconfig中读出配置菜单,用户配置完后保存到.config(在顶层目录下生成)中。在内核编译时,主Makefile调用这个.config,就知道了用户对内核的配置情况。
上面的内容说明:Kconfig就是对应着内核的配置菜单。假如要想添加新的驱动到内核的源码中,可以通过修改Kconfig来增加对我们驱动的配置菜单,这样就有途径选择我们的驱动,假如想使这个驱动被编译,还要修改该驱动所在目录下的Makefile。
Kconfig的语法结构。
Kconfig
每个菜单项都有一个关键字标识,最常见的就是config。
语法:
config symbol
options
<!--[if !supportLineBreakNewLine]-->
<!--[endif]-->
symbol就是新的菜单项,options是在这个新的菜单项下的属性和选项
其中options部分有:
1) 类型定义:
每个config菜单项都要有类型定义,bool:布尔类型, tristate三态:内建、模块、移除, string:字符串, hex:十六进制, integer:整型
例如config HELLO_MODULE
bool "hello test module"
bool类型的只能选中或不选中,tristate类型的菜单项多了编译成内核模块的选项,假如选择编译成内核模块,则会在.config中生成一个CONFIG_HELLO_MODULE=m的配置,假如选择内建,就是直接编译成内核影响,就会在.config中生成一个CONFIG_HELLO_MODULE=y的配置.
2) 依赖型定义depends on或requires
指此菜单的出现是否依赖于另一个定义
config HELLO_MODULE
bool "hello test module"
depends on ARCH_PXA
这个例子表明HELLO_MODULE这个菜单项只对XScale处理器有效,即只有在选择了ARCH_PXA, 该菜单才可见(可配置)。
3) 帮助性定义
只是增加帮助用关键字help或---help---
<!--[if !supportLineBreakNewLine]-->
<!--[endif]-->
4、 内核的Makefile 文件分析
内核的Makefile分为5个组成部分:
1) Makefile 最顶层的Makefile
2) .config 内核的当前配置文档,编译时成为顶层Makefile的一部分
3) arch/$(ARCH)/Makefile 和体系结构相关的Makefile
4) s/ Makefile.* 一些Makefile的通用规则
5) kbuild Makefile 各级目录下的大概约500个文档,编译时根据上层Makefile传下来的宏定义和其他编译规则,将源代码编译成模块或编入内核。
顶层的Makefile文档读取 .config文档的内容,并总体上负责build内核和模块。Arch Makefile则提供补充体系结构相关的信息。 s目录下的Makefile文档包含了任何用来根据kbuild Makefile 构建内核所需的定义和规则。
(其中.config的内容是在make menuconfig的时候,通过Kconfig文档配置的结果)
顶层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/$(SRCARCH)/kernel/vmlinux.lds
export KBUILD_VMLINUX_OBJS := $(vmlinux-all)
// 内核需要添加的模块
init-y := init/
drivers-y := drivers/ pifm/ switch/ sound/ firmware/
net-y := net/
libs-y := lib/
core-y := usr/
head-y := arch/arm/kernel/head$(MMUEXT).o arch/arm/kernel/init_task.o
5、 Vmlinux.lds文件分析
位于arch/arm/kernel/vmlinux.lds
OUTPUT_ARCH(arm) /* 输出格式 */
ENTRY(stext) /* 定义_start作为入口点 */
jiffies = jiffies_64;
SECTIONS
{
. = 0x80000000 + 0x00008000; /* 定义当前段的偏移量 */
.init : { /* Init code and data */
_stext = .;
// 初始化代码段
_sinittext = .;
*(.head.text)
*(.init.text) *(.cpuinit.text) *(.meminit.text)
_einittext = .;
__proc_info_begin = .; *(.proc.info.init) __proc_info_end = .;
__arch_info_begin = .;
*(.arch.info.init)
__arch_info_end = .;
__tagtable_begin = .;
*(.taglist.init)
__tagtable_end = .;
__pv_table_begin = .;
*(.pv_table)
__pv_table_end = .;
. = ALIGN(16); __setup_start = .; *(.init.setup) __setup_end = .;
__initcall_start = .; *(.initcallearly.init) __early_initcall_end = .; *(.initcall0.init) *(.initcall0s.init) *(.initcall1.init) *(.initcall1s.init) *(.initcall2.init) *(.initcall2s.init) *(.initcall3.init) *(.initcall3s.init) *(.initcall4.init) *(.initcall4s.init) *(.initcall5.init) *(.initcall5s.init) *(.initcallrootfs.init) *(.initcall6.init) *(.initcall6s.init) *(.initcall7.init) *(.initcall7s.init) __initcall_end = .;
__con_initcall_start = .; *(.con_initcall.init) __con_initcall_end = .;
__security_initcall_start = .; *(.security_initcall.init) __security_initcall_end = .;
. = ALIGN(4); __initramfs_start = .; *(.init.ramfs) . = ALIGN(8); *(.init.ramfs.info)
__init_begin = _stext;
*(.init.data) *(.cpuinit.data) *(.meminit.data) . = ALIGN(8); __ctors_start = .; *(.ctors) __ctors_end = .; *(.init.rodata) *(.cpuinit.rodata) *(.meminit.rodata)
}
. = ALIGN((1 << 12)); .data..percpu : AT(ADDR(.data..percpu) - 0) { __per_cpu_load = .; __per_cpu_start = .; *(.data..percpu..first) . = ALIGN((1 << 12)); *(.data..percpu..page_aligned) *(.data..percpu..readmostly) *(.data..percpu) *(.data..percpu..shared_aligned) __per_cpu_end = .; }
. = ALIGN((1 << 12));
__init_end = .;
/*
* unwind exit sections must be discarded before the rest of the
* unwind sections get included.
*/
/DISCARD/ : {
*(.ARM.exidx.exit.text)
*(.ARM.extab.exit.text)
*(.ARM.exidx.cpuexit.text)
*(.ARM.extab.cpuexit.text)
}
.text : { /* Real text segment */
_text = .; /* Text and read-only data */
__exception_text_start = .;
*(.exception.text)
__exception_text_end = .;
. = ALIGN(8); *(.text.hot) *(.text) *(.ref.text) *(.devinit.text) *(.devexit.text) *(.text.unlikely)
. = ALIGN(8); __sched_text_start = .; *(.sched.text) __sched_text_end = .;
. = ALIGN(8); __lock_text_start = .; *(.spinlock.text) __lock_text_end = .;
. = ALIGN(8); __kprobes_text_start = .; *(.kprobes.text) __kprobes_text_end = .;
*(.fixup)
*(.gnu.warning)
*(.rodata)
*(.rodata.*)
*(.glue_7)
*(.glue_7t)
. = ALIGN(4);
*(.got) /* Global offset table */
}
. = ALIGN(((1 << 12))); .rodata : AT(ADDR(.rodata) - 0) { __start_rodata = .; *(.rodata) *(.rodata.*) *(__vermagic) *(__markers_strings) *(__tracepoints_strings) } .rodata1 : AT(ADDR(.rodata1) - 0) { *(.rodata1) } . = ALIGN(8); __jump_table : AT(ADDR(__jump_table) - 0) { __start___jump_table = .; *(__jump_table) __stop___jump_table = .; } .pci_fixup : AT(ADDR(.pci_fixup) - 0) { __start_pci_fixups_early = .; *(.pci_fixup_early) __end_pci_fixups_early = .; __start_pci_fixups_header = .; *(.pci_fixup_header) __end_pci_fixups_header = .; __start_pci_fixups_final = .; *(.pci_fixup_final) __end_pci_fixups_final = .; __start_pci_fixups_enable = .; *(.pci_fixup_enable) __end_pci_fixups_enable = .; __start_pci_fixups_resume = .; *(.pci_fixup_resume) __end_pci_fixups_resume = .; __start_pci_fixups_resume_early = .; *(.pci_fixup_resume_early) __end_pci_fixups_resume_early = .; __start_pci_fixups_suspend = .; *(.pci_fixup_suspend) __end_pci_fixups_suspend = .; } .builtin_fw : AT(ADDR(.builtin_fw) - 0) { __start_builtin_fw = .; *(.builtin_fw) __end_builtin_fw = .; } .rio_ops : AT(ADDR(.rio_ops) - 0) { __start_rio_switch_ops = .; *(.rio_switch_ops) __end_rio_switch_ops = .; } __ksymtab : AT(ADDR(__ksymtab) - 0) { __start___ksymtab = .; *(__ksymtab) __stop___ksymtab = .; } __ksymtab_gpl : AT(ADDR(__ksymtab_gpl) - 0) { __start___ksymtab_gpl = .; *(__ksymtab_gpl) __stop___ksymtab_gpl = .; } __ksymtab_unused : AT(ADDR(__ksymtab_unused) - 0) { __start___ksymtab_unused = .; *(__ksymtab_unused) __stop___ksymtab_unused = .; } __ksymtab_unused_gpl : AT(ADDR(__ksymtab_unused_gpl) - 0) { __start___ksymtab_unused_gpl = .; *(__ksymtab_unused_gpl) __stop___ksymtab_unused_gpl = .; } __ksymtab_gpl_future : AT(ADDR(__ksymtab_gpl_future) - 0) { __start___ksymtab_gpl_future = .; *(__ksymtab_gpl_future) __stop___ksymtab_gpl_future = .; } __kcrctab : AT(ADDR(__kcrctab) - 0) { __start___kcrctab = .; *(__kcrctab) __stop___kcrctab = .; } __kcrctab_gpl : AT(ADDR(__kcrctab_gpl) - 0) { __start___kcrctab_gpl = .; *(__kcrctab_gpl) __stop___kcrctab_gpl = .; } __kcrctab_unused : AT(ADDR(__kcrctab_unused) - 0) { __start___kcrctab_unused = .; *(__kcrctab_unused) __stop___kcrctab_unused = .; } __kcrctab_unused_gpl : AT(ADDR(__kcrctab_unused_gpl) - 0) { __start___kcrctab_unused_gpl = .; *(__kcrctab_unused_gpl) __stop___kcrctab_unused_gpl = .; } __kcrctab_gpl_future : AT(ADDR(__kcrctab_gpl_future) - 0) { __start___kcrctab_gpl_future = .; *(__kcrctab_gpl_future) __stop___kcrctab_gpl_future = .; } __ksymtab_strings : AT(ADDR(__ksymtab_strings) - 0) { *(__ksymtab_strings) } __init_rodata : AT(ADDR(__init_rodata) - 0) { *(.ref.rodata) *(.devinit.rodata) *(.devexit.rodata) } __param : AT(ADDR(__param) - 0) { __start___param = .; *(__param) __stop___param = .; . = ALIGN(((1 << 12))); __end_rodata = .; } . = ALIGN(((1 << 12)));
_etext = .; /* End of text and rodata section */
. = ALIGN(8192);
__data_loc = .;
.data : AT(__data_loc) {
_data = .; /* address in memory */
_sdata = .;
/*
* first, the init task union, aligned
* to an 8192 byte boundary.
*/
. = ALIGN(8192); *(.data..init_task)
. = ALIGN((1 << 12)); __nosave_begin = .; *(.data..nosave) . = ALIGN((1 << 12)); __nosave_end = .;
. = ALIGN(32); *(.data..cacheline_aligned)
/*
* The exception fixup table (might need resorting at runtime)
*/
. = ALIGN(32);
__start___ex_table = .;
*(__ex_table)
__stop___ex_table = .;
/*
* and the usual data section
*/
*(.data) *(.ref.data) *(.data..shared_aligned) *(.devinit.data) *(.devexit.data) . = ALIGN(32); __start___tracepoints = .; *(__tracepoints) __stop___tracepoints = .; . = ALIGN(8); __start___verbose = .; *(__verbose) __stop___verbose = .; . = ALIGN(32); . = ALIGN(32);
CONSTRUCTORS
_edata = .;
}
_edata_loc = __data_loc + SIZEOF(.data);
. = ALIGN(0); __bss_start = .; . = ALIGN(0); .sbss : AT(ADDR(.sbss) - 0) { *(.sbss) *(.scommon) } . = ALIGN(0); .bss : AT(ADDR(.bss) - 0) { *(.bss..page_aligned) *(.dynbss) *(.bss) *(COMMON) } . = ALIGN(0); __bss_stop = .;
_end = .;
.stab 0 : { *(.stab) } .stabstr 0 : { *(.stabstr) } .stab.excl 0 : { *(.stab.excl) } .stab.exclstr 0 : { *(.stab.exclstr) } .stab.index 0 : { *(.stab.index) } .stab.indexstr 0 : { *(.stab.indexstr) } .comment 0 : { *(.comment) }
.comment 0 : { *(.comment) }
/* Default discards */
/DISCARD/ : { *(.exit.text) *(.cpuexit.text) *(.memexit.text) *(.exit.data) *(.cpuexit.data) *(.cpuexit.rodata) *(.memexit.data) *(.memexit.rodata) *(.exitcall.exit) *(.discard) *(.discard.*) }
/DISCARD/ : {
*(.alt.smp.init)
}
}
/*
* These must never be empty
* If you have to comment these two assert statements out, your
* binutils is too old (for other reasons as well)
*/
ASSERT((__proc_info_end - __proc_info_begin), "missing CPU support")
ASSERT((__arch_info_end - __arch_info_begin), "no machine record defined")
通过分析vmlinux文件的头信息来查看各个段:
objdump --headers vmlinux
vmlinux: file format elf32-little
Sections:
Idx Name Size VMA LMA File off Algn
0 .note.gnu.build-id 00000024 00000000 00000000 00008000 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
1 .init 000322eb 80008000 80008000 00010000 2**5
CONTENTS, ALLOC, LOAD, CODE
2 .text 003d736c 8003b000 8003b000 00043000 2**6
CONTENTS, ALLOC, LOAD, READONLY, CODE
3 __ksymtab 00005a10 80413000 80413000 0041b000 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
4 __ksymtab_gpl 00003138 80418
展开阅读全文