用 C 表示内存



我正在尝试用C编写一个指令集模拟器来模拟运行ARM的机器。我需要能够有效地表示 4GB 内存,经过一些挖掘,得出了一个由 1024 个指针组成的数组的解决方案,每个指针指向一个 4MB 的块,该块在第一次使用时动态分配

#define MEMSIZE 1024    //1024 * 2Mb = 4Gb
#define PAGESIZE 4194304    //4 Mb
#define PAGEEXP 22      //2^PAGEEXP = PAGESIZE
uint32_t* mem[MEMSIZE];

我的问题是我如何访问某个内存地址?

我尝试将地址分解为索引和偏移量,如下所示,但这似乎只为索引和偏移量返回 0。(memAdd是我尝试访问的地址)

memIdx = memAdd >> PAGEEXP;
memOfs = memAdd & PAGESIZE;

获得地址后,我用来读/写的功能如下:

void memWrite(uint32_t idx, uint32_t ofs, uint32_t val)
{
    if(mem[idx] == 0)
        mem[idx] = malloc(PAGESIZE);
    *(mem[idx] + ofs) = *(mem[idx] + ofs) & val;
}
uint32_t memRead(uint32_t idx, uint32_t ofs)
{
    if(mem[idx] == 0)
        return 0;
    else
        return *(mem[idx] + ofs);
}

这些在我的脑海中似乎是正确的,但是我仍然不是 100% 对指针感到满意,所以这可能是错误的。

抱歉,如果这已经在某处讨论过,但我找不到任何与我需要的内容相关的内容(我的关键字非常广泛)

从逻辑上看它,而不是从位级别看。

每个页有 4,194,304 字节。

因此,从算术上讲,要将线性地址转换为(页面,偏移量)对,请除以 4,194,304 以获得页码,并取余数以获得页面的偏移量。

page = address / PAGESIZE;
offset = address % PAGESIZE;

由于您希望有效地执行此操作,并且这些是 2 的幂,因此您可以将除以 PAGESIZE 替换为右移以 PAGESIZE 的底数为 2 的对数,即 22:

page = address >> PAGEEXP;

因此,代码的这一部分是正确的。 但是,要获得偏移量,您要做的是屏蔽除刚刚从页码中移出的位之外的所有内容。 为此,您必须与PAGESIZE - 1.

offset = address & (PAGESIZE - 1);

这是因为在二进制中,你从看起来像这样的数字开始(其中 p 是页码位,o 是偏移位):

address = ppppppppppoooooooooooooooooooooo

您希望单独获取页码和偏移量。 您显然希望向右移动 22 位以获取页码:

page = addresss >> 22 = 0000000000000000000000pppppppppp

但是,如果您使用页面大小(二进制00000000010000000000000000000000),则答案中最多只有一个 1 位,它只会告诉您页码是奇数还是偶数。 没用。

你想要 AND 的比这少一点,这是二进制00000000001111111111111111111111,因此:

  ppppppppppoooooooooooooooooooooo 
& 00000000001111111111111111111111
-----------------------------------
= 0000000000oooooooooooooooooooooo

这就是你获得偏移量的方式。

这是一个一般规则:如果 N 是 2 的整数幂,则除以 N 与右移 log(N)/log(2) 相同,并且这种除法的其余部分由 ANDDing 与 (N-1) 给出。

如果 PAGESIZE 是 2 的幂,则它只设置了 1 位。 因此,用另一个值进行 AND-ing 只能在结果中保留零位或一位。 两个可能的值。 但是您将其用作数组索引。

此外,您的memWrite(uint32_t idx, uint32_t ofs, uint32_t val)函数始终以 val 的值 AND 表示。 因此,例如,如果val uint32_max则对此函数的任何调用都不会产生任何影响。

最后,您不仅不检查malloc()结果是否失败,也不初始化返回的内存块。

尝试这样的方法(不幸的是我无法测试它,我刚才没有编译器)。

enum { SIM_PAGE_BITS = 22 };   // 2^22 = 4MiB
enum { SIM_MEM_PAGES = 1024 }; // 1024 * 4MiB = 4GiB
enum { SIM_PAGE_SIZE = (1<<SIM_PAGE_BITS) };
enum { SIM_PAGE_MASK = SIM_PAGE_SIZE-1 };
enum { UNINITIALISED_MEMORY_CONTENT = 0 };
enum { WORD_BYTES = sizeof(uint32_t)/sizeof(unsigned char) };
#define PAGE_OFFSET(addr) (SIM_PAGE_MASK & (uint32_t)addr)
// cast to unsigned type to avoid sign extension surprises if addr<0
#define PAGE_NUM(addr) (((uint32_t)addr) >> SIM_PAGE_BITS)
#define IS_UNALIGNED(addr) (addr & (WORD_BYTES-1))
unsigned char* mem[MEMSIZE];
uint32_t memRead(uint32_t addr) {
    if (IS_UNALIGNED(addr)) return handle_unaligned_read(addr);
    const uint32_t page = PAGE_NUM(addr);
    if (mem[page]) {
        const unsigned char *p = mem[page] + PAGE_OFFSET(addr);
        return *(uint32_t*)p;
    } else {
        return UNINITIALISED_MEMORY_CONTENT;
    }
}
void memWrite(uint32_t addr, uint32_t val) {
    if (IS_UNALIGNED(addr)) return handle_unaligned_write(addr, val);
    const uint32_t page = PAGE_NUM(addr);
    if (!mem[page]) {
        if (val == UNINITIALISED_MEMORY_CONTENT) {
            return;
        }
        mem[page] = malloc(SIM_PAGE_SIZE);
        if (!mem[page]) {
            handle_out_of_memory();
        }
        // If UNINITIALISED_MEMORY_CONTENT is always 0 we can 
        // use calloc instead of malloc then memset.
        memset(mem[page], UNINITIALISED_MEMORY_CONTENT, SIM_PAGE_SIZE);
    } 
    const unsigned char *p = mem[page] + PAGE_OFFSET(addr);
    *(uint32_t*)p = val;
}

这将做你想要的。我使用了较小的尺寸。为了清楚起见,我省略了错误检查。它使用使用索引器数组的方案。

#include <cstdlib>
#include <cstdio>
#include <stdint.h>
#define NUMPAGE 1024
#define NUMINTSPERPAGE 4
uint32_t* buf;
uint32_t* idx[NUMPAGE];
void InitBuf()
{
    buf = (uint32_t*) calloc(NUMPAGE, NUMINTSPERPAGE * sizeof uint32_t );
    for ( size_t i = 0; i < NUMPAGE; i++ )
    {
        idx[i] = &buf[i * NUMINTSPERPAGE * sizeof uint32_t];
    }
}
void memWrite(size_t i, size_t ofs, uint32_t val)
{
    idx[i][ofs] = val;
}
uint32_t memRead(size_t i, size_t ofs)
{
    return idx[i][ofs];
}
int main()
{
    InitBuf();
    uint32_t val = 1243;
    memWrite(1, 2, val);
    printf("difference = %ld", val - memRead(1, 2));
    getchar();
}

我不相信memOfs的值被正确计算。 例如,PAGESIZE 表示的十进制值4194304以十六进制形式0x400000,这意味着在按位 AND 运算之后,您只会得到原始地址的第 22 位,而不是较低的 22 位。 将该值添加到 4MB 页面数组指针实际上会使您超出堆上分配数组的末尾。将偏移量计算的掩码更改为 0x3FFFFF ,然后按位 AND 使用原始内存地址,以便计算到页面中的正确偏移量。 例如:

memIdx = memAdd >> PAGEEXP;
memOfs = memAdd & 0x3FFFFF;  //value of memOfs will be between 0 and 4194303

最新更新