9-bo‘lim
Xotira boshqaruvi asoslari
Manzil fazosi, xotira ajratish usullari, fragmentatsiya, brk va mmap, malloc ichkarisi va xotira sizishini aniqlash.
Ushbu bo‘lim mundarijasi
- Muammo: bir necha jarayon, bitta RAM
- Manzil fazosi
- Uzluksiz ajratish va fragmentatsiya
- brk va mmap
- mmap bilan to'g'ridan-to'g'ri ishlash
- Faylni xotiraga bog'lash
- malloc ichkarisi
- Xotira sizishini aniqlash
- Jarayon xotirasini kuzatish
- Xotira ajratish strategiyalari
- Xotira tugaganda nima bo'ladi?
- Xulosa
Har bir jarayon o'zini butun xotira egasi deb hisoblaydi. Bu illyuziyani operatsion tizim yaratadi.
Muammo: bir necha jarayon, bitta RAM #
Manzil fazosi #
#include <stdio.h>
#include <stdlib.h>
int global_ozgaruvchi = 42;
int global_nol;
const char *matn = "doimiy satr";
int main(void) {
int stek_ozgaruvchisi = 7;
int *uyum = malloc(sizeof(int));
if (uyum == NULL) {
return 1;
}
/* Manzillarni bir-biri bilan solishtiramiz */
void *kod = (void *) main;
void *data = &global_ozgaruvchi;
void *bss = &global_nol;
void *stek = &stek_ozgaruvchisi;
printf("Kod eng pastdami? %s\n", kod < data ? "ha" : "yo'q");
printf("Data bss dan pastdami? %s\n", data < bss ? "ha" : "yo'q");
printf("Uyum bss dan yuqoridami? %s\n", (void *) uyum > bss ? "ha" : "yo'q");
printf("Stek uyumdan yuqoridami? %s\n", stek > (void *) uyum ? "ha" : "yo'q");
free(uyum);
return 0;
}
Kod eng pastdami? ha
Data bss dan pastdami? ha
Uyum bss dan yuqoridami? ha
Stek uyumdan yuqoridami? ha
cat /proc/self/maps
55e4a1b2f000-55e4a1b31000 r--p 00000000 08:02 1234567 /usr/bin/cat
55e4a1b31000-55e4a1b35000 r-xp 00002000 08:02 1234567 /usr/bin/cat
55e4a1b35000-55e4a1b37000 r--p 00006000 08:02 1234567 /usr/bin/cat
55e4a1b38000-55e4a1b39000 rw-p 00008000 08:02 1234567 /usr/bin/cat
55e4a2f1a000-55e4a2f3b000 rw-p 00000000 00:00 0 [heap]
7f2a3c000000-7f2a3c028000 r--p 00000000 08:02 2345678 /usr/lib/libc.so.6
7ffd8b2c1000-7ffd8b2e2000 rw-p 00000000 00:00 0 [stack]
7ffd8b3f5000-7ffd8b3f9000 r--p 00000000 00:00 0 [vvar]
Har bir qator - alohida xotira sohasi. Huquqlar:
r- o'qish,w- yozish,x- bajarish;p- shaxsiy (private),s- bo'lishilgan (shared).
E'tibor bering: kod sohasi r-xp - o'qish va bajarish mumkin,
lekin yozib bo'lmaydi. Bu himoya choralaridan biri.
Uzluksiz ajratish va fragmentatsiya #
| Strategiya | G'oya | Kamchiligi |
|---|---|---|
| Birinchi mos (first fit) | Birinchi yetarli bo'lakni ol | Bosh qismda mayda bo'laklar to'planadi |
| Eng yaxshi mos (best fit) | Eng kichik yetarli bo'lakni ol | Juda mayda qoldiqlar hosil qiladi |
| Eng yomon mos (worst fit) | Eng katta bo'lakni ol | Katta bo'laklarni tez tugatadi |
| Keyingi mos (next fit) | Oxirgi joydan davom et | Tez, lekin sifatsiz |
Tashqi fragmentatsiya - bo'sh joy bor, lekin u tarqoq (yuqoridagi rasm).
Ichki fragmentatsiya - ajratilgan bo'lak ichida ishlatilmagan joy. Masalan 100 baytga 4096 baytlik sahifa ajratilsa, 3996 bayt behuda ketadi.
Sahifalash tashqi fragmentatsiyani butunlay yo'q qiladi, lekin ichki fragmentatsiyani keltirib chiqaradi. Bu maqbul almashuv.
brk va mmap #
#include <stdio.h>
#include <stdlib.h>
int main(void) {
char *kichik = malloc(100);
char *ikkinchi = malloc(100);
char *katta = malloc(200 * 1024 * 1024); /* 200 MB */
if (kichik == NULL || ikkinchi == NULL || katta == NULL) {
return 1;
}
long uyum_ichida = ikkinchi - kichik;
long katta_farq = katta > kichik ? katta - kichik : kichik - katta;
printf("Ikki kichik bo'lak orasidagi masofa: %ld bayt\n", uyum_ichida);
printf("Ular bitta sohadami? %s\n",
uyum_ichida < 4096 ? "ha" : "yo'q");
printf("Katta bo'lak 1 GB dan uzoqdami? %s\n",
katta_farq > 1024L * 1024 * 1024 ? "ha" : "yo'q");
free(kichik);
free(ikkinchi);
free(katta);
return 0;
}
Ikki kichik bo'lak orasidagi masofa: 112 bayt
Ular bitta sohadami? ha
Katta bo'lak 1 GB dan uzoqdami? ha
Ikki kichik bo'lak yonma-yon - ikkalasi ham uyum ichida. Katta
bo'lak esa gigabaytlar naridagi butunlay boshqa sohada - u mmap
bilan olingan.
sbrk va brk ni ishlatmangUyum chegarasini sbrk(0) bilan bevosita o'qish mumkin edi, lekin bu
funksiyalar POSIX.1-2008 da standartdan chiqarilgan:
error: implicit declaration of function 'sbrk'
Sabab - ular malloc bilan ziddiyatga kiradi: ikkalasi ham bir xil
chegarani boshqaradi.
Yadro darajasida brk hali ham mavjud va malloc uni ishlatadi,
lekin sizning kodingiz to'g'ridan-to'g'ri mmap ishlatishi
kerak.
malloc ikki xil ishlaydi| So'rov hajmi | Nima ishlatiladi |
|---|---|
| Kichik (< 128 KB) | brk - uyum chegarasini suradi |
| Katta (≥ 128 KB) | mmap - alohida soha ajratadi |
Chegara mallopt(M_MMAP_THRESHOLD, ...) bilan o'zgartiriladi.
Nima uchun ikki xil? brk bilan olingan xotirani faqat oxiridan
qaytarish mumkin. Agar o'rtadagi bo'lak bo'shasa, u tizimga
qaytarilmaydi.
mmap bilan olingan katta bo'lakni esa free qilganda darhol
tizimga qaytariladi.
mmap bilan to'g'ridan-to'g'ri ishlash #
#include <stdio.h>
#include <sys/mman.h>
#include <string.h>
int main(void) {
size_t hajm = 4096;
int *xotira = mmap(NULL, hajm,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS,
-1, 0);
if (xotira == MAP_FAILED) {
printf("mmap muvaffaqiyatsiz\n");
return 1;
}
for (int i = 0; i < 10; i++) {
xotira[i] = i * i;
}
printf("mmap bilan xotira olindi\n");
printf("Boshlang'ich qiymat nolmi? %s\n", xotira[500] == 0 ? "ha" : "yo'q");
printf("Yozilgan qiymatlar: %d %d %d\n", xotira[2], xotira[3], xotira[4]);
munmap(xotira, hajm);
printf("Xotira qaytarildi\n");
return 0;
}
mmap bilan xotira olindi
Boshlang'ich qiymat nolmi? ha
Yozilgan qiymatlar: 4 9 16
Xotira qaytarildi
mmap nima uchun nol beradi?MAP_ANONYMOUS bilan olingan xotira doim nol bilan to'ldirilgan
bo'ladi.
Bu xavfsizlik talabi: agar yadro boshqa jarayondan qolgan sahifani tozalamasdan bersa, siz uning parollarini yoki kalitlarini o'qib olishingiz mumkin edi.
Shuning uchun calloc katta hajmlar uchun juda tez - u shunchaki
mmap chaqiradi va nollashni yadro allaqachon bajargan bo'ladi.
Faylni xotiraga bog'lash #
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/stat.h>
int main(void) {
const char *nom = "sinov_mmap.txt";
const char *matn = "Salom, xotiraga bog'langan fayl!";
/* fayl yaratamiz */
FILE *f = fopen(nom, "w");
if (f == NULL) {
return 1;
}
fputs(matn, f);
fclose(f);
/* faylni ochib, xotiraga bog'laymiz */
int fd = open(nom, O_RDONLY);
if (fd < 0) {
return 1;
}
struct stat holat;
if (fstat(fd, &holat) < 0) {
close(fd);
return 1;
}
char *xarita = mmap(NULL, (size_t) holat.st_size,
PROT_READ, MAP_PRIVATE, fd, 0);
close(fd);
if (xarita == MAP_FAILED) {
printf("mmap muvaffaqiyatsiz\n");
return 1;
}
printf("Fayl hajmi : %ld bayt\n", (long) holat.st_size);
printf("Birinchi belgi : %c\n", xarita[0]);
printf("Mazmuni to'g'rimi? %s\n",
strncmp(xarita, matn, (size_t) holat.st_size) == 0 ? "ha" : "yo'q");
munmap(xarita, (size_t) holat.st_size);
remove(nom);
return 0;
}
Fayl hajmi : 32 bayt
Birinchi belgi : S
Mazmuni to'g'rimi? ha
mmap bilan fayl o'qish nima uchun tez?Oddiy read da ma'lumot ikki marta ko'chiriladi:
- Diskdan yadro keshiga;
- Yadro keshidan sizning buferingizga.
mmap da esa sahifalar to'g'ridan-to'g'ri sizning manzil
fazongizga bog'lanadi - ikkinchi nusxalash yo'q.
Shuning uchun ma'lumotlar bazasi tizimlari (SQLite, MongoDB) katta
fayllar bilan mmap orqali ishlaydi.
Kamchiligi: kichik fayllar uchun mmap ning o'zi qimmat, va xato
yuz berganda SIGSEGV olasiz - read dagi kabi xato kodi emas.
malloc ichkarisi #
#include <stdio.h>
#include <stdlib.h>
int main(void) {
void *a = malloc(1);
void *b = malloc(1);
if (a == NULL || b == NULL) {
return 1;
}
long farq = (char *) b - (char *) a;
printf("Ikki marta malloc(1) chaqirildi\n");
printf("Manzillar orasidagi farq 1 baytdan kattami? %s\n",
farq > 1 || farq < -1 ? "ha" : "yo'q");
printf("Farq kamida 16 baytmi? %s\n",
farq >= 16 || farq <= -16 ? "ha" : "yo'q");
free(a);
free(b);
return 0;
}
Ikki marta malloc(1) chaqirildi
Manzillar orasidagi farq 1 baytdan kattami? ha
Farq kamida 16 baytmi? ha
int *massiv = malloc(10 * sizeof(int));
massiv[-1] = 0; /* SARLAVHANI BUZDI */
free(massiv); /* uyum buziladi, dastur qulaydi */
Chegaradan oldinga chiqish ham xavfli - u yerda malloc ning
xizmat ma'lumoti yotadi.
Natija odatda darhol emas, keyingi malloc yoki free da
ko'rinadi:
malloc(): invalid size (unsorted)
free(): invalid pointer
double free or corruption (out)
Bu xabarlarni ko'rsangiz - dasturingizda chegaradan chiqish bor. AddressSanitizer aniq joyini ko'rsatadi.
Xotira sizishini aniqlash #
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* Ataylab siziydigan funksiya */
static void sizadi(void) {
char *bufer = malloc(1024);
if (bufer != NULL) {
strcpy(bufer, "bu xotira hech qachon ozod qilinmaydi");
}
/* free(bufer) yo'q */
}
int main(void) {
printf("Xotira sizishini ko'rsatuvchi dastur\n");
for (int i = 0; i < 5; i++) {
sizadi();
}
printf("5 marta 1024 bayt sizdi = 5120 bayt\n");
printf("Buni sanitizer yoki valgrind aniqlaydi\n");
return 0;
}
Xotira sizishini ko'rsatuvchi dastur
5 marta 1024 bayt sizdi = 5120 bayt
Buni sanitizer yoki valgrind aniqlaydi
gcc -std=c17 -g -fsanitize=address sizish.c -o sizish
./sizish
=================================================================
ERROR: LeakSanitizer: detected memory leaks
Direct leak of 5120 byte(s) in 5 object(s) allocated from:
#0 0x... in malloc
#1 0x... in sizadi sizish.c:7
#2 0x... in main sizish.c:22
SUMMARY: AddressSanitizer: 5120 byte(s) leaked in 5 allocation(s).
Jarayon xotirasini kuzatish #
ps -o pid,vsz,rss,comm -p $$
PID VSZ RSS COMMAND
1204 12456 5204 bash
| Ustun | Ma'nosi |
|---|---|
| VSZ | Virtual hajm - so'ralgan xotira (KB) |
| RSS | Rezident hajm - RAM da haqiqatan turgan (KB) |
cat /proc/self/status | grep -E "VmSize|VmRSS|VmData|VmStk"
VmSize: 12456 kB
VmRSS: 5204 kB
VmData: 1024 kB
VmStk: 132 kB
RSS bilan aniqlash qiyinfree qilingan xotira darhol tizimga qaytmaydi - malloc uni
keyingi so'rovlar uchun zaxirada saqlaydi.
Shuning uchun to'g'ri yozilgan dasturda ham RSS o'sib, keyin
barqarorlashadi.
Haqiqiy sizish belgisi - RSS ning to'xtovsiz o'sishi. Buni
aniqlash uchun dasturni uzoq ishlatib, grafigini chizing.
Aniq javob esa faqat sanitizer yoki valgrind dan keladi.
Xotira ajratish strategiyalari #
| Maslahat | Sabab |
|---|---|
Ko'p kichik malloc o'rniga bitta katta | Sarlavha yuki kamayadi |
| Obyektlar hovuzi (pool) ishlating | Ajratish/ozod qilish tezlashadi |
| Hajmni oldindan bilsangiz - stek ishlating | malloc umuman kerak emas |
realloc da sig'imni ikki barobar oshiring | Ko'chirish soni kamayadi |
| Uzoq ishlaydigan dasturda fragmentatsiyaga e'tibor bering | RSS o'sib ketadi |
Obyektlar hovuzi g'oyasi: bir xil hajmdagi ko'p obyekt kerak bo'lsa, ularni bitta katta blokda ajratib, o'zingiz taqsimlang.
Tugun *hovuz = malloc(1000 * sizeof(Tugun)); /* bitta chaqiruv */
/* keyin hovuz[0], hovuz[1], ... ni ishlatasiz */
free(hovuz); /* bitta ozod qilish */
Bu 1000 ta alohida malloc dan ancha tezroq va fragmentatsiya
keltirmaydi.
Xotira tugaganda nima bo'ladi? #
#include <stdio.h>
#include <stdlib.h>
int main(void) {
/* Juda katta so'rov - albatta rad etiladi */
size_t juda_katta = (size_t) 1024 * 1024 * 1024 * 1024; /* 1 TB */
void *xotira = malloc(juda_katta);
if (xotira == NULL) {
printf("malloc NULL qaytardi - so'rov rad etildi\n");
printf("Dastur qulamadi, chunki natija tekshirildi\n");
return 0;
}
printf("Kutilmaganda muvaffaqiyatli bo'ldi\n");
free(xotira);
return 0;
}
malloc NULL qaytardi - so'rov rad etildi
Dastur qulamadi, chunki natija tekshirildi
Linux ortiqcha va'da berish (overcommit) siyosatini qo'llaydi:
malloc so'ralgan xotira mavjud bo'lmasa ham muvaffaqiyat qaytarishi
mumkin.
Sabab: dasturlar odatda so'ragan xotiraning hammasini ishlatmaydi.
Muammo shundaki, xotira haqiqatan tugaganda yadro OOM Killer ni ishga tushiradi va jarayonlardan birini majburan o'ldiradi:
dmesg | grep -i "killed process"
Out of memory: Killed process 4521 (mysqld) total-vm:8123456kB
Qaysi jarayon o'ldirilishini oom_score belgilaydi:
cat /proc/self/oom_score
Muhim xizmatni himoya qilish uchun:
echo -1000 > /proc/<PID>/oom_score_adj
Shuning uchun serverlarda swap va xotira chegaralari (cgroups) to'g'ri sozlanishi juda muhim.
/proc/self/mapsfaylini o'qib, sohalarni aniqlang.- Kod sohasi nima uchun
whuquqisiz ekanini tushuntiring. - Kichik va katta
mallocdasbrk(0)o'zgarishini kuzating. mmapbilan xotira olib, uning nol ekanini tekshiring.- Faylni
mmapbilan o'qing. - Ikki marta
malloc(1)qilib, manzillar farqini o'lchang. massiv[-1]ga yozib,freeda nima bo'lishini ko'ring.- Xotira siziydigan dastur yozib, sanitizer bilan toping.
ps -o vsz,rssbilan dasturingiz xotirasini kuzating.- 1000 ta alohida
mallocva bitta hovuz variantini taqqoslang.
Xulosa #
- OT uch masalani hal qiladi: joylashtirish, himoya, yetishmovchilik.
- Jarayon xotirasi sohalarga bo'linadi; kod sohasi yozishdan himoyalangan.
- Tashqi fragmentatsiya - bo'sh joy bor, lekin tarqoq.
- Ichki fragmentatsiya - ajratilgan bo'lak ichida isrof.
mallockichik so'rovlar uchunbrk, katta uchunmmapishlatadi.mmapbilan olingan xotira doim nol bilan to'ldirilgan.- Faylni
mmapqilish ortiqcha nusxalashni yo'q qiladi. - Har bir
mallocbo'lagida sarlavha bor - uni buzmang. VSZso'ralgan,RSShaqiqatan ishlatilgan xotira.freeqilingan xotira darhol tizimga qaytmaydi.- Linux ortiqcha va'da beradi; xotira tugasa OOM Killer ishlaydi.
Keyingi bo'limda virtual xotira va sahifalashni 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.