Kernel Lab 2: Observing the Page Cache
Kernel Lab 2: Observing the Page Cache The Program The below program opens a file via mmap and performs read/write operations via commands. It also shows page faults, resident pages via mincore, etc. // page_cache_lab.c #define _GNU_SOURCE #include <errno.h> #include <fcntl.h> #include <inttypes.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/mman.h> #include <sys/stat.h> #include <unistd.h> #include <sys/resource.h> static void print_help(void) { printf("\nCommands:\n"); printf(" help Show commands\n"); printf(" info Mapping information\n"); printf(" read <page> Read first byte of page\n"); printf(" write <page> <char> Fill entire page with character\n"); printf(" dump <page> Dump first 64 bytes\n"); printf(" msync Flush dirty pages\n"); printf(" dontneed madvise(MADV_DONTNEED)\n"); printf(" pause Wait for ENTER\n"); printf(" mincore Show all pages compact (R=resident .=not)\n"); printf(" mincore <page> Check if specific page is resident\n"); printf(" quit\n\n"); } // compact: prints [R....R..R] style, 64 pages per line static void print_mincore_compact(char *region, size_t filesize, long pagesize) { size_t npages = (filesize + pagesize - 1) / pagesize; unsigned char *vec = calloc(npages, 1); if (!vec) { perror("calloc"); return; } if (mincore(region, filesize, vec) != 0) { perror("mincore"); free(vec); return; } printf("\nPage residency (%zu pages, R=resident .=not):\n\n", npages); for (size_t i = 0; i < npages; i++) { if (i % 64 == 0) printf("%5zu: ", i); putchar((vec[i] & 1) ? 'R' : '.'); if ((i + 1) % 64 == 0 || i == npages - 1) putchar('\n'); } // print resident page numbers explicitly printf("\nResident pages: "); int any = 0; for (size_t i = 0; i < npages; i++) { if (vec[i] & 1) { printf("R(%zu) ", i); any = 1; } } if (!any) printf("none"); printf("\n\n"); free(vec); } // single page check static void print_mincore_page(char *region, size_t filesize, long pagesize, size_t page) { size_t npages = (filesize + pagesize - 1) / pagesize; if (page >= npages) { printf("Page %zu out of range (max %zu)\n", page, npages - 1); return; } unsigned char *vec = calloc(npages, 1); if (!vec) { perror("calloc"); return; } if (mincore(region, filesize, vec) != 0) { perror("mincore"); free(vec); return; } printf("Page %zu: %s\n", page, (vec[page] & 1) ? "resident" : "not resident"); free(vec); } typedef struct { unsigned long minflt; unsigned long majflt; char vmrss[64]; char rssanon[64]; char rssfile[64]; char rssshmem[64]; } ProcStats; static void get_proc_stats(ProcStats *s) { memset(s, 0, sizeof(*s)); /* ---------- /proc/self/stat ---------- */ FILE *fp = fopen("/proc/self/stat", "r"); if (fp) { char buf[4096]; if (fgets(buf, sizeof(buf), fp)) { /* Skip "pid (comm)" because comm may contain spaces */ char *p = strrchr(buf, ')'); if (p) { unsigned long values[64] = {0}; int n = 0; char *tok = strtok(p + 2, " "); while (tok && n < 64) { values[n++] = strtoul(tok, NULL, 10); tok = strtok(NULL, " "); } /* * After ')' the fields begin with: * * 0 state * 1 ppid * ... * 7 flags * 8 minflt * 10 majflt */ if (n > 10) { s->minflt = values[7]; s->majflt = values[9]; } } } fclose(fp); } /* ---------- /proc/self/status ---------- */ fp = fopen("/proc/self/status", "r"); if (fp) { char line[256]; while (fgets(line, sizeof(line), fp)) { sscanf(line, "VmRSS: %63[^\n]", s->vmrss); sscanf(line, "RssAnon: %63[^\n]", s->rssanon); sscanf(line, "RssFile: %63[^\n]", s->rssfile); sscanf(line, "RssShmem: %63[^\n]", s->rssshmem); } fclose(fp); } } static void print_stats(void) { static unsigned long last_min = 0; static unsigned long last_maj = 0; ProcStats s; get_proc_stats(&s); printf("\n---------------------------------------\n"); printf("Minor Faults : %-8lu (%+ld)\n", s.minflt, (long)s.minflt - (long)last_min); printf("Major Faults : %-8lu (%+ld)\n", s.majflt, (long)s.majflt - (long)last_maj); printf("---------------------------------------\n\n"); last_min = s.minflt; last_maj = s.majflt; } struct rusage ru; static long last_minflt = 0; static long last_majflt = 0; int main(int argc, char **argv) { if (argc != 2) { fprintf(stderr, "Usage: %s <file>\n", argv[0]); return 1; } const char *filename = argv[1]; int fd = open(filename, O_RDWR); if (fd < 0) { perror("open"); return 1; } struct stat st; if (fstat(fd, &st) != 0) { perror("fstat"); return 1; } size_t filesize = st.st_size; long pagesize = sysconf(_SC_PAGESIZE); if (filesize == 0) { fprintf(stderr, "File is empty.\n"); return 1; } char *region = mmap(NULL, filesize, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (region == MAP_FAILED) { perror("mmap"); return 1; } printf("\n=====================================\n"); printf("Page Cache Lab\n"); printf("=====================================\n"); printf("PID : %d\n", getpid()); printf("File : %s\n", filename); printf("Size : %zu bytes\n", filesize); printf("Page Size : %ld\n", pagesize); printf("Pages : %zu\n", filesize / pagesize); printf("Mapping : %p\n", region); print_help(); char line[256]; while (1) { printf("pagecache> "); fflush(stdout); if (!fgets(line, sizeof(line), stdin)) break; if (strncmp(line, "help", 4) == 0) { print_help(); } else if (strncmp(line, "info", 4) == 0) { printf("\n"); printf("PID : %d\n", getpid()); printf("Mapping : %p\n", region); printf("Size : %zu bytes\n", filesize); printf("Pages : %zu\n", filesize / pagesize); printf("\n"); } else if (strncmp(line, "read", 4) == 0) { size_t page; if (sscanf(line, "read %zu", &page) != 1) { printf("Usage: read <page>\n"); continue; } size_t offset = page * pagesize; if (offset >= filesize) { printf("Out of range\n"); continue; } volatile char c = region[offset]; printf("Read page %zu : '%c' (0x%02x)\n", page, (c >= 32 && c <= 126) ? c : '.', (unsigned char)c); } else if (strncmp(line, "write", 5) == 0) { size_t page; char ch; if (sscanf(line, "write %zu %c", &page, &ch) != 2) { printf("Usage: write <page> <char>\n"); continue; } size_t offset = page * pagesize; if (offset >= filesize) { printf("Out of range\n"); continue; } size_t remaining = filesize - offset; size_t len = remaining < (size_t)pagesize ? remaining : (size_t)pagesize; memset(region + offset, ch, len); printf("Filled page %zu with '%c'\n", page, ch); } else if (strncmp(line, "dump", 4) == 0) { size_t page; if (sscanf(line, "dump %zu", &page) != 1) { printf("Usage: dump <page>\n"); continue; } size_t offset = page * pagesize; if (offset >= filesize) { printf("Out of range\n"); continue; } printf("\n"); for (int i = 0; i < 64; i++) { if (offset + i >= filesize) break; unsigned char c = region[offset + i]; if (c >= 32 && c <= 126) putchar(c); else putchar('.'); } printf("\n\n"); } else if (strncmp(line, "msync", 5) == 0) { if (msync(region, filesize, MS_SYNC) != 0) perror("msync"); else printf("Pages flushed.\n"); } else if (strncmp(line, "dontneed", 8) == 0) { if (madvise(region, filesize, MADV_DONTNEED) != 0) perror("madvise"); else printf("MADV_DONTNEED completed.\n"); } else if (strncmp(line, "stats", 5) == 0) { print_stats(); continue; } else if (strncmp(line, "mincore", 7) == 0) { size_t page; if (sscanf(line, "mincore %zu", &page) == 1) { // single page query print_mincore_page(region, filesize, pagesize, page); } else { // compact full view print_mincore_compact(region, filesize, pagesize); } continue; } else if (strncmp(line, "pause", 5) == 0) { printf("Press ENTER..."); getchar(); } else if (strncmp(line, "quit", 4) == 0) { break; } else { printf("Unknown command.\n"); } print_stats(); } munmap(region, filesize); close(fd); return 0; } For the setup, i had to make some changes for this to work. ...