На всех языках программирования (которые я использую как минимум) вы должны открыть файл, прежде чем сможете его прочитать или написать.
Но что делает эта открытая операция?
Страницы руководства для типичных функций фактически не говорят вам ничего, кроме "открывает файл для чтения/записи":
http://www.cplusplus.com/reference/cstdio/fopen/
https://docs.python.org/3/library/functions.html#open
Очевидно, что использование функции, которую вы можете сказать, предполагает создание какого-то объекта, который облегчает доступ к файлу.
Другой способ поставить это, если бы я должен был реализовать open
функцию, что ей нужно делать в Linux?
В почти каждом высокоуровневом языке функция, открывающая файл, представляет собой оболочку вокруг соответствующего системного вызова ядра. Это может сделать и другие причудливые вещи, но в современных операционных системах открытие файла всегда должно проходить через ядро.
Вот почему аргументы функции библиотеки fopen
или Python open
очень напоминают аргументы системного вызова open(2)
.
В дополнение к открытию файла эти функции обычно устанавливают буфер, который, следовательно, будет использоваться с операциями чтения/записи. Назначение этого буфера состоит в том, чтобы гарантировать, что всякий раз, когда вы хотите читать N байтов, соответствующий вызов библиотеки возвращает N байтов, независимо от того, обращаются ли вызовы к базовым системным вызовам меньше.
Я действительно не заинтересован в реализации моей собственной функции; просто понимая, что, черт возьми, происходит... "за пределами языка", если хотите.
В Unix-подобных операционных системах успешный вызов open
возвращает "файловый дескриптор", который является просто целым числом в контексте пользовательского процесса. Этот дескриптор, следовательно, передается любому вызову, который взаимодействует с открытым файлом, и после вызова close
на нем дескриптор становится недействительным.
Важно отметить, что вызов open
действует как точка проверки, при которой выполняются различные проверки. Если не все условия выполнены, вызов завершается неудачей, возвращая -1
вместо дескриптора, а вид ошибки указывается в errno
. Существенные проверки:
В контексте ядра должно быть какое-то сопоставление между файловыми дескрипторами процесса и физически открытыми файлами. Внутренняя структура данных, которая отображается в дескриптор, может содержать еще один буфер, который имеет дело с блочными устройствами, или внутренний указатель, который указывает на текущую позицию чтения/записи.
man dup2
и проверьте тонкость между дескриптором открытого файла (это FD, который оказывается открытым) и описанием открытого файла (OFD).
Я бы посоветовал вам взглянуть на это руководство с помощью упрощенной версии системного вызова open()
. Он использует следующий фрагмент кода, который отражает то, что происходит за кулисами при открытии файла.
0 int sys_open(const char *filename, int flags, int mode) {
1 char *tmp = getname(filename);
2 int fd = get_unused_fd();
3 struct file *f = filp_open(tmp, flags, mode);
4 fd_install(fd, f);
5 putname(tmp);
6 return fd;
7 }
Вкратце, вот что делает этот код, строка за строкой:
Функция filp_open
имеет реализацию
struct file *filp_open(const char *filename, int flags, int mode) {
struct nameidata nd;
open_namei(filename, flags, mode, &nd);
return dentry_open(nd.dentry, nd.mnt, flags);
}
который выполняет две функции:
struct file
с необходимой информацией об inode и верните его. Эта структура становится записью в этом списке открытых файлов, о которых я упоминал ранее.Сохранить ( "установить" ) возвращенную структуру в список процессов открытых файлов.
read()
, write()
и close()
. Каждый из них передаст управление ядру, которое может использовать файловый дескриптор для поиска соответствующего указателя файла в списке процессов и использовать информацию в этом указателе файла для фактического выполнения чтения, записи или закрытия.Если вы чувствуете амбициозность, вы можете сравнить этот упрощенный пример с реализацией системного вызова open()
в ядре Linux, функции do_sys_open()
. Вам не должно быть никаких проблем с поиском сходства.
Конечно, это только "верхний уровень" того, что происходит, когда вы вызываете open()
- или, точнее, это кусок ядра самого высокого уровня, который вызывается при открытии файла. Язык программирования высокого уровня может добавить к нему дополнительные слои. Там много, что происходит на более низких уровнях. (Спасибо Ruslan и pjc50 для объяснения.) Грубо, сверху вниз:
open_namei()
и dentry_open()
вызывать код файловой системы, который также является частью ядра, для доступа к метаданным и контенту для файлов и каталогов. filesystem читает необработанные байты с диска и интерпретирует эти шаблоны байтов как дерево файлов и каталогов./dev/sda
и т.п.).Это может быть несколько неверно из-за кэширования.:-P Серьезно, однако, есть много деталей, которые я забыл - человек (а не я) мог написать несколько книг, описывающих, как работает весь этот процесс. Но это должно дать вам идею.
Любая файловая система или операционная система, о которой вы хотите поговорить, в порядке. Ницца!
На ZX-спектре инициализация команды LOAD
приведет систему в замкнутый цикл, читая строку Audio In.
Начало данных указывается постоянным тоном, после чего последовательность длинных/коротких импульсов сопровождается коротким импульсом для двоичного 0
и более длинного для двоичного 1
(https://en.wikipedia.org/wiki/ZX_Spectrum_software). Контейнер с плотной нагрузкой собирает биты до тех пор, пока он не заполнит байт (8 бит), сохранит его в памяти, увеличит указатель на память, а затем снова вернется к сканированию большего количества бит.
Обычно первое, что читал загрузчик, - это короткий заголовок с фиксированным форматом, указывающий, по крайней мере, количество ожидаемых байтов и, возможно, дополнительную информацию, такую как имя файла, тип файла и адрес загрузки. После прочтения этого короткого заголовка программа может решить, следует ли продолжать загрузку основной части данных или выйти из процедуры загрузки и отобразить соответствующее сообщение для пользователя.
Состояние конечного файла может быть распознано путем приема как можно большего количества байтов (либо фиксированное количество байтов, либо аппаратное обеспечение в программном обеспечении, либо число переменных, указанное в заголовке). Произошла ошибка, если цикл загрузки не получил импульс в ожидаемом диапазоне частот в течение определенного промежутка времени.
Немного истории об этом ответе
Описанная процедура загружает данные с обычной аудиокассеты - следовательно, необходимо сканировать Audio In (она связана со стандартным подключением к магнитофонам). Команда LOAD
технически совпадает с open
файлом, но физически связана с фактической загрузкой файла. Это связано с тем, что магнитофон не управляется компьютером, и вы не можете (успешно) открыть файл, но не загружать его.
"Плотная петля" упоминается, потому что (1) CPU, Z80-A (если память используется), была очень медленной: 3,5 МГц и (2) у Спектрума не было внутренних часов! Это означает, что он должен был точно подсчитывать количество T-состояний (время обучения) для каждого. Один. инструкция. внутри этого цикла, просто для поддержания точного времени сигнала.
К счастью, эта низкая скорость ЦП имела явное преимущество в том, что вы могли бы рассчитать количество циклов на листе бумаги и, следовательно, в реальном мире, которое они будут делать.
В зависимости от операционной системы, что именно происходит при открытии файла. Ниже я описываю, что происходит в Linux, поскольку оно дает вам представление о том, что происходит, когда вы открываете файл, и вы можете проверить исходный код, если вас интересует более подробная информация. Я не рассматриваю разрешения, так как этот ответ слишком долго.
В Linux каждый файл распознается структурой inode. Каждая структура имеет уникальный номер, и каждый файл получает только один номер inode. Эта структура хранит метаданные для файла, например размер файла, разрешения файлов, метки времени и указатель на блоки диска, однако это не само имя самого файла. Каждый файл (и каталог) содержит запись имени файла и номер inode для поиска. Когда вы открываете файл, если у вас есть соответствующие разрешения, создается дескриптор файла с использованием уникального номера inode, связанного с именем файла. Поскольку многие процессы/приложения могут указывать на один и тот же файл, inode имеет поле ссылки, которое поддерживает общее количество ссылок на файл. Если файл присутствует в каталоге, его количество ссылок равно единице, если у него есть жесткая ссылка, счетчик ссылок будет равен двум, и если файл будет открыт процессом, количество ссылок будет увеличено на 1.
Бухгалтерия, в основном. Это включает в себя различные проверки типа "существует ли файл?". и "У меня есть разрешения на открытие этого файла для записи?".
Но все вещи ядра - если вы не реализуете свою игрушку, вам нечего вникать (если есть, получайте удовольствие - это отличный опыт обучения). Конечно, вы все равно должны изучить все возможные коды ошибок, которые вы можете получить при открытии файла, чтобы вы могли справиться с ними должным образом - но обычно это небольшие небольшие абстракции.
Самая важная часть на уровне кода заключается в том, что она дает вам дескриптор открытого файла, который вы используете для всех других операций, которые вы делаете с файлом. Не могли бы вы использовать имя файла вместо этого произвольного дескриптора? Ну, конечно - но используя ручку дает вам некоторые преимущества:
read
от последней позиции в вашем файле. Используя дескриптор для определения определенного "открытия" файла, вы можете иметь несколько параллельных ручек в один и тот же файл, каждый из которых считывается из своих мест. В некотором смысле дескриптор выступает в качестве перемещаемого окна в файл (и способ выдавать асинхронные запросы ввода-вывода, которые очень удобны).Также есть и другие трюки, которые вы можете сделать (например, обмениваться дескрипторами между процессами с каналом связи без использования физического файла, в системах Unix файлы также используются для устройств и различных других виртуальных каналов, так что это не " t строго необходимо), но они действительно не привязаны к самой операции open
, поэтому я не буду вникать в это.
В основе его при открытии для чтения ничего необычного не должно быть. Все, что нужно сделать, это проверить, существует ли файл, и приложение имеет достаточно привилегий для его чтения и создания дескриптора, на котором вы можете выдавать команды чтения в файл.
Это на тех командах, которые будет отправлено фактическое чтение.
OS часто получает начало чтения при запуске операции чтения, чтобы заполнить буфер, связанный с дескриптором. Затем, когда вы на самом деле читаете, он может сразу вернуть содержимое буфера, а затем ждать на диске IO.
Для открытия нового файла для записи ОС необходимо будет добавить запись в каталог для нового (в настоящее время пустого) файла. И снова создается дескриптор, на котором вы можете выдавать команды записи.
В принципе, вызов open должен найти файл, а затем записать все, что ему нужно, чтобы последующие операции ввода-вывода могли найти его снова. Это довольно расплывчато, но это будет верно для всех операционных систем, о которых я могу сразу подумать. Специфика варьируется от платформы к платформе. Многие ответы уже здесь говорят о современных настольных операционных системах. Я немного программировал на CP/M, поэтому я буду предлагать свои знания о том, как это работает на CP/M (MS-DOS, вероятно, работает одинаково, но по соображениям безопасности обычно это делается не так, как сегодня).
В CP/M у вас есть вещь, называемая FCB (как вы упомянули C, вы можете назвать ее структурой, это действительно 35-байтная непрерывная область в ОЗУ, содержащая различные поля). FCB имеет поля для записи имени файла и целочисленного (4-разрядного) целого числа, определяющего диск. Затем, когда вы вызываете ядро Open File, вы передаете указатель на эту структуру, помещая его в один из регистров процессора. Спустя некоторое время операционная система возвращается со структурой, слегка изменившейся. Независимо от того, что вы делаете с этим файлом, вы передаете указатель на эту структуру системному вызову.
Что делает CP/M с этим FCB? Он резервирует определенные поля для собственного использования и использует их для отслеживания файла, поэтому вам лучше не трогать их изнутри вашей программы. Операция "Открыть файл" выполняет поиск по таблице в начале диска для файла с тем же именем, что и в FCB (символ подстановки "?" Соответствует любому символу). Если он находит файл, он копирует некоторую информацию в FCB, включая физическое местоположение (файлы) файла на диске, так что последующие вызовы ввода-вывода в конечном итоге вызывают BIOS, который может передать эти места в драйвер диска. На этом уровне специфика различна.
Проще говоря, при открытии файла вы фактически запрашиваете операционную систему для загрузки нужного файла (скопируйте содержимое файла) из вторичного хранилища в ram для обработки. И причина этого (загрузка файла) заключается в том, что вы не можете обрабатывать файл непосредственно с жесткого диска из-за его чрезвычайно медленной скорости по сравнению с Ram.
Команда open генерирует системный вызов, который, в свою очередь, копирует содержимое файла из вторичного хранилища (жесткий диск) в основное хранилище (Ram).
И мы закрываем файл, потому что измененное содержимое файла должно быть отражено в исходном файле, который находится на жестком диске.:)
Надеюсь, что это поможет.
C
и Linux; поскольку то, что делают Linux и Windows, отличается. В противном случае это слишком широкий. Кроме того, любой язык более высокого уровня в конечном итоге вызовет либо C API для системы, либо компиляцию до C для выполнения, поэтому выход на уровне «C» означает его наименьший общий знаменатель.