Home / Alt manpages / proc_kpageflags(5)

  • proc_kpageflags(5)
  • File format
  • linux

Inspect Physical Page Flags with /proc/kpageflags

You will read the 64-bit flag mask for a physical page frame, decode the bits you care about, and keep the result tied to the correct page frame number (PFN). The interface is read-only, but it exposes physical-memory metadata, so the normal access check may require elevated privilege. Allow about 15 minutes if you already have a PFN to inspect.

The examples use the installed manpages package version 6.7-2 and a Linux 6.8.0-139-generic kernel. The local manual describes the interface through bit 26. Kernel documentation can add or rename meanings over time, so do not copy a flag table from a different kernel release without checking its header or documentation.

1. Confirm that the interface exists

Check the file and its mode before attempting a read:

$ stat -c '%A %U %G %s %n' /proc/kpageflags
-r-------- root root 0 /proc/kpageflags

The procfs file is present only when the kernel was built with CONFIG_PROC_PAGE_MONITOR. Its reported size is zero because procfs generates the data when you read it. The mode shown here means root can read it, while an ordinary account cannot.

Checkpoint: if the path is missing, check the kernel configuration and the procfs mount. Do not create a replacement file. A regular file with the same name would not provide page flags.

2. Read records on eight-byte boundaries

/proc/kpageflags contains one 64-bit mask per PFN. Record number zero starts at byte offset zero; PFN N starts at byte offset N * 8. Reads and seeks must use eight-byte alignment and a length that is a multiple of eight.

Reading is the only step that may need sudo on this machine:

$ sudo od -An -t u8 -N 16 /proc/kpageflags
           4294967296           4294967296

That command reads PFNs 0 and 1 as unsigned decimal 64-bit values. The values are masks, not page counts or physical addresses. They can differ between boots, kernels and memory states. If sudo is not available, the same command as an ordinary user should fail with a permission error; that is an access result, not evidence that the interface is absent.

Do not use an arbitrary byte offset such as 1, and do not ask head for a text preview. The file is a binary sequence of fixed-size records.

3. Decode one PFN without changing the system

Use this small Python program when you have a PFN from another page-table or memory diagnostic. It seeks to that record, reads exactly eight bytes, and prints each flag whose bit is set:

#!/usr/bin/env python3
import os
import struct
import sys

FLAGS = {
    0: 'LOCKED', 1: 'ERROR', 2: 'REFERENCED', 3: 'UPTODATE',
    4: 'DIRTY', 5: 'LRU', 6: 'ACTIVE', 7: 'SLAB',
    8: 'WRITEBACK', 9: 'RECLAIM', 10: 'BUDDY', 11: 'MMAP',
    12: 'ANON', 13: 'SWAPCACHE', 14: 'SWAPBACKED',
    15: 'COMPOUND_HEAD', 16: 'COMPOUND_TAIL', 17: 'HUGE',
    18: 'UNEVICTABLE', 19: 'HWPOISON', 20: 'NOPAGE',
    21: 'KSM', 22: 'THP', 23: 'OFFLINE', 24: 'ZERO_PAGE',
    25: 'IDLE', 26: 'PGTABLE',
}

if len(sys.argv) != 2 or not sys.argv[1].isdigit():
    raise SystemExit(f'usage: {sys.argv[0]} PFN')
pfn = int(sys.argv[1], 10)
with open('/proc/kpageflags', 'rb', buffering=0) as flags_file:
    flags_file.seek(pfn * 8, os.SEEK_SET)
    raw = flags_file.read(8)
if len(raw) != 8:
    raise SystemExit('could not read a complete 64-bit record')
mask = struct.unpack('=Q', raw)[0]
print(f'PFN {pfn}: mask 0x{mask:016x}')
for bit, name in FLAGS.items():
    if mask & (1 << bit):
        print(f'  bit {bit}: {name}')

Save it as a temporary file or paste it into your own diagnostic tool, then run it with a real PFN:

$ sudo python3 /path/to/show-kpageflags.py 12345
PFN 12345: mask 0x0000000000000000

The zero mask is a possible result, not a promised result for PFN 12345. Replace that placeholder with the PFN you actually intend to investigate. The script uses the host's native 64-bit byte order, which matches the binary kernel interface on the host running it.

4. Connect a PFN to a process carefully

/proc/kpageflags is indexed by physical frame number, not by a virtual address and not by a process ID. The corresponding /proc/<pid>/pagemap file can map a process virtual page to a PFN when the page is present, but PFNs are restricted: current kernel documentation says that CAP_SYS_ADMIN is required to obtain them. Without that capability, the PFN field is zeroed on modern kernels.

That restriction is a security boundary. Do not treat a zero PFN as proof that the process uses physical frame zero, and do not weaken it by changing permissions or boot settings. If you are investigating a process, use the least-privileged approved diagnostic path and record the kernel version alongside the result.

A page flag is also a point-in-time observation. For example, DIRTY, REFERENCED and IDLE can change as the kernel runs. The current documentation says that the idle flag can be stale until the page-idle bitmap is read. Take repeated samples only when your diagnostic question needs them, and label each sample with its timestamp and PFN.

5. Keep the flag names version-specific

The installed proc_kpageflags(5) page, dated 15 August 2023, labels bit 23 KPF_BALLOON and lists bits 24 to 26 as KPF_ZERO_PAGE, KPF_IDLE and KPF_PGTABLE. The kernel 6.8 UAPI header installed on this host and the current kernel documentation label bit 23 KPF_OFFLINE instead. This is not a harmless spelling variation if you are comparing reports from different versions.

For a report made against this host's kernel, use the UAPI header or the kernel documentation that matches the running kernel, and include that source in the report. For a report intended to reproduce the installed manpage exactly, call the bit KPF_BALLOON as that page does. Do not infer a flag's meaning from a set bit alone.

6. Finish with a reproducible record

Keep the command, PFN, mask, kernel release and privilege context together. This gives another operator enough information to repeat the read without guessing:

$ uname -r
6.8.0-139-generic
$ getconf PAGE_SIZE
4096
$ sudo python3 /path/to/show-kpageflags.py 12345
PFN 12345: mask 0x0000000000000000

PAGE_SIZE is useful when the PFN came from a virtual address calculation, but it does not change the eight-byte record size in /proc/kpageflags. Never edit procfs files, redirect output back into them, or run a page-flag read as part of a script that also changes memory policy unless that change has been separately reviewed.

Done means

  • /proc/kpageflags exists and its access requirement is understood.
  • The PFN was converted to an offset with PFN * 8, and reads stayed eight-byte aligned.
  • The returned 64-bit value was treated as a bit mask, not as a count or address.
  • The decoded names match the relevant kernel version, including the bit 23 naming difference.
  • The report records the PFN, mask, kernel release, page size and privilege context.