内核的.config文件里会有如下一些启动选项,来控制内核的启动,比如传参是通过dtb,还是atags,还是两种方式一起组合;内核自解压的位置等
#
# Boot options
#
CONFIG_USE_OF=y
CONFIG_ATAGS=y
# CONFIG_DEPRECATED_PARAM_STRUCT is not set
CONFIG_ZBOOT_ROM_TEXT=0x0
CONFIG_ZBOOT_ROM_BSS=0x0
CONFIG_ARM_APPENDED_DTB=y
CONFIG_ARM_ATAG_DTB_COMPAT=y
CONFIG_ARM_ATAG_DTB_COMPAT_CMDLINE_FROM_BOOTLOADER=y
# CONFIG_ARM_ATAG_DTB_COMPAT_CMDLINE_EXTEND is not set
CONFIG_CMDLINE="rdinit=/busybox/rdinit console=ttyS0,115200 mem=128M user_debug=255 earlyprintk"
CONFIG_CMDLINE_FROM_BOOTLOADER=y
# CONFIG_CMDLINE_EXTEND is not set
# CONFIG_CMDLINE_FORCE is not set
CONFIG_KEXEC=y
CONFIG_ATAGS_PROC=y
CONFIG_CRASH_DUMP=y
CONFIG_AUTO_ZRELADDR=y
Bootloader 将 0 放入寄存器 r0,将 Machine ID 放入寄存器 r1,并将指向 ATAG 的指针放入寄存器 r2。
ATAG 包含物理内存的位置和大小,内核被放置在该内存中的某个位置。只要确保有足够解压内核的空间,它就能从任何地址执行,cmdline就是atag的一个成员
在现代设备树内核中,r2 被重新用作指向物理内存中设备树 (DTB) 的指针。在这种情况下,r1 被忽略。DTB 也可以附加到内核映像后,并且可以选择使用来自 r2 的 ATAG 进行修改。
CONFIG_ARM_APPENDED_DTB=y
CONFIG_ARM_ATAG_DTB_COMPAT=y
前面两个宏在文件arch/arm/boot/compressed/head.S文件中,核心是atags_to_fdt函数,判断到atag_list内有ATAG_CMDLINE后就会跑两个分支:由do_extend_cmdline宏决定
1.merge_fdt_bootargs //合并atag中bootargs和dtb文件中bootargs参数
2.setprop_string //用atag中bootargs参数覆盖dtb文件中的bootarts参数。
int atags_to_fdt(void *atag_list, void *fdt, int total_space)
{
struct tag *atag = atag_list;
/* In the case of 64 bits memory size, need to reserve 2 cells for
* address and size for each bank */
uint32_t mem_reg_property[2 * 2 * NR_BANKS];
int memcount = 0;
int ret, memsize;
/* make sure we've got an aligned pointer */
if ((u32)atag_list & 0x3)
return 1;
/* if we get a DTB here we're done already */
if (*(u32 *)atag_list == fdt32_to_cpu(FDT_MAGIC))
return 0;
/* validate the ATAG */
if (atag->hdr.tag != ATAG_CORE ||
(atag->hdr.size != tag_size(tag_core) &&
atag->hdr.size != 2))
return 1;
/* let's give it all the room it could need */
ret = fdt_open_into(fdt, fdt, total_space);
if (ret < 0)
return ret;
for_each_tag(atag, atag_list) {
if (atag->hdr.tag == ATAG_CMDLINE) {
/* Append the ATAGS command line to the device tree
* command line.
* NB: This means that if the same parameter is set in
* the device tree and in the tags, the one from the
* tags will be chosen.
*/
if (do_extend_cmdline)
merge_fdt_bootargs(fdt,
atag->u.cmdline.cmdline);
else
setprop_string(fdt, "/chosen", "bootargs",
atag->u.cmdline.cmdline);
} else if (atag->hdr.tag == ATAG_MEM) {
if (memcount >= sizeof(mem_reg_property)/4)
continue;
if (!atag->u.mem.size)
continue;
memsize = get_cell_size(fdt);
if (memsize == 2) {
/* if memsize is 2, that means that
* each data needs 2 cells of 32 bits,
* so the data are 64 bits */
uint64_t *mem_reg_prop64 =
(uint64_t *)mem_reg_property;
mem_reg_prop64[memcount++] =
cpu_to_fdt64(atag->u.mem.start);
mem_reg_prop64[memcount++] =
cpu_to_fdt64(atag->u.mem.size);
} else {
mem_reg_property[memcount++] =
cpu_to_fdt32(atag->u.mem.start);
mem_reg_property[memcount++] =
cpu_to_fdt32(atag->u.mem.size);
}
} else if (atag->hdr.tag == ATAG_INITRD2) {
uint32_t initrd_start, initrd_size;
initrd_start = atag->u.initrd.start;
initrd_size = atag->u.initrd.size;
setprop_cell(fdt, "/chosen", "linux,initrd-start",
initrd_start);
setprop_cell(fdt, "/chosen", "linux,initrd-end",
initrd_start + initrd_size);
} else if (atag->hdr.tag == ATAG_SERIAL) {
char serno[16+2];
hex_str(serno, atag->u.serialnr.high);
hex_str(serno+8, atag->u.serialnr.low);
setprop_string(fdt, "/", "serial-number", serno);
}
}
if (memcount) {
setprop(fdt, "/memory", "reg", mem_reg_property,
4 * memcount * memsize);
}
return fdt_pack(fdt);
}
uboot中如此填充cmdline
static void setup_commandline_tag(bd_t *bd, char *commandline)
{
char *p;
if (!commandline)
return;
/* eat leading white space */
for (p = commandline; *p == ' '; p++);
/* skip non-existent command lines so the kernel will still
* use its default command line.
*/
if (*p == '\0')
return;
params->hdr.tag = ATAG_CMDLINE;
params->hdr.size =
(sizeof (struct tag_header) + strlen (p) + 1 + 4) >> 2;
strcpy (params->u.cmdline.cmdline, p);
params = tag_next (params);
}
CONFIG_AUTO_ZRELADDR=y 用来开启自动设置内核自解压地址
ARM Linux 一般都使用压缩的内核,例如 zImage。vmlinux压缩后的 zImage会节省很大部分的空间
通常情况下,解压消耗的时间比从存储介质传输未压缩镜像的时间要短。例如从 NAND Flash 加载内核,就是一种很典型的情况
解压代码首先会确定物理内存的起始位置。在大多数现代平台上,这是通过 Kconfig 选择 AUTO_ZRELADDR 完成的,使能这个配置后,
内核会通过将 PC 寄存器进行 128MB 的对齐的方式来获得物理内存的起始地址,内核总是假设它是在物理内存的第一块的第一部分加载和执行
arch/arm/boot/compressed/head.S
#ifdef CONFIG_AUTO_ZRELADDR
/*
* Find the start of physical memory. As we are executing
* without the MMU on, we are in the physical address space.
* We just need to get rid of any offset by aligning the
* address.
*
* This alignment is a balance between the requirements of
* different platforms - we have chosen 128MB to allow
* platforms which align the start of their physical memory
* to 128MB to use this feature, while allowing the zImage
* to be placed within the first 128MB of memory on other
* platforms. Increasing the alignment means we place
* stricter alignment requirements on the start of physical
* memory, but relaxing it means that we break people who
* are already placing their zImage in (eg) the top 64MB
* of this range.
*/
mov r4, pc
and r4, r4, #0xf8000000
/* Determine final kernel image address. */
add r4, r4, #TEXT_OFFSET
#else
ldr r4, =zreladdr
#endif
TEXT_OFFSET,顾名思义,这是内核 .text 段应位于的位置。.text 段包含可执行代码,因此这就是解压缩后内核的实际起始地址。
TEXT_OFFSET 通常为 0x8000,因此解压后的内核将位于物理内存起始地址 + TEXT_OFFSET 。TEXT_OFFSET 在 arch/arm/Makefile 中定义
0x8000 (32KB) 偏移量是一个惯例,因为通常有一些固定的架构相关的数据放置在 0x00000000 处,例如中断向量
许多旧系统将 ATAG 放置在 0x00000100 处。另外还需要额外的空间,是因为当内核最终启动时,它将从该地址中减去 0x4000(或 LPAE 的 0x5000),并将初始内核页表 (initial kernel page table) 存储在那里
对于某些特定平台,TEXT_OFFSET 将在内存中向后扩展,特别是一些高通平台会将其扩展到 0x00208000,因为物理内存的第一个 0x00200000 (2 MB) 用于与 modern CPU 的共享内存通信。
arch/arm/boot/Makefile:ZRELADDR := $(zreladdr-y)
arch/arm/mach-s3c24xx/Makefile.boot: zreladdr-y += 0x30008000
链接地址一般等于PAGE_OFFSET + TEXT_OFFSET
PAGE_OFFSET一般就是内核空间的起始地址,对于不同架构,不同内核版本,也是差异的,并不是一个固定的值;对于5.4.195公版内核来说
64位机器的话,如果是48bit的虚拟地址,PAGE_OFFSET就是0xffff 0000 0000 0000
arch/arm64/include/asm/memory.h
#define VA_BITS (CONFIG_ARM64_VA_BITS)
#define _PAGE_OFFSET(va) (-(UL(1) << (va)))
#define PAGE_OFFSET (_PAGE_OFFSET(VA_BITS))
32位机器的话,如果是低端3G用户空间,高端1G内核空间 ,PAGE_OFFSET就是0xC000 0000
arch/arm/Kconfig
config PAGE_OFFSET
hex
default PHYS_OFFSET if !MMU
default 0x40000000 if VMSPLIT_1G
default 0x80000000 if VMSPLIT_2G
default 0xB0000000 if VMSPLIT_3G_OPT
default 0xC0000000
内核为了安全,可以把TEXT_OFFSET设为随机的
# The byte offset of the kernel image in RAM from the start of RAM.
ifeq ($(CONFIG_ARM64_RANDOMIZE_TEXT_OFFSET), y)
TEXT_OFFSET := $(shell awk 'BEGIN {srand(); printf "0x%03x000\n", int(512 * rand())}')
else
TEXT_OFFSET := 0x00080000
endif
对于不同的内核版本,不同的机器位数,不同的内存布局,虚拟地址和物理地址的转换都是不一样的;
概括的说:虚拟地址减去内核虚拟地址的一个起始地址;再加上物理地址的起始地址,就是虚拟地址对应的物理地址;内核会自解压到ZRELADDR;ZRELADDR就是链接地址所对应的物理地址上,内核就可以成功启动了
arch/arm/include/asm/memory.h:
#ifndef __virt_to_phys
#define __virt_to_phys(x) ((x) - PAGE_OFFSET + PHYS_OFFSET)
#define __phys_to_virt(x) ((x) - PHYS_OFFSET + PAGE_OFFSET)
#endif