First, here is a general block diagram of the page table structure of the arm mmu (the following discussion is gradually expanded from this diagram):
The above is the typical structure of the arm's page table diagram: that is, the secondary page table structure:
The first-level page table (L1) consists of the high 12 bits (bits [31:20]) of the virtual address, so the first-level page table has 4096 items, each item occupies 4 bytes, so the size of the first-level page table is 16KB, and the lowest 2 bits of each entry in the first-level page table can be used to distinguish the specific type of page table entry. 2 bits can distinguish 4 types of page table entries. The specific structure of each page table entry is as follows:
In short, there are two main categories of page table entries in the L1 page table:
The first category is the base address pointing to the second-level page table (L2 page table);
The second type directly points to 1MB of physical memory.
Each entry in the L1 page table can cover 1MB of memory. Since there are 4096K items, a total of 4096K*1MB=4GB of memory space can be covered.
Specifically corresponding to Linux, since the Linux software architecture supports a 3-level page table structure, and the arm architecture actually only has a 2-level page table structure, the implementation of the intermediate page table in the Linux code is empty. In the Linux code, the page directory table entries of the first-level page table are represented by pgd, the page directory table entries of the intermediate-level page table are represented by pud (actually not required in the arm architecture), and the page directory table entries of the third-level page table are represented by pmd (since the intermediate pud is empty, pgd=pmd). In addition, the page size of RAM in the current arm-based mobile devices is generally 4KB/page, so the page table entries in the L1 page table all point to the fine page table.
However, in the initialization phase of the Linux kernel startup, a temporary page table (initial page tables) is created to provide an execution environment for the Linux kernel initialization. At this time, the L1 page table entry uses the second page table entry (section enty), which directly maps 1M of memory space. For details, please refer to the __create_page_tables function in arch/arm/kernel/head.S. Due to space limitations, I will not elaborate on it here.
For this section page translation, the MMU hardware performs the following process to convert the virtual address to the physical address:
The temporary page tables used in the initialization process will be overwritten at the end of kernel startup, that is, the page tables will be re-established in the paging_init--->map_lowmem function, which establishes a one-to-one mapping table for physical memory from address 0 to low-end memory (lowmem_limit). The so-called one-to-one mapping means that the physical address and the virtual address differ by a fixed offset, which is generally 0xc0000000 (haha, why is it 0xc0000000?)
Here we introduce an important concept, which is high-end memory relative to low-end memory. What is high-end memory? Why do we need high-end memory? In order to solve this problem, we assume that the physical memory we use is 2GB in size. In addition, since the address range of our kernel space is from 3G-4G space, and as mentioned earlier, the low-end memory space of the Linux kernel is mapped one by one. If we do not introduce the concept of high-end memory and use a one-to-one mapping method, the kernel can only access 1GB of physical memory. But in fact, we need the kernel to be able to access all 4GB of memory in the kernel space. How can we do this?
The method is that we do not use one-to-one mapping for the 3G-4G space, but map the physical address [0x00, fix_addr] (fix_addr < 1GB) to the kernel space virtual address [0x00+3G, fix_addr+3G], and then reserve the space [fix_addr+3G, 4G] for dynamic mapping, so that we can access the physical memory space from fix_addr to 4GB through this virtual address. How is it done?
For example, if we want to access any section in the range of physical address [fix_addr, 4GB], I will use the valuable kernel virtual address [fix_addr+3G, 4G] to map it, build the page table used by the mmu hardware, and after the access, clear the mapping and release the kernel virtual address for the next access to other physical memory. In this way, we can achieve the purpose of accessing all 4GB of physical memory.
So how does the kernel code create the mapping table?
We focus on the create_mapping function in arch/arm/mm/mmu.c. Before the analysis, let's first look at how the arm mmu hardware implements the conversion of virtual addresses to physical addresses in the secondary page table structure.
First post the original code (arch/arm/mm/mmu.c):
The function description is as follows:
Create the page directory entries and any necessary
page tables for the mapping specified by `md'. We
are able to cope here with varying sizes and address
offsets, and we take full advantage of sections and
supersections.
Line 737-line 742: Parameter validity check, this function does not create a mapping table for the virtual address of the user space (remember to ask yourself one more question why?)
line744-line750: If it is iomemory, the mapped virtual address range should belong to the high memory range. Since we are using regular memory, that is, type is MT_MEMORY, we will not enter this branch.
line775: Get which entry of the first-level page table (L1) the virtual address addr belongs to. Follow the pgd_offset_k function in detail (defined in: arch/arm/include/asm/pgtable.h). You will find that the base address of our kernel's L1 page directory table is at 0xc0004000, and our kernel code is placed at 0xc0008000. The size of the interval from 0xc0004000 to 0xc0008000 is 16KB, which is exactly the size of the L1 page table (see the description at the beginning of the article).
Here we need to pay attention to a concept: kernel page directory entries and process page directory entries. Kernel page directory entries are common to all processes in the system, while process page directory entries are related to specific processes. Each application process has its own page directory entry, but the page directory table of the kernel space corresponding to each process is the same. It is precisely because each process has its own page directory table that each process can independently have its own [0, 3GB] memory space.
line778 pgd_addr_end() ensures that the [addr, next] address does not cross the maximum memory space that an L1 table entry can map, which is 2MB (why 2MB instead of 1MB? This is a processing trick of Linux, which will be explained in detail later)
line780 alloc_init_pud() function creates a mapping table for the secondary page table (L2) pointed to by the located L1 page directory entry pgd
line784 pdg++ moves down the L1 page directory table entry pgd, mapping the virtual address of the next 2MB space to the corresponding 2MB physical space.
Let's analyze here why the L1 page directory entry pgd can map 2MB of virtual address space.
In the first figure of this article, it is a typical MMU mapping framework diagram of ARM, but not of Linux. The Linux mapping framework diagram has made some adjustments and optimizations on its basis.
The adjustments made by Linux are described as follows (the following is extracted from the comments provided in the Linux kernel: arch/arm/include/asm/pgtable-2level.h):
/*
* Hardware-wise, we have a two level page table structure, where the first
* level has 4096 entries, and the second level has 256 entries. Each entry
* is one 32-bit word. Most of the bits in the second level entry are used
* by hardware, and there aren't any "accessed" and "dirty" bits.
*
* Linux on the other hand has a three level page table structure, which can
* be wrapped to fit a two level page table structure easily - using the PGD
* and PTE only. However, Linux also expects one "PTE" table per page, and
* at least a "dirty" bit.
*
* Therefore, we tweak the implementation slightly - we tell Linux that we
* have 2048 entries in the first level, each of which is 8 bytes (iow, two
* hardware pointers to the second level.) The second level contains two
* hardware PTE tables arranged contiguously, preceded by Linux versions
* which contain the state information Linux needs. We, therefore, end up
* with 512 entries in the "PTE" level.
*
* This leads to the page tables having the following layout:
*
Important adjustments are as follows:
The L1 page table is increased from 4096 items to 2048 items, but the size of each item is increased from 4 bytes to 8 bytes.
In a page, two L2 page tables are placed, each with 256 entries, each with 4 bytes, so the total is 256*2*4=2KB, placed in the lower half of the page, and the upper part is placed with the corresponding page table used by the Linux memory management system, which is not used by the MMU hardware. So it just takes up the size of a page (4KB), so there is no waste of space.
With the above foundation, let's analyze the above line780 function alloc_init_pud in detail. This function will eventually call the alloc_init_pte function:
line598 The early_pte_alloc function determines whether the corresponding L2 page table pointed to by pmd exists. If not, the L2 page table is allocated. If it exists, the virtual address of the page where the L2 page table is located is returned.
Line 572 determines whether the L2 page table pointed to by pmd exists. If not, early_alloc function is used to allocate a 4KB physical page of PTE_HWTABLE_OFF (512*4=2KB) + PTE_HWTABLE_SIZE (512*4=2KB) to store two linuxpet page tables + two hwpte page tables.
Line574 returns the virtual address of this physical page
Back to line 599 of the alloc_init_pte function:
Line 183 pte_index is used to determine the offset of the virtual address in the L2 page table. That is, bits [12~21] of the virtual address total 9 bits, which are just used to address two L2 page tables (a total of 512 items)
Previous article:2440 flash programming problem
Next article:arm stack-(selection of stack starting address)
- Popular Resources
- Popular amplifiers
Professor at Beihang University, dedicated to promoting microcontrollers and embedded systems for over 20 years.
- LED chemical incompatibility test to see which chemicals LEDs can be used with
- Application of ARM9 hardware coprocessor on WinCE embedded motherboard
- What are the key points for selecting rotor flowmeter?
- LM317 high power charger circuit
- A brief analysis of Embest's application and development of embedded medical devices
- Single-phase RC protection circuit
- stm32 PVD programmable voltage monitor
- Introduction and measurement of edge trigger and level trigger of 51 single chip microcomputer
- Improved design of Linux system software shell protection technology
- What to do if the ABB robot protection device stops
- Huawei's Strategic Department Director Gai Gang: The cumulative installed base of open source Euler operating system exceeds 10 million sets
- Download from the Internet--ARM Getting Started Notes
- Learn ARM development(22)
- Learn ARM development(21)
- Learn ARM development(20)
- Learn ARM development(19)
- Learn ARM development(14)
- Learn ARM development(15)
- Analysis of the application of several common contact parts in high-voltage connectors of new energy vehicles
- Wiring harness durability test and contact voltage drop test method
- Current control scheme of three-phase four-wire active filter based on FPGA
- Some Problems in Automobile CAN Communication
- Several basic concepts of DSP software and hardware
- Q&A on Connectivity: Mesh Networking and the Growth of IoT Devices
- Problems with constant current source composed of op amp + MOS tube
- WeChat group voice Q&A at 2pm: Vicor power experts answer netizens' power design questions
- Ti's C28x series ADC experience
- How to judge whether an oil chromatograph is suitable for selection?
- Battery interface problem
- What is the relationship between STM32 and IoT?