微视linux 根文件系统之二 bootloader(以uboot为例)的准备

本文详细介绍了U-Boot环境下Linux内核及Initrd的加载过程。从初始化内核镜像结构开始,通过读取内核镜像头部获取OS和Initrd的基地址与大小,修正Initrd基地址,并将其移动到新位置。最后,将Initrd地址保存在设备树中,供内核使用。同时,文章还探讨了ramdisk、initrd与initramfs之间的关系。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

自己的理解

初始化linux内核镜像结构

static int bootm_start(cmd_tbl_t *cmdtp, int flag, int argc,
		       char * const argv[])
{
	memset((void *)&images, 0, sizeof(images));
	images.verify = env_get_yesno("verify");

	boot_start_lmb(&images);

	bootstage_mark_name(BOOTSTAGE_ID_BOOTM_START, "bootm_start");
	images.state = BOOTM_STATE_START;

	return 0;
}

读取内核镜像头部,获取os和initrd基地址和大小

static int bootm_find_os(cmd_tbl_t *cmdtp, int flag, int argc,
			 char * const argv[])
{
	/* get kernel image header, start address and length */
	os_hdr = boot_get_kernel(cmdtp, flag, argc, argv,
			&images, &images.os.image_start, &images.os.image_len);
			
	
}

修正上一步获取的initrd基地址

int boot_ramdisk_high(struct lmb *lmb, ulong rd_data, ulong rd_len,
		  ulong *initrd_start, ulong *initrd_end)
{
	*initrd_start = (ulong)lmb_alloc_base(lmb, rd_len, 0x1000, initrd_high);
	
	//挪到新的地址
	memmove_wd((void *)*initrd_start, (void *)rd_data, rd_len, CHUNKSZ);
	return 0;
}

将initrd地址保存在设备树,将来用于内核解析

int fdt_initrd(void *fdt, ulong initrd_start, ulong initrd_end)
{
	
	err = fdt_setprop_uxx(fdt, nodeoffset, "linux,initrd-start",
			      (uint64_t)initrd_start, is_u64);

	err = fdt_setprop_uxx(fdt, nodeoffset, "linux,initrd-end",
			      (uint64_t)initrd_end, is_u64);
}

下面是deepseek的回答

在 U-Boot 加载 ARM64 Linux 内核并移交控制权的过程中,内核的启动入口地址实际上是 **`.head.text` 段的 `_text` 符号**(即内核的第一条指令地址)。以下是详细流程:

---

### 1. **ARM64 Linux 内核的入口点**
ARM64 Linux 内核的启动入口点定义在 **`arch/arm64/kernel/head.S`** 中,通过链接脚本(`vmlinux.lds`)将 `.head.text` 段放置在镜像的最前面,并标记 `_text` 符号为入口地址。  
关键点:
- **`_text` 符号**:是内核的虚拟地址入口(编译时确定),但实际加载时需要转换为物理地址。
- **`text_offset`**:内核镜像头部(`arm64_image_header`)中的字段,表示内核期望的 **物理加载地址偏移量**(相对于内存起始地址)。

---

### 2. **U-Boot 如何获取 `_text` 的物理地址**
U-Boot 并不直接解析内核的符号表(如 `_text`),而是通过以下步骤间接定位入口地址:

#### (1)解析内核镜像头部
U-Boot 读取内核镜像的前 64 字节(`arm64_image_header`),获取以下关键信息:
- **`text_offset`**:内核的物理加载偏移量(通常是 `0x80000`)。
- **`image_size`**:内核镜像大小。
- **`magic`**:校验魔数(`0x644d5241`,即 "ARM\x64")。

#### (2)计算物理入口地址
U-Boot 根据预定义的 **内存起始地址**(如 `0x40000000` 或 `0x80000000`,由设备决定)和 `text_offset`,计算出内核的物理加载地址:
```
kernel_load_addr = memory_start + text_offset
```
例如:
- 内存起始地址:`0x40000000`
- `text_offset`:`0x80000`  
  则物理加载地址为 `0x40080000`,这就是 `_text` 符号对应的 **实际物理地址**。

#### (3)加载内核镜像
U-Boot 将整个内核镜像(从头部开始)加载到 `kernel_load_addr` 处。此时:
- `.head.text` 段被放置在 `kernel_load_addr`。
- `_text` 符号对应的第一条指令即位于此地址。

---

### 3. **控制权移交**
U-Boot 通过 `booti` 命令跳转到内核:
```bash
booti ${kernel_load_addr} ${initrd_addr} ${dtb_addr}
```
- **`kernel_load_addr`**:即 `memory_start + text_offset`,对应 `_text` 的物理地址。
- 内核启动后,会通过 MMU 将物理地址映射到虚拟地址(`_text` 的虚拟地址由链接脚本决定,如 `0xffff800010080000`)。

---

### 4. **关键代码与链接脚本**
#### (1)Linux 内核的链接脚本(`vmlinux.lds`)
```ld
SECTIONS {
    . = KIMAGE_VADDR;  /* 内核虚拟地址(如 0xffff800010000000) */
    .head.text : {
        _text = .;     /* _text 符号指向 .head.text 段的起始 */
        *(.head.text)
    }
    /* 其他段... */
}
```
- `KIMAGE_VADDR` 是内核的虚拟地址基址,编译时确定。

#### (2)U-Boot 的启动逻辑(`booti` 命令)
```c
// arch/arm/lib/booti.c
int do_booti(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) {
    // 解析内核头部,获取 text_offset
    // 计算 kernel_load_addr = memory_start + text_offset
    // 跳转到 kernel_load_addr(即 _text 的物理地址)
}
```

---

### 5. **为什么 U-Boot 不直接解析 `_text` 符号?**
- **符号表未加载**:U-Boot 不会解析内核的 ELF 符号表(如 `_text`),因为内核镜像可能是压缩的(如 `Image.gz`)或纯二进制格式(`Image`)。
- **依赖固定约定**:ARM64 内核通过 `text_offset` 明确指定物理加载偏移量,U-Boot 只需按约定加载即可。

---

### 6. **总结**
- **`_text` 是虚拟地址**:由内核链接脚本定义,编译时确定。
- **`text_offset` 是物理偏移量**:由内核头部指定,U-Boot 通过 `memory_start + text_offset` 计算出 `_text` 的实际加载地址。
- **U-Boot 跳转的地址**:就是 `_text` 对应的物理地址(`kernel_load_addr`)。

整个过程体现了 **“内核告诉 U-Boot 如何加载自己”** 的协作机制,而非 U-Boot 主动解析内核内部符号。

注意

uboot加载内核和设备树的时候就解析了内核和设备树的头部,并且把去掉头之后的真正内容放在内存里面。

理由:如果内核大小和头部有问题使用uboot命令 kernel load和设备树加载命令会失败

参考

ramdisk与 initrd、initramfs的关系
https://blog.youkuaiyun.com/findstr/article/details/6990801

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值