1、Linux 内核开发框架学习一、 常见 linux 内核文件的区别1、 vmlinux 编译出来的最原始的内核文件,未压缩。2、 zImagevmlinux 经过 gzip 压缩后的文件。3、 bzImage bz 表示“big zImage”,不是用 bzip2 压缩的。两者的不同之处在于,zImage 解压缩内核到低端内存(第一个 640K), bzImage 解压缩内核到高端内存(1M 以上)。如果内核比较小,那么采用 zImage 或 bzImage 都行,如果比较大应该用 bzImage。4、 uImageU-boot 专用的映像文件,它是在 zImage 之前加上一个长度为 0x4
2、0 的 tag。5、 vmlinuzbzImage/zImage 文件的拷贝或指向 bzImage/zImage 的链接。6、 initrd“initial ramdisk”的简写。一般被用来临时的引导硬件到实际内核 vmlinuz 能够接管并继续引导的状态。二、 linux 内核源码目录结构1、 arch 目录包括了所有和体系结构相关的核心代码。它下面的每一个子目录都代表一种 Linux 支持的体系结构,例如 i386 就是 Intel CPU 及与之相兼容体系结构的子目录。 PC 机一般都基于此目录。 2、 include 目录包括编译核心所需要的大部分头文件,例如与平台无关的头文件在 i
3、nclude/linux 子目录下。 3、 init 目录包含核心的初始化代码(不是系统的引导代码) ,有 main.c 和 Version.c 两个文件。这是研究核心如何工作的好起点。 4、 mm 目录包含了所有的内存管理代码。与具体硬件体系结构相关的内存管理代码位于arch/*/mm 目录下。 5、 drivers 目录是系统中所有的设备驱动程序。它又进一步划分成几类设备驱动,每一种有对应的子目录,如声卡的驱动对应于 drivers/sound。 6、 ipc 目录包含了核心进程间的通信代码。 7、 modules 目录存放了已建好的、可动态加载的模块。 8、 fs 目录存放 Linux
4、支持的文件系统代码。不同的文件系统有不同的子目录对应,如 ext3 文件系统对应的就是 ext3 子目录。 9、 Kernel 内核管理的核心代码放在这里。同时与处理器结构相关代码都放在 arch/*/kernel 目录下。 10、 net 目录里面是核心的网络部分代码,其每个子目录对应于网络的一个方面。 11、 lib 目录包含了核心的库代码,不过与处理器结构相关的库代码被放在 arch/*/lib/目录下。 12、 scripts 目录包含用于配置核心的脚本文件。 13、 documentation 目录该目录是对其它每个目录作用的具体说明。三、 Uboot 加载内核的流程uboot 通过
5、执行 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, boot_fn = boot_osimages.os.os; / 选择启动函数,即 do_bootm_linuxboot_fn(0, argc, argv, / 执行 do_bootm_linuxint do_bootm_linux(int flag, int argc,
6、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 准备以
7、下准备:1 CPU 寄存器的设置: R00;R1Machine 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 struc
8、t 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;
9、/* 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_nameIH_NMLEN; /* Image Name */ image_header_t;四、 linux 内核的配置机制及其编译过程1、 配置系统的基本结构Linux 内核的配置系统由三个部分组成,分别是:1) Makef
10、ile:分布在 Linux 内核源代码根目录及各层目录中,定义 Linux 内核的编译规则;2) 配置文件(config.in(2.4 内核,2.6 内核) ):给用户提供配置选择的功能;3) 配置工具:包括配置命令解释器(对配置脚本中使用的配置命令进行解释)和配置用户界面(提供基于字符界面、基于 Ncurses 图形界面以及基于 Xwindows 图形界面的用户配置界面,各自对应于 Make config、Make menuconfig 和 make xconfig) 。2、 Linux 内核配置相关的文件1) Linux 内核根目录下的 scripts 文件夹:scripts 文件夹存放的
11、是跟 make menuconfig 配置界面的图形绘制相关的文件,我们作为使用者无需关心这个文件夹的内容。2) arch/$ARCH/Kconfig 文件、各层目录下的 Kconfig 文件:当我们执行 make menuconfig 命令出现上述蓝色配置界面以前,系统帮我们做了以下工作:首先系统会读取 arch/$ARCH/目录下的 Kconfig 文件生成整个配置界面选项(Kconfig 是整个 linux 配置机制的核心) ,默认生成的界面是所有参数都是没有值的。ARCH 环境变量的值由 linux 内核根目录下的 makefile 文件决定的,在 makefile 下有此环境变量的定
12、义: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 文件作为内核的默认选项,我们一般会根据开发板的类型从中选取一个与我们开发板最接近
13、的系列到 Linux 内核根目录下。.config文件与我们的板子并不是完全匹配,这时我们可以选择直接修改.config 文件然后执行 make menuconfig 命令读取新的选项。.config 文件与我们的板子并不是完全匹配,这时我们可以选择直接修改.config 文件然后执行 make menuconfig 命令读取新的选项。5) Linux 内核根目录下的 include/generated/autoconf.h 文件:当你保存 make menuconfig 选项时,系统会除了会自动更新.config 外,还会将所有的选项以宏的形式保存在 Linux 内核根目录下的 includ
14、e/generated/autoconf.h文件下。内核中的源代码就都会包含以上.h 文件,跟宏的定义情况进行条件编译。当我们需要对一个文件整体选择如是否编译时,还需要修改对应的 makefile 文件。最后我们会发现,整个 linux 内核配置过程中,留给用户的接口其实只有各层Kconfig、 makefile 文件以及对应的源文件。比如我们如果想要给内核增加一个功能,并且通过 make menuconfig 控制其生成过程:首先需要做的工作是:修改对应目录下的 Kconfig 文件,按照 Kconfig 语法增加对应的选项;其次执行 make menuconfig 选择编译进内核或者不编译
15、进内核,或者编译为模块,.config 文件和 autoconf.h 文件会自动生成;再次修改对应目录下的 makefile 文件完成编译选项的添加;最后执行 make zImage 命令进行编译。3、 Kconfig 文件分析分布在各目录下的 Kconfig 构成了一个分布式的内核配置数据库,每个 Kconfig 分别描述了所属目录源文件相关的内核配置菜单。在内核配置 make menuconfig(或 xconfig 等) 时,从 Kconfig 中读出配置菜单,用户配置完后保存到.config( 在顶层目录下生成)中。在内核编译时,主 Makefile 调用这个.config,就知道了用
16、户对内核的配置情况。上面的内容说明:Kconfig 就是对应着内核的配置菜单。假如要想添加新的驱动到内核的源码中,可以通过修改 Kconfig 来增加对我们驱动的配置菜单,这样就有途径选择我们的驱动,假如想使这个驱动被编译,还要修改该驱动所在目录下的 Makefile。Kconfig 的语法结构。Kconfig每个菜单项都有一个关键字标识,最常见的就是 config。语法:config symboloptionssymbol 就是新的菜单项,options 是在这个新的菜单项下的属性和选项其中 options 部分有:1) 类型定义:每个 config 菜单项都要有类型定义,bool:布尔类型
17、, tristate 三态:内建、模块、移除, string:字符串, hex:十六进制, integer:整型例如 config HELLO_MODULEbool “hello test module“bool 类型的只能选中或不选中,tristate 类型的菜单项多了编译成内核模块的选项,假如选择编译成内核模块,则会在.config 中生成一个 CONFIG_HELLO_MODULE=m 的配置,假如选择内建,就是直接编译成内核影响,就会在.config 中生成一个 CONFIG_HELLO_MODULE=y的配置.2) 依赖型定义 depends on 或 requires指此菜单的出现
18、是否依赖于另一个定义config HELLO_MODULEbool “hello test module“depends on ARCH_PXA这个例子表明 HELLO_MODULE 这个菜单项只对 XScale 处理器有效,即只有在选择了ARCH_PXA, 该菜单才可见(可配置)。3) 帮助性定义只是增加帮助用关键字 help 或-help-4、 内核的 Makefile 文件分析内核的 Makefile 分为 5 个组成部分: 1) Makefile 最顶层的 Makefile 2) .config 内核的当前配置文档,编译时成为顶层 Makefile 的一部分3) arch/$(ARCH
19、)/Makefile 和体系结构相关的 Makefile 4) s/ Makefile.* 一些 Makefile 的通用规则 5) kbuild Makefile 各级目录下的大概约 500 个文档,编译时根据上层 Makefile传下来的宏定义和其他编译规则,将源代码编译成模块或编入内核。顶层的 Makefile 文档读取 .config 文档的内容,并总体上负责 build 内核和模块。Arch Makefile 则提供补充体系结构相关的信息。 s 目录下的 Makefile 文档包含了任何用来根据kbuild Makefile 构建内核所需的定义和规则。(其中.config 的内容是在
20、 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.ldsexport KBUILD_VMLINUX_OBJS := $(vmlinux-all)/ 内核需要添加的模块ini
21、t-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.o5、 Vmlinux.lds 文件分析位于 arch/arm/kernel/vmlinux.ldsOUTPUT_ARCH(arm) /* 输出格式 */ENTRY(stext) /* 定义_start 作为入口点 */jiffies = jiffies
22、_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)_arc
23、h_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) *(.initcal
24、l1.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
25、 = .;_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) *(.mem
26、init.data) . = ALIGN(8); _ctors_start = .; *(.ctors) _ctors_end = .; *(.init.rodata) *(.cpuinit.rodata) *(.meminit.rodata). = ALIGN(1 18) _attribute_(_section_(“.arch.info.init“)表明该结构体在并以后存放的位置。在链接文件链接脚本文件 arch/arm/kernel/vmlinux.lds 中SECTIONS#ifdef CONFIG_XIP_KERNEL. = XIP_VIRT_ADDR(CONFIG_XIP_PHYS
27、_ADDR);#else. = PAGE_OFFSET + TEXT_OFFSET;#endif.text.head : _stext = .;_sinittext = .;*(.text.head).init : /* Init code and data */INIT_TEXT_einittext = .;_proc_info_begin = .;*(.proc.info.init)_proc_info_end = .;_arch_info_begin = .;*(.arch.info.init)_arch_info_end = .;在_arch_info_begin 和 _arch_in
28、fo_end 之间存放了 linux 内核所支持的所有平台对应的 machine_desc 结构体。.long _proc_info_begin.long _proc_info_end3: .long .long _arch_info_begin.long _arch_info_end_lookup_machine_type:adr r3, 3b /r3 存储的是 标号 3 的物理地址/R4=标号 3 处的虚拟地址,r5=_arch_info_begin,r6=_arch_info_end 。ldmia r3, r4, r5, r6sub r3, r3, r4 get offset betwe
29、en virt union struct tag_core core; struct tag_mem32 mem; struct tag_videotext videotext; struct tag_ramdisk ramdisk; struct tag_initrd initrd; struct tag_serialnr serialnr; struct tag_revision revision; struct tag_videolfb videolfb; struct tag_cmdline cmdline; struct tag_acorn acorn; struct tag_mem
30、clk memclk; u; ;参数结构体包括两个部分,一个是 tag_header 结构体,一个是 u 联合体。tag_header 结构体的定义如下: struct tag_header u32 size; u32 tag; ; 其中 size:表示整个 tag 结构体的大小(用字的个数来表示,而不是字节的个数),等于tag_header 的大小加上 u 联合体的大小,例如,参数 结构体 ATAG_CORE 的 size=(sizeof(tag-tag_header)+sizeof(tag-u.core)2,一般通过函数tag_size(struct * tag_xxx)来获得每个参数结构
31、体的 size 。其中 tag:表示整个 tag 结构体的标记,如: ATAG_CORE等。 _vet_atags:tst r2, #0x3 /r2 指向该参数链 表的起始位置,此处判断它是否字对齐bne 1fldr r5, r2, #0 /获取第一个 tag 结构的 size/#define ATAG_CORE_SIZE (2*4 + 3*4) 2) 判断该 tag 的长度是否合法subs r5, r5, #ATAG_CORE_SIZEbne 1fldr r5, r2, #4 /获取第一个 tag 结构体的标记,ldr r6, =ATAG_CORE cmp r5, r6 /判断第一个 tag
32、 结构体的标记是不是 ATAG_COREbne 1f mov pc, lr /正常退出1: mov r2, #0mov pc, lr /参数连表不正确ENDPROC(_vet_atags)/*/_create_page_tables/*/.macro pgtbl, rdldr rd, =(KERNEL_RAM_PADDR - 0x4000).endm_create_page_tables:/r4 = 0x30004000 这是转换表的物理基地址,最终将写入 CP15 的寄存器 2,C2 。/这个值必须是 16K 对齐的。pgtbl r4 /为内核代码存储区域创建页表,首先将内核起始地址-0x4
33、000 到内核起始地址之间的16K /存储器清 0,将创建的页表存于此处。mov r0, r4mov r3, #0add r6, r0, #0x40001: str r3, r0, #4str r3, r0, #4str r3, r0, #4str r3, r0, #4teq r0, r6bne 1b/从 proc_info_list 结构中获取字段_cpu_mm_mmu_flags ,该字段包含了存储空间访问权限 /等。此处指令执行之后 r7=0x00000c1eldr r7, r10, #PROCINFO_MM_MMUFLAGS mm_mmuflags/*此处建立一个物理地址到物理地址的平
34、板映射,这个映射将在函数 paging_init(). 被清除。r6 = 0x300 r3 = 0x30000c1e 0x30004c00=0x30000c1e*/mov r6, pc, lsr #20 start of kernel sectionorr r3, r7, r6, lsl #20 flags + kernel basestr r3, r4, r6, lsl #2 /字对齐/*MMU 是通过 C2 中基地址(高 18 位)与虚拟地址的高 12 位组合成物理地址,在转换表中查找地址条目。R4 中存放的就是这个基地址 0x30004000。下面通过两次获取虚拟地址KERNEL_STA
35、RT 的高 12 位。KERNEL_START 是内核存放的起始地址,为 0X30008000。*/add r0, r4, #(KERNEL_START /* location in binary */. = PAGE_OFFSET + TEXT_OFFSET;#else. = ALIGN(THREAD_SIZE);_data_loc = .;#endif.data : AT(_data_loc) /此处数据存储在上面_data_loc 处。_data = .; /* address in memory */*(.data.init_task).bss : _bss_start = .; /*
36、 BSS */*(.bss)*(COMMON)_end = .;init_thread_union 是 init 进程的基地址. 在 arch/arm/kernel/init_task.c 中:00033: union thread_union init_thread_union00034: _attribute_(_section_(“.init.task“) =00035: INIT_THREAD_INFO(init_task) ; 对照 vmlnux.lds.S 中,我们可以知道 init task 是存放在 .data 段的开始 8k, 并且是THREAD_SIZE(8k)对齐的*/_
37、mmap_switched:adr r3, _switch_data + 4ldmia r3!, r4, r5, r6, r7 cmp r4, r5 Copy data segment if needed1: cmpne r5, r6 /将 _data_loc 处数据搬移到_data 处ldrne fp, r4, #4strne fp, r5, #4bne 1bmov fp, #0 /清除 BSS 段内容1: cmp r6, r7 strcc fp, r6,#4bcc 1bldmia r3, r4, r5, r6, r7, spstr r9, r4 Save processor IDstr r
38、1, r5 Save machine typestr r2, r6 Save atags pointerbic r4, r0, #CR_A Clear A bitstmia r7, r0, r4 Save control register valuesb start_kernel /程序跳转到函数 start_kernel 进入 C 语言部分。ENDPROC(_mmap_switched)/*/8、 Linux 内核的启动过程在 bootloader 将 Linux 内核映像拷贝到 RAM 以后,可以通过下例代码启动 Linux内核:call_linux(0, machine_type, kernel_params_base)。其中,machine_tpye 是 bootloader 检测出来的处理器类型, kernel_params_base是启动参数在 RAM 的地址。通过这种方式将 Linux 启动需要的参数从 bootloader 传递到内核。Linux 内核有两种映像:一种是非压缩内核,叫 Image,另一种是它的压缩版本,