Логотип коледжу
Оптико-механічний фаховий коледж

Лабораторна робота №2. Системні виклики ядра Linux для роботи з файловими дескрипторами

Мета

Реалізувати й дослідити системні виклики open, read, write, close та lseek у невеликій програмі C, а також навчитися обробляти помилки системних викликів.

Підготовка

Перед виконанням опрацюйте пов'язану лекцію:

Для роботи потрібне Linux-середовище з компілятором GCC. Перевірте:

gcc --version

Робота виконується повністю в межах папки lab-02 у вашому особистому репозиторії operational-systems. Права адміністратора й зміна системних налаштувань не потрібні.

Завдання

Завдання 1. Відтворіть програму з файловими дескрипторами

У папці lab-02 створіть файл fd_demo.c із таким вмістом:

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

int main(void) {
    const char *path = "notes.txt";
    char buffer[64];
    ssize_t n;
    int fd;

    fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0644);
    if (fd == -1) {
        fprintf(stderr, "open (write): %s\n", strerror(errno));
        return 1;
    }
    const char *line = "Hello from lab-02\n";
    if (write(fd, line, strlen(line)) == -1) {
        fprintf(stderr, "write: %s\n", strerror(errno));
        close(fd);
        return 1;
    }
    close(fd);

    fd = open(path, O_RDONLY);
    if (fd == -1) {
        fprintf(stderr, "open (read): %s\n", strerror(errno));
        return 1;
    }
    n = read(fd, buffer, sizeof(buffer) - 1);
    if (n == -1) {
        fprintf(stderr, "read: %s\n", strerror(errno));
        close(fd);
        return 1;
    }
    buffer[n] = '\0';
    printf("Прочитано %zd байтів: %s", n, buffer);

    if (lseek(fd, 11, SEEK_SET) == -1) {
        fprintf(stderr, "lseek: %s\n", strerror(errno));
        close(fd);
        return 1;
    }
    n = read(fd, buffer, 6);
    if (n == -1) {
        fprintf(stderr, "read: %s\n", strerror(errno));
        close(fd);
        return 1;
    }
    buffer[n] = '\0';
    printf("Зі зміщення 11 прочитано %zd байтів: %s\n", n, buffer);

    close(fd);
    return 0;
}

Скомпілюйте з суворими прапорами та запустіть:

gcc -Wall -Wextra -pedantic -o fd_demo fd_demo.c
./fd_demo

Поясніть кожен системний виклик: які аргументи він отримує, що повертає у разі успіху та у разі помилки. Зверніть увагу, що open із прапорцями O_WRONLY | O_CREAT | O_TRUNC створює або очищає файл, а lseek змінює лише позицію у файловому дескрипторі.

Завдання 2. Змініть зміщення та передбачте результат

Перед запуском змініть програму: замініть lseek(fd, 11, SEEK_SET) на lseek(fd, 0, SEEK_SET), а кількість байтів на читання — на 5. Запишіть, який текст, на вашу думку, буде прочитано. Скомпілюйте й запустіть, порівняйте з прогнозом.

Потім поверніть оригінальне значення 11 і виконайте трасування системних викликів, якщо strace доступний:

strace -e trace=openat,read,write,lseek,close ./fd_demo

Знайдіть у трасуванні openat, write, read і lseek, зіставте їх із рядками коду.

Завдання 3. Прикладний: міні-копіювач файлів

Створіть файл copy_file.c, який копіює вміст одного файла в інший, читаючи по 4096 байтів у циклі:

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

int main(int argc, char *argv[]) {
    if (argc != 3) {
        fprintf(stderr, "Використання: %s <джерело> <призначення>\n", argv[0]);
        return 1;
    }
    int in = open(argv[1], O_RDONLY);
    if (in == -1) {
        fprintf(stderr, "open %s: %s\n", argv[1], strerror(errno));
        return 1;
    }
    int out = open(argv[2], O_WRONLY | O_CREAT | O_TRUNC, 0644);
    if (out == -1) {
        fprintf(stderr, "open %s: %s\n", argv[2], strerror(errno));
        close(in);
        return 1;
    }
    char buf[4096];
    ssize_t n;
    while ((n = read(in, buf, sizeof(buf))) > 0) {
        if (write(out, buf, n) == -1) {
            fprintf(stderr, "write: %s\n", strerror(errno));
            close(in);
            close(out);
            return 1;
        }
    }
    if (n == -1) {
        fprintf(stderr, "read: %s\n", strerror(errno));
    }
    close(in);
    close(out);
    return n == -1 ? 1 : 0;
}

Скомпілюйте та скопіюйте notes.txt у notes-copy.txt:

gcc -Wall -Wextra -pedantic -o copy_file copy_file.c
./copy_file notes.txt notes-copy.txt
cmp notes.txt notes-copy.txt

cmp без виводу означає, що файли ідентичні. Поясніть, чому copy_file записує рівно стільки байтів, скільки повернув read, а не весь буфер 4096.

Очікуваний результат

Для завдання 1 програма fd_demo має вивести:

Прочитано 18 байтів: Hello from lab-02
Зі зміщення 11 прочитано 6 байтів: lab-02

Для завдання 3 cmp notes.txt notes-copy.txt не має вивести жодного повідомлення, а файли notes.txt і notes-copy.txt мають збігатися за вмістом.

Структура репозиторію

operational-systems/
├── README.md
├── lab-01/
└── lab-02/
    ├── fd_demo.c
    ├── copy_file.c
    ├── notes.txt
    └── notes-copy.txt

Що здати

  • Посилання на ваш особистий репозиторій operational-systems, надіслане у формі здачі на поточній сторінці.
  • Папку lab-02 з файлами fd_demo.c і copy_file.c (бінарні файли та створені notes*.txt завантажувати не обов'язково).
  • Оновлений кореневий README.md з назвою роботи та командами компіляції.

Питання для захисту

  1. Що таке файловий дескриптор і що повертає open у разі помилки?
  2. Для чого потрібні прапорці O_CREAT і O_TRUNC?
  3. Що змінює виклик lseek?
  4. Чому у copy_file записують рівно n байтів, а не 4096?
  5. Чи можна виконати ці операції без системних викликів, лише функціями бібліотеки?

Пов'язані лекції

Ресурси

Здати роботу