Чтение файла с указанной c позиции в x86 - PullRequest
1 голос
/ 15 марта 2020

Можно ли начать чтение файла с указанной строки c или байта. В настоящее время я использую этот код для чтения 4 байтов файла:

section .data
    filename db "file.txt", 0

section .bss
    read_data resb 4

section .text
    global _start

_start:
  mov rax, SYS_OPEN
  mov rdi, filename
  mov rsi, O_RDONLY
  mov rdx, 0
  syscall

  push rax
  mov rdi, rax
  mov rax, SYS_READ
  mov rsi, read_data
  mov rdx, 4
  syscall

  mov rax, SYS_CLOSE
  pop rdi
  syscall

Этот код всегда читает первые 4 байта, но я хочу начать чтение с других частей файла, например, с середины. Что мне нужно добавить или изменить?

Ответы [ 2 ]

4 голосов
/ 15 марта 2020

Только что открытый дескриптор файла начинается с позиции = 0. Если вы продолжите чтение с того же fd в oop, вы получите последовательные фрагменты. (Используйте больший буфер, такой как 8kiB и l oop над двойными словами в пользовательском пространстве, хотя, используя значение, которое read вернуло в качестве верхнего предела! Системный вызов очень дорог во времени ЦП.)

Можно ли начать чтение файла с заданной c строки или байта.

  • Байт: да
  • Строка: нет. В Unix / Linux ядро ​​не имеет индекса смещения начала строки или какого-либо другого линейно-ориентированного API. Например, обработка строки в stdio fgets выполняется исключительно в пользовательском пространстве. Существовали некоторые исторические ОС с файлами на основе записей, но файлы Unix представляют собой плоские массивы байтов. (Они могут иметь дыры, неписанные экстенты и расширенные атрибуты ... Но API ядра для содержимого основного файла работают только с байтовыми смещениями).

Если вы хотите сделать строки, прочитайте большой блок и l oop вперёд, пока вы не увидите некоторое количество новых строк. Если вы еще не там, прочитайте другой блок; повторяйте, пока не найдете начало и конец нужного номера строки, или пока не нажмете EOF. x86-64 может эффективно искать 16 байтов одновременно с pcmpeqb / pmovmskb / popcnt (popcnt требует SSE4.2 или заданный c функциональный бит popcnt).

Или только с SSE2 или при оптимизации для больших блоков: от pcmpeqb / psadbw (против всех нулей) до байтов от hsum до qwords / paddd. Затем проверьте, сколько строк вы проходили так часто с помощью скалярного кода. Или сделайте это простым и переходите к поиску первой новой строки в векторе SIMD.

Очевидно, что медленным и простым вариантом является l oop байт за раз, который насчитывает '\n' символов - если вы знать, как сделать strchr с SSE2, это должно быть просто векторизовать этот поиск, используя приведенные выше предложения.


Но если вам нужны только некоторые определенные c байтовые позиции, у вас есть два основных варианта :

  • искать с lseek(2) до read(2) (см. Ответ @ Nicolae Natea)
  • Использовать POSIX / Linux pread(2) для чтения из указанного смещения , не перемещая смещение файла FD для будущих вызовов read. Linux имя системного вызова: pread64 (__NR_pread64 equ 17 от asm/unistd_64.h)

    ssize_t pread(int fd, void *buf, size_t count, off_t offset); Единственное отличие от read - смещение аргумента, 4-й аргумент таким образом передается в R10 (не RCX, как соглашение о вызове функций в пространстве пользователя). off_t - это 64-битный тип, который просто передается в одном регистре в 64-битном коде.

Кроме имени pread64 в .h, в этом нет ничего особенного Интерфейс asm по сравнению с интерфейсом C соответствует стандартному соглашению о системных вызовах. (Он существует с Linux 2.1.60; до этого оболочка glib c эмулировала его с помощью lseek.)


Есть и другие вещи, которые вы можете сделать, например mmap или preadv системный вызов, но pread - это именно то, что вам нужно, если у вас есть известная позиция, с которой вы хотите прочитать.

2 голосов
/ 15 марта 2020

Перед выполнением чтения вы должны выполнить lseek, чтобы обновить положение файла.

, чтобы что-то вроде строк:

mov     rdi, rax        ; fd
mov     rax, SYS_LSEEK
mov     rsi, <whatever offset you want>
mov     rdx, 0  ; keep 0 if the offset should be from the begining of the file
syscall

примечание: RDI будет по-прежнему содержать то же самое fd значение после syscall, поэтому вам не нужно дополнительное сохранение / восстановление для fd через lseek / read / close.

Совет: может быть проще написать код в c и скомпилируйте его с gcc -g -S -fverbose-asm -Og -c main.c, а затем посмотрите на main.s. ( Как убрать "шум" из вывода сборки GCC / clang? ). Но это покажет только то, что компилятор выполняет вызовы функций-оболочек lib c, если только вы не используете встроенные макросы системных вызовов, такие как MUSL lib c.

...