update report
This commit is contained in:
+225
@@ -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;
|
||||
}
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user