WVB Operating System(Seventh Stage)
Welcome back to my seventh article on operating system implementation. In the last articles, we learned about how to integrate user modes. This article will discuss Virtual memory and paging.
The abstraction of physical memory is virtual memory. Virtual memory was created to make application development easier and to allow processes to access more memory than is physically available on the machine. Due to security concerns, we don’t want programs to meddle with the kernel or other applications’ memory. Virtual memory can be implemented in the x86 architecture in two ways, segmentation, and paging. The most frequent and versatile strategy is paging, which we’ll use in the next chapter. To allow code to operate under different permission levels, some segmentation is still required. A large part of what an operating system does is manage memory. This is dealt with by paging and page frame allocation.
You could completely avoid paging and rely solely on segmentation for virtual memory. Each user-mode process would have its own segment, with the appropriate base address and limit. No process can see the memory of another process in this way. A difficulty with this is that a process’ physical memory must be continuous. Either we need to know how much memory the program will need ahead of time, or we can transfer memory segments to places where they can grow once the limit is reached. Both of these issues are solved via paging.
When it comes to the paging, A logical address is translated into a linear address by segmentation. Paging converts these linear addresses to physical locations, as well as determining access permissions and how memory should be cached. Paging is the most frequent method for enabling virtual memory in x86 processors. Virtual memory is achieved using paging, which gives each process the impression that the available memory range is 0x00000000–0xFFFFFFFF, despite the fact that the actual memory space is significantly smaller. It also means that a process will use a virtual (linear) address instead of a physical address when accessing a byte of memory.
Identity paging is the most basic type of paging, in which each virtual address is mapped to an identical physical address. This can be accomplished at build time by constructing a page directory in which each entry points to the 4 MB frame that corresponds to it.
Next look at how to enabling the paging. Paging is enabled by first writing the address of a page directory to cr3 and then setting bit 31 of cr0 to 1. The Below code shows the “paging_enable.s” file.
In order to implement the paging, we need to create “paging.h” and “paging.c” files like below.
There will be challenges linking the user-mode process code if the kernel is put at the beginning of the virtual address space, that is, the virtual address space (0x00000000, “size of kernel”) translates to the position of the kernel in memory. The kernel should ideally be located at a very high virtual memory address, such as 0xC0000000 (3 GB). The user-mode process is unlikely to be 3 GB in size, which is the only way it may now cause a kernel conflict. A higher-half kernel is one that uses virtual addresses in the range of 3 GB and up. In order to start with, it is better to place the kernel at 0xC0100000 than 0xC0000000, since this makes it possible to map.
Paging makes it possible to do two things that are beneficial to virtual memory. For starters, it enables fine-grained memory access control. Pages can be marked as read-only, read-write, exclusively for PL0, and so on. Second, it gives the appearance of a continuous memory. Memory can be accessed as if it were contiguous by user-mode processes and the kernel, and the contiguous memory can be extended without the need to transfer data around in memory. We can also allow the user-mode programs access to all memory below 3 GB, but unless they actually use it, we don’t have to assign page frames to the pages.