Реализация файловой системы nfs

Файловая система NFS (Network File System – сетевая файловая система) корпорации Sun Microsystems, использующуюся на всех современных системах UNIX (а также на некоторых не-UNIX системах) для объединения на логическом уровне файловых систем отдельных сетевых машин в единое целое.

В основе файловой системы NFS лежит представление о том, что пользоваться общей файловой системой может произвольный набор клиентов и серверов. Во многих случаях все клиенты и серверы располагаются на одной и той же локальной сети, хотя этого не требуется. Файловая система NFS может также работать в глобальной сети, если сервер находится далеко от клиента. Для простоты мы будем говорить о клиентах и серверах, как если бы они работали на различных машинах, хотя файловая система NFS позволяет каждой машине одновременно быть клиентом и сервером. Каждый сервер файловой системы NFS экспортирует один или несколько ее каталогов, предоставляя доступ к ним удаленным клиентам. Как правило, доступ к каталогу предоставляется вместе со всеми его подкаталогами, то есть все дерево каталогов экспортируется как единое целое. Список экспортируемых сервером каталогов хранится в файле /etc/fexports, таким образом, эти каталоги экспортируются автоматически при загрузке сервера. Клиенты получают доступ к экспортируемым каталогам, монтируя эти каталоги. Когда клиент монтирует удаленный каталог, этот каталог становится частью иерархии каталогов клиента. У одного и того же файла могут быть различные имена на различных клиентах, так как их каталоги могут монтироваться в различных узлах каталоговых деревьев. Выбор узла, в котором монтируется удаленный каталог, целиком зависит от клиента. Сервер не знает, где клиент монтирует его каталог.

Так как одна из целей файловой системы NFS заключается в поддержке разнородных систем, в которых клиенты и серверы могут работать под управлением различных операционных систем и на различном оборудовании, существенно, чтобы интерфейс между клиентами и серверами был тщательно определен. Только в этом случае можно ожидать, что новый написанный клиент будет корректно работать с существующими серверами, и наоборот. В файловой системе NFS эта задача выполняется при помощи двух протоколов клиент-сервер. Протокол – это набор запросов, посылаемых клиентами серверам, и ответов серверов, посылаемых клиентам.

Первый протокол NFS управляет монтированием каталогов. Клиент может послать серверу путь к каталогу и запросить разрешение смонтировать этот каталог где-либо в своей иерархии каталогов. Данные о месте, в котором клиент намеревается смонтировать удаленный каталог, серверу не посылаются, так как серверу это безразлично. Если путь указан верно и указанный каталог был экспортирован, тогда сервер возвращает клиенту дескриптор файла, содержащий поля, однозначно идентифицирующие тип файловой системы, диск, i-узел каталога и информацию о правах доступа. Этот дескриптор файла используется последующими обращениями чтения и записи к файлам в монтированном каталоге или в любом из его подкаталогов.

Во время загрузки операционная система UNIX, прежде чем перейти в многопользовательский режим, запускает сценарий оболочки /etc/rc. В этом сценарии можно разместить команды монтировки файловых систем. Таким образом, все необходимые удаленные файловые системы будут автоматически смонтированы прежде, чем будет разрешена регистрация в системе. В качестве альтернативы в большинстве версий системы UNIX также поддерживается автомонтировка. Это свойство позволяет ассоциировать с локальным каталогом несколько удаленных каталогов. Ни один из этих удаленных каталогов не монтируется во время загрузки операционной системы (не происходит даже контакта с сервером). Вместо этого при первом обращении к удаленному файлу (когда файл открывается) операционная система посылает каждому серверу сообщение. Побеждает ответивший первым сервер, чей каталог и монтируется.

У автомонтировки есть два принципиальных преимущества перед статической монтировкой с использованием файла /etc/rc. Во-первых, если один из серверов, перечисленных в файле /etc/rc, окажется выключенным, запустить клиента будет невозможно, по крайней мере без определенных трудностей, задержки и большого количества сообщений об ошибках. Если пользователю в данный момент этот сервер не нужен, вся работа просто окажется напрасной. Во-вторых, предоставление клиенту возможности связаться с несколькими серверами параллельно позволяет значительно повысить устойчивость системы к сбоям (так как для работы достаточно всего одного работающего сервера) и улучшить показатели производи-тельности (так как первый ответивший сервер скорее всего окажется наименее загруженным). С другой стороны, при таком подходе неявно подразумевается, что все указанные как альтернативные файловые системы идентичны для автомонтировки. Так как файловая система NFS не предоставляет поддержки репликации файлов или каталогов, то следить за идентичностью всех файловых систем должен сам пользователь. Поэтому автомонтировка, как правило, используется для файловых систем, в которых клиенту разрешено только чтение. Такие файловые системы обычно содержат системные двоичные файлы, а также другие редко изменяемые файлы.

Второй протокол NFS предназначен для доступа к каталогам и файлам. Клиенты могут посылать серверам сообщения, содержащие команды управления каталогами и файлами, что позволяет им создавать, удалять, читать и писать файлы. Кроме того, у клиентов есть доступ к атрибутам файла, таким как режим, размер и время последнего изменения файла. Файловой системой NFS поддерживается большинство системных вызовов операционной системы UNIX, за исключением системных вызовов open и close. Пропуск системных вызовов open и close не случаен. Это сделано намеренно. Нет необходимости открывать файл, прежде чем прочитать его. Также не нужно закрывать файл после того, как данные из него прочитаны. Вместо этого, чтобы прочитать файл, клиент посылает на сервер сообщение lookup, содержащее имя файла, с запросом найти этот файл и вернуть дескриптор файла, представляющий собой структуру, идентифицирующую файл (то есть содержащую идентификатор файловой системы и номер i-узла вместе с прочей информацией). В отличие от системного вызова open, операция lookup не копирует никакой информации во внутренние системные таблицы. Системному вызову read подается на входе дескриптор файла, который предстоит прочитать, смещение в файле, а также количество байтов, которые нужно прочитать. Таким образом, каждое сообщение является самодостаточным. Преимущество такой схемы заключается в том, что серверу не нужно помнить что-либо об открытых соединениях между обращениями к нему. Поэтому если на сервере произойдет сбой с последующей перезагрузкой, не будет потеряно никакой информации об открытых файлах, так как терять просто нечего. Такие серверы называются серверами без состояния.

К сожалению, метод файловой системы NFS усложняет достижение точной файловой семантики системы UNIX. Например, в операционной системе UNIX файл может быть открыт и заблокирован, так что никакой другой процесс не сможет получить к этому файлу доступ. Когда файл закрывается, все его блокировки снимаются. В сервере без состояния, как в файловой системе NFS, с открытыми файлами нельзя связать блокировку, так как сервер не знает, какие файлы открыты. Следовательно, в файловой системе NFS требуется отдельный специальный механизм осуществления блокировки.

Файловая система NFS использует стандартный механизм защиты UNIX с битами rwx для владельца, группы и всех прочих пользователей. Изначально каждое сообщение с запросом просто содержало идентификаторы пользователя и группы вызывающего процесса, которые сервер NFS использовал для проверки прав доступа. В настоящее время для установки надежного ключа для аутентификации клиента и сервера при каждом запросе и каждом ответе можно использовать шифрование с открытым ключом. При этом злоумышленник не сможет выдать себя за другого клиента (или другой сервер), так как ему неизвестен секретный ключ этого клиента (или сервера).

Хотя реализация программ клиента и сервера не зависит от протоколов NFS, в большинстве систем UNIX используется их трехуровневая реализация. Верхний уровень представляет собой уровень системных вызовов. Он управляет такими системными вызовами, как open, read и close. После анализа системного вызова и проверки его параметров он вызывает второй уровень – уровень VFS (Virtual File System – виртуальная файловая система).

Задача уровня VFS заключается в управлении таблицей, содержащей по одной записи для каждого открытого файла, аналогичной таблице i-узлов для открытых файлов в системе UNIX. В обычной системе UNIX i-узел однозначно указывается парой: устройство – номер i-узла. Вместо этого уровень VFS содержит для каждого открытого файла записи, называемые v-узлами (virtual i-node – виртуальный i-узел). V-узлы используются, чтобы отличать локальные файлы от удаленных. Для удаленных файлов предоставляется информация, достаточная для доступа к ним. Для локальных файлов записываются сведения о файловой системе и i-узле, так как современные системы UNIX могут поддерживать несколько файловых систем (например, V7, Berkeley Fast, ext2, /proc, FAT и т. д.). Хотя уровень VFS был создан для поддержки файловой системы NFS, сегодня он поддерживается большинством современных систем UNIX как составная часть операционной системы, даже если NFS не используется.

Чтобы понять, как используются v-узлы, рассмотрим выполнение последовательности системных вызовов mount, open и read. Чтобы смонтировать файловую систему, системный администратор (или сценарий /etc/rc) вызывает программу mount, указывая ей удаленный каталог, локальный каталог, в котором следует смонтировать удаленный каталог, и прочую информацию. Программа mount анализирует имя удаленного каталога и обнаруживает имя сервера NFS, на котором располагается удаленный каталог. Затем она соединяется с этой машиной, запрашивая у нее дескриптор удаленного каталога. Если этот каталог существует и его удаленное монтирование разрешено, сервер возвращает его дескриптор. Наконец, программа mount обращается к системному вызову mount, передавая ядру полученный от сервера дескриптор каталога. Затем ядро формирует для удаленного каталога v-узел и просит программу клиента NFS создать в своих внутренних таблицах r-узел (удаленный i-узел) для хранения дескриптора файла. V-узел указывает на r-узел. Каждый v-узел на уровне VFS будет в конечном итоге содержать либо указатель на r-узел в программе клиента NFS, либо указатель на i-узел в одной из локальных файловых систем. По содержимому v-узла можно понять, является ли файл или каталог локальным или удаленным. Если он локальный, то может быть найдена соответствующая файловая система и i-узел. Если файл удаленный, может быть найден удаленный хост и дескриптор файла.

Когда на клиенте открывается удаленный файл, при анализе пути файла ядро обнаруживает каталог, в котором смонтирована удаленная файловая система. Оно видит, что этот каталог удаленный, а в v-узле каталога находит указатель на r-узел. Затем она просит программу клиента NFS открыть файл. Программа клиента NFS просматривает оставшуюся часть пути на удаленном сервере, ассоциированном с монтированным каталогом, и получает обратно дескриптор файла для него. Он создает в своих таблицах r-узел для удаленного файла и докладывает об этом уровню VFS, который помещает в свои таблицы v-узел для файла, указывающий на r-узел. Таким образом и в этом случае у каждого открытого файла или каталога есть v-узел, указывающий на r-узел или i-узел.

Вызывающему процессу выдается дескриптор удаленного файла. Этот дескриптор файла отображается на v-узел при помощи таблиц уровня VFS. Необходимо обратить внимание, что на сервере не создается никаких записей в таблицах. Хотя сервер готов предоставить дескрипторы файлов по запросу, он не следит за состоянием дескрипторов файлов. Когда дескриптор файла присылается серверу для доступа к файлу, сервер проверяет дескриптор и использует его, если дескриптор действителен. При проверке может проверяться ключ аутентификации, содержащийся в заголовках вызова удаленной процедуры RPC.

Когда дескриптор файла используется в последующем системном вызове, например read, уровень VFS находит соответствующий v-узел и по нему определяет, является ли он локальным или удаленным, а также какой i-узел или r-узел его описывает. Затем он посылает серверу сообщение, содержащее дескриптор, смещение в файле (хранящееся на стороне клиента, а не сервера) и количество байтов. Для повышения эффективности обмен информацией между клиентом и сервером выполняется большими порциями, как правило, по 8192 байт, даже если запрашивается меньшее количество байтов.

Когда сообщение с запросом прибывает на сервер, оно передается там уровню VFS, который определяет файловую систему, содержащую файл. Затем уровень VFS обращается к этой файловой системе, чтобы прочитать и вернуть байты. Эти данные передаются клиенту. После того, как уровень VFS клиента получает 8-ки-лобайтную порцию данных, которую запрашивал, он автоматически посылает запрос на следующую порцию, чтобы она была под рукой, когда понадобится. Такая функция, называемая опережающим чтением, позволяет значительно увеличить производительность.

При записи в удаленный файл проходится аналогичный путь от клиента к серверу. Данные также передаются 8-килобайтными порциями. Если системному вызову write подается менее 8 Кбайт данных, данные просто накапливаются локально. Только когда порция в 8 Кбайт готова, она посылается серверу. Если файл закрывается, то весь остаток немедленно посылается серверу.

Кроме того, для увеличения производительности применяется кэширование, как в обычной системе UNIX. Серверы кэшируют данные, чтобы снизить количество обращений к дискам, но это происходит незаметно для клиентов. Клиенты управляют двумя кэшами, одним для атрибутов файлов (i-узлов) и одним для данных. Когда требуется либо i-узел, либо блок файла, проверяется, нельзя ли получить эту информацию из кэша. Если да, то обращения к сети можно избежать.

Хотя кэширование на стороне клиента во много раз повышает производительность, оно также приводит к появлению непростых проблем. Например, если два клиента сохранили в своих кэшах один и тот же блок файла, а затем один из клиентов его модифицировал, тогда другой клиент, считывая этот блок, получает из кэша старое значение блока. Учитывая серьезность данной проблемы, реализация NFS пытается смягчить ее остроту несколькими способами. Во-первых, с каждым блоком кэша ассоциирован таймер. Когда время истекает, запись считается недействительной. Как правило, для блоков с данными таймер устанавливается на 3 секунды, а для блоков каталога – на 30 секунд. Таким образом риск несколько снижается. Кроме того, при каждом открытии кэшированного файла серверу посылается сообщение, чтобы определить, когда в последний раз был модифицирован этот файл. Если последнее изменение произошло после того, как была сохранена в кэше локальная копия файла, эта копия из кэша удаляется, а с сервера получается новая копия. Наконец, каждые 30 секунд истекает время таймера, и все модифицированные («грязные») блоки кэша посылаются на сервер. Хотя такая схема и далека от совершенства, но она успешно используется системой в большинстве практических случаев.

7.6. Безопасность в UNIX

Основные понятия

Каждый пользователь операционной системы UNIX регистрируется в системе, получая свой уникальный UID (User ID – идентификатор пользователя). UID представляет собой целое число в пределах от 0 до 65 535. Идентификатором владельца помечаются файлы, процессы и другие ресурсы. По умолчанию владельцем файла является пользователь, создавший этот файл, хотя владельца можно сменить.

Пользователи могут организовываться в группы, которые также нумеруются 16-разрядными целыми числами, называемыми GID (Group ID – идентификатор группы). Назначение пользователя к группе выполняется вручную системным администратором и заключается в создании нескольких записей в системной базе данных, в которой содержится информация о том, какой пользователь к какой группе принадлежит. Вначале пользователь мог принадлежать только к одной группе, но теперь в некоторых версиях системы UNIX пользователь может одновременно принадлежать к нескольким группам.

Основной механизм безопасности в операционной системе UNIX заключается в следующем. Каждый процесс несет на себе UID и GID своего владельца. Когда создается файл, он получает UID и GID создающего его процесса. Файл также получает набор разрешений доступа, определяемых создающим процессом. Эти разрешения определяют доступ к этому файлу для владельца файла, для других членов группы владельца файла и для всех прочих пользователей. Для каждой из этих трех категорий определяется три вида доступа: чтение, запись и исполнение файла, что обозначается соответственно буквами r, w и х (read, write, execute). Возможность исполнять файл, конечно, имеет смысл только в том случае, если этот файл является исполняемой двоичной программой. Попытка запустить файл, у которого есть разрешение на исполнение, но который не является исполняемым (то есть не начинается с соответствующего заголовка), закончится ошибкой. Поскольку существует три категории пользователей и три вида доступа для каждой категории, все режимы доступа к файлу можно закодировать 9 битами.

Пользователь, UID которого равен 0, является особым пользователем и называется суперпользователем (superuser или root). Суперпользователь может читать и писать все файлы в системе, независимо от того, кто ими владеет и как они защищены. Процессы с UID=0 также обладают возможностью обращаться к небольшой группе системных вызовов, доступ к которым запрещен для обычных пользователей. Как правило, пароль суперпользователя известен только системному администратору.

Каталоги представляют собой файлы и обладают теми же самыми режимами защиты, что и обычные файлы. Отличие состоит в том, что бит х интерпретируется в отношении каталогов как разрешение не исполнения, а поиска в каталоге. У специальных файлов, соответствующих устройствам ввода-вывода, есть те же самые биты защиты. Благодаря этому может использоваться тот же самый механизм для ограничения доступа к устройствам ввода-вывода. Существует общая проблема регулируемого доступа ко всем устройствам ввода-вывода и другим системным ресурсам. Эта проблема решается с помощью добавления к указанным выше 9 бит нового бита защиты – бита SETUID. Когда выполняется программа с установленным битом SETUID, то запускаемому процессу присваивается не UID вызвавшего его пользователя или процесса, a UID владельца файла. Когда процесс пытается открыть файл, то проверяется его рабочий UID, а не UID запустившего его пользователя. Таким образом, если программой, обращающейся к принтеру, будет владеть демон с установленным битом SETUID, тогда любой пользователь сможет запустить ее и запущенный процесс будет обладать полномочиями демона, но только для запуска этой программы (которая может устанавливать задания в очередь на принтер).

Помимо бита SETUID, есть также еще и бит SETGID, работающий аналогично и временно предоставляющий пользователю рабочий GID программы. Однако на практике этот бит почти не используется.

005. Файловые системы — Виктор Ашик


Похожие статьи.

Понравилась статья? Поделиться с друзьями: