内核启动时的各参数详解

.config

内核的.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

 ATAGS

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

cmdline

前面两个宏在文件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);
}

 填充cmdline到atags

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);
}

 自动ZRELADDR

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_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) 存储在那里

固定ZRELADDR


对于某些特定平台,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

链接地址一般等于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

 

你可能感兴趣的:(Linux内核之启动流程,驱动开发)