自己的理解
初始化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