Linux memory demystified

Poby’s Home
4 min readJun 3, 2020

--

snapshot of “/proc/{$process}/status”
VmPeak: 1598724 kB
VmSize: 1598724 kB
VmLck: 0 kB
VmPin: 0 kB
VmHWM: 187852 kB
VmRSS: 187852 kB
RssAnon: 79612 kB
RssFile: 102264 kB
RssShmem: 5976 kB
VmData: 813488 kB
VmStk: 136 kB
VmExe: 83444 kB
VmLib: 40380 kB

https://ewx.livejournal.com/579283.html

VmPeak: Peak “address space” used.

VmSize: Current “address space” in use.

VmHWM: High Water Mark of “physical RAM”

VmRSS: Current usage of “physical RAM”

VmData: VmSize minus Size of (shared pages and stack pages)

VmStk: the size of the stack of the “initial thread” in the process

#include <stdio.h>
#include <new>
#include <sys/mman.h>
#include <sys/types.h>
#include <unistd.h>
#define ALLOC_SIZE 4*1024*1024
unsigned char* pool[1024*1024*4];
unsigned long long size = 0;
void printProc(const char* description)
{
char tmp[513];
sprintf(tmp, "/proc/%d/status", getpid());
if (FILE *fstat = fopen(tmp, "r")) {
const int r = fread(tmp, 1, 512, fstat);
if (r > 0) {
tmp[r] = '\0';
printf("%s ==================\n", description);
printf("%s\n", tmp);
} else {
printf("Failed to read from %s", tmp);
}
fclose(fstat);
}
}
int main()
{
int i = 0;
while (true) {
unsigned char* pool_
= static_cast<unsigned char*>(mmap(nullptr,
16*1024*1024,
PROT_READ | PROT_WRITE,
MAP_ANONYMOUS | MAP_PRIVATE,
-1, 0));
if (pool_ == nullptr ||
pool_ == reinterpret_cast<void*>(MAP_FAILED) ) {
printProc("mmap failed");
close();
assert(0); // want to know what caused MAP_FAILED
}
}
}

This will result in

errno:12 
Umask: 0002
State: R (running)
Tgid: 9548
Ngid: 0
Pid: 9548
PPid: 5393
TracerPid: 0
Uid: 1000 1000 1000 1000
Gid: 1000 1000 1000 1000
FDSize: 256
Groups: 4 24 27 30 46 113 128 1000
NStgid: 9548
NSpid: 9548
NSpgid: 9548
NSsid: 5393
VmPeak: 4151256 kB
VmSize: 4151256 kB
VmLck: 68 kB
VmPin: 0 kB
VmHWM: 175144 kB
VmRSS: 175144 kB
RssAnon: 79196 kB
RssFile: 91200 kB
RssShmem: 4748 kB
VmData: 3929320 kB
VmStk: 136 kB
VmExe: 57488 kB
VmLib: 43780 kB
VmPTE

It failed with errno 12 which means cannot allocate memory. Here, the reason is that it depleted all virtual memory address space with `VmSize: 4151256 kB`

When filling the memory with random values

int i = 0;
while (true) {
pool_ = static_cast<unsigned char*>(mmap(nullptr,
16*1024*1024,
PROT_READ | PROT_WRITE,
MAP_ANONYMOUS | MAP_PRIVATE,
-1, 0));
if (pool_ == nullptr ||
pool_ == reinterpret_cast<void*>(MAP_FAILED) ) {
printProc("mmap failed");
close();
assert(0); // want to know what caused MAP_FAILED
} else {
RAND_bytes(pool_, 16*1024*1024);
}

still, we fail when there is no virtual address space left.

Umask: 0002
State: R (running)
Tgid: 12279
Ngid: 0
Pid: 12279
PPid: 5393
TracerPid: 0
Uid: 1000 1000 1000 1000
Gid: 1000 1000 1000 1000
FDSize: 256
Groups: 4 24 27 30 46 113 128 1000
NStgid: 12279
NSpid: 12279
NSpgid: 12279
NSsid: 5393
VmPeak: 4152612 kB
VmSize: 4152612 kB
VmLck: 68 kB
VmPin: 0 kB
VmHWM: 3502280 kB
VmRSS: 3502280 kB
RssAnon: 3405460 kB
RssFile: 91096 kB
RssShmem: 5724 kB
VmData: 3929764 kB
VmStk: 136 kB
VmExe: 57488 kB
VmLib: 43780 kB

Explanation from “man top”

Linux Memory Types

For our purposes, there are three types of memory, and one is optional.

  • physical memory: a limited resource where code and data must reside when executed or referenced.
  • (optional) swap file: where modified (dirty) memory can be saved and later retrieved if too many demands are made on physical memory.
  • virtual memory: a nearly unlimited resource serving the following goals:

1. abstraction, free from physical memory addresses/limits
2. isolation, every process in a separate address space
3. sharing, a single mapping can serve multiple needs
4. flexibility, assign a virtual address to a file

Regardless of which of these forms memory may take, all are managed as pages (typically 4096 bytes) but expressed by default in ‘top’ as KiB (kibibyte).

$ getconf PAGESIZE
4096

For each such process, every memory page is restricted to a single quadrant from the table below.

Both physical memory and virtual memory can include any of the four, while the swap file only includes #1 through #3.

The memory in quadrant #4, when modified, acts as its own dedicated swap file.

The following may help in interpreting process-level memory values displayed as scalable columns.

  • %MEM — simply RES divided by total physical memory.
  • CODE — the `pgms’ portion of quadrant 3.
  • DATA — the entire quadrant 1 portion of VIRT plus all explicit mmap file-backed pages of quadrant 3.
  • RES — anything occupying physical memory which, beginning with Linux-4.5, is the sum of the following three fields:
    RSan — quadrant 1 pages, which include any former quadrant 3 pages if modified
    RSfd — quadrant 3 and quadrant 4 pages
    RSsh — quadrant 2 pages
  • RSlk — subset of RES which cannot be swapped out (any quadrant)
  • SHR — subset of RES (excludes 1, includes all 2 & 4, some 3)
  • SWAP — potentially any quadrant except 4
  • USED — simply the sum of RES and SWAP
  • VIRT — everything in-use and/or reserved (all quadrants)

Note: Even though program images and shared libraries are considered private to a process, they will be accounted for as shared (SHR) by the kernel.

--

--

No responses yet