На самом низком уровне (по крайней мере для кода пользователя) вы будете использовать системные вызовы. На 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, устарела. Все до того, как это все еще работает, хотя, я полагаю, если не на упрощенном уровне.