Reading Linux Transparent Huge Pages settings: enabled, per-size controls, madvise and khugepaged
THP has a top-level enabled file and per-size hugepages-<size>kB/enabled files. It covers inherit, MADV_HUGEPAGE/MADV_NOHUGEPAGE and khugepaged, and how to check real use in /proc and sysfs.
Transparent Huge Pages (THP) is the Linux kernel feature that backs virtual memory with pages larger than the base page size, automatically. Current kernel documentation has per-size settings in addition to /sys/kernel/mm/transparent_hugepage/enabled. If you read only the top-level file and conclude that THP is “on” or “off”, you can be wrong.
This post follows the docs.kernel.org documentation as checked on 13 October 2026 (the pages show the build label 7.3.0-rc7) and madvise(2) from man-pages 6.19. On a different kernel some files or values may not exist, so read this alongside the documentation for your kernel version. Every command below is read-only.
What THP does
The kernel documentation assumes a 4 KB base page and a 2 MB huge page in its examples. The real sizes depend on the CPU architecture. It names two benefits.
- Only one page fault happens for each 2 MB region a program touches, so the program enters and leaves the kernel 512 times less often. This only helps on first access to a mapping, and each fault has a larger page to clear or copy.
- The bigger effect is on every access after that. One TLB entry covers far more virtual memory, so there are fewer TLB misses, and a miss is handled faster.
There is a cost. If a program mmaps a large region and touches a single byte, it can get a 2 MB page instead of a 4 KB one. That is why a mode exists that uses THP only in regions marked with MADV_HUGEPAGE instead of system-wide.
The opening of the kernel document says THP currently works only for anonymous memory and tmpfs/shmem. The same document’s khugepaged section and madvise(2), however, also cover collapsing regular file mappings to PMD size under certain conditions. This post deals mainly with anonymous memory.
Recent kernels also have multi-size THP (mTHP). It allocates anonymous memory in sizes larger than a base page but smaller than PMD size (2 MB in the example above), such as 16K, 32K and 64K. mTHP pages are still mapped with PTEs, but the documentation says page faults drop, and because each page is smaller, latency spikes at fault time are much less prominent than with PMD-sized pages.
Settings come in two layers
THP settings for anonymous memory live in two places.
| File | Accepted values | Default (per the docs) |
|---|---|---|
/sys/kernel/mm/transparent_hugepage/enabled |
always, madvise, never |
Not stated in this document |
/sys/kernel/mm/transparent_hugepage/hugepages-<size>kB/enabled |
always, madvise, never, inherit |
inherit for PMD size, never for every other size |
If a per-size file says inherit, that size follows the top-level enabled value. Otherwise the per-size value applies as is. Which size directories exist varies by system. If several sizes are enabled, the kernel picks the most appropriate enabled size for each allocation.
The values mean:
always: use THP system-wide.madvise: use THP only in regions marked withmadvise(MADV_HUGEPAGE).never: this setting does not use THP. There is an exception, covered below.
The document does not give the top-level default. It only says the boot-time default can be changed with the boot parameter transparent_hugepage=always|madvise|never. Per-size control uses the thp_anon= parameter, and once it appears even once, every size not listed, PMD size included, becomes never. So check the kernel command line for thp_anon= as well.
You can read the current state like this:
cat /sys/kernel/mm/transparent_hugepage/enabled
cat /sys/kernel/mm/transparent_hugepage/hpage_pmd_size
grep -H . /sys/kernel/mm/transparent_hugepage/hugepages-*kB/enabled
cat /proc/cmdline
hpage_pmd_size gives the size of a PMD-sized THP in bytes. The kernel documentation only shows examples of writing to these files and does not describe what reading them prints. If there are no hugepages-*kB directories at all, your kernel probably differs from the documentation used here, so check the docs for that version.
tmpfs and shmem have their own settings: shmem_enabled, the per-size hugepages-<size>kB/shmem_enabled, and the tmpfs mount option huge=. This post covers only the anonymous memory side.
THP can appear even when everything is never
The kernel documentation states that setting every sysfs THP control to never does not disable THP completely. madvise(..., MADV_COLLAPSE) ignores those settings and collapses the range into PMD-sized huge pages. The madvise(2) manual also says MADV_COLLAPSE (since Linux 6.1) is independent of every setting under /sys/kernel/mm/transparent_hugepage. It does respect regions marked MADV_NOHUGEPAGE.
The way the documentation gives to turn THP off completely for one process is prctl(PR_SET_THP_DISABLE, 1, 0, 0, 0). It disables THP for that process regardless of global settings or MADV_COLLAPSE, and the setting is inherited across fork(2) and execve(2). Calling prctl(PR_SET_THP_DISABLE, 1, PR_THP_DISABLE_EXCEPT_ADVISED, 0, 0) instead, with the constant as the third argument after 1, allows THP only in regions requested with MADV_HUGEPAGE or MADV_COLLAPSE.
To see whether a process has THP fully disabled, look at THP_enabled in /proc/<PID>/status. It is 0 when PR_SET_THP_DISABLE has disabled THP completely, not just partially.
grep THP_enabled /proc/<PID>/status
MADV_HUGEPAGE and MADV_NOHUGEPAGE
In madvise mode, an application has to mark regions itself to get THP. Both flags exist since Linux 2.6.38.
MADV_HUGEPAGEenables THP for the given range. The kernel regularly scans regions marked this way and replaces them with huge pages, and it can allocate huge pages directly when the region is naturally aligned to the huge page size.MADV_NOHUGEPAGEensures the given range is not backed by THP.
The manual lists these conditions:
- The flags are available only if the kernel was built with
CONFIG_TRANSPARENT_HUGEPAGE. File and shmem memory also needCONFIG_READ_ONLY_THP_FOR_FS. - For every memory type, only parts on huge page boundaries are replaced. File mappings, tmpfs included, must also be aligned within the file, and for non-tmpfs files the file must not be open for write and the mapping must be executable.
- Stack memory and regions with flags such as
VM_NOHUGEPAGE,VM_HUGETLBorVM_IOare not eligible.
The VmFlags line in /proc/<PID>/smaps shows which regions carry a flag: hg is the huge page advise flag and nh the no huge page advise flag.
The manual warns that MADV_HUGEPAGE can very easily waste memory, and says to use it only in regions whose access pattern you know in advance won’t grow the memory footprint. It also says “most common kernel configurations provide MADV_HUGEPAGE-style behavior by default”. The actual default depends on your distribution and kernel build configuration, so check it yourself. This manual does not mention the per-size mTHP controls.
defrag: how hard to try for an allocation
Even when THP is wanted, a large contiguous free block may not be available right away. /sys/kernel/mm/transparent_hugepage/defrag decides what happens then.
| Value | Behavior |
|---|---|
always |
On allocation failure, stall and do direct reclaim and compaction on the spot |
defer |
Wake kswapd and kcompactd to prepare in the background; khugepaged installs the THP later |
defer+madvise |
Behave like always for MADV_HUGEPAGE regions and like defer for the rest |
madvise |
Do direct reclaim only for MADV_HUGEPAGE regions. The documented default |
never |
Don’t try to defragment; fall back to small pages right away |
Even with never, MADV_COLLAPSE can still produce THPs. The documentation also notes that the expectation of gaining more from THP than the CPU time spent on defrag isn’t always met.
What khugepaged does and doesn’t do
khugepaged is a daemon that scans memory and collapses small pages into huge pages. It starts automatically when any THP size is enabled (a per-size or the top-level setting is always or madvise) and stops when all sizes are disabled (per-size and top-level settings are all never).
Its scope is limited.
- File and shmem memory are collapsed only to PMD size. Only anonymous memory is collapsed to other sizes.
- An mTHP collapse fails if the range contains any shared or swapped-out page.
MADV_COLLAPSEcollapses only to PMD size and does not attempt mTHP.
Its settings are under /sys/kernel/mm/transparent_hugepage/khugepaged/. pages_to_scan is the number of pages scanned per pass, scan_sleep_millisecs the wait between passes, and max_ptes_none the number of empty pages allowed when collapsing. A higher max_ptes_none can use more memory; a lower one means fewer collapses. For mTHP collapse only 0 and HPAGE_PMD_NR - 1 are supported; any value in between emits a warning and behaves as 0.
khugepaged/pages_collapsed is only a rough sign of progress, because “collapsed” can mean replacing a PTE mapping with a PMD mapping or replacing physical pages with one huge page. For accurate accounting the documentation points to /proc/vmstat and the per-size stats directories.
Changes to the top-level enabled, the per-size hugepages-<size>kB/enabled and the tmpfs mount option only affect future behavior. To apply them to applications already running, and to regions already registered with khugepaged, you have to restart any application that could have been using huge pages.
Checking whether THP is actually used
A setting being on does not mean THPs were allocated. Usage has to be read separately from /proc.
grep -E '^(AnonHugePages|ShmemHugePages|ShmemPmdMapped|FileHugePages|FilePmdMapped):' /proc/meminfo
grep -E '^(AnonHugePages|ShmemPmdMapped|FilePmdMapped):' /proc/<PID>/smaps_rollup
One catch: AnonHugePages has that name for historical reasons but counts only anonymous THP mapped at PMD size. The documentation says it should have been called AnonHugePmdMapped. smaps has no dedicated entries for THPs that are not mapped at the PMD level, so mTHP such as 64K does not show up here.
| Field | Where | Meaning |
|---|---|---|
AnonHugePages |
meminfo, smaps | Anonymous THP mapped at the PMD level |
ShmemHugePages |
meminfo | shmem and tmpfs memory allocated with huge pages |
ShmemPmdMapped |
meminfo, smaps | shmem mapped into user space with huge pages |
FileHugePages |
meminfo | File data (page cache) allocated with huge pages |
FilePmdMapped |
meminfo, smaps | Page cache mapped with huge pages (excluding shmem/tmpfs in smaps) |
For a single process, smaps_rollup is convenient. It holds the sums over all mappings, and the documentation says deriving the same numbers from smaps costs significantly more. It also warns that reading smaps frequently adds overhead. Look at smaps only when you need to know which mapping holds THPs.
KernelPageSize and MMUPageSize in smaps don’t change when THP is in use, so don’t rely on them. THPeligible being 1 means the mapping is eligible for THP of some currently enabled size. It does not mean THP was actually allocated.
To see each size, mTHP included, read the files under hugepages-<size>kB/stats/.
grep -H . /sys/kernel/mm/transparent_hugepage/hugepages-*kB/stats/nr_anon
grep -H . /sys/kernel/mm/transparent_hugepage/hugepages-*kB/stats/anon_fault_alloc
grep -H . /sys/kernel/mm/transparent_hugepage/hugepages-*kB/stats/anon_fault_fallback
On kernels that don’t expose per-size statistics, these paths or files may be missing. The commands then only report that no matching file exists and print no values.
nr_anon is how many anonymous THPs of that size exist in the whole system. Some may be fully mapped, others partially mapped or have unused subpages. anon_fault_fallback counts faults where allocating or charging a page of that size failed and the kernel fell back to a smaller size or base pages.
What the /proc/vmstat counters tell you
grep -E '^(thp_|compact_(stall|success|fail) )' /proc/vmstat
| Counter | Incremented when |
|---|---|
thp_fault_alloc |
A page fault successfully allocates and charges a huge page |
thp_fault_fallback |
A page fault fails to allocate or charge a huge page and falls back to small pages |
thp_collapse_alloc |
khugepaged finds a range to collapse and allocates a new huge page |
thp_collapse_alloc_failed |
khugepaged finds a range to collapse but the allocation fails |
thp_split_page |
A huge page is split into base pages. A common reason is reclaiming an old huge page |
thp_file_alloc |
A shmem huge page is allocated. Despite the name, it counts only shmem |
compact_stall |
A process stalls to run compaction so a huge page is free |
These counters go up each time an event happens. A single reading says little; read twice at an interval and look at the difference. A rising thp_fault_fallback means some faults wanted a huge page and didn’t get one, and a rising compact_stall means something stalled for an allocation. These counters don’t tell you how long the stalls were. The documentation suggests measuring that with the function tracer and the mm_page_alloc tracepoint.
Successful allocation is not evidence that an application got faster. Measure performance on the workload itself; these counters help narrow down the cause.
Order of checks
- Read
enabledandhugepages-*kB/enabledtogether and work out each size’s effective policy, remembering thatinheritsizes follow the top-level value. - Check
/proc/cmdlinefortransparent_hugepage=andthp_anon=. - Look at the target process’s
THP_enabledand thehgandnhentries inVmFlags. - Check PMD-sized usage in meminfo and
smaps_rollup, and mTHP in the per-sizestats. - Compare the fallback and compaction counters in
/proc/vmstatover an interval.
The kernel documentation has plenty of echo examples for changing settings, but a change to the top-level or per-size enabled only affects future behavior and needs application restarts. Before changing anything on a production system, record the current state using the steps above.
For how to read the other fields of /proc/meminfo, see Linux free shows almost no free memory? Read available first.