1.3: Файлові дескриптори
- Page ID
- 34568
Одне з перших речей, які вивчає програміст UNIX, полягає в тому, що кожна запущена програма починається з трьох вже відкритих файлів:
| Описова назва | Коротка назва | Номер файлу | Опис |
|---|---|---|---|
| Стандартний В | stdin | 0 | Введення з клавіатури |
| Стандартний вихід | стаут | 1 | Вихід на консоль |
| Стандартна помилка | стерр | 2 | Помилка виведення на консоль |
При цьому виникає питання про те, що собою являє відкритий файл. Значення, яке повертається відкритим викликом, називається файловим дескриптором і по суті є індексом у масив відкритих файлів, що зберігаються ядром.
відкритий виклик і пов'язує дескриптор файлу з деякою абстракцією базового файлоподібного об'єкта, будь то фактичний апаратний пристрій, або файлова система або щось інше цілком. Отже, процес читання або запису викликів, які посилаються на те, що файловий дескриптор спрямовуються в правильне місце ядром, щоб врешті-решт зробити щось корисне.Коротше кажучи, файловий дескриптор є шлюзом у абстракції ядра базового обладнання. Загальний вигляд абстракції для фізико-апаратів наведено на малюнку 1.3, «Абстракція».
Починаючи з найнижчого рівня, операційна система вимагає від програміста створення драйвера пристрою, щоб мати можливість спілкуватися з апаратним пристроєм. Цей драйвер пристрою записується до API, що надається ядром так само, як у прикладі 1.2, «Абстракція in include/linux/virtio.h»; драйвер пристрою надасть ряд функцій, які викликаються ядром у відповідь на різні вимоги. У спрощеному прикладі вище ми бачимо, що драйвери надають функцію читання і запису, яка буде викликана у відповідь на аналогічні операції над файловим дескриптором. Драйвер пристрою знає, як перетворити ці загальні запити в конкретні запити або команди для конкретного пристрою.
Щоб забезпечити абстракцію до простору користувача, ядро надає файловий інтерфейс через те, що зазвичай називається шаром пристрою. Фізичні пристрої на хості представлені файлом в спеціальній файловій системі типу /dev. У Unix-подібних системах так звані вузли пристроїв мають те, що називається основним і незначним числом, які дозволяють ядру асоціювати певні вузли з їх базовим драйвером. Їх можна визначити за допомогою ls, як показано в прикладі 1.3, «Приклад основних і другорядних чисел».
$ ls -l /dev/null /dev/zero /dev/tty
crw-rw-rw- 1 root root 1, 3 Aug 26 13:12 /dev/null
crw-rw-rw- 1 root root 5, 0 Sep 2 15:06 /dev/tty
crw-rw-rw- 1 root root 1, 5 Aug 26 13:12 /dev/zero
Це підводить нас до файлового дескриптора, який є ручкою користувальницького простору, який використовує для спілкування з базовим пристроєм. У широкому сенсі те, що відбувається, коли файл відкритий ed, полягає в тому, що ядро використовує інформацію про шлях для відображення дескриптора файлу з чимось, що забезпечує відповідне читання та запис тощо, API. Якщо це відкрито для пристрою (/dev/sr0 вище), основний і незначний номер відкритого вузла пристрою надає інформацію, необхідну ядру для пошуку правильного драйвера пристрою та завершення відображення. Потім ядро буде знати, як направляти подальші виклики, такі як читання, до базових функцій, наданих драйвером пристрою.
Файл, що не є пристроєм, працює аналогічно, хоча між ними є більше шарів. Абстракцією тут є точка монтування; монтування файлової системи має подвійну мету налаштування відображення, щоб файлова система знала базовий пристрій, який забезпечує зберігання, а ядро знає, що файли, відкриті під цією точкою монтування, повинні бути спрямовані до драйвера файлової системи. Як і драйвери пристроїв, файлові системи записуються в певний загальний API файлової системи, що надається ядром.
Є дійсно багато інших шарів, які ускладнюють картину в реальному житті. Наприклад, ядро докладе великих зусиль, щоб кешувати якомога більше даних з дисків у вільній пам'яті; це забезпечує безліч переваг швидкості. Він також намагатиметься організувати доступ до пристрою найбільш ефективними способами; наприклад, намагаючись замовити доступ до диска, щоб забезпечити отримання даних, що зберігаються фізично близько один до одного, навіть якщо запити не надходили в послідовному порядку. Крім того, багато пристроїв мають більш загальний клас, наприклад USB або SCSI пристрої, які забезпечують свої власні шари абстракції для запису. Таким чином, замість того, щоб писати безпосередньо на пристрої, файлові системи будуть проходити через ці численні шари. Розуміння ядра полягає в тому, щоб зрозуміти, як ці багато API взаємопов'язані та співіснують.
Оболонка є шлюзом для взаємодії з операційною системою. Будь то bash, zsh, csh або будь-яка з безлічі інших оболонок, всі вони принципово мають лише одну основну задачу — дозволити вам виконувати програми (ви почнете розуміти, як саме оболонка насправді це робить, коли ми поговоримо про деякі нутрощі операційної системи пізніше).
Але оболонки роблять набагато більше, ніж дозволяють просто виконати програму. Вони мають потужні здібності перенаправляти файли, дозволяють виконувати кілька програм одночасно і скриптувати повні програми. Всі вони повертаються до всього файлу ідіома.
Часто ми не хочемо, щоб стандартні файлові дескриптори, згадані в розділі «Дескриптори файлів», вказували на місця за замовчуванням. Наприклад, ви можете захопити всі вихідні дані програми у файл на диску або, як варіант, прочитати її команди з файлу, який ви підготували раніше. Інша корисна задача може хотіти передати висновок однієї програми на вхід іншої. З операційною системою оболонка полегшує все це і багато іншого.
| Ім'я | Командування | Опис | Приклад |
|---|---|---|---|
| Перенаправлення на файл | > назва файла |
Візьміть всі вихідні дані зі стандартного виходу і помістіть його в ім'я файлу. Примітка за допомогою >> буде додано до файлу, а не перезаписувати його. |
ls > назва файлу |
| Читання з файлу | < назва файла |
Скопіюйте всі дані з файлу на стандартний вхід програми | echo < назва файлу |
| Труба | програма1 | програма2 |
Візьміть все від стандартного з програми1 і передайте його на стандартний вхід програми2 |
ls | більше |
Реалізація ls | more - це лише ще один приклад сили абстракції. Що принципово відбувається тут, полягає в тому, що замість того, щоб асоціювати файловий дескриптор для стандартного виводу з якимось базовим пристроєм (наприклад, консоль, для виведення на термінал), дескриптор вказується на буфер в пам'яті, наданий ядром зазвичай називають трубою. Хитрість тут полягає в тому, що інший процес може пов'язувати свій стандартний вхід з іншою стороною цього самого буфера і ефективно споживати вихід іншого процесу. Це проілюстровано на малюнку 1.4, «Труба в дії».
запису), які потрібно злити (через читання).Записи в трубу зберігаються ядром до тих пір, поки відповідне читання з іншого боку не стікає буфер. Це дуже потужна концепція і є однією з фундаментальних форм міжпроцесної комунікації або IPC в Unix-подібних операційних системах. Труба дозволяє не просто передавати дані, вона може виступати в ролі сигнального каналу. Якщо процес читається з порожньою трубою, він за замовчуванням блокує або буде переведений у сплячий режим, поки не з'являться деякі дані (це обговорюється набагато глибше в розділі 5, Процес). Таким чином, два процеси можуть використовувати канал, щоб повідомити, що деякі дії були вжиті просто шляхом написання байта даних; замість того, щоб фактичні дані були важливими, просто наявність будь-яких даних у трубі може сигналізувати про повідомлення. Скажімо, наприклад, один процес запитує, щоб інший надрукувати файл - те, що займе деякий час. Ці два процеси можуть встановити канал між собою, де процес запиту робить читання на порожній трубі; будучи порожнім, що виклик блоків і процес не продовжується. Після того, як друк буде зроблений, інший процес може записати повідомлення в трубу, що ефективно пробуджує процес запиту і сигналізує про те, що робота виконана.
Дозволяючи процесам передавати дані між собою, як це призводить до іншої поширеної UNIX ідіоми невеликих інструментів, що роблять одну конкретну річ. Ланцювання цих дрібних інструментів дає гнучкість, яку єдиний монолітний інструмент часто не може.