update report

This commit is contained in:
2026-05-18 17:22:11 +07:00
parent ba6614d7a9
commit 9a16b6d369
+225
View File
@@ -25,6 +25,18 @@
В качестве ориентира использовался предоставленный пример реализации, однако приоритет был отдан требованиям задания. Поэтому итоговая программа сохраняет простую учебную архитектуру примера, но дополнительно реализует операции `open`, `close`, `seek`, `read`, `write`, `rename`, импорт и экспорт, дефрагментацию и сжатие. В качестве ориентира использовался предоставленный пример реализации, однако приоритет был отдан требованиям задания. Поэтому итоговая программа сохраняет простую учебную архитектуру примера, но дополнительно реализует операции `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` | | Физический уровень | Кластеры фиксированного размера по 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`. Один дескриптор описывает один файл или каталог: имя, тип, родителя, размеры и список кластеров. Корневой каталог хранится в нулевой записи. Благодаря индексному файлу метаданные можно прочитать сразу после монтирования носителя, не просматривая всю область данных. Индексный файл — это таблица дескрипторов `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`; внутри программы они работают через буфер и скрывают детали физического размещения. Каталог реализован без отдельного файла содержимого: у каждого объекта есть поле `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 ```mermaid
flowchart LR flowchart LR
A[Команда пользователя] --> B[Путь /docs/a.txt] A[Команда пользователя] --> B[Путь /docs/a.txt]
@@ -84,6 +182,8 @@ flowchart LR
В начале виртуального диска находится суперблок. Он хранит сигнатуру файловой системы, размер диска, размер кластера, смещения служебных областей и индекс корневого каталога. В начале виртуального диска находится суперблок. Он хранит сигнатуру файловой системы, размер диска, размер кластера, смещения служебных областей и индекс корневого каталога.
> **Теоретическая основа.** Суперблок — это точка входа в структуру файловой системы. После подключения носителя именно по нему программа понимает, корректный ли перед ней формат и где искать остальные области. Потеря суперблока делает интерпретацию содержимого носителя затруднительной даже при сохранности пользовательских данных.
```c ```c
typedef struct { typedef struct {
uint32_t magic; uint32_t magic;
@@ -116,6 +216,17 @@ typedef struct {
Поле `size` хранит логический размер файла, видимый пользователю, а `stored_size` — фактическое количество байт на носителе. Это различие необходимо для поддержки сжатых файлов. Поле `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 ```mermaid
flowchart TB flowchart TB
S[Суперблок] --> B[Битовая карта] S[Суперблок] --> B[Битовая карта]
@@ -137,6 +248,8 @@ gcc -std=c11 -Wall -Wextra -pedantic simplefs.c -o simplefs
Интерактивная оболочка поддерживает историю команд: в обычном терминале стрелки **Up** и **Down** перемещаются по ранее введенным строкам. Основные команды названы в стиле Unix, чтобы работа с учебной файловой системой напоминала привычную командную строку. Интерактивная оболочка поддерживает историю команд: в обычном терминале стрелки **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`. Открытый файл использует буфер в памяти, поэтому чтение и запись записей произвольной длины выполняются без прямой работы пользователя с кластерами. На уровне программного интерфейса реализованы функции `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` создается бинарный файл нужного размера, вычисляются размеры служебных областей, резервируются занятые кластеры и создается корневой каталог `/`. При выполнении `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`, содержащее индекс родительского каталога. Для поиска элемента программа просматривает индексный файл и сравнивает имя и родителя. Каталог не хранит отдельный массив дочерних элементов. Вместо этого у каждого дескриптора есть поле `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` выполняет обратное преобразование и снова сохраняет файл без сжатия. Команда `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` временно считывает содержимое файлов, очищает карту размещения области данных и записывает файлы заново подряд. После операции используемые кластеры становятся компактно расположенными. Команда `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 ```text
format 512 format 512
mkdir /docs 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 эффективно не для всех данных | алгоритм выбран ради понятности реализации | | RLE эффективно не для всех данных | алгоритм выбран ради понятности реализации |
| Нет одновременного доступа нескольких процессов | учебная однопользовательская модель | | Нет одновременного доступа нескольких процессов | учебная однопользовательская модель |
> **Теоретическая основа.** Ограничения учебной реализации обычно связаны с заранее фиксированными структурами данных. Это упрощает код и делает поведение системы предсказуемым, но уменьшает масштабируемость по сравнению с промышленными файловыми системами, где размеры таблиц, число блоков и механизмы синхронизации более гибкие.
Большинство ограничений заданы непосредственно константами программы. Это делает реализацию прозрачной для учебных целей, но одновременно фиксирует максимальные размеры структур:
```c
#define MAX_NAME 32
#define MAX_PATH 256
#define MAX_ENTRIES 128
#define MAX_FILE_CLUSTERS 128
```
## Вывод ## Вывод
В ходе работы была реализована простая файловая система, работающая поверх бинарного файла как виртуального носителя. Реализация охватывает все основные уровни, указанные в задании: физическое размещение, учет свободных блоков, индексные метаданные, каталоги, операции над файлами и сервисные команды управления носителем. В ходе работы была реализована простая файловая система, работающая поверх бинарного файла как виртуального носителя. Реализация охватывает все основные уровни, указанные в задании: физическое размещение, учет свободных блоков, индексные метаданные, каталоги, операции над файлами и сервисные команды управления носителем.
Полученная программа остается достаточно простой для изучения начинающим разработчиком на C, но при этом показывает полный путь данных: от пользовательской команды до изменения битовой карты, индексного дескриптора и байтов в кластерах виртуального диска. Полученная программа остается достаточно простой для изучения начинающим разработчиком на 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;
}
```