diff --git a/rgz/report.md b/rgz/report.md index 632d61d..6412551 100644 --- a/rgz/report.md +++ b/rgz/report.md @@ -25,6 +25,18 @@ В качестве ориентира использовался предоставленный пример реализации, однако приоритет был отдан требованиям задания. Поэтому итоговая программа сохраняет простую учебную архитектуру примера, но дополнительно реализует операции `open`, `close`, `seek`, `read`, `write`, `rename`, импорт и экспорт, дефрагментацию и сжатие. +> **Теоретическая основа.** Файловая система — это способ организации долговременного хранения данных, при котором содержимое носителя представляется не как сплошной массив байтов, а как совокупность именованных объектов, метаданных и правил размещения. Даже в упрощенной реализации должны присутствовать три ключевых слоя: физическое хранение, служебные структуры и пользовательский интерфейс доступа к файлам. + +Уже в начале программы задаются основные параметры будущей файловой системы: сигнатура формата, размер кластера, число дескрипторов и ограничения на открытый файл. + +```c +#define FS_MAGIC 0x53465331u +#define CLUSTER_SIZE 1024u +#define MAX_ENTRIES 128 +#define MAX_FILE_CLUSTERS 128 +#define MAX_OPEN_DATA (MAX_FILE_CLUSTERS * CLUSTER_SIZE) +``` + ## Постановка задачи Согласно заданию необходимо разработать систему управления файлами, где разделом является бинарный файл произвольного доступа. Реализация должна включать: @@ -37,10 +49,28 @@ - символьные операции над файлами и каталогами; - прикладную утилиту для форматирования, просмотра, импорта, экспорта, дефрагментации и сжатия. +> **Теоретическая основа.** При проектировании файловой системы важно разделять данные пользователя и служебные данные самой системы. Пользователь работает с именами файлов и каталогов, однако внутри реализации требуется хранить состояние носителя, сведения о свободном пространстве, метаданные объектов и текущий контекст работы. + +В программе эта постановка отражается в центральной структуре состояния файловой системы. Она объединяет физический носитель, карту свободных блоков, таблицу дескрипторов и текущий каталог пользователя. + +```c +typedef struct { + FILE *disk; + char image_path[MAX_PATH]; + SuperBlock sb; + unsigned char *bitmap; + Entry entries[MAX_ENTRIES]; + uint32_t current_dir; + bool mounted; +} FileSystem; +``` + ## Выбранная архитектура файловой системы Файловая система разделена на уровни. Такое деление полезно не только для описания в отчете: каждый уровень решает свою задачу и скрывает детали от следующего. Пользователь работает с командами и путями, а не с номерами кластеров; функции чтения работают с файлами, а не с отдельными битами карты свободного пространства. +> **Теоретическая основа.** Многоуровневая архитектура уменьшает связность системы. Физический уровень отвечает за доступ к байтам носителя, базовый — за метаданные, логический — за каталоги и пути, а прикладной — за команды пользователя. Такое разделение облегчает развитие системы: например, способ учета свободных блоков можно изменить, почти не затрагивая синтаксис команд. + | Уровень | Реализация | Назначение | | --- | --- | --- | | Физический уровень | Кластеры фиксированного размера по 1024 байта | Делит носитель на одинаковые адресуемые блоки; смещение кластера вычисляется как `номер * 1024` | @@ -55,22 +85,90 @@ Носителем является обычный бинарный файл. Он рассматривается как последовательность кластеров одинакового размера. Фиксированный размер выбран ради простоты: адрес любого кластера находится прямым умножением, а алгоритм выделения не обязан искать переменные по длине участки памяти. Недостатком является внутреннее фрагментирование: даже файл в несколько байт занимает целый кластер. +> **Теория.** Кластер — минимальная единица выделения дискового пространства. Чем больше размер кластера, тем меньше служебных записей требуется для описания больших файлов, но тем выше потери на внутреннее фрагментирование при хранении маленьких файлов. + +Адрес кластера вычисляется напрямую по его номеру. Это видно при записи данных: + +```c +fseek(fs->disk, (long)(entry->clusters[i] * CLUSTER_SIZE), SEEK_SET); +fwrite(data + written, 1, chunk, fs->disk); +``` + ### Уровень свободного пространства Для каждого кластера хранится один бит. При форматировании служебные кластеры суперблока, битовой карты и индексного файла сразу помечаются занятыми. При записи файла функция выделения просматривает карту от начала области данных и выбирает первые свободные кластеры; при удалении файла соответствующие биты сбрасываются обратно в ноль. +> **Теория.** Битовая карта — одна из классических структур учета свободного пространства. Она компактна и проста в реализации: состояние каждого блока кодируется одним битом. Недостатком является необходимость линейного просмотра при поиске свободных блоков, если не используются дополнительные индексы или кеши. + +```c +static bool bit_is_set(FileSystem *fs, uint32_t cluster) { + return (fs->bitmap[cluster / 8] & (1u << (cluster % 8))) != 0; +} + +static int alloc_cluster(FileSystem *fs) { + for (uint32_t i = fs->sb.data_start_cluster; i < fs->sb.total_clusters; i++) { + if (!bit_is_set(fs, i)) { + bit_set(fs, i); + return (int)i; + } + } + return -1; +} +``` + ### Базовый уровень Индексный файл — это таблица дескрипторов `Entry`. Один дескриптор описывает один файл или каталог: имя, тип, родителя, размеры и список кластеров. Корневой каталог хранится в нулевой записи. Благодаря индексному файлу метаданные можно прочитать сразу после монтирования носителя, не просматривая всю область данных. +> **Теория.** Метаданные отделяются от содержимого файлов, чтобы система могла быстро находить объект, определять его тип, размер и расположение без чтения пользовательских данных. В реальных файловых системах аналогичную роль играют, например, inode-структуры. + +```c +typedef struct { + char name[MAX_NAME]; + uint8_t type; + uint8_t compressed; + uint16_t reserved; + uint32_t parent; + uint32_t size; + uint32_t stored_size; + uint32_t cluster_count; + uint32_t clusters[MAX_FILE_CLUSTERS]; +} Entry; +``` + ### Уровень размещения файлов Каждый файл хранит массив номеров своих кластеров. Поэтому файл может лежать не непрерывно, а частями в разных местах виртуального диска. При чтении система идет по этому массиву и собирает содержимое в правильной последовательности. Дефрагментация перестраивает эти списки так, чтобы занятые кластеры снова располагались компактно. +> **Теория.** Несмежное размещение повышает гибкость выделения места: файл можно расширять даже при отсутствии одного большого непрерывного участка. Однако чтение таких файлов требует хранения списка блоков и может приводить к фрагментации. + +```c +for (i = 0; i < entry->cluster_count; i++) { + uint32_t chunk = entry->stored_size - read_count; + if (chunk > CLUSTER_SIZE) chunk = CLUSTER_SIZE; + fseek(fs->disk, (long)(entry->clusters[i] * CLUSTER_SIZE), SEEK_SET); + fread(data + read_count, 1, chunk, fs->disk); + read_count += chunk; +} +``` + ### Логический и символьный уровни Каталог реализован без отдельного файла содержимого: у каждого объекта есть поле `parent`, содержащее индекс родительского каталога. Путь разбивается по символу `/`, после чего система шаг за шагом ищет дочерний объект в текущем каталоге. Символьный уровень дает привычные операции над открытым файлом: `open`, `close`, `seek`, `read`, `write`; внутри программы они работают через буфер и скрывают детали физического размещения. +> **Теория.** Логический уровень преобразует человекоориентированные имена в внутренние идентификаторы объектов. Символьный уровень дополняет это абстракцией «открытого файла», где пользователь работает с текущей позицией чтения и записи, а не с физическими блоками. + +```c +static int find_child(FileSystem *fs, uint32_t parent, const char *name) { + for (int i = 0; i < MAX_ENTRIES; i++) { + if (fs->entries[i].type != TYPE_FREE && + fs->entries[i].parent == parent && + strcmp(fs->entries[i].name, name) == 0) return i; + } + return -1; +} +``` + ```mermaid flowchart LR A[Команда пользователя] --> B[Путь /docs/a.txt] @@ -84,6 +182,8 @@ flowchart LR В начале виртуального диска находится суперблок. Он хранит сигнатуру файловой системы, размер диска, размер кластера, смещения служебных областей и индекс корневого каталога. +> **Теоретическая основа.** Суперблок — это точка входа в структуру файловой системы. После подключения носителя именно по нему программа понимает, корректный ли перед ней формат и где искать остальные области. Потеря суперблока делает интерпретацию содержимого носителя затруднительной даже при сохранности пользовательских данных. + ```c typedef struct { uint32_t magic; @@ -116,6 +216,17 @@ typedef struct { Поле `size` хранит логический размер файла, видимый пользователю, а `stored_size` — фактическое количество байт на носителе. Это различие необходимо для поддержки сжатых файлов. +При форматировании эти области рассчитываются последовательно: после суперблока располагается битовая карта, затем индексный файл, после чего начинается пользовательская область данных. + +```c +fs->sb.bitmap_offset = CLUSTER_SIZE; +fs->sb.bitmap_size = (fs->sb.total_clusters + 7) / 8; +bitmap_clusters = bytes_to_clusters(fs->sb.bitmap_size); +fs->sb.index_offset = (1 + bitmap_clusters) * CLUSTER_SIZE; +fs->sb.index_clusters = bytes_to_clusters((uint32_t)(sizeof(Entry) * MAX_ENTRIES)); +fs->sb.data_start_cluster = 1 + bitmap_clusters + fs->sb.index_clusters; +``` + ```mermaid flowchart TB S[Суперблок] --> B[Битовая карта] @@ -137,6 +248,8 @@ gcc -std=c11 -Wall -Wextra -pedantic simplefs.c -o simplefs Интерактивная оболочка поддерживает историю команд: в обычном терминале стрелки **Up** и **Down** перемещаются по ранее введенным строкам. Основные команды названы в стиле Unix, чтобы работа с учебной файловой системой напоминала привычную командную строку. +> **Теоретическая основа.** Набор операций файловой системы обычно разделяется на операции над объектами (`create`, `remove`, `rename`), операции навигации (`cd`, `ls`, `pwd`), операции передачи данных (`read`, `write`, `import`, `export`) и сервисные операции обслуживания носителя (`format`, `defrag`, `compress`). + ### Справочник команд | Команда | Синтаксис | Назначение | @@ -168,32 +281,107 @@ gcc -std=c11 -Wall -Wextra -pedantic simplefs.c -o simplefs На уровне программного интерфейса реализованы функции `fs_open`, `fs_close`, `fs_seek`, `fs_read` и `fs_write`. Открытый файл использует буфер в памяти, поэтому чтение и запись записей произвольной длины выполняются без прямой работы пользователя с кластерами. +```c +uint32_t fs_write(OpenFile *f, const unsigned char *buffer, uint32_t count) { + if (!f || !buffer || f->mode != MODE_WRITE || + f->position + count > f->capacity) return 0; + memcpy(f->data + f->position, buffer, count); + f->position += count; + if (f->position > f->size) f->size = f->position; + f->dirty = true; + return count; +} +``` + ## Алгоритмы работы +В этом разделе приведены основные алгоритмы, которые обеспечивают корректное изменение состояния носителя. Они важны не только как детали реализации, но и как отражение базовых принципов файловых систем: инициализация структуры, выделение ресурсов, разрешение имен, преобразование данных и восстановление компактного размещения. + ### Форматирование При выполнении `format` создается бинарный файл нужного размера, вычисляются размеры служебных областей, резервируются занятые кластеры и создается корневой каталог `/`. +```c +for (i = 0; i < fs->sb.data_start_cluster; i++) bit_set(fs, i); +memset(fs->entries, 0, sizeof(fs->entries)); +safe_copy(fs->entries[0].name, "/", MAX_NAME); +fs->entries[0].type = TYPE_DIR; +fs->entries[0].parent = 0; +``` + ### Выделение свободного кластера Алгоритм линейно просматривает битовую карту, начиная с области данных. Первый свободный бит помечается как занятый, а номер соответствующего кластера возвращается вызывающей функции. +```c +static int alloc_cluster(FileSystem *fs) { + for (uint32_t i = fs->sb.data_start_cluster; i < fs->sb.total_clusters; i++) { + if (!bit_is_set(fs, i)) { + bit_set(fs, i); + return (int)i; + } + } + return -1; +} +``` + ### Работа с каталогами Каталог не хранит отдельный массив дочерних элементов. Вместо этого у каждого дескриптора есть поле `parent`, содержащее индекс родительского каталога. Для поиска элемента программа просматривает индексный файл и сравнивает имя и родителя. +```c +bool fs_create_dir(FileSystem *fs, const char *path) { + int parent, index; + char name[MAX_NAME]; + if (!split_parent_name(fs, path, &parent, name) || + find_child(fs, parent, name) >= 0) return false; + index = free_entry_index(fs); + if (index < 0) return false; + memset(&fs->entries[index], 0, sizeof(Entry)); + safe_copy(fs->entries[index].name, name, MAX_NAME); + fs->entries[index].type = TYPE_DIR; + fs->entries[index].parent = (uint32_t)parent; + fs_sync(fs); + return true; +} +``` + ### Сжатие Команда `compress` использует простой алгоритм RLE: одинаковые подряд идущие байты заменяются парой «количество + значение». Если сжатый вариант не меньше исходного, файл не изменяется. При чтении сжатый файл автоматически восстанавливается в обычный вид. Для наблюдения за внутренним представлением предусмотрена команда `rawcat`: она показывает сохраненные байты в шестнадцатеричном виде и расшифровывает каждую RLE-пару. Команда `decomp` выполняет обратное преобразование и снова сохраняет файл без сжатия. +```c +while (i < size) { + unsigned char value = src[i]; + uint32_t count = 1; + while (i + count < size && src[i + count] == value && count < 255) count++; + out[j++] = (unsigned char)count; + out[j++] = value; + i += count; +} +``` + ### Дефрагментация Команда `defrag` временно считывает содержимое файлов, очищает карту размещения области данных и записывает файлы заново подряд. После операции используемые кластеры становятся компактно расположенными. +```c +memset(fs->bitmap, 0, fs->sb.bitmap_size); +for (c = 0; c < fs->sb.data_start_cluster; c++) bit_set(fs, c); +for (i = 0; i < MAX_ENTRIES; i++) { + if (fs->entries[i].type == TYPE_FILE) { + fs->entries[i].cluster_count = 0; + write_stored_bytes(fs, &fs->entries[i], copies[i], stored_sizes[i]); + } +} +``` + ## Тестирование Пример базового сценария: +> **Теоретическая основа.** Тестирование файловой системы должно проверять не только отдельные команды, но и сохранение инвариантов: после записи файл должен читаться тем же содержимым, после удаления пространство должно освобождаться, после сжатия логические данные не должны меняться, а после дефрагментации содержимое должно сохраняться при изменении физического размещения. + ```text format 512 mkdir /docs @@ -220,6 +408,15 @@ info | Экспорт | создается внешний файл с тем же содержимым | | Дефрагментация | содержимое файлов сохраняется, кластеры уплотняются | +Для проверки сценария полезно опираться на сами команды интерактивной оболочки. Разбор пользовательского ввода связывает каждую строку с соответствующей функцией файловой системы: + +```c +else if (strcmp(cmd, "mkdir") == 0 && a) printf(fs_create_dir(&fs, a) ? "ok\n" : "mkdir failed\n"); +else if (strcmp(cmd, "write") == 0 && a && b) printf(fs_write_text(&fs, a, b) ? "ok\n" : "write failed\n"); +else if (strcmp(cmd, "compress") == 0 && a) printf(fs_compress_file(&fs, a) ? "ok\n" : "compression did not reduce file\n"); +else if (strcmp(cmd, "defrag") == 0) printf(fs_defrag(&fs) ? "ok\n" : "defrag failed\n"); +``` + ## Ограничения реализации | Ограничение | Причина | @@ -231,8 +428,36 @@ info | RLE эффективно не для всех данных | алгоритм выбран ради понятности реализации | | Нет одновременного доступа нескольких процессов | учебная однопользовательская модель | +> **Теоретическая основа.** Ограничения учебной реализации обычно связаны с заранее фиксированными структурами данных. Это упрощает код и делает поведение системы предсказуемым, но уменьшает масштабируемость по сравнению с промышленными файловыми системами, где размеры таблиц, число блоков и механизмы синхронизации более гибкие. + +Большинство ограничений заданы непосредственно константами программы. Это делает реализацию прозрачной для учебных целей, но одновременно фиксирует максимальные размеры структур: + +```c +#define MAX_NAME 32 +#define MAX_PATH 256 +#define MAX_ENTRIES 128 +#define MAX_FILE_CLUSTERS 128 +``` + ## Вывод В ходе работы была реализована простая файловая система, работающая поверх бинарного файла как виртуального носителя. Реализация охватывает все основные уровни, указанные в задании: физическое размещение, учет свободных блоков, индексные метаданные, каталоги, операции над файлами и сервисные команды управления носителем. Полученная программа остается достаточно простой для изучения начинающим разработчиком на C, но при этом показывает полный путь данных: от пользовательской команды до изменения битовой карты, индексного дескриптора и байтов в кластерах виртуального диска. + +> **Итоговое обобщение.** Разработанная система демонстрирует фундаментальный принцип файловых систем: удобные для человека операции над именованными объектами опираются на строго организованный набор служебных структур и алгоритмов управления блоками носителя. + +Ключевая идея реализации хорошо видна в операции синхронизации: состояние файловой системы собирается из нескольких уровней и затем последовательно сохраняется на виртуальный носитель. + +```c +static bool fs_sync(FileSystem *fs) { + fseek(fs->disk, 0, SEEK_SET); + fwrite(&fs->sb, sizeof(SuperBlock), 1, fs->disk); + fseek(fs->disk, fs->sb.bitmap_offset, SEEK_SET); + fwrite(fs->bitmap, 1, fs->sb.bitmap_size, fs->disk); + fseek(fs->disk, fs->sb.index_offset, SEEK_SET); + fwrite(fs->entries, sizeof(Entry), MAX_ENTRIES, fs->disk); + fflush(fs->disk); + return true; +} +```