Обработка каталогов
Пример, рассмотренный выше, относился к администратору последовательного порта. Мы также высказывали предположение, что хотим точного соответствия имен путей при поиске по дереву. Это предположение справедливо только наполовину — все соответствия имен путей, о которых мы будем говорить в этой главе, основаны на полном соответствии компонента имени пути, но вовсе не обязательно имени пути целиком. Давайте вкратце это поясним.
Предположим, у меня есть код, который делает следующее:
fp = fopen("/etc/passwd", "r");
Напомним, что функция fopen() в конечном счете вызывает функцию open(), так что реально мы имеем функцию open(), запрашивающую имя пути /etc/passwd. Но такого имени на рисунке нет:
Пространство имен путей в QNX/Neutrino.
Однако, из рисунка видно, что модуль fs-qnx4 зарегистрировал свою тройку ND/PID/CHID для имени пути «/». Хоть это и не показано на рисунке, файловая система fs-qnx4 зарегистрировалась как «администратор каталога», сказав администратору процессов, что будет отвечать за «/» и все то, что расположено «ниже». «Администраторы устройств» (например, администратор последовательного порта) так не делают. Установив флаг каталога, fs-qnx4 получает возможность обработать запрос для имени пути «/etc/passwd», потому что это имя начинается с «/», а значит, есть совпадение!
А что произошло бы, если бы мы попытались сделать так?
fd = open("/dev/ser1/9600.8.1.n", O_WRONLY);
Ну, поскольку у администратора последовательного порта не установлен флаг каталога, администратор процессов увидит эта и скажет: «Опаньки, извините, /dev/ser1 — не каталог. В обработке отказано». Запрос прямо здесь и заканчивается — администратор процессов даже не возвращает функции open() четверку ND/PID/CHID/handle.
Из параметров функции open() в примере выше видно, что может показаться заманчивой идеей позволить некоторым «традиционным» устройствам открываться с дополнительными параметрами, указываемыми после «обычного» имени. Однако, эмпирическое правило здесь такое: если это пройдет на совещании по организации проекта, тогда вперед. Некоторые из моих студентов, услышав это от меня, заявляют: «Так я и есть сам себе комитет по проектным решениям!» На что я обычно отвечаю. «Пистолет у вас есть. Прострелите себе ногу. :-)»
Объединенные файловые системы
Взгляните повнимательнее на уже знакомый нам рисунок.
Пространство имен путей в QNX/Neutrino.
Обратите внимание, что ответственными за префикс «/» объявили себя как файловая система fs-qnx4, так и администратор процессов. Это нормально, и беспокоиться тут не о чем. Мало того, иногда это оказывается очень даже неплохой идеей. Рассмотрим один такой случай.
Файловые системы с перекрытием
Предположим, что у вас очень медленное сетевое соединение, и вы смонтировали поверх него сетевую файловую систему. Вы замечаете, что некоторый файлы используются достаточно часто, и хотели бы, чтобы эти файлы неким волшебным способом «кешировались» на вашей машине, но увы и ах, проектировщики сетевой файловой системы это почему-то не предусмотрели. И тогда вы решаете самостоятельно написать кеширующую файловую систему (назовем ее, например, fs-cache) и поместить ее поверх сетевой файловой системы. Вот как это будет смотреться с позиции клиента:
Обе файловые системы, fs-nfs (сетевая файловая система) и ваша кэшированная файловая система (fs-cache) регистрируются под одним и тем же префиксом, «/nfs» уже упомянули выше, в QNX/Neutrino это нормально и абсолютно законно.
Предположим, что ваша система только что стартовала, и в вашей кэшированной файловой системе еще ничего нет. Клиентская программа пробует открыть какой-нибудь файл — скажем /nfs/home/rk/abc.txt. Ваша кэшированная файловая система находится «перед» сетевой файловой системой (я потом покажу вам, как это сделать, когда мы будем обсуждать реализацию администратора ресурса).
Клиентский вызов open() выполняет свои обычные действия:
1. Спрашивает администратор процессов: «К кому обратиться по поводу файла /nfs/home/rk/abc.txt?»
2. Получает ответ от администратора процессов: «Поговори сначала с fs-cache, а потом с fs-nfs».
Обратите внимание, что здесь администратор процессов возвращает две четверки ND/PID/CHID/handle — одну для файловой системы fs-cache и одну для файловой системы fs-nfs. Это критично.
Далее функция open() делает следующее:
1. Направляет сообщение файловой системе fs-cache. «Я бы хотел открыть файл /nfs/home/rk/abc.txt на чтение, пожалуйста.»
2. Получает ответ от файловой системы fs-cache: «Сожалею, но я никогда о таком не слышала.»
Здесь становится ясно, что с администратором файловой системы fs-cache клиентской функции open() не повезло. Файл не существует! Однако, вызов open() знает, что он получил список из двух четверок ND/PID/CHID/handle, и поэтому пробует второй вариант:
1. Направляет сообщение файловой системе fs-nfs: «Я бы хотел открыть файл /nfs/home/rk/abc.txt на чтение, пожалуйста.»
2. От файловой системы приходит ответ: «Запросто, никаких проблем!»
Теперь, после того как у функции open() есть EOK («никаких проблем»), она возвращает дескриптор файла. Все дальнейшие операции клиент выполняет непосредственно с администратором сетевой файловой системы fs-nfs.
Имя пути разрешается только один раз — во время вызова функции open(). Это означает, что как только мы успешно открыли нужный администратор ресурса, все дальнейшие вызовы, работающие с дескрипторами файлов, будут идти через него.
Так когда же вступает в игру наша кеширующая файловая система fs-cache? Ну, допустим, пользователь закончил считывание файла (файл теперь загружен в текстовый редактор). Когда файл понадобится сохранить, произойдет та же самая последовательность действий, но возникнет один любопытный поворот:
1. Сообщение администратору процессов: «С кем я должен переговорить насчет файла /nfs/home/rk/abc.txt?»
2. Ответ администратора процессов: «Поговори сначала с fs-cache, а затем с fs-nfs».
3. Сообщение fs-cache: «Мне хотелось бы открыть файл /nfs/home/rk/abc.txt на запись, пожалуйста».
4. Ответ от fs-cache: «Запросто, нет проблем».
Обратите внимание на то, что на 3 этапе мы открыли файл на запись, а не на чтение, как в первый раз. Поэтому не удивительно, что fs-cache на этот раз разрешает эту операцию (этап 4).
Еще более интересные события происходят, когда мы повторно пытаемся прочитать этот файл:
1. Сообщение администратору процессов: «С кем я должен переговорить насчет файла /nfs/home/rk/abc.txt?»
2. Ответ администратора процессов: «Поговори сначала с fs-cache, а затем с fs-nfs».
3. Сообщение fs-cache: «Мне хотелось бы открыть файл /nfs/home/rk/abc.txt на чтение, пожалуйста».
4. Ответ от fs-cache: «Запросто, нет проблем».
Да-да, на этот раз fs-cache обработала запрос на чтение!
Мы опустили несколько деталей, но для восприятия базовых идей они не так важны. Очевидно, кеширующая файловая система должна предусматривать некоторый способ отправки данных по сети на «реальный» носитель. Она также должна уметь перед отправкой данных клиенту проверять, не изменился ли файл (чтобы клиент не получил устаревшие данные). К тому же, кеширующая файловая система вполне могла бы сама обработать первый запрос на чтение, загрузив данные из сетевой файловой системы в свой кэш. И так далее.
Объединенные файловые системы (UFS — Unioned File Systems) и объединенные точки монтирования (UMP — Unioned Mount Points)
Дабы не путать понятия, сделаем небольшой экскурс в терминологию. Основное различие между объединенной файловой системой (UFS) и объединенной точкой монтирования (UMP) заключается в том, что UFS ориентирована на файлы, а UMP — на точки монтирования. В вышеупомянутой кеширующей файловой системе у нас была UFS, потому что оба администратора могли получить доступ к файлу вне зависимости от глубины его размещения файла в дереве каталогов. Давайте для примера рассмотрим другой администратор ресурса (назовем его «foobar»), отвечающий за путь «/nfs/other». В UFS-системе процесс fs-cache был бы способен кэшировать файлы и оттуда тоже, присоединившись к «/nfs». В случае с UMP, что принято в QNX/Neutrino по умолчанию, поскольку там все основано на соответствии самого длинного префикса, запросы смог бы обрабатывать только администратор foobar.