10-bo‘lim
Virtual xotira va sahifalash
Virtual va fizik manzil, sahifa jadvali, MMU va TLB, sahifa xatosi, dangasa ajratish va nusxalash-yozishda.
Ushbu bo‘lim mundarijasi
Oldingi bo'limda ko'rgan uch muammoni bitta g'oya hal qiladi: har bir jarayonga o'zining manzil fazosini berish.
Virtual manzildan fizikka #
Sahifa hajmi #
#include <stdio.h>
#include <unistd.h>
int main(void) {
long sahifa = sysconf(_SC_PAGESIZE);
printf("Sahifa hajmi : %ld bayt\n", sahifa);
printf("Kilobaytda : %ld KB\n", sahifa / 1024);
printf("4096 baytmi? : %s\n", sahifa == 4096 ? "ha" : "yo'q");
/* Sahifa hajmi doim ikkining darajasi */
printf("Ikkining darajasimi?: %s\n",
(sahifa & (sahifa - 1)) == 0 ? "ha" : "yo'q");
/* Manzilning ichki siljishini hisoblash */
long manzil = 0x12345;
printf("\n0x12345 manzili uchun:\n");
printf(" Sahifa raqami : %ld\n", manzil / sahifa);
printf(" Ichki siljish : %ld\n", manzil % sahifa);
return 0;
}
Sahifa hajmi : 4096 bayt
Kilobaytda : 4 KB
4096 baytmi? : ha
Ikkining darajasimi?: ha
0x12345 manzili uchun:
Sahifa raqami : 18
Ichki siljish : 837
Chunki u holda bo'lish va qoldiq bitli amallarga aylanadi:
sahifa_raqami = manzil >> 12; /* 4096 = 2^12 ga bo'lish */
siljish = manzil & 0xFFF; /* 4096 ga qoldiq */
Bu apparatda bir taktda bajariladi. Agar sahifa 5000 bayt bo'lsa, har bir xotira murojaatida haqiqiy bo'lish kerak bo'lardi - bu tizimni o'nlab marta sekinlashtirardi.
64 bitli manzilda 4 KB sahifa uchun:
- Quyi 12 bit - sahifa ichidagi siljish;
- Qolgan bitlar - sahifa raqami.
Ko'p darajali sahifa jadvali #
64 bitli tizimda 4 KB sahifalar bilan bitta tekis jadval kerak bo'lardi:
| Ko'rsatkich | Qiymat |
|---|---|
| Sahifalar soni | 2⁵² ta |
| Har bir yozuv | 8 bayt |
| Jami hajm | 32 petabayt |
Bu bitta jarayon uchun! Ko'p darajali jadvalda esa jarayon faqat haqiqatan ishlatgan sohalar uchun jadval sahifalarini oladi - odatiy dastur uchun bu bir necha o'nlab kilobayt.
Bu ma'lumotlar tuzilmalaridagi klassik siyrak (sparse) massiv
g'oyasi: bo'sh sohalarda yuqori darajadagi yozuv shunchaki NULL
bo'ladi.
TLB - tarjima keshi #
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define SAHIFA 4096
#define SAHIFALAR 4096 /* 16 MB */
#define TAKROR 200
static double olchash(char *xotira, long qadam) {
struct timespec b, o;
volatile long yigindi = 0;
clock_gettime(CLOCK_MONOTONIC, &b);
for (int t = 0; t < TAKROR; t++) {
for (long i = 0; i < (long) SAHIFALAR * SAHIFA; i += qadam) {
yigindi += xotira[i];
}
}
clock_gettime(CLOCK_MONOTONIC, &o);
return (o.tv_sec - b.tv_sec) + (o.tv_nsec - b.tv_nsec) / 1e9;
}
int main(void) {
size_t hajm = (size_t) SAHIFALAR * SAHIFA;
char *xotira = malloc(hajm);
if (xotira == NULL) {
return 1;
}
for (size_t i = 0; i < hajm; i++) {
xotira[i] = 1;
}
/* Ketma-ket: bitta sahifadan 64 marta o'qiladi */
double ketma_ket = olchash(xotira, 64);
/* Sakrab: har safar yangi sahifa - TLB doim yangilanadi */
double sakrab = olchash(xotira, SAHIFA);
/* Sakrab variantda 64 barobar kam o'qish bor,
shunga qaramay u kutilganidek arzon emas */
double nisbat = ketma_ket / sakrab;
printf("Ketma-ket o'qish 64 barobar ko'p bayt o'qidi\n");
printf("Lekin vaqti 64 barobar ko'p emas: %s\n",
nisbat < 40.0 ? "ha" : "yo'q");
printf("Sabab - sahifa bo'ylab sakrash TLB ni buzadi\n");
free(xotira);
return 0;
}
Ketma-ket o'qish 64 barobar ko'p bayt o'qidi
Lekin vaqti 64 barobar ko'p emas: ha
Sabab - sahifa bo'ylab sakrash TLB ni buzadi
TLB (Translation Lookaside Buffer) - MMU ichidagi kichik kesh. Unda oxirgi ishlatilgan virtual → fizik tarjimalar saqlanadi.
| Holat | Narxi |
|---|---|
| TLB hit | ~1 takt |
| TLB miss (jadval keshda) | ~10-30 takt |
| TLB miss + sahifa xatosi | ~100 000+ takt |
Zamonaviy protsessorlarda TLB 1500-3000 ta yozuvni saqlaydi. 4 KB sahifalar bilan bu ~10 MB ni qamraydi.
Katta ma'lumotlar bilan ishlaydigan dasturlar uchun katta sahifalar (huge pages, 2 MB) yoqiladi - o'shanda bir xil TLB ~5 GB ni qamraydi.
Dangasa ajratish (lazy allocation) #
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static long rss_kb(void) {
FILE *f = fopen("/proc/self/status", "r");
char qator[256];
long qiymat = -1;
if (f == NULL) {
return -1;
}
while (fgets(qator, sizeof qator, f) != NULL) {
if (strncmp(qator, "VmRSS:", 6) == 0) {
sscanf(qator + 6, "%ld", &qiymat);
break;
}
}
fclose(f);
return qiymat;
}
int main(void) {
long oldin = rss_kb();
size_t hajm = 512L * 1024 * 1024; /* 512 MB */
char *katta = malloc(hajm);
if (katta == NULL) {
return 1;
}
long sorovdan_keyin = rss_kb();
/* Endi haqiqatan tegib chiqamiz */
for (size_t i = 0; i < hajm; i += 4096) {
katta[i] = 1;
}
long tegishdan_keyin = rss_kb();
printf("512 MB so'raldi, lekin hali tegilmadi:\n");
printf(" RSS o'sishi 10 MB dan kammi? %s\n",
sorovdan_keyin - oldin < 10 * 1024 ? "ha" : "yo'q");
printf("Har bir sahifaga tegildi:\n");
printf(" RSS o'sishi 400 MB dan ko'pmi? %s\n",
tegishdan_keyin - sorovdan_keyin > 400 * 1024 ? "ha" : "yo'q");
free(katta);
return 0;
}
512 MB so'raldi, lekin hali tegilmadi:
RSS o'sishi 10 MB dan kammi? ha
Har bir sahifaga tegildi:
RSS o'sishi 400 MB dan ko'pmi? ha
malloc(512 MB) bajarilganda yadro faqat manzil fazosida joy
belgilaydi - RAM ajratmaydi.
Fizik ramka faqat siz o'sha sahifaga birinchi marta tekkaningizda ajratiladi. Bu jarayon:
- Siz bog'lanmagan sahifaga murojaat qilasiz;
- MMU sahifa xatosini (page fault) qo'zg'atadi;
- Yadro bo'sh ramka topib, sahifa jadvaliga yozadi;
- Buyruq qaytadan bajariladi - endi u ishlaydi.
Bu "yumshoq" sahifa xatosi (minor fault) - disk ishtirok etmaydi.
Sahifa xatolarini sanash #
#include <stdio.h>
#include <stdlib.h>
#include <sys/resource.h>
int main(void) {
struct rusage r;
getrusage(RUSAGE_SELF, &r);
long yumshoq_oldin = r.ru_minflt;
long qattiq_oldin = r.ru_majflt;
size_t hajm = 64L * 1024 * 1024; /* 64 MB */
char *xotira = malloc(hajm);
if (xotira == NULL) {
return 1;
}
for (size_t i = 0; i < hajm; i += 4096) {
xotira[i] = 1;
}
getrusage(RUSAGE_SELF, &r);
long yumshoq_keyin = r.ru_minflt;
long qattiq_keyin = r.ru_majflt;
long farq = yumshoq_keyin - yumshoq_oldin;
long kutilgan = (long) (hajm / 4096);
printf("64 MB ga tegildi = %ld ta sahifa\n", kutilgan);
printf("Yumshoq sahifa xatolari kamida shuncha bo'ldimi? %s\n",
farq >= kutilgan / 2 ? "ha" : "yo'q");
printf("Bu yerda qattiq (diskli) xato bo'ldimi? %s\n",
qattiq_keyin - qattiq_oldin > 0 ? "ha" : "yo'q");
free(xotira);
return 0;
}
64 MB ga tegildi = 16384 ta sahifa
Yumshoq sahifa xatolari kamida shuncha bo'ldimi? ha
Bu yerda qattiq (diskli) xato bo'ldimi? yo'q
| Xato turi | Nima bo'ladi | Narxi |
|---|---|---|
| Yumshoq (minor) | RAM dagi ramka bog'lanadi | ~1 mikrosekund |
| Qattiq (major) | Diskdan o'qish kerak | ~100 mikrosekund - 10 ms |
| Xato (invalid) | SIGSEGV yuboriladi | Dastur qulaydi |
/usr/bin/time -v ./dastur 2>&1 | grep -i faults
Major (requiring I/O) page faults: 0
Minor (reclaiming a frame) page faults: 16891
Nusxalash-yozishda (copy-on-write) #
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
int main(void) {
size_t hajm = 64L * 1024 * 1024; /* 64 MB */
char *xotira = malloc(hajm);
if (xotira == NULL) {
return 1;
}
memset(xotira, 'A', hajm); /* haqiqatan RAM da */
fflush(stdout); /* fork dan oldin shart! */
pid_t bola = fork();
if (bola < 0) {
return 1;
}
if (bola == 0) {
/* Bola faqat o'qiydi - nusxa olinmaydi */
printf("Bola: birinchi belgi = %c\n", xotira[0]);
/* Endi yozamiz - shu sahifaning nusxasi olinadi */
xotira[0] = 'B';
printf("Bola: o'zgartirdi -> %c\n", xotira[0]);
fflush(stdout); /* _exit bufer tozalamaydi */
_exit(0);
}
wait(NULL);
printf("Ota: qiymat o'zgardimi? %s\n", xotira[0] == 'A' ? "yo'q" : "ha");
printf("Ota: birinchi belgi = %c\n", xotira[0]);
free(xotira);
return 0;
}
Bola: birinchi belgi = A
Bola: o'zgartirdi -> B
Ota: qiymat o'zgardimi? yo'q
Ota: birinchi belgi = A
fork dan oldin fflush(stdout)Bu misolda fflush(stdout) bo'lmasa, fork dan oldingi chiqish
buferi bolaga ham nusxalanadi va ikki marta chop etiladi.
Chiqish terminalga ketganda bufer qatorlab tozalanadi va muammo ko'rinmaydi. Faylga yoki quvurga yozganda esa u to'liq bufer bo'ladi va xato yuzaga chiqadi:
./dastur > natija.txt
Shuning uchun har doim fork dan oldin fflush(stdout) yozing.
Bo'lishilgan xotira (shared mapping) #
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/wait.h>
int main(void) {
/* MAP_SHARED - fork dan keyin ham umumiy qoladi */
int *umumiy = mmap(NULL, 4096,
PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_ANONYMOUS, -1, 0);
/* MAP_PRIVATE - copy-on-write bo'ladi */
int *shaxsiy = mmap(NULL, 4096,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (umumiy == MAP_FAILED || shaxsiy == MAP_FAILED) {
return 1;
}
umumiy[0] = 100;
shaxsiy[0] = 100;
fflush(stdout);
pid_t bola = fork();
if (bola < 0) {
return 1;
}
if (bola == 0) {
umumiy[0] = 999;
shaxsiy[0] = 999;
_exit(0);
}
wait(NULL);
printf("MAP_SHARED qiymati : %d\n", umumiy[0]);
printf("MAP_PRIVATE qiymati : %d\n", shaxsiy[0]);
printf("Bola o'zgarishi ko'rindimi? shared=%s private=%s\n",
umumiy[0] == 999 ? "ha" : "yo'q",
shaxsiy[0] == 999 ? "ha" : "yo'q");
munmap(umumiy, 4096);
munmap(shaxsiy, 4096);
return 0;
}
MAP_SHARED qiymati : 999
MAP_PRIVATE qiymati : 100
Bola o'zgarishi ko'rindimi? shared=ha private=yo'q
Sahifa himoyasi #
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <setjmp.h>
#include <sys/mman.h>
#include <unistd.h>
static sigjmp_buf sakrash;
static void segv_ishlovchi(int signal) {
(void) signal;
siglongjmp(sakrash, 1);
}
int main(void) {
long sahifa = sysconf(_SC_PAGESIZE);
char *xotira = mmap(NULL, (size_t) sahifa,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (xotira == MAP_FAILED) {
return 1;
}
xotira[0] = 'X';
printf("Yozish mumkin: %c\n", xotira[0]);
/* Endi faqat o'qishga o'tkazamiz */
mprotect(xotira, (size_t) sahifa, PROT_READ);
printf("O'qish hali ham mumkin: %c\n", xotira[0]);
signal(SIGSEGV, segv_ishlovchi);
if (sigsetjmp(sakrash, 1) == 0) {
xotira[0] = 'Y'; /* SIGSEGV qo'zg'atadi */
printf("Yozish o'tdi - kutilmagan\n");
} else {
printf("Yozishga urinish SIGSEGV berdi - himoya ishladi\n");
}
munmap(xotira, (size_t) sahifa);
return 0;
}
Yozish mumkin: X
O'qish hali ham mumkin: X
Yozishga urinish SIGSEGV berdi - himoya ishladi
mprotect ning kuchi va xavfimprotect bilan sahifa huquqlarini ish vaqtida o'zgartirish mumkin.
Bu quyidagi joylarda ishlatiladi:
| Qo'llanish | Qanday |
|---|---|
| Guard page | Stek oxiriga PROT_NONE sahifa qo'yiladi |
| JIT kompilyator | Kod yozilgach PROT_EXEC ga o'tkaziladi |
| Debug | Buzilayotgan xotirani PROT_READ qilib topish |
| Garbage collector | O'zgargan sahifalarni kuzatish |
Xavfsizlik qoidasi: sahifa hech qachon bir vaqtda yoziladigan
va bajariladigan (W^X buzilishi) bo'lmasligi kerak. Aks holda
hujumchi kod kiritib, uni ishga tushirishi mumkin.
sysconf(_SC_PAGESIZE)bilan sahifa hajmini aniqlang.- Bitli surish orqali manzildan sahifa raqamini chiqaring.
- Tekis sahifa jadvali 64 bitda qancha joy olishini hisoblang.
- Sahifa bo'ylab sakrab o'qish tezligini ketma-ket bilan solishtiring.
- 512 MB
mallocqilib, RSS o'zgarmasligini kuzating. - Har bir sahifaga tegib, RSS o'sishini o'lchang.
getrusagebilan yumshoq sahifa xatolarini sanang.forkdan keyin copy-on-write ishlashini isbotlang.MAP_SHAREDvaMAP_PRIVATEfarqini ko'rsating.mprotectbilan sahifani himoyalab,SIGSEGVni ushlang.
Xulosa #
- Har bir jarayon o'z virtual manzil fazosida yashaydi.
- MMU virtual manzilni sahifa jadvali orqali fizikka tarjima qiladi.
- Sahifa hajmi ikkining darajasi - tarjima bitli amallarga aylanadi.
- x86-64 da to'rt darajali jadval; bo'sh sohalar joy olmaydi.
- TLB tarjimalarni keshlaydi; usiz tizim bir necha barobar sekin.
- Dangasa ajratish:
mallocfaqat manzil ajratadi, RAM emas. - Yumshoq sahifa xatosi arzon, qattiq xato disk kutadi.
- Copy-on-write
forkni katta jarayonlarda ham tez qiladi. MAP_SHAREDbolaga ko'rinadi,MAP_PRIVATEyo'q.mprotectsahifa huquqlarini ish vaqtida o'zgartiradi.- Sahifa bir vaqtda yoziladigan va bajariladigan bo'lmasin.
Keyingi bo'limda sahifa almashtirish algoritmlarini ko'ramiz.
O‘qish tarixini saqlamoqchimisiz?
Tizimga kirsangiz, tugatgan bo‘limlaringiz saqlanadi va qoldirgan joyingizdan davom etasiz.
Xatolik topdingizmi?
Imlo xatosi, ishlamaydigan kod yoki noto‘g‘ri ma‘lumotni ko‘rsangiz - bizga xabar bering. Har bir xabar administrator tomonidan ko‘rib chiqiladi.