На всех языках программирования (которые я использую по крайней мере) вы должны открыть файл, прежде чем сможете читать или писать в него.
Но что на самом деле делает эта операция открытия?
Страницы руководства по типичным функциям на самом деле не говорят вам ничего, кроме «открытия файла для чтения / записи»:
http://www.cplusplus.com/reference/cstdio/fopen/
https://docs.python.org/3/library/functions.html#open
Очевидно, что при использовании функции вы можете сказать, что она включает в себя создание какого-то объекта, который облегчает доступ к файлу.
Иначе говоря, если бы я реализовал open
функцию, что бы она сделала в Linux?
C
Linux; поскольку то, что делают Linux и Windows, отличается. В противном случае это слишком широкий. Кроме того, любой язык более высокого уровня в конечном итоге вызовет либо C API для системы, либо компиляцию до C для выполнения, поэтому выход на уровне «C» означает его наименьший общий знаменатель.Ответы:
Почти на каждом языке высокого уровня функция, открывающая файл, является оболочкой для соответствующего системного вызова ядра. Это может делать и другие причудливые вещи, но в современных операционных системах открытие файла всегда должно проходить через ядро.
Вот почему аргументы
fopen
библиотечной функции или функции Pythonopen
очень похожи на аргументыopen(2)
системного вызова.В дополнение к открытию файла эти функции обычно устанавливают буфер, который впоследствии будет использоваться с операциями чтения / записи. Цель этого буфера состоит в том, чтобы гарантировать, что всякий раз, когда вы захотите прочитать N байтов, соответствующий библиотечный вызов вернет N байтов, независимо от того, возвращают ли вызовы базовых системных вызовов меньше.
В Unix-подобных операционных системах успешный вызов
open
return возвращает «дескриптор файла», который является просто целым числом в контексте пользовательского процесса. Этот дескриптор, следовательно, передается любому вызову, который взаимодействует с открытым файлом, и после обращенияclose
к нему дескриптор становится недействительным.Важно отметить, что вызов
open
действует как точка проверки, в которой выполняются различные проверки. Если не все условия выполнены, вызов завершается ошибкой, возвращаясь-1
вместо дескриптора, и тип ошибки указывается вerrno
. Основные проверки:В контексте ядра должно быть какое-то отображение между файловыми дескрипторами процесса и физически открытыми файлами. Внутренняя структура данных, которая отображается на дескриптор, может содержать еще один буфер, который работает с блочными устройствами, или внутренний указатель, который указывает на текущую позицию чтения / записи.
источник
man dup2
и проверьте тонкость между дескриптором открытого файла (то есть FD, который оказывается открытым) и описанием открытого файла (OFD).Я бы посоветовал вам взглянуть на это руководство через упрощенную версию
open()
системного вызова . Он использует следующий фрагмент кода, который представляет то, что происходит за кулисами при открытии файла.Вкратце, вот что делает этот код, строка за строкой:
filp_open
Функция имеет реализациюкоторый делает две вещи:
struct file
необходимую информацию об индексе и верните ее. Эта структура становится записью в этом списке открытых файлов, о которых я упоминал ранее.Сохранить («установить») возвращенную структуру в список открытых файлов процесса.
read()
,write()
иclose()
. Каждый из них передаст управление ядру, которое может использовать дескриптор файла для поиска соответствующего указателя файла в списке процесса и использовать информацию в этом указателе файла для фактического выполнения чтения, записи или закрытия.Если вы чувствуете себя амбициозно, вы можете сравнить этот упрощенный пример с реализацией
open()
системного вызова в ядре Linux, вызываемой функцииdo_sys_open()
. У вас не должно быть проблем с поиском сходства.Конечно, это только «верхний уровень» того, что происходит при вызове,
open()
или, точнее, это фрагмент кода самого высокого уровня, который вызывается в процессе открытия файла. Язык программирования высокого уровня может добавить дополнительные слои поверх этого. Есть много всего, что происходит на более низких уровнях. (Спасибо Руслану и pjc50 за объяснения.) Грубо говоря, сверху вниз:open_namei()
иdentry_open()
вызывать код файловой системы, которая также является частью ядра, для доступа к метаданным и контенту для файлов и каталогов. Файловая система считывает исходные байты с диска и интерпретирует эти шаблоны байт в виде дерева файлов и каталогов./dev/sda
и т.п.)Это также может быть несколько неправильно из-за кэширования . :-P Если серьезно, то я упустил много деталей - человек (не я) мог написать несколько книг, описывающих, как работает весь этот процесс. Но это должно дать вам представление.
источник
Любая файловая система или операционная система, о которой вы хотите поговорить, меня устраивает. Ницца!
На ZX Spectrum инициализация
LOAD
команды замкнет систему, читая строку Audio In.Начало данных обозначается постоянным тоном, после чего следует последовательность длинных / коротких импульсов, где короткий импульс предназначен для двоичного,
0
а более длинный - для двоичного1
( https://en.wikipedia.org/ wiki / ZX_Spectrum_software ). Плотная нагрузочная петля собирает биты до тех пор, пока не заполнит байт (8 бит), сохранит его в памяти, увеличит указатель памяти, а затем вернется в цикл для поиска большего количества битов.Как правило, первое, что прочитает загрузчик, это короткий фиксированный заголовок формата , указывающий, по крайней мере, ожидаемое количество байтов и, возможно, дополнительную информацию, такую как имя файла, тип файла и адрес загрузки. После прочтения этого короткого заголовка программа может решить, продолжить ли загрузку основной массы данных или выйти из процедуры загрузки и отобразить соответствующее сообщение для пользователя.
Состояние конца файла можно распознать, получив столько байтов, сколько ожидается (либо фиксированное число байтов, встроенное в программное обеспечение, либо переменное число, такое как указано в заголовке). Выдалась ошибка, если в течение определенного промежутка времени нагрузочный контур не получал импульс в ожидаемом диапазоне частот.
Небольшая предыстория этого ответа
Описанная процедура загружает данные с обычной аудиокассеты - отсюда и необходимость сканирования Audio In (она подключается со стандартным штекером к магнитофону).
LOAD
Команда технически же , как иopen
файл - но он физически привязан к фактически загрузке файла. Это связано с тем, что магнитофон не контролируется компьютером, и вы не можете (успешно) открыть файл, но не можете загрузить его.Упоминается «тесная петля», потому что (1) процессор, Z80-A (если память служит), работал очень медленно: 3,5 МГц, и (2) у Spectrum не было внутренних часов! Это означает, что он должен был точно вести подсчет T-состояний (времени команд) для каждого. не замужем. инструкция. внутри этой петли, просто чтобы поддерживать точное время звукового сигнала.
К счастью, эта низкая скорость процессора имела явное преимущество, заключающееся в том, что вы можете рассчитать количество циклов на листе бумаги и, следовательно, время, которое они потратят в реальном мире.
источник
Это зависит от операционной системы, что именно происходит при открытии файла. Ниже я опишу, что происходит в Linux, поскольку он дает вам представление о том, что происходит, когда вы открываете файл, и вы можете проверить исходный код, если вас интересует более подробная информация. Я не покрываю разрешения, поскольку это сделало бы этот ответ слишком длинным.
В Linux каждый файл распознается структурой, называемой inode, Каждая структура имеет уникальный номер, и каждый файл получает только один номер inode. Эта структура хранит метаданные для файла, например, размер файла, права доступа к файлу, отметки времени и указатель на блоки диска, но не само фактическое имя файла. Каждый файл (и каталог) содержит запись имени файла и номер индекса для поиска. Когда вы открываете файл, предполагая, что у вас есть соответствующие разрешения, дескриптор файла создается с использованием уникального номера inode, связанного с именем файла. Поскольку многие процессы / приложения могут указывать на один и тот же файл, в inode есть поле ссылки, в котором хранится общее количество ссылок на файл. Если файл присутствует в каталоге, его счетчик ссылок равен единице, если он имеет жесткую ссылку, его счетчик ссылок будет равен двум, а если файл открывается процессом, счетчик ссылок будет увеличен на 1.
источник
Бухгалтерия, в основном. Это включает в себя различные проверки, такие как «Файл существует?» и «Есть ли у меня разрешения на открытие этого файла для записи?».
Но это все, что нужно для ядра - если вы не реализуете свою собственную игрушечную ОС, углубляться в нее нечем (если вам это интересно, то это отличный опыт обучения). Конечно, вы все равно должны изучить все возможные коды ошибок, которые вы можете получить при открытии файла, чтобы вы могли обрабатывать их правильно - но это, как правило, хорошие небольшие абстракции.
Наиболее важной частью на уровне кода является то, что он дает вам дескриптор открытого файла, который вы используете для всех других операций, которые вы выполняете с файлом. Не могли бы вы использовать имя файла вместо этого произвольного дескриптора? Ну, конечно - но использование ручки дает вам некоторые преимущества:
read
с последней позиции в вашем файле. Используя дескриптор для определения конкретного «открытия» файла, вы можете иметь несколько одновременных дескрипторов для одного и того же файла, каждый из которых читает со своих мест. В некотором смысле, дескриптор действует как перемещаемое окно в файл (и способ выдавать асинхронные запросы ввода-вывода, которые очень удобны).Есть также некоторые другие приемы, которые вы можете сделать (например, обмениваться дескрипторами между процессами, чтобы иметь канал связи без использования физического файла; в системах Unix файлы также используются для устройств и различных других виртуальных каналов, поэтому это не является строго обязательным ), но они на самом деле не связаны с самой
open
операцией, поэтому я не буду углубляться в это.источник
В основе этого при открытии для чтения не должно быть ничего фантастического . Все, что ему нужно сделать, - это проверить, что файл существует, и у приложения достаточно прав для его чтения и создать дескриптор, с помощью которого вы можете выдавать команды чтения в файл.
Именно по этим командам будет отправлено реальное чтение.
ОС часто получает преимущество при чтении, запуская операцию чтения, чтобы заполнить буфер, связанный с дескриптором. Затем, когда вы действительно выполняете чтение, он может немедленно вернуть содержимое буфера, а не ждать на дисководе.
Для открытия нового файла для записи ОС необходимо будет добавить запись в каталог для нового (на данный момент пустого) файла. И снова создается дескриптор, по которому вы можете выдавать команды записи.
источник
По сути, вызов open должен найти файл, а затем записать все, что ему нужно, чтобы последующие операции ввода-вывода могли найти его снова. Это довольно расплывчато, но это будет верно для всех операционных систем, о которых я могу сразу подумать. Специфика варьируется от платформы к платформе. Многие ответы уже здесь говорят о современных настольных операционных системах. Я немного программировал на CP / M, поэтому я предложу свои знания о том, как он работает на CP / M (MS-DOS, вероятно, работает таким же образом, но по соображениям безопасности, обычно это не делается сегодня ).
В CP / M у вас есть вещь, называемая FCB (как вы упомянули C, вы можете назвать ее структурой; это действительно 35-байтовая непрерывная область в RAM, содержащая различные поля). FCB имеет поля для записи имени файла и (4-разрядного) целого числа, идентифицирующего дисковод. Затем, когда вы вызываете открытый файл ядра, вы передаете указатель на эту структуру, помещая ее в один из регистров процессора. Некоторое время спустя операционная система возвращается с немного измененной структурой. Что бы вы ни делали с этим файлом, вы передаете указатель на эту структуру системному вызову.
Что делает CP / M с этим FCB? Он резервирует определенные поля для собственного использования и использует их для отслеживания файла, поэтому вам лучше не трогать их изнутри вашей программы. Операция Open File ищет в таблице в начале диска файл с тем же именем, что и в FCB (подстановочный знак '?' Соответствует любому символу). Если он находит файл, он копирует некоторую информацию в FCB, в том числе физическое местоположение файла на диске, так что последующие вызовы ввода-вывода в конечном итоге вызывают BIOS, который может передать эти места драйверу диска. На этом уровне особенности различаются.
источник
Проще говоря, когда вы открываете файл, вы фактически запрашиваете у операционной системы загрузить нужный файл (скопировать содержимое файла) из вторичного хранилища в оперативную память для обработки. И причина этого (загрузка файла) заключается в том, что вы не можете обработать файл напрямую с жесткого диска из-за его чрезвычайно низкой скорости по сравнению с Ram.
Команда open сгенерирует системный вызов, который, в свою очередь, скопирует содержимое файла из вторичного хранилища (жесткого диска) в первичное хранилище (Ram).
И мы «закрываем» файл, потому что измененное содержимое файла должно быть отражено в исходном файле, который находится на жестком диске. :)
Надеюсь, это поможет.
источник