/*
* TD3 - exploration 1: the memory zones of a process (original slide 62, updated).
* Prints the address of the code, the data, the heap, a large malloc, a library function and the stack,
* and the region of /proc/self/maps that contains each one. Run it twice: with ASLR (address space
* layout randomization) the addresses change on every run, but the order of the zones does not.
*/
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "utils.h"
int global_array[10]; /* uninitialized global: .bss */
static int initialized = 42; /* initialized global: .data */
/* Finds the line of /proc/self/maps whose range contains addr and prints its permissions and name. */
static void show(const char *what, const void *addr)
{
FILE *maps = fopen("/proc/self/maps", "r");
exit_if(maps == NULL, "/proc/self/maps");
char line[512];
uintptr_t a = (uintptr_t)addr;
while (fgets(line, sizeof line, maps) != NULL) {
unsigned long start, end;
char perms[5], path[256] = "";
if (sscanf(line, "%lx-%lx %4s %*s %*s %*s %255[^\n]", &start, &end, perms, path) < 3)
continue;
if (a >= start && a < end) {
char *name = path;
while (*name == ' ')
name++;
printf("%-24s %#14lx %s %s\n", what, (unsigned long)a, perms, *name ? name : "(anonymous)");
break;
}
}
exit_if(fclose(maps) == EOF, "fclose");
}
int main(void)
{
int local = 0;
void *small = malloc(10);
void *big = malloc(64 << 20); /* above M_MMAP_THRESHOLD (128 KiB): glibc uses mmap */
exit_if(small == NULL || big == NULL, "malloc");
printf("%-24s %14s %s %s\n", "zone", "address", "perm", "region");
show("code (main)", (const void *)main);
show("data (initialized)", &initialized);
show("bss (global_array)", global_array);
show("heap (malloc 10 B)", small);
show("heap (end: sbrk(0))", (char *)sbrk(0) - 1);
show("mmap (malloc 64 MiB)", big);
show("libc (printf)", (const void *)printf);
show("stack (local)", &local);
free(big);
free(small);
return EXIT_SUCCESS;
}
$ ./zones
zone address perm region
code (main) 0x5f935f9d1350 r-xp ~/fso/tds/bin/td3/zones
data (initialized) 0x5f935f9d5010 rw-p ~/fso/tds/bin/td3/zones
bss (global_array) 0x5f935f9d5040 rw-p ~/fso/tds/bin/td3/zones
heap (malloc 10 B) 0x5f938dc96010 rw-p [heap]
heap (end: sbrk(0)) 0x5f938dcb6fff rw-p [heap]
mmap (malloc 64 MiB) 0x71b340fff010 rw-p (anonymous)
libc (printf) 0x71b345064080 r-xp /usr/lib/x86_64-linux-gnu/libc.so.6
stack (local) 0x7fff6c1e68b4 rw-p [stack]
$ for i in 1 2 3; do ./zones | grep main; done
code (main) 0x57a1f784a350 r-xp ~/fso/tds/bin/td3/zones
code (main) 0x5d648e064350 r-xp ~/fso/tds/bin/td3/zones
code (main) 0x56e46bc21350 r-xp ~/fso/tds/bin/td3/zones
El orden de las zonas se mantiene (código, datos y heap abajo; bibliotecas y mmap arriba; la pila en lo más alto), pero la base cambia en cada ejecución: ASLR. El malloc de 64 MiB no está en el heap: glibc usa mmap por encima de 128 KiB.The order of the zones stays (code, data and heap at the bottom; libraries and mmap above; the stack at the very top), but the base changes on every run: ASLR. The 64 MiB malloc is not in the heap: glibc uses mmap above 128 KiB.L’ordre des zones reste le même (code, données et tas en bas ; bibliothèques et mmap au-dessus ; la pile tout en haut), mais la base change à chaque exécution : ASLR. Le malloc de 64 Mio n’est pas dans le tas : la glibc utilise mmap au-delà de 128 Kio.L’ordre de les zones es manté (codi, dades i heap a baix; biblioteques i mmap a sobre; la pila a dalt de tot), però la base canvia a cada execució: ASLR. El malloc de 64 MiB no és al heap: glibc fa servir mmap per sobre de 128 KiB.領域の順序は変わりません(コード、データ、ヒープが下、ライブラリと mmap がその上、スタックが最上部)が、基点は実行のたびに変わります:ASLR。64 MiB の malloc はヒープにありません:glibc は 128 KiB を超えると mmap を使います。
/*
* TD3 - exploration 2: lazy allocation (original slides 59 and 61).
* malloc of a large block returns at once and costs almost no physical memory: the kernel only
* reserves virtual addresses. Each page gets a physical frame the first time it is touched, through a
* (minor) page fault. The program measures the resident memory (VmRSS) and the page faults
* (getrusage) before and after touching half of the block.
* ./lazy [MiB] default: 1024 MiB
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/resource.h>
#include <time.h>
#include <unistd.h>
#include "utils.h"
static long minor_faults(void)
{
struct rusage ru;
exit_if(getrusage(RUSAGE_SELF, &ru) == -1, "getrusage");
return ru.ru_minflt;
}
/* Resident set size in KiB, from /proc/self/status. */
static long rss_kib(void)
{
FILE *f = fopen("/proc/self/status", "r");
exit_if(f == NULL, "/proc/self/status");
char line[256];
long kib = -1;
while (fgets(line, sizeof line, f) != NULL)
if (sscanf(line, "VmRSS: %ld kB", &kib) == 1)
break;
exit_if(fclose(f) == EOF, "fclose");
return kib;
}
static double now(void)
{
struct timespec ts;
exit_if(clock_gettime(CLOCK_MONOTONIC, &ts) == -1, "clock_gettime");
return ts.tv_sec + ts.tv_nsec / 1e9;
}
static void report(const char *step, long f0)
{
printf("%-34s VmRSS = %8ld KiB page faults so far = %ld\n", step, rss_kib(), minor_faults() - f0);
}
int main(int argc, char *argv[])
{
size_t mib = argc > 1 ? (size_t)atol(argv[1]) : 1024;
size_t size = mib << 20, page = (size_t)sysconf(_SC_PAGESIZE);
long f0 = minor_faults();
report("start", f0);
char *p = malloc(size);
exit_if(p == NULL, "malloc");
printf("malloc(%zu MiB) returned %p\n", mib, (void *)p);
report("after malloc", f0);
long before = minor_faults();
double t0 = now();
for (size_t i = 0; i < size / 2; i += page) /* touch one byte of every page of the first half */
p[i] = 1;
double t1 = now();
long faults = minor_faults() - before;
report("after touching half of the pages", f0);
printf("pages touched: %zu (%zu bytes each) page faults: %ld %.0f ns per fault\n",
size / 2 / page, page, faults, faults ? (t1 - t0) * 1e9 / faults : 0.0);
free(p);
report("after free", f0);
return EXIT_SUCCESS;
}
$ ./lazy 1024
start VmRSS = 1756 KiB page faults so far = 0
malloc(1024 MiB) returned 0x7fadb67ff010
after malloc VmRSS = 1952 KiB page faults so far = 8
after touching half of the pages VmRSS = 526300 KiB page faults so far = 131081
pages touched: 131072 (4096 bytes each) page faults: 131073 1968 ns per fault
after free VmRSS = 2012 KiB page faults so far = 131081
malloc(1 GiB) no cambia la memoria residente: solo reserva direcciones. Cada página tocada por primera vez produce un fallo de página en el que el núcleo asigna y pone en cero un marco: tantos fallos como páginas.malloc(1 GiB) does not change resident memory: it only reserves addresses. Every page touched for the first time causes a page fault in which the kernel allocates and zeroes a frame: as many faults as pages.malloc(1 GiB) ne change pas la mémoire résidente : il ne fait que réserver des adresses. Chaque page touchée pour la première fois provoque un défaut de page où le noyau alloue et met à zéro un cadre : autant de défauts que de pages.malloc(1 GiB) no canvia la memòria resident: només reserva adreces. Cada pàgina tocada per primera vegada provoca una fallada de pàgina en què el nucli assigna i posa a zero un marc: tantes fallades com pàgines.malloc(1 GiB) は常駐メモリを変えません:アドレスを予約するだけです。初めて触れたページごとにページフォールトが起き、カーネルがフレームを割り当ててゼロで埋めます:フォールトの数はページ数と同じです。
/*
* TD3 - exploration 3: copy-on-write after fork (original slide 74).
* The parent fills a block, so all its pages are resident. After fork, parent and child share the
* physical pages, marked read-only. The child reads the whole block (no copy: no page faults) and then
* writes to a quarter of the pages: each first write causes a page fault in which the kernel copies
* that single page. The page faults are counted with getrusage.
* ./cow [MiB] default: 256 MiB
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/resource.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>
#include "utils.h"
static long minor_faults(void)
{
struct rusage ru;
exit_if(getrusage(RUSAGE_SELF, &ru) == -1, "getrusage");
return ru.ru_minflt;
}
static double now(void)
{
struct timespec ts;
exit_if(clock_gettime(CLOCK_MONOTONIC, &ts) == -1, "clock_gettime");
return ts.tv_sec + ts.tv_nsec / 1e9;
}
int main(int argc, char *argv[])
{
size_t mib = argc > 1 ? (size_t)atol(argv[1]) : 256;
size_t size = mib << 20, page = (size_t)sysconf(_SC_PAGESIZE), pages = size / page;
char *p = malloc(size);
exit_if(p == NULL, "malloc");
memset(p, 'x', size); /* every page is now resident */
printf("parent: %zu MiB filled (%zu pages of %zu bytes)\n", mib, pages, page);
fflush(stdout); /* do not duplicate the stdio buffer */
double t0 = now();
pid_t pid = fork();
exit_if(pid == -1, "fork");
if (pid == 0) {
printf("fork took %.2f ms (only the page tables are copied)\n", (now() - t0) * 1e3);
long f = minor_faults();
unsigned long sum = 0;
for (size_t i = 0; i < size; i += page)
sum += (unsigned char)p[i];
printf("child reads %zu pages: %ld page faults (shared, nothing copied; sum %lu)\n", pages, minor_faults() - f, sum);
f = minor_faults();
double t1 = now();
for (size_t i = 0; i < size / 4; i += page)
p[i] = 'y';
double t2 = now();
long faults = minor_faults() - f;
printf("child writes %zu pages: %ld page faults (one copy per page), %.0f ns per copy\n",
pages / 4, faults, faults ? (t2 - t1) * 1e9 / faults : 0.0);
free(p);
exit(EXIT_SUCCESS);
}
int status;
exit_if(waitpid(pid, &status, 0) == -1, "waitpid");
printf("parent: the child exited with status %d; my copy still says '%c'\n", WEXITSTATUS(status), p[0]);
free(p);
return EXIT_SUCCESS;
}
$ ./cow 256
parent: 256 MiB filled (65536 pages of 4096 bytes)
fork took 6.70 ms (only the page tables are copied)
child reads 65536 pages: 0 page faults (shared, nothing copied; sum 7864320)
child writes 16384 pages: 16384 page faults (one copy per page), 2753 ns per copy
parent: the child exited with status 0; my copy still says 'x'
Tras fork, leer no copia nada (0 fallos); escribir produce exactamente un fallo —una copia— por página escrita. Por eso fork de un proceso de 256 MiB tarda milisegundos: solo copia las tablas de páginas.After fork, reading copies nothing (0 faults); writing causes exactly one fault —one copy— per page written. That is why forking a 256 MiB process takes milliseconds: it only copies the page tables.Après fork, lire ne copie rien (0 défaut) ; écrire provoque exactement un défaut — une copie — par page écrite. C’est pourquoi un fork d’un processus de 256 Mio prend des millisecondes : il ne copie que les tables des pages.Després de fork, llegir no copia res (0 fallades); escriure provoca exactament una fallada —una còpia— per pàgina escrita. Per això fer fork d’un procés de 256 MiB triga mil·lisegons: només copia les taules de pàgines.fork の後、読むだけなら何もコピーされず(フォールト 0 回)、書くと書いたページごとにちょうど 1 回のフォールト(1 回のコピー)が起きます。だから 256 MiB のプロセスの fork がミリ秒で済むのです:コピーするのはページテーブルだけです。
/*
* TD3 - exploration 4: the end of a process: zombies and orphans (original slides 80-82 and 88).
* 1. A child that exits stays as a ZOMBIE (state Z, <defunct>) until its parent calls wait: the
* kernel keeps its PID and exit status. ps shows it before and after waitpid.
* 2. A process whose parent dies is an ORPHAN: the kernel gives it a new parent, init (PID 1) or the
* nearest ancestor marked as "subreaper" (on a desktop, usually systemd --user).
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>
#include "utils.h"
static void pause_ms(long ms)
{
struct timespec ts = { ms / 1000, (ms % 1000) * 1000000L };
while (nanosleep(&ts, &ts) == -1)
;
}
static void ps(pid_t pid)
{
char cmd[128];
snprintf(cmd, sizeof cmd, "ps -o pid,ppid,stat,comm -p %d | sed 's/^/ /'", (int)pid);
fflush(stdout);
if (system(cmd) == -1)
perror("system");
}
/* Name of a process, from /proc/PID/comm. */
static void comm_of(pid_t pid, char *name, size_t len)
{
char path[64];
snprintf(path, sizeof path, "/proc/%d/comm", (int)pid);
FILE *f = fopen(path, "r");
if (f == NULL || fgets(name, (int)len, f) == NULL)
snprintf(name, len, "?");
name[strcspn(name, "\n")] = '\0';
if (f != NULL)
fclose(f);
}
int main(void)
{
/* 1. Zombie */
pid_t child = fork();
exit_if(child == -1, "fork");
if (child == 0)
_exit(7);
pause_ms(200);
printf("1) the child %d has exited, but nobody has called wait yet:\n", (int)child);
ps(child);
int status;
exit_if(waitpid(child, &status, 0) == -1, "waitpid");
printf(" waitpid -> WIFEXITED = %d, WEXITSTATUS = %d; now ps finds nothing:\n", WIFEXITED(status), WEXITSTATUS(status));
ps(child);
/* 2. Orphan: the middle process exits, the grandchild is adopted */
printf("2) orphan: my PID is %d\n", (int)getpid());
fflush(stdout);
pid_t middle = fork();
exit_if(middle == -1, "fork");
if (middle == 0) {
pid_t grandchild = fork();
exit_if(grandchild == -1, "fork");
if (grandchild == 0) {
pid_t before = getppid();
pause_ms(200); /* meanwhile its parent exits */
pid_t after = getppid();
char name[64];
comm_of(after, name, sizeof name);
printf(" grandchild %d: parent was %d, now it is %d (%s)\n", (int)getpid(), (int)before, (int)after, name);
exit(EXIT_SUCCESS);
}
exit(EXIT_SUCCESS); /* the grandchild becomes an orphan */
}
exit_if(waitpid(middle, NULL, 0) == -1, "waitpid");
pause_ms(400); /* let the grandchild report */
return EXIT_SUCCESS;
}
$ ./lifecycle
1) the child 356667 has exited, but nobody has called wait yet:
PID PPID STAT COMMAND
356667 356666 Z lifecycle
waitpid -> WIFEXITED = 1, WEXITSTATUS = 7; now ps finds nothing:
PID PPID STAT COMMAND
2) orphan: my PID is 356666
grandchild 356679: parent was 9771, now it is 9771 (systemd)
El hijo terminado aparece con estado Z hasta el waitpid, que recupera su código (7). El nieto huérfano no pasa a init sino a systemd de la sesión del usuario, un subreaper (prctl(PR_SET_CHILD_SUBREAPER)).The finished child shows state Z until waitpid, which collects its code (7). The orphaned grandchild does not go to init but to the user session's systemd, a subreaper (prctl(PR_SET_CHILD_SUBREAPER)).L’enfant terminé affiche l’état Z jusqu’au waitpid, qui récupère son code (7). Le petit-enfant orphelin ne va pas à init mais au systemd de la session utilisateur, un subreaper (prctl(PR_SET_CHILD_SUBREAPER)).El fill acabat mostra l’estat Z fins al waitpid, que en recull el codi (7). El nét orfe no va a init sinó al systemd de la sessió d’usuari, un subreaper (prctl(PR_SET_CHILD_SUBREAPER)).終了した子は waitpid がコード(7)を回収するまで状態 Z です。孤児になった孫は init ではなく、ユーザーセッションの systemd(subreaper、prctl(PR_SET_CHILD_SUBREAPER))に引き取られます。
Míralo animado → A4.1 memoria virtual · A4.2 copia en escritura · A4.3 estadosSee it animated → A4.1 virtual memory · A4.2 copy-on-write · A4.3 statesLe voir animé → A4.1 mémoire virtuelle · A4.2 copie sur écriture · A4.3 étatsMira-ho animat → A4.1 memòria virtual · A4.2 còpia en escriptura · A4.3 estatsアニメーションで見る → A4.1 仮想メモリ · A4.2 コピーオンライト · A4.3 状態