Accurate Memory With USS PSS
Fixing RSS’s shared-page blind spot on embedded Linux — free linux device drivers course
In the previous lecture of this free linux kernel development course we saw that summing RSS across processes overstates real memory usage because shared library pages get counted once per process. This lecture introduces the two metrics that fix that: USS (unique set size) and PSS (proportional set size), and the smem tool that reports them from /proc/<PID>/smaps.
Keywords covered
What You Will Learn
- Why RSS alone cannot answer “how much total RAM is in use”
- What USS and PSS mean, with a worked shared-library example
- How to read
/proc/<PID>/smapsby hand - How to install and run
smem— and the lightweight alternative for targets without Python - An original two-process demo that proves PSS adds up correctly across processes
Prerequisites
This lecture builds directly on VSS/RSS from the previous lecture. You will need a target (or VM) where you can install Python 3, since smem is a Python tool.
The Shared-Page Problem, Restated
Picture six independent daemons on a board, all dynamically linked against the same libc. Each process’s RSS includes every page of libc that it has touched. If you naively add up six RSS values, you count that libc code six times over, even though it physically exists in RAM exactly once. On a small embedded board this error can be the difference between “we have headroom” and “we are about to hit OOM” in your own capacity planning.
USS: Unique Set Size
USS is the memory that is resident and private to one process — not shared with anything else. It is exactly the memory that would be freed the instant that process exited. USS is the most conservative, “worst case if this process dies” number, and it is immune to the shared-library double-counting problem by definition, since shared pages are excluded from it entirely.
PSS: Proportional Set Size
PSS solves the double-counting problem differently: instead of excluding shared pages, it divides each shared page’s cost evenly across every process mapping it. If a 12-page library segment is shared by six processes, each process’s PSS gains exactly 2 pages for that mapping. Sum the PSS of every process on the system and you get the true total physical memory in use — no over- or under-counting.
Reading /proc/PID/smaps By Hand
Every mapped region in a process gets its own block in smaps, with Rss and Pss already broken out per-region:
$ grep -A8 'libc' /proc/$$/smaps | head -9
7f2a1c000000-7f2a1c1c0000 r-xp 00000000 08:01 123456 /usr/lib/x86_64-linux-gnu/libc.so.6
Size: 1792 kB
Rss: 620 kB
Pss: 38 kB
Shared_Clean: 620 kB
Shared_Dirty: 0 kB
Private_Clean: 0 kB
Private_Dirty: 0 kB
This single region shows Rss of 620 kB but Pss of only 38 kB — the gap is exactly the shared-page effect: most of this shell’s mapped libc pages are also resident in dozens of other processes on the box.
Installing And Running smem
smem collates every process’s smaps file and totals USS/PSS/RSS in one table:
# On the target (needs Python 3):
$ sudo apt-get install smem # desktop/Debian-based rootfs
$ smem -t -k
PID User Command Swap USS PSS RSS
412 root ep-uniqdemo 0 892 918 1204
588 root ep-shareddemo 0 120 946 1832
591 root ep-shareddemo 0 120 946 1832
------------------------------------------------------------
0 1132 2810 4868
Note the last line: the sum of PSS (2810) is much closer to real physical usage than the sum of RSS (4868) would suggest.
Many embedded rootfs images cannot afford a full Python environment just for one tool. For that case, capture the raw state on-target with smemcap (part of BusyBox) and analyze it later on your desktop:
# On the target (no Python needed):
# smemcap > smem-board-cap.tar
# Copy the tar to your host, then:
$ smem -t -S smem-board-cap.tar
Original Demo: Two Processes, One Shared Library
This original demo builds a small shared library and two processes: one that links it and uses it heavily (creating private, unshared heap allocations too), and one that just links it and idles — so you can see USS diverge while PSS reflects fair sharing.
/* ep_shared_lib.c - a tiny "shared" library, deliberately built as .so */
#include <string.h>
static char pad[512 * 1024]; /* forces real pages once touched */
void ep_touch_shared(void)
{
memset(pad, 0x11, sizeof(pad));
}
/* ep_uniqdemo.c - links the shared lib AND allocates a lot privately */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
extern void ep_touch_shared(void);
int main(void)
{
ep_touch_shared();
char *priv = malloc(2 * 1024 * 1024);
memset(priv, 0x22, 2 * 1024 * 1024); /* private, unique memory */
printf("ep_uniqdemo pid=%d ready\n", getpid());
pause();
return 0;
}
$ gcc -fPIC -shared -o libepshared.so ep_shared_lib.c
$ gcc -o ep_uniqdemo ep_uniqdemo.c -L. -lepshared -Wl,-rpath,.
$ LD_LIBRARY_PATH=. ./ep_uniqdemo &
$ LD_LIBRARY_PATH=. ./ep_uniqdemo & # second copy, shares the .so
Expected smem output — USS differs little between the two copies (each has its own private 2 MB), but the shared library’s contribution shows up fairly split in PSS rather than doubled in RSS:
$ smem -t -k -P ep_uniqdemo
PID User Command Swap USS PSS RSS
2201 pi ep_uniqdemo 0 2148 2402 2860
2202 pi ep_uniqdemo 0 2148 2402 2860
USS vs PSS vs RSS At A Glance
| Metric | Includes shared pages? | Best use |
|---|---|---|
| RSS | Yes, fully, per process | Quick single-process check; do not sum across processes |
| USS | No — private only | “What would I free if I killed this process right now” |
| PSS | Yes, fairly divided | Accurate system-wide total memory in use |
Other Tools Worth Knowing
If smem is unavailable, ps_mem reports similar PSS-based numbers in a simpler format, and on Android-derived systems procrank reports the same class of data and can be cross-compiled for generic embedded Linux with minor changes.
Common Mistakes
- Summing RSS instead of PSS when estimating total system memory use — always sum PSS, never RSS, across processes.
- Assuming USS is “total” memory — it deliberately excludes shared pages and will understate a process’s real footprint if shared libraries dominate.
- Installing full smem on a tiny rootfs when
smemcap+ host-side analysis would avoid pulling in a Python dependency.
Best Practices
- Report USS and PSS together for any process you are investigating — USS for “what frees on exit”, PSS for “fair system-wide share”.
- Use
smemcapon production images to keep Python off the target, and analyze the capture on your development host. - When comparing memory usage between builds, always compare PSS totals, not RSS totals — RSS totals are not stable across changes in the number of processes.
Summary And Key Takeaways
RSS alone cannot be summed meaningfully across processes because shared pages get counted repeatedly. USS gives you the safe, private-only floor; PSS gives you the fair, summable total. smem (or its lightweight smemcap companion) reads this directly out of /proc/<PID>/smaps.
Conclusion
Once you can read USS and PSS confidently, you have a trustworthy, summable picture of memory usage across an entire embedded system — a core skill for anyone progressing through a free linux kernel development course toward real board bring-up and long-running daemon debugging.
FAQ
What does USS stand for and what does it mean?
USS is Unique Set Size — the memory resident and private to one process, which would be freed if that process exited.
What does PSS stand for and what does it mean?
PSS is Proportional Set Size — shared pages are divided fairly across every process that maps them, so summing PSS across processes gives the correct system-wide total.
Why is summing RSS across processes wrong?
Shared library pages are counted in full for every process that maps them, inflating the summed total far beyond actual physical memory used.
Do I need Python to use smem on an embedded target?
Not necessarily — use smemcap (part of BusyBox) to capture /proc state on the target, then analyze the capture with smem on your development host.
Where does smem get its data from?
It reads /proc/PID/smaps, which breaks Rss and Pss down per memory mapping for each process.
Is PSS always smaller than RSS?
PSS is less than or equal to RSS for any process with shared mappings, and equal to RSS for a process with no shared pages at all.
What is the lightweight alternative to smem?
ps_mem reports similar PSS-based numbers in a simpler format without needing the full smem package.
Continue The Free Linux Device Drivers Course
Next up: finding memory leaks with mtrace — a glibc-native leak tracer.
Next Lecture Course Index
2 Comments