Map a Page to Its Memory Cgroup with /proc/kpagecgroup

A container is eating memory, and /proc/kpagecgroup can prove which cgroup a specific physical page belongs to. You will inspect the kernel's page-to-memory-cgroup map and decode entries without treating the file as ordinary text. Allow about 15 minutes.

You need a Linux system with /proc mounted, the standard dd and od utilities, and elevated privileges to read the file on this machine. The examples are read-only: they do not change a cgroup, reclaim memory or alter a process.

The local source is the proc_kpagecgroup(5) man page from the manpages package, version 6.7-2. The running kernel here is 6.8.0-139-generic. The man page describes the interface as available since Linux 4.3, but the kernel's exact access policy still matters on each host.

1. Check that the interface exists

Start by checking the exact path, without using sudo yet:

$ ls -l /proc/kpagecgroup
-r-------- 1 root root 0 Sep 26 13:02 /proc/kpagecgroup

The apparent size of zero is normal for this procfs interface. It does not mean the map has no entries: the file is a virtual, indexed view supplied by the kernel rather than a stored byte stream with a useful filesystem length.

On the example host, the file is present but readable only by root. If the path is missing, check that you are looking at the host's procfs and that the running kernel was built with CONFIG_MEMCG. The man page says the interface is present only when that kernel configuration option is enabled:

$ grep '^CONFIG_MEMCG=' /boot/config-$(uname -r)
CONFIG_MEMCG=y

Your distribution may store the running kernel's configuration elsewhere, or may not install it. A missing configuration file is not proof that memory cgroups are disabled.

2. Read an entry with the required privilege

Each page-frame number, or PFN, selects one entry. That entry is a 64-bit inode number identifying the memory cgroup charged for the page. The first entry is the value at PFN zero. Read it as one eight-byte unsigned integer:

$ sudo dd if=/proc/kpagecgroup bs=8 count=1 status=none | od -An -tu8
                   0

The leading spaces are just od's formatting. A zero is a valid decoded value, not an error message. Do not use cat or a text editor here: the data is binary, and arbitrary bytes may be displayed as control characters or misleading text.

Checkpoint: if the command prints Permission denied, rerun the same read through sudo. If sudo is not available, ask an administrator to perform the read or grant the narrowly scoped access required by your system policy. Do not make the procfs tree world-readable just to simplify an inspection.

3. Select a different page-frame number

For PFN 12345, use that number as the number of eight-byte records to skip:

$ sudo dd if=/proc/kpagecgroup bs=8 skip=12345 count=1 status=none | od -An -tu8
                   0

Replace 12345 with a real PFN. This command reads one record at offset PFN * 8; it does not read the whole virtual file into memory. count=1 is the safety boundary worth keeping in scripts, because an accidental full scan can create unnecessary work on a large system.

When the selected page is charged to a non-root memory cgroup, the printed number is the cgroup directory's inode number. It is not the cgroup path and it is not a byte count. To resolve it, search the mounted cgroup hierarchy for a directory with the same inode:

$ sudo find /sys/fs/cgroup -xdev -type d -inum 123456789 -print
/sys/fs/cgroup/my-service

Replace 123456789 with the decoded value. The hierarchy may use a different mount point, and a cgroup can disappear between the two reads. An empty result means the inode was not found in that hierarchy at that moment, not necessarily that the page was never charged.

4. Obtain PFNs carefully

/proc/kpagecgroup is indexed by PFN, so it is useful alongside a process's /proc/<pid>/pagemap. That file maps virtual-page queries to page-frame information. The kernel restricts pagemap details on many systems because PFNs can expose information useful for attacks, so an unprivileged read may be incomplete or denied.

Do not guess a PFN from a virtual address, and do not assume a successful read of /proc/<pid>/pagemap grants access to every page. Follow the running kernel's pagemap access rules, record the permissions used, and keep the two interfaces distinct: pagemap supplies the index, while kpagecgroup supplies the cgroup inode for that index.

A useful first check is simply to confirm the target process has a proc entry:

$ test -r /proc/1234/pagemap && echo "pagemap path is present" || echo "pagemap is unavailable"
pagemap path is present

Replace 1234 with the target process ID. This does not prove PFN data will be readable. It only prevents a common mistake: debugging the wrong PID or a process that has already exited.

5. Keep the result in context

The map answers one narrow question: which memory cgroup inode the kernel charges a page to. It does not report a cgroup's current usage, limit, reclaim history or process membership. Read the cgroup's own files for those questions, using the cgroup version and mount layout active on the host.

There is no configuration file to edit and no enable command associated with /proc/kpagecgroup. The interface appears because the kernel has memory-cgroup support enabled. Changing cgroup limits or moving a process is a separate administrative operation and can affect service availability, so do not combine it with this read-only investigation.

For repeatable diagnostics, save the kernel release, the PFN, the decoded inode, the cgroup mount searched and the timestamp. That makes a later empty lookup explainable when a short-lived cgroup has been removed.

Done means