High Memory Handling¶
By: Peter Zijlstra <a.p.zijlstra@chello.nl>
What Is High Memory?¶
High memory (highmem) is used when the size of physical memory approaches orexceeds the maximum size of virtual memory. At that point it becomesimpossible for the kernel to keep all of the available physical memory mappedat all times. This means the kernel needs to start using temporary mappings ofthe pieces of physical memory that it wants to access.
The part of (physical) memory not covered by a permanent mapping is what werefer to as ‘highmem’. There are various architecture dependent constraints onwhere exactly that border lies.
In the i386 arch, for example, we choose to map the kernel into every process’sVM space so that we don’t have to pay the full TLB invalidation costs forkernel entry/exit. This means the available virtual memory space (4GiB oni386) has to be divided between user and kernel space.
The traditional split for architectures using this approach is 3:1, 3GiB foruserspace and the top 1GiB for kernel space:
+--------+ 0xffffffff| Kernel |+--------+ 0xc0000000| || User || |+--------+ 0x00000000
This means that the kernel can at most map 1GiB of physical memory at any onetime, but because we need virtual address space for other things - includingtemporary maps to access the rest of the physical memory - the actual directmap will typically be less (usually around ~896MiB).
Other architectures that have mm context tagged TLBs can have separate kerneland user maps. Some hardware (like some ARMs), however, have limited virtualspace when they use mm context tags.
Temporary Virtual Mappings¶
The kernel contains several ways of creating temporary mappings. The followinglist shows them in order of preference of use.
kmap_local_page(),kmap_local_folio()- These functions are used to createshort term mappings. They can be invoked from any context (includinginterrupts) but the mappings can only be used in the context which acquiredthem. The only differences between them consist in the first taking a pointerto astructpageand the second taking a pointer tostructfolioand the byteoffset within the folio which identifies the page.These functions should always be used, whereas
kmap_atomic()andkmap()havebeen deprecated.These mappings are thread-local and CPU-local, meaning that the mappingcan only be accessed from within this thread and the thread is bound to theCPU while the mapping is active. Although preemption is never disabled bythis function, the CPU can not be unplugged from the system viaCPU-hotplug until the mapping is disposed.
It’s valid to take pagefaults in a local kmap region, unless the contextin which the local mapping is acquired does not allow it for other reasons.
As said, pagefaults and preemption are never disabled. There is no need todisable preemption because, when context switches to a different task, themaps of the outgoing task are saved and those of the incoming one arerestored.
kmap_local_page(), as well askmap_local_folio()always returns valid virtualkernel addresses and it is assumed thatkunmap_local()will never fail.On CONFIG_HIGHMEM=n kernels and for low memory pages they return thevirtual address of the direct mapping. Only real highmem pages aretemporarily mapped. Therefore, users may call a plain
page_address()for pages which are known to not come from ZONE_HIGHMEM. However, it isalways safe to use kmap_local_{page,folio}() /kunmap_local().While they are significantly faster than
kmap(), for the highmem case theycome with restrictions about the pointers validity. Contrary tokmap()mappings, the local mappings are only valid in the context of the callerand cannot be handed to other contexts. This implies that users mustbe absolutely sure to keep the use of the return address local to thethread which mapped it.Most code can be designed to use thread local mappings. User shouldtherefore try to design their code to avoid the use of
kmap()by mappingpages in the same thread the address will be used and preferkmap_local_page()orkmap_local_folio().Nesting
kmap_local_page()andkmap_atomic()mappings is allowed to a certainextent (up to KMAP_TYPE_NR) but their invocations have to be strictly orderedbecause the map implementation is stack based. Seekmap_local_page()kdocs(included in the “Functions” section) for details on how to manage nestedmappings.kmap_atomic(). This function has been deprecated; usekmap_local_page().NOTE: Conversions to
kmap_local_page()must take care to follow the mappingrestrictions imposed onkmap_local_page(). Furthermore, the code betweencalls tokmap_atomic()andkunmap_atomic()may implicitly depend on the sideeffects of atomic mappings, i.e. disabling page faults or preemption, or both.In that case, explicit calls topagefault_disable()orpreempt_disable()orboth must be made in conjunction with the use ofkmap_local_page().[Legacy documentation]
This permits a very short duration mapping of a single page. Since themapping is restricted to the CPU that issued it, it performs well, butthe issuing task is therefore required to stay on that CPU until it hasfinished, lest some other task displace its mappings.
kmap_atomic()may also be used by interrupt contexts, since it does notsleep and the callers too may not sleep until afterkunmap_atomic()iscalled.Each call of
kmap_atomic()in the kernel creates a non-preemptible sectionand disable pagefaults. This could be a source of unwanted latency. Thereforeusers should preferkmap_local_page()instead ofkmap_atomic().It is assumed that k[un]
map_atomic()won’t fail.kmap(). This function has been deprecated; usekmap_local_page().NOTE: Conversions to
kmap_local_page()must take care to follow the mappingrestrictions imposed onkmap_local_page(). In particular, it is necessary tomake sure that the kernel virtual memory pointer is only valid in the threadthat obtained it.[Legacy documentation]
This should be used to make short duration mapping of a single page with norestrictions on preemption or migration. It comes with an overhead as mappingspace is restricted and protected by a global lock for synchronization. Whenmapping is no longer needed, the address that the page was mapped to must bereleased with
kunmap().Mapping changes must be propagated across all the CPUs.
kmap()alsorequires global TLB invalidation when the kmap’s pool wraps and it mightblock when the mapping space is fully utilized until a slot becomesavailable. Therefore,kmap()is only callable from preemptible context.All the above work is necessary if a mapping must last for a relativelylong time but the bulk of high-memory mappings in the kernel areshort-lived and only used in one place. This means that the cost of
kmap()is mostly wasted in such cases.kmap()was not intended for longterm mappings but it has morphed in that direction and its use isstrongly discouraged in newer code and the set of the preceding functionsshould be preferred.On 64-bit systems, calls to
kmap_local_page(),kmap_atomic()andkmap()haveno real work to do because a 64-bit address space is more than sufficient toaddress all the physical memory whose pages are permanently mapped.vmap(). This can be used to make a long duration mapping of multiplephysical pages into a contiguous virtual space. It needs globalsynchronization to unmap.
Cost of Temporary Mappings¶
The cost of creating temporary mappings can be quite high. The arch has tomanipulate the kernel’s page tables, the data TLB and/or the MMU’s registers.
If CONFIG_HIGHMEM is not set, then the kernel will try and create a mappingsimply with a bit of arithmetic that will convert the pagestructaddress intoa pointer to the page contents rather than juggling mappings about. In such acase, the unmap operation may be a null operation.
If CONFIG_MMU is not set, then there can be no temporary mappings and nohighmem. In such a case, the arithmetic approach will also be used.
i386 PAE¶
The i386 arch, under some circumstances, will permit you to stick up to 64GiBof RAM into your 32-bit machine. This has a number of consequences:
Linux needs a page-frame structure for each page in the system and thepageframes need to live in the permanent mapping, which means:
you can have 896M/sizeof(
structpage) page-frames at most; withstructpagebeing 32-bytes that would end up being something in the order of 112Gworth of pages; the kernel, however, needs to store more than justpage-frames in that memory...PAE makes your page tables larger - which slows the system down as moredata has to be accessed to traverse in TLB fills and the like. Oneadvantage is that PAE has more PTE bits and can provide advanced featureslike NX and PAT.
The general recommendation is that you don’t use more than 8GiB on a 32-bitmachine - although more might work for you and your workload, you’re prettymuch on your own - don’t expect kernel developers to really care much if thingscome apart.
Functions¶
Parameters
structpage*pagePointer to the page to be mapped
Return
The virtual address of the mapping
Description
Can only be invoked from preemptible task context because on 32bitsystems with CONFIG_HIGHMEM enabled this function might sleep.
For systems with CONFIG_HIGHMEM=n and for pages in the low memory areathis returns the virtual address of the direct kernel mapping.
The returned virtual address is globally visible and valid up to thepoint where it is unmapped viakunmap(). The pointer can be handed toother contexts.
For highmem pages on 32bit systems this can be slow as the mapping spaceis limited and protected by a global lock. In case that there is nomapping slot available the function blocks until a slot is released viakunmap().
Parameters
conststructpage*pagePointer to the page which was mapped by
kmap()
Description
Counterpart tokmap(). A NOOP for CONFIG_HIGHMEM=n and for mappings ofpages in the low memory area.
- structpage*kmap_to_page(void*addr)¶
Get the page for a kmap’ed address
Parameters
void*addrThe address to look up
Return
The page which is mapped toaddr.
- voidkmap_flush_unused(void)¶
Flush all unused kmap mappings in order to remove stray mappings
Parameters
voidno arguments
Parameters
conststructpage*pagePointer to the page to be mapped
Return
The virtual address of the mapping
Description
Can be invoked from any context, including interrupts.
Requires careful handling when nesting multiple mappings because the mapmanagement is stack based. The unmap has to be in the reverse order ofthe map operation:
addr1 = kmap_local_page(page1);addr2 = kmap_local_page(page2);...kunmap_local(addr2);kunmap_local(addr1);
Unmapping addr1 before addr2 is invalid and causes malfunction.
Contrary tokmap() mappings the mapping is only valid in the context ofthe caller and cannot be handed to other contexts.
On CONFIG_HIGHMEM=n kernels and for low memory pages this returns thevirtual address of the direct mapping. Only real highmem pages aretemporarily mapped.
Whilekmap_local_page() is significantly faster thankmap() for the highmemcase it comes with restrictions about the pointer validity.
On HIGHMEM enabled systems mapping a highmem page has the side effect ofdisabling migration in order to keep the virtual address stable acrosspreemption. No caller ofkmap_local_page() can rely on this side effect.
- void*kmap_local_folio(conststructfolio*folio,size_toffset)¶
Map a page in this folio for temporary usage
Parameters
conststructfolio*folioThe folio containing the page.
size_toffsetThe byte offset within the folio which identifies the page.
Description
Requires careful handling when nesting multiple mappings because the mapmanagement is stack based. The unmap has to be in the reverse order ofthe map operation:
addr1 = kmap_local_folio(folio1, offset1);addr2 = kmap_local_folio(folio2, offset2);...kunmap_local(addr2);kunmap_local(addr1);
Unmapping addr1 before addr2 is invalid and causes malfunction.
Contrary tokmap() mappings the mapping is only valid in the context ofthe caller and cannot be handed to other contexts.
On CONFIG_HIGHMEM=n kernels and for low memory pages this returns thevirtual address of the direct mapping. Only real highmem pages aretemporarily mapped.
While it is significantly faster thankmap() for the highmem case itcomes with restrictions about the pointer validity.
On HIGHMEM enabled systems mapping a highmem page has the side effect ofdisabling migration in order to keep the virtual address stable acrosspreemption. No caller ofkmap_local_folio() can rely on this side effect.
Context
Can be invoked from any context.
Return
The virtual address ofoffset.
Parameters
conststructpage*pagePointer to the page to be mapped
Return
The virtual address of the mapping
Description
In fact a wrapper aroundkmap_local_page() which also disables pagefaultsand, depending on PREEMPT_RT configuration, also CPU migration andpreemption. Therefore users should not count on the latter two side effects.
Mappings should always be released bykunmap_atomic().
Do not use in new code. Usekmap_local_page() instead.
It is used in atomic context when code wants to access the contents of apage that might be allocated from high memory (see __GFP_HIGHMEM), forexample a page in the pagecache. The API has two functions, and theycan be used in a manner similar to the following:
// Find the page of interest.struct page *page = find_get_page(mapping, offset);// Gain access to the contents of that page.void *vaddr = kmap_atomic(page);// Do something to the contents of that page.memset(vaddr, 0, PAGE_SIZE);// Unmap that page.kunmap_atomic(vaddr);
Note that thekunmap_atomic() call takes the result of thekmap_atomic()call, not the argument.
If you need to map two pages because you want to copy from one page toanother you need to keep the kmap_atomic calls strictly nested, like:
vaddr1 = kmap_atomic(page1);vaddr2 = kmap_atomic(page2);
memcpy(vaddr1, vaddr2, PAGE_SIZE);
kunmap_atomic(vaddr2);kunmap_atomic(vaddr1);
- structfolio*vma_alloc_zeroed_movable_folio(structvm_area_struct*vma,unsignedlongvaddr)¶
Allocate a zeroed page for a VMA.
Parameters
structvm_area_struct*vmaThe VMA the page is to be allocated for.
unsignedlongvaddrThe virtual address the page will be inserted into.
Description
This function will allocate a page suitable for inserting into thisVMA at this virtual address. It may be allocated from highmem orthe movable zone. An architecture may provide its own implementation.
Return
A folio containing one allocated and zeroed page or NULL ifwe are out of memory.
- voidmemcpy_from_folio(char*to,structfolio*folio,size_toffset,size_tlen)¶
Copy a range of bytes from a folio.
Parameters
char*toThe memory to copy to.
structfolio*folioThe folio to read from.
size_toffsetThe first byte in the folio to read.
size_tlenThe number of bytes to copy.
- voidmemcpy_to_folio(structfolio*folio,size_toffset,constchar*from,size_tlen)¶
Copy a range of bytes to a folio.
Parameters
structfolio*folioThe folio to write to.
size_toffsetThe first byte in the folio to store to.
constchar*fromThe memory to copy from.
size_tlenThe number of bytes to copy.
Parameters
structfolio*folioThe folio to zero.
size_toffsetThe byte offset in the folio to start zeroing at.
void*kaddrThe address the folio is currently mapped to.
Description
If you have already usedkmap_local_folio() to map a folio, writtensome data to it and now need to zero the end of the folio (and flushthe dcache), you can use this function. If you do not have thefolio kmapped (eg the folio has been partially populated by DMA),usefolio_zero_range() orfolio_zero_segment() instead.
Return
An address which can be passed tokunmap_local().
- voidfolio_fill_tail(structfolio*folio,size_toffset,constchar*from,size_tlen)¶
Copy some data to a folio and pad with zeroes.
Parameters
structfolio*folioThe destination folio.
size_toffsetThe offset intofolio at which to start copying.
constchar*fromThe data to copy.
size_tlenHow many bytes of data to copy.
Description
This function is most useful for filesystems which support inline data.When they want to copy data from the inode into the page cache, thisfunction does everything for them. It supports large folios even onHIGHMEM configurations.
- size_tmemcpy_from_file_folio(char*to,structfolio*folio,loff_tpos,size_tlen)¶
Copy some bytes from a file folio.
Parameters
char*toThe destination buffer.
structfolio*folioThe folio to copy from.
loff_tposThe position in the file.
size_tlenThe maximum number of bytes to copy.
Description
Copy up tolen bytes from this folio. This may be limited by PAGE_SIZEif the folio comes from HIGHMEM, and by the size of the folio.
Return
The number of bytes copied from the folio.
- voidfolio_zero_segments(structfolio*folio,size_tstart1,size_txend1,size_tstart2,size_txend2)¶
Zero two byte ranges in a folio.
Parameters
structfolio*folioThe folio to write to.
size_tstart1The first byte to zero.
size_txend1One more than the last byte in the first range.
size_tstart2The first byte to zero in the second range.
size_txend2One more than the last byte in the second range.
Parameters
structfolio*folioThe folio to write to.
size_tstartThe first byte to zero.
size_txendOne more than the last byte to zero.
Parameters
structfolio*folioThe folio to write to.
size_tstartThe first byte to zero.
size_tlengthThe number of bytes to zero.
Parameters
structfolio*folioThe folio to release.
void*addrThe address previously returned by a call to
kmap_local_folio().
Description
It is common, eg in directory handling to kmap a folio. This functionunmaps the folio and drops the refcount that was being held to keep thefolio alive while we accessed it.
Parameters
structpage*pagestructpageto map
Description
Returns the page’s virtual memory address.
We cannot call this from interrupts, as it may block.
Parameters
conststructpage*pagestructpageto pin
Description
Returns the page’s current virtual memory address, or NULL if no mappingexists. If and only if a non null address is returned then amatching call tokunmap_high() is necessary.
This can be called from any context.
Parameters
conststructpage*pagestructpageto unmap
Description
If ARCH_NEEDS_KMAP_HIGH_GET is not defined then this may be calledonly from user context.
Parameters
conststructpage*pagestructpageto get the virtual address of
Description
Returns the page’s virtual address.
Parameters
structpage*pagestructpageto setvoid*virtualvirtual address to use
- kunmap_atomic¶
kunmap_atomic(__addr)
Unmap the virtual address mapped by
kmap_atomic()- deprecated!
Parameters
__addrVirtual address to be unmapped
Description
Unmaps an address previously mapped bykmap_atomic() and re-enablespagefaults. Depending on PREEMP_RT configuration, re-enables alsomigration and preemption. Users should not count on these side effects.
Mappings should be unmapped in the reverse order that they were mapped.Seekmap_local_page() for details on nesting.
__addr can be any address within the mapped page, so there is no needto subtract any offset that has been added. In contrast tokunmap(),this function takes the address returned fromkmap_atomic(), not thepage passed to it. The compiler will warn you if you pass the page.
- kunmap_local¶
kunmap_local(__addr)
Unmap a page mapped via
kmap_local_page().
Parameters
__addrAn address within the page mapped
Description
__addr can be any address within the mapped page. Commonly it is theaddress return fromkmap_local_page(), but it can also include offsets.
Unmapping should be done in the reverse order of the mapping. Seekmap_local_page() for details.