9-bo‘lim

Xotira boshqaruvi asoslari

Manzil fazosi, xotira ajratish usullari, fragmentatsiya, brk va mmap, malloc ichkarisi va xotira sizishini aniqlash.

🕑 20 daqiqa o‘qish 📄 1 149 so‘z 👁 1 marta ko‘rilgan
Ushbu bo‘lim mundarijasi
  1. Muammo: bir necha jarayon, bitta RAM
  2. Manzil fazosi
  3. Uzluksiz ajratish va fragmentatsiya
  4. brk va mmap
  5. mmap bilan to'g'ridan-to'g'ri ishlash
  6. Faylni xotiraga bog'lash
  7. malloc ichkarisi
  8. Xotira sizishini aniqlash
  9. Jarayon xotirasini kuzatish
  10. Xotira ajratish strategiyalari
  11. Xotira tugaganda nima bo'ladi?
  12. Xulosa

Har bir jarayon o'zini butun xotira egasi deb hisoblaydi. Bu illyuziyani operatsion tizim yaratadi.

Muammo: bir necha jarayon, bitta RAM #

OT hal qilishi kerak bo'lgan uch savol 1. Joylashtirish Yangi jarayonni RAM ning qayeriga qo'yish kerak? 2. Himoya A jarayoni B ning xotirasini o'qiy olmasin 3. Yetishmovchilik RAM 16 GB, lekin dasturlar 30 GB so'rasa nima qilish? Yechim: virtual xotira Uchala muammoni bir yo'la hal qiladi (10-bo'lim)
Zamonaviy tizimlarda bu muammolar birgalikda hal qilinadi

Manzil fazosi #

C
#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;
}
Natija
Kod eng pastdami?          ha
Data bss dan pastdami?     ha
Uyum bss dan yuqoridami?   ha
Stek uyumdan yuqoridami?   ha
Xotira xaritasini ko'rish
Terminal
cat /proc/self/maps
Natija
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 #

Tashqi fragmentatsiya Boshlanish: A (100 MB) B (70 MB) C (120 MB) bo'sh (110) B tugadi: A bo'sh (70) C bo'sh (110) A tugadi: bo'sh (100) bo'sh (70) C bo'sh (110) 280 MB bo'sh, lekin 150 MB lik dastur sig'maydi Bo'sh joy uch bo'lakka bo'linib ketgan: 100 + 70 + 110 Bu tashqi fragmentatsiya - sahifalash uni hal qiladi
Fragmentatsiya - uzluksiz ajratishning asosiy muammosi
StrategiyaG'oyaKamchiligi
Birinchi mos (first fit)Birinchi yetarli bo'lakni olBosh qismda mayda bo'laklar to'planadi
Eng yaxshi mos (best fit)Eng kichik yetarli bo'lakni olJuda mayda qoldiqlar hosil qiladi
Eng yomon mos (worst fit)Eng katta bo'lakni olKatta bo'laklarni tez tugatadi
Keyingi mos (next fit)Oxirgi joydan davom etTez, lekin sifatsiz
Ichki va tashqi fragmentatsiya

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 #

C
#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;
}
Natija
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 ishlatmang

Uyum chegarasini sbrk(0) bilan bevosita o'qish mumkin edi, lekin bu funksiyalar POSIX.1-2008 da standartdan chiqarilgan:

Natija
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 hajmiNima 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 #

C
#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;
}
Natija
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 #

C
#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;
}
Natija
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:

  1. Diskdan yadro keshiga;
  2. 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 #

malloc(100) aslida qancha joy oladi? Sarlavha 8-16 bayt Sizning 100 baytingiz malloc shu manzilni qaytaradi To'ldirish tekislash uchun ← qaytgan ko'rsatkich Sarlavhada bo'lak hajmi va bandligi saqlanadi Shuning uchun free() ga hajm aytish shart emas
Sarlavhani buzsangiz, uyum butunlay ishdan chiqadi
C
#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;
}
Natija
Ikki marta malloc(1) chaqirildi
Manzillar orasidagi farq 1 baytdan kattami? ha
Farq kamida 16 baytmi? ha
Sarlavhani buzish - halokatli
C
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:

Natija
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 #

C
#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;
}
Natija
Xotira sizishini ko'rsatuvchi dastur
5 marta 1024 bayt sizdi = 5120 bayt
Buni sanitizer yoki valgrind aniqlaydi
Terminal
gcc -std=c17 -g -fsanitize=address sizish.c -o sizish
./sizish
Natija
=================================================================
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 #

Terminal
ps -o pid,vsz,rss,comm -p $$
Natija
    PID    VSZ   RSS COMMAND
   1204  12456  5204 bash
UstunMa'nosi
VSZVirtual hajm - so'ralgan xotira (KB)
RSSRezident hajm - RAM da haqiqatan turgan (KB)
Terminal
cat /proc/self/status | grep -E "VmSize|VmRSS|VmData|VmStk"
Natija
VmSize:       12456 kB
VmRSS:        5204 kB
VmData:        1024 kB
VmStk:         132 kB
Xotira sizishini RSS bilan aniqlash qiyin

free 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 #

Amaliy maslahatlar
MaslahatSabab
Ko'p kichik malloc o'rniga bitta kattaSarlavha yuki kamayadi
Obyektlar hovuzi (pool) ishlatingAjratish/ozod qilish tezlashadi
Hajmni oldindan bilsangiz - stek ishlatingmalloc umuman kerak emas
realloc da sig'imni ikki barobar oshiringKo'chirish soni kamayadi
Uzoq ishlaydigan dasturda fragmentatsiyaga e'tibor beringRSS o'sib ketadi

Obyektlar hovuzi g'oyasi: bir xil hajmdagi ko'p obyekt kerak bo'lsa, ularni bitta katta blokda ajratib, o'zingiz taqsimlang.

C
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? #

C
#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;
}
Natija
malloc NULL qaytardi - so'rov rad etildi
Dastur qulamadi, chunki natija tekshirildi
OOM Killer

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:

Terminal
dmesg | grep -i "killed process"
Natija
Out of memory: Killed process 4521 (mysqld) total-vm:8123456kB

Qaysi jarayon o'ldirilishini oom_score belgilaydi:

Terminal
cat /proc/self/oom_score

Muhim xizmatni himoya qilish uchun:

Terminal
echo -1000 > /proc/<PID>/oom_score_adj

Shuning uchun serverlarda swap va xotira chegaralari (cgroups) to'g'ri sozlanishi juda muhim.

Amaliy topshiriq
  1. /proc/self/maps faylini o'qib, sohalarni aniqlang.
  2. Kod sohasi nima uchun w huquqisiz ekanini tushuntiring.
  3. Kichik va katta malloc da sbrk(0) o'zgarishini kuzating.
  4. mmap bilan xotira olib, uning nol ekanini tekshiring.
  5. Faylni mmap bilan o'qing.
  6. Ikki marta malloc(1) qilib, manzillar farqini o'lchang.
  7. massiv[-1] ga yozib, free da nima bo'lishini ko'ring.
  8. Xotira siziydigan dastur yozib, sanitizer bilan toping.
  9. ps -o vsz,rss bilan dasturingiz xotirasini kuzating.
  10. 1000 ta alohida malloc va 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.
  • malloc kichik so'rovlar uchun brk, katta uchun mmap ishlatadi.
  • mmap bilan olingan xotira doim nol bilan to'ldirilgan.
  • Faylni mmap qilish ortiqcha nusxalashni yo'q qiladi.
  • Har bir malloc bo'lagida sarlavha bor - uni buzmang.
  • VSZ so'ralgan, RSS haqiqatan ishlatilgan xotira.
  • free qilingan xotira darhol tizimga qaytmaydi.
  • Linux ortiqcha va'da beradi; xotira tugasa OOM Killer ishlaydi.

Keyingi bo'limda virtual xotira va sahifalashni ko'ramiz.

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.