5.7 - מיפוי זכרון - פתרון
פתרונות - מיפוי זכרון - mmap¶
פתרון 1 - allocation זכרון עם mmap¶
#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
int main() {
// allocate 4096 bytes with mmap - private anonymous mapping
void *ptr = mmap(NULL, 4096, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (ptr == MAP_FAILED) {
perror("mmap");
return 1;
}
// write a string to memory
char *msg = (char *)ptr;
strcpy(msg, "hello from mmap!");
// read and print
printf("read from mmap: %s\n", msg);
// free the memory
if (munmap(ptr, 4096) < 0) {
perror("munmap");
return 1;
}
return 0;
}
פתרון 2 - קריאת קובץ עם mmap¶
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>
int main(int argc, char *argv[]) {
if (argc != 2) {
fprintf(stderr, "usage: %s <filename>\n", argv[0]);
return 1;
}
// open the file for reading
int fd = open(argv[1], O_RDONLY);
if (fd < 0) {
perror("open");
return 1;
}
// get the file size
struct stat sb;
if (fstat(fd, &sb) < 0) {
perror("fstat");
close(fd);
return 1;
}
// map the file into memory
char *mapped = mmap(NULL, sb.st_size, PROT_READ,
MAP_PRIVATE, fd, 0);
if (mapped == MAP_FAILED) {
perror("mmap");
close(fd);
return 1;
}
close(fd); // the fd can be closed now - the mapping remains
// print the content byte by byte
for (size_t i = 0; i < (size_t)sb.st_size; i++) {
putchar(mapped[i]);
}
// free the mapping
munmap(mapped, sb.st_size);
return 0;
}
פתרון 3 - זכרון משותף בין processes¶
#include <stdio.h>
#include <sys/mman.h>
#include <sys/wait.h>
#include <unistd.h>
int main() {
// create a shared memory region
int *shared = mmap(NULL, sizeof(int), PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_ANONYMOUS, -1, 0);
if (shared == MAP_FAILED) {
perror("mmap");
return 1;
}
*shared = 0;
pid_t pid = fork();
if (pid == 0) {
// child process - read the value
printf("child reads: %d\n", *shared);
_exit(0);
} else if (pid > 0) {
// parent process - write the value
*shared = 1234;
printf("parent wrote: 1234\n");
// wait for the child
wait(NULL);
} else {
perror("fork");
return 1;
}
munmap(shared, sizeof(int));
return 0;
}
הערה: בדוגמה הזו process האב כותב לזכרון לפני שprocess הבן קורא. בתרחיש אמיתי ייתכן שהבן ירוץ לפני שהאב יספיק לכתוב. כדי לפתור את זה צריך סנכרון (נלמד על זה בפרק 5.8 על threads). כאן זה עובד כי fork יוצר process חדש וזה לוקח זמן, כך שבדרך כלל האב מספיק לכתוב לפני שהבן מתחיל לרוץ.
פתרון 4 - שינוי קובץ דרך mmap¶
#include <stdio.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>
int main() {
// open the file for reading and writing
int fd = open("testfile.txt", O_RDWR);
if (fd < 0) {
perror("open");
return 1;
}
// get the file size
struct stat sb;
if (fstat(fd, &sb) < 0) {
perror("fstat");
close(fd);
return 1;
}
// map the file with MAP_SHARED - changes will affect the file on disk
char *mapped = mmap(NULL, sb.st_size, PROT_READ | PROT_WRITE,
MAP_SHARED, fd, 0);
if (mapped == MAP_FAILED) {
perror("mmap");
close(fd);
return 1;
}
close(fd);
// change the first byte
printf("before: first byte = '%c'\n", mapped[0]);
mapped[0] = 'X';
printf("after: first byte = '%c'\n", mapped[0]);
// free the mapping - the changes are written to the file
munmap(mapped, sb.st_size);
printf("file modified! check with: cat testfile.txt\n");
return 0;
}
כדי לבדוק, צרו קובץ testfile.txt עם תוכן כלשהו ואז הריצו את התוכנית:
תראו שהתו הראשון השתנה ל-X, כך שהתוכן הוא Xello World.
פתרון 5 - שאלה תיאורטית¶
הקרנל משתמש ב-mmap לטעינת קבצי ELF מכמה סיבות:
-
ביצועים עם demand paging - כשממפים קובץ עם mmap, הpages לא נטענים מיד לזכרון. הקרנל יוצר את ערכי ה-page table, אבל הpages עצמם נטענים רק כשהprocess ניגש אליהם (page fault). בקובץ הרצה גדול, ייתכן שחלקים ממנו לעולם לא ירוצו (למשל קוד טיפול בשגיאות נדירות), אז אין סיבה לטעון אותם. עם
read()היינו חייבים להעתיק את הכל מראש. -
שיתוף pages בין processes - אם 100 processes מריצים את אותה תוכנית, הpages של קטע הקוד (שהוא read-only) קיימים פעם אחת בRAM ומשותפים בין כל הprocesses. עם
read()כל process היה מקבל עותק פרטי, מה שבזבוז של זכרון. -
אין צורך בהעתקה כפולה - עם
read(), הקרנל קורא מהדיסק ל-page cache, ואז מעתיק מה-page cache לbuffer של הprocess. עם mmap, הקרנל פשוט ממפה את pages ה-page cache ישירות למרחב הכתובות של הprocess - חיסכון של העתקה שלמה. -
ניהול זכרון יעיל - כשהמערכת צריכה זכרון, הקרנל יכול לזרוק pages של קבצים ממופים (כי הם קיימים על הדיסק ואפשר לטעון אותם מחדש). עם
read()הזכרון הוא "אנונימי" ואי אפשר לזרוק אותו - צריך לרשום אותו ל-swap.