Как на самом деле работает потоковая передача файлов? - PullRequest
14 голосов
/ 20 мая 2011

Некоторое время я задавался вопросом, как именно работает потоковая передача файлов? Под потоковой передачей файлов я имею в виду доступ к частям файла без загрузки всего файла в память.
Я (верю) знаю, что классы C ++ (i|o)fstream делают именно это, но как это реализовано? Можно ли реализовать потоковую передачу файлов самостоятельно?
Как это работает на самом низком уровне C / C ++ (или любом языке, поддерживающем потоковую передачу файлов)? Функции C * fopen, fclose, fread и указатель FILE* уже заботятся о потоковой передаче (т. Е. Не загружают весь файл в память)? Если нет, то как бы вы читали напрямую с жесткого диска и есть ли такая возможность уже реализована в C / C ++?

Любые ссылки, подсказки, указатели в правильном направлении уже были бы очень полезны. Я гуглил, но, похоже, Google не совсем понимает, что я ищу ...


Ninja-Edit : Если кто-нибудь знает что-нибудь о том, как это работает на уровне сборка / машинный код и возможно ли это реализовать самостоятельно или если вам приходится полагаться на систему звонки, это было бы здорово. :) Не требуется ответа, хотя ссылка в правильном направлении была бы хороша.

Ответы [ 2 ]

24 голосов
/ 20 мая 2011

На самом низком уровне (по крайней мере для кода пользователя) вы будете использовать системные вызовы. На UNIX-подобных платформах они включают:

  • open
  • close
  • read
  • write
  • lseek

... и другие. Они работают, передавая эти вещи, называемые дескрипторами файлов. Файловые дескрипторы являются просто непрозрачными целыми числами. Внутри операционной системы каждый процесс имеет таблицу дескрипторов файлов, содержащую все дескрипторы файлов и соответствующую информацию, такую ​​как, какой это файл, какой это файл и т. Д.

Существуют также вызовы Windows API, аналогичные системным вызовам в UNIX:

Windows проходит около HANDLE с, которые похожи на файловые дескрипторы, но, как мне кажется, немного менее гибкие. (например, в UNIX файловые дескрипторы могут представлять не только файлы, но также сокеты, каналы и другие вещи)

Функции стандартной библиотеки C fopen, fclose, fread, fwrite и fseek являются просто оболочками для этих системных вызовов.

Когда вы открываете файл, обычно его содержимое не читается в память. Когда вы используете fread или read, вы сообщаете операционной системе, что нужно прочитать определенное количество байтов в буфер. Это конкретное число байтов может быть, но не обязательно, длиной файла. Таким образом, при желании вы можете читать только часть файла в память.

Ответ ниндзя-редактировать:

Вы спросили, как это работает на уровне машинного кода. Я могу только реально объяснить, как это работает в Linux и 32-битной архитектуре Intel. Когда вы используете системный вызов, некоторые аргументы помещаются в регистры. После помещения аргументов в регистры возникает прерывание 0x80. Так, например, чтобы прочитать один килобайт из stdin (дескриптор файла 0) по адресу 0xDEADBEEF, вы можете использовать этот код сборки:

mov eax, 0x03       ; system call number (read = 0x03)
mov ebx, 0          ; file descriptor (stdin = 0)
mov ecx, 0xDEADBEEF ; buffer address
mov edx, 1024       ; number of bytes to read
int 0x80 ; Linux system call interrupt

int 0x80 вызывает программное прерывание, которое операционная система обычно регистрирует в таблице векторов прерываний или в таблице дескрипторов прерываний. В любом случае, процессор перейдет в определенное место в памяти. Оказавшись там, обычно операционная система переходит в режим ядра (если необходимо), а затем делает эквивалент C switch на eax. Оттуда он перейдет к реализации для read. В read он обычно считывает некоторые метаданные о дескрипторе из таблицы дескрипторов файлов вызывающего процесса. Как только у него есть все необходимые данные, он выполняет свои функции и возвращается к пользовательскому коду.

Чтобы "сделать свое дело", давайте предположим, что он читает с диска, а не из канала или stdin или какого-то другого нефизического места. Давайте также предположим, что он читает с основного жесткого диска. Также предположим, что операционная система все еще может получать доступ к прерываниям BIOS.

Чтобы получить доступ к файлу, ему нужно сделать кучу вещей в файловой системе. Например, обход дерева каталогов, чтобы найти, где находится настоящий файл. Я не собираюсь это подробно освещать, так как держу пари, что вы можете догадаться.

Интересной частью является чтение данных с диска, будь то метаданные файловой системы, содержимое файла или что-то еще. Сначала вы получаете адрес логического блока (LBA). LBA - это просто индекс блока данных на диске. Каждый блок обычно составляет 512 байт (хотя эта цифра может быть датирована). Если предположить, что у нас есть доступ к BIOS и ОС использует его, он преобразует LBA в формат CHS. Обозначение CHS (головка-цилиндр-сектор) - это еще один способ ссылки на части жесткого диска. Раньше он соответствовал физическим понятиям, но в настоящее время он устарел, но почти каждый BIOS поддерживает его. Оттуда ОС заполняет данные в регистры и запускает прерывание 0x13, прерывание чтения диска в BIOS.

Это самый низкий уровень, который я могу объяснить, и я уверен, что часть после того, как я предположил, что операционная система использовала BIOS, устарела. Все до того, как это все еще работает, хотя, я полагаю, если не на упрощенном уровне.

2 голосов
/ 20 мая 2011

На самом низком уровне, на платформах POSIX, открытые файлы представлены «дескрипторами» в пространстве пользователя.Файловый дескриптор - это просто целое число, уникальное для открытых файлов в любой момент времени.Дескриптор используется для определения того, к какому открытому файлу должна применяться операция, когда ядро ​​запрашивает фактическое выполнение этой операции.Так, read(0, charptr, 1024) выполняет чтение из открытого файла, который связан с дескриптором 0 (условно, это, вероятно, будет стандартный ввод процесса).

Насколько может сказать пользовательское пространство, единственноечасти файла, которые загружаются в память, - это те, которые необходимы для выполнения операции, подобной read.Для чтения байтов из середины файла поддерживается другая операция - «поиск».Это говорит ядру переместить смещение в конкретный файл.Следующая операция read (или write) будет работать с байтами из этого нового смещения.Таким образом, lseek(123, 100, SEEK_SET) перемещает смещение для файла, связанного с 123 (значение дескриптора, которое я только что составил), в позицию 100-го байта.Следующее чтение в 123 будет читать, начиная с этой позиции, а не с начала файла (или там, где смещение было ранее).И любые не прочитанные байты не нужно загружать в память.

За кадром немного больше сложности - диск обычно не может прочитать меньше, чем «блок», который обычно является степеньюдва около 4096;ядро, вероятно, выполняет дополнительное кэширование и что-то, называемое readahead.Но это оптимизации, и основная идея - это то, что я описал выше.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...