On the X86 platform, the memory is divided into low-end memory and high-end memory, so the corresponding virtual address of page search in the two regions is different.
1. Definition of page_address () function on x86
In include/Linux/mm. h, there are three macro definitions for the page_address () function, mainly dependent on different platforms:
First, let's take a look at the definition of several macros:
Config_highmem: as the name suggests, whether high-end memory is supported. You can view the config file. Generally, if the memory exceeds MB, It is configured to support high-end memory.
Want_page_virtual: The X86 platform is not defined.
Therefore, the following hashed_page_virtual is defined on the i386 platform that supports high-end memory.
#if defined(CONFIG_HIGHMEM) && !defined(WANT_PAGE_VIRTUAL) #define HASHED_PAGE_VIRTUAL #endif
|
1. // This is false. page_address () is not defined here on i386.
#if defined(WANT_PAGE_VIRTUAL)
#define page_address(page) ((page)->virtual)
#define set_page_address(page, address) \\ do { \\ (page)->virtual = (address); \\ } while(0) #define page_address_init() do { } while(0) #endif
|
2. // on the i386 platform without config_highmem configured, page_address is defined here
#if !defined(HASHED_PAGE_VIRTUAL) && !defined(WANT_PAGE_VIRTUAL)
#define page_address(page) lowmem_page_address(page)
#define set_page_address(page, address) do { } while(0)
#define page_address_init() do { } while(0)
#endif
|
3. // Therefore, page_address () is defined here on the i386 platform that supports high-end memory.
#if defined(HASHED_PAGE_VIRTUAL)
void *page_address(struct page *page);
void set_page_address(struct page *page, void *virtual);
void page_address_init(void);
#endif
|
II.Implementation of page_address () corresponding to page in low-end memory
Page_address () is equivalentlowmem_page_address
():
#define page_address(page) lowmem_page_address(page)
static __always_inline void *lowmem_page_address(struct page *page) { return __va(page_to_pfn(page) << PAGE_SHIFT); }
#define page_to_pfn(page) ((unsigned long)((page) - mem_map) + \ ARCH_PFN_OFFSET)
#define __va(x) ((void *)((unsigned long)(x) + PAGE_OFFSET))
|
We know that the physical address space smaller than 896 m (low-end memory) and the linear address space 3g--3g + 896m are mapped one by one, so we only need to know the physical address corresponding to the page, the linear address space (PA + page_offset) corresponding to the page is displayed ).
So how do I find the physical address corresponding to a page? We know that the physical memory is divided into many pages by size (1 <page_shift), each of which corresponds to a struct page * Page Structure, these page description structures are stored in an array called mem_map, which is stored strictly in the order of physical memory, that is, the first page description structure of the physical system, as the first element of the mem_map array, and so on. Therefore, the physical address of the page can be obtained by multiplying the location of the page in the array mem_map by the page size. The above code is based on this principle:
The page_to_pfn (PAGE) function is used to obtain the position of each page in mem_map, and the left-shift page_shift function is multiplied by the page size. This gives you the physical address of the page. Add page_offset (3G) to the physical address to obtain the linear address of the page.
In the low-end memory (less than 896 MB), the virtual address obtained through the page (struct page * Page) is converted in this way.
3. Implementation of page_address () corresponding to pages in high-end memory:
On the i386 platform with config_highmem configured, page_address is implemented in mm/highmem. C:
/** * page_address - get the mapped virtual address of a page * @page: &struct page to get the virtual address of * * Returns the page\‘s virtual address. */ void *page_address(struct page *page) {
unsigned long flags;
void *ret;
struct page_address_slot *pas;
If (! Pagehighmem (page) // determines whether it belongs to high-end memory. If not, it is low. End-to-End Stored, you can directly find
return lowmem_page_address(page);
PAS = page_slot (PAGE); // see the following analysis. Pas points to the table header of the page_address_map structure corresponding to the page.
ret = NULL;
spin_lock_irqsave(&pas->lock, flags);
if (!list_empty(&pas->lh)) {
struct page_address_map *pam;
list_for_each_entry(pam, &pas->lh, list) {
if (pam->page == page) {
ret = pam->virtual;
goto done; }
}
}
done:
spin_unlock_irqrestore(&pas->lock, flags);
return ret;
}
|
In high-end memory, a structure called page_address_map is introduced because the linear address cannot be obtained directly by adding page_offset to the physical address just like in low-end memory, this structure stores each page (only in high-end memory) and the corresponding virtual address. All mappings in high-end memory are linked through the linked list, this structure is created during high-end memory ing and added to the linked list.
/* * Describes one page-> virtual Association */ Struct page_address_map { Struct page * page; // page Void * virtual; // virtual address Struct list_head list; // point to the next Structure };
|
If the memory is much larger than 896 MB, there will be more pages in the high-end memory (memory-896 MB)/4 K pages, assuming the page size is 4 K ), if you use only one linked list, it takes a long time to search. Therefore, the hash algorithm is introduced here. Multiple linked lists are used, and each page uses a certain hash algorithm, corresponding to a linked list, there are always enough 128 linked lists:
/* * Hash table Bucket */ Static struct page_address_slot { Struct list_head LH; // list of page_address_maps points to
// Page_address_map Structure linked list Spinlock_t lock;/* protect this bucket \'s list */ } Page_address_htable [1 <pa_hash_order];
|
Pa_hash_order = 7, so a total of 1 <7 (128) linked list, each page corresponds to a page_address_htable linked list after the hash algorithm, then, traverse the linked list to find the corresponding page and virtual address.
The following code corresponds to a page_address_htable linked list after page uses the hash algorithm:
static struct page_address_slot *page_slot(struct page *page) { return &page_address_htable[hash_ptr(page, PA_HASH_ORDER)]; }
|
The hash_ptr (Val, BITs) function is a simple hash algorithm on a 32-bit machine, that is, to multiply Val by a golden value golden_ratio_prime_32, In the result (32-bit) take the high bits bit (here 7 digits) as the index of the hash table
static inline u32 hash_32(u32 val, unsigned int bits) { /* On some cpus multiply is faster, on others gcc will do shifts */ u32 hash = val * GOLDEN_RATIO_PRIME_32;
/* High bits are more random, so use them. */ return hash >> (32 - bits); }
|
In this way, after the PAS = page_slot (PAGE) is executed, the PAS points to the header of the linked table where the page_address_map structure corresponding to the page is located.
Then traverse the linked list to find the corresponding linear address (if any). Otherwise, null is returned.
list_for_each_entry(pam, &pas->lh, list) { if (pam->page == page) { ret = pam->virtual; goto done; } }
|