12-bo‘lim

Fayl tizimi

Fayl deskriptori, ochish-o'qish-yozish, kursor, kataloglar, yo'llar, huquqlar, qattiq va ramziy havolalar.

🕑 17 daqiqa o‘qish 📄 980 so‘z 👁 1 marta ko‘rilgan
Ushbu bo‘lim mundarijasi
  1. Fayl deskriptori
  2. Fayl ochish va o'qish
  3. Ochish bayroqlari
  4. Kursor va lseek
  5. Fayl haqidagi ma'lumot
  6. Huquqlar
  7. Kataloglarni o'qish
  8. Qattiq va ramziy havolalar
  9. Atomar almashtirish
  10. Xulosa

Unix falsafasida hamma narsa fayl: disk, klaviatura, tarmoq ulanishi, hatto jarayonlar haqidagi ma'lumot ham.

Fayl deskriptori #

Uch darajali jadval Jarayonning fd jadvali 0 - stdin 1 - stdout 2 - stderr 3 - hisobot.txt har bir jarayonda alohida Ochiq fayllar jadvali tizim bo'yicha yagona kursor: 1024 rejim: O_RDWR bayroqlar fork qilganda bo'lishiladi inode jadvali faylning o'zi hajm, egasi huquqlar vaqtlar blok manzillari diskda saqlanadi Bir necha fd bitta faylga, bir necha yozuv bitta inode ga ishora qilishi mumkin
Fayl deskriptori - shu zanjirning boshidagi oddiy son

Fayl ochish va o'qish #

C
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>

int main(void) {
    const char *nom = "sinov_fayl.txt";
    const char *matn = "Birinchi qator\nIkkinchi qator\n";

    /* Yozish uchun ochamiz */
    int fd = open(nom, O_WRONLY | O_CREAT | O_TRUNC, 0644);

    if (fd < 0) {
        perror("open");
        return 1;
    }

    printf("Yozish uchun ochilgan fd: %d\n", fd);

    ssize_t yozildi = write(fd, matn, strlen(matn));
    printf("Yozilgan bayt: %zd\n", yozildi);

    close(fd);

    /* O'qish uchun qayta ochamiz */
    fd = open(nom, O_RDONLY);

    if (fd < 0) {
        return 1;
    }

    char bufer[64];
    ssize_t oqildi = read(fd, bufer, sizeof bufer - 1);
    bufer[oqildi] = '\0';

    printf("O'qilgan bayt : %zd\n", oqildi);
    printf("Mazmuni to'g'rimi? %s\n", strcmp(bufer, matn) == 0 ? "ha" : "yo'q");

    close(fd);
    unlink(nom);

    return 0;
}
Natija
Yozish uchun ochilgan fd: 3
Yozilgan bayt: 30
O'qilgan bayt : 30
Mazmuni to'g'rimi? ha
Nima uchun birinchi fd = 3?

Har bir jarayon uchta fayl deskriptori bilan tug'iladi:

fdNomiOdatiy manzili
0stdinKlaviatura
1stdoutEkran
2stderrEkran

open eng kichik bo'sh deskriptorni qaytaradi. 0, 1, 2 band bo'lgani uchun birinchi yangi fayl 3 ni oladi.

Bu xossa quvur (pipe) qurishda ishlatiladi: close(1) qilib darhol open chaqirsangiz, yangi fayl stdout o'rniga o'tiradi. Amalda buning uchun dup2 ishlatiladi - 15-bo'limda ko'ramiz.

Ochish bayroqlari #

BayroqMa'nosi
O_RDONLYFaqat o'qish
O_WRONLYFaqat yozish
O_RDWRO'qish va yozish
O_CREATBo'lmasa yaratish (rejim kerak)
O_TRUNCBor bo'lsa bo'shatish
O_APPENDHar yozishda oxiriga o'tish
O_EXCLO_CREAT bilan: bor bo'lsa xato
O_NONBLOCKBloklamaydigan rejim
C
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>

int main(void) {
    const char *nom = "yagona.txt";

    /* Birinchi marta - muvaffaqiyatli */
    int birinchi = open(nom, O_WRONLY | O_CREAT | O_EXCL, 0644);
    printf("Birinchi urinish: %s\n", birinchi >= 0 ? "yaratildi" : "xato");

    /* Ikkinchi marta - O_EXCL xato beradi */
    int ikkinchi = open(nom, O_WRONLY | O_CREAT | O_EXCL, 0644);
    printf("Ikkinchi urinish: %s\n", ikkinchi >= 0 ? "yaratildi" : "xato");

    if (ikkinchi < 0) {
        printf("Xato sababi   : %s\n", strerror(errno));
        printf("EEXIST mi?    : %s\n", errno == EEXIST ? "ha" : "yo'q");
    }

    if (birinchi >= 0) {
        close(birinchi);
    }

    unlink(nom);

    return 0;
}
Natija
Birinchi urinish: yaratildi
Ikkinchi urinish: xato
Xato sababi   : File exists
EEXIST mi?    : ha
O_CREAT | O_EXCL - poyga holatidan himoya

Ko'p dasturchi shunday yozadi:

C
if (access(nom, F_OK) != 0) {      /* fayl yo'qmi? */
    fd = open(nom, O_WRONLY | O_CREAT, 0644);
}

Bu TOCTOU (Time-of-check to time-of-use) zaifligi. Tekshiruv va ochish orasida boshqa jarayon o'sha nomda fayl (yoki ramziy havola) yaratishi mumkin.

To'g'ri yechim - bitta atomar amal:

C
fd = open(nom, O_WRONLY | O_CREAT | O_EXCL, 0644);

Yadro tekshiruv va yaratishni bo'linmas tarzda bajaradi. Vaqtinchalik fayl yaratishda esa mkstemp ishlating.

Kursor va lseek #

C
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>

int main(void) {
    const char *nom = "kursor.txt";

    int fd = open(nom, O_RDWR | O_CREAT | O_TRUNC, 0644);

    if (fd < 0) {
        return 1;
    }

    write(fd, "0123456789", 10);

    printf("Yozgandan keyingi kursor: %ld\n", (long) lseek(fd, 0, SEEK_CUR));

    lseek(fd, 0, SEEK_SET);
    printf("Boshiga qaytgan kursor  : %ld\n", (long) lseek(fd, 0, SEEK_CUR));

    char bufer[4];
    read(fd, bufer, 3);
    bufer[3] = '\0';

    printf("Uch bayt o'qildi        : %s\n", bufer);
    printf("O'qigandan keyingi kursor: %ld\n", (long) lseek(fd, 0, SEEK_CUR));

    /* Oxiridan orqaga */
    long oxir = (long) lseek(fd, -2, SEEK_END);
    read(fd, bufer, 2);
    bufer[2] = '\0';

    printf("Oxirdan 2 bayt oldin    : %ld\n", oxir);
    printf("O'sha yerdagi mazmun    : %s\n", bufer);

    close(fd);
    unlink(nom);

    return 0;
}
Natija
Yozgandan keyingi kursor: 10
Boshiga qaytgan kursor  : 0
Uch bayt o'qildi        : 012
O'qigandan keyingi kursor: 3
Oxirdan 2 bayt oldin    : 8
O'sha yerdagi mazmun    : 89
Teshikli fayllar (sparse files)

lseek bilan faylning oxiridan nariga o'tib yozish mumkin:

C
lseek(fd, 1024 * 1024 * 1024, SEEK_SET);   /* 1 GB naridan */
write(fd, "X", 1);

Fayl hajmi 1 GB ko'rinadi, lekin diskda bir necha blok joy oladi. Oradagi "teshik" nol bayt sifatida o'qiladi, lekin haqiqatan saqlanmaydi.

Terminal
ls -l teshikli.dat      # ko'rsatilgan hajm
du -h teshikli.dat      # haqiqiy disk joyi

Virtual mashina disk obrazlari, ma'lumotlar bazasi fayllari shu usuldan foydalanadi.

Fayl haqidagi ma'lumot #

C
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>

int main(void) {
    const char *nom = "malumot.txt";

    int fd = open(nom, O_WRONLY | O_CREAT | O_TRUNC, 0640);

    if (fd < 0) {
        return 1;
    }

    write(fd, "12345", 5);
    close(fd);

    struct stat h;

    if (stat(nom, &h) < 0) {
        return 1;
    }

    printf("Hajmi          : %ld bayt\n", (long) h.st_size);
    printf("Oddiy faylmi?  : %s\n", S_ISREG(h.st_mode) ? "ha" : "yo'q");
    printf("Katalogmi?     : %s\n", S_ISDIR(h.st_mode) ? "ha" : "yo'q");
    printf("Huquqlar       : %o\n", h.st_mode & 07777);
    printf("Havolalar soni : %ld\n", (long) h.st_nlink);
    printf("Bloklar soni   : %ld\n", (long) h.st_blocks);

    unlink(nom);

    return 0;
}
Natija
Hajmi          : 5 bayt
Oddiy faylmi?  : ha
Katalogmi?     : yo'q
Huquqlar       : 640
Havolalar soni : 1
Bloklar soni   : 8
5 bayt uchun 8 ta blok?

st_blocks 512 baytli birliklarda sanaladi (tarixiy sabab). 8 × 512 = 4096 bayt = bitta 4 KB blok.

Ya'ni fayl tizimi eng kichik fayl uchun ham butun blok ajratadi. 1 baytlik million fayl 4 GB disk egallaydi.

Shuning uchun ko'p mayda fayl saqlaydigan tizimlarda (kesh, log) ularni bitta katta faylga birlashtirish samaraliroq.

Huquqlar #

rw-r----- = 0640 Egasi rw- = 6 Guruh r-- = 4 Boshqalar --- = 0 Bitlar: r = 4 w = 2 x = 1 Katalogda ma'nosi boshqacha r = ro'yxatni ko'rish, w = fayl qo'shish/o'chirish x = katalog ichiga kirish (eng muhimi)
Katalogda "x" bo'lmasa, ichidagi faylga umuman kira olmaysiz
C
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>

int main(void) {
    const char *nom = "huquq.txt";

    /* umask ta'sirini yo'qotamiz */
    umask(0);

    int fd = open(nom, O_WRONLY | O_CREAT | O_TRUNC, 0666);

    if (fd < 0) {
        return 1;
    }

    close(fd);

    struct stat h;
    stat(nom, &h);
    printf("Yaratilgandagi huquq: %o\n", h.st_mode & 0777);

    chmod(nom, 0600);
    stat(nom, &h);
    printf("chmod dan keyin     : %o\n", h.st_mode & 0777);

    printf("O'qish mumkinmi?    : %s\n", access(nom, R_OK) == 0 ? "ha" : "yo'q");
    printf("Bajarish mumkinmi?  : %s\n", access(nom, X_OK) == 0 ? "ha" : "yo'q");

    unlink(nom);

    return 0;
}
Natija
Yaratilgandagi huquq: 666
chmod dan keyin     : 600
O'qish mumkinmi?    : ha
Bajarish mumkinmi?  : yo'q
umask - yashirin filtr

open(nom, ..., 0666) yozsangiz ham fayl odatda 0644 bo'lib yaratiladi. Sababi - umask.

umask - jarayonning "olib tashlanadigan huquqlar" niqobi:

Natija
So'ralgan huquq :  0666  (rw-rw-rw-)
umask           :  0022  (----w--w-)
Natija          :  0644  (rw-r--r--)
Terminal
umask          # joriy qiymatni ko'rish
umask 077      # faqat egaga ruxsat

Shuning uchun yuqoridagi misolda umask(0) chaqirdik - aks holda natija 0666 emas, 0644 bo'lardi.

Maxfiy ma'lumot yozadigan dasturlarda ochish rejimini aniq 0600 qiling va umask ga tayanmang.

Kataloglarni o'qish #

C
#include <stdio.h>
#include <string.h>
#include <dirent.h>
#include <sys/stat.h>
#include <unistd.h>

int main(void) {
    /* Sinov katalogi tuzamiz */
    mkdir("sinov_katalog", 0755);

    FILE *f;
    const char *nomlar[3] = { "bir.txt", "ikki.txt", "uch.txt" };
    char yol[64];

    for (int i = 0; i < 3; i++) {
        snprintf(yol, sizeof yol, "sinov_katalog/%s", nomlar[i]);
        f = fopen(yol, "w");

        if (f != NULL) {
            fputs("x", f);
            fclose(f);
        }
    }

    DIR *katalog = opendir("sinov_katalog");

    if (katalog == NULL) {
        return 1;
    }

    int oddiy = 0;
    int nuqtali = 0;
    struct dirent *yozuv;

    while ((yozuv = readdir(katalog)) != NULL) {
        if (yozuv->d_name[0] == '.') {
            nuqtali++;
        } else {
            oddiy++;
        }
    }

    closedir(katalog);

    printf("Oddiy fayllar   : %d\n", oddiy);
    printf("Nuqtali yozuvlar: %d\n", nuqtali);
    printf("Nuqtalilar . va .. mi? %s\n", nuqtali == 2 ? "ha" : "yo'q");

    for (int i = 0; i < 3; i++) {
        snprintf(yol, sizeof yol, "sinov_katalog/%s", nomlar[i]);
        unlink(yol);
    }

    rmdir("sinov_katalog");

    return 0;
}
Natija
Oddiy fayllar   : 3
Nuqtali yozuvlar: 2
Nuqtalilar . va .. mi? ha
Katalog nima?

Katalog - bu maxsus fayl, ichida (nom, inode raqami) juftliklari saqlanadi.

Natija
.           -> inode 128
..          -> inode 96
bir.txt     -> inode 4521
ikki.txt    -> inode 4522

Ya'ni fayl nomi faylda emas, katalogda saqlanadi. Faylning o'zida (inode da) nom yo'q - faqat hajm, huquqlar va ma'lumot bloklarining manzillari.

Bu muhim xulosaga olib keladi: bitta fayl bir necha nomga ega bo'lishi mumkin - qattiq havolalar.

readdir tartibini kafolatlab bo'lmaydi. Alifbo tartibi kerak bo'lsa, scandir ishlating yoki o'zingiz saralang.

Qattiq va ramziy havolalar #

C
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>

int main(void) {
    const char *asl = "asl.txt";

    FILE *f = fopen(asl, "w");

    if (f == NULL) {
        return 1;
    }

    fputs("mazmun", f);
    fclose(f);

    link(asl, "qattiq.txt");            /* qattiq havola */
    symlink(asl, "ramziy.txt");         /* ramziy havola */

    struct stat a, q, r, rl;

    stat(asl, &a);
    stat("qattiq.txt", &q);
    stat("ramziy.txt", &r);
    lstat("ramziy.txt", &rl);

    printf("Asl inode raqami       : %s\n",
           a.st_ino == q.st_ino ? "qattiq havola bilan bir xil" : "farqli");
    printf("Havolalar soni         : %ld\n", (long) a.st_nlink);
    printf("Ramziy havola (stat)   : %s\n",
           r.st_ino == a.st_ino ? "aslga olib boradi" : "boshqa");
    printf("Ramziy havola (lstat)  : %s\n",
           rl.st_ino == a.st_ino ? "bir xil" : "o'zi alohida obyekt");
    printf("Ramziy havolami?       : %s\n",
           S_ISLNK(rl.st_mode) ? "ha" : "yo'q");

    /* Aslni o'chiramiz - qattiq havola orqali ma'lumot qoladi */
    unlink(asl);

    f = fopen("qattiq.txt", "r");
    char bufer[16] = {0};

    if (f != NULL) {
        fgets(bufer, sizeof bufer, f);
        fclose(f);
    }

    printf("Asl o'chirilgach qattiq: %s\n", bufer);

    f = fopen("ramziy.txt", "r");
    printf("Asl o'chirilgach ramziy: %s\n", f == NULL ? "buzilgan" : "ishlaydi");

    if (f != NULL) {
        fclose(f);
    }

    unlink("qattiq.txt");
    unlink("ramziy.txt");

    return 0;
}
Natija
Asl inode raqami       : qattiq havola bilan bir xil
Havolalar soni         : 2
Ramziy havola (stat)   : aslga olib boradi
Ramziy havola (lstat)  : o'zi alohida obyekt
Ramziy havolami?       : ha
Asl o'chirilgach qattiq: mazmun
Asl o'chirilgach ramziy: buzilgan
XususiyatQattiq havolaRamziy havola
Nima saqlaydiInode raqaminiMatn ko'rinishidagi yo'lni
Fayl tizimlari orasidaMumkin emasMumkin
KataloggaMumkin emasMumkin
Asl o'chirilsaMa'lumot qoladiBuziladi
ls -l daOddiy fayl-> bilan ko'rinadi
unlink nomi bejiz emas

Fayl o'chirish tizim chaqiruvi delete emas, unlink deb ataladi.

Sabab: u faylni emas, katalogdagi yozuvni o'chiradi va inode dagi havolalar sonini bittaga kamaytiradi.

Fayl ma'lumoti faqat ikki shart bajarilganda yo'qoladi:

  1. Havolalar soni nolga tushganda;
  2. Uni ochib turgan jarayon qolmaganda.

Shuning uchun ishlab turgan dastur log faylini o'chirsangiz, disk joyi bo'shamaydi - dastur qayta ishga tushirilgunicha:

Terminal
lsof +L1        # o'chirilgan, lekin ochiq fayllarni ko'rish

Atomar almashtirish #

C
#include <stdio.h>
#include <string.h>
#include <unistd.h>

int main(void) {
    const char *asl = "sozlama.conf";
    const char *vaqtinchalik = "sozlama.conf.yangi";

    FILE *f = fopen(asl, "w");

    if (f == NULL) {
        return 1;
    }

    fputs("eski=1\n", f);
    fclose(f);

    /* Yangi mazmunni alohida faylga yozamiz */
    f = fopen(vaqtinchalik, "w");

    if (f == NULL) {
        return 1;
    }

    fputs("yangi=2\n", f);
    fflush(f);
    fsync(fileno(f));                   /* diskka kafolatli yozish */
    fclose(f);

    /* Atomar almashtirish */
    rename(vaqtinchalik, asl);

    f = fopen(asl, "r");
    char bufer[32] = {0};

    if (f != NULL) {
        fgets(bufer, sizeof bufer, f);
        fclose(f);
    }

    printf("Faylning yangi mazmuni: %s", bufer);
    printf("Vaqtinchalik fayl qoldimi? %s\n",
           access(vaqtinchalik, F_OK) == 0 ? "ha" : "yo'q");

    unlink(asl);

    return 0;
}
Natija
Faylning yangi mazmuni: yangi=2
Vaqtinchalik fayl qoldimi? yo'q
Sozlama faylini to'g'ridan-to'g'ri o'zgartirmang

Bu xavfli:

C
f = fopen("sozlama.conf", "w");    /* fayl darhol bo'shaydi! */
yozish(f);                          /* shu paytda tok o'chsa... */

Natija - bo'sh yoki yarim yozilgan sozlama fayli. Xizmat qayta ishga tushmaydi.

To'g'ri usul (yuqoridagi misol):

  1. Yangi mazmunni vaqtinchalik faylga yozing;
  2. fsync bilan diskka kafolatli tushiring;
  3. rename bilan atomar almashtiring.

rename POSIX bo'yicha atomar: har qanday paytda fayl yo eski, yo yangi mazmunda bo'ladi - o'rtasi yo'q.

Systemd, Git, ma'lumotlar bazalari - hammasi shu usulni ishlatadi.

Amaliy topshiriq
  1. open bilan fayl yaratib, qaytgan fd raqamini tushuntiring.
  2. O_CREAT | O_EXCL bilan ikki marta ochib, EEXIST ni oling.
  3. lseek bilan kursorni harakatlantirib, o'qing.
  4. Teshikli fayl yaratib, ls -l va du -h ni solishtiring.
  5. stat bilan hajm va bloklar sonini chiqaring.
  6. umask ni o'zgartirib, yaratilgan fayl huquqini kuzating.
  7. opendir/readdir bilan katalogni sanang.
  8. Qattiq va ramziy havola yaratib, farqini isbotlang.
  9. Aslni o'chirib, ikkala havolaning holatini tekshiring.
  10. Sozlama faylini rename orqali atomar yangilang.

Xulosa #

  • Fayl deskriptori - jarayon jadvalidagi oddiy butun son.
  • 0, 1, 2 band bo'lgani uchun birinchi yangi fd 3 bo'ladi.
  • O_CREAT | O_EXCL TOCTOU zaifligidan himoya qiladi.
  • Kursor har bir ochiq faylda alohida saqlanadi.
  • lseek bilan teshikli fayl yaratish mumkin.
  • Fayl tizimi eng kichik fayl uchun ham butun blok ajratadi.
  • umask so'ralgan huquqlardan bitlarni olib tashlaydi.
  • Katalog - (nom, inode) juftliklari saqlanadigan maxsus fayl.
  • Qattiq havola - o'sha inode ga yangi nom.
  • Ramziy havola - matn ko'rinishidagi yo'l; asl o'chsa buziladi.
  • unlink faylni emas, katalog yozuvini o'chiradi.
  • Sozlama faylini rename orqali atomar yangilang.

Keyingi bo'limda fayl tizimining ichki tuzilishini ochamiz.

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.