Файловый ввод-вывод в NASM на платформе Linux x86-64 реализуется через системные вызовы (open, read, write, close) и требует понимания архитектуры VFS ядра. Это руководство покрывает теорию файловых дескрипторов, практику syscall и интеграцию с модулями I/O.
🌊 1. Теоретический фундамент
Философия Unix: Всё Есть Файл
Unix-подобные системы строятся на элегантной абстракции: любой источник или приёмник данных представляется файлом. Это означает единый интерфейс для работы с:
- Обычными файлами на диске (текст, бинарные данные)
- Директориями (список файлов)
- Устройствами (
/dev/sda— диск,/dev/null— чёрная дыра) - Каналами (pipes для межпроцессного взаимодействия)
- Сетевыми сокетами (TCP/UDP соединения)
Один набор системных вызовов (open, read, write, close) работает со всеми этими сущностями.
📘 Краткая справка: Основные syscall и соглашения о регистрах описаны в шпаргалке по NASM.
Virtual File System (VFS) и архитектура ядра
VFS — слой абстракции в ядре Linux, обеспечивающий унифицированный доступ к различным файловым системам и устройствам.
Архитектура:
┌──────────────────────────────────────────┐
│ Ваша программа │
│ (системные вызовы open/read/write) │
└─────────────────────┬────────────────────┘
│
▼
┌──────────────────────────────────────────┐
│ VFS (ядро) │
│ Маршрутизатор запросов к драйверам │
└──────┬──────────────┬──────────────┬─────┘
│ │ │
▼ ▼ ▼
┌────────┐ ┌─────────┐ ┌──────────┐
│ ext4 │ │ NFS │ │ /dev/sda │
│ драйвер│ │ драйвер │ │ драйвер │
└────────┘ └─────────┘ └──────────┘
│ │ │
▼ ▼ ▼
[Диск] [Сеть] [Устройство]Принцип работы:
- Программа вызывает
read(fd, buf, 100) - VFS определяет тип файла по дескриптору
- Вызывается соответствующая функция драйвера
- Данные возвращаются программе
Благодаря VFS программист не думает о физической реализации — работа с сетевым файлом NFS идентична локальному ext4.
Файловые дескрипторы и таблица FD
Файловый дескриптор (FD) — неотрицательное целое число, идентифицирующее открытый файл в процессе.
Структура доступа:
Процесс
└─> Таблица дескрипторов процесса
├─> FD 0 (stdin) ──┐
├─> FD 1 (stdout) ──┼──> Системная таблица открытых файлов
├─> FD 3 ──┘ └──> inode (метаданные файла)
└─> FD 4 ─────> Другой entryКлючевые свойства:
- Локальность: FD уникален только внутри процесса
- Последовательность: Ядро выдаёт наименьший свободный номер
- Стандартные потоки:
0— stdin (стандартный ввод)1— stdout (стандартный вывод)2— stderr (поток ошибок)
- Наследование: При
fork()дочерний процесс копирует все FD родителя
Практический пример: Перенаправление вывода
Когда вы пишете ./program > output.txt, shell выполняет:
1. close(1) # Освобождает FD 1
2. open("output.txt") # Получает FD 1 (наименьший свободный)
3. exec("./program") # Запускает программуПрограмма пишет в FD 1, “думая” что это экран, но физически данные идут в файл.
Демонстрация в коде
section .data
msg db "Hello, World!", 10
section .text
global _start
_start:
; Запись в FD 1 (stdout)
mov rax, 1
mov rdi, 1 ; Независимо от того, что под FD 1
lea rsi, [rel msg] ; программа просто пишет в этот дескриптор
mov rdx, 14
syscall
mov rax, 60
xor rdi, rdi
syscall
Запуск:
./program→ вывод на экран./program > file.txt→ вывод в файл
Код программы не меняется!
Системные вызовы и переход Ring 3 → Ring 0
Системный вызов — переключение процессора из пользовательского режима (Ring 3) в режим ядра (Ring 0) для выполнения привилегированных операций.
Механика перехода:
Ring 3 (User Mode) Ring 0 (Kernel Mode)
┌──────────────┐ ┌───────────────┐
│ │ syscall │ │
│ Ваша │───────────>│ Ядро Linux │
│ программа │ │ (драйверы, │
│ │ sysret │ VFS) │
│ │<───────────│ │
└──────────────┘ └───────────────┘
Огранич. Полный
доступ доступЧто происходит при syscall
- Сохранение контекста:
RIP → RCX,RFLAGS → R11 - Смена привилегий: Ring 3 → Ring 0
- Переход в ядро: Процессор прыгает на адрес обработчика (LSTAR)
- Валидация: Ядро проверяет права доступа и корректность аргументов
- Выполнение: Драйвер выполняет операцию
- Возврат:
sysretпереключает Ring 0 → Ring 3
Соглашение о Регистрах (x86-64 System V ABI)
| Регистр | Назначение |
|---|---|
rax |
Номер syscall (вход), результат (выход) |
rdi |
1-й аргумент |
rsi |
2-й аргумент |
rdx |
3-й аргумент |
r10 |
4-й аргумент (не rcx!) |
r8 |
5-й аргумент |
r9 |
6-й аргумент |
Важно: rcx и r11 уничтожаются ядром. rbx, rbp, r12-r15 сохраняются.
📖 Дополнительно: Полное описание System V ABI и ролей регистров см. в разделе «Архитектура регистров» шпаргалки по NASM.
- Выполняется в Ring 3
- Цена: 1-3 такта CPU
- Просто переход по адресу
- Без смены прав доступа
- Переход Ring 3 → Ring 0 → Ring 3
- Цена: сотни-тысячи тактов
- Полная смена контекста
- Валидация всех параметров
Права Доступа Unix
При создании файла с O_CREAT необходимо указать права доступа (mode) — битовую маску, определяющую операции для трёх категорий пользователей.
Структура прав:
Формат: 0[User][Group][Others]
Каждая позиция — сумма битов:
4 (read) + 2 (write) + 1 (execute) = 7 (все права)Примеры:
| Восьм. | Символ. | Описание |
|---|---|---|
0644 |
rw-r--r-- |
Владелец: чтение+запись, остальные: только чтение |
0755 |
rwxr-xr-x |
Владелец: всё, остальные: чтение+выполнение |
0600 |
rw------- |
Только владелец может читать и писать |
Механизм проверки: При каждом обращении к файлу ядро сравнивает UID (User ID) процесса с правами в inode файла и разрешает/запрещает операцию.
Буферизация
Системные вызовы дороги из-за переключения контекста. Буферизация — техника минимизации syscall путём накопления данных в памяти.
Задача:
Прочитать 4 КБ
Реализация:
mov rcx, 4096
.loop:
mov rax, 0
mov rdi, [fd]
lea rsi, [buffer]
mov rdx, 1 ; 1 байт!
syscall
loop .loop
Цена:
4096 системных вызовов
Задача:
Прочитать 4 КБ
Реализация:
mov rax, 0
mov rdi, [fd]
lea rsi, [buffer]
mov rdx, 4096 ; Весь блок
syscall
Цена:
1 системный вызов
Оптимальный размер буфера: 4096 байт (размер страницы памяти x86-64). Ядро работает с памятью страницами, выравнивание по этой границе даёт максимальную эффективность.
🔌 2. Практика системных вызовов
Таблица Системных Вызовов
| syscall | RAX | Описание | Основное применение |
|---|---|---|---|
open |
2 | Открыть файл | Получение файлового дескриптора |
read |
0 | Читать данные | Загрузка содержимого в память |
write |
1 | Записать данные | Сохранение данных на диск |
close |
3 | Закрыть файл | Освобождение дескриптора |
lseek |
8 | Изменить позицию | Произвольный доступ к данным |
📋 Справочник: Полная таблица номеров системных вызовов (
rax) и аргументов находится в «Шпаргалке NASM».
open — Получение Доступа к Файлу
Системный вызов open — это запрос у ядра на доступ к файлу. Если запрос одобрен, ядро возвращает файловый дескриптор.
Регистры:
rax = 2 ; Номер системного вызова
rdi = pathname ; Адрес строки с путём к файлу
rsi = flags ; Битовая маска флагов
rdx = mode ; Права доступа (только при создании)Возвращаемое значение:
rax ≥ 0— файловый дескриптор (успех)rax < 0— код ошибки (отрицательный errno)
Флаги Открытия
Режим доступа (обязателен, выбрать один):
O_RDONLY equ 0 ; Только чтение
O_WRONLY equ 1 ; Только запись
O_RDWR equ 2 ; Чтение и записьМодификаторы поведения (опциональны, комбинируются через OR):
O_CREAT equ 0o100 ; Создать файл, если не существует
O_EXCL equ 0o200 ; Ошибка, если файл уже существует (с O_CREAT)
O_TRUNC equ 0o1000 ; Обрезать файл до нуля при открытии
O_APPEND equ 0o2000 ; Все записи добавляются в конец файла⚠️ Распространённая ошибка: Забыть указать права доступа
rdxпри использованииO_CREATприведёт к созданию файла со случайными правами. Эта и другие частые ошибки разобраны в статье «Топ ошибок в NASM: Почему падает Segfault и неверные расчёты».
Типичные Комбинации
mov rax, 2
lea rdi, [rel filename]
mov rsi, 0 ; O_RDONLY
syscall
Поведение:
- Открыть файл для чтения
- Позиция на начало
- Ошибка, если файл не существует
mov rax, 2
lea rdi, [rel filename]
mov rsi, 0o101 ; O_WRONLY | O_CREAT
mov rdx, 0644o ; rw-r--r--
syscall
Поведение:
- Создать файл, если не существует
- Открыть для записи
- Установить права 0644
mov rax, 2
lea rdi, [rel filename]
mov rsi, 0o1101 ; O_WRONLY | O_CREAT | O_TRUNC
mov rdx, 0644o
syscall
Поведение:
- Создать, если нет
- Стереть содержимое, если есть
- Писать с начала
Аналог shell: > file.txt
mov rax, 2
lea rdi, [rel filename]
mov rsi, 0o2101 ; O_WRONLY | O_CREAT | O_APPEND
mov rdx, 0644o
syscall
Поведение:
- Создать, если нет
- Сохранить содержимое, если есть
- Писать в конец
Аналог shell: >> file.txt
🔗 Связь: Если имя файла нужно брать не из константы, а из аргументов командной строки (например,
./prog data.txt), изучите статью «Аргументы командной строки в NASM: Работа с argc и argv».
Специальный Случай: Атомарная Блокировка (O_EXCL)
Флаг O_EXCL в комбинации с O_CREAT превращает проверку существования и создание файла в одну неделимую (атомарную) операцию.
Без атомарности между проверкой и созданием есть “окно уязвимости”:
[ Процесс A ] [ Процесс B ]
│ │
1. Проверка: "Файл есть?" │
↳ Ответ: ❌ НЕТ │
│ │
│ 2. Проверка: "Файл есть?"
│ ↳ Ответ: ❌ НЕТ
┌─────────┴───────────────────────┴──────┐
│ ОПАСНАЯ ЗОНА (Оба считают первыми) │
└─────────┬───────────────────────┬──────┘
│ │
3. creat() -> ✅ 4. creat() -> ⚠️
Создал ПЕРЕЗАПИСАЛ!Ядро гарантирует, что никто не вклинится:
[ Процесс A ] [ Процесс B ]
│ │
1. open(O_CREAT | O_EXCL) │
│ │
↳ 🟢 АТОМАРНЫЙ УСПЕХ │
(Нет -> Создан, FD=3) │
│ │
│ 2. open(O_CREAT | O_EXCL)
│ │
│ ↳ 🔴 АТОМАРНЫЙ СБОЙ
▼ (Есть -> EEXIST)
Владеет ресурсом (Блокировка не получена)Реализация lock-файла
; ============================================================================
; lockfile_demo.asm
;
; Демонстрация реализации lock-файла для обеспечения эксклюзивного доступа.
; Использует атомарную операцию open() с флагами O_CREAT | O_EXCL для
; гарантии, что только один процесс может владеть блокировкой одновременно.
;
; АЛГОРИТМ:
; 1. Попытка атомарного создания lock-файла (open с O_CREAT | O_EXCL)
; 2. Если успех:
; - Вывод сообщения о захвате блокировки
; - Выполнение критической секции (здесь: заглушка)
; - Освобождение блокировки (close + unlink)
; - Завершение программы
; 3. Если файл уже существует (EEXIST):
; - Вывод сообщения о занятой блокировке
; - Ожидание 1 секунда (nanosleep)
; - Повторная попытка захвата (goto шаг 1)
; 4. Если другая ошибка - немедленное завершение
;
; ПРИМЕНЕНИЕ:
; • Обеспечение единственного экземпляра программы (singleton)
; • Защита критических секций между процессами
; • Координация доступа к разделяемым ресурсам
;
; ОГРАНИЧЕНИЯ:
; ⚠️ Если процесс аварийно завершится (SIGKILL, crash), lock-файл
; останется и потребует ручного удаления!
; ✅ Для production использовать: PID в файле + проверку существования
; процесса через kill(pid, 0) или /proc/[pid]
;
; АТОМАРНОСТЬ:
; Операция open(O_CREAT | O_EXCL) выполняется ядром атомарно:
; - Если файл НЕ существует → создаётся и возвращается FD
; - Если файл существует → возвращается -EEXIST (не создаётся)
; - Между проверкой и созданием НЕТ временного окна (race condition)
;
; ТЕСТИРОВАНИЕ:
; Терминал 1: ./lockfile_demo # Захватит блокировку
; Терминал 2: ./lockfile_demo # Будет ждать освобождения
; ============================================================================
default rel ; Использовать RIP-relative адресацию по умолчанию
section .data
; Путь к lock-файлу (в /tmp для автоочистки при перезагрузке)
lockfile db "/tmp/myapp.lock", 0
; Сообщения о состоянии блокировки
msg_acquired db "Lock acquired", 10
msg_acquired_len equ $ - msg_acquired
msg_busy db "Lock is busy, waiting...", 10
msg_busy_len equ $ - msg_busy
section .bss
lock_fd resq 1 ; Файловый дескриптор lock-файла
timespec resb 16 ; Структура для nanosleep (2 x qword)
section .text
global _start
; ============================================================================
; ОСНОВНАЯ ПРОГРАММА
; ============================================================================
_start:
; --- Инициализация структуры timespec (1 секунда) ---
mov qword [timespec], 1 ; tv_sec = 1 секунда
mov qword [timespec + 8], 0 ; tv_nsec = 0 наносекунд
; --- Попытка захвата блокировки (повторяется при занятости) ---
.try_acquire:
; syscall: open(filename, flags, mode)
; Атомарная попытка создания lock-файла
mov rax, 2
lea rdi, [lockfile]
mov rsi, 0o301 ; Флаги: O_WRONLY | O_CREAT | O_EXCL
; O_WRONLY (0o001 = 1) - только запись
; O_CREAT (0o100 = 64) - создать если нет
; O_EXCL (0o200 = 128) - ошибка если есть
; Сумма: 1 + 64 + 128 = 193 = 0o301
mov rdx, 0o600 ; Права доступа: 0600 (rw-------)
; Только владелец может читать/писать
syscall
; Проверка результата операции
test rax, rax ; Проверка: rax < 0? (ошибка)
js .check_error ; Если да - анализ типа ошибки
; --- Блокировка успешно захвачена ---
mov [lock_fd], rax ; Сохранение файлового дескриптора
; Вывод сообщения о захвате блокировки
; syscall: write(STDOUT, msg, len)
mov rax, 1
mov rdi, 1
lea rsi, [msg_acquired]
mov rdx, msg_acquired_len
syscall
; ============================================================================
; КРИТИЧЕСКАЯ СЕКЦИЯ
; ============================================================================
; Здесь выполняется работа, требующая эксклюзивного доступа к ресурсу.
; Примеры:
; - Обработка общего файла данных
; - Запись в лог без гонок (race conditions)
; - Обновление shared memory
;
; В этом демо добавим sleep 5 секунд, чтобы успеть запустить второй процесс
; ============================================================================
; === НАЧАЛО КРИТИЧЕСКОЙ СЕКЦИИ ===
; Ждём 5 секунд (для демонстрации блокировки)
mov qword [timespec], 5 ; tv_sec = 5 секунд
mov rax, 35 ; sys_nanosleep
lea rdi, [timespec]
xor rsi, rsi
syscall
; === КОНЕЦ КРИТИЧЕСКОЙ СЕКЦИИ ===
; --- Освобождение блокировки ---
; Шаг 1: Закрытие файлового дескриптора
; syscall: close(fd)
mov rax, 3
mov rdi, [lock_fd]
syscall
; Шаг 2: Удаление lock-файла из файловой системы
; syscall: unlink(filename)
mov rax, 87
lea rdi, [lockfile]
syscall
; Переход к завершению программы
jmp .exit
; ============================================================================
; ОБРАБОТКА ОШИБОК ЗАХВАТА БЛОКИРОВКИ
; ============================================================================
.check_error:
; Проверка типа ошибки: EEXIST (файл уже существует) или другая
cmp rax, -17 ; EEXIST = 17, возвращается как -17
jne .exit ; Если другая ошибка - немедленный выход
; --- Блокировка занята другим процессом ---
; Вывод информационного сообщения
; syscall: write(stdout, string, length)
mov rax, 1
mov rdi, 1
lea rsi, [msg_busy]
mov rdx, msg_busy_len
syscall
; Ожидание перед повторной попыткой
; syscall: nanosleep(req, rem)
mov qword [timespec], 1 ; tv_sec = 1 секунда (восстановить)
mov rax, 35
lea rdi, [timespec]
xor rsi, rsi ; rem = NULL (не нужно остаток времени)
syscall
; Повторная попытка захвата блокировки
jmp .try_acquire
; ============================================================================
; ЗАВЕРШЕНИЕ ПРОГРАММЫ
; ============================================================================
.exit:
; syscall: exit(0)
mov rax, 60
xor rdi, rdi
syscall
Применение: Гарантия, что только один экземпляр программы работает одновременно (демоны, cron-задачи).
read — Чтение Данных из Файла
Регистры:
rax = 0 ; Номер системного вызова
rdi = fd ; Файловый дескриптор
rsi = buffer ; Адрес буфера для размещения данных
rdx = count ; Максимальное количество байтВозвращаемое значение:
rax > 0— количество реально прочитанных байтrax = 0— достигнут конец файла (EOF)rax < 0— ошибка (отрицательный errno)
Критическая Особенность: Частичное Чтение
read НЕ гарантирует чтение всех запрошенных байт. Это происходит из-за:
- Достижения конца файла
- Прерывания сигналом (EINTR)
- Особенностей устройства (терминалы, сокеты)
; Предполагаем полное чтение
mov rax, 0
mov rdi, [fd]
lea rsi, [rel buffer]
mov rdx, 1000
syscall
; rax может быть: 100, 500, 1000
; или ЛЮБОЕ другое значение ≤ 1000!read_exact:
; rdi=fd, rsi=buffer, rdx=count
mov r12, rsi ; текущий указатель
mov r13, rdx ; осталось
.loop:
mov rax, 0
mov rdi, [fd]
mov rsi, r12
mov rdx, r13
syscall
test rax, rax
jle .error ; ≤ 0: ошибка/EOF
add r12, rax ; сдвиг
sub r13, rax ; уменьшение
jnz .loop ; повтор
ret
.error:
retФайловая позиция: После read позиция автоматически сдвигается на количество прочитанных байт.
write — Запись Данных в Файл
Регистры:
rax = 1 ; Номер системного вызова
rdi = fd ; Файловый дескриптор
rsi = buffer ; Адрес данных для записи
rdx = count ; Количество байт для записиВозвращаемое значение:
rax > 0— количество реально записанных байтrax < 0— ошибка (отрицательный errno)
Особенности:
- Частичная запись — аналогично
read, может записать меньше данных - Буферизация ядра — данные попадают в кеш страниц, физическая запись позже
- Синхронизация — используйте
fsync(fd)для гарантированного сохранения на диск
lseek — Управление Позицией в Файле
Регистры:
rax = 8 ; Номер системного вызова
rdi = fd ; Файловый дескриптор
rsi = offset ; Смещение (может быть отрицательным)
rdx = whence ; Точка отсчётаТочки отсчёта:
SEEK_SET equ 0 ; От начала файла
SEEK_CUR equ 1 ; От текущей позиции
SEEK_END equ 2 ; От конца файлаВозвращаемое значение: rax = новая позиция от начала файла (или < 0 при ошибке)
get_file_size:
; Сохранить позицию
mov rax, 8
mov rdi, [fd]
xor rsi, rsi
mov rdx, 1 ; SEEK_CUR
syscall
push rax
; Перейти в конец
mov rax, 8
mov rdi, [fd]
xor rsi, rsi
mov rdx, 2 ; SEEK_END
syscall
mov rbx, rax ; размер
; Восстановить
pop rsi
mov rax, 8
mov rdi, [fd]
xor rdx, rdx ; SEEK_SET
syscall
mov rax, rbx
ret; RECORD_SIZE должен быть определён
seek_to_record:
; rdi=fd, rsi=record_index
push rbx
mov rbx, rsi
imul rbx, RECORD_SIZE
mov rax, 8
mov rdi, [fd]
mov rsi, rbx
xor rdx, rdx ; SEEK_SET
syscall
pop rbx
ret
; Использование:
; mov rdi, [fd]
; mov rsi, 42 ; 42-я запись
; call seek_to_record
; ; Теперь можно читатьclose — Освобождение Файлового Дескриптора
Регистры:
rax = 3 ; Номер системного вызова
rdi = fd ; Файловый дескрипторВозвращаемое значение: rax = 0 (успех) или < 0 (ошибка)
Что происходит:
- Освобождение номера FD
- Декремент счётчика ссылок
- Если последний FD: сброс буферов на диск
bad_function:
mov rax, 2
lea rdi, [rel file]
xor rsi, rsi
syscall
mov [fd], rax
; Работа с файлом
ret ; ЗАБЫЛИ ЗАКРЫТЬ!
После 1000+ вызовов процесс исчерпает лимит FD
good_function:
mov rax, 2
lea rdi, [rel file]
xor rsi, rsi
syscall
test rax, rax
js .error
mov [fd], rax
; Работа
mov rax, 3 ; ВСЕГДА закрываем
mov rdi, [fd]
syscall
ret
.error:
ret
Детерминированное освобождение ресурсов
🧩 3. Паттерн fd-as-parameter и Dependency Injection
Почему “файл внутри функции” — антипаттерн
read_number_from_file:
; Функция сама открывает файл
mov rax, 2
lea rdi, [rel filename]
xor rsi, rsi
syscall
; Жёстко привязана к одному файлу
; Нельзя подставить stdin или пайп
; Невозможно тестировать
Проблемы:
- Жёсткая привязка к конкретному файлу
- Нельзя подставить пайп, stdin или сокет
- Невозможно тестировать изолированно
- Утечка FD при ошибке внутри функции
read_number_from_stream:
; Вход: rdi = fd (файловый дескриптор)
; Функция НЕ открывает, просто читает
; Работает с любым источником:
; - файлом (fd = 3)
; - stdin (fd = 0)
; - пайпом (fd = 0 при перенаправлении)
; - сокетом (fd = 5)
Преимущества:
- Универсальность: один код для всех источников
- Тестируемость: подставить mock-fd в тестах
- Гибкость: программист управляет ресурсом
- Безопасность: вызывающий отвечает за close()
Принцип Dependency Injection:
- Зависимость (файловый дескриптор) передаётся извне, а не создаётся внутри функции
- Функция становится тестируемой: можно подставить любой источник данных
- Функция становится переиспользуемой: один код для файлов, пайпов, сокетов
🔗 См. также: Концепция передачи параметров через регистры подробно разобрана в руководстве «Связь C и NASM: Конвенции вызовов (ABI) и Оптимизация», где показаны практические примеры использования calling conventions.
Как и зачем передавать fd через параметры
Регистровое соглашение (x86-64 System V):
| Регистр | Назначение |
|---|---|
rdi |
1-й параметр (обычно fd) |
rsi |
2-й параметр (обычно адрес буфера) |
rdx |
3-й параметр (обычно размер) |
rax |
Возвращаемое значение |
Типичная сигнатура читающей функции:
; read_int16_stream(rdi=fd, rsi=&result) → rax
;
; Вход:
; rdi = файловый дескриптор
; rsi = адрес переменной для результата
;
; Выход:
; rax > 0: успех, число прочитано
; rax = 0: EOF
; rax < 0: ошибка (отрицательный код)Унификация для файлов, пайпов, stdin/out и тестов
┌─────────────────────────────────────────────────────────────┐
│ ПРИМЕР 1: Чтение из файла │
├─────────────────────────────────────────────────────────────┤
│ mov rax, 2 ; sys_open │
│ lea rdi, [filename] │
│ xor rsi, rsi ; O_RDONLY │
│ syscall │
│ mov r15, rax ; r15 = fd │
│ │
│ mov rdi, r15 ; Передаём fd в функцию │
│ lea rsi, [result] │
│ call read_int16_stream ; ← Читаем из файла! │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ ПРИМЕР 2: Чтение из пайпа │
├─────────────────────────────────────────────────────────────┤
│ ; В тесте: echo "12345" | ./program │
│ │
│ xor rdi, rdi ; fd = 0 (stdin = пайп) │
│ lea rsi, [result] │
│ call read_int16_stream ; ← Читаем из пайпа! │
│ │
│ ; Программа не знает, откуда данные — stdin или пайп │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ ПРИМЕР 3: Автоматическое тестирование │
├─────────────────────────────────────────────────────────────┤
│ ; test_runner.sh: │
│ echo "100 200 300" > test_input.txt │
│ ./program < test_input.txt > test_output.txt │
│ diff test_output.txt expected_output.txt │
│ │
│ ; Программа использует read_int16_stream с fd=0 │
│ ; Shell подменяет stdin на файл — программа не меняется! │
└─────────────────────────────────────────────────────────────┘📌 Заметка: В примерах выше
filename— строковая константа в секции.data. Если имя файла передаётся при запуске программы (например,./program data.txt), используйте доступ кargv— подробности в статье «Аргументы командной строки в NASM: Работа с argc и argv».
Принцип Dependency Injection:
- Зависимость (файловый дескриптор) передаётся извне, а не создаётся внутри функции
- Функция становится тестируемой: можно подставить любой источник данных
- Функция становится переиспользуемой: один код для файлов, пайпов, сокетов
🎓 4. Интеграция с модулями ввода-вывода
Все три модуля (io_signed.asm, io_unsigned.asm, io_float.asm) изначально реализуют двухслойную архитектуру (Layer 1: парсинг, Layer 2: интерактивный I/O). Полные исходники этих модулей доступны в разделе «📦 2. Исходники модулей» статьи «Модули ввода-вывода NASM x86-64 без libc».
Для добавления поддержки файлового ввода-вывода (Layer 3) скопируйте готовый код из соответствующего <details>-блока ниже и вставьте его в свой модуль.
Расширение io_signed.asm
Модуль для знаковых 16-битных целых чисел (int16_t). После интеграции Layer 3 вы сможете читать числа из файлов и записывать 32-битные результаты.
📄 Полный код расширения Layer 3 для io_signed.asm
Шаг 1: Добавьте в секцию .bss
; Буферы файлового режима (Слой 3)
io_file_buffer resb 32 ; Буфер для файловых операций
io_file_char_buf resb 1 ; Буфер для одного символа
io_file_stream_buffer resb 128 ; Буфер потокового чтения
io_file_stream_pos resq 1 ; Позиция в потоковом буфере
io_file_stream_size resq 1 ; Размер данных в потоке
Шаг 2: Добавьте в секцию .text (после существующих global)
global read_int16_from_fd ; Слой 3: чтение из файла
global read_int16_stream ; Слой 3: потоковое чтение
global write_int32_to_fd ; Слой 3: запись в файл
Шаг 3: Вставьте в конец файла (после Layer 2)
; ============================================================================
; СЛОЙ 3: ФАЙЛОВЫЙ ВВОД-ВЫВОД
; ============================================================================
; ----------------------------------------------------------------------------
; read_int16_from_fd
;
; Читает одно знаковое 16-битное целое число из файлового дескриптора.
; Подходит для однократного чтения из файлов.
;
; ПРИМЕНЕНИЕ:
; • Чтение конфигурационных файлов
; • Однократная загрузка значений
; • Простые случаи без необходимости потоковой обработки
;
; ОГРАНИЧЕНИЯ:
; • Максимальная длина строки: 31 символ
; • Не оптимально для множественных чтений (используйте read_int16_stream)
;
; ДИАПАЗОН: -32768 до 32767
;
; ПРИМЕРЫ ИСПОЛЬЗОВАНИЯ:
; C-style:
; int fd = open("config.txt", O_RDONLY);
; int16_t value;
; ssize_t result = read_int16_from_fd(fd, &value);
; if (result > 0) {
; // value содержит число
; }
; close(fd);
;
; Assembly:
; ; Открыть файл
; mov rax, 2 ; sys_open
; lea rdi, [filename]
; xor rsi, rsi ; O_RDONLY
; syscall
; mov r15, rax ; Сохранить FD
;
; ; Прочитать число
; mov rdi, r15
; lea rsi, [my_value]
; call read_int16_from_fd
;
; ; Закрыть файл
; mov rax, 3 ; sys_close
; mov rdi, r15
; syscall
;
; @param rdi Файловый дескриптор (int fd)
; rsi Адрес переменной для результата (int16_t *)
; @return rax >0 = количество прочитанных байт
; 0 = EOF (конец файла)
; -1 = ошибка I/O (чтение из файла)
; -2 = ошибка формата (некорректное число)
; -3 = переполнение (выход за границы int16_t)
; -4 = переполнение буфера (строка > 30 символов)
; @uses rbx, rdx, r12, r13
; @calls parse_int16
;
; @see read_int16_stream - для множественных чтений
; @see parse_int16 - базовая функция парсинга
; ----------------------------------------------------------------------------
read_int16_from_fd:
push rbp
mov rbp, rsp
push rbx
push r12
push r13
mov r12, rdi ; Сохранение файлового дескриптора
mov r13, rsi ; Сохранение адреса результата
; --- Чтение данных из файла ---
; syscall: read(fd, buffer, count)
xor rax, rax
mov rdi, r12
lea rsi, [io_file_buffer] ; Адрес буфера
mov rdx, 31
syscall
test rax, rax ; Проверка результата
js .io_error ; rax < 0 → ошибка I/O
jz .eof ; rax = 0 → EOF (конец файла)
mov rbx, rax ; Сохранение количества прочитанных байт
; --- Добавление null-терминатора ---
lea rdx, [io_file_buffer] ; Загрузка базового адреса буфера
mov byte [rdx + rax], 0 ; Установка null-терминатора после данных
; --- Парсинг прочитанной строки ---
lea rdi, [io_file_buffer] ; Адрес строки для парсинга
call parse_int16 ; Вызов функции парсинга
test rdx, rdx ; Проверка кода ошибки (0 = успех)
jnz .parse_error ; rdx != 0 → ошибка парсинга
; --- Сохранение результата ---
mov word [r13], ax ; Сохранение распарсенного значения (16 бит)
mov rax, rbx ; Возврат количества прочитанных байт
jmp .return
; --- Обработка ошибок ---
.io_error:
mov rax, -1 ; Код ошибки I/O
jmp .return
.eof:
xor rax, rax ; Код EOF (0)
jmp .return
.parse_error:
; Преобразование кода ошибки парсинга: 1,2,3 → -2,-3,-4
mov rax, rdx ; Копирование кода ошибки
neg rax ; Инвертирование знака
dec rax ; Декремент (1→-2, 2→-3, 3→-4)
.return:
pop r13
pop r12
pop rbx
pop rbp
ret
; ----------------------------------------------------------------------------
; read_int16_stream
;
; Читает одно знаковое 16-битное целое число из потока с буферизацией.
; Идеально для последовательного чтения нескольких чисел из файла.
; Пропускает ведущие whitespace символы (пробелы, табы, LF, CR).
;
; ПРИМЕНЕНИЕ:
; • Парсинг файлов с множественными значениями
; • Обработка потоков данных
; • Эффективное чтение из больших файлов
;
; ОСОБЕННОСТИ:
; • Внутренняя буферизация 128 байт минимизирует syscall
; • Автоматический пропуск whitespace между числами
; • Сохраняет позицию для следующего вызова
;
; ДИАПАЗОН: -32768 до 32767
;
; ПРИМЕРЫ ИСПОЛЬЗОВАНИЯ:
; Чтение файла с числами:
; numbers.txt: "100 -200 300\n400 500"
;
; int fd = open("numbers.txt", O_RDONLY);
; int16_t values[5];
; for (int i = 0; i < 5; i++) {
; int result = read_int16_stream(fd, &values[i]);
; if (result != 1) break;
; }
; close(fd);
;
; Assembly (читаем 3 числа):
; mov rax, 2
; lea rdi, [filename]
; xor rsi, rsi
; syscall
; mov r15, rax
;
; ; Чтение первого числа
; mov rdi, r15
; lea rsi, [num1]
; call read_int16_stream
;
; ; Чтение второго числа
; mov rdi, r15
; lea rsi, [num2]
; call read_int16_stream
;
; ; Чтение третьего числа
; mov rdi, r15
; lea rsi, [num3]
; call read_int16_stream
;
; mov rax, 3
; mov rdi, r15
; syscall
;
; @param rdi Файловый дескриптор (int fd)
; rsi Адрес переменной для результата (int16_t *)
; @return rax 1 = успех (число прочитано)
; 0 = EOF (конец файла)
; -1 = ошибка I/O
; -2 = ошибка формата
; -3 = переполнение
; -4 = переполнение буфера
; @uses rbx, rdx, r12, r13, r14
; @calls parse_int16, .read_char (внутренняя)
;
; @complexity O(n), где n - длина числа
; @memory Использует статический буфер 128 байт
;
; @see read_int16_from_fd - для однократного чтения
; @see .read_char - внутренняя функция буферизованного чтения символа
; ----------------------------------------------------------------------------
read_int16_stream:
push rbp
mov rbp, rsp
push rbx
push r12
push r13
push r14
mov r12, rdi ; Сохранение файлового дескриптора
mov r14, rsi ; Сохранение адреса результата
lea r13, [io_file_buffer] ; r13 = база для записи числа
xor rbx, rbx ; rbx = позиция записи (начинаем с 0)
; --- Пропуск начальных whitespace символов ---
.skip_whitespace:
call .read_char ; Чтение одного символа
test rax, rax ; Проверка результата
jle .handle_read_error ; Если <= 0 (ошибка или EOF) - обработка
movzx rax, byte [io_file_char_buf] ; Загрузка прочитанного символа
; Проверка whitespace: ' ', '\t', '\n', '\r'
cmp al, ' ' ; Проверка: пробел?
je .skip_whitespace ; Если да - пропускаем
cmp al, 10 ; Проверка: LF?
je .skip_whitespace ; Если да - пропускаем
cmp al, 9 ; Проверка: TAB?
je .skip_whitespace ; Если да - пропускаем
cmp al, 13 ; Проверка: CR?
je .skip_whitespace ; Если да - пропускаем
; --- Первый значащий символ - начало числа ---
mov byte [r13 + rbx], al ; Сохранение первого символа числа
inc rbx ; Увеличение позиции записи
; --- Чтение остальных символов числа ---
.read_number:
cmp rbx, 30 ; Проверка: буфер переполнен?
jge .buffer_overflow ; Если да - ошибка переполнения буфера
call .read_char ; Чтение следующего символа
test rax, rax ; Проверка результата
jle .handle_read_error_in_number ; Если <= 0 - обработка
movzx rax, byte [io_file_char_buf] ; Загрузка прочитанного символа
; Проверка разделителей (конец числа)
cmp al, ' ' ; Проверка: пробел?
je .parse ; Если да - конец числа, парсим
cmp al, 10 ; Проверка: LF?
je .parse ; Если да - конец числа, парсим
cmp al, 9 ; Проверка: TAB?
je .parse ; Если да - конец числа, парсим
cmp al, 13 ; Проверка: CR?
je .parse ; Если да - конец числа, парсим
; --- Продолжение числа (цифра или знак) ---
mov byte [r13 + rbx], al ; Сохранение символа
inc rbx ; Увеличение позиции записи
jmp .read_number ; Продолжение чтения
; --- Парсинг собранного числа ---
.parse:
mov byte [r13 + rbx], 0 ; Установка null-терминатора
lea rdi, [io_file_buffer] ; Адрес строки для парсинга
call parse_int16 ; Вызов функции парсинга
test rdx, rdx ; Проверка кода ошибки
jnz .parse_error ; Если ошибка - обработка
; --- Сохранение результата ---
mov word [r14], ax ; Сохранение распарсенного значения (16 бит)
mov rax, 1 ; Код успеха
jmp .ret
; --- Обработка ошибок чтения (до начала числа) ---
.handle_read_error:
test rax, rax ; Проверка кода возврата
jz .eof ; rax = 0 → EOF
mov rax, -1 ; rax < 0 → I/O error
jmp .ret
; --- Обработка ошибок чтения (внутри числа) ---
.handle_read_error_in_number:
test rax, rax ; Проверка кода возврата
js .io_error ; rax < 0 → I/O error
jmp .parse ; rax = 0 → EOF, но есть данные для парсинга
; --- Коды возврата ---
.eof:
xor rax, rax ; Код EOF (0)
jmp .ret
.io_error:
mov rax, -1 ; Код ошибки I/O
jmp .ret
.buffer_overflow:
mov rax, -4 ; Код ошибки переполнения буфера
jmp .ret
.parse_error:
; Преобразование кода ошибки парсинга: 1,2,3 → -2,-3,-4
mov rax, rdx ; Копирование кода ошибки
neg rax ; Инвертирование знака
dec rax ; Декремент
.ret:
pop r14
pop r13
pop r12
pop rbx
pop rbp
ret
; ----------------------------------------------------------------------------
; .read_char (внутренняя функция read_int16_stream)
;
; Читает один символ из файла с использованием буферизации.
; Минимизирует количество системных вызовов read.
;
; АЛГОРИТМ:
; 1. Проверка наличия данных в буфере
; 2. Если буфер пуст: системный вызов read() на 128 байт
; 3. Извлечение одного символа из буфера
; 4. Обновление позиции и размера
;
; БУФЕРИЗАЦИЯ:
; Буфер: io_file_stream_buffer (128 байт)
; Позиция: io_file_stream_pos
; Размер: io_file_stream_size
;
; @param r12 Файловый дескриптор (сохранён вызывающей функцией)
; @return rax 1 = успех (символ в io_file_char_buf)
; 0 = EOF
; <0 = ошибка I/O
; @uses rcx, rdx, rdi, rsi (сохраняются и восстанавливаются)
;
; @complexity O(1) амортизированная (O(n/128) syscall для n символов)
; @internal Используется только внутри read_int16_stream
; ----------------------------------------------------------------------------
.read_char:
push rdi
push rsi
push rdx
push rcx
; --- Проверка наличия данных в буфере ---
mov rax, [io_file_stream_size] ; Загрузка количества доступных байт
test rax, rax ; Проверка: есть ли данные в буфере?
jnz .has_buffered ; Если да - извлечение из буфера
; --- Буфер пуст - системный вызов ---
; syscall: read(fd, buffer, count)
xor rax, rax
mov rdi, r12
lea rsi, [io_file_stream_buffer]
mov rdx, 128
syscall
test rax, rax ; Проверка результата
jle .read_done ; Если <= 0 (EOF или ошибка) - завершение
mov [io_file_stream_size], rax ; Сохранение количества прочитанных байт
mov qword [io_file_stream_pos], 0 ; Сброс позиции чтения
; --- Извлечение символа из буфера ---
.has_buffered:
mov rcx, [io_file_stream_pos] ; Загрузка текущей позиции чтения
mov al, byte [io_file_stream_buffer + rcx] ; Чтение символа из буфера
mov byte [io_file_char_buf], al ; Сохранение символа в выходной буфер
inc qword [io_file_stream_pos] ; Увеличение позиции чтения
dec qword [io_file_stream_size] ; Уменьшение количества доступных байт
mov rax, 1 ; Код успешного чтения
.read_done:
pop rcx
pop rdx
pop rsi
pop rdi
ret
; ----------------------------------------------------------------------------
; write_int32_to_fd
;
; Записывает знаковое 32-битное целое число в файловый дескриптор
; в текстовом виде (десятичный формат) с завершающим символом LF.
; Поддерживает отрицательные числа.
;
; ПРИМЕНЕНИЕ:
; • Запись результатов вычислений в файл
; • Экспорт данных в текстовом формате
; • Логирование числовых значений
;
; ДИАПАЗОН: -2147483648 до 2147483647 (int32_t)
; ФОРМАТ ЗАПИСИ: "<число>\n"
;
; ПРИМЕРЫ:
; write_int32_to_fd(3, 0) → "0\n"
; write_int32_to_fd(3, 12345) → "12345\n"
; write_int32_to_fd(3, -98765) → "-98765\n"
;
; ПРИМЕРЫ ИСПОЛЬЗОВАНИЯ:
; C-style:
; int fd = open("output.txt", O_WRONLY | O_CREAT, 0644);
; write_int32_to_fd(fd, -12345);
; write_int32_to_fd(fd, 67890);
; close(fd);
; // Файл содержит: "-12345\n67890\n"
;
; Assembly:
; ; Открыть файл для записи
; mov rax, 2 ; sys_open
; lea rdi, [filename]
; mov rsi, 0x241 ; O_WRONLY | O_CREAT
; mov rdx, 0x1A4 ; 0644
; syscall
; mov r15, rax ; Сохранить FD
;
; ; Записать число
; mov rdi, r15
; mov esi, -12345
; call write_int32_to_fd
;
; ; Закрыть файл
; mov rax, 3 ; sys_close
; mov rdi, r15
; syscall
;
; @param rdi Файловый дескриптор (int fd)
; esi Значение для записи (int32_t)
; @return rax Количество записанных байт (или код ошибки от write)
; @uses rbx, rdx, r12, r13, r14
;
; @complexity O(log₁₀(n)), где n - абсолютное значение числа
; @memory O(1), использует фиксированный буфер 32 байта
; ----------------------------------------------------------------------------
write_int32_to_fd:
push rbp
mov rbp, rsp
push rbx
push r12
push r13
push r14
mov r14, rdi ; Сохранение файлового дескриптора
movsxd rax, esi ; Знаковое расширение int32 → int64
; --- Формирование строки справа налево в io_file_buffer ---
lea r13, [io_file_buffer + 31] ; r13 = указатель на конец буфера
mov byte [r13], 10 ; Установка LF (символ новой строки) в конце
dec r13 ; Смещение указателя назад
; --- Специальная обработка нуля ---
test rax, rax ; Проверка: число равно нулю?
jnz .check_sign ; Если нет - обработка знака
mov byte [r13], '0' ; Запись символа '0'
jmp .write ; Переход к записи в файл
; --- Обработка знака ---
.check_sign:
xor rbx, rbx ; rbx = флаг знака (0 по умолчанию)
test rax, rax ; Проверка знака числа (установка флагов)
jge .convert ; Если >= 0 - переход к конвертации
neg rax ; Взятие модуля (двухкомплементное отрицание)
mov rbx, 1 ; Установка флага отрицательного числа
; --- Конвертация числа в строку (справа налево) ---
; Инвариант цикла: r13 указывает на следующую позицию для записи
.convert:
dec r13 ; Смещение указателя назад
xor rdx, rdx ; Обнуление rdx перед делением
mov r12, 10 ; Делитель = 10 (основание системы счисления)
div r12 ; rax = rax / 10, rdx = rax % 10 (остаток)
add dl, '0' ; Преобразование цифры (0-9) в ASCII ('0'-'9')
mov [r13], dl ; Запись ASCII-символа в буфер
test rax, rax ; Проверка: остались ли ещё цифры?
jnz .convert ; Если да - продолжение конвертации
; --- Добавление знака минус ---
test rbx, rbx ; Проверка флага отрицательного числа
jz .write ; Если положительное - переход к записи
dec r13 ; Смещение указателя назад
mov byte [r13], '-' ; Запись символа минуса
; --- Запись строки в файл ---
.write:
; syscall: write(fd, string, length)
mov rax, 1
mov rdi, r14
mov rsi, r13 ; Адрес начала строки
lea rdx, [io_file_buffer + 32] ; Адрес конца буфера
sub rdx, r13 ; Вычисление длины: конец - начало
syscall
pop r14
pop r13
pop r12
pop rbx
pop rbp
ret
Расширение io_unsigned.asm
Модуль для беззнаковых 16-битных целых чисел (uint16_t).
📄 Полный код расширения Layer 3 для io_unsigned.asm
Шаг 1: Добавьте в секцию .bss
; Буферы файлового режима (Слой 3)
io_file_buffer resb 32 ; Буфер для файловых операций
io_file_char_buf resb 1 ; Буфер для одного символа
io_file_stream_buffer resb 128 ; Буфер потокового чтения
io_file_stream_pos resq 1 ; Позиция в потоковом буфере
io_file_stream_size resq 1 ; Размер данных в потоке
Шаг 2: Добавьте в секцию .text (после существующих global)
global read_uint16_from_fd ; Слой 3: чтение из файла
global read_uint16_stream ; Слой 3: потоковое чтение
global write_uint32_to_fd ; Слой 3: запись в файл
Шаг 3: Вставьте в конец файла (после Layer 2)
; ============================================================================
; СЛОЙ 3: ФАЙЛОВЫЙ ВВОД-ВЫВОД
; ============================================================================
; ----------------------------------------------------------------------------
; read_uint16_from_fd
;
; Читает одно беззнаковое 16-битное целое число из файлового дескриптора.
; Подходит для однократного чтения из файлов.
;
; ПРИМЕНЕНИЕ:
; • Чтение конфигурационных файлов
; • Однократная загрузка значений
; • Простые случаи без необходимости потоковой обработки
;
; ОГРАНИЧЕНИЯ:
; • Максимальная длина строки: 31 символ
; • Не оптимально для множественных чтений (используйте read_uint16_stream)
;
; ДИАПАЗОН: 0 до 65535
;
; ПРИМЕРЫ ИСПОЛЬЗОВАНИЯ:
; C-style:
; int fd = open("config.txt", O_RDONLY);
; uint16_t value;
; ssize_t result = read_uint16_from_fd(fd, &value);
; if (result > 0) {
; // value содержит число
; }
; close(fd);
;
; Assembly:
; ; Открыть файл
; mov rax, 2 ; sys_open
; lea rdi, [filename]
; xor rsi, rsi ; O_RDONLY
; syscall
; mov r15, rax ; Сохранить FD
;
; ; Прочитать число
; mov rdi, r15
; lea rsi, [my_value]
; call read_uint16_from_fd
;
; ; Закрыть файл
; mov rax, 3 ; sys_close
; mov rdi, r15
; syscall
;
; @param rdi Файловый дескриптор (int fd)
; rsi Адрес переменной для результата (uint16_t *)
; @return rax >0 = количество прочитанных байт
; 0 = EOF (конец файла)
; -1 = ошибка I/O (чтение из файла)
; -2 = ошибка формата (некорректное число)
; -3 = переполнение (выход за границы uint16_t)
; -4 = переполнение буфера (строка > 30 символов)
; @uses rbx, rdx, r12, r13
; @calls parse_uint16
;
; @see read_uint16_stream - для множественных чтений
; @see parse_uint16 - базовая функция парсинга
; ----------------------------------------------------------------------------
read_uint16_from_fd:
push rbp
mov rbp, rsp
push rbx
push r12
push r13
mov r12, rdi ; Сохранение файлового дескриптора
mov r13, rsi ; Сохранение адреса результата
; --- Чтение данных из файла ---
; syscall: read(fd, buffer, count)
xor rax, rax
mov rdi, r12
lea rsi, [io_file_buffer]
mov rdx, 31
syscall
test rax, rax ; Проверка результата
js .io_error ; rax < 0 → ошибка I/O
jz .eof ; rax = 0 → EOF (конец файла)
mov rbx, rax ; Сохранение количества прочитанных байт
; --- Добавление null-терминатора ---
lea rdx, [io_file_buffer] ; Загрузка базового адреса буфера
mov byte [rdx + rax], 0 ; Установка null-терминатора после данных
; --- Парсинг прочитанной строки ---
lea rdi, [io_file_buffer] ; Адрес строки для парсинга
call parse_uint16 ; Вызов функции парсинга
test rdx, rdx ; Проверка кода ошибки (0 = успех)
jnz .parse_error ; rdx != 0 → ошибка парсинга
; --- Сохранение результата ---
mov word [r13], ax ; Сохранение распарсенного значения (16 бит)
mov rax, rbx ; Возврат количества прочитанных байт
jmp .return
; --- Обработка ошибок ---
.io_error:
mov rax, -1 ; Код ошибки I/O
jmp .return
.eof:
xor rax, rax ; Код EOF (0)
jmp .return
.parse_error:
; Преобразование кода ошибки парсинга: 1,2,3 → -2,-3,-4
mov rax, rdx ; Копирование кода ошибки
neg rax ; Инвертирование знака
dec rax ; Декремент (1→-2, 2→-3, 3→-4)
.return:
pop r13
pop r12
pop rbx
pop rbp
ret
; ----------------------------------------------------------------------------
; read_uint16_stream
;
; Читает одно беззнаковое 16-битное целое число из потока с буферизацией.
; Идеально для последовательного чтения нескольких чисел из файла.
; Пропускает ведущие whitespace символы (пробелы, табы, LF, CR).
;
; ПРИМЕНЕНИЕ:
; • Парсинг файлов с множественными значениями
; • Обработка потоков данных
; • Эффективное чтение из больших файлов
;
; ОСОБЕННОСТИ:
; • Внутренняя буферизация 128 байт минимизирует syscall
; • Автоматический пропуск whitespace между числами
; • Сохраняет позицию для следующего вызова
;
; ДИАПАЗОН: 0 до 65535
;
; ПРИМЕРЫ ИСПОЛЬЗОВАНИЯ:
; Чтение файла с числами:
; numbers.txt: "100 200 300\n400 500"
;
; int fd = open("numbers.txt", O_RDONLY);
; uint16_t values[5];
; for (int i = 0; i < 5; i++) {
; int result = read_uint16_stream(fd, &values[i]);
; if (result != 1) break;
; }
; close(fd);
;
; Assembly (читаем 3 числа):
; mov rax, 2
; lea rdi, [filename]
; xor rsi, rsi
; syscall
; mov r15, rax
;
; ; Чтение первого числа
; mov rdi, r15
; lea rsi, [num1]
; call read_uint16_stream
;
; ; Чтение второго числа
; mov rdi, r15
; lea rsi, [num2]
; call read_uint16_stream
;
; ; Чтение третьего числа
; mov rdi, r15
; lea rsi, [num3]
; call read_uint16_stream
;
; mov rax, 3
; mov rdi, r15
; syscall
;
; @param rdi Файловый дескриптор (int fd)
; rsi Адрес переменной для результата (uint16_t *)
; @return rax 1 = успех (число прочитано)
; 0 = EOF (конец файла)
; -1 = ошибка I/O
; -2 = ошибка формата
; -3 = переполнение
; -4 = переполнение буфера
; @uses rbx, rdx, r12, r13, r14
; @calls parse_uint16, .read_char (внутренняя)
;
; @complexity O(n), где n - длина числа
; @memory Использует статический буфер 128 байт
;
; @see read_uint16_from_fd - для однократного чтения
; @see .read_char - внутренняя функция буферизованного чтения символа
; ----------------------------------------------------------------------------
read_uint16_stream:
push rbp
mov rbp, rsp
push rbx
push r12
push r13
push r14
mov r12, rdi ; Сохранение файлового дескриптора
mov r14, rsi ; Сохранение адреса результата
lea r13, [io_file_buffer] ; r13 = база для записи числа
xor rbx, rbx ; rbx = позиция записи (начинаем с 0)
; --- Пропуск начальных whitespace символов ---
.skip_whitespace:
call .read_char ; Чтение одного символа
test rax, rax ; Проверка результата
jle .handle_read_error ; Если <= 0 (ошибка или EOF) - обработка
movzx rax, byte [io_file_char_buf] ; Загрузка прочитанного символа
; Проверка whitespace: ' ', '\t', '\n', '\r'
cmp al, ' ' ; Проверка: пробел?
je .skip_whitespace ; Если да - пропускаем
cmp al, 10 ; Проверка: LF?
je .skip_whitespace ; Если да - пропускаем
cmp al, 9 ; Проверка: TAB?
je .skip_whitespace ; Если да - пропускаем
cmp al, 13 ; Проверка: CR?
je .skip_whitespace ; Если да - пропускаем
; --- Первый значащий символ - начало числа ---
mov byte [r13 + rbx], al ; Сохранение первого символа числа
inc rbx ; Увеличение позиции записи
; --- Чтение остальных символов числа ---
.read_number:
cmp rbx, 30 ; Проверка: буфер переполнен?
jge .buffer_overflow ; Если да - ошибка переполнения буфера
call .read_char ; Чтение следующего символа
test rax, rax ; Проверка результата
jle .handle_read_error_in_number ; Если <= 0 - обработка
movzx rax, byte [io_file_char_buf] ; Загрузка прочитанного символа
; Проверка разделителей (конец числа)
cmp al, ' ' ; Проверка: пробел?
je .parse ; Если да - конец числа, парсим
cmp al, 10 ; Проверка: LF?
je .parse ; Если да - конец числа, парсим
cmp al, 9 ; Проверка: TAB?
je .parse ; Если да - конец числа, парсим
cmp al, 13 ; Проверка: CR?
je .parse ; Если да - конец числа, парсим
; --- Продолжение числа (цифра или знак) ---
mov byte [r13 + rbx], al ; Сохранение символа
inc rbx ; Увеличение позиции записи
jmp .read_number ; Продолжение чтения
; --- Парсинг собранного числа ---
.parse:
mov byte [r13 + rbx], 0 ; Установка null-терминатора
lea rdi, [io_file_buffer] ; Адрес строки для парсинга
call parse_uint16 ; Вызов функции парсинга
test rdx, rdx ; Проверка кода ошибки
jnz .parse_error ; Если ошибка - обработка
; --- Сохранение результата ---
mov word [r14], ax ; Сохранение распарсенного значения (16 бит)
mov rax, 1 ; Код успеха
jmp .ret
; --- Обработка ошибок чтения (до начала числа) ---
.handle_read_error:
test rax, rax ; Проверка кода возврата
jz .eof ; rax = 0 → EOF
mov rax, -1 ; rax < 0 → I/O error
jmp .ret
; --- Обработка ошибок чтения (внутри числа) ---
.handle_read_error_in_number:
test rax, rax ; Проверка кода возврата
js .io_error ; rax < 0 → I/O error
jmp .parse ; rax = 0 → EOF, но есть данные для парсинга
; --- Коды возврата ---
.eof:
xor rax, rax ; Код EOF (0)
jmp .ret
.io_error:
mov rax, -1 ; Код ошибки I/O
jmp .ret
.buffer_overflow:
mov rax, -4 ; Код ошибки переполнения буфера
jmp .ret
.parse_error:
; Преобразование кода ошибки парсинга: 1,2,3 → -2,-3,-4
mov rax, rdx ; Копирование кода ошибки
neg rax ; Инвертирование знака
dec rax ; Декремент
.ret:
pop r14
pop r13
pop r12
pop rbx
pop rbp
ret
; ----------------------------------------------------------------------------
; .read_char (внутренняя функция read_uint16_stream)
;
; Читает один символ из файла с использованием буферизации.
; Минимизирует количество системных вызовов read.
;
; АЛГОРИТМ:
; 1. Проверка наличия данных в буфере
; 2. Если буфер пуст: системный вызов read() на 128 байт
; 3. Извлечение одного символа из буфера
; 4. Обновление позиции и размера
;
; БУФЕРИЗАЦИЯ:
; Буфер: io_file_stream_buffer (128 байт)
; Позиция: io_file_stream_pos
; Размер: io_file_stream_size
;
; @param r12 Файловый дескриптор (сохранён вызывающей функцией)
; @return rax 1 = успех (символ в io_file_char_buf)
; 0 = EOF
; <0 = ошибка I/O
; @uses rcx, rdx, rdi, rsi (сохраняются и восстанавливаются)
;
; @complexity O(1) амортизированная (O(n/128) syscall для n символов)
; @internal Используется только внутри read_uint16_stream
; ----------------------------------------------------------------------------
.read_char:
push rdi
push rsi
push rdx
push rcx
; --- Проверка наличия данных в буфере ---
mov rax, [io_file_stream_size] ; Загрузка количества доступных байт
test rax, rax ; Проверка: есть ли данные в буфере?
jnz .has_buffered ; Если да - извлечение из буфера
; --- Буфер пуст - системный вызов
; syscall: read(fd, buffer, count) ---
xor rax, rax
mov rdi, r12
lea rsi, [io_file_stream_buffer]
mov rdx, 128
syscall
test rax, rax ; Проверка результата
jle .read_done ; Если <= 0 (EOF или ошибка) - завершение
mov [io_file_stream_size], rax ; Сохранение количества прочитанных байт
mov qword [io_file_stream_pos], 0 ; Сброс позиции чтения
; --- Извлечение символа из буфера ---
.has_buffered:
mov rcx, [io_file_stream_pos] ; Загрузка текущей позиции чтения
mov al, byte [io_file_stream_buffer + rcx] ; Чтение символа из буфера
mov byte [io_file_char_buf], al ; Сохранение символа в выходной буфер
inc qword [io_file_stream_pos] ; Увеличение позиции чтения
dec qword [io_file_stream_size] ; Уменьшение количества доступных байт
mov rax, 1 ; Код успешного чтения
.read_done:
pop rcx
pop rdx
pop rsi
pop rdi
ret
; ----------------------------------------------------------------------------
; write_uint32_to_fd
;
; Записывает беззнаковое 32-битное целое число в файловый дескриптор
; в текстовом виде (десятичный формат) с завершающим символом LF.
;
; ПРИМЕНЕНИЕ:
; • Запись результатов вычислений в файл
; • Экспорт данных в текстовом формате
; • Логирование числовых значений
;
; ДИАПАЗОН: 0 до 4294967295 (uint32_t)
; ФОРМАТ ЗАПИСИ: "<число>\n"
;
; ПРИМЕРЫ:
; write_uint32_to_fd(3, 0) → "0\n"
; write_uint32_to_fd(3, 12345) → "12345\n"
; write_uint32_to_fd(3, 4000000000) → "4000000000\n"
;
; ПРИМЕРЫ ИСПОЛЬЗОВАНИЯ:
; C-style:
; int fd = open("output.txt", O_WRONLY | O_CREAT, 0644);
; write_uint32_to_fd(fd, 12345);
; write_uint32_to_fd(fd, 67890);
; close(fd);
; // Файл содержит: "12345\n67890\n"
;
; Assembly:
; ; Открыть файл для записи
; mov rax, 2 ; sys_open
; lea rdi, [filename]
; mov rsi, 0x241 ; O_WRONLY | O_CREAT
; mov rdx, 0x1A4 ; 0644
; syscall
; mov r15, rax ; Сохранить FD
;
; ; Записать число
; mov rdi, r15
; mov esi, 12345
; call write_uint32_to_fd
;
; ; Закрыть файл
; mov rax, 3 ; sys_close
; mov rdi, r15
; syscall
;
; @param rdi Файловый дескриптор (int fd)
; esi Значение для записи (uint32_t)
; @return rax Количество записанных байт (или код ошибки от write)
; @uses rbx, rdx, r12, r13, r14
;
; @complexity O(log₁₀(n)), где n - значение числа
; @memory O(1), использует фиксированный буфер 32 байта
; ----------------------------------------------------------------------------
write_uint32_to_fd:
push rbp
mov rbp, rsp
push rbx
push r12
push r13
push r14
mov r14, rdi ; Сохранение файлового дескриптора
mov eax, esi ; Загрузка беззнакового 32-битного числа
; --- Формирование строки справа налево в io_file_buffer ---
lea r13, [io_file_buffer + 31] ; r13 = указатель на конец буфера
mov byte [r13], 10 ; Установка LF (символ новой строки) в конце
dec r13 ; Смещение указателя назад
; --- Специальная обработка нуля ---
test rax, rax ; Проверка: число равно нулю?
jnz .convert ; Если нет - переход к конвертации
mov byte [r13], '0' ; Запись символа '0'
jmp .write ; Переход к записи в файл
; --- Конвертация числа в строку (справа налево) ---
; Инвариант цикла: r13 указывает на следующую позицию для записи
.convert:
dec r13 ; Смещение указателя назад
xor rdx, rdx ; Обнуление rdx перед делением
mov r12, 10 ; Делитель = 10 (основание системы счисления)
div r12 ; rax = rax / 10, rdx = rax % 10 (остаток - цифра)
add dl, '0' ; Преобразование цифры (0-9) в ASCII ('0'-'9')
mov [r13], dl ; Запись ASCII-символа в буфер
test rax, rax ; Проверка: остались ли ещё цифры?
jnz .convert ; Если да - продолжение конвертации
; --- Запись строки в файл ---
.write:
; syscall: write(fd, string, length)
mov rax, 1
mov rdi, r14
mov rsi, r13 ; Адрес начала строки
lea rdx, [io_file_buffer + 32] ; Адрес конца буфера
sub rdx, r13 ; Вычисление длины: конец - начало
syscall
pop r14
pop r13
pop r12
pop rbx
pop rbp
ret
Расширение io_float.asm
Модуль для чисел с плавающей точкой (float) и знаковых 32-битных целых (int32_t).
📄 Полный код расширения Layer 3 для io_float.asm
Шаг 1: Добавьте в секцию .bss
; Буферы файлового режима (Слой 3)
io_file_buffer resb 64 ; Буфер для файловых операций
io_file_char_buf resb 1 ; Буфер для одного символа
io_file_stream_buffer resb 128 ; Буфер потокового чтения
io_file_stream_pos resq 1 ; Позиция в потоковом буфере
io_file_stream_size resq 1 ; Размер данных в потоке
Шаг 2: Добавьте в секцию .text (после существующих global)
global read_float_from_fd ; Слой 3: чтение float из файла
global read_float_stream ; Слой 3: потоковое чтение float
global write_float_to_fd ; Слой 3: запись float в файл
global read_int32_from_fd_float ; Слой 3: чтение int32 из файла
global read_int32_stream_float ; Слой 3: потоковое чтение int32
global write_int32_to_fd_float ; Слой 3: запись int32 в файл
Шаг 3: Вставьте в конец файла (после Layer 2 и перед вспомогательными функциями)
; ============================================================================
; СЛОЙ 3: ФАЙЛОВЫЙ ВВОД-ВЫВОД
; ============================================================================
; ----------------------------------------------------------------------------
; read_float_from_fd
;
; Читает одно число с плавающей точкой из файлового дескриптора.
; Подходит для однократного чтения из файлов.
;
; ПРИМЕНЕНИЕ:
; • Чтение конфигурационных файлов
; • Однократная загрузка значений
; • Простые случаи без необходимости потоковой обработки
;
; ОГРАНИЧЕНИЯ:
; • Максимальная длина строки: 63 символа
; • Не оптимально для множественных чтений (используйте read_float_stream)
;
; ПРИМЕРЫ ИСПОЛЬЗОВАНИЯ:
; C-style:
; int fd = open("config.txt", O_RDONLY);
; float value;
; ssize_t result = read_float_from_fd(fd, &value);
; if (result > 0) {
; // value содержит число
; }
; close(fd);
;
; Assembly:
; ; Открыть файл
; mov rax, 2 ; sys_open
; lea rdi, [filename]
; xor rsi, rsi ; O_RDONLY
; syscall
; mov r15, rax ; Сохранить FD
;
; ; Прочитать число
; mov rdi, r15
; lea rsi, [my_float]
; call read_float_from_fd
;
; ; Закрыть файл
; mov rax, 3 ; sys_close
; mov rdi, r15
; syscall
;
; @param rdi Файловый дескриптор (int fd)
; rsi Адрес переменной для результата (float *)
; @return rax >0 = количество прочитанных байт
; 0 = EOF (конец файла)
; -1 = ошибка I/O (чтение из файла)
; -2 = ошибка формата (некорректное число)
; -3 = переполнение
; @uses rbx, rdx, r12, r13
; @calls parse_float
;
; @see read_float_stream - для множественных чтений
; @see parse_float - базовая функция парсинга
; ----------------------------------------------------------------------------
read_float_from_fd:
push rbp
mov rbp, rsp
push rbx
push r12
push r13
mov r12, rdi ; Сохранение файлового дескриптора
mov r13, rsi ; Сохранение адреса результата
; --- Чтение данных из файла ---
; syscall: read(fd, buffer, count)
xor rax, rax
mov rdi, r12
lea rsi, [io_file_buffer] ; Адрес буфера
mov rdx, 63
syscall
test rax, rax ; Проверка результата
js .io_error ; rax < 0 → ошибка I/O
jz .eof ; rax = 0 → EOF (конец файла)
mov rbx, rax ; Сохранение количества прочитанных байт
; --- Добавление null-терминатора ---
lea rdx, [io_file_buffer] ; Загрузка базового адреса буфера
mov byte [rdx + rax], 0 ; Установка null-терминатора после данных
; --- Парсинг прочитанной строки ---
lea rdi, [io_file_buffer] ; Адрес строки для парсинга
call parse_float ; Вызов функции парсинга
test rdx, rdx ; Проверка кода ошибки (0 = успех)
jnz .parse_error ; rdx != 0 → ошибка парсинга
; --- Сохранение результата ---
mov [r13], eax ; Сохранение битового представления float
mov rax, rbx ; Возврат количества прочитанных байт
jmp .return
; --- Обработка ошибок ---
.io_error:
mov rax, -1 ; Код ошибки I/O
jmp .return
.eof:
xor rax, rax ; Код EOF (0)
jmp .return
.parse_error:
; Преобразование кода ошибки парсинга: 1,2 → -2,-3
mov rax, rdx ; Копирование кода ошибки
neg rax ; Инвертирование знака
dec rax ; Декремент (1→-2, 2→-3)
.return:
pop r13
pop r12
pop rbx
pop rbp
ret
; ----------------------------------------------------------------------------
; read_int32_from_fd_float
;
; Читает одно знаковое 32-битное целое число из файлового дескриптора.
; Подходит для однократного чтения из файлов.
; Суффикс _float используется для избежания конфликтов имён.
;
; ПРИМЕНЕНИЕ:
; • Чтение конфигурационных файлов
; • Однократная загрузка значений
; • Простые случаи без необходимости потоковой обработки
;
; ОГРАНИЧЕНИЯ:
; • Максимальная длина строки: 63 символа
; • Не оптимально для множественных чтений
;
; ДИАПАЗОН: -2147483648 до 2147483647
;
; ПРИМЕРЫ ИСПОЛЬЗОВАНИЯ:
; C-style:
; int fd = open("config.txt", O_RDONLY);
; int32_t value;
; ssize_t result = read_int32_from_fd_float(fd, &value);
; if (result > 0) {
; // value содержит число
; }
; close(fd);
;
; Assembly:
; mov rax, 2
; lea rdi, [filename]
; xor rsi, rsi
; syscall
; mov r15, rax
;
; mov rdi, r15
; lea rsi, [my_int]
; call read_int32_from_fd_float
;
; mov rax, 3
; mov rdi, r15
; syscall
;
; @param rdi Файловый дескриптор (int fd)
; rsi Адрес переменной для результата (int32_t *)
; @return rax >0 = количество прочитанных байт
; 0 = EOF (конец файла)
; -1 = ошибка I/O (чтение из файла)
; -2 = ошибка формата (некорректное число)
; -3 = переполнение
; @uses rbx, rdx, r12, r13
; @calls parse_int32
;
; @see read_int32_stream_float - для множественных чтений
; @see parse_int32 - базовая функция парсинга
; ----------------------------------------------------------------------------
read_int32_from_fd_float:
push rbp
mov rbp, rsp
push rbx
push r12
push r13
mov r12, rdi ; Сохранение файлового дескриптора
mov r13, rsi ; Сохранение адреса результата
; --- Чтение данных из файла ---
; syscall: read(fd, buffer, count)
xor rax, rax
mov rdi, r12
lea rsi, [io_file_buffer] ; Адрес буфера
mov rdx, 63
syscall
test rax, rax ; Проверка результата
js .io_error ; rax < 0 → ошибка I/O
jz .eof ; rax = 0 → EOF (конец файла)
mov rbx, rax ; Сохранение количества прочитанных байт
; --- Добавление null-терминатора ---
lea rdx, [io_file_buffer] ; Загрузка базового адреса буфера
mov byte [rdx + rax], 0 ; Установка null-терминатора после данных
; --- Парсинг прочитанной строки ---
lea rdi, [io_file_buffer] ; Адрес строки для парсинга
call parse_int32 ; Вызов функции парсинга
test rdx, rdx ; Проверка кода ошибки (0 = успех)
jnz .parse_error ; rdx != 0 → ошибка парсинга
; --- Сохранение результата ---
mov [r13], eax ; Сохранение значения int32
mov rax, rbx ; Возврат количества прочитанных байт
jmp .return
; --- Обработка ошибок ---
.io_error:
mov rax, -1 ; Код ошибки I/O
jmp .return
.eof:
xor rax, rax ; Код EOF (0)
jmp .return
.parse_error:
; Преобразование кода ошибки парсинга: 1,2 → -2,-3
mov rax, rdx ; Копирование кода ошибки
neg rax ; Инвертирование знака
dec rax ; Декремент (1→-2, 2→-3)
.return:
pop r13
pop r12
pop rbx
pop rbp
ret
; ----------------------------------------------------------------------------
; read_float_stream
;
; Читает одно число с плавающей точкой из потока с буферизацией.
; Идеально для последовательного чтения нескольких чисел из файла.
; Пропускает ведущие whitespace символы (пробелы, табы, LF, CR).
;
; ПРИМЕНЕНИЕ:
; • Парсинг файлов с множественными значениями
; • Обработка потоков данных
; • Эффективное чтение из больших файлов
;
; ОСОБЕННОСТИ:
; • Внутренняя буферизация 128 байт минимизирует syscall
; • Автоматический пропуск whitespace между числами
; • Сохраняет позицию для следующего вызова
;
; ПРИМЕРЫ ИСПОЛЬЗОВАНИЯ:
; Чтение файла с числами:
; numbers.txt: "1.5 2.25 3.75\n4.0 5.5"
;
; int fd = open("numbers.txt", O_RDONLY);
; float values[5];
; for (int i = 0; i < 5; i++) {
; int result = read_float_stream(fd, &values[i]);
; if (result != 1) break;
; }
; close(fd);
;
; Assembly (читаем 3 числа):
; mov rax, 2
; lea rdi, [filename]
; xor rsi, rsi
; syscall
; mov r15, rax
;
; mov rdi, r15
; lea rsi, [num1]
; call read_float_stream
;
; mov rdi, r15
; lea rsi, [num2]
; call read_float_stream
;
; mov rdi, r15
; lea rsi, [num3]
; call read_float_stream
;
; mov rax, 3
; mov rdi, r15
; syscall
;
; @param rdi Файловый дескриптор (int fd)
; rsi Адрес переменной для результата (float *)
; @return rax 1 = успех (число прочитано)
; 0 = EOF (конец файла)
; -1 = ошибка I/O
; -2 = ошибка формата
; -3 = переполнение
; -4 = переполнение буфера
; @uses rbx, rdx, r12, r13, r14
; @calls parse_float, .read_char (внутренняя)
;
; @complexity O(n), где n - длина числа
; @memory Использует статический буфер 128 байт
;
; @see read_float_from_fd - для однократного чтения
; @see .read_char - внутренняя функция буферизованного чтения символа
; ----------------------------------------------------------------------------
read_float_stream:
push rbp
mov rbp, rsp
push rbx
push r12
push r13
push r14
mov r12, rdi ; Сохранение файлового дескриптора
mov r14, rsi ; Сохранение адреса результата
lea r13, [io_file_buffer] ; r13 = база для записи числа
xor rbx, rbx ; rbx = позиция записи (начинаем с 0)
; --- Пропуск начальных whitespace символов ---
.skip_whitespace:
call .read_char ; Чтение одного символа
test rax, rax ; Проверка результата
jle .handle_read_error ; Если <= 0 (ошибка или EOF) - обработка
movzx rax, byte [io_file_char_buf] ; Загрузка прочитанного символа
; Проверка whitespace: ' ', '\t', '\n', '\r'
cmp al, ' ' ; Проверка: пробел?
je .skip_whitespace ; Если да - пропускаем
cmp al, 10 ; Проверка: LF?
je .skip_whitespace ; Если да - пропускаем
cmp al, 9 ; Проверка: TAB?
je .skip_whitespace ; Если да - пропускаем
cmp al, 13 ; Проверка: CR?
je .skip_whitespace ; Если да - пропускаем
; --- Первый значащий символ - начало числа ---
mov byte [r13 + rbx], al ; Сохранение первого символа числа
inc rbx ; Увеличение позиции записи
; --- Чтение остальных символов числа ---
.read_number:
cmp rbx, 62 ; Проверка: буфер переполнен?
jge .buffer_overflow ; Если да - ошибка переполнения буфера
call .read_char ; Чтение следующего символа
test rax, rax ; Проверка результата
jle .handle_read_error_in_number ; Если <= 0 - обработка
movzx rax, byte [io_file_char_buf] ; Загрузка прочитанного символа
; Проверка разделителей (конец числа)
cmp al, ' ' ; Проверка: пробел?
je .parse ; Если да - конец числа, парсим
cmp al, 10 ; Проверка: LF?
je .parse ; Если да - конец числа, парсим
cmp al, 9 ; Проверка: TAB?
je .parse ; Если да - конец числа, парсим
cmp al, 13 ; Проверка: CR?
je .parse ; Если да - конец числа, парсим
; --- Продолжение числа (цифра, точка или знак) ---
mov byte [r13 + rbx], al ; Сохранение символа
inc rbx ; Увеличение позиции записи
jmp .read_number ; Продолжение чтения
; --- Парсинг собранного числа ---
.parse:
mov byte [r13 + rbx], 0 ; Установка null-терминатора
lea rdi, [io_file_buffer] ; Адрес строки для парсинга
call parse_float ; Вызов функции парсинга
test rdx, rdx ; Проверка кода ошибки
jnz .parse_error ; Если ошибка - обработка
; --- Сохранение результата ---
mov [r14], eax ; Сохранение битового представления float
mov rax, 1 ; Код успеха
jmp .ret
; --- Обработка ошибок чтения (до начала числа) ---
.handle_read_error:
test rax, rax ; Проверка кода возврата
jz .eof ; rax = 0 → EOF
mov rax, -1 ; rax < 0 → I/O error
jmp .ret
; --- Обработка ошибок чтения (внутри числа) ---
.handle_read_error_in_number:
test rax, rax ; Проверка кода возврата
js .io_error ; rax < 0 → I/O error
jmp .parse ; rax = 0 → EOF, но есть данные для парсинга
; --- Коды возврата ---
.eof:
xor rax, rax ; Код EOF (0)
jmp .ret
.io_error:
mov rax, -1 ; Код ошибки I/O
jmp .ret
.buffer_overflow:
mov rax, -4 ; Код ошибки переполнения буфера
jmp .ret
.parse_error:
; Преобразование кода ошибки парсинга: 1,2 → -2,-3
mov rax, rdx ; Копирование кода ошибки
neg rax ; Инвертирование знака
dec rax ; Декремент
.ret:
pop r14
pop r13
pop r12
pop rbx
pop rbp
ret
; ----------------------------------------------------------------------------
; .read_char (внутренняя функция read_float_stream)
;
; Читает один символ из файла с использованием буферизации.
; Минимизирует количество системных вызовов read.
;
; АЛГОРИТМ:
; 1. Проверка наличия данных в буфере
; 2. Если буфер пуст: системный вызов read() на 128 байт
; 3. Извлечение одного символа из буфера
; 4. Обновление позиции и размера
;
; БУФЕРИЗАЦИЯ:
; Буфер: io_file_stream_buffer (128 байт)
; Позиция: io_file_stream_pos
; Размер: io_file_stream_size
;
; @param r12 Файловый дескриптор (сохранён вызывающей функцией)
; @return rax 1 = успех (символ в io_file_char_buf)
; 0 = EOF
; <0 = ошибка I/O
; @uses rcx, rdx, rdi, rsi (сохраняются и восстанавливаются)
;
; @complexity O(1) амортизированная (O(n/128) syscall для n символов)
; @internal Используется только внутри read_float_stream
; ----------------------------------------------------------------------------
.read_char:
push rdi
push rsi
push rdx
push rcx
; --- Проверка наличия данных в буфере ---
mov rax, [io_file_stream_size] ; Загрузка количества доступных байт
test rax, rax ; Проверка: есть ли данные в буфере?
jnz .has_buffered ; Если да - извлечение из буфера
; --- Буфер пуст - системный вызов ---
; syscall: read(fd, buffer, count)
xor rax, rax
mov rdi, r12
lea rsi, [io_file_stream_buffer] ; Адрес буфера
mov rdx, 128
syscall
test rax, rax ; Проверка результата
jle .read_done ; Если <= 0 (EOF или ошибка) - завершение
mov [io_file_stream_size], rax ; Сохранение количества прочитанных байт
mov qword [io_file_stream_pos], 0 ; Сброс позиции чтения
; --- Извлечение символа из буфера ---
.has_buffered:
mov rcx, [io_file_stream_pos] ; Загрузка текущей позиции чтения
mov al, byte [io_file_stream_buffer + rcx] ; Чтение символа из буфера
mov byte [io_file_char_buf], al ; Сохранение символа в выходной буфер
inc qword [io_file_stream_pos] ; Увеличение позиции чтения
dec qword [io_file_stream_size] ; Уменьшение количества доступных байт
mov rax, 1 ; Код успешного чтения
.read_done:
pop rcx
pop rdx
pop rsi
pop rdi
ret
; ----------------------------------------------------------------------------
; read_int32_stream_float
;
; Читает одно знаковое 32-битное целое число из потока с буферизацией.
; Идеально для последовательного чтения нескольких чисел из файла.
; Пропускает ведущие whitespace символы (пробелы, табы, LF, CR).
; Суффикс _float используется для избежания конфликтов имён.
;
; ПРИМЕНЕНИЕ:
; • Парсинг файлов с множественными значениями
; • Обработка потоков данных
; • Эффективное чтение из больших файлов
;
; ОСОБЕННОСТИ:
; • Внутренняя буферизация 128 байт минимизирует syscall
; • Автоматический пропуск whitespace между числами
; • Сохраняет позицию для следующего вызова
;
; ДИАПАЗОН: -2147483648 до 2147483647
;
; ПРИМЕРЫ ИСПОЛЬЗОВАНИЯ:
; Чтение файла с числами:
; numbers.txt: "100 -200 300\n400 500"
;
; int fd = open("numbers.txt", O_RDONLY);
; int32_t values[5];
; for (int i = 0; i < 5; i++) {
; int result = read_int32_stream_float(fd, &values[i]);
; if (result != 1) break;
; }
; close(fd);
;
; @param rdi Файловый дескриптор (int fd)
; rsi Адрес переменной для результата (int32_t *)
; @return rax 1 = успех (число прочитано)
; 0 = EOF (конец файла)
; -1 = ошибка I/O
; -2 = ошибка формата
; -3 = переполнение
; -4 = переполнение буфера
; @uses rbx, rdx, r12, r13, r14
; @calls parse_int32, .read_char_int (внутренняя)
;
; @complexity O(n), где n - длина числа
; @memory Использует статический буфер 128 байт
;
; @see read_int32_from_fd_float - для однократного чтения
; @see .read_char_int - внутренняя функция буферизованного чтения символа
; ----------------------------------------------------------------------------
read_int32_stream_float:
push rbp
mov rbp, rsp
push rbx
push r12
push r13
push r14
mov r12, rdi ; Сохранение файлового дескриптора
mov r14, rsi ; Сохранение адреса результата
lea r13, [io_file_buffer] ; r13 = база для записи числа
xor rbx, rbx ; rbx = позиция записи (начинаем с 0)
; --- Пропуск начальных whitespace символов ---
.skip_whitespace:
call .read_char_int ; Чтение одного символа
test rax, rax ; Проверка результата
jle .handle_read_error ; Если <= 0 (ошибка или EOF) - обработка
movzx rax, byte [io_file_char_buf] ; Загрузка прочитанного символа
; Проверка whitespace: ' ', '\t', '\n', '\r'
cmp al, ' ' ; Проверка: пробел?
je .skip_whitespace ; Если да - пропускаем
cmp al, 10 ; Проверка: LF?
je .skip_whitespace ; Если да - пропускаем
cmp al, 9 ; Проверка: TAB?
je .skip_whitespace ; Если да - пропускаем
cmp al, 13 ; Проверка: CR?
je .skip_whitespace ; Если да - пропускаем
; --- Первый значащий символ - начало числа ---
mov byte [r13 + rbx], al ; Сохранение первого символа числа
inc rbx ; Увеличение позиции записи
; --- Чтение остальных символов числа ---
.read_number:
cmp rbx, 62 ; Проверка: буфер переполнен?
jge .buffer_overflow ; Если да - ошибка переполнения буфера
call .read_char_int ; Чтение следующего символа
test rax, rax ; Проверка результата
jle .handle_read_error_in_number ; Если <= 0 - обработка
movzx rax, byte [io_file_char_buf] ; Загрузка прочитанного символа
; Проверка разделителей (конец числа)
cmp al, ' ' ; Проверка: пробел?
je .parse ; Если да - конец числа, парсим
cmp al, 10 ; Проверка: LF?
je .parse ; Если да - конец числа, парсим
cmp al, 9 ; Проверка: TAB?
je .parse ; Если да - конец числа, парсим
cmp al, 13 ; Проверка: CR?
je .parse ; Если да - конец числа, парсим
; --- Продолжение числа (цифра или знак) ---
mov byte [r13 + rbx], al ; Сохранение символа
inc rbx ; Увеличение позиции записи
jmp .read_number ; Продолжение чтения
; --- Парсинг собранного числа ---
.parse:
mov byte [r13 + rbx], 0 ; Установка null-терминатора
lea rdi, [io_file_buffer] ; Адрес строки для парсинга
call parse_int32 ; Вызов функции парсинга
test rdx, rdx ; Проверка кода ошибки
jnz .parse_error ; Если ошибка - обработка
; --- Сохранение результата ---
mov [r14], eax ; Сохранение значения int32
mov rax, 1 ; Код успеха
jmp .ret
; --- Обработка ошибок чтения (до начала числа) ---
.handle_read_error:
test rax, rax ; Проверка кода возврата
jz .eof ; rax = 0 → EOF
mov rax, -1 ; rax < 0 → I/O error
jmp .ret
; --- Обработка ошибок чтения (внутри числа) ---
.handle_read_error_in_number:
test rax, rax ; Проверка кода возврата
js .io_error ; rax < 0 → I/O error
jmp .parse ; rax = 0 → EOF, но есть данные для парсинга
; --- Коды возврата ---
.eof:
xor rax, rax ; Код EOF (0)
jmp .ret
.io_error:
mov rax, -1 ; Код ошибки I/O
jmp .ret
.buffer_overflow:
mov rax, -4 ; Код ошибки переполнения буфера
jmp .ret
.parse_error:
; Преобразование кода ошибки парсинга: 1,2 → -2,-3
mov rax, rdx ; Копирование кода ошибки
neg rax ; Инвертирование знака
dec rax ; Декремент
.ret:
pop r14
pop r13
pop r12
pop rbx
pop rbp
ret
; ----------------------------------------------------------------------------
; .read_char_int (внутренняя функция read_int32_stream_float)
;
; Читает один символ из файла с использованием буферизации.
; Минимизирует количество системных вызовов read.
; Идентична .read_char, но с отдельным именем для ясности контекста.
;
; АЛГОРИТМ:
; 1. Проверка наличия данных в буфере
; 2. Если буфер пуст: системный вызов read() на 128 байт
; 3. Извлечение одного символа из буфера
; 4. Обновление позиции и размера
;
; БУФЕРИЗАЦИЯ:
; Буфер: io_file_stream_buffer (128 байт)
; Позиция: io_file_stream_pos
; Размер: io_file_stream_size
;
; @param r12 Файловый дескриптор (сохранён вызывающей функцией)
; @return rax 1 = успех (символ в io_file_char_buf)
; 0 = EOF
; <0 = ошибка I/O
; @uses rcx, rdx, rdi, rsi (сохраняются и восстанавливаются)
;
; @complexity O(1) амортизированная (O(n/128) syscall для n символов)
; @internal Используется только внутри read_int32_stream_float
; ----------------------------------------------------------------------------
.read_char_int:
push rdi
push rsi
push rdx
push rcx
; --- Проверка наличия данных в буфере ---
mov rax, [io_file_stream_size] ; Загрузка количества доступных байт
test rax, rax ; Проверка: есть ли данные в буфере?
jnz .has_buffered ; Если да - извлечение из буфера
; --- Буфер пуст - системный вызов ---
; syscall: read(fd, buffer, count)
xor rax, rax
mov rdi, r12
lea rsi, [io_file_stream_buffer] ; Адрес буфера
mov rdx, 128
syscall
test rax, rax ; Проверка результата
jle .read_done ; Если <= 0 (EOF или ошибка) - завершение
mov [io_file_stream_size], rax ; Сохранение количества прочитанных байт
mov qword [io_file_stream_pos], 0 ; Сброс позиции чтения
; --- Извлечение символа из буфера ---
.has_buffered:
mov rcx, [io_file_stream_pos] ; Загрузка текущей позиции чтения
mov al, byte [io_file_stream_buffer + rcx] ; Чтение символа из буфера
mov byte [io_file_char_buf], al ; Сохранение символа в выходной буфер
inc qword [io_file_stream_pos] ; Увеличение позиции чтения
dec qword [io_file_stream_size] ; Уменьшение количества доступных байт
mov rax, 1 ; Код успешного чтения
.read_done:
pop rcx
pop rdx
pop rsi
pop rdi
ret
; ----------------------------------------------------------------------------
; write_float_to_fd
;
; Записывает число с плавающей точкой в файловый дескриптор
; в текстовом формате с точностью 6 знаков после запятой + LF.
;
; АЛГОРИТМ:
; 1. Загрузка float в FPU
; 2. Определение знака и добавление '-' в начало буфера при необходимости
; 3. Отделение целой части через FPU truncation
; 4. Формирование целой части в локальный буфер (справа налево)
; 5. Копирование целой части в выходной буфер (слева направо)
; 6. Добавление десятичной точки
; 7. Формирование дробной части (6 цифр слева направо)
; 8. Добавление символа новой строки
; 9. Запись в файл
;
; ПРИМЕНЕНИЕ:
; • Запись результатов вычислений в файл
; • Экспорт данных в текстовом формате
; • Логирование числовых значений
;
; ФОРМАТ ЗАПИСИ: "[-]целая.дробная\n"
; ТОЧНОСТЬ: 6 знаков после запятой (FRAC_DIGITS)
;
; ПРИМЕРЫ:
; write_float_to_fd(3, 0x40490FDB) → "3.141593\n" (π)
; write_float_to_fd(3, 0xC0000000) → "-2.000000\n"
; write_float_to_fd(3, 0x00000000) → "0.000000\n"
;
; ПРИМЕРЫ ИСПОЛЬЗОВАНИЯ:
; C-style:
; int fd = open("output.txt", O_WRONLY | O_CREAT, 0644);
; float pi = 3.14159f;
; write_float_to_fd(fd, *(uint32_t*)&pi);
; close(fd);
; // Файл содержит: "3.141593\n"
;
; Assembly:
; ; Открыть файл для записи
; mov rax, 2 ; sys_open
; lea rdi, [filename]
; mov rsi, 0x241 ; O_WRONLY | O_CREAT
; mov rdx, 0x1A4 ; 0644
; syscall
; mov r15, rax ; Сохранить FD
;
; ; Записать число
; mov rdi, r15
; mov esi, 0x40490FDB ; π
; call write_float_to_fd
;
; ; Закрыть файл
; mov rax, 3 ; sys_close
; mov rdi, r15
; syscall
;
; @param rdi Файловый дескриптор (int fd)
; esi Битовое представление float (IEEE 754)
; @return rax Количество записанных байт (или код ошибки от write)
; @uses rbx, rdx, r12, r13, r14, r15, FPU stack
;
; @complexity O(1), фиксированная точность вывода
; @memory O(1), использует фиксированный буфер 64 байта
;
; @note Использует FPU для разделения на целую и дробную части
; ----------------------------------------------------------------------------
write_float_to_fd:
push rbp
mov rbp, rsp
sub rsp, 64 ; Локальное пространство для FPU операций и временных данных
push rbx
push r12
push r13
push r14
push r15
mov r14, rdi ; Сохранение файлового дескриптора
mov [rbp-4], esi ; Сохранение битового представления в памяти для загрузки в FPU
fld dword [rbp-4] ; Загрузка float в FPU: ST(0) = число
; --- Инициализация выходного буфера ---
; Формирование строки слева направо упрощает логику и исключает ошибки порядка символов
lea r15, [io_file_buffer] ; r15 = указатель на начало буфера
xor rbx, rbx ; rbx = позиция записи (начинаем с 0)
; --- Проверка и обработка знака числа ---
; Проверка знака числа через FPU
ftst ; Сравнение ST(0) с 0.0 (устанавливает флаги в FPU status word)
fstsw ax ; Копирование FPU status word в AX
sahf ; Загрузка AH в флаги процессора (для условных переходов)
jae .positive ; Если >= 0 - переход к обработке положительного числа
; Число отрицательное - добавление знака минус
mov byte [r15 + rbx], '-' ; Запись символа '-' в текущую позицию буфера
inc rbx ; Увеличение позиции записи
fabs ; Взятие модуля: ST(0) = |ST(0)| (работаем с положительным значением)
; --- Разделение на целую и дробную части ---
; Используем FPU для точного разделения числа на компоненты
.positive:
fld st0 ; Дублирование числа в стеке FPU: ST(0) = число, ST(1) = число
; Усечение до целой части (truncation к нулю)
push rax
sub rsp, 8 ; Выделение места для control word на стеке
fnstcw [rsp] ; Сохранение текущего control word FPU
mov ax, [rsp] ; Загрузка control word в AX
or ax, FPU_TRUNC_MODE ; Установка режима усечения (0x0C00 - округление к нулю)
mov [rsp+4], ax ; Сохранение модифицированного control word
fldcw [rsp+4] ; Загрузка нового control word в FPU
frndint ; Округление ST(0) к целому (с учётом режима усечения)
fldcw [rsp] ; Восстановление исходного control word
add rsp, 8 ; Освобождение стека
pop rax
; Сохранение целой части
fist dword [rbp-8] ; ST(0) → [rbp-8] (сохранение целой части как int32)
mov r13d, [rbp-8] ; Загрузка целой части в r13d для последующей обработки
; Вычисление дробной части: исходное_число - целая_часть
fsubr st0, st1 ; ST(0) = ST(1) - ST(0) = исходное - целое
fstp dword [rbp-12] ; Сохранение дробной части в память: [rbp-12] = дробная
fstp st0 ; Удаление исходного числа из стека FPU (очистка стека)
; --- Формирование целой части числа ---
; Алгоритм: сначала конвертация справа налево во временный буфер,
; затем копирование слева направо в выходной буфер
mov eax, r13d ; Загрузка целой части для обработки
test eax, eax ; Проверка: равно нулю?
jnz .int_convert ; Если не ноль - переход к конвертации
; Специальная обработка нуля
mov byte [r15 + rbx], '0' ; Запись символа '0' для нулевой целой части
inc rbx ; Увеличение позиции записи
jmp .decimal_point ; Переход к добавлению десятичной точки
; --- Конвертация целой части в строку (справа налево во временный буфер) ---
.int_convert:
lea r12, [rbp-32] ; r12 = адрес локального временного буфера
xor rcx, rcx ; rcx = количество цифр (счётчик, начинаем с 0)
; Инвариант цикла: rcx = количество записанных цифр
.int_loop:
xor edx, edx ; Обнуление EDX перед делением (для 64-битного деления)
mov esi, 10 ; Делитель = 10 (основание десятичной системы)
div esi ; EAX = EAX / 10, EDX = EAX % 10 (остаток - младшая цифра)
add dl, '0' ; Преобразование цифры (0-9) в ASCII-символ ('0'-'9')
mov [r12 + rcx], dl ; Запись цифры во временный буфер
inc rcx ; Увеличение счётчика цифр
test eax, eax ; Проверка: остались ли ещё цифры?
jnz .int_loop ; Если да - продолжение конвертации
; --- Копирование цифр в правильном порядке (обратный к записи) ---
; Временный буфер содержит цифры в обратном порядке (младшие сначала),
; копируем их в выходной буфер слева направо (старшие сначала)
.int_copy:
dec rcx ; Переход к последней записанной цифре (самой старшей)
mov al, [r12 + rcx] ; Загрузка цифры из временного буфера
mov [r15 + rbx], al ; Запись в выходной буфер в текущую позицию
inc rbx ; Увеличение позиции в выходном буфере
test rcx, rcx ; Проверка: остались ли цифры для копирования?
jnz .int_copy ; Если да - продолжение копирования
; --- Добавление десятичной точки ---
.decimal_point:
mov byte [r15 + rbx], '.' ; Запись символа '.' (десятичный разделитель)
inc rbx ; Увеличение позиции записи
; --- Формирование дробной части (6 цифр слева направо) ---
; Алгоритм: последовательное умножение дробной части на 10,
; извлечение целой части (текущей цифры), повторение
fld dword [rbp-12] ; Загрузка дробной части в FPU: ST(0) = дробная часть
mov rcx, FRAC_DIGITS ; rcx = 6 (количество знаков после запятой для вывода)
; Инвариант цикла: rcx = количество оставшихся цифр для вывода
.frac_loop:
fmul dword [io_const_ten] ; ST(0) = ST(0) * 10.0 (сдвиг разряда влево)
fld st0 ; Дублирование: ST(0) = ST(1) = дробная * 10^i
; Усечение для получения текущей цифры (целая часть от умножения)
push rax
sub rsp, 8 ; Выделение места для control word
fnstcw [rsp] ; Сохранение текущего control word
mov ax, [rsp]
or ax, FPU_TRUNC_MODE ; Установка режима усечения
mov [rsp+4], ax
fldcw [rsp+4] ; Загрузка режима усечения в FPU
frndint ; Усечение до целого (получение текущей цифры)
fldcw [rsp] ; Восстановление исходного control word
add rsp, 8
pop rax
fist dword [rbp-20] ; Сохранение текущей цифры в память
mov eax, [rbp-20] ; Загрузка цифры в EAX для валидации
; Валидация цифры (защита от ошибок округления FPU)
; Должна быть в диапазоне 0-9
cmp eax, 0 ; Проверка: меньше 0?
jl .frac_zero ; Если да - использовать безопасное значение '0'
cmp eax, 9 ; Проверка: больше 9?
jg .frac_zero ; Если да - использовать безопасное значение '0'
add al, '0' ; Преобразование цифры (0-9) в ASCII ('0'-'9')
jmp .frac_out
.frac_zero:
mov al, '0' ; Использование '0' для недопустимых значений (защита)
.frac_out:
mov [r15 + rbx], al ; Запись ASCII-цифры в выходной буфер
inc rbx ; Увеличение позиции записи
fsubp st1, st0 ; ST(0) = ST(1) - ST(0) (удаление целой части цифры)
; Остаётся дробная часть для следующей итерации
dec rcx ; Уменьшение счётчика оставшихся цифр
jnz .frac_loop ; Если остались цифры - продолжение цикла
fstp st0 ; Удаление остатка дробной части из стека FPU (очистка)
; --- Добавление символа новой строки ---
mov byte [r15 + rbx], 10 ; Запись LF (line feed, символ новой строки '\n')
inc rbx ; Увеличение позиции (rbx = итоговая длина строки)
; --- Запись сформированной строки в файл ---
; syscall: write(fd, buffer, length)
mov rax, 1
mov rdi, r14
mov rsi, r15 ; Адрес начала строки (io_file_buffer)
mov rdx, rbx ; Длина строки (количество записанных байт)
syscall
pop r15
pop r14
pop r13
pop r12
pop rbx
mov rsp, rbp
pop rbp
ret
; ----------------------------------------------------------------------------
; write_int32_to_fd_float
;
; Записывает знаковое 32-битное целое число в файловый дескриптор
; в текстовом виде (десятичный формат) с завершающим символом LF.
; Поддерживает отрицательные числа.
; Суффикс _float используется для избежания конфликтов имён.
;
; ПРИМЕНЕНИЕ:
; • Запись результатов вычислений в файл
; • Экспорт данных в текстовом формате
; • Логирование числовых значений
;
; ДИАПАЗОН: -2147483648 до 2147483647 (int32_t)
; ФОРМАТ ЗАПИСИ: "[-]<число>\n"
;
; ПРИМЕРЫ:
; write_int32_to_fd_float(3, 0) → "0\n"
; write_int32_to_fd_float(3, 12345) → "12345\n"
; write_int32_to_fd_float(3, -98765) → "-98765\n"
;
; ПРИМЕРЫ ИСПОЛЬЗОВАНИЯ:
; C-style:
; int fd = open("output.txt", O_WRONLY | O_CREAT, 0644);
; write_int32_to_fd_float(fd, -12345);
; write_int32_to_fd_float(fd, 67890);
; close(fd);
; // Файл содержит: "-12345\n67890\n"
;
; Assembly:
; ; Открыть файл для записи
; mov rax, 2 ; sys_open
; lea rdi, [filename]
; mov rsi, 0x241 ; O_WRONLY | O_CREAT
; mov rdx, 0x1A4 ; 0644
; syscall
; mov r15, rax ; Сохранить FD
;
; ; Записать число
; mov rdi, r15
; mov esi, -12345
; call write_int32_to_fd_float
;
; ; Закрыть файл
; mov rax, 3 ; sys_close
; mov rdi, r15
; syscall
;
; @param rdi Файловый дескриптор (int fd)
; esi Значение для записи (int32_t)
; @return rax Количество записанных байт (или код ошибки от write)
; @uses rbx, rdx, r12, r13, r14
;
; @complexity O(log₁₀(n)), где n - абсолютное значение числа
; @memory O(1), использует фиксированный буфер 64 байта
; ----------------------------------------------------------------------------
write_int32_to_fd_float:
push rbp
mov rbp, rsp
push rbx
push r12
push r13
push r14
mov r14, rdi ; Сохранение файлового дескриптора
movsxd rax, esi ; Знаковое расширение int32 → int64
; --- Формирование строки справа налево в io_file_buffer ---
lea r13, [io_file_buffer + 63] ; r13 = указатель на конец буфера
mov byte [r13], 10 ; Установка LF (символ новой строки) в конце
dec r13 ; Смещение указателя назад
; --- Специальная обработка нуля ---
test rax, rax ; Проверка: число равно нулю?
jnz .check_sign ; Если нет - обработка знака
mov byte [r13], '0' ; Запись символа '0'
jmp .write ; Переход к записи в файл
; --- Обработка знака ---
.check_sign:
xor rbx, rbx ; rbx = флаг знака (0 по умолчанию)
test rax, rax ; Проверка знака числа (установка флагов)
jge .convert ; Если >= 0 - переход к конвертации
neg rax ; Взятие модуля (двухкомплементное отрицание)
mov rbx, 1 ; Установка флага отрицательного числа
; --- Конвертация числа в строку (справа налево) ---
; Инвариант цикла: r13 указывает на следующую позицию для записи
.convert:
dec r13 ; Смещение указателя назад
xor rdx, rdx ; Обнуление rdx перед делением
mov r12, 10 ; Делитель = 10 (основание системы счисления)
div r12 ; rax = rax / 10, rdx = rax % 10 (остаток - цифра)
add dl, '0' ; Преобразование цифры (0-9) в ASCII ('0'-'9')
mov [r13], dl ; Запись ASCII-символа в буфер
test rax, rax ; Проверка: остались ли ещё цифры?
jnz .convert ; Если да - продолжение конвертации
; --- Добавление знака минус ---
test rbx, rbx ; Проверка флага отрицательного числа
jz .write ; Если положительное - переход к записи
dec r13 ; Смещение указателя назад
mov byte [r13], '-' ; Запись символа минуса
; --- Запись строки в файл ---
.write:
; syscall: write(fd, string, length)
mov rax, 1
mov rdi, r14
mov rsi, r13 ; Адрес начала строки
lea rdx, [io_file_buffer + 64] ; Адрес конца буфера
sub rdx, r13 ; Вычисление длины: конец - начало
syscall
pop r14
pop r13
pop r12
pop rbx
pop rbp
ret
🏛️ 5. Архитектурные концепции Layer 3
Данный раздел дополняет концепции, описанные в статье «Ввод-вывод на чистом NASM: Готовые модули (без libc)». Там рассматривается двухслойная модель (Layer 1 — парсинг, Layer 2 — интерактивный I/O), буферизация stdin, weak symbols и трёхуровневая обработка ошибок.
Здесь мы фокусируемся на новых принципах, которые вводит Layer 3 (файловый ввод-вывод).
Концепция: Коды возврата вместо exit()
Проблема:
Layer 2 (интерактивный режим) завершает программу при первой же ошибке ввода: вывел сообщение в stderr, вызвал exit(1). Это удобно для диалоговых утилит, но неприемлемо для batch-обработки файлов.
Сценарий 1: Интерактивный режим (Layer 2)
┌─────────────────────────────────────────────────────────────┐
│ КАЛЬКУЛЯТОР (интерактивная программа) │
├─────────────────────────────────────────────────────────────┤
│ Пользователь вводит: "abc" (не число) │
│ ↓ │
│ read_signed_input_vars: │
│ 1. Чтение из stdin → "abc" │
│ 2. Вызов parse_int16 → rdx=1 (ошибка формата) │
│ 3. Проверка: test rdx, rdx → ошибка! │
│ 4. Вывод в stderr: "ERROR: Invalid number format" │
│ 5. syscall exit(1) │
│ ↓ │
│ ПРОГРАММА ЗАВЕРШЕНА │
│ │
│ ✅ ДЛЯ КАЛЬКУЛЯТОРА ЭТО OK: │
│ Пользователь видит ошибку, перезапускает программу │
└─────────────────────────────────────────────────────────────┘Сценарий 2: Файловый режим с exit() (ПРОБЛЕМА)
┌─────────────────────────────────────────────────────────────┐
│ ФАЙЛ data.txt СОДЕРЖИТ 10000 ЧИСЕЛ: │
│ "100 200 abc 400 500 600 ... 10000" │
│ ↑ ошибка на строке 3 │
├─────────────────────────────────────────────────────────────┤
│ ГИПОТЕТИЧЕСКИЙ Layer 3 с exit(): │
│ │
│ Число 1: 100 ✅ OK │
│ Число 2: 200 ✅ OK │
│ Число 3: "abc" → parse_int16 → rdx=1 │
│ ↓ │
│ ❌ exit(1) │
│ │
│ РЕЗУЛЬТАТ: │
│ - Обработано 2 из 10000 чисел (0.02%) │
│ - 9997 корректных чисел ПРОИГНОРИРОВАНЫ │
│ - Пользователь не знает, где именно ошибка │
│ - Нет возможности пропустить проблемную строку │
│ │
│ ❌ ЭТО КАТАСТРОФА ДЛЯ BATCH-ОБРАБОТКИ │
└─────────────────────────────────────────────────────────────┘Сценарий 3: Файловый режим с кодами возврата (РЕШЕНИЕ)
┌─────────────────────────────────────────────────────────────┐
│ ТОТ ЖЕ ФАЙЛ: "100 200 abc 400 500 ..." │
├─────────────────────────────────────────────────────────────┤
│ Layer 3 с кодами возврата: │
│ │
│ Число 1: read_int16_stream → rax=1 (успех), value=100 │
│ Программа: сохраняем 100, продолжаем │
│ │
│ Число 2: read_int16_stream → rax=1 (успех), value=200 │
│ Программа: сохраняем 200, продолжаем │
│ │
│ Число 3: read_int16_stream → rax=-2 (ошибка формата!) │
│ ┌─────────────────────────────────────────┐ │
│ │ ПРОГРАММА РЕШАЕТ, ЧТО ДЕЛАТЬ: │ │
│ │ - Вариант A: Пропустить, продолжить │ │
│ │ - Вариант B: Логировать, продолжить │ │
│ │ - Вариант C: Остановиться │ │
│ └─────────────────────────────────────────┘ │
│ Программа: логируем "строка 3: ошибка", skip │
│ │
│ Число 4: read_int16_stream → rax=1 (успех), value=400 │
│ Программа: сохраняем 400, продолжаем │
│ │
│ ... │
│ │
│ Число 10000: read_int16_stream → rax=0 (EOF) │
│ Программа: завершаем цикл нормально │
│ │
│ РЕЗУЛЬТАТ: │
│ - Обработано 9999 из 10000 чисел (99.99%) │
│ - Ошибка залогирована с точным местоположением │
│ - Программа завершилась успешно │
│ │
│ ✅ ЭТО ПРАВИЛЬНОЕ ПОВЕДЕНИЕ ДЛЯ BATCH-ОБРАБОТКИ │
└─────────────────────────────────────────────────────────────┘Таблица кодов возврата Layer 3
Код rax |
Значение | Действие программиста |
|---|---|---|
> 0 |
Количество прочитанных байт | ✅ Успех, данные валидны |
0 |
EOF (конец файла) | Нормальное завершение цикла |
-1 |
I/O error (read syscall) |
Проверить дескриптор, закрыть файл |
-2 |
Ошибка формата (не число) | Пропустить / логировать |
-3 |
Переполнение типа данных | Пропустить / использовать int32 |
-4 |
Переполнение буфера (>30 символов) | Увеличить буфер / прервать |
Сравнение философий
Философия: Программа общается с человеком. Ошибка = нет смысла продолжать.
; Внутри read_signed_input_vars:
call parse_int16
test rdx, rdx
jnz .handle_error
.handle_error:
; stderr: "ERROR: ..."
mov rax, 60 ; sys_exit
mov rdi, 1
syscall ; ← УБИЛИ ПРОЦЕСС
ПОСЛЕДСТВИЯ
- Простота для программиста: не нужно проверять результат
- Идеально для CLI-утилит
- Неприменимо для batch-обработки
Философия: Программа обрабатывает данные. Решение об ошибке — за вызывающим кодом.
; Внутри read_int16_stream:
call parse_int16
test rdx, rdx
jnz .parse_error
.parse_error:
mov rax, rdx ; Копируем код ошибки
neg rax ; 1 → -1
dec rax ; -1 → -2 (формат)
jmp .ret ; ← ВОЗВРАЩАЕМСЯ, НЕ УБИВАЕМ
ПОСЛЕДСТВИЯ
- Программист обязан проверять
raxпосле каждого вызова - Гибкость: skip/log/abort — на выбор
- Единственный способ для batch-обработки
Концепция: Потоковое чтение с буферизацией
Проблема:
При чтении файла с множеством чисел наивный подход — один read() syscall на каждое число. Это убивает производительность: ~1000 тактов на syscall × 10000 чисел = 10 миллионов тактов впустую.
Сценарий 1: Однократное чтение (read_*_from_fd)
┌─────────────────────────────────────────────────────────────┐
│ ФАЙЛ: "12345" │
├─────────────────────────────────────────────────────────────┤
│ read_int16_from_fd(fd, &result): │
│ │
│ 1. syscall read(fd, io_file_buffer, 31) │
│ Ядро читает: "12345" (5 байт) │
│ Возвращает: rax = 5 │
│ │
│ 2. Добавляем null-терминатор: │
│ io_file_buffer = '\0' │
│ Буфер: "12345\0" │
│ │
│ 3. call parse_int16 │
│ rax = 12345, rdx = 0 (успех) │
│ │
│ 4. Сохраняем результат и возвращаемся │
│ │
│ ✅ ХОРОШО ДЛЯ ОДНОГО ЧИСЛА │
│ ❌ ПЛОХО ДЛЯ ФАЙЛА С 10000 ЧИСЕЛ: │
│ 10000 syscall = ~10 миллионов тактов │
└─────────────────────────────────────────────────────────────┘Сценарий 2: Потоковое чтение (read_*_stream)
┌─────────────────────────────────────────────────────────────┐
│ ФАЙЛ: "100 200 300 400 500 ... 10000\\n" (50000 байт) │
├─────────────────────────────────────────────────────────────┤
│ ПЕРВЫЙ ВЫЗОВ read_int16_stream(fd, &result): │
│ │
│ 1. Проверка: [io_file_stream_size] == 0? → Да, пуст │
│ │
│ 2. syscall read(fd, io_file_stream_buffer, 128) │
│ Ядро читает 128 байт: "100 200 300 400 500 600 7..." │
│ io_file_stream_size = 128 │
│ io_file_stream_pos = 0 │
│ │
│ 3. Извлекаем символы из буфера (БЕЗ syscall!): │
│ '1' ← buffer, pos=1, size=127 │
│ '0' ← buffer, pos=2, size=126 │
│ '0' ← buffer, pos=3, size=125 │
│ ' ' ← buffer → КОНЕЦ ЧИСЛА │
│ │
│ 4. Парсим "100", возвращаем rax=1, result=100 │
│ │
│ СОСТОЯНИЕ: pos=4, size=124 (ещё 124 байта в буфере!) │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ ВТОРОЙ ВЫЗОВ read_int16_stream(fd, &result): │
├─────────────────────────────────────────────────────────────┤
│ 1. Проверка: [io_file_stream_size] == 124? → НЕТ syscall! │
│ │
│ 2. Извлекаем символы из буфера: │
│ '2' ← buffer, pos=5, size=123 │
│ '0' ← buffer, pos=6, size=122 │
│ '0' ← buffer, pos=7, size=121 │
│ ' ' ← buffer → КОНЕЦ ЧИСЛА │
│ │
│ 3. Парсим "200", возвращаем rax=1, result=200 │
│ │
│ БЕЗ ЕДИНОГО SYSCALL! │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ ...после ~30 чисел буфер исчерпан... │
├─────────────────────────────────────────────────────────────┤
│ ВЫЗОВ №31: read_int16_stream(fd, &result): │
│ │
│ 1. Проверка: [io_file_stream_size] == 0? → Да, пуст │
│ │
│ 2. syscall read(fd, io_file_stream_buffer, 128) │
│ Ядро читает следующие 128 байт │
│ io_file_stream_size = 128 │
│ io_file_stream_pos = 0 │
│ │
│ 3. Продолжаем чтение из нового буфера... │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ ИТОГО ДЛЯ 10000 ЧИСЕЛ: │
├─────────────────────────────────────────────────────────────┤
│ Побайтовый подход: ~40000 syscall (~40 млн тактов) │
│ Однократный подход: ~10000 syscall (~10 млн тактов) │
│ Потоковый подход: ~400 syscall (~400 тыс тактов) │
│ │
│ УСКОРЕНИЕ: ×25 по сравнению с однократным чтением │
│ ×100 по сравнению с побайтовым │
└─────────────────────────────────────────────────────────────┘Архитектура потокового буфера
┌─────────────────────────────────────────────────────────────┐
│ ГЛОБАЛЬНОЕ СОСТОЯНИЕ (в .bss) │
├─────────────────────────────────────────────────────────────┤
│ io_file_stream_buffer: resb 128 ← Буфер данных │
│ io_file_stream_pos: resq 1 ← Текущая позиция │
│ io_file_stream_size: resq 1 ← Остаток непрочитанных│
│ io_file_char_buf: resb 1 ← Текущий символ │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ ВНУТРЕННЯЯ ФУНКЦИЯ .read_char │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────┐ │
│ │ Проверка: есть данные в буфере? │ │
│ │ mov rax, [io_file_stream_size] │ │
│ │ test rax, rax │ │
│ └─────────────────────────────────┘ │
│ │ │
│ ┌──────┴──────┐ │
│ │ │ │
│ ▼ ▼ │
│ size == 0 size > 0 │
│ (буфер пуст) (данные есть) │
│ │ │ │
│ ▼ ▼ │
│ ┌─────────┐ ┌───────────────────────────────┐ │
│ │ syscall │ │ Извлечь символ: │ │
│ │ read │ │ rcx = [io_file_stream_pos] │ │
│ │ (128 │ │ al = [buffer + rcx] │ │
│ │ байт) │ │ [io_file_char_buf] = al │ │
│ │ │ │ pos++ │ │
│ │ size=n │ │ size-- │ │
│ │ pos=0 │ │ rax = 1 (успех) │ │
│ └─────────┘ └───────────────────────────────┘ │
│ │ │ │
│ └─────┬───────┘ │
│ ▼ │
│ Символ в io_file_char_buf │
└─────────────────────────────────────────────────────────────┘Концепция: Разделение буферов по источникам
Проблема:
Если использовать общие буферы для stdin (Layer 2) и файлового ввода (Layer 3), возникает состязание за данные — один слой перезаписывает данные другого.
Сценарий: Конфликт буферов (БАГ)
┌─────────────────────────────────────────────────────────────┐
│ ПРОГРАММА: Запрашивает подтверждение, затем читает файл │
├─────────────────────────────────────────────────────────────┤
│ 1. Пользователь вводит: "yes" + Enter │
│ read_signed_input читает в io_interactive_buffer: │
│ ┌───┬───┬───┬───┬───────────────────────────┐ │
│ │'y'│'e'│'s'│\n │ ... (остаток буфера) │ │
│ └───┴───┴───┴───┴───────────────────────────┘ │
│ io_interactive_pos = 0 │
│ io_interactive_size = 4 │
│ │
│ 2. БЕЗ РАЗДЕЛЕНИЯ БУФЕРОВ: │
│ read_int16_stream использует ТОТ ЖЕ буфер! │
│ ┌───┬───┬───┬───┬───────────────────────────┐ │
│ │'1'│'0'│'0'│' '│'2'│'0'│'0'│ ... (из файла)│ │
│ └───┴───┴───┴───┴───────────────────────────┘ │
│ │
│ ❌ ДАННЫЕ "yes\n" ПЕРЕЗАПИСАНЫ! │
│ Если программа попытается дочитать stdin — получит │
│ данные из файла вместо пользовательского ввода │
└─────────────────────────────────────────────────────────────┘Решение: Изолированные пространства имён
┌─────────────────────────────────────────────────────────────┐
│ РАЗДЕЛЬНЫЕ БУФЕРЫ │
├─────────────────────────────────────────────────────────────┤
│ LAYER 2 (stdin): │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ io_interactive_buffer: resb 128 ← Только для stdin │ │
│ │ io_interactive_pos: resq 1 │ │
│ │ io_interactive_size: resq 1 │ │
│ └────────────────────────────────────────────────────────┘ │
│ │
│ LAYER 3 (файлы): │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ io_file_buffer: resb 32 ← Однократное чтение│ │
│ │ io_file_stream_buffer: resb 128 ← Потоковое чтение │ │
│ │ io_file_stream_pos: resq 1 │ │
│ │ io_file_stream_size: resq 1 │ │
│ │ io_file_char_buf: resb 1 ← Текущий символ │ │
│ └────────────────────────────────────────────────────────┘ │
│ │
│ ✅ НЕЗАВИСИМЫЕ СОСТОЯНИЯ │
│ - Чтение из stdin не влияет на чтение из файла │
│ - Можно миксовать интерактивный и файловый ввод │
│ - Каждый буфер сохраняет своё состояние (pos, size) │
└─────────────────────────────────────────────────────────────┘Таблица буферов по назначению
| Буфер | Размер | Слой | Назначение |
|---|---|---|---|
io_interactive_buffer |
128 байт | Layer 2 | Чтение из stdin |
io_interactive_pos |
8 байт | Layer 2 | Позиция в буфере stdin |
io_interactive_size |
8 байт | Layer 2 | Остаток байт в буфере stdin |
io_file_buffer |
32-64 байта | Layer 3 | Однократное чтение из файла |
io_file_stream_buffer |
128 байт | Layer 3 | Потоковое чтение из файла |
io_file_stream_pos |
8 байт | Layer 3 | Позиция в файловом потоке |
io_file_stream_size |
8 байт | Layer 3 | Остаток байт в файловом потоке |
io_file_char_buf |
1 байт | Layer 3 | Текущий извлечённый символ |
Концепция: Автоматический пропуск whitespace
Проблема:
Текстовые файлы могут содержать произвольное форматирование: пробелы, табы, переносы строк в любых комбинациях. Наивный парсер сломается на втором пробеле или переносе строки.
Сценарий: Проблемный файл
┌─────────────────────────────────────────────────────────────────┐
│ ФАЙЛ numbers.txt (реальный файл, созданный в редакторе): │
├─────────────────────────────────────────────────────────────────┤
│ 100 200 300 │
│ 400 │
│ 500 │
│ │
│ БАЙТОВОЕ ПРЕДСТАВЛЕНИЕ: │
│ '1' '0' '0' ' ' ' ' '2' '0' '0' ' ' ' ' ' ' ' ' '3' '0' │
│ ↑ ↑ ↑ ↑ │
│ множественные тоже пробелы │
│ пробелы │
│ │
│ '0' '\n' '4' '0' '0' '\n' ' ' ' ' '5' '0' '0' '\n' │
│ ↑ ↑ ↑ ↑ │
│ перенос перенос ведущие │
│ строки строки пробелы │
└─────────────────────────────────────────────────────────────────┘Алгоритм пропуска whitespace
┌─────────────────────────────────────────────────────────────┐
│ ФУНКЦИЯ read_*_stream: ЦИКЛ ПРОПУСКА │
├─────────────────────────────────────────────────────────────┤
│ │
│ .skip_whitespace: │
│ call .read_char ; Читаем один символ │
│ test rax, rax │
│ jle .handle_read_error ; EOF или ошибка │
│ │
│ movzx rax, byte [io_file_char_buf] │
│ │
│ cmp al, ' ' ; Пробел (ASCII 32)? │
│ je .skip_whitespace ; → Пропускаем │
│ │
│ cmp al, 10 ; LF (ASCII 10)? │
│ je .skip_whitespace ; → Пропускаем │
│ │
│ cmp al, 9 ; TAB (ASCII 9)? │
│ je .skip_whitespace ; → Пропускаем │
│ │
│ cmp al, 13 ; CR (ASCII 13)? │
│ je .skip_whitespace ; → Пропускаем │
│ │
│ ; Символ НЕ whitespace → начало числа! │
│ mov byte [r13 + rbx], al ; Сохраняем первый символ │
│ inc rbx ; Увеличиваем позицию │
│ jmp .read_number ; Переходим к чтению числа │
│ │
└─────────────────────────────────────────────────────────────┘Обрабатываемые символы
| Символ | ASCII | Название | Встречается в |
|---|---|---|---|
' ' |
32 | Пробел | Любой файл |
'\t' |
9 | Табуляция | TSV-файлы, исходный код |
'\n' |
10 | Line Feed | Unix, Linux, macOS |
'\r' |
13 | Carriage Return | Windows (\r\n), старые Mac |
Результат: Код корректно обрабатывает файлы с любым форматированием и из любой ОС.
Концепция: Dependency Injection на уровне syscall
Проблема:
Layer 2 жёстко привязан к stdin (дескриптор 0) и stdout (дескриптор 1). Невозможно переключить источник данных без переписывания кода.
Сценарий: Жёсткая привязка (Layer 2)
┌─────────────────────────────────────────────────────────────┐
│ LAYER 2: read_signed_input_vars │
├─────────────────────────────────────────────────────────────┤
│ │
│ layer2_read_string_signed: │
│ ... │
│ xor rax, rax ; sys_read │
│ xor rdi, rdi ; fd = 0 (STDIN) ← ЖЁСТКО! │
│ lea rsi, [buffer] │
│ mov rdx, 128 │
│ syscall │
│ │
│ ❌ НЕВОЗМОЖНО: │
│ - Читать из файла │
│ - Читать из пайпа │
│ - Читать из сокета │
│ - Тестировать без участия человека │
└─────────────────────────────────────────────────────────────┘Сценарий: Параметризованный дескриптор (Layer 3)
┌─────────────────────────────────────────────────────────────┐
│ LAYER 3: read_int16_stream(fd, &result) │
├─────────────────────────────────────────────────────────────┤
│ │
│ read_int16_stream: │
│ push rbp │
│ mov rbp, rsp │
│ ... │
│ mov r12, rdi ; r12 = fd (ПАРАМЕТР!) │
│ ... │
│ │
│ .read_char: │
│ ... │
│ xor rax, rax ; sys_read │
│ mov rdi, r12 ; fd из параметра ← ГИБКО! │
│ lea rsi, [buffer] │
│ mov rdx, 128 │
│ syscall │
│ │
│ ✅ МОЖНО ПЕРЕДАТЬ ЛЮБОЙ ДЕСКРИПТОР: │
│ - fd = результат open("file.txt") │
│ - fd = результат pipe() │
│ - fd = результат socket() │
│ - fd = 0 (stdin) — для совместимости │
└─────────────────────────────────────────────────────────────┘Примеры использования
┌─────────────────────────────────────────────────────────────┐
│ ПРИМЕР 1: Чтение из файла │
├─────────────────────────────────────────────────────────────┤
│ mov rax, 2 ; sys_open │
│ lea rdi, [filename] │
│ xor rsi, rsi ; O_RDONLY │
│ syscall │
│ mov r15, rax ; r15 = fd │
│ │
│ mov rdi, r15 ; Передаём fd в функцию │
│ lea rsi, [result] │
│ call read_int16_stream ; ← Читаем из файла! │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ ПРИМЕР 2: Чтение из пайпа (для тестирования) │
├─────────────────────────────────────────────────────────────┤
│ ; В тесте: echo "12345" | ./program │
│ │
│ xor rdi, rdi ; fd = 0 (stdin = пайп) │
│ lea rsi, [result] │
│ call read_int16_stream ; ← Читаем из пайпа! │
│ │
│ ; Программа не знает, откуда данные — stdin или пайп │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ ПРИМЕР 3: Автоматическое тестирование │
├─────────────────────────────────────────────────────────────┤
│ ; test_runner.sh: │
│ echo "100 200 300" > test_input.txt │
│ ./program < test_input.txt > test_output.txt │
│ diff test_output.txt expected_output.txt │
│ │
│ ; Программа использует read_int16_stream с fd=0 │
│ ; Shell подменяет stdin на файл — программа не меняется! │
└─────────────────────────────────────────────────────────────┘Принцип Dependency Injection:
- Зависимость (файловый дескриптор) передаётся извне, а не создаётся внутри функции
- Функция становится тестируемой: можно подставить любой источник данных
- Функция становится переиспользуемой: один код для файлов, пайпов, сокетов
📋 6. Практические примеры интеграции
Этот раздел демонстрирует реальные паттерны использования файлового ввода-вывода (Layer 3) в типичных задачах программирования на ассемблере. Все примеры используют модули из статьи «Ввод-вывод на чистом NASM: Готовые модули (без libc)» с добавленной поддержкой Layer 3 (см. раздел «🎓 4. Интеграция с модулями ввода-вывода» выше).
Чтение и сумма массива (int16_t)
Задача: прочитать неизвестное количество знаковых 16-битных чисел из файла, вычислить сумму и вывести результат.
Полный код sum_from_file.asm (io_signed)
; ============================================================================
; sum_numbers.asm
;
; Программа для суммирования знаковых 16-битных целых чисел из файла.
; Читает числа из текстового файла, вычисляет их сумму и выводит результат
; в стандартный поток вывода.
;
; АЛГОРИТМ:
; 1. Открытие входного файла для чтения
; 2. Инициализация суммы нулём
; 3. Циклическое чтение чисел из файла:
; - Чтение очередного числа (int16_t)
; - Знаковое расширение до int32_t
; - Добавление к накопленной сумме
; 4. Вывод итоговой суммы в формате "Result = <сумма>"
; 5. Корректное завершение с кодом возврата
;
; ФОРМАТ ВХОДНОГО ФАЙЛА:
; Текстовый файл с числами, разделёнными пробелами, табуляцией или
; переводами строк. Каждое число должно быть в диапазоне -32768..32767.
;
; Пример (numbers.txt):
; 10 20 30
; -5 15
; 100
;
; ФОРМАТ ВЫВОДА:
; Result = <сумма>\n
;
; Пример:
; Result = 170
;
; ОБРАБОТКА ОШИБОК:
; • Ошибка открытия файла → exit(1)
; • Ошибка парсинга числа → пропуск числа, продолжение чтения
; • EOF без данных → сумма = 0
;
; ИСПОЛЬЗУЕМЫЕ МОДУЛИ:
; • io_signed.asm - для чтения int16 и вывода int32
; ============================================================================
default rel ; Использовать RIP-relative адресацию по умолчанию
section .data
; Имя входного файла с числами для суммирования
fname db "numbers.txt", 0 ; Null-терминированная строка
section .bss
; Переменные программы
fd resq 1 ; Файловый дескриптор открытого файла (int fd)
current_num resw 1 ; Текущее прочитанное число (int16_t)
sum resd 1 ; Накопленная сумма всех чисел (int32_t)
section .text
; Точка входа программы
global _start
; Импортируемые функции из модуля io_signed.asm
extern read_int16_stream ; Потоковое чтение int16 из файла
extern print_signed_output_var ; Вывод int32 в формате "Result = N"
; ============================================================================
; ОСНОВНАЯ ПРОГРАММА
; ============================================================================
_start:
; --- Открытие входного файла ---
; syscall: open(filename, flags, mode)
; Открываем файл только для чтения (O_RDONLY = 0)
mov rax, 2
lea rdi, [fname]
xor rsi, rsi ; Флаги: O_RDONLY (0) - только чтение
syscall
; Проверка результата открытия файла
test rax, rax ; Проверка: rax < 0? (ошибка открытия)
js .error_open ; Если да - переход к обработке ошибки
mov [fd], rax ; Сохранение файлового дескриптора
; --- Инициализация суммы ---
mov dword [sum], 0 ; Установка начального значения суммы = 0
; --- Главный цикл чтения и суммирования ---
; Инвариант цикла: [sum] содержит сумму всех прочитанных чисел
.read_loop:
; Чтение очередного числа из файла
; Параметры: rdi = fd, rsi = адрес для сохранения числа
mov rdi, [fd]
lea rsi, [current_num] ; Адрес переменной для сохранения числа
call read_int16_stream
; Анализ результата чтения
; rax: 1 = успех, 0 = EOF, <0 = ошибка
test rax, rax ; Проверка значения rax
jz .calc_done ; rax = 0 → EOF, завершение чтения
js .read_loop ; rax < 0 → ошибка парсинга, пропускаем число
; --- Добавление прочитанного числа к сумме ---
; Знаковое расширение int16 → int32 для корректной арифметики
movsx eax, word [current_num] ; Загрузка и знаковое расширение:
; int16 (-32768..32767) → int32
add [sum], eax ; Добавление к накопленной сумме
jmp .read_loop
; --- Завершение вычислений ---
.calc_done:
; Закрытие входного файла
; syscall: close(fd)
mov rax, 3
mov rdi, [fd]
syscall
; --- Вывод результата в stdout ---
; print_signed_output_var автоматически выводит "Result = <число>\n"
mov edi, [sum] ; Загрузка итоговой суммы (int32)
call print_signed_output_var
; --- Успешное завершение программы ---
; syscall: exit(0)
mov rax, 60
xor rdi, rdi
syscall
; ============================================================================
; ОБРАБОТКА ОШИБОК
; ============================================================================
; --- Ошибка открытия файла ---
; Возникает, если файл не существует, нет прав доступа и т.д.
.error_open:
mov rdi, 1
jmp .exit
; --- Ошибка парсинга числа ---
; Возникает при некорректном формате числа в файле
; В текущей версии не используется, так как ошибки обрабатываются в цикле
.error_parse:
mov rdi, 2
; --- Общая точка завершения с ошибкой ---
.exit:
; syscall: exit(error_code)
mov rax, 60
; rdi уже содержит код ошибки (1 или 2)
syscall
Компиляция и тест:
echo "10 20 30 40 50" > numbers.txt
nasm -f elf64 io_signed.asm -o io_signed.o
nasm -f elf64 sum_from_file.asm -o sum_from_file.o
ld -o sum_from_file sum_from_file.o io_signed.o
./sum_from_file
Входной файл numbers.txt:
10 20 30
40 50
Ожидаемый вывод:
Result = 150
Генерация таблицы квадратов (int32_t запись)
Задача: вычислить и записать в файл квадраты чисел от 1 до 10.
Полный код write_squares.asm (io_signed запись)
; ============================================================================
; write_squares.asm
;
; Программа для генерации таблицы квадратов чисел от 1 до 10.
; Вычисляет квадраты целых чисел и записывает результаты в текстовый файл.
;
; АЛГОРИТМ:
; 1. Открытие выходного файла для записи (с созданием/перезаписью)
; 2. Цикл от 1 до 10:
; - Вычисление квадрата текущего числа (i²)
; - Запись результата в файл в текстовом формате
; 3. Закрытие файла
; 4. Корректное завершение программы
;
; ФОРМАТ ВЫХОДНОГО ФАЙЛА:
; Текстовый файл с квадратами чисел, каждое значение на отдельной строке.
;
; Пример (squares.txt):
; 1
; 4
; 9
; 16
; 25
; 36
; 49
; 64
; 81
; 100
;
; ОБРАБОТКА ОШИБОК:
; • Ошибка создания файла → exit(1)
; • Успешное выполнение → exit(0)
;
; ИСПОЛЬЗУЕМЫЕ МОДУЛИ:
; • io_signed.asm - для записи int32 в файл
;
; ОСОБЕННОСТИ РЕАЛИЗАЦИИ:
; • Используется регистр RBX для счётчика цикла, так как RCX может
; изменяться внутри системных вызовов, выполняемых write_int32_to_fd
; • Диапазон вычислений: 1² до 10² (результаты: 1..100)
; ============================================================================
default rel ; Использовать RIP-relative адресацию по умолчанию
section .data
; Имя выходного файла для записи квадратов
fname db "squares.txt", 0 ; Null-терминированная строка
section .bss
; Переменные программы
fd resq 1 ; Файловый дескриптор открытого файла (int fd)
section .text
; Точка входа программы
global _start
; Импортируемые функции из модуля io_signed.asm
extern write_int32_to_fd ; Запись int32 в файл в текстовом формате
; ============================================================================
; ОСНОВНАЯ ПРОГРАММА
; ============================================================================
_start:
; --- Открытие выходного файла ---
; syscall: open(filename, flags, mode)
mov rax, 2
lea rdi, [fname]
mov rsi, 0x241 ; Флаги: O_WRONLY | O_CREAT | O_TRUNC
; O_WRONLY (0x01) - только запись
; O_CREAT (0x40) - создать если не существует
; O_TRUNC (0x200) - очистить если существует
mov rdx, 0x1A4 ; Права доступа: 0644 (rw-r--r--)
; Владелец: чтение+запись
; Группа: чтение
; Остальные: чтение
syscall
; Проверка результата открытия файла
test rax, rax ; Проверка: rax < 0? (ошибка создания)
js .error ; Если да - переход к обработке ошибки
mov [fd], rax ; Сохранение файлового дескриптора
; --- Инициализация счётчика цикла ---
; ВАЖНО: используем RBX, так как RCX портится в syscall внутри write_int32_to_fd
mov rbx, 1 ; rbx = 1 (начальное значение счётчика)
; --- Главный цикл вычисления и записи квадратов ---
; Инвариант цикла: rbx ∈ [1, 10], в файл записаны квадраты от 1² до (rbx-1)²
.loop:
; Проверка условия завершения цикла
cmp rbx, 11 ; Проверка: rbx >= 11?
jge .done ; Если да - выход из цикла (обработано 1..10)
; --- Вычисление квадрата текущего числа ---
; Алгоритм: i² = i × i
mov rax, rbx ; rax = текущее значение счётчика (i)
imul rax, rax ; rax = rax × rax = i²
; --- Запись результата в файл ---
; Параметры write_int32_to_fd:
; rdi = файловый дескриптор
; esi = значение для записи (int32)
mov rdi, [fd] ; Загрузка файлового дескриптора
mov esi, eax ; esi = квадрат числа (младшие 32 бита rax)
call write_int32_to_fd ; Запись числа в файл (добавляется '\n')
; --- Переход к следующей итерации ---
inc rbx ; rbx++ (следующее число)
jmp .loop
; --- Завершение вычислений ---
.done:
; Закрытие выходного файла
; syscall: close(fd)
mov rax, 3
mov rdi, [fd]
syscall
; --- Успешное завершение программы ---
; syscall: exit(0)
mov rax, 60
xor rdi, rdi
syscall
; ============================================================================
; ОБРАБОТКА ОШИБОК
; ============================================================================
; --- Ошибка создания/открытия файла ---
; Возникает, если невозможно создать файл (нет прав, файловая система и т.д.)
.error:
; syscall: exit(1)
mov rax, 60
mov rdi, 1
syscall
Компиляция и тест:
nasm -f elf64 io_signed.asm -o io_signed.o
nasm -f elf64 write_squares.asm -o write_squares.o
ld -o write_squares write_squares.o io_signed.o
./write_squares
cat squares.txt
Ожидаемый выходной файл squares.txt:
1
4
9
16
25
36
49
64
81
100
Фильтрация и обработка беззнаковых чисел (uint16_t)
Задача: прочитать беззнаковые 16-битные числа из файла, удвоить каждое и записать результаты в новый файл.
Полный код process_unsigned.asm (io_unsigned)
; ============================================================================
; process_unsigned.asm
;
; Программа для обработки беззнаковых 16-битных чисел из файла.
; Читает числа типа uint16 из входного файла, удваивает каждое значение
; и записывает результаты в выходной файл в формате uint32.
;
; АЛГОРИТМ:
; 1. Открытие входного файла для чтения (unsigned.txt)
; 2. Открытие выходного файла для записи (doubled.txt, с созданием/перезаписью)
; 3. Цикл обработки:
; - Чтение очередного uint16 числа из входного файла
; - Удвоение значения (value × 2)
; - Запись результата в выходной файл как uint32
; - Повтор до достижения конца файла (EOF)
; 4. Закрытие обоих файлов
; 5. Корректное завершение программы
;
; ФОРМАТ ВХОДНОГО ФАЙЛА:
; Текстовый файл с беззнаковыми 16-битными числами (uint16).
; Каждое число на отдельной строке.
; Диапазон значений: 0..65535
;
; Пример (unsigned.txt):
; 100
; 200
; 32767
; 65535
;
; ФОРМАТ ВЫХОДНОГО ФАЙЛА:
; Текстовый файл с беззнаковыми 32-битными числами (uint32).
; Каждое значение — удвоенное входное число.
;
; Пример (doubled.txt):
; 200
; 400
; 65534
; 131070
;
; ОБРАБОТКА ОШИБОК:
; • Ошибка открытия входного/выходного файла → exit(1)
; • Ошибка чтения числа из входного файла → exit(2)
; • Успешное выполнение → exit(0)
;
; ИСПОЛЬЗУЕМЫЕ МОДУЛИ:
; • io_unsigned.asm - для чтения uint16 и записи uint32
;
; ОСОБЕННОСТИ РЕАЛИЗАЦИИ:
; • Используется регистр RBX для счётчика обработанных строк, так как
; он сохраняется через системные вызовы внутри внешних функций
; • Беззнаковое расширение (movzx) гарантирует корректную обработку uint16
; • Результат удвоения записывается как uint32 для предотвращения переполнения
; (максимум: 65535 × 2 = 131070, помещается в uint32)
; ============================================================================
default rel ; Использовать RIP-relative адресацию по умолчанию
section .data
; Имена файлов для обработки
fin db "unsigned.txt", 0 ; Входной файл с uint16 числами
fout db "doubled.txt", 0 ; Выходной файл с удвоенными значениями (uint32)
section .bss
; Переменные программы
fd_in resq 1 ; Файловый дескриптор входного файла (int fd)
fd_out resq 1 ; Файловый дескриптор выходного файла (int fd)
current resw 1 ; Буфер для текущего прочитанного числа (uint16)
section .text
; Точка входа программы
global _start
; Импортируемые функции из модуля io_unsigned.asm
extern read_uint16_stream ; Чтение uint16 из файла в текстовом формате
extern write_uint32_to_fd ; Запись uint32 в файл в текстовом формате
; ============================================================================
; ОСНОВНАЯ ПРОГРАММА
; ============================================================================
_start:
; --- Открытие входного файла ---
; syscall: open(filename, flags, mode)
mov rax, 2
lea rdi, [fin]
xor rsi, rsi ; Флаги: O_RDONLY (0x00) - только чтение
syscall
; Проверка результата открытия входного файла
test rax, rax ; Проверка: rax < 0? (ошибка открытия)
js .error ; Если да - переход к обработке ошибки
mov [fd_in], rax ; Сохранение файлового дескриптора входного файла
; --- Открытие выходного файла ---
; syscall: open(filename, flags, mode)
mov rax, 2
lea rdi, [fout]
mov rsi, 0x241 ; Флаги: O_WRONLY | O_CREAT | O_TRUNC
; O_WRONLY (0x01) - только запись
; O_CREAT (0x40) - создать если не существует
; O_TRUNC (0x200) - очистить если существует
mov rdx, 0x1A4 ; Права доступа: 0644 (rw-r--r--)
; Владелец: чтение+запись
; Группа: чтение
; Остальные: чтение
syscall
; Проверка результата открытия выходного файла
test rax, rax ; Проверка: rax < 0? (ошибка создания)
js .error ; Если да - переход к обработке ошибки
mov [fd_out], rax ; Сохранение файлового дескриптора выходного файла
; --- Инициализация счётчика обработанных строк ---
; ВАЖНО: используем RBX, так как он сохраняется через syscall в функциях I/O
xor rbx, rbx ; rbx = 0 (счётчик прочитанных строк)
; --- Главный цикл обработки чисел ---
; Инвариант цикла: обработано rbx чисел, результаты записаны в выходной файл
.loop:
inc rbx ; rbx++ (увеличение счётчика строк)
; --- Чтение очередного числа из входного файла ---
; Параметры read_uint16_stream:
; rdi = файловый дескриптор
; rsi = адрес буфера для записи прочитанного uint16
; Возвращает:
; rax > 0 - успешное чтение
; rax = 0 - конец файла (EOF)
; rax < 0 - ошибка чтения/парсинга
mov rdi, [fd_in] ; Загрузка файлового дескриптора входного файла
lea rsi, [current] ; Адрес буфера для числа
call read_uint16_stream ; Чтение uint16 из файла
; Проверка результата чтения
test rax, rax ; Проверка значения rax
jz .done ; Если rax = 0 → EOF, завершение обработки
js .error_read ; Если rax < 0 → ошибка чтения
; --- Загрузка и удвоение прочитанного числа ---
; ВАЖНО: movzx для беззнакового расширения uint16 → uint32
movzx eax, word [current] ; eax = прочитанное число (uint16 → uint32)
; Алгоритм удвоения: result = value × 2
add eax, eax ; eax = eax + eax = 2 × value
; --- Запись результата в выходной файл ---
; Параметры write_uint32_to_fd:
; rdi = файловый дескриптор
; esi = значение для записи (uint32)
mov rdi, [fd_out] ; Загрузка файлового дескриптора выходного файла
mov esi, eax ; esi = удвоенное значение (uint32)
call write_uint32_to_fd ; Запись числа в файл (добавляется '\n')
; --- Переход к следующей итерации ---
jmp .loop
; --- Завершение обработки (достигнут EOF) ---
.done:
; Закрытие входного файла
; syscall: close(fd)
mov rax, 3
mov rdi, [fd_in]
syscall
; Закрытие выходного файла
; syscall: close(fd)
mov rax, 3
mov rdi, [fd_out]
syscall
; --- Успешное завершение программы ---
; syscall: exit(0)
mov rax, 60
xor rdi, rdi
syscall
; ============================================================================
; ОБРАБОТКА ОШИБОК
; ============================================================================
; --- Ошибка чтения/парсинга числа из входного файла ---
; Возникает при некорректных данных или проблемах чтения
.error_read:
; syscall: exit(2)
mov rdi, 2
jmp .exit
; --- Ошибка открытия/создания файла ---
; Возникает, если невозможно открыть входной файл или создать выходной файл
; (нет прав доступа, файл не существует, файловая система и т.д.)
.error:
; syscall: exit(1)
mov rdi, 1
; --- Общая точка выхода с кодом ошибки ---
.exit:
; syscall: exit(rdi)
; rdi уже содержит код возврата (1 или 2)
mov rax, 60
syscall
Компиляция и тест:
echo "5 10 100 1000 65535" > unsigned.txt
nasm -f elf64 io_unsigned.asm -o io_unsigned.o
nasm -f elf64 process_unsigned.asm -o process_unsigned.o
ld -o process_unsigned process_unsigned.o io_unsigned.o
./process_unsigned
cat doubled.txt
Входной файл unsigned.txt:
5 10 100 1000 65535
Ожидаемый выходной файл doubled.txt:
10
20
200
2000
131070
Обработка чисел с плавающей точкой (float)
Задача: прочитать числа типа float из файла, умножить каждое на 2.5 и записать результаты.
Полный код process_floats.asm (io_float)
; ============================================================================
; process_floats.asm
;
; Программа для обработки вещественных чисел одинарной точности (float).
; Читает числа типа float из входного файла, масштабирует каждое значение
; умножением на константу 2.5 и записывает результаты в выходной файл.
;
; АЛГОРИТМ:
; 1. Открытие входного файла для чтения (floats.txt)
; 2. Открытие выходного файла для записи (scaled.txt, с созданием/перезаписью)
; 3. Цикл обработки:
; - Чтение очередного float числа из входного файла
; - Загрузка значения в стек сопроцессора FPU
; - Умножение на константу 2.5
; - Сохранение результата из FPU в память
; - Запись результата в выходной файл
; - Повтор до достижения конца файла (EOF)
; - При ошибке чтения строки — пропуск и продолжение
; 4. Закрытие обоих файлов
; 5. Корректное завершение программы
;
; ФОРМАТ ВХОДНОГО ФАЙЛА:
; Текстовый файл с вещественными числами одинарной точности (float).
; Каждое число на отдельной строке в десятичной нотации.
; Диапазон значений: ±3.4 × 10³⁸ (IEEE 754 single precision)
;
; Пример (floats.txt):
; 10.0
; -5.5
; 3.14159
; 100.25
;
; ФОРМАТ ВЫХОДНОГО ФАЙЛА:
; Текстовый файл с вещественными числами одинарной точности (float).
; Каждое значение — масштабированное входное число (× 2.5).
;
; Пример (scaled.txt):
; 25.0
; -13.75
; 7.8539749
; 250.625
;
; ОБРАБОТКА ОШИБОК:
; • Ошибка открытия входного/выходного файла → exit(1)
; • Ошибка чтения/парсинга числа → пропуск строки, продолжение обработки
; • Успешное выполнение → exit(0)
;
; ИСПОЛЬЗУЕМЫЕ МОДУЛИ:
; • io_float.asm - для чтения и записи float в текстовом формате
;
; ОСОБЕННОСТИ РЕАЛИЗАЦИИ:
; • Использование стека сопроцессора x87 FPU для вычислений с плавающей точкой
; • Константа масштабирования (2.5) хранится в секции .data как float (4 байта)
; • Некорректные строки пропускаются без прерывания программы
; • Битовое представление float передаётся в write_float_to_fd через регистр esi
; ============================================================================
default rel ; Использовать RIP-relative адресацию по умолчанию
section .data
; Имена файлов для обработки
fin db "floats.txt", 0 ; Входной файл с float числами
fout db "scaled.txt", 0 ; Выходной файл с масштабированными значениями
scale_factor dd 2.5 ; Константа масштабирования (float, 4 байта)
section .bss
; Переменные программы
fd_in resq 1 ; Файловый дескриптор входного файла (int fd)
fd_out resq 1 ; Файловый дескриптор выходного файла (int fd)
current resd 1 ; Буфер для текущего числа (float, 4 байта)
section .text
; Точка входа программы
global _start
; Импортируемые функции из модуля io_float.asm
extern read_float_stream ; Чтение float из файла в текстовом формате
extern write_float_to_fd ; Запись float в файл в текстовом формате
; ============================================================================
; ОСНОВНАЯ ПРОГРАММА
; ============================================================================
_start:
; --- Открытие входного файла ---
; syscall: open(filename, flags, mode)
mov rax, 2
lea rdi, [fin]
xor rsi, rsi ; Флаги: O_RDONLY (0x00) - только чтение
syscall
; Проверка результата открытия входного файла
test rax, rax ; Проверка: rax < 0? (ошибка открытия)
js .error ; Если да - переход к обработке ошибки
mov [fd_in], rax ; Сохранение файлового дескриптора входного файла
; --- Открытие выходного файла ---
; syscall: open(filename, flags, mode)
mov rax, 2
lea rdi, [fout]
mov rsi, 0x241 ; Флаги: O_WRONLY | O_CREAT | O_TRUNC
; O_WRONLY (0x01) - только запись
; O_CREAT (0x40) - создать если не существует
; O_TRUNC (0x200) - очистить если существует
mov rdx, 0x1A4 ; Права доступа: 0644 (rw-r--r--)
; Владелец: чтение+запись
; Группа: чтение
; Остальные: чтение
syscall
; Проверка результата открытия выходного файла
test rax, rax ; Проверка: rax < 0? (ошибка создания)
js .error ; Если да - переход к обработке ошибки
mov [fd_out], rax ; Сохранение файлового дескриптора выходного файла
; --- Главный цикл обработки чисел ---
; Инвариант цикла: все корректные числа из обработанных строк масштабированы
; и записаны в выходной файл; некорректные строки пропущены
.loop:
; --- Чтение очередного числа из входного файла ---
; Параметры read_float_stream:
; rdi = файловый дескриптор
; rsi = адрес буфера для записи прочитанного float
; Возвращает:
; rax > 0 - успешное чтение
; rax = 0 - конец файла (EOF)
; rax < 0 - ошибка чтения/парсинга
mov rdi, [fd_in] ; Загрузка файлового дескриптора входного файла
lea rsi, [current] ; Адрес буфера для числа (float)
call read_float_stream ; Чтение float из файла
; Проверка результата чтения
test rax, rax ; Проверка значения rax
jz .done ; Если rax = 0 → EOF, завершение обработки
js .loop ; Если rax < 0 → ошибка, пропуск строки
; --- Вычисление масштабированного значения (FPU) ---
; Алгоритм: result = value × scale_factor
; Загрузка исходного значения в стек FPU
fld dword [current] ; ST(0) = прочитанное float число
; Умножение на константу масштабирования
fmul dword [scale_factor] ; ST(0) = ST(0) × 2.5
; Сохранение результата из стека FPU в память
fstp dword [current] ; [current] = ST(0), затем pop стека FPU
; После fstp стек FPU пуст
; --- Запись результата в выходной файл ---
; Параметры write_float_to_fd:
; rdi = файловый дескриптор
; esi = битовое представление float (4 байта, IEEE 754)
mov rdi, [fd_out] ; Загрузка файлового дескриптора выходного файла
mov esi, [current] ; esi = битовое представление результата (float)
call write_float_to_fd ; Запись числа в файл (добавляется '\n')
; --- Переход к следующей итерации ---
jmp .loop
; --- Завершение обработки (достигнут EOF) ---
.done:
; Закрытие входного файла
; syscall: close(fd)
mov rax, 3
mov rdi, [fd_in]
syscall
; Закрытие выходного файла
; syscall: close(fd)
mov rax, 3
mov rdi, [fd_out]
syscall
; --- Успешное завершение программы ---
; syscall: exit(0)
mov rax, 60
xor rdi, rdi
syscall
; ============================================================================
; ОБРАБОТКА ОШИБОК
; ============================================================================
; --- Ошибка открытия/создания файла ---
; Возникает, если невозможно открыть входной файл или создать выходной файл
; (нет прав доступа, файл не существует, файловая система и т.д.)
.error:
; syscall: exit(1)
mov rax, 60
mov rdi, 1
syscall
Компиляция и тест:
echo "1.0 2.5 10.0 -3.14" > floats.txt
nasm -f elf64 io_float.asm -o io_float.o
nasm -f elf64 process_floats.asm -o process_floats.o
ld -o process_floats process_floats.o io_float.o
./process_floats
cat scaled.txt
Входной файл floats.txt:
1.0 2.5 10.0 -3.14
Ожидаемый выходной файл scaled.txt:
2.500000
6.250000
25.000000
-7.850000
Пакетная обработка: множественные файлы (int16_t)
Задача: обработать несколько входных файлов, вычислить статистику (минимум, максимум, среднее) для каждого и записать результаты.
Полный код batch_stats.asm (io_signed)
; ============================================================================
; batch_stats.asm
;
; Программа для пакетного статистического анализа целочисленных данных.
; Обрабатывает несколько входных файлов, вычисляя для каждого файла
; статистики: минимум, максимум и среднее арифметическое значение.
; Результаты записываются в единый лог-файл с форматированными метками.
;
; АЛГОРИТМ:
; 1. Открытие выходного файла для записи статистик (stats.log)
; 2. Цикл по списку входных файлов:
; a. Открытие очередного входного файла из таблицы files[]
; b. Инициализация статистических аккумуляторов:
; - min_val = 2147483647 (MAX_INT32)
; - max_val = -2147483648 (MIN_INT32)
; - sum = 0
; - count = 0
; c. Чтение и обработка всех int16 чисел из файла:
; - Обновление минимума
; - Обновление максимума
; - Накопление суммы
; - Подсчёт количества чисел
; - При ошибке парсинга — пропуск строки
; d. Вычисление среднего: avg = sum / count
; e. Запись в лог форматированных строк:
; "Min = <значение>\n"
; "Max = <значение>\n"
; "Avg = <значение>\n"
; f. Закрытие входного файла
; 3. Закрытие выходного лог-файла
; 4. Корректное завершение программы
;
; ФОРМАТ ВХОДНЫХ ФАЙЛОВ:
; Текстовые файлы с целыми числами типа int16.
; Каждое число на отдельной строке.
; Диапазон значений: -32768..32767
;
; Пример (data1.txt):
; 10
; -5
; 20
; 15
;
; Пример (data2.txt):
; 100
; -50
; 75
;
; ФОРМАТ ВЫХОДНОГО ФАЙЛА:
; Лог-файл stats.log с результатами анализа всех входных файлов.
; Для каждого файла записываются три форматированные строки.
;
; Пример (stats.log) для двух файлов выше:
; Min = -5
; Max = 20
; Avg = 10
; Min = -50
; Max = 100
; Avg = 41
;
; СПИСОК ОБРАБАТЫВАЕМЫХ ФАЙЛОВ:
; Задаётся таблицей указателей files[] в секции .data:
; files[0] → "data1.txt"
; files[1] → "data2.txt"
; files[2] → NULL (sentinel, конец списка)
;
; ОБРАБОТКА ОШИБОК:
; • Ошибка парсинга числа → пропуск строки, продолжение обработки
; • Пустой файл (count = 0) → пропуск записи статистик
; • Успешное выполнение → exit(0)
;
; ИСПОЛЬЗУЕМЫЕ МОДУЛИ:
; • io_signed.asm - для чтения int16 и записи int32
;
; ОСОБЕННОСТИ РЕАЛИЗАЦИИ:
; • Используется регистр R12 для индекса в таблице файлов, так как
; он сохраняется через системные вызовы внутри внешних функций
; • Минимум инициализируется MAX_INT32, максимум — MIN_INT32
; • Знаковое расширение (movsx) int16 → int32 для корректных сравнений
; • Целочисленное деление с расширением знака (cdq) для вычисления среднего
; • Регистр EBX используется для временного хранения среднего значения
; • Текстовые префиксы ("Min = ", "Max = ", "Avg = ") записываются
; системным вызовом write перед записью чисел
; ============================================================================
default rel ; Использовать RIP-relative адресацию по умолчанию
section .data
; Имена файлов для обработки
file1 db "data1.txt", 0 ; Первый входной файл
file2 db "data2.txt", 0 ; Второй входной файл
fout db "stats.log", 0 ; Выходной лог-файл со статистиками
; Таблица указателей на имена входных файлов
files:
dq file1 ; files[0] → "data1.txt"
dq file2 ; files[1] → "data2.txt"
dq 0 ; Sentinel: NULL завершает список
; Текстовые префиксы для форматированного вывода
str_min db "Min = " ; Префикс для минимума (6 байт)
str_min_len equ $ - str_min
str_max db "Max = " ; Префикс для максимума (6 байт)
str_max_len equ $ - str_max
str_avg db "Avg = " ; Префикс для среднего (6 байт)
str_avg_len equ $ - str_avg
section .bss
; Переменные программы
fd_in resq 1 ; Файловый дескриптор текущего входного файла
fd_out resq 1 ; Файловый дескриптор выходного лог-файла
current_num resw 1 ; Буфер для текущего прочитанного числа (int16)
; Статистические аккумуляторы (int32)
min_val resd 1 ; Минимальное значение в текущем файле
max_val resd 1 ; Максимальное значение в текущем файле
sum resd 1 ; Сумма всех чисел в текущем файле
count resd 1 ; Количество чисел в текущем файле
section .text
; Точка входа программы
global _start
; Импортируемые функции из модуля io_signed.asm
extern read_int16_stream ; Чтение int16 из файла в текстовом формате
extern write_int32_to_fd ; Запись int32 в файл в текстовом формате
; ============================================================================
; ОСНОВНАЯ ПРОГРАММА
; ============================================================================
_start:
; --- Открытие выходного лог-файла ---
; syscall: open(filename, flags, mode)
mov rax, 2
lea rdi, [fout]
mov rsi, 0x241 ; Флаги: O_WRONLY | O_CREAT | O_TRUNC
; O_WRONLY (0x01) - только запись
; O_CREAT (0x40) - создать если не существует
; O_TRUNC (0x200) - очистить если существует
mov rdx, 0x1A4 ; Права доступа: 0644 (rw-r--r--)
syscall
mov [fd_out], rax ; Сохранение файлового дескриптора лог-файла
; --- Инициализация индекса таблицы файлов ---
; ВАЖНО: используем R12, так как он сохраняется через syscall в функциях I/O
xor r12, r12 ; r12 = 0 (индекс первого файла в таблице)
; ============================================================================
; ЦИКЛ ОБРАБОТКИ ВХОДНЫХ ФАЙЛОВ
; ============================================================================
; Инвариант: обработано r12 файлов, результаты записаны в stats.log
.process_files:
; --- Получение имени очередного входного файла ---
; Загрузка адреса из таблицы: files[r12]
lea rax, [files] ; rax = адрес начала таблицы files[]
mov rdi, [rax + r12*8] ; rdi = files[r12] (указатель на имя файла)
; Проверка sentinel (конец списка)
test rdi, rdi ; Проверка: rdi == NULL?
jz .all_done ; Если да - все файлы обработаны
; --- Открытие очередного входного файла ---
; syscall: open(filename, flags, mode)
; rdi уже содержит адрес имени файла
mov rax, 2
xor rsi, rsi ; Флаги: O_RDONLY (0x00) - только чтение
syscall
mov [fd_in], rax ; Сохранение файлового дескриптора входного файла
; --- Инициализация статистических аккумуляторов ---
; min = MAX_INT32 (чтобы любое число было меньше начального min)
; max = MIN_INT32 (чтобы любое число было больше начального max)
; sum = 0, count = 0
mov dword [min_val], 0x7FFFFFFF ; min = 2147483647
mov dword [max_val], 0x80000000 ; max = -2147483648
mov dword [sum], 0 ; sum = 0
mov dword [count], 0 ; count = 0
; ============================================================================
; ЦИКЛ ЧТЕНИЯ И ОБРАБОТКИ ЧИСЕЛ ИЗ ТЕКУЩЕГО ФАЙЛА
; ============================================================================
; Инвариант: обработано count чисел, аккумуляторы min/max/sum актуальны
.read_loop:
; --- Чтение очередного числа из текущего входного файла ---
; Параметры read_int16_stream:
; rdi = файловый дескриптор
; rsi = адрес буфера для записи прочитанного int16
; Возвращает:
; rax > 0 - успешное чтение
; rax = 0 - конец файла (EOF)
; rax < 0 - ошибка чтения/парсинга
mov rdi, [fd_in] ; Загрузка файлового дескриптора входного файла
lea rsi, [current_num] ; Адрес буфера для числа (int16)
call read_int16_stream ; Чтение int16 из файла
; Проверка результата чтения
test rax, rax ; Проверка значения rax
jz .file_done ; Если rax = 0 → EOF, файл обработан
js .read_loop ; Если rax < 0 → ошибка, пропуск строки
; --- Знаковое расширение int16 → int32 ---
; ВАЖНО: movsx для корректного сравнения знаковых чисел
movsx eax, word [current_num] ; eax = прочитанное число (int16 → int32)
; --- Обновление минимального значения ---
; Алгоритм: if (current < min) min = current
cmp eax, [min_val] ; Сравнение: eax < min_val?
jge .check_max ; Если eax >= min_val, пропуск обновления
mov [min_val], eax ; min_val = eax (новый минимум)
; --- Обновление максимального значения ---
; Алгоритм: if (current > max) max = current
.check_max:
cmp eax, [max_val] ; Сравнение: eax > max_val?
jle .add_sum ; Если eax <= max_val, пропуск обновления
mov [max_val], eax ; max_val = eax (новый максимум)
; --- Накопление суммы и подсчёт количества ---
.add_sum:
add [sum], eax ; sum += eax
inc dword [count] ; count++
jmp .read_loop ; Переход к чтению следующего числа
; ============================================================================
; ЗАВЕРШЕНИЕ ОБРАБОТКИ ТЕКУЩЕГО ФАЙЛА
; ============================================================================
.file_done:
; --- Закрытие текущего входного файла ---
; syscall: close(fd)
mov rax, 3
mov rdi, [fd_in]
syscall
; --- Вычисление среднего арифметического ---
; Алгоритм: avg = sum / count
mov eax, [sum] ; eax = sum
mov ecx, [count] ; ecx = count
; Проверка на пустой файл (count = 0)
test ecx, ecx ; Проверка: count == 0?
jz .next_file ; Если да - пропуск записи статистик
; Целочисленное деление со знаком
cdq ; Расширение знака: eax → edx:eax
idiv ecx ; eax = edx:eax / ecx (sum / count)
mov ebx, eax ; ebx = среднее (сохранение для последующей записи)
; --- Запись статистик в выходной лог-файл ---
; --- Запись "Min = <значение>\n" ---
; Запись префикса "Min = "
; syscall: write(fd, buffer, count)
mov rax, 1
mov rdi, [fd_out]
lea rsi, [str_min]
mov rdx, str_min_len
syscall
; Запись значения минимума
mov rdi, [fd_out] ; Загрузка файлового дескриптора лог-файла
mov esi, [min_val] ; esi = минимальное значение
call write_int32_to_fd ; Запись числа в лог (добавляется '\n')
; --- Запись "Max = <значение>\n" ---
; syscall: write(fd, buffer, count)
mov rax, 1
mov rdi, [fd_out]
lea rsi, [str_max]
mov rdx, str_max_len
syscall
; Запись значения максимума
mov rdi, [fd_out]
mov esi, [max_val] ; esi = максимальное значение
call write_int32_to_fd ; Запись числа в лог (добавляется '\n')
; --- Запись "Avg = <значение>\n" ---
; syscall: write(fd, buffer, count)
mov rax, 1
mov rdi, [fd_out]
lea rsi, [str_avg]
mov rdx, str_avg_len
syscall
; Запись значения среднего
mov rdi, [fd_out]
mov esi, ebx ; esi = среднее значение (из ebx)
call write_int32_to_fd ; Запись числа в лог (добавляется '\n')
; --- Переход к следующему файлу ---
.next_file:
inc r12 ; r12++ (следующий индекс в таблице files[])
jmp .process_files ; Обработка следующего файла
; ============================================================================
; ЗАВЕРШЕНИЕ ОБРАБОТКИ ВСЕХ ФАЙЛОВ
; ============================================================================
.all_done:
; --- Закрытие выходного лог-файла ---
; syscall: close(fd)
mov rax, 3
mov rdi, [fd_out]
syscall
; --- Успешное завершение программы ---
; syscall: exit(0)
mov rax, 60
xor rdi, rdi
syscall
Компиляция и тест:
echo "5 -10 15 20" > data1.txt
echo "100 -50 0 75" > data2.txt
nasm -f elf64 io_signed.asm -o io_signed.o
nasm -f elf64 batch_stats.asm -o batch_stats.o
ld -o batch_stats batch_stats.o io_signed.o
./batch_stats
cat stats.log
Входные файлы:
data1.txt:5 -10 15 20data2.txt:100 -50 0 75
Ожидаемый выходной файл stats.log:
Min = -10
Max = 20
Avg = 7
Min = -50
Max = 100
Avg = 31
📚 7. Полные примеры программ
Программа 1: Аналог команды cat
Создадим программу, читающую файл блоками и выводящую содержимое в stdout.
Алгоритм:
- Открыть файл для чтения
- Цикл: читать блок 4096 байт → писать в stdout
- При EOF — закрыть файл и завершиться
Полная реализация
; ============================================================================
; file_cat.asm - Аналог команды cat
; Использование: ./file_cat (читает input.txt)
; ============================================================================
section .data
filename db "input.txt", 0
error_open db "Error: Cannot open file input.txt", 10
error_open_len equ $ - error_open
section .bss
buffer resb 4096 ; Буфер размером со страницу памяти
fd resq 1 ; Файловый дескриптор
section .text
global _start
_start:
; === ОТКРЫТИЕ ФАЙЛА ===
mov rax, 2 ; open
lea rdi, [rel filename]
xor rsi, rsi ; O_RDONLY (0)
syscall
test rax, rax
js .error_open ; Если rax < 0
mov [fd], rax ; Сохранить дескриптор
; === ЦИКЛ ЧТЕНИЯ-ЗАПИСИ ===
.loop:
; Читать блок из файла
mov rax, 0 ; read
mov rdi, [fd]
lea rsi, [rel buffer]
mov rdx, 4096 ; Максимум 4096 байт
syscall
test rax, rax
js .error_read ; Ошибка чтения
jz .close_file ; EOF (rax = 0)
; Записать в stdout
mov rdx, rax ; !!! ВАЖНО: длина = прочитанные байты
mov rax, 1 ; write
mov rdi, 1 ; stdout
lea rsi, [rel buffer]
syscall
jmp .loop ; Следующий блок
; === ЗАКРЫТИЕ И ЗАВЕРШЕНИЕ ===
.close_file:
mov rax, 3 ; close
mov rdi, [fd]
syscall
mov rax, 60 ; exit
xor rdi, rdi ; код 0 (успех)
syscall
.error_open:
.error_read:
mov rax, 1 ; write
mov rdi, 2 ; stderr
lea rsi, [rel error_open]
mov rdx, error_open_len
syscall
mov rax, 60
mov rdi, 1 ; код 1 (ошибка)
syscall
Сборка и тестирование:
# Создать тестовый файл
echo 'Hello from NASM assembly!' > input.txt
# Сборка
nasm -f elf64 file_cat.asm -o file_cat.o
ld file_cat.o -o file_cat
# Запуск
./file_cat
# Вывод: Hello from NASM assembly!
Критический момент: Строка mov rdx, rax перед write. Мы записываем ровно столько байт, сколько прочитали, а не размер буфера.
Программа 2: Запись в Файл
Программа создаёт файл и записывает в него текстовое сообщение.
Полная реализация
; ============================================================================
; file_write.asm - Создание и запись в файл
; ============================================================================
section .data
filename db "output.txt", 0
message db "This file was created by NASM x86-64!", 10
msg_len equ $ - message
error_create db "Error: Cannot create file", 10
error_create_len equ $ - error_create
section .bss
fd resq 1
section .text
global _start
_start:
; === СОЗДАНИЕ ФАЙЛА ===
mov rax, 2 ; open
lea rdi, [rel filename]
mov rsi, 0o1101 ; O_WRONLY | O_CREAT | O_TRUNC
mov rdx, 0644o ; rw-r--r--
syscall
test rax, rax
js .error_create
mov [fd], rax
; === ЗАПИСЬ ДАННЫХ ===
mov rax, 1 ; write
mov rdi, [fd]
lea rsi, [rel message]
mov rdx, msg_len
syscall
cmp rax, msg_len ; Проверка полной записи
jne .error_write
; === ЗАКРЫТИЕ ===
mov rax, 3 ; close
mov rdi, [fd]
syscall
mov rax, 60 ; exit
xor rdi, rdi
syscall
.error_create:
.error_write:
mov rax, 1 ; write
mov rdi, 2 ; stderr
lea rsi, [rel error_create]
mov rdx, error_create_len
syscall
mov rax, 60
mov rdi, 1
syscall
Проверка результата:
# Сборка
nasm -f elf64 file_write.asm -o file_write.o
ld file_write.o -o file_write
# Запуск
./file_write
# Проверка содержимого
cat output.txt
# Вывод: This file was created by NASM x86-64!
# Проверка прав доступа
ls -l output.txt
# -rw-r--r-- ... output.txt
# └─ Права 0644 применились корректно
Программа 3: Копирование Файла
Полнофункциональная утилита копирования с обработкой ошибок.
Полная реализация
; ============================================================================
; file_copy.asm - Копирование файла
; Использование: ./file_copy (копирует source.txt → dest.txt)
; ============================================================================
section .data
src_file db "source.txt", 0
dst_file db "dest.txt", 0
err_src db "Error: Cannot open source.txt", 10
err_src_len equ $ - err_src
err_dst db "Error: Cannot create dest.txt", 10
err_dst_len equ $ - err_dst
section .bss
buffer resb 4096
fd_src resq 1
fd_dst resq 1
section .text
global _start
_start:
; === ОТКРЫТИЕ ИСХОДНОГО ФАЙЛА ===
mov rax, 2
lea rdi, [rel src_file]
xor rsi, rsi ; O_RDONLY
syscall
test rax, rax
js .error_src
mov [fd_src], rax
; === СОЗДАНИЕ ФАЙЛА НАЗНАЧЕНИЯ ===
mov rax, 2
lea rdi, [rel dst_file]
mov rsi, 0o1101 ; O_WRONLY | O_CREAT | O_TRUNC
mov rdx, 0644o
syscall
test rax, rax
js .error_dst
mov [fd_dst], rax
; === ЦИКЛ КОПИРОВАНИЯ ===
.copy_loop:
; Читать из источника
mov rax, 0
mov rdi, [fd_src]
lea rsi, [rel buffer]
mov rdx, 4096
syscall
test rax, rax
jle .done ; EOF или ошибка
mov r12, rax ; Сохранить количество байт
; Писать в назначение
mov rax, 1
mov rdi, [fd_dst]
lea rsi, [rel buffer]
mov rdx, r12
syscall
cmp rax, r12 ; Проверка полной записи
jne .done
jmp .copy_loop
; === ЗАКРЫТИЕ ФАЙЛОВ ===
.done:
mov rax, 3
mov rdi, [fd_src]
syscall
mov rax, 3
mov rdi, [fd_dst]
syscall
mov rax, 60
xor rdi, rdi
syscall
.error_src:
mov rax, 1
mov rdi, 2
lea rsi, [rel err_src]
mov rdx, err_src_len
syscall
jmp .exit_error
.error_dst:
; Закрыть исходный файл перед выходом
mov rax, 3
mov rdi, [fd_src]
syscall
mov rax, 1
mov rdi, 2
lea rsi, [rel err_dst]
mov rdx, err_dst_len
syscall
.exit_error:
mov rax, 60
mov rdi, 1
syscall
Тестирование:
# Создать исходный файл
echo "Original content for copying" > source.txt
# Сборка и запуск
nasm -f elf64 file_copy.asm -o file_copy.o
ld file_copy.o -o file_copy
./file_copy
# Проверка
cat dest.txt
# Вывод: Original content for copying
# Сравнение файлов
diff source.txt dest.txt
# Нет вывода = файлы идентичны
⚠️ 8. Обработка ошибок
Коды Ошибок Linux (errno)
Системные вызовы возвращают отрицательный errno при ошибке:
mov rax, 2
lea rdi, [rel filename]
xor rsi, rsi
syscall
test rax, rax
jns .success ; rax ≥ 0: успех
neg rax ; Преобразуем -errno → errno
; Теперь rax содержит положительный код ошибки
Основные Коды
| Код | Константа | Описание |
|---|---|---|
| 2 | ENOENT | Файл или директория не существует |
| 9 | EBADF | Неверный файловый дескриптор |
| 13 | EACCES | Нет прав доступа |
| 17 | EEXIST | Файл уже существует (с O_EXCL) |
| 21 | EISDIR | Это директория, не обычный файл |
| 28 | ENOSPC | Нет места на устройстве |
Полная Обработка Ошибок
Реализация обработчика ошибок
section .data
err_noent db "Error: File not found", 10
err_noent_len equ $ - err_noent
err_acces db "Error: Permission denied", 10
err_acces_len equ $ - err_acces
err_nospc db "Error: No space left on device", 10
err_nospc_len equ $ - err_nospc
err_generic db "Error: Operation failed", 10
err_generic_len equ $ - err_generic
section .text
handle_error:
; Вход: rax = отрицательный errno
neg rax
cmp rax, 2
je .print_noent
cmp rax, 13
je .print_acces
cmp rax, 28
je .print_nospc
jmp .print_generic
.print_noent:
mov rax, 1
mov rdi, 2 ; stderr
lea rsi, [rel err_noent]
mov rdx, err_noent_len
syscall
ret
.print_acces:
mov rax, 1
mov rdi, 2
lea rsi, [rel err_acces]
mov rdx, err_acces_len
syscall
ret
.print_nospc:
mov rax, 1
mov rdi, 2
lea rsi, [rel err_nospc]
mov rdx, err_nospc_len
syscall
ret
.print_generic:
mov rax, 1
mov rdi, 2
lea rsi, [rel err_generic]
mov rdx, err_generic_len
syscall
ret
🐛 Отладка: При возникновении ошибок файлового I/O (например,
EACCESилиENOENT) используйте техники, описанные в руководстве «Отладка ASM в VS Code: Настройка GDB и визуальный интерфейс», чтобы пошагово проследить значения регистров и вызовы syscall.
⚡ 9. Оптимизация и лучшие практики
Рекомендации по Производительности
Множественные мелкие операции:
; Чтение по 100 байт
mov rcx, 100
.loop:
mov rax, 0
mov rdi, [fd]
lea rsi, [buffer]
mov rdx, 100
syscall
; обработка
loop .loop
Проблемы:
- 100 системных вызовов
- Overhead переключения контекста
- Неэффективное использование кеша
Одна большая операция:
; Чтение 10 КБ
mov rax, 0
mov rdi, [fd]
lea rsi, [buffer]
mov rdx, 10240
syscall
; Обработка в памяти
mov rcx, rax
lea rsi, [buffer]
.process:
; обработка байта [rsi]
inc rsi
loop .process
Преимущества:
- 1 системный вызов
- Минимальный overhead
- Locality of reference
Паттерны Управления Ресурсами
Паттерн 1: RAII-подобное Управление
process_file:
push rbp
mov rbp, rsp
push r12
; Открытие
mov rax, 2
lea rdi, [rel filename]
xor rsi, rsi
syscall
test rax, rax
js .error_open
mov r12, rax ; Сохранить FD
; === Работа с файлом ===
; ...
; Закрытие в любом случае
.cleanup:
mov rax, 3
mov rdi, r12
syscall
pop r12
pop rbp
ret
.error_open:
; Обработка ошибки без закрытия
pop r12
pop rbp
ret
Паттерн 2: Множественные Файлы
process_multiple:
push rbp
mov rbp, rsp
sub rsp, 32
; Открытие файлов с отслеживанием состояния
mov qword [rbp-8], 0 ; fd1 (0 = не открыт)
mov qword [rbp-16], 0 ; fd2
; Открыть первый
mov rax, 2
lea rdi, [rel file1]
xor rsi, rsi
syscall
test rax, rax
js .cleanup
mov [rbp-8], rax
; Открыть второй
mov rax, 2
lea rdi, [rel file2]
xor rsi, rsi
syscall
test rax, rax
js .cleanup
mov [rbp-16], rax
; === Работа ===
; ...
; Закрытие всех открытых файлов
.cleanup:
cmp qword [rbp-8], 0
je .check_fd2
mov rax, 3
mov rdi, [rbp-8]
syscall
.check_fd2:
cmp qword [rbp-16], 0
je .done
mov rax, 3
mov rdi, [rbp-16]
syscall
.done:
mov rsp, rbp
pop rbp
ret
Обработка Больших Файлов
Для файлов > 100 МБ используйте потоковую обработку:
process_large_file:
push rbp
mov rbp, rsp
push r12
push r13
; Открытие
mov rax, 2
lea rdi, [rel large_file]
xor rsi, rsi
syscall
mov r12, rax
.process_loop:
; Чтение блока
mov rax, 0
mov rdi, r12
lea rsi, [rel buffer]
mov rdx, 4096 ; Размер страницы
syscall
test rax, rax
jle .done ; EOF или ошибка
mov r13, rax ; Количество байт
; === ОБРАБОТКА БЛОКА В ПАМЯТИ ===
lea rsi, [rel buffer]
mov rcx, r13
.process_block:
; Обработка байта [rsi]
; ...
inc rsi
loop .process_block
jmp .process_loop
.done:
mov rax, 3
mov rdi, r12
syscall
pop r13
pop r12
pop rbp
ret
✅ Заключение
Файловый ввод-вывод на ассемблере требует понимания трёх уровней: теория VFS и syscalls, практика системных вызовов, архитектура Layer 3 модулей.
Ключевые принципы:
- Всегда закрывайте файлы — предотвращайте утечки дескрипторов
- Проверяйте возврат syscall — обрабатывайте все ошибки
- Используйте блочное чтение — минимизируйте системные вызовы
- Обрабатывайте частичное чтение — не предполагайте полноты