内存映射是指将地址空间划分为不同的部分或段,每个段有不同的用途和属性。这段话介绍了五个段:文本段、全局数据段、动态数据段、异常处理器段和操作系统(OS)段。下面是对每个段的简要说明:
- 文本段:存放程序的代码和常量数据,如字符串和数组。文本段是只读的,不能被修改。用户可以自己定义文本段的位置和大小。
- 全局数据段:全局数据段存放全局变量,即可以被程序中所有函数访问的变量。全局变量在程序开始执行之前就在内存中分配好了,通常使用全局指针寄存器gp(寄存器x3)来访问,它指向全局数据段的中间位置。在这个例子中,gp是0x10000800。使用12位有符号偏移量,程序员可以使用gp来访问整个全局数据段。
- 动态数据段:动态数据段包括栈和堆。这个段中的数据在程序启动时是不确定的,而是在程序运行过程中动态分配和释放的。操作系统在程序启动时设置栈指针(sp,寄存器x2)指向栈的顶部,在这个例子中是0xBFFFFFF0。栈通常向下增长,如图所示。栈包括一些临时存储和局部变量,如数组,它们不能放在寄存器中。函数还使用栈来保存和恢复寄存器。每个栈帧按照后进先出的顺序访问。堆存放程序运行时分配的数据。像宿舍地板上的一堆衣服一样,堆中的数据可以按任意顺序使用和丢弃。堆通常从动态数据段的底部向上增长。如果栈和堆相互碰撞了,程序的数据就会被破坏。内存分配器保证这种情况不会发生,如果没有足够的空间来分配更多的动态数据,它会返回一个内存不足的错误。
- 异常处理器、操作系统和I/O段:例子中RISC-V内存映射的最低部分是保留给异常处理器和启动时运行的引导代码的。最高部分是保留给操作系统和内存映射I/O的。内存映射I/O是指将外部设备(如键盘、显示器等)的寄存器映射到内存地址空间中,以便通过读写内存来控制设备。异常处理器、操作系统和I/O段通常是受保护的,只能在特权模式下访问。
RISC-V不定义一个特定的内存映射,而是由用户自己决定各个段的位置和大小。这样可以提供灵活性,特别是对于一些较小的系统,如手持设备,它们可能只使用了部分内存范围,并且只有部分物理内存。
RISC-V要求sp保持16字节对齐,以便与RISC-V基础指令集RV128I兼容,它可以操作128位(即16字节)的数据。因此,sp每次减少16的倍数来为栈腾出空间,即使需要更少的栈空间也是如此。
RISC-V要求sp保持16字节对齐,这意味着sp的值必须是16的倍数。这样做的原因是为了与RISC-V基础指令集RV128I兼容,它可以操作128位(即16字节)的数据。如果sp不是16字节对齐的,那么RV128I指令就不能正确地访问栈上的数据,可能会导致数据错误或异常。因此,为了保证兼容性和正确性,RISC-V规定了sp的对齐要求。
因此,sp每次减少16的倍数来为栈腾出空间,即使需要更少的栈空间也是如此。例如,如果一个函数需要在栈上分配8个字节的空间,那么sp就要减少16个字节,而不是8个字节。这样做会浪费一些内存空间,但是可以保证sp始终是16字节对齐的。