Що насправді відкриває файл?


266

В усіх мовах програмування (якими я користуюся щонайменше) ви повинні відкрити файл, перш ніж ви зможете прочитати чи записати на нього.

Але що насправді робить ця відкрита операція?

Сторінки вручну для типових функцій насправді нічого не говорять, окрім того, що це "відкриває файл для читання / запису":

http://www.cplusplus.com/reference/cstdio/fopen/

https://docs.python.org/3/library/functions.html#open

Очевидно, завдяки використанню функції ви можете сказати, що це передбачає створення якогось об'єкта, що полегшує доступ до файлу.

Іншим способом цього було б, якби я реалізував openфункцію, що це потрібно робити в Linux?


13
Редагування цього питання, щоб зосередитись на CLinux та Linux; оскільки те, що роблять Linux та Windows, відрізняється. Інакше це трохи занадто широко. Крім того, будь-яка мова вищого рівня в кінцевому підсумку вимагатиме або API API для системи, або компіляція до C для виконання, тому залишення на рівні "C" ставить його на найменший загальний знаменник.
Джордж Стокер

1
Не кажучи вже про те, що не всі мови програмування мають цей інструмент, або це засоби, які сильно залежать від середовища. Звичайно, це рідко в ці дні, але по сьогоднішній день обробка файлів є абсолютно необов'язковою частиною ANSI Forth, і навіть не була присутня в деяких реалізаціях в минулому.

Відповіді:


184

Майже в кожній мові високого рівня функцією, яка відкриває файл, є обгортка навколо відповідного виклику системи ядра. Це може робити і інші фантазії, але в сучасних операційних системах відкриття файлу завжди має проходити через ядро.

Ось чому аргументи fopenбібліотечної функції або Python openдуже нагадують аргументи open(2)системного виклику.

Крім відкриття файлу, ці функції зазвичай встановлюють буфер, який згодом буде використовуватися при операціях читання / запису. Мета цього буфера полягає в тому, щоб кожен раз, коли ви хочете прочитати N байт, відповідний виклик бібліотеки повертає N байтів, незалежно від того, чи буде менше дзвінків до базових системних викликів.

Мені не цікаво реалізувати власну функцію; просто в розумінні того, що, до біса, відбувається ... "поза мовою", якщо хочете.

В операційних системах, схожих на Unix, успішний виклик openповертає "дескриптор файлу", який є лише цілим числом у контексті користувача. Отже, цей дескриптор передається будь-якому виклику, який взаємодіє з відкритим файлом, і після виклику closeна нього дескриптор стає недійсним.

Важливо зауважити, що заклик openдіє як точка перевірки, в якій проводяться різні перевірки. Якщо не виконано всі умови, виклик завершується, повертаючись -1замість дескриптора, і тип помилки вказується в errno. Основними перевірками є:

  • Чи існує файл;
  • Чи привілейований процес виклику для відкриття цього файлу у вказаному режимі. Це визначається шляхом відповідності дозволів файлів, ідентифікатора власника та ідентифікатора групи відповідному ідентифікатору процесу виклику.

У контексті ядра повинно бути якесь відображення між дескрипторами файлів процесу та фізично відкритими файлами. Внутрішня структура даних, яка відображена в дескриптор, може містити ще один буфер, який працює з блоковими пристроями, або внутрішній покажчик, який вказує на поточну позицію читання / запису.


2
Варто відзначити, що в Unix-подібних ОС, дескриптори файлів структури в ядрі, відображені на карті, називається "відкритим описом файлу". Таким чином, FD процесів відображаються в ядрах OFD. Це важливо для розуміння документації. Наприклад, перегляньте man dup2та перевірте тонкощі між дескриптором відкритого файлу (тобто FD, який трапляється відкритим) та відкритим описом файлу (OFD).
rodrigo

1
Так, дозволи перевіряються у відкритий час. Ви можете зайти та прочитати джерело для "відкритої" реалізації ядра : lxr.free-electrons.com/source/fs/open.c, хоча більшу частину роботи делегує конкретному драйверу файлової системи.
pjc50

1
(у системах ext2 це передбачає зчитування записів каталогів, щоб визначити, в якій іноді є метадані, а потім завантаження цього inode в кеш inode. Зауважте, що можуть бути псевдофайлові системи типу "/ proc" та "/ sys", які можуть робити довільні речі коли ви відкриєте файл)
pjc50

1
Зауважте, що перевірки відкритого файлу - на наявність файлу та наявності дозволу - на практиці недостатньо. Файл може зникнути, або його дозволи можуть змінитися під вашими ногами. Деякі файлові системи намагаються запобігти цьому, але поки ваша ОС підтримує мережеве сховище, це неможливо запобігти (ОС може "панікувати", якщо локальна файлова система не поводиться і бути розумною: така, яка робить це, коли ділиться мережею, це не життєздатна ОС). Ці перевірки також проводяться під час відкриття файлу, але вони повинні (ефективно) здійснюватися і при всіх інших доступ до файлів.
Якк - Адам Невраумон

2
Не забувати оцінку та / або створення замків. Вони можуть бути спільними або ексклюзивними і можуть впливати на весь файл або лише його частину.
Thinkeye

83

Я б запропонував переглянути цей посібник через спрощену версію 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  }

Коротко, ось що робить цей код, рядок за рядком:

  1. Виділіть блок пам'яті, керованої ядром, і скопіюйте в неї ім'я файлу з керованої користувачем пам'яті.
  2. Виберіть невикористаний дескриптор файлу, який ви можете вважати цілим індексом у список, який можна відкрити, що відкривається. У кожного процесу є свій такий список, хоча він підтримується ядром; ваш код не може отримати доступ до нього безпосередньо. Запис у списку містить будь-яку інформацію, яку використовує базова файлова система для витягування байтів з диска, таких як номер inode, дозволи процесу обробки, відкриті прапорці тощо.
  3. 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);
    }

    яка робить дві речі:

    1. Використовуйте файлову систему для пошуку введення (або загалом, будь-якого типу внутрішнього ідентифікатора, який використовує файлова система), відповідного імені файлу або шляху, який був переданий.
    2. Створіть a struct fileіз суттєвою інформацією про inode та поверніть її. Ця структура стає записом у тому списку відкритих файлів, про який я згадував раніше.
  4. Збережіть ("встановіть") повернуту структуру у список відкритих файлів процесу.

  5. Звільніть виділений блок пам'яті, керованої ядром.
  6. Повертає дескриптор файлу, який потім може бути переданий в експлуатацію файл функцій , таких як read(), write()і close(). Кожен з них передасть управління ядру, яке може використовувати дескриптор файлу для пошуку відповідного вказівника на файл у списку процесу, а також використовувати інформацію в цьому покажчику файлу для фактичного виконання читання, запису чи закриття.

Якщо ви відчуваєте себе амбітним, ви можете порівняти цей спрощений приклад із реалізацією open()системного виклику в ядрі Linux, функцією, що називається do_sys_open(). У вас не повинно виникнути проблем із пошуком подібності.


Звичайно, це лише "верхній шар" того, що відбувається під час дзвінка open()- а точніше, це фрагмент коду ядра найвищого рівня, який викликається в процесі відкриття файлу. Мова програмування високого рівня може додавати додаткові шари поверх цього. Існує багато, що відбувається на нижчих рівнях. (Дякую Руслану та pjc50 за пояснення.) Приблизно, зверху вниз:

  • open_namei()і dentry_open()викликати код файлової системи, який також є частиною ядра, для доступу до метаданих та вмісту для файлів і каталогів. Файлова система зчитує вихідні байти з диска і інтерпретує ці шаблони байт у вигляді дерева файлів і каталогів.
  • Файлова система використовує рівень пристрою блоку , знову частина ядра, для отримання цих необроблених байтів з диска. (Веселий факт: Linux дозволяє отримувати доступ до необроблених даних із рівня блокового пристрою за /dev/sdaдопомогою тощо).
  • Рівень блокового пристрою викликає драйвер пристрою зберігання даних, який також є кодом ядра, для перекладу з середнього рівня інструкції, наприклад "читати сектор X", до окремих інструкцій вводу / виводу в машинному коді. Існує кілька типів драйверів пристроїв зберігання даних, включаючи IDE , (S) ATA , SCSI , Firewire тощо, що відповідають різним стандартам зв'язку, якими може користуватися диск. (Зверніть увагу, що називання - це безлад.)
  • Інструкції вводу / виводу використовують вбудовані можливості процесорного чіпа та контролера материнської плати для передачі та прийому електричних сигналів по дроту, що йде на фізичний привід. Це апаратне забезпечення, а не програмне забезпечення.
  • На іншому кінці дроту прошивка диска (вбудований код управління) інтерпретує електричні сигнали для обертання блюд та переміщення головки (HDD), читання флеш-пам’яті (SSD) або будь-якого необхідного для доступу до даних на тип накопичувального пристрою.

Це також може бути дещо неправильним через кешування . :-П Серйозно, однак, я залишив безліч деталей - людина (не я) могла написати кілька книг, де описано, як працює весь цей процес. Але це має дати вам ідею.


67

Будь-яка файлова система чи операційна система, про яку ви хочете поговорити, мені добре. Приємно!


На ZX Spectrum ініціалізація LOADкоманди покладе систему в щільний цикл, читаючи рядок Audio In.

Початкові дані позначаються постійним тоном, після чого слідує послідовність довгих / коротких імпульсів, де короткий імпульс для двійкового 0і довший для двійкового 1( https://uk.wikipedia.org/ wiki / ZX_Spectrum_software ). Тугий цикл завантаження збирає біти, поки він не заповнить байт (8 біт), зберігає це в пам'яті, збільшує покажчик пам'яті, а потім петлю повертає назад, щоб перевірити наявність бітів.

Як правило, перше, що прочитає завантажувач, - це короткий заголовок фіксованого формату , який вказує принаймні кількість байтів, які очікують, і, можливо, додаткову інформацію, таку як ім'я файлу, тип файлу та адресу завантаження. Прочитавши цей короткий заголовок, програма могла вирішити, чи продовжувати завантажувати основну масу даних, або вийти з режиму завантаження та відобразити відповідне повідомлення для користувача.

Стан кінцевого файлу можна розпізнати, отримавши стільки байтів, скільки очікувалося (або фіксовану кількість байтів, вбудовану в програмне забезпечення, або змінне число, наприклад, вказане в заголовку). Помилка була допущена, якщо цикл завантаження не отримав імпульс у очікуваному діапазоні частот протягом певного часу.


Трохи про цю відповідь

Описана процедура завантажує дані із звичайної аудіокасети - звідси необхідність сканувати Audio In (вона пов'язана зі стандартним штекером до магнітофонів). LOADКоманда технічно ж , як і openфайл - але він фізично прив'язаний до фактично завантаженні файлу. Це відбувається тому, що магнітофон не контролюється комп'ютером, і ви не можете (успішно) відкрити файл, але не завантажити його.

Про "тугий цикл" згадується тому, що (1) процесор, Z80-A (якщо служить пам'ять), був справді повільним: 3,5 МГц, і (2) Спектр не мав внутрішнього годинника! Це означає, що він повинен був точно вести підрахунок T-станів (час інструкцій) для кожного. неодружений інструкція. всередині цього циклу, просто для підтримки точного часу звукового сигналу.
На щастя, ця низька швидкість процесора мала виразну перевагу, що ви могли обчислити кількість циклів на аркуші паперу, а отже, і реальний світовий час, який вони забирають.


10
@BillWoodger: так, так. Але це справедливе питання (я маю на увазі ваше). Я проголосував за закриття як "занадто широкий", і моя відповідь має на меті проілюструвати, наскільки надзвичайно широке питання насправді.
usr2564301

8
Я думаю, ти занадто сильно розширюєш відповідь. ZX Spectrum мав команду OPEN, і це зовсім відрізнялося від LOAD. І важче зрозуміти.
rodrigo

3
Я також не згоден із закриттям питання, але мені дуже подобається ваша відповідь.
Енцо Фербер

23
Хоча я редагував своє запитання, щоб обмежитись операційною системою linux / Windows, намагаючись не розкрити його, ця відповідь цілком справедлива і корисна. Як сказано в моєму запитанні, я не прагну щось реалізувати або змусити інших людей робити свою роботу, я хочу вчитися. Щоб навчитися, ви повинні задати «великі» питання. Якщо ми постійно замикаємось на питаннях про те, що він є "занадто широким", це ризикує стати місцем, щоб просто змусити людей написати ваш код для вашого, не даючи пояснень, що, де чи чому. Я вважаю за краще зберегти його як місце, куди можу прийти вчитися.
jramm

14
Ця відповідь здається доказом того, що ваше тлумачення питання занадто широке, а не те, що саме питання занадто широке.
jwg

17

Від операційної системи залежить, що саме відбувається, коли ви відкриєте файл. Нижче я описую те, що відбувається в Linux, оскільки він дає вам уявлення про те, що відбувається під час відкриття файлу, і ви можете перевірити вихідний код, якщо вас цікавить детальніше. Я не охоплюю дозволів, оскільки це відповідь зробило б занадто довгим.

У Linux кожен файл розпізнається структурою під назвою inode. Кожна структура має унікальний номер, і кожен файл отримує лише одне число inode. Ця структура зберігає метадані для файлу, наприклад, розмір файлу, дозволи на файл, часові позначки та покажчик на блоки диска, однак, не власне ім'я файлу. Кожен файл (і каталог) містить запис імені файлу та номер введення для пошуку. Коли ви відкриваєте файл, за умови, що у вас є відповідні дозволи, дескриптор файлу створюється за допомогою унікального номера inode, пов'язаного з ім'ям файлу. Оскільки багато процесів / додатків можуть вказувати на один і той же файл, inode має поле посилання, яке підтримує загальну кількість посилань на файл. Якщо файл присутній у каталозі, кількість його посилань дорівнює одному, якщо у нього є жорстке посилання, його кількість посилань буде два, а якщо файл відкривається процесом, кількість посилань збільшується на 1.


6
Що це стосується власне питання?
Білл Вудгер

1
Тут описано, що відбувається на низькому рівні, коли ви відкриваєте файл в Linux. Я погоджуюся, що питання досить широке, тому це, можливо, не було відповіді, яку шукав jramm.
Олексій

1
Отже, знову ж таки, не перевіряючи дозволів?
Білл Вудгер

11

Здебільшого бухгалтерія. Сюди входять різні перевірки на кшталт "Чи існує файл?" та "Чи є у мене дозволи на відкриття цього файлу для запису?".

Але це все, що стосується ядра - якщо ви не реалізуєте власну ОС іграшок, не варто багато заглиблюватися (якщо ви є, розважайтеся - це чудовий досвід навчання). Звичайно, ви все-таки повинні вивчити всі можливі коди помилок, які ви можете отримати під час відкриття файлу, щоб ви могли правильно їх обробити - але це, як правило, непогані невеликі абстракції.

Найважливіша частина на рівні коду полягає в тому, що він дає вам обробку відкритого файлу, яку ви використовуєте для всіх інших операцій, які ви робите з файлом. Не могли б ви використовувати ім'я файлу замість цього довільного ручка? Ну звичайно - але використання ручки дає певні переваги:

  • Система може відслідковувати всі відкриті на даний момент файли та не допускати їх видалення (наприклад).
  • Сучасні ОС побудовані навколо ручок - є безліч корисних речей, які ви можете зробити з ручками, і всі різні види ручок ведуть себе майже однаково. Наприклад, коли асинхронна операція вводу / виводу завершується на ручці файлу Windows, ручка сигналізується - це дозволяє блокувати на ручці доти, доки не буде подано сигнал, або виконати операцію повністю асинхронно. Очікування на ручці файлу точно така ж, як очікування на ручці потоку (сигналізується, наприклад, коли закінчується нитка), ручці процесу (знову ж, сигналізується, коли процес закінчується), або сокет (коли завершується деяка асинхронна операція). Не менш важливо, що ручками належать відповідні процеси, тому коли процес несподівано припиняється (або програма написана погано), ОС знає, що обробляє його, можливо, випустити.
  • Більшість операцій є позиційними - ви readз останнього місця у вашому файлі. Використовуючи ручку для ідентифікації конкретного "відкриття" файлу, ви можете мати кілька одночасних ручок до одного файлу, кожен з яких читає зі своїх місць. Певним чином, ручка виступає як вікно, що переміщується у файл (і спосіб видавати асинхронні запити вводу / виводу, які дуже зручні).
  • Ручки набагато менше, ніж імена файлів. Ручка зазвичай має розмір вказівника, як правило, 4 або 8 байт. З іншого боку, назви файлів можуть мати сотні байтів.
  • Ручки дозволяють ОС переміщувати файл, навіть незважаючи на те, що програми відкриті - ручка все ще дійсна, і вона все одно вказує на той самий файл, навіть якщо ім'я файлу змінилося.

Також можна виконати деякі інші хитрощі (наприклад, обмінюватися ручками між процесами, щоб мати канал зв'язку без використання фізичного файлу; в системах Unix файли також використовуються для пристроїв та різних інших віртуальних каналів, тому це не обов'язково потрібно ), але вони насправді не пов'язані з openсамою операцією, тому я не збираюся заглиблюватися в це.


7

В основі цього при відкритті для читання нічого фантазійного насправді не повинно відбуватися. Все, що потрібно зробити, це перевірити, чи існує файл, і програма має достатньо привілеїв, щоб прочитати його та створити ручку, на якій ви можете видавати файлу команди для читання.

Саме за тими командами фактичне читання буде відправлено.

ОС часто отримує початок читання, починаючи операцію зчитування, щоб заповнити буфер, пов'язаний з ручкою. Тоді, коли ви насправді читаєте, він може негайно повернути вміст буфера, а не потрібно почекати на диску IO.

Для відкриття нового файлу для запису ОС потрібно буде додати запис у каталог нового (поки що порожнього) файлу. І знову створюється ручка, на яку можна видавати команди запису.


5

В основному, для виклику відкриття потрібно знайти файл, а потім записати все, що потрібно, щоб пізніше операції вводу / виводу змогли його знайти знову. Це зовсім невиразно, але це буде правдою для всіх операційних систем, про які я можу відразу подумати. Специфіка варіюється від платформи до платформи. Багато відповідей, які вже є тут, говорять про сучасні настільні операційні системи. Я трохи програмував на CP / M, тому я запропоную свої знання про те, як це працює на CP / M (MS-DOS, ймовірно, працює так само, але з міркувань безпеки, зазвичай це не робиться так, як сьогодні ).

У CP / M у вас є річ, яка називається FCB (як ви згадали C, ви могли б назвати її структурою; це дійсно 35-байт суміжна область в оперативній пам'яті, що містить різні поля). У FCB є поля для запису імені файлу та (4-бітного) цілого числа, що ідентифікує диск. Потім, коли ви викликаєте Відкритий файл ядра, ви передаєте вказівник на цю структуру, помістивши його в один з регістрів процесора. Через деякий час операційна система повертається з дещо зміненою структурою. Що б ви не зробили з цим файлом, ви передаєте вказівник на цю структуру на системний виклик.

Що CP / M робить з цим FCB? Він зберігає певні поля для власного використання та використовує їх для відстеження файлу, тому вам краще ніколи не торкатися їх всередині програми. Операція "Відкрити файл" шукає через таблицю на початку диска файл з тим самим іменем, що і у FCB (символ "?" Підкреслює будь-який символ). Якщо він знаходить файл, він копіює деяку інформацію у FCB, включаючи фізичне розташування файлів на диску, щоб наступні дзвінки вводу / виводу в кінцевому підсумку викликали BIOS, який може передавати ці місця драйверу диска. На цьому рівні специфіка змінюється.


-7

Простіше кажучи, коли ви відкриваєте файл, ви фактично просите операційну систему завантажити потрібний файл (скопіювати вміст файлу) з вторинного сховища в рамну для обробки. І причина цього (завантаження файлу) полягає в тому, що ви не можете обробити файл безпосередньо з жорсткого диска через його надзвичайно низьку швидкість порівняно з Ram.

Команда open відкриє системний виклик, який, в свою чергу, копіює вміст файлу з вторинного накопичувача (жорсткого диска) на первинний сховище (Ram).

І ми «закриваємо» файл, оскільки модифікований вміст файлу має бути відображений у вихідному файлі, який знаходиться на жорсткому диску. :)

Сподіваюся, що це допомагає.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.