I am somewhat making this up as I go along. Feel free to call me an idiot by email and educate me
This is being written whilst I learn how the memory management in ARM26 works, which gives me an opportunity to explain from a beginners perspective how memory management works in linux when you have only a TLB (no 2nd level page tables) and the MMU doesnt have hardware support for querying if a page is dirty etc.
The memory map of a typical ARM26 machine is dictated my the MEMC chip (at least, I know of no ARM26 machines that do not use this MMU. The MEMC is an unusual chip, as you will see. Heres the tpyical layout:
| Read | Write | |
|---|---|---|
| ROM (high) | MEMC TLB registers | 0×0380 0000 - 0x03ff ffff |
| ROM (low) | DMA Address gen | 0×0360 0000 |
| ROM (low) | Video control regs | 0×0340 0000 |
| IO Address space | 0×0300 0000 | |
| Physically mapped RAM (16MB) | 0×0200 0000 | |
| Logically mapped space (32MB) | 0×0000 0000 | |
Only the logically mapped space can be accessed from user mode. Supervisor mode may access the entire memory map. The mapping of logical to physical pages is determined by the programming of the MEMC TLB registers, which as you can see above are write only.
On a 4MB or higher machine, MEMC breaks the logical space into 1024 32KB pages, which can map to any physical page. For now, only support for machines with 32K pages is planned, at least until the kernel is working again.
So, what does linux do with this address space? Well, the lower 480KB are initially assumed to be screen RAM (480K is the maximum MEMC allows for this), so the kernel will not load that low. The actual kernel proper is loaded starting from 0×02080000, leaving a small gap just after the scren memory. Once booted, the unused screen memory is released
Each process in linux has its own low level mapping of logical to physical pages, which is stored in a 'page directory'. This is found in the tasks mm_struct. This is where we start to differ from the mor emodern architectures.
More modern architectures have a 'three level' page table, a 'page directory' (pgd), containing pointers to 'page middle directories' (pmd) which point to groups of 'page table entries' (pte). This gives an efficient way of handling memory and fits closelyt with the design of their MMUs. the ARM2 and 3 (arm26 types) have no such features, sporting only a single 'top level' page table as it were.
We get around this problem by 'collapsing' the pgd and pmds together. this gives us a two level structure instead. In *theory* the tree could be collapsed completely, but we dont do this because keeping two levels allows us to group ptes together. Typically, we have up to 32 pgds pointing to 32 ptes each, covering 1024 pages.
Since the pointers we use on ARM26 are 32 bit and word aligned, the lower 2 bits arent needed, and in the case of ptes, they are page aligned, which means the lower 5 bits are free too. We take advantage of this, and use these bits to encode some information.
In the case of pgd entries we only use the lower bit. this tells us if the entry is present or not. there is a difference between a NULL pgd and a non present one, and this bit is what allows us to tell them apart. (a non NULL not present pgs has been swapped.
In the case of ptes, things arent so simple. we encode wether the pte is present or not, as well as wether it is 'old' (never read from), 'dirty' (has been written to), wether it is read only, and lastly, wether it is accessible to userspace.
The dirty/clean old/young flags are how we cope with not having an MMU that can tell us if a page has been written to directly. What we do is map the page as inaccessible (no read or write permissions), and then if we get a read access, mark the page as young (!old). For a write access, the page will be flagged dirty (!clean). since the mapping the page has will cause page faults when it is read or written to for the first time, so we can keep a record of this in memory.