用户态,内核态用链接脚本分离(lds),将进程放进用户态

物理地址(0x00000000 开始)

├── 0x00010000 开始加载内核代码、数据(.text/.data/.bss)
│ ↓↓↓
│ ±------------------+
│ | 内核代码段 .text|
│ | 只读数据段 .rodata|
│ | 初始化数据段 .data|
│ | BSS段(未初始化)|
│ ±------------------+ ← e_data

├── 虚拟地址 0x80000000 开始(first_task 映射的起始地址)
│ ↓↓↓
│ ±--------------------+
│ | 用户态 first_task |
│ | 包括 lib_syscall 等 |
│ ±--------------------+ ← mem_free_start(空闲内存)

SECTIONS
{
	PROVIDE(kernel_base = 0x0);

	. = 0x00010000;

	PROVIDE(s_text = .);
	.text : {
		*(EXCLUDE_FILE(*first_task* *lib_syscall*) .text)
	}
	.rodata : {
		*(EXCLUDE_FILE(*first_task* *lib_syscall*) .rodata)
	}
	PROVIDE(e_text = .);

	. = ALIGN(4096);//必须页边界对齐
	PROVIDE(s_data = .);
	.data : {
		*(EXCLUDE_FILE(*first_task* *lib_syscall*) .data)
	}
	.bss : {
		*(EXCLUDE_FILE(*first_task* *lib_syscall*) .bss)
	}
	e_data = .;

	/* 初始进程的配置:接紧着在低端1MB内存开始存储,但是运行时搬运到0x80000000处 */
	. = 0x80000000;

	PROVIDE(s_first_task = LOADADDR(.first_task));
	.first_task : AT(e_data) {
		*first_task_entry*(.text .data. rodata .data)
		*first_task*(.text .data. rodata .data)
	    *lib_syscall*(.text .data .bss)
	}
	PROVIDE(e_first_task = LOADADDR(.first_task) + SIZEOF(.first_task));

	PROVIDE(mem_free_start = e_first_task); /*链接脚本,用extern调用*/
}

你这段 linker script(链接脚本)写得非常完整!👏
这里结合你之前的问题,我们来做一个全景解析,特别说明 SECTIONS 中每一段在干嘛、为什么这么写、它背后的内核内存布局含义是什么。


✅ 一、整体结构图解(大局观)

物理地址(0x00000000 开始)
│
├── 0x00010000 开始加载内核代码、数据(.text/.data/.bss)
│     ↓↓↓
│   +-------------------+
│   |   内核代码段 .text|
│   |   只读数据段 .rodata|
│   |   初始化数据段 .data|
│   |   BSS段(未初始化)|
│   +-------------------+ ← e_data
│
├── 虚拟地址 0x80000000 开始(first_task 映射的起始地址)
│     ↓↓↓
│   +---------------------+
│   | 用户态 first_task   |
│   | 包括 lib_syscall 等 |
│   +---------------------+ ← mem_free_start(空闲内存)

✅ 二、关键点逐段分析


🔸 PROVIDE(kernel_base = 0x0);

  • 定义一个符号 kernel_base 为 0
  • 通常表示 内核的物理起始地址,供内核代码中引用

🔸 . = 0x00010000;

  • 指定 内核段的起始加载地址是 64KB(0x10000
  • BIOS/MBR 占用 0x0000~0x7C00,0x00010000 是比较标准的内核加载起始

🔸 .text, .rodata, .data, .bss

.text : {
    *(EXCLUDE_FILE(*first_task* *lib_syscall*) .text)
}

💡 把所有目标文件的 .text 段都合并过来,但排除掉名字中包含 first_tasklib_syscall 的目标文件。

📌 意义是:

  • 把内核代码和数据放入内核空间 .text/.rodata/.data/.bss
  • 不要把用户态的 first_tasklib_syscall 也一起混进去

🔸 e_data = .;

  • e_data 是所有内核段结束的地方
  • 它就是下一个加载段(first_task)放置的物理地址

🔸 . = 0x80000000;

  • 指定 .first_task虚拟地址起始位置是 0x80000000
  • 这是用户空间或用户任务运行时所在的高虚拟地址

🔸 .first_task : AT(e_data)

.first_task : AT(e_data) {
    *first_task_entry*(.text .data .rodata .data)
    *first_task*(.text .data .rodata .data)
    *lib_syscall*(.text .data .bss)
}
  • 定义 .first_task 段,从虚拟地址 0x80000000 开始
  • 但实际加载在物理地址 e_data
  • 包含内容:
    • 用户进程入口(first_task_entry
    • 用户任务本体(first_task
    • 系统调用接口(lib_syscall

🧠 这是典型的:

运行地址 ≠ 加载地址
程序加载后要“搬运”到虚拟地址运行(通常在页表映射完成后)


🔸 PROVIDE(...) 符号的用途

PROVIDE(s_first_task = LOADADDR(.first_task));
PROVIDE(e_first_task = LOADADDR(.first_task) + SIZEOF(.first_task));
PROVIDE(mem_free_start = e_first_task);

这几行定义了几个关键符号:

符号含义
s_first_task.first_task 段的加载地址(物理地址)
e_first_task.first_task 段的物理结束地址
mem_free_start第一个任务结束后的可用物理内存开始地址,用于后续内存分配

在 C 中你可以这样使用这些符号:

extern char mem_free_start[];
memory_init(mem_free_start);  // 初始化内存分配器

✅ 总结一下这份链接脚本干了什么?

目的实现方式
保留内核低端物理空间. = 0x10000
按模块组织段.text, .data, .bss, .rodata
排除用户进程代码EXCLUDE_FILE(*first_task*)
用户任务运行于高虚拟地址. = 0x80000000; .first_task
支持虚实地址分离加载.first_task : AT(e_data)
导出关键地址符号供内核使用PROVIDE(...)

🧠 面试总结金句:

这份链接脚本通过 EXCLUDE_FILE 和虚拟地址定位,清晰地区分了内核段与用户进程段,并通过 AT() 实现虚拟地址和加载地址分离,确保用户任务可以在高地址运行,同时保留了内核的低端物理布局。


要不要我给你画个完整的“内核加载段 vs 用户任务段(虚拟地址映射)”的对比图?📊 非常适合做注释笔记或面试展示~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值