16KB页,32bit 地址的划分有些问题

本文探讨了32位内核环境下,针对不同页面大小(如16KB)时TLB填充处理器(tlb_refill_handler)的具体实现细节,并指出了原有实现中存在的索引重叠问题及其解决方案。

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

32 bit kernel, 16KB 页大小,龙芯2E平台

tlb_refill_handler为:

lui       k1, %hi(pgd_current)
mfc0   k0, C0_BADVADDR
lw       k1, %lo(pgd_current)(k1)
srl     k0, k0, PGDIR_SHIFT
sll       k0, k0, PGD_T_LOG2
addu   k1, k1, k0

mfc0   k0, C0_CONTEXT
lw       k1, 0(k1)
srl     k0, k0, 3
andi     k0, k0, (PTRS_PER_PTE/2-1) << (PTE_T_LOG2 + 1)
addu   k1, k1, k0

lw       k0, 0(k1)
lw       k1, sizeof(pte_t)(k1)
srl     k0, k0, 6
mtc0   k0, C0_ENTRYLO0
srl     k1, k1, 6
mtc0   k1, C0_ENTRYLO1
tlbwr
eret

相应的,直接从正在运行的 kernel 里获取的 tlb_refill_handler 为:

    0:   52801b3c   lui k1,0x8052
    4:   00401a40   mfc0   k0,$8       /* 从 BadVaddr 获取转换失败的 VA */
    8:   18007b8f   lw k1,24(k1)       /* 读取 PGD 所在基地址 */
    c:   82d51a00   srl k0,k0,0x16     /* VA 右移22位 */
    10:   80d01a00   sll k0,k0,0x2
    14:   21d87a03   addu   k1,k1,k0

    18:   00201a40   mfc0   k0,$4       /* 读取 Context 值 */
    1c:   00007b8f   lw k1,0(k1)
    20:   c2d01a00   srl k0,k0,0x3        
    24:   f83f5a33   andi   k0,k0,0x3ff8
    28:   21d87a03   addu   k1,k1,k0

    2c:   00007a8f   lw k0,0(k1)
    30:   04007b8f   lw k1,4(k1)
    34:   82d11a00   srl k0,k0,0x6
    38:   00109a40   mtc0   k0,$2
    3c:   82d91b00   srl k1,k1,0x6
    40:   00189b40   mtc0   k1,$3
    44:   06000042   tlbwr
    48:   18000042   eret

Context 之格式为:

63                       23 22           4 3     0
|                           |     BadVPN2   |   0   |

22~4 共 19 位对应导致 TLB miss 之虚地址的31~13(VA[31:13]),因此如下指令

    20:   c2d01a00   srl k0,k0,0x3        
    24:   f83f5a33   andi   k0,k0,0x3ff8     /* 获取 VA[25:15] 共11位,低3位为0 */

的 意图旨在获取 VA[25:15],并以 2^3=8 字节为单位索引PageTable,实际的索引范围为 2^12 = 4K ,这个与每页容纳的项数相一致(16K/4=4k)。实质上使用 VA[25:14]索引 PageTable,因每次取一对,故而VA[14] 不用。

VA[13:0] 页内位移,没有问题。

但 使用 VA[31:22] 索引 PGD 就让人困惑了。32bit kernel 中定义 PGDIR_SHIFT 始终为 22 ,不管页大小为何,这个在4KB页大小下,是正好的,但在16KB下,由于使用 VA[25:15] 索引PageTable,因此用于索引 PGD 的域与索引 PT 的域重叠了4位(25~22)!

如果有2个虚址,VA1[22] = 1,VA2[23] = 1 ,当 TLB miss 时,当前内核的 tlb_miss_handler 是向2个不同的 PGD 入口找寻 PTE 的,尽管按照地址划分他们应该在同一个 PGD 入口才是。

关于地址的划分,应该从低位往高位切分才是,即:首先切取PAGE_SIZE所需位,然后根据PAGE_SIZE,确定索引 PT 的位数。如4KB,则为4K/4=1K, 10 位,16KB则为12 位,64KB 则为14位。

余下的高位作为索引PGD 的位数才是。因此应该是 6 | 12 | 14 才对。

---------------------------------

32 bit 模式下, PGD 索引位与PT 索引位部分重叠并不会导致灾难性的后果,只是会造成核心分配给PT的页过多,浪费空间。

测了一下,改动后的 kernel,启动后 nr_page_table_pages 为 63 左右,而原有kernel 在101左右,多分配了 (101-63)*16KB 作为 PageTable。

另外我写了一个测试程序,使用mmap 映射一个40MB左右的文件在地址 0x2ac94000 附近,然后随机访问该文件中的数据 1亿次,新旧对比,原 kernel 要多分配 13*16KB 作为 PageTable(cat /proc/vmstat)。

---------------------------------

后 记:该问题已经提交到linux-mips mail list,最新内核的 PGDIR_SHIFT 定义已经修改为: (2 * PAGE_SHIFT + PTE_ORDER - PTE_T_LOG2)(原来写死为 22),现在可以根据 PAGE_ SHIFT 动态调整了。



 
<think>好的,用户想了解STM32的内存地址架构,我需要详细解释这个问题。首先,我得回忆一下STM32的内存结构,结合之前看到的引用内容。 记得引用[1]提到,STM32的存储器结构与Cortex-M3相似,但加入了Flash和SRAM等实际组件。所以应该从Cortex-M3的架构说起,然后过渡到STM32的具体实现。 首先,Cortex-M3的地址空间是4GB,分为不同的区域,比如代码区、SRAM区、外设等。STM32在此基础上划分了Flash、SRAM、外设寄存器等部分。需要详细说明每个区域的地址范围,比如Flash通常从0x08000000开始,SRAM从0x20000000开始。 然后,启动配置也很重要。STM32的启动模式选择不同会影响初始地址,比如从主Flash启动时,0x08000000会被映射到0x00000000。这一点在启动文件里可能有设置,引用[2]提到启动文件.s中设置了堆栈,堆栈是在RAM分配给RW和ZI数据之后分配的。需要解释堆栈的位置如何影响内存布局。 内存分配方面,用户可能想知道编译后的程序如何占用内存。代码段放在Flash,已初始化的全局变量在RW-data,未初始化的在ZI-data,堆栈在RAM末尾。需要结合引用[2]的内容,说明堆栈的分配位置以及如何调整。 外设寄存器部分,地址固定,比如APB和AHB总线对应的外设地址范围。引用[3]虽然主要讲ADC,但提到外设集成,可以举例说明外设寄存器的访问方式。 内存映射方面,可能需要提到别名区域,比如通过不同地址访问同一物理内存,用于位带操作,这在Cortex-M3中支持,STM32是否也有?需要确认并解释其作用。 最后,实际应用中的注意事项,如地址越界、堆栈溢出,以及如何通过分散加载文件调整内存分配。这部分可以引用启动文件的内容,如引用[2]提到的堆栈设置。 用户可能还想知道如何查看内存映射,或者如何优化内存使用,但根据问题,重点在架构详解,所以需要覆盖结构、启动配置、分配、外设和映射等内容,确保每个部分都有引用支持,并自然添加引用标识。</think>### STM32内存地址架构详解 STM32的内存地址架构基于**Cortex-M系列处理器的统一存储器映射**,其设计遵循ARM架构规范,同时结合了芯片实际硬件资源(如Flash、SRAM、外设寄存器等)。以下是具体分析: --- #### 1. **内存地址空间划分** STM32的4GB地址空间(32位系统)分为以下主要区域: - **代码区(Code Area)** 地址范围:$0x00000000$ - $0x1FFFFFFF$ 用途:存放程序代码(Flash),例如主Flash通常从$0x08000000$开始[^1]。 - **SRAM区(SRAM Area)** 地址范围:$0x20000000$ - $0x3FFFFFFF$ 用途:运行时数据存储,例如STM32F4的SRAM起始地址为$0x20000000$,大小因型号而异(如192KB或更多)。 - **外设寄存器区(Peripheral Registers)** 地址范围:$0x40000000$ - $0x5FFFFFFF$ 用途:映射所有片上外设(如GPIO、ADC、USART)的控制寄存器[^3]。 - **其他区域** 包括位带别名区($0x42000000$)、系统控制区($0xE0000000$)等,用于特殊操作(如位操作、调试)。 --- #### 2. **启动配置与地址映射** STM32支持多种启动模式,通过BOOT引脚配置: - **主Flash启动**(默认):Flash起始地址$0x08000000$映射到$0x00000000$,CPU从此读取初始堆栈指针和复位向量[^2]。 - **系统存储器启动**:用于内置Bootloader,地址由芯片厂商定义。 - **SRAM启动**:调试时临时使用,直接运行RAM中的代码。 --- #### 3. **内存分配与堆栈管理** - **编译后的内存占用**: 代码段(`.text`)存储在Flash中; 已初始化全局变量(`.data`)加载到RAM; 未初始化变量(`.bss`)占用RAM的ZI-data区域。 - **堆栈分配**: 堆(Heap)和栈(Stack)位于RAM末尾,由启动文件(`.s`)定义大小。例如: ```c Stack_Size EQU 0x00000400 // 1KB栈 Heap_Size EQU 0x00000200 // 512B堆 ``` --- #### 4. **外设寄存器的地址固定性** 每个外设的寄存器地址在芯片设计时已固定。例如: - **GPIOA**的寄存器基址为$0x40020000$; - **ADC1**的寄存器基址为$0x40012000$(不同型号可能不同)[^3]。 --- #### 5. **内存映射的灵活性** STM32支持**别名区(Bit-Band)**操作,允许通过位带别名地址直接操作单个比特。例如: - 原始地址:$0x20000000$的某比特; - 别名地址:$0x22000000 + (0x20000000 - 基址) \times 32 + 比特序号 \times 4$。 --- #### 6. **实际应用注意事项** - **地址越界**:访问未分配区域会触发硬件错误(HardFault)。 - **堆栈溢出**:需合理设置栈大小,避免覆盖其他数据。 - **分散加载文件(Scatter File)**:可自定义代码和数据的存储位置。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值