Text
                    Эта книга посвящена сообществу BSD.
Без вклада членов этого сообщества
было бы не о чем писать


The Design and Implementation of the FreeBSD Operating System Marshall Kirk McKusick George V. Neville-Neil MrAddison-Wesley TT Boston • San Francisco • New York • Toronto • Montreal London • Munich • Paris • Madrid Capetown • Sydney • Tokyo • Singapore • Mexico City
FreeBSD: архитектура и реализация Маршалл Кирк МакКузик Джордж В. Невилл-Нил КУДИЦ-ОБРАЗ Москва • 2006
ББК 32.973.26-018.2 УДК 681.3 М15 МакКузик М. К., Невилл-Нил Д. В. FreeBSD: архитектура и реализация / Пер. с англ. - М: КУДИЦ-ОБРАЗ, 2006. - 800 с. В книге подробно описана внутренняя структура операционной системы FreeBSD. В ней рассказывается об организации ядра FreeBSD и его службах, управлении процессами, потоками и памятью, сетевой и локальной файловых подсистемах и сетевых протоколах. Отражены вопросы межпроцессного взаимодействия и сетевой коммуникации. Рассматривается одна из последних версий FreeBSD - 5.2. Информация представлена в структурированном виде, поэтому книга может быть исполь- использована в качестве справочника. Данная книга полезна широкому кругу читателей: от системных администраторов, использующих FreeBSD, до системных программистов. Маршалл Кир к МакКузик, Джордж В. Невилл-Нил FreeBSD: архитектура и реализация Учебно-справочное издание Перевод с англ. Р. Галеев Корректор В. Клименко Научный редактор И. Мурашко Макет О. Горкина ООО «ИД КУДИЦ-ОБРАЗ» 119049, Москва, Ленинский пр-т., д. 4, стр. 1А Тел.: 333-82-11, ok@kudits.ru Подписано в печать 10.11.2005 г. Отпечатано в полном соответствии Формат 70х 100/16 с качеством предоставленных диапозитивов Печать офсетная. Бум. офсетная. ^^Z^^tT^™* Усл. печ л. 64,5. Тираж 2000. Заказ 2094 "нскии пер., 6 ISBN 5-9579-0103-2 (рус.) © Перевод, макет и обложка «ИД КУДИЦ-ОБРАЗ», 2006 ISBN 0-201 -70245-2 © 2005 by Pearson Education, Inc. Authorized translation from the English language edition, entitled DESIGN AND IMPLEMENTATION OF THE FREEBSD OPERATING SYSTEM, THE, 1st Edition, ISBN 0201702452, by MCKUSICK, KIRK; BOSTIC, KEITH; KARELS, MICHAEL J.; and LEFFLER, SAMUEL J., published by Pearson Education, Inc, publishing as Addison Wesley Professional, Copyright © 2005 by Pearson Education, Inc. All rights reserved. No part of this book may be reproduced or transmitted in any forms or by any means, electronic or mechanical, including photocopying, recording or by any information storage retrieval system, without permis- permission from Pearson Education Inc. UNIX является зарегистрированной торговой маркой Х/Open в Соединенных Штатах Америки и других странах. Многие из обозначений, используемых производителями и продавцами для отметки своих продуктов, утверждены в качестве товарных знаков. Там, где эти обозначения появляются в данной книге, и издательство Addison-Wesley знало об их использовании в качестве товарного знака, обозначения были набраны с заглавной буквы или всеми заглавными буквами. Авторы и издатель предприняли все меры при подготовке данной книге, но не дают явных или предпола- предполагаемых гарантий любого рода и не предполагают никакой ответственности за ошибки или упущения. Не пред- предполагается никакой ответственности за случайный или побочный ущерб, связанный с использованием сведе- сведений или программ, содержащихся в книге.
Предисловие Данная книга следует официальным и полным описаниям дизайна и реализации версий 4.3BSD и 4.4BSD операционной системы UNIX, разработанных в Калифорний- Калифорнийском университете в Беркли. С момента финального выпуска Беркли в 1994 г. несколь- несколько групп продолжили разработку BSD. В данной книге подробно излагается FreeBSD, система с наибольшим числом разработчиков и самыми широко распространенными редакциями. Хотя редакция FreeBSD включает около 1000 вспомогательных программ в своей основной системе и около 10 000 дополнительных утилит в коллекции портов, данная книга фокусируется почти исключительно на ядре. UNIX-подобные системы UNIX-подобные системы включают системы традиционных производителей, такие, как Solaris и HP-UX, редакции на основе Linux, такие, как Red Hat, Debian, Suse и Slackware, и редакции на основе BSD, такие, как FreeBSD, NetBSD, OpenBSD и Darwin. Они рабо- работают на компьютерах от портативных до суперкомпьютеров. Это предпочтительные операционные системы для большинства многопроцессорных, графических и векторных систем, широко использующиеся для первоначальной задачи разделения времени. Будучи наиболее обычной платформой для обеспечения сетевых служб Интернета (от FTP до WWW), они в целом являются наиболее переносимой операционной систе- системой, когда-либо разработанной. Частью эта переносимость обязана языку реализации систем, С [Kernighan & Ritchie, 1989] (который сам является широко переносимым языком), а частью обязана элегантному дизайну системы. Начиная с отправной точки в 1969 г. [Ritchie & Thompson, 1978], система UNIX развилась в нескольких расходящихся и вновь сходящихся направлениях. Оригиналь- Оригинальные разработчики продолжали развивать современный технический уровень в девятой и десятой редакциях UNIX в рамках AT&T Bell Laboratories и их преемника UNIX Plan 9. Тем временем AT&T лицензировала UNIX System V в качестве продукта до продажи его Novell. Novell передала торговую марку UNIX X/OPEN и продала исходный код и права на распространение Santa Cruz Operation (SCO). Как на System V, так и на девя- девятую редакцию UNIX сильно повлияло Berkley Software Distributions, созданное группой Computer System Research Group (CSRG) Калифорнийского университета в Беркли. Операционная система Linux, хотя она и разработана независимо от других вариантов UNIX, реализует интерфейс UNIX. Таким образом, приложения, разработанные для работы на других основанных на UNIX платформах, легко могут быть перенесены для работы на Linux.
Berkley Software Distributions Дистрибутивы из Беркли были первыми основанными на UNIX системами, которые ввели многие важные особенности, включая следующие: * поддержка виртуальной памяти с подкачкой по требованию; автоматическое конфигурирование аппаратных средств и системы ввода/вывода; быстрая и восстанавливаемая файловая система; * примитивы межпроцессного взаимодействия (IPC) на основе сокетов; эталонная реализация TCP/IP. Версии Беркли нашли свой путь в системы UNIX многих производителей и были внутренне использованы группами разработчиков многих других производителей. Реализация набора сетевых протоколов TCP/IP в 4.2BSD и 4.3BSD и доступность этих систем сыграли ключевую роль в превращении набора сетевых протоколов TCP/IP в международный стандарт. Даже производители He-UNIX-систем, такие, как Microsoft, приспособили дизайн сокетов Беркли для своего интерфейса IPC Winsock. Выпуски BSD оказали также сильное влияние на стандарт интерфейса операцион- операционных систем POSIX (стандарт IEEE 1003.1) и связанные с ним стандарты. Несколько особенностей - такие, как надежные сигналы, управление заданиями, несколько групп доступа для процесса и процедуры для операций с каталогами - были приспособлены из BSD для POSIX. Ранние выпуски BSD содержали лицензионный код UNIX, требуя таким образом от получателей наличия лицензии исходного кода AT&T для получения и использова- использования BSD. В 1988 г. Беркли разделил свой дистрибутив на лицензированный AT&T и на свободно распространяемый код. Свободно распространяемый код был лицензирован отдельно и мог быть получен, использован и распространен дальше любым желающим. Финальный свободно распространяемый выпуск 4.4BSD-Lite2 из Беркли 1994 г. содержал почти полное ядро и все важные библиотеки и утилиты. В 1993 г. возникли две группы, NetBSD и FreeBSD, начавшие поддерживать и рас- распространять системы, построенные из свободно распространяемых выпусков, сделан- сделанных Беркли. Группа NetBSD придавала особое значение переносимости и минимали- минималистскому подходу, перенеся системы на более чем 40 платформ и продолжая сохранять систему небольшой, чтобы поддерживать встроенные приложения. Группа FreeBSD делала упор на максимальную поддержку архитектуры PC и нацеливала систему на простоту установки и распространение среди как можно более широкой аудитории. В 1995 г. от группы NetBSD отделилась группа OpenBSD для разработки дистрибутива с упором на безопасность. В течение ряда лет было здоровое соперничество среди дистрибутивов BSD, причем они обменивались множеством идей и кодом.
Предисловие 7 Материал, рассматриваемый в данной книге Данная книга о внутренней структуре ядра FreeBSD 5.2 и о концепциях, структурах данных и алгоритмах, использованных при реализации возможностей системы FreeBSD. Уровень детализации сходен с уровнем книги Bach's о UNIX System V [Bach, 1986]; однако данное руководство фокусируется на возможностях, структурах данных и ал- алгоритмах, использованных в варианте FreeBSD операционной системы UNIX. Система освещает FreeBSD от уровня системных вызовов и ниже - от интерфейса ядра до самого аппаратного обеспечения. Ядро включает системные возможности, такие, как управление процессами, виртуальная память, система ввода/вывода, файловые систе- системы, механизм ТРС-сокетов и реализация сетевых протоколов. Материал, относящийся к уровню выше системных вызовов - такому, как библиотеки, оболочки, команды, языки программирования и другие интерфейсы пользователя, - не включен, за исключением некоторых сведений, относящихся к интерфейсу терминалов и запуску системы. Следуя расположению, впервые принятому в книге Organick для Multics [Organick, 1975], данная книга является подробным исследованием современной операционной системы. Там, где существенно определенное аппаратное обеспечение, книга ссылается на архитектуру персональных компьютеров (PC) Intel. Поскольку FreeBSD придает особое значение разработке на PC, эта архитектура характеризуется наиболее полной поддержкой и поэтому является наиболее удобной точкой привязки. Использование профессионалами в вычислительной технике FreeBSD широко используется для поддержки ядра инфраструктуры многих компаний по всему миру. Поскольку она может быть построена с использованием небольшого дис- дискового пространства, она находит также возрастающее применение во встроенных при- приложениях. Условия лицензирования FreeBSD не требуют распространения изменений и улучшений системы. Условия лицензирования Linux требуют, чтобы все изменения и улучшения ядра были сделаны доступными в виде исходного кода за минимальную плату. Таким образом, компании, которым нужно контролировать распространение своей интеллектуальной собственности, строят свои продукты с использованием FreeBSD. Данная книга предназначена для непосредственного использования профессионалами, работающими с системами FreeBSD. Лица, связанные с технической поддержкой и продажей, могут изучить возможности и ограничения системы; разработчики приложе- приложений могут изучить, как эффективно и рационально скоординировать работу с системой; системные администраторы, не имеющие прямого опыта работы с ядром FreeBSD, могут изучить, как сопровождать, настраивать и конфигурировать систему а системные про- программисты могут изучить, как расширять, улучшать и слаженно работать с системой. Круг читателей, которым данная книга может принести пользу, включает разра- разработчиков операционных систем, системных программистов, разработчиков приложе- приложений UNIX, администраторов и любознательных пользователей. Книгу можно читать
8 Предисловие в качестве дополнения к исходному коду системы, причем она попадает по уровню рассмотрения деталей между справочными страницами (manual pages) и исходным кодом. Но данная книга не является исключительно ни руководством по программиро- программированию для UNIX, ни руководством пользователя (если вам нужен учебник, обратитесь к Libes & Ressler [1988]). Предварительное знакомство с использованием некоторых версий системы UNIX (см., например, Stevens [1992]) и языка программирования С (см., например, Kernighan & Ritchie [1989]) было бы чрезвычайно полезным. Использование в ходе обучения операционным системам Данная книга подходит для использования в качестве справочного руководства для создания основы для учебника в начальной программе по операционным системам. Она не предназначена для использования в качестве учебника, вводящего в операционные системы; читатель должен уже быть знаком с терминологией, такой, как управление памя- памятью, планирование процессов и системы ввода/вывода [Silberschatz et al., 2002]. Знакомство с концепциями сетевых протоколов [Comer, 2000; Stallings, 2000; Tanenbaum, 2003] будет полезным для понимания некоторых из последующих глав. Данная книга может использоваться в сочетании с копией системы FreeBSD для более углубленного курса операционных систем. Задания студентам могут включать изменения или замены ключевых компонентов системы, таких, как планировщик, демон подкачки, файловые системы, сигнализации потоков, различные сетевые уровни и управления вводом/выводом. Способность загружать, заменять и выгружать модули из работающего ядра дает студентам возможность экспериментировать без необходи- необходимости компилировать и повторно загружать систему. Работая с реальной операцион- операционной системой, студенты могут непосредственно оценивать и испытывать различные эффекты своих изменений. Благодаря интенсивным экспертным оценкам и настойчи- настойчивому использованию хорошо определенных стандартов программирования в течение своей 25-летней истории ядро FreeBSD стало значительно лучшего качества, более мо- дульно и соответственно проще для понимания и изменения, чем большинство про- программных проектов такого же размера и возраста. В конце каждой главы предусмотрены упражнения. Они подразделены на три кате- категории, обозначаемые одной или двумя звездочками или их отсутствием. Ответы на упражнения, которые не имеют звездочек, можно найти в тексте. Упражнения с одной звездочкой требуют дополнительного шага рассуждений или интуиции помимо кон- концепций, представленных в тексте. Упражнения с двумя звездочками представляют большие дизайнерские проекты или открытые исследовательские вопросы. Организация В данном руководстве обсуждаются как философские проблемы, так и проблемы про- проектирования, а также подробности действительной реализации. Часто обсуждение начинается с уровня системных вызовов и спускается на уровень ядра. Для прояснения
Предисловие 9 структур данных и потока управления используются таблицы и рисунки. Псевдокод, сходный с языком С, отображает алгоритмы. Жирным шрифтом обозначены имена программ и имена путей файлов. Курсивом вводятся термины, которые указаны в глос- глоссарии и которые обозначают имена системных вызовов, переменных, процедур и структур. Имена процедур (кроме системных вызовов) обозначаются дополнительно парой скобок в конце имени (например, mallocQ является именем процедуры, тогда как argv является именем переменной). Книга подразделена на пять частей, организованных следующим образом. * Часть I «Обзор». Три вводные главы предоставляют контекст для всей операци- операционной системы и для оставшейся части книги. Глава 1 «История и цели», делает набросок исторического развития системы, подчеркивая исследовательскую направленность системы. Глава 2 «Обзор дизайна FreeBSD», описывает службы, предлагаемые системой, и обрисовывает внутреннюю организацию ядра. В ней также обсуждаются проектные решения, которые были сделаны при разработке системы. В разделах с 2.3 по 2.14 главы 2 дается обзор соответствующих глав. В главе 3 «Службы ядра», объясняется, как осуществляются системные вызовы, и подробно описываются основные службы ядра. Часть II «Процессы». Первая глава в данной части — глава 4 «Управление процес- процессами» - готовит основу для последующих глав, описывая структуру процесса, алгоритмы, использованные для планировки выполнения потоков, составляющих процесс, и механизмы синхронизации, используемые системой для обеспечения согла- согласованного доступа к резидентным структурам данных ядра. В главе 5 «Управление памятью», подробно обсуждается система управления виртуальной памятью. * Часть III «Система ввода/вывода». Сначала в главе 6 «Обзор системы ввода/ вывода», объясняется системный интерфейс ввода/вывода и описывается струк- структура средств, которые поддерживают этот интерфейс. За этим введением следуют четыре главы, описывающие подробности главных частей системы ввода/вывода. В главе 7 «Устройства», приводится описание архитектуры ввода/вывода PC и того, как управляется подсистема ввода/вывода и как ядро вначале планирует, а затем управляет подключением и отключением присоединенных устройств. В главе 8 «Локальные файловые системы», приводятся подробности структур данных и алгоритмов, реализующих файловые системы, с точки зрения прикладных программ, а также то, как локальные файловые системы связываются с интерфей- интерфейсами устройств, описанными в главе 7. В главе 9 «Сетевая файловая система», объясняется сетевая файловая система с точки зрения как сервера, так и клиента. В главе 10 «Управление терминалами», обсуждается поддержка символьных терминалов и предоставляется описание драйвера устройства псевдотерминала.
10 Предисловие * Часть IV «Межпроцессное взаимодействие». Глава 11 «Межпроцессное взаимо- взаимодействие», описывает механизм для предоставления взаимодействия между связан- связанными или несвязанными процессами. Главы 12 и 13 «Сетевое взаимодействие» и «Сетевые протоколы», тесно связаны, поскольку возможности, описанные в первой, реализованы в конкретных протоколах, таких, как набор протоколов TCP/IP, которые объясняются в последней. * Часть V «Работа системы». В главе 14 «Запуск и остановка», обсуждается запуск системы и ее остановка и объясняется инициализация системы на уровне процесса от инициализации ядра до регистрации пользователя. Книга предназначена для чтения в том порядке, как представлены главы, однако части, кроме I, независимы одна от другой и их можно читать отдельно. Главу 14 следует читать после всех остальных, но подготовленные читатели могут найти ее полезной саму по себе. В конце книги имеется глоссарий с краткими определениями главных терминов и предметный указатель. В каждой главе есть раздел ссылок на литературу с упомина- упоминаниями связанного материала. Приобретение BSD Все дистрибутивы BSD доступны либо для загрузки по сети, либо на сменных носителях, таких, как CD-ROM или DVD. Информацию о получении исходного кода и двоичных файлов для FreeBSD можно получить на сайте http://www.FreeBSD.org. Дистрибутив NetBSD откомпилирован и готов к запуску на большинстве архитектур рабочих станций. За дополнительными сведениями обратитесь по адресу http://www.NetBSD.org/. Дистри- Дистрибутив OpenBSD откомпилирован и готов к запуску на широком ряде архитектур рабочих станций и был тщательно проверен на предмет безопасности и надежности. За дополнительной информацией обращайтесь на веб-сайт проекта OpenBSD http:// www.OpenBSD.org/. Для тех твердолобых, кто дочитал до конца предисловия, наградой является воз- возможность получения футболок с репродукцией оригинальной иллюстрации, сделан- сделанной Джоном Лассетером (John Lasseter) для обложки данной книги (да, это тот самый Джон Лассетер, который руководил производством «Истории игрушки» (Toy Story). За дополнительной информацией о приобретении футболки посетите веб-страницу «История футболок BSD» по адресу http://www.McKusick.com/beastie/. Другие доступ- доступные для продажи вещи на сайте включают 32-часовой учебный видеокурс, основан- основанный на данной книге, 40-часовой углубленный видеокурс, основанный на исходном коде FreeBSD 5.2, и 2,5-часовую видеолекцию по истории BSD от Беркли. Эти предметы описаны в рекламе, следующей за предметным указателем1. В английском издании книги. - Примеч. пер.
Предисловие 11 Благодарности Мы выражаем особую благодарность Нейт Лоусон (Nate Lawson) за предоставление неоце- неоценимого проникновения в суть работы архитектуры PC и интерфейса FreeBSD для нее. Мы благодарим также следующих людей за прочтение и комментирование почти всей книги: Майкла Шустера (Michael Schuster, Sun Microsystems, Inc.) и наших рецен- рецензентов из издательства Addison-Wesley Криса Купера (Chris Cooper, Hewlett-Packard) и Роберта Китцбергера (Robert Kitzberger, IBM). Мы благодарим следующих людей, каждый из которых прочел и прокомментировал начальные черновики различных глав книги: Samy Al Bahra (Kemeled.com), Dorr H. Clark (Santa Clara University), Matthew Dillon (The DragonFly BSD Project), John Dyson, Andreas Gustafsson, Poul-Henning Kamp (проект FreeBSD), David G. Lawrence (проект FreeBSD), Samuel Leffler, M. Warner Losh (Timing Solutions), Andre Oppermann (Internet Business Solutions AG), David I. Parfitt (независимый хакер), Doug Rabson (Qube Software Ltd.), Jeffrey Roberson (проект FreeBSD), Soren Schmidt (старший разработчик FreeBSD), Ken Smith (University at Buffalo CSE Department), Gregory Sutter (Daemon News), Charles P. Wright (SUNY Stony Brook) и Erez Zadok (Stony Brook University). Мы признательны нашему редактору Петеру Гордону (Peter Gordon), сохранявше- сохранявшему веру в нашу способность написать книгу, несмотря на несколько лет отсрочек с нашей стороны, и ускорявшему производство, когда мы наконец завершили руко- рукопись. Мы благодарим всех работников издательства Addison-Wesley, помогавших нам в завершении книги: управляющего редактора John Fuller, помощника редактора Bernie Gaffhey, супервизора производства Elizabeth Ryan и дизайнера обложки Chuti Prasertsith. Мы благодарим также персонал Stratford Publishing Services: менеджера службы редакции Kathy Glidden, технического редактора Debbie Prato и корректора Hilary Farquhar. Наконец, мы выражаем благодарность за вклад Jaap Akkerhuis, разра- разработавшему макросы troff для книг BSD, и John Lasseter за рисунок демона BSD, использованный на обложке. Данная книга была создана с использованием реализаций Джеймса Кларка (James Clark) программ pic, tbl, eqn и groff. Предметный указатель был создан с помощью сценариев awk, полученных от программ индексирования, написанных Джоном Бентли и Брайном Керниганом [Bentley & Kemighan, 1986]. Большая часть рисунков создана с помощью xfig. Размещение рисунков и устранение висячих строк были вы- выполнены макросами groff, но удаление висячих строк и создание нижней части четных страниц пришлось делать от руки. Мы просим читателей присылать нам советы по улучшению и комментарии каса- касательно типографских и прочих найденных в книге ошибок; электронную почту отправ- отправляйте, пожалуйста, по адресу: FreeBSDbook-bugs@McKusick.COM.
12 Ссылки Ссылки Bach, 1986. М. J. Bach, The Design of the UNIX Operating System, Prentice-Hall, Englewood Cliffs, NJ, 1986. Bentley & Kernighan, 1986. J. Bentley & B. Kernighan, "Tools for Printing Indexes", Computing Science Technical Report 128, AT&T Bell Laboratories, Murray Hill, NJ, 1986. Comer, 2000. D. Comer, Internetworking with TCP/IP Volume 1, 4th ed., Prentice-Hall, Upper Saddle River, NJ, 2000. Kernighan & Ritchie, 1989. B. W. Kernighan & D. M. Ritchie, The С Programming Language, 2nd ed., Prentice-Hall, Englewood Cliffs, NJ, 19891. Libes & Ressler, 1988. D. Libes & S. Ressler, Life with UNIX, Prentice-Hall, Englewood Cliffs, NJ, 1988. Organick, 1975. E. I. Organick, TheMultics System: An Examination of Its Structure, MIT Press, Cambridge, MA, 1975. Ritchie & Thompson, 1978. D. M. Ritchie & K. Thompson, "The UNIX Time-Sharing System", Bell System Tech- Technical Journal, vol. 57, no. 6, Part 2, pp. 78-90, July-August 1978. Оригинальная версия [Comm. ACM vol. 7, no. 7, pp. 365—375 (July 1974)\ описывала 6-е издание; данная ссылка описывает 7-е издание. Silberschatz et al., 2002. A. Silberschatz, P. Galvin, & G. Gagne, Operating System Concepts, 6th ed., John Wiley and Sons, Hoboken, NJ, 2002. Stallings, 2000. R. Stallings, Data aMnd CoMmputer Communications, 6th ed., Prentice Hall, Hoboken, NJ, 2000. Stevens, 1992. W. Stevens, Advanced Programming in the Unix Environment, Addison-Wesley, Reading, MA, 1992. Tanenbaum, 2003. A. S. Tanenbaum, Computer Networks, 4th ed, Prentice-Hall, Englewood Cliffs, NJ, 20032. 1 Русский перевод: Брапн Керниган, Денис Ритчи. Язык программирования Си. 2-е изд., Невский диалект, 2000. - Примеч. науч. ред. 2 Русский перевод: Э. Таненбаум. Компьютерные сети 4-е изд, С.-Петербург, Питер, 2004. - Примеч. науч. ред.
Об авторах Джордж В. Невилл-Нил (слева) и Маршалл Кирк МакКузик (справа) Маршалл Кирк МакКузик (Marshall Kirk McKusick) пишет книги и статьи, кон- консультирует и ведет курсы по предметам, связанным с UNIX и BSD. Будучи в Кали- Калифорнийском университете в Беркли, реализовал быструю файловую систему 4.2BSD и был научным сотрудником по компьютерным исследованиям в группе исследований компьютерных систем Беркли (Computer Systems Research Group - CSRG), наблюдая за разработкой и выпуском 4.3ВSD и 4.4BSD. Областью его особых интересов являют- являются системы виртуальной памяти и файловые системы. Он получил диплом инженера- электрика Корнельского университета и защитил диссертацию в Калифорнийском университете в Беркли, получив степень магистра по вычислительной технике и управлению бизнесом, а затем докторскую степень по вычислительной технике. Он дважды был президентом правления Usenix Association, в настоящее время являет- является членом редакционной коллегии журнала ACM Queue и членом Usenix Association,
14 Об авторах ACM и IEEE. В свободное время любит плавать, нырять с аквалангом и коллекцио- коллекционировать вина. Вино хранится в специально сконструированном винном погребе (который доступен через веб по адресу http://www.McKusick.com/cgi-bin/readhouse) в подвале дома, который он делит с Эриком Аллманом (Eric Allman), партнером по дому в 25 с небольшим лет. Джордж В. Невилл-Нил (George V. Neville-Neil) работает над сетями и кодом операционных систем для развлечения и получения прибыли, а также преподает на курсах по различным предметам, связанным с программированием. Областью его ин- интересов является исследование кода, операционные системы реального времени и сети. Он получил степень бакалавра по вычислительной технике в Северо-Вос- Северо-Восточном университете в Бостоне, штат Массачусетс. Он служит в редакционной колле- коллегии журнала ACM Queue и является членом Usenix Association, ACM и IEEE. Он заяд- заядлый велосипедист, мотоциклист и путешественник, сделавший Сан-Франциско своим домом с 1990 г.
Часть I Обзор
Глава 1 История и цели 1.1. История UNIX Операционная система UNIX широко использовалась в течение свыше 30 лет и помогла определить многие области вычислительной техники. Хотя в разработку UNIX внесли (и продолжают вносить) вклад многие личности и организации, эта книга сосредоточи- сосредоточивается главным образом на линии развития BSD. # Bell Laboratories, создавшие UNIX. # Группа исследований компьютерных систем (Computer Systems Research Group - CSRG) в Калифорнийском университете в Беркли, давшая UNIX виртуальную память и эталонную реализацию TCP/IP. Проект FreeBSD, проект NetBSD и проект OpenBSD, продолжающие работу, нача- начатую CSRG. Операционная система Darwin в основе OS X Apple. Darwin основана на FreeBSD. Происхождение Первая версия системы UNIX была разработана в Bell Laboratories в 1969 г. Кеном Томпсоном (Ken Thompson) в качестве частного исследовательского проекта для ис- использования с PDP-7, не имевшей операционной системы. К Томпсону вскоре присое- присоединился Денис Ритчи (Dennis Ritchie), который не только внес вклад в создание проек- проекта и реализацию системы, но также создал язык программирования С. В дальнейшем система была полностью переписана на С, почти без ассемблера. Оригинальный эле- элегантный дизайн системы [Ritchie, 1978] и разработки в течение первых 15 лет [Ritchie, 1984а; Compton, 1985] сделали UNIX важной и мощной операционной системой [Ritchie, 1987].
18 Глава 1. История и цели Ритчи, Томпсон и другие ранние разработчики UNIX в Bell Laboratories работали до этого над проектом Multics [Peirce, 1985; Organick, 1975], который сильно повлиял на эту более новую операционную систему. Даже имя UNIX является просто калам- каламбуром с Multics; в областях, в которых Multics пытался выполнять множество задач, UNIX старался выполнить лишь одну задачу, но сделать это хорошо. Базовая организа- организация файловой системы UNIX, идея использования процесса пользователя для команд- командного интерпретатора, общая организация интерфейса файловой системы и многие другие характеристики системы пришли непосредственно из Multics. Были внедрены также идеи из других операционных систем, таких как CTSS Мас- сачусетского технологического института (Massachusetts Institute of Technology - MIT). Операция fork для создания новых процессов пришла из операционной системы GENIE Беркли (SDS-940, позже XDS-940). Возможность недорогого создания процес- процессов для пользователя вела к использованию одного процесса на команду, вместо ис- использования для команд вызовов процедур, как было сделано в Multics. Исследовательский UNIX Первыми большими изданиями UNIX были Исследовательские системы (Research sys- systems) из Bell Laboratories. В дополнение к самым ранним версиям UNIX эти системы включали шестую версию UNIX с системой разделения времени (Time-Sharing System, Sixth Edition), общеизвестную как V6, которая в 1976 г. была первой версией, широко доступной за пределами Bell Laboratories. Системы идентифицируются номерами редакций Руководства программиста UNIX, которые были текущими на момент соз- создания дистрибутивов. Система UNIX отличалась от других операционных систем в трех главных отно- отношениях. 1. Она была написана на языке высокого уровня. 2. Она распространялась в виде исходных кодов. 3. Она предусматривала мощные примитивы, которые обычно можно было найти лишь в операционных системах, работавших на более дорогом оборудовании. Большая часть исходного кода системы была написана на С, а не на языке ассемб- ассемблера. Господствующим убеждением в то время было то, что операционная система должна была быть написана на ассемблере, чтобы обеспечить необходимую эффектив- эффективность и получить доступ к аппаратному обеспечению. Сам язык С был достаточно высо- высокого уровня, чтобы позволить легкую компиляцию для широкого спектра компьютерного оборудования без излишних усложнений или ограничений, которые заставили бы про- программистов возвратиться к языку ассемблера, чтобы получить необходимые эффектив- эффективность или возможности. Доступ к аппаратному обеспечению был предусмотрен через ассемблерные заглушки для 3 процентов функций операционной системы - таких, как переключение контекста, - которые нуждались в них. Хотя успех UNIX не произрастает
1.1. История UNIX 19 лишь из того, что она была написана на языке высокого уровня, использование С было критическим первым шагом [Kernighan & Ritchie, 1978; Kernighan & Ritchie, 1989; Ritchie et al., 1978]. Язык Ритчи С происходил [Rosier, 1984] от языка В Томпсона, который сам происходил от BCPL [Richards & Whitby-Strevens, 1980]. В настоящее время С продолжа- продолжает развиваться [Tuthill, 1985; ISO, 1999]. Вторым важным отличием UNIX было ее распространение из Bell Laboratories в другие исследовательские учреждения в форме исходного кода. Предоставляя исход- исходный код, основатели системы гарантировали, что другие организации могли не только использовать систему, но также подправлять ее внутреннюю работу. Легкость, с ко- которой новые идеи могли быть приняты в новую систему, всегда была ключом к изме- изменениям, которые с ней делались. Каждый раз, когда развивалась новая система, пытав- пытавшаяся обставить UNIX, кто-нибудь анализировал пришельца и клонировал его основ- основные идеи в UNIX. Уникальная способность использовать небольшую, постижимую систему, написанную на языке высокого уровня, в окружении, погруженном в новые идеи, вела к системе UNIX, которая развилась значительно дальше своего скромного начала. Хотя получатели исходного кода должны были его лицензировать, для уни- университетов были доступны дешевые университетские лицензии. Таким образом, многие люди стали опытными в работе UNIX, создав платформу для мира с открытым исходным кодом, который последовал в дальнейшем. Третьим важным отличием UNIX было то, что она предоставляла отдельным поль- пользователям возможность параллельно запускать множество процессов и подключать эти процессы в конвейеры команд. В то время лишь операционные системы, рабо- работающие на больших и дорогих машинах, имели возможность запускать несколько про- процессов, а число параллельных процессов обычно строго контролировалось системным администратором. Самые ранние системы UNIX работали на PDP-11, которая была недорогой и мощной для своего времени. Тем не менее был по крайней мере один ранний перенос шестой редакции UNIX (Sixth Edition UNIX) на машину с другой архитектурой: Interdata 7/32 [Miller, 1978]. PDP-11 имела также неудобно маленькое адресное пространство. Появление машин с 32-разрядными адресными пространствами, особенно VAX-11/780, предоставило UNIX возможность расширить свои службы со включением виртуальной памяти и работы в сети. Ранние эксперименты Исследовательской группы (Research group) по предоставлению UNIX-подобных возможностей на другом оборудовании привели к заключению, что переносить всю операционную систему было так же просто, как дублировать службы UNIX под другой операционной систе- системой. Первой системой UNIX с переносимостью в качестве конкретной цели была система UNIX с разделением времени, седьмая редакция (UNIX Time-Sharing System, Seventh Edition (V7), которая работала на PDP-11 и Interdata 8/32 и имела разновид- разновидность VAX, которая называлась UNIX/32V Time-Sharing, System Version 1.0 C2V).
20 Глава 1. История и цели Исследовательская группа в Bell Laboratories разработала также восьмую, девятую и десятую версии UNIX с разделением времени (соответственно V8, V9 и V10). Их системой 1996 г. является Plan 9. AT&T UNIX System III и System V После выпуска седьмого издания в 1978 г. Исследовательская группа перешла от внеш- внешних распространений в группу поддержки UNIX (UNIX Support Group - USG). USG раньше распространяла такие системы, как UNIX Programmer's Work Bench (PWB), внутри себя, а также иногда распространяла их вовне [Mohr, 1985]. Первым внешним распространением USG после седьмого издания в 1982 г. стала UNIX System III, которая включала в себя возможности седьмого издания, 32V, а также нескольких систем UNIX, разработанных группами помимо Исследовательской груп- группы. Были включены возможности UNIX/RT (системы UNIX реального времени), а также многие возможности PWB. В 1983 г. USG выпустила UNIX System V; эта сис- система в значительной степени производная от System III. Отделение по решению суда Bell Operating Companies от AT&T позволило AT&T агрессивно продвигать System V на рынок [Bach, 1986; Wilson, 1985]. USG трансформировалась в Лабораторию разработки систем UNIX (UNIX System Development Laboratory - USDL), которая в 1984 г. выпустила UNIX System V, Release 2. В System V, Release 2, Version 4 появилась страничная подкачка [Jung, 1985; Miller, 1984], включая копирование при записи и разделяемую память. Реализация System V не была основана на системе страничной подкачки Беркли. USDL была сменена AT&T Information Systems (ATTIS), которая в 1987 г. распространяла UNIX System V, Release 3. Эта система включала потоки (STREAMS), механизм межпроцессного взаимодейст- взаимодействия (IPC), взятый из V8 [Presotto & Ritchie, 1985]. ATTIS была сменена UNIX System Laboratory (USL), которая была продана фирме Novell в 1993 г. Novell передала торго- торговую марку UNIX консорциуму X/OPEN, дав последнему исключительные права на ус- установку стандартов сертификации для использования в продуктах имени UNIX. Двумя годами позже Novell продала UNIX фирме Santa Cruz Operation (SCO). Berkley Software Distributions Помимо групп разработчиков UNIX Bell Laboratories и AT&T наиболее влиятельным был Калифорнийский университет в Беркли [DiBona et al., 1999]. Программное обес- обеспечение из Беркли выпускалось в Berkley Software Distributions (BSD) - например, 4.4BSD. Беркли был источником для имени BSD, а их дистрибутивы были первыми особыми изданиями операционной системы BSD. Первой работой Беркли VAX UNIX было добавление к 32V виртуальной памяти, подкачки по требованию и замещения страниц, выполненное в 1979 г. Вильямом Джоем (William Joy) и Озалпом Бабоуглу (Ozalp Babaoglu) с созданием 3BSD [Babaoglu & Joy, 1981]. Основанием для большого пространства виртуальной памяти 3BSD была разработка таких больших в то время
1.1. История UNIX 21 программ, как Franz LISP Беркли. Работа по управлению памятью убедила Управление перспективных исследовательских программ (DARPA) финансировать команду Беркли для последующей разработки стандартной системы DBSD) для использования подрядчиками DARPA. Целью проекта 4BSD было предоставление поддержки для сетевых протоколов Интернета DARPA, TCP/IP [Comer, 2000]. Реализация сетевых возможностей была достаточно общей для взаимодействия различных сетевых средств — от локальных сетей, таких, как Ethernet и маркерные кольца (token ring), до дальних сетей, таких, как ARPANET DARPA. Мы ссылаемся на все системы VAX UNIX Беркли после 3BSD как на 4BSD, хотя на самом деле было несколько выпусков: 4.0BSD, 4.1 BSD, 4.2BSD, 4.3BSD, 4.3BSD Tahoe и 4.3BSD Reno. 4BSD был предпочтительной операционной системой UNIX для VAX с момента, когда VAX стал впервые доступным в 1977 г., до выпуска System V в 1983 г. Большинство организаций покупали лицензию 32V, но заказывали 4BSD из Беркли. Многие установки внутри Bell System работали с 4.1 BSD (и замещали ее 4.3BSD, когда последняя стала доступной). В 4.4BSD появилась новая система виртуальной памяти. VAX достигла конца своего полезного времени жизни, поэтому 4.4BSD не была пере- перенесена на эту машину. Вместо этого 4.4BSD работала на более новых архитектурах: 68000, SPARC, MIPS и Intel PC. Работа 4BSD для DARPA направлялась руководящим комитетом, который включал многих известных людей как из коммерческих, так и академических органи- организаций. Кульминацией первоначального проекта UNIX DARPA в Беркли стал выпуск 4.2 BSD в 1983 г.; дальнейшие исследования в Беркли породили в середине 1986 г. 4.3BSD. Следующие выпуски включали выпуски 4.3BSD Tahoe в июне 1988 г. и 4.3BSD Reno в июне 1990 г. Эти выпуски были в первую очередь перенесением на ап- аппаратную платформу Computer Consoles Incorporated. Перемежающимися с ними были два свободных сетевых выпуска: 4.3BSD Netl в марте 1989 г. и 4.3BSD Net2 в июне 1991 г. Эти выпуски содержали извлеченный из 4.3BSD непатентованный код; они могли свободно распространяться в виде исходных кодов и в двоичной форме компа- компаниям и отдельным лицам, на которых не распространялась исходная лицензия UNIX. Финальным выпуском CSRG, для которого требовалась исходная лицензия AT&T, был 4.4BSD в июне 1993 г. Спустя год судебного разбирательства (см. раздел 1.3) в апреле 1994 г. был выпущен свободно распространяемый 4.4BSD-Lite. Финальным выпуском CSRG был 4.4BSD-Lite Release 2 в июне 1995 г. UNIX в мире Система UNIX является также плодотворной почвой для приложения академических усилий. Томпсону и Ритчи была присуждена премия Тьюринга Ассоциации по вычис- вычислительной технике за проект системы [Ritchie, 1984b]. Система UNIX и связанные с ней специально спроектированные обучающие системы, такие, как Tunis [Ewens et al.,
22 Глава 1. История и цели 1985; Holt, 1983], XINU [Comer, 1984] и MINIX [Tanenbaum, 1987], широко исполь- используются в курсах по операционным системам. Линус Торвалдс (Linus Torvalds) заново реализовал интерфейс UNIX в своей свободно распространяемой операционной систе- системе Linux. Система UNIX повсеместна в университетах и исследовательских учрежде- учреждениях по всему свету и даже еще шире используется в промышленности и торговле. L2, BSD и другие системы CSRG объединяла возможности не только систем UNIX, но и других операционных систем. Многие из особенностей драйверов терминалов 4BSD взяты от TEXEX/TOPS-20. Управление заданиями (концепция - не реализация) происходит от TOPS-20 и от несо- несовместимой системы с разделением времени (Incompatible Timesharing System - ITS) MIT. Интерфейс виртуальной памяти, впервые предложенный для 4.2ВSD и реализо- реализованный в конечном счете в 4.4ВSD, был основан на интерфейсе отображения файлов и интерфейсе уровня страниц, которые впервые появились в TENEX/TOPS-20. Совре- Современная система виртуальной памяти FreeBSD (см. главу 5) была приспособлена от Mach, которая сама является ответвлением 4.3BSD. Часто исходной точкой в проек- проектировании новых возможностей был Multics. Поиск эффективности был главным фактором большей части работы CSRG. Неко- Некоторые улучшения эффективности были сделаны вследствие сравнений с патентован- патентованными операционными системами для VAX, VMS [Joy, 1980; Kashtan, 1980]. Другие варианты UNIX приняли многие особенности 4BSD. UNIX System V AT&T [AT&T, 1987], стандарт POSIX.l IEEE [P1003.1, 1988] и связанный Федеральный стан- стандарт обработки информации (Federal Information Processing Standard - FIPS) Нацио- Национального бюро стандартов (National Bureau of Standards-NBS) приняли следующее. Управление заданиями (глава 2). * Надежные сигналы (глава 4). * Несколько групп с правами доступа к файлу (глава 6). Интерфейсы файловых систем (глава 8). Группа X/OPEN (первоначально состоявшая лишь из европейских производите- производителей, но теперь включающая большинство производителей UNIX США) создала Руко- Руководство по переносимости X/OPEN [X/OPEN, 1987], а недавно Руководство специфи- спецификации 1170. Эти документы определяют как интерфейс ядра, так и многие вспомога- вспомогательные программы, доступные пользователям системы UNIX. Когда в 1993 г. фирма Novell приобрела UNIX от AT&T, она передала исключительное право владения именем UNIX группе X/OPEN. Таким образом, все системы, которые хотят обозначать себя как UNIX, должны отвечать спецификациям интерфейсов X/OPEN. К настоящему времени ни одна система BSD не была когда-либо проведена через тесты специфика- спецификаций интерфейсов X/OPEN, поэтому ни одна из них не может называться UNIX.
1.2. BSD и другие системы 23 Руководства X/OPEN приняли многие из возможностей POSIX. Стандарт POSIX.1 является также международным стандартом ISO с названием SC22 WG15. Таким обра- образом, возможности POSIX были приняты в большинстве UNIX-подобных систем по всему миру. Механизм межпроцессного взаимодействия сокетов 4BSD (см. главу 11) был спро- спроектирован для обеспечения переносимости и немедленно перенесен в System III AT&T, хотя он никогда не распространялся с этой системой. Реализация 4BSD набора сетевых протоколов TCP/IP (см. главу 13) широко используется в качестве основы для дальней- дальнейших реализаций на системах от машин AT&T ЗВ с System V до VMS со встроенными операционными системами, такими, как VxWorks. CSRG тесно сотрудничала с производителями, системы которых были основаны на 4.2BSD и 4.3BSD. Эта одновременная разработка внесла свой вклад в упрощение дальнейших переносов 4.3BSD и продолжающейся разработки системы. Влияние сообщества пользователей Значительная часть работы по разработке Беркли была сделана в ответ на пожелания сообщества пользователей. Идеи и ожидания приходили не только из DARPA, главной непосредственно финансирующей организации, но также от пользователей системы в компаниях и университетах по всему миру. Исследователи в Беркли приняли не только идеи сообщества пользователей, но и реальное программное обеспечение. Вклад в 4BSD был сделан университетами и другими организациями в Австралии, Канаде, Европе, Японии и Соединенных Штатах. Этот вклад включал в себя важные особенности, такие, как авто конфигурирование и квоты диска. Некоторые идеи, такие, как системный вызов fcntl, были взяты от System V, хотя соображения лицензирования и цены не допускали использование в 4BSD какого-нибудь кода из System III или System V. В дополнение ко вкладу, который был соответствующим образом включен в дистрибутивы, CSRG также рас- распространял ряд программного обеспечения пользователей. Примером разработанных сообществом возможностей является пакет общего пользования по работе с часовыми поясами, который был принят с выпуском 4.3BSD Tahoe. Он был спроектирован и реализован международной группой, включавшей Артура Олсона (Arthur Olson), Роберта Элца (Robert Elz) и Гая Гарриса (Guy Harris), частично в результате дискуссий в группе новостей USENET comp.std.unix. Этот пакет полностью удаляет правила преобразования часовых поясов библиотеки С, по- помещая их в файлы, не требующие изменений в системном коде для изменения правил часовых поясов; это изменение особенно полезно для дистрибутивов UNIX лишь в двоичном виде. Метод позволяет также отдельным процессам выбирать правила, а не содержать в системе спецификацию одного набора правил. Дистрибутив включает большую базу данных правил, используемых во многих областях по всему миру, от Китая до Австралии и Европы. Дистрибутивы соответственно упрощаются,
24 Глава 1. История и цели поскольку не нужно по-разному настраивать программное обеспечение для различных мест назначения, пока включается вся база данных. Принятие пакета часовых поясов в BSD привлекло к технологии внимание коммерческих производителей, таких, как Sun Microsystems, заставив их включить ее в свои системы. 1.3. Переход BSD на открытый исходный код До выпуска 4.3BSD Tahoe включительно все получатели BSD должны были сначала получить лицензию на исходный код AT&T. Так было потому, что системы BSD нико- никогда не выпускались Беркли лишь в двоичном формате; дистрибутивы всегда содержали полный исходный код каждой части системы. История системы UNIX и системы BSD в частности продемонстрировала мощь предоставления исходного кода пользовате- пользователям. Вместо пассивного использования системы они активно работали над исправле- исправлением ошибок, улучшением производительности и качества и даже добавляли со- совершенно новые возможности. С возрастанием стоимости лицензий исходного кода AT&T производители, желав- желавшие строить автономные сетевые продукты на основе TCP/IP для рынка PC с использо- использованием кода BSD, сочли цены чрезмерно высокими. Поэтому они потребовали, чтобы Беркли отделил сетевой код и утилиты и предоставил их им на условиях, которые не тре- требовали бы лицензии исходного кода AT&T. Код для работы в сети TCP/IP явно не суще- существовал в 32/V и соответственно был разработан целиком в Беркли. Происходящий от BSD код для работы в сети и инструменты поддержки были выпущены в июне 1989 г. в виде Networking Release I, первого свободно распространяемого кода от Беркли. Условия лицензирования были свободными. Обладатель лицензии мог выпускать измененный или неизмененный исходный или двоичный код без отчета или отчисле- отчислений Беркли. Единственным требованием было то, чтобы уведомление об авторском праве в исходном файле было оставлено без изменений и чтобы продукты, в которые был включен этот код, указывали, что продукт содержит код от Калифорнийского уни- университета и его помощников. Хотя Беркли назначил цену 1000$ за получение ленты, любой мог получить свободную копию от того, у кого она уже была. Естественно, не- несколько больших сайтов вскоре после выпуска поместили его на анонимный FTP. Хотя код был свободно доступен, несколько сотен организаций приобрели ленты, которые помогли финансировать CSRG и способствовали дальнейшим разработкам. Networking Release 2 С успехом первого выпуска со свободным исходным кодом CSRG решила посмотреть, какую часть BSD она могла бы предоставить свободно. Кит Бостик (Keith Bostic) руко- руководил заинтересованными людьми, работающими над переписыванием утилит UNIX с нуля на основе лишь их опубликованных описаний. Их единственным вознаграждени- вознаграждением было бы перечисление их имен в списке людей, внесших вклад в создание Беркли,
1.3. Переход BSD на открытый исходный код 25 рядом с названием утилиты, которую они переписали. Поступления от разработчиков начинались медленно и осуществлялись главным образом для незначительных утилит. Но по мере того как список завершенных инструментов рос, а Бостик продолжал пред- предлагать внести свой вклад в общественные мероприятия, такие как Usenix, число вкладчиков продолжало расти. Вскоре список пересек величину в 100 утилит, и в тече- течение 18 месяцев почти все важные утилиты и библиотеки были переписаны. Ядро стало большей задачей, поскольку его нельзя было легко переписать заново. Был сделан обзор всего ядра, файл за файлом, с удалением кода, который происходил из выпуска 32/V. Когда обзор был завершен, осталось лишь шесть файлов ядра, ко- которые все еще были загрязнены и которые заведомо не могли быть переписаны. Хотя была предоставлена оплата для переписывания этих шести файлов, чтобы можно было выпустить ядро полностью, CSRG решила выпустить лишь менее сомнительный набор. CSRG добилась разрешения для расширенного выпуска от людей, стоящих над администрацией университета. После многих внутренних обсуждений и проверки методов, используемых для обнаружения патентованного кода, CSRG было дано разрешение осуществить выпуск. Первоначально задумывалось предстать с новым названием для второго свободно распространяемого выпуска. Однако написание и утверждение университетскими юристами новой лицензии потребовало бы нескольких месяцев. Поэтому новый выпуск получил название Networking Release 2, поскольку это можно было сделать лишь с помощью пересмотра утвержденного лицензионного соглашения Networking Release 1. Этот второй значительно расширенный свободно распространяемый выпуск начал поставляться в июне 1991 г. Условия распространения и стоимость были теми же, как для первого сетевого выпуска. Как и раньше, несколько сотен частных лиц и организаций заплатили взнос 1000$, чтобы получить дистрибутив из Беркли. Закрывание промежутка между дистрибутивом Networking Release 2 и полностью функциональной системой не заняло много времени. В течение шести месяцев после выпуска Билл Джолитц (Bill Jolitz) создал замену для шести недостающих файлов. Он быстро выпустил полностью откомпилированную и способную загружаться систе- систему для основанной на 386-й архитектуре PC в январе 1992 г., назвав ее 386/BSD. Распространение 386/BSD Джолитца было почти полностью осуществлено по сети. Он просто поместил ее на анонимный FTP и позволил любому желающему бесплатно ее загружать. В течение недель у него было огромное число последователей. К сожалению, необходимость сохранения полной занятости означала, что Джолитц не мог выделить время, необходимое для обработки потока поступающих исправлений ошибок и улучшений 386/BSD. Поэтому в течение нескольких месяцев после выпуска 386/BSD группа пользователей-энтузиастов 386/BSD образовала группу NetBSD для объединения своих коллективных ресурсов, чтобы помочь в сопровождении и дальней- дальнейшем усовершенствовании системы. К началу 1993 г. они делали выпуск, который стал из- известен как дистрибутив NetBSD. Группа NetBSD решила сделать упор на поддержке как можно большего числа платформ и продолжила развитие в стиле исследований, которое
26 Глава 1. История и цели осуществлялось CSRG. До 1998 г. они распространяли продукт исключительно через сеть, без дистрибутивов на носителях. Их группа продолжает ориентироваться в первую очередь на хорошо технически подготовленных пользователей. Группа FreeBSD была сформирована несколько месяцев спустя после группы NetBSD для поддержки лишь архитектуры PC и нацелена на более многочисленную и технически менее подготовленную аудиторию, во многом подобно Linux. Она разрабо- разработала продуманные сценарии установки и начала поставлять свою систему на CD-ROM с невысокой стоимостью в декабре 1993 г. Сочетание простоты установки и интенсивного продвижения в сети и большинстве торговых выставок, таких, как Comdex, привело к большой и быстрой кривой роста. FreeBSD быстро приобрела самую большую уста- установленную базу среди всех производных от Networking Release 2 систем. FreeBSD использовала также волну популярности Linux, добавив режим эмуляции Linux, который позволил применять двоичные файлы Linux для запуска на платформе FreeBSD. Эта особенность позволила пользователям FreeBSD использовать постоянно растущий набор приложений, доступных для Linux, в то же время получая устойчи- устойчивость, надежность и производительность системы FreeBSD. В 1995 г. от группы NetBSD выделилась OpenBSD. Их технической задачей было улучшение безопасности системы. Их рыночной целью было сделать систему проще для использования и более широко доступной. Таким образом, они начали произво- производить и продавать CD-ROM, использовав многие из идей упрощения установки, взятых из дистрибутива FreeBSD. Судебный процесс В дополнение к группам, организованным для свободного распространения систем, веду- ведущих начало от Networking Release 2 на магнитной ленте, для разработки и распростране- распространения коммерчески поддерживаемой версии кода была сформирована компания Berkeley Software Design Incorporated (BSDI). Подобно другим группам, она начала с добавле- добавления шести отсутствующих файлов, которые Билл Джолитц написал для своего выпуска 386/BSD. BSDI в январе 1992 г. начала продажу системы за 995$, включив как исходные, так и двоичные файлы. Она начала усиленно рекламировать 99-процентную скидку по сравнению с ценой, запрашиваемой за исходные и двоичные файлы System V. Заин- Заинтересовавшиеся читатели должны были позвонить по номеру 1-800-ITS-UNIX. Вскоре после начала кампании по продажам BSDI она получила письмо из Лабора- Лаборатории систем UNIX (USL) (главным образом дочерняя компания AT&T, созданная для развития и продажи UNIX) [Ritchie, 2004]. В письме требовалось, чтобы BSDI прекра- прекратила продвижение своего продукта в качестве UNIX, и в частности, чтобы она прекра- прекратила использование вводящего в заблуждение телефонного номера. Хотя использова- использование телефонного номера было быстро прекращено, а реклама изменена с объяснением того, что продукт не являлся UNIX, USL все еще была недовольна и подала иск, чтобы наложить запрет на продажу продукта BSDI. В иске утверждалось, что продукт BSDI
1.3. Переход BSD на открытый исходный код 27 содержит патентованный код и производственные секреты USL. USL требовала судеб- судебного запрета для приостановки продаж BSDI до тех пор, пока не будет принято реше- решение по иску, заявляя, что она понесет невосполнимые убытки от потери своих произ- производственных секретов, если продажи дистрибутивов BSDI продолжатся. В предварительных слушаниях для судебного предписания BSDI утверждала, что она просто использовала исходные коды, свободно распространяемые Калифорний- Калифорнийским университетом, плюс шесть дополнительных файлов. BSDI была готова обсудить содержание любых из шести дополнительных файлов, но не считала, что она должна нести ответственность за распространение файлов Калифорнийским университетом. Судья согласился с доводом BSDI и заявил USL, что ей придется пересмотреть свою жалобу, основываясь исключительно на шести файлах, иначе иск будет отклонен. Понимая, что у нее будет трудное время при выдвижении иска из-за всего лишь шести файлов, USL решила переформулировать иск против BSDI и против Калифорнийского университета. Как и ранее, USL требовала судебного предписания по поводу поставки Networking Release 2 в продуктах университета и BSDI. В преддверии неминуемых слушаний по поводу судебного предписания всего через несколько коротких недель, началась серьезная подготовка. Были допрошены все члены CSRG, а также почти каждый, кто работал в BSDI. Аргументы, контраргументы и ответы на контраргументы носились взад и вперед между адвокатами. Штат CSRG перешел от написания кода к написанию нескольких сотен страниц материалов, которые были использованы в юридических сводках. В декабре 1992 г. Диккинсон Р. Дебевуаз (Dickinson R. Debevoise), судья Федераль- Федерального районного суда США в Нью-Джерси, выслушал аргументы для судебного пред- предписания. Хотя судьи обычно принимают решения по судебным предписаниям немед- немедленно, он решил принять решение после подробного рассмотрения. В пятницу шесть недель спустя он выдал решение на 40 страницах, в которых иск отклонялся и отверга- отвергались все обвинения, кроме двух [Debevoise, 1993]. Оставшиеся два обвинения сужа- сужались до последних авторских прав и возможности утраты производственных секретов. Он предложил также, чтобы прослушивание состоялось сначала в суде штата, прежде чем слушаться в федеральном суде. Калифорнийский университет использовал намек и в следующий понедельник утром бросился в суд штата Калифорния со встречным иском против USL. Путем первой подачи иска в Калифорнии университет установил место действия для любых дальнейших судебных мероприятий. Конституционное право требует, чтобы все дела по штатам проводились в одном штате, чтобы не дать возможность состоятельной стороне измотать оппонента, выдвинув против него 50 исков, по одному в каждом штате. Результатом было то, что если USL захотела бы предпринять какие-либо действия против университета в судах штатов, она вынуждена была бы сделать это в Калифорнии, а не в своем родном штате Нью-Джерси.
28 Глава 1. История и цели В иске университета утверждалось, что USL не выполнила свои обязательства по возданию университету должного за использование кода BSD в System V, как того тре- требовала лицензия, которую данная фирма подписала с университетом [Linzner & Mac- Donald, 1993]. Если бы иск был признан действительным, университет просил заста- заставить USL перепечатать всю свою документацию с добавлением соответствующего ут- утверждения, уведомить всех своих лицензиатов о своей оплошности и опубликовать полностраничную рекламу в главных изданиях, таких, как Wall Street Journal и журнал Fortune, с уведомлением делового сообщества о своей непреднамеренной оплошности. Вскоре после подачи иска в суде штата USL была перекуплена у AT&T фирмой Novell. Директор-распорядитель Novell Рэй Норда (Ray Noorda) публично заявил, что он пред- предпочел бы соперничать на рынке, а не в суде. К лету 1993 г. начались переговоры по урегу- урегулированию. К сожалению, обе стороны погрузились так глубоко, что переговоры продвига- продвигались очень медленно. Под некоторым дополнительным давлением Рея Норда со стороны USL многие камни преткновения были сняты, и в конечном счете в январе 1994 г. было достигнуто соглашение. В результате были удалены три файла из 18 000, составлявших Networking Release 2, а в других файлах были сделаны несколько небольших изменений. В дополнение университет согласился добавить уведомления об авторских правах USL к примерно 70 файлам, хотя эти файлы продолжали свободно распространяться. 4.4BSD Вновь благословленный выпуск был назван 4.4BSD-Lite и вышел в июне 1994 г. на усло- условиях, идентичных тем, которые использовались в Networking release. В частности, усло- условия разрешали свободное распространение в виде исходных кодов и в двоичной форме, при единственном условии, что уведомления об авторских правах университета остают- остаются без изменений и что университет будет упомянут, когда другие используют этот код. В то же время полная система была выпущена как 4.4BSD-Encumbered («обремененная»), для которой по-прежнему требовалась лицензия USL на исходный код. В решении по судебному иску было также оговорено, что USL не будет преследовать по суду любую организацию, использующую в качестве основы для своей системы 4.4BSD-Lite. Поэтому все сохранившиеся группы BSD - BSDI, NetBSD и FreeBSD - должны были возобновить основу своего кода с исходных кодов 4.4BSD-Lite, с которым они затем объединили бы свои усовершенствования и изменения. Хотя это повторное воссоединение вызвало кратковременную задержку в разработке различных систем BSD, это была неприятность, оказавшаяся благом, поскольку она заставила различные группы повторно синхронизировать наработки, сделанные за три года с момента первого выпус- выпуска CSRG Networking Release 2. 4.4BSD-Lite Release 2 Деньги, полученные от выпусков 4.4BSD-Encumbered и 4.4BSD-Lite, были использова- использованы для финансирования на условиях частичной оплаты усилий по интеграции исправ-
1.4. Модель разработки FreeBSD 29 лений ошибок и улучшений. Эти изменения продолжались в течение двух лет до тех пор, пока частота сообщений об ошибках и улучшениях возможностей не сошла прак- практически на нет. Финальный набор изменений был выпущен под видом 4.4BSD-Lite Release 2 в июне 1995 г. Большая часть изменений, внедренных в 4.4BSD-Lite Release 2, сделала ее в конечном счете исходной основой для других систем. Хотя условие лицензии, требующее упоминать университет, было чрезвычайно по- полезным при судебном процессе, университет согласился снять его после последнего выпуска. Поскольку многие люди начали использовать для своего собственного кода уведомления об авторских правах в стиле BSD, распространение пунктов об упомина- упоминании в программном обеспечении с открытым исходным кодом стало трудным для определения и неуправляемо большим. Согласившись убрать пункт об упоминании, университет надеялся дать пример другим по использованию своей лицензии. С течением времени благодаря значительным усилиям со стороны сообщества BSD пункт упоми- упоминания был удален из многих программ с открытым исходным кодом, которые исполь- используют лицензии в стиле BSD. После выпуска 4.4BSD-Lite Release 2 CSRG была распущена. Спустя 15 лет управ- управления кораблем BSD настало время дать возможность другим со свежими идеями и безграничным энтузиазмом взять верх. Хотя могло бы показаться лучшим иметь одну централизованную организацию, наблюдающую над разработкой системы, идея наличия нескольких групп с различными уставами гарантирует, что будут пробоваться различные подходы и что не будет одного места ошибок. Поскольку система выпуска- выпускается в виде исходных кодов, лучшие идеи легко могут быть переняты другими группами. Конечно, перекрестное опыление идеями между проектами с открытым исходным кодом является обычным явлением. 1.4. Модель разработки FreeBSD Продвижение проекта с открытым исходным кодом отличается от хода традиционной разработки программного обеспечения. При традиционной разработке персонал оплачива- оплачивается, поэтому есть возможность иметь менеджеров и архитекторов системы, состав- составляющих расписания и направляющих деятельность программистов. При открытом исход- исходном коде разработчики являются добровольцами. Они имеют тенденцию быть временны- временными, обычно осуществляющими один или два проекта, до того как найти какую-нибуць другую область деятельности, на которую предпочитают тратить свое свободное время. Их нельзя направлять, поскольку они работают лишь над тем, что их интересует. Посколь- Поскольку их работа, семьи и общественная жизнь часто берут верх над работой с проектом, невоз- невозможно составить общее расписание. В конце концов, нет оплачиваемого персонала, который выполнял бы управляющую роль традиционной разработки. Таким образом, ус- успешный проект разработки с открытым исходным кодом должен быть самоорганизующим- самоорганизующимся и элегантно справляющимся с высокой текучестью своих активных разработчиков.
30 Глава 1. История и цели Модель разработки, используемая FreeBSD (а также NetBSD и OpenBSD), была впервые приведена в действие CSRG [McKusick et al., 1989]. CSRG всегда была небольшой группой разработчиков программного обеспечения. Такое ограничение ресурса требовало тщательного управления разработкой программного обеспечения. Тщательная координация была необходима не только персоналу CSRG, но также и членам общего сообщества, вносившим вклад в разработку системы. У некоторых внешних разработчиков было разрешение на непосредственное изменение главной копии исходного кода системы. Люди, которым давался доступ к главным источникам, предварительно тщательно отбирались, но за ними не было внимательного надзора. Каждый, кто совершал изменения исходного кода системы, получал уведомления обо всех изменениях, что давало возможность каждому быть в курсе происходивших в сис- системе изменений. От каждого требовалось, чтобы любые нетривиальные изменения просматривались по крайней мере еще одним лицом, перед тем как внести их в дерево. Такая модель давала возможность многим направлениям разработки осуществляться параллельно, сохраняя в то же время связность проекта. Проект FreeBSD организован во многом таким же образом, как CSRG. Весь проект FreeBSD, включая весь исходный код, документацию, списки ошибок, архивы рассы- лок и даже административные данные, поддерживается в публично доступной для чтения системе управления исходным кодом. Любой может просматривать исходный код и существующие сообщения об ошибках, отслеживать ход исправления ошибок и сообщения после них. Любой может присоединиться и принять участие в многочис- многочисленных списках рассылки FreeBSD. Есть три группы людей, непосредственно работающих над FreeBSD: разработчики, люди, имеющие право вносить изменения в основные исходные тексты (коммитеры, committers), и основная команда (core team). Имеется от 3000 до 4000 разработчиков, каждый из которых работает над неко- некоторой частью системы, такой, как сопровождение ядра FreeBSD, продолжающаяся разработка 1000 основных инструментов FreeBSD, написание документации FreeBSD и обновление другого программного обеспечения с открытым исходным кодом в кол- коллекции перенесенных программ FreeBSD. Разработчики имеют доступ к репозиторию исходного кода, но им не разрешено его изменять. Вместо этого они должны работать с утверждающим или передать сообщение о проблеме, чтобы их изменения были до- добавлены в систему. В настоящее время имеется от 300 до 400 коммитеров. Подобно разработчикам, большинство из них специализируются в той или иной части системы. В отличие от разработчиков, им разрешено делать изменения в тех частях репозитория исходного кода, в которой им было разрешено работать. Все нетривиальные изменения должны быть просмотрены еще одним или более коммитерами, прежде чем они будут отмече- отмечены в дереве исходного кода. Большинство коммитеров выполняют свою собственную работу, а также делают обзоры и утверждают работу нескольких разработчиков. Выдвижение для продвижения от разработчика в коммитеры осуществляется существующими коммитерами. Чаще всего разработчик выдвигается коммитером,
1.4. Модель разработки FreeBSD 31 с которым он работал. Выдвижение, наряду с описанием и оценкой последней проде- проделанной работы и предварительными рамками новой работы, отправляется основной команде для утверждения. В центре проекта находится основная команда. Основная команда составлена из девяти человек, которые выбираются каждые два года. Кандидаты для основной команды выбираются из коммитеров, и коммитеры выбирают основную команду. Основная команда действует в качестве последних сторожей исходного кода. Они отслеживают, что утверждается, и разрешают конфликты, если два или более комми- коммитеров не могут согласиться в том, как разрешить определенную проблему. Основная команда также санкционирует продвижение разработчиков в коммитеры и (в редких случаях) временно или постоянно удаляет кого-нибудь из группы утверждающих. Обычной причиной для такого удаления является бездеятельность (отсутствие измене- изменений в системе в течение более года). Структура разработки проекта FreeBSD непосредственно происходит из структуры, которую мы установили в CSRG. Как CSRG, так и FreeBSD используют центральный репозиторий управления исходным кодом. Основная команда FreeBSD является анало- аналогом штата сотрудников CSRG. Коммитеры FreeBSD во многом сходны с людьми, которым Беркли дал учетные записи на машине разработки CSRG, позволяющие им утверждать изменения в исходных кодах CSRG. А разработчики FreeBSD сходны с людьми, вносящими вклад в Беркли, но у них нет учетных записей в машине разра- разработки CSRG. Проект FreeBSD сделал несколько важных усовершенствований. Во-первых, они признали, что даже самый преданный программист в конечном счете «сгорит», потеряет интерес или иным способом решит уйти дальше. Для этих людей должен существовать какой-то способ элегантно уступить дорогу другим, вместо того чтобы из-за отсутст- отсутствия их внимания создавать пустоту в критической точке проекта. Поэтому в отличие от модели CSRG со штатом, который был бы диктатором для жизни, FreeBSD перешел на выбираемое ядро, которое отвечает перед утверждающими. Член основной команды, который «сгорел», может решить (или быть вынужденным) не избираться повторно, когда его или ее срок закончится. Члены основной команды, которые не служат интере- интересам коммитеров, не будут выбраны повторно. Также важно, что активные и энергичные люди имеют массу возможностей продвигаться по рангам. Поскольку основная коман- команда выбирается, люди восходят в этот ранг, так как их товарищи, активно работающие над проектом, чувствуют, что у них должна быть работа. Такой подход работает лучше, чем продвижение, поскольку вы являетесь хорошими приятелями с кем-то наверху Это также гарантирует, что основная команда составлена из тех, у кого хорошие навыки общения с другими, важное качество для такого положения. Другим значительным усовершенствованием, сделанным проектом FreeBSD, является автоматизация многих задач и установка удаленных зеркал репозитория исходного кода, веб-сайта и сообщений об ошибках. Эти изменения позволили проекту обеспечить поддержку значительно большего числа вносящих вклад, чем это было
32 Глава 1. История и цели возможно в модели CSRG. Проект FreeBSD ухитрился также стать менее центрирован- центрированным вокруг Соединенных Штатов путем приглашения разработчиков по всему миру, включая активных людей в Японии, Австралии, России, Южной Африке, Дании, Франции, Германии и Великобритании, называя лишь немногие из стран с активной разработкой FreeBSD. CSRG обычно выпускала новые версии системы примерно раз в два года. Изменения в этих дистрибутивах были редки, обычно это были лишь небольшие изменения, кри- критичные для безопасности или стабильности. Между версиями CSRG делала тестовые вы- выпуски, чтобы приобрести опыт с новыми возможностями, которые разрабатывались. Проект FreeBSD значительно расширил схему распространения CSRG. В любой момент времени имеется два дистрибутива FreeBSD. Первый является «стабильным» («stable») выпуском, который предназначен для использования в средах производства. Второй является «текущим» («current») выпуском, представляющим текущее состоя- состояние системы FreeBSD и предназначенным для использования разработчиками и поль- пользователями, которым нужны самые последние возможности. Стабильный выпуск изменяется медленно, и изменения ограничены исправления- исправлениями ошибок, улучшением производительности и добавлением увеличивающейся под- поддержки аппаратного обеспечения. Стабильная система выпускается от трех до четырех раз в год, хотя пользователи, желающие более частых обновлений, могут загружать и устанавливать последний стабильный код так часто, как хотят (например, после того как была сделана крупная заплатка для безопасности). Стабильная версия FreeBSD аналогична выпускам старших версий CSRG, за тем исключением, что они более активно обновляются и сделаны доступными пользователям. Подобно стабильным выпускам, каждые несколько месяцев создаются снимки текущего выпуска. Однако большинство пользователей текущего выпуска обновляют систему значительно чаще (обычны ежедневные обновления). Имея доступные по всему миру зеркальные копии стабильного и текущего дистрибутивов, проект FreeBSD позволяет базе пользователей по всему миру оставаться на уровне современных требований значительно проще, чем это было возможно с дистрибутивами CSRG. Примерно каждые два года текущая ветвь разветвляется для создания нового ста- стабильного выпуска. Когда новая стабильная ветвь доказывает свою достаточную надеж- надежность для использования в производстве, работа над старой стабильной ветвью почти полностью прекращается, и пользователи на производстве переключаются на новый стабильный выпуск. Основная разработка продолжается над текущей ветвью. Почти все изменения делаются сначала в текущей ветви. Только после того как изменение было протестировано в текущей ветви и доказало свою работоспособность в этом окружении, оно переносится с текущего в стабильный выпуск. Одним преимуществом, которое в течение длительного времени было у CSRG над проектом FreeBSD, являлось то, что CSRG был частью Калифорнийского университета в Беркли. Поскольку университет является некоммерческой организацией, вклад,
1.4. Модель разработки FreeBSD 33 сделанный в CSRG, был бы для делающего пожертвование подлежащим вычету из налогов. Некоторые люди в проекте FreeBSD в течение длительного времени чувство- чувствовали, что им следует найти способ дать спонсорам проекта возможность вычета из на- налогов. Несколько лет назад они основали Фонд FreeBSD, которому после трех лет хорошей некоммерческой работы налоговым управлением Соединенных Штатов был присужден статус 501(сK. Этот статус означает, что вклад, сделанный в Фонд FreeBSD, может быть вычтен из федеральных налогов и налогов штатов Соединенных Штатов таким же образом, как могут вычитаться вклады, сделанные в университет. Возможность получить освобождение от налогов заметно увеличила объем денежных вкладов в проект FreeBSD, что позволило ему финансировать разработку частей системы, которые утоми- утомительно создавать, но которые являются необходимыми и важными. В течение последних 10 лет проект FreeBSD рос устойчивыми, но приемлемыми шагами. Хотя Linux привлек массу последователей, FreeBSD продолжает удерживать свое место в пространстве высокопроизводительных серверов. Конечно, Linux помог проповедовать жизнеспособность открытых исходных кодов корпоративному рынку, и FreeBSD выехал на их связи. Гораздо проще убедить свое управление перейти с Linux на FreeBSD, чем убедить их перейти с Microsoft Windows на FreeBSD. Linux обеспечил также устойчивый поток разработчиков для FreeBSD. До недавнего време- времени у Linux не было центрального репозитория исходного кода, поэтому, чтобы внести свой вклад, вам приходилось работать на распространителей Linux или быть услышан- услышанным членом небольшой группы людей, которые могли вносить изменения в систему. Более равноправная и строящаяся на основе заслуг организация проекта FreeBSD обеспечила постоянный наплыв разработчиков высокой квалификации. Типичный новый коммитер в проекте FreeBSD имеет возраст от 25 до 30 лет и программировал для Linux или других проектов с открытым исходным кодом в течение десяти лет. У этих людей достаточно опыта и зрелости, чтобы быстро стать эффективными сотрудниками проекта. А наставничество, присущее продвижению от разработчика до утверждающего, гарантирует, что к тому времени, когда кто-то получит право непо- непосредственно утверждать код в дереве FreeBSD, он поймет правила стиля и ясности кода, которые критически важны для сохранения качества, устойчивости и удобства сопровождения FreeBSD. Задачей проекта FreeBSD является предоставление программного обеспечения, которое может использоваться для любой цели и без добавления строк. Многие разра- разработчики сделали значительные вложения в код (и проект) и безусловно не возражают против небольших финансовых вознаграждений время от времени, но они определен- определенно не настаивают на этом. Они полагают, что их первой и самой главной задачей явля- является предоставление кода любому и всем посетителям для любой цели таким образом, чтобы код нашел как можно более широкое применение и предоставил наибольшую возможную выгоду [Hubbard, 2004].
34 Глава 1. История и цели Ссылки AT&T, 1987. AT&T, The System V Interface Definition (SVID), Issue 2, American Telephone and Telegraph, Murray Hill, NJ, January 1987. Babaoglu & Joy, 1981. O. Babaoglu & W. N. Joy, "Converting a Swap-Based System to Do Paging in an Archi- Architecture Lacking Page-Referenced Bits", Proceedings of the Eighth Symposium on Operating Systems Principles, pp. 78-86, December 1981. Bach, 1986. M. J. Bach, The Design of the UNIX Operating System, Prentice-Hall, Englewood Cliffs, NJ, 1986. Comer, 2000. D. Comer, Internetworking with TCP/IP Volume I, 4th ed., Prentice-Hall, Upper Saddle River, NJ, 2000. Comer, 1984. D. Comer, Operating System Design: The Xinu Approach, Prentice-Hall, Englewood Cliffs, NJ, 1984. Compton, 1985. M. Compton, editor, "The Evolution of UNIX", UNIX Review, vol. 3, no. 1, January 1985. Debevoise, 1993. D. Debevoise, Civ. No. 92-1667, Unix System Laboratories Inc. vs. Berkeley Software Design Inc., http://sco.tuxrocks.com/Docs/USL/Doc-92.html, March 3, 1993. DiBonaetaL, 1999. C. DiBona, S. Ockman, & M. Stone, Open Sources: Voices from the Open Source Revolu- Revolution, pp. 31-46, Chapter 2—Twenty Years of Berkeley Unix: From AT&T-Owned to Freely Redistributable, http://www.oreilly.com/catalog/opensources/book/kirkmck.html, ISBN 1-56592-582-3, O'Reilly & Associates, Inc., Sebastopol, CA 95472, 1999. Ewensetal., 1985. P. Ewens, D. R. Blythe, M. Funkenhauser, & R. С Holt, "Tunis: A Distributed Multipro- Multiprocessor Operating System", USENIX Association Conference Proceedings, pp. 247-254, June 1985.
Ссылки 35 Holt, 1983. R. С. Holt, Concurrent Euclid, the UNIX System, and Tunis, Addison-Wesley, Reading, MA, 1983. Hubbard, 2004. J. Hubbard, "A Brief History of FreeBSD", FreeBSD Handbook, section 1.3.1, http:// www.n-eebsd.org/doc/en_US.ISO8859-l/books/handbook/history.html, March 2004. ISO, 1999. ISO, "ISO/IEC 9899 Programming Language С Standard", ISO 9899, можно заказать на сайте http://www.iso.org, December, 1999. Joy, 1980. W. N. Joy, "Comments on the Performance of UNIX on the VAX", Technical Report, University of California Computer System Research Group, Berkeley, CA, April 1980. Jung, 1985. R. S. Jung, "Porting the AT&T Demand Paged UNIX Implementation to Microcomputers", USENIXAssociation Conference Proceedings, pp. 361-370, June 1985. Kashtan, 1980. D. L. Kashtan, "UNIX and VMS: Some Performance Comparisons", Technical Report, SRI International, Menlo Park, CA, February 1980. Kernighan & Ritchie, 1978. B. W. Kernighan & D. M. Ritchie, The С Programming Language, Prentice-Hall, Engle- wood Cliffs, NJ, 1978. Kernighan & Ritchie, 1989. B. W. Kemighan & D. M. Ritchie, The С Programming Language, 2nd ed., Prentice- Hall, Englewood Cliffs, NJ, 1989. Linzner & MacDonald, 1993. J. Linzner & M. MacDonald, University of California at Berkeley versus Unix System Labo- Laboratories Inc., http://cm.bell-labsxom/cm/cs/who/dmr/bsdi/930610.ucb_complaint.txt, June 1993. McKusicketal., 1989. M. K. McKusick, M. Karels, & K. Bostic, "The Release Engineering of 4.3BSD", Pro- Proceedings of the New Orleans Usenix Workshop on Software Management, pp. 95-100, April 1989. Miller, 1978. R. Miller, "UNIX—A Portable Operating System", ACM Operating System Review, vol. 12, no. 3, pp. 32-37, July 1978.
36 Глава 1. История и цели Miller, 1984. R. Miller, "A Demand Paging Virtual Memory Manager for System V", USENIXAsso- USENIXAssociation Conference Proceedings, pp. 178—182, June 1984. Mohr, 1985. A. Mohr, "The Genesis Story", UNIX Review, vol. 3, no. 1, p. 18, January 1985. Organick, 1975. E. I. Organick, The Multics System: An Examination of Its Structure, MIT Press, Cam- Cambridge, MA, 1975. P1003.1, 1988. PI003.1, IEEE PI003.1 Portable Operating System Interface for Computer Environ- Environments (POSIX), Institute of Electrical and Electronic Engineers, Piscataway, NJ, 1988. Peirce, 1985. N. Peirce, ''Putting UNIX in Perspective: An Interview with Victor Vyssotsky", UNIX Review, vol. 3, no. 1, p. 58, January 1985. Presotto & Ritchie, 1985. D. L. Presotto & D. M. Ritchie, "Interprocess Communication in the Eighth Edition UNIX System", USENIX Association Conference Proceedings, pp. 309-316, June 1985. Richards & Whitby-Strevens, 1980. M. Richards & C. Whitby-Strevens, BCPL: The Language and Its Compiler, Cambridge University Press, Cambridge, U.K., 1980, 1982. Ritchie, 1978. D. M. Ritchie, "A Retrospective", Bell System Technical Journal, vol. 57, no. 6, pp. 1947-1969, July-August 1978. Ritchie, 1984a. D. M. Ritchie, "The Evolution of the UNIX Time-Sharing System", AT&T Bell Labora- Laboratories TechnicalJournal, vol. 63, no. 8, pp. 1577-1593, October 1984. Ritchie, 1987. D. M. Ritchie, "Unix: A Dialectic", USENIX Association Conference Proceedings, pp. 29-34, January 1987. Ritchie, 1984b. D. M. Ritchie, "Reflections on Software Research", Comm ACM, vol. 27, no. 8, pp. 758-760, 1984.
Ссылки 37 Ritchie, 2004. D. M. Ritchie, Documents on Unix System Laboratories Inc. versus Berkeley Software Design Inc., http://cm.bel 1-labs.com/cm/cs/who/dmr/bsdi/bsdisuit.html, March 2004. Ritchie et al., 1978. D. M. Ritchie, S. C. Johnson, M. E. Lesk, & B. W. Kernighan, "The С Programming Language", Bell System TechnicalJournal, vol. 57, no. 6, pp. 1991-2019, July-August 1978. Rosier, 1984. L. Rosier, "The Evolution of С—Past and Future", AT&T Bell Laboratories Technical Journal, vol. 63, no. 8, pp. 1685-1699, October 1984. Tanenbaum, 1987. A. S. Tanenbaum, Operating Systems: Design and Implementation, Prentice-Hall, Englewood Cliffs, NJ, 1987. Tuthill, 1985. B. Tuthill, "The Evolution of C: Heresy and Prophecy", UNIX Review, vol. 3, no. 1, p. 80, January 1985. Wilson, 1985. O. Wilson, "The Business Evolution of the UNIX System", UNIX Review, vol. 3, no. 1, p. 46, January 1985. X/OPEN, 1987. X/OPEN, The X/OPEN Portability Guide (XPG), Issue 2, Elsevier Science, Amsterdam, Netherlands, 1987.
Глава 2 Обзор дизайна FreeBSD 2.1. Средства FreeBSD и ядро Ядро FreeBSD предоставляет четыре главных средства: процессы, файловую систему, коммуникации и запуск системы. Данный раздел намечает в общих чертах, где в этой книге описывается каждая из четырех служб. 1. Процесс состоит из адресного пространства с одним или несколькими потоками управления, запущенным внутри него. Механизмы для создания, завершения и другого управления процессами описаны в главе 4. Система мультиплексирует отдельные виртуальные адресные пространства для каждого процесса; это управление памятью обсуждается в главе 5. 2. Интерфейсы пользователя для файловой системы и устройств сходны; общие аспекты обсуждаются в главе 6. Организация и управление устройствами в подсис- подсистеме ввода/вывода обсуждаются в главе 7. Файловая система предоставляет опера- операции для манипулирования набором именованных файлов, организованных в дре- древовидных структурированных иерархиях каталогов. Файловая система должна организовать хранилище этих файлов и каталогов на физическом носителе, таком, как диски. Роль файловой системы в осуществлении этих задач представлена в гла- главе 8. Доступ к файлам на удаленных машинах является предметом главы 9. Псев- Псевдотерминалы используются для доступа к системе; их работа является предметом главы 10. 3. Механизмы коммуникации, предоставляемые традиционными системами UNIX, включают однонаправленные надежные потоки байтов между связанными процес- процессами (см. «Каналы», раздел 11.1) и уведомления об исключительных событиях (см. «Сигналы», раздел 4.7). FreeBSD имеет также общие возможности взаимодей- взаимодействия между процессами (IPC). Эти средства, описанные в главе 11, используют
2.1. Средства FreeBSD и ядро 39 механизмы доступа, отличающиеся от механизмов файловой системы, но после установления соединения процесс может получить доступ к ним, как если бы они были каналами. Имеется общая сетевая структура, обсуждаемая в главе 12, которая обычно используется в качестве уровня, лежащего в основе средств IPC. В главе 13 подробно описывается конкретная реализация сетевых средств. 4. У каждой реальной операционной системы есть рабочие вопросы, такие, как начало ее работы. Запуск и рабочие вопросы обсуждаются в главе 14. В разделах с 2.3 по 2.14 представлены вводные материалы, относящиеся к главам с 3-й по 14-ю. Мы определяем термины, рассматриваем основные системные вызовы и исследуем историческое развитие. Наконец, мы объясняем причины принятия многих проектных решений. Ядро Ядро является частью системы, работающей в защищенном режиме и обеспечи- обеспечивающей доступ всех пользовательских программ к нижележащему оборудованию (например, центральному процессору, клавиатуре, монитору, дискам, сетевым соеди- соединениям) и программным конструкциям (например, файловой системе, сетевым прото- протоколам). Ядро предоставляет основные системные средства; оно создает и управляет процессами и предоставляет функции для доступа к файловой системе и средствам коммуникации. Эти функции, называемые системными вызовами, выглядят для поль- пользовательских процессов как библиотечные подпрограммы. Эти системные вызовы являются лишь интерфейсами, которые есть у процессов для этих средств. Подробности механизма системных вызовов приведены в главе 3, так же как описания нескольких механизмов ядра, которые не выполняются в качестве непосредственного результата осуществления процессом системного вызова. Ядро, в традиционной терминологии операционных систем, является небольшой центральной частью программного обеспечения, предоставляющей лишь минималь- минимальные средства, необходимые для реализации дополнительных служб операционной системы. На протяжении большей части 1980-х гг. исследовательские операционные системы, такие, как Chorus [Rozier et al., 1988], Mach [Accetta et al., 1986], Tunis [Ewens et al., 1985] и V Kernel [Cheriton, 1988], пытались разделить функциональность на более чем одну логическую единицу. Службы, такие, как файловые системы и сетевые протоколы, были реализованы в виде клиентских прикладных процессов центральной части или ядра. Эти микроядра в значительной степени потерпели неудачу из-за боль- больших накладных расходов по переключениям между процессами ядра. Ядро FreeBSD не разделено на несколько процессов. Это базовое проектное реше- решение было сделано в самых ранних версиях UNIX. Две первые реализации Кена Томп- Томпсона не имели отображения памяти и поэтому не делали аппаратно осуществляемого разделения между пространствами пользователя и ядра [Ritchie, 1988]. Так же легко, как действительно реализованная модель процессов ядра и пользователей, могла бы
40 Глава 2. Обзор дизайна FreeBSD быть реализована система, передающая сообщения. Монолитное ядро было выбрано для обеспечения простоты и производительности. И первые ядра были небольшими; добавление таких возможностей, как работа в сети, увеличило их размер, хотя ядро попрежнему небольшое по сравнению со многими приложениями, которые работают с ним. Пользователи обычно взаимодействуют с системой посредством интерпретатора командного языка, называемого оболочкой, и посредством дополнительных программ пользователя. Такие программы и оболочка реализованы в виде процессов, а не части ядра. Детали таких программ выходят за рамки данной книги, в которой фокус сделан вместо этого почти исключительно на ядре. В разделах 2.3 и 2.4 описываются службы, предоставляемые ядром FreeBSD, и дается обзор дизайна последних. В дальнейших главах подробно описывается дизайн и реализация этих служб, как они осуществлены в FreeBSD. 2.2. Организация ядра В данном разделе мы оценим организацию ядра FreeBSD двояко. 1. Как статическую основную часть программного обеспечения, категоризируемую по выполняемым функциям, предлагаемым составляющими ядро модулями. 2. По его динамической работе, категоризируемой в соответствии со службами, пре- предоставляемыми пользователям. Самая большая часть ядра реализует системные службы, к которым приложения получают доступ через системные вызовы. В FreeBSD это программное обеспечение организовано следующим образом. * Базовые средства ядра: таймер и обработка системного времени, управление дескрип- дескрипторами и процессами. * Поддержка управления памятью: замещение страниц и подкачка. Общие системные интерфейсы: операции ввода/вывода, управления и мульти- мультиплексирования, осуществляемые с дескрипторами. Файловая система: файлы, каталоги, преобразование имен путей, блокировки файлов и управление буферированием ввода/вывода. * Поддержка управления терминалами: интерфейс псевдотерминалов и дисциплины линий связей терминалов. Средства межпроцессного взаимодействия: сокеты. * Поддержка сетевой коммуникации: коммуникационные протоколы и общие сетевые средства, такие, как маршрутизация.
2.2. Организация ядра 41 Большая часть программного обеспечения в данных категориях является машинно- независимой и переносимой между различными аппаратными архитектурами. Машинно-зависимые аспекты ядра изолированы от основного кода. В частности, машинно-независимый код не содержит условного кода для определенных архитектур. Когда необходимо зависимое от архитектуры действие, машинно-независимый код вызывает архитектурно-зависимую функцию, которая размещается в машинно-зависи- машинно-зависимом коде. Машинно-зависимое программное обеспечение включает следующее. * Низкоуровневые действия по запуску системы. * Обработка исключений и отказов. * Низкоуровневая работа с контекстом времени выполнения процесса. * Конфигурирование и инициализация аппаратных устройств. Поддержка устройств ввода/вывода времени выполнения. Табл. 2.1. Машинно-независимое Категория Заголовки Инициализация Средства ядра Общие интерфейсы Межпроцессное взаимодействие Обработка терминалов Виртуальная память Управление vnode Локальная файловая система Различные файловые системы A9) Сетевая файловая система Сетевая коммуникация Протоколы интернета V4 Протоколы интернета V6 Ipsec Netgraph Поддержка криптографии Уровень GEOM Уровень САМ Уровень АТА программное обеспечение в ядре FreeBSD Строк кода 38 158 1663 53 805 22 191 10019 5798 24 714 22 764 28 067 58 753 22 436 46 570 41 220 45 527 17 956 74 338 7515 11 563 41 805 14 192 Процентное соотношение в ядре 4,8% 6,7% 6,7% 2,8% 1,3% 0,7% 3,1% 2,9% 3,5% 7,4% 2,8% 5,8% 5,2% 5,7% 2,2% 9,3% 0,9% 1,4% 5,2% 1,8%
42 Глава 2. Обзор дизайна FreeBSD Табл. 2.1. Машинно-независимое программное обеспечение в ядре FreeBSD (Окончание) Категория Строк кода Процентное соотношение в ядре Шина ISA 10 984 1,4% Шина PCI 72 366 9,1% Шинарссагс! 6916 0,9% Совместимость с Linux 10 474 1,3% Всего машинно-независимого 689 794 86,4% Обозначения: GEOM - геометрия; САМ - метод общего доступа (Common Access Method); ATA - интерфейс жестких дисков (Advanced Technology Attachment); ISA - промышленная стандартная архитек- архитектура (Industry Standard Architecture); PCI - соединение периферийных компонентов (Peripheral Component Interconnect). Табл. 2.2. Машинно-зависимое программное обеспечение для PC в ядре FreeBSD Категория Машинно-зависимых заголовков Шина ISA Шина PCI Виртуальная память Остальной машинно-зависимый процедур на ассемблере Совместимость с Linux Всего машинно-зависимого Обозначения: ISA - Industry Standard Architecture; PCI - Peripheral Component Interconnect. В табл. 2.1 приведена сводка машинно-независимого программного обеспечения, составляющего ядро FreeBSD для PC. Числа в столбце 2 показывают число строк исходного кода С, заголовочных файлов и ассемблера. Практически все программное обеспечение ядра написано на языке программирования С; лишь 0,6 процента написа- написано на языке ассемблера. Как показывает статистика в табл. 2.2, машинно-зависимое программное обеспечение, за исключением поддержки устройств, составляет лишь 6,9 процента ядра. Не показаны 846 525 строк кода для сотен поддерживаемых устройств, лишь некоторые из них загружаются в любое конкретное ядро. Лишь небольшая часть ядра отвечает за загрузку системы. Этот код используется во время начальной загрузки и отвечает за установку аппаратного и программного окруже- окружения (см. главу 14). Некоторые операционные системы (особенно с ограниченной физиче- физической памятью) сбрасывают или перекрывают (overlay) программное обеспечение, осу- осуществляющее эти функции, после их отработки. Ядро FreeBSD не восстанавливает Строк кода 16 115 50 882 2266 3118 26 708 4400 4857 108 346 Процентное соотношение в ядре 2,0% 6,4% 0,3% 0,4% 3,3% 0,6% 0,6% 13,6%
2.3. Службы ядра 43 память, используемую кодом инициализации, поскольку это пространство памяти зани- занимает лишь 0,2 процента ресурсов ядра, используемых на обычной машине. Код инициа- инициализации не находится также в каком-то определенном месте ядра - он разбросан везде и появляется обычно в местах, логически связанных с тем, что инициализируется. 2.3. Службы ядра Граница между кодом уровня ядра и пользователя осуществляется аппаратными сред- средствами защиты, предоставляемыми нижележащим оборудованием. Ядро работает в отдельном адресном пространстве, доступ к которому невозможен из процессов пользователя. Привилегированные операции, такие, как запуск ввода/вывода и оста- остановка центрального процессора, доступны лишь ядру. Приложения запрашивают обслуживание ядром с помощью системных вызовов. Системные вызовы используют- используются, чтобы заставить ядро выполнить сложные операции, такие, как запись данных во вторичное хранилище, и простые операции, такие, как возвращение текущего вре- времени дня. Все системные вызовы представлены приложениям как синхронные: прило- приложение не будет продолжать работу до тех пор, пока ядро не выполнит действия, свя- связанные с системным вызовом. Ядро может завершить некоторые операции, связанные с системным вызовом, после своего возвращения. Например, системный вызов write копирует данные, которые должны быть записаны из процесса пользователя в буфер ядра, во время ожидания процесса, но обычно возвращается из системного вызова до того, как буфер ядра будет записан на диск. Системный вызов обычно реализован в виде аппаратного исключения, которое изме- изменяет режим работы центрального процессора и текущее отображение виртуальной памяти. Параметры, предоставленные пользователями в системных вызовах, проверяются ядром до начала использования. Такая проверка гарантирует целостность системы. Все пара- параметры, переданные ядру, копируются в адресное пространство ядра, чтобы гарантировать неизменность проверенных параметров в результате побочного эффекта системного вызо- вызова. Результаты системного вызова возвращаются ядром либо в аппаратных регистрах, либо путем копирования их значений по указанным пользователем адресам памяти. Как и пара- параметры, переданные в ядро, адреса, используемые для возвращения результатов, должны быть проверены на предмет принадлежности адресному пространству пользователя. Если ядро при обработке системного вызова сталкивается с ошибкой, оно возвращает пользова- пользователю код ошибки. В языке программирования С этот код ошибки хранится в глобальной переменной еггпо, а функция, выполнявшая системный вызов, возвращает-1. Пользовательские приложения и ядро действуют независимо друг от друга. FreeBSD не хранит управляющие блоки ввода/вывода или другие связанные с операционной системой структуры данных в адресном пространстве приложения. Каждому приложе-
44 Глава 2. Обзор дизайна FreeBSD нию уровня пользователя предоставляется отдельное адресное пространство, в котором оно выполняется. Ядро делает изменения состояния, такие, как приостановка процесса во время работы другого процесса, незаметными для вовлеченных процессов. 2.4. Управление процессами FreeBSD поддерживает многозадачное окружение. Каждая задача или поток исполне- исполнения называется процессом. Контекст процесса FreeBSD состоит из состояния пользо- пользователя, которое включает содержимое адресного пространства и окружения времени выполнения, и состояния ядра, включающего параметры планировки процесса, управ- управление ресурсами и идентификационную информацию. Контекст включает все исполь- используемое ядром для обслуживания процесса. Пользователи могут создавать процессы, управлять ходом их выполнения и получать уведомления, когда состояние выполнения процесса изменяется. Каждому процессу присваивается уникальное значение, назы- называемое идентификатором процесса (process identifier - PID). Это значение использу- используется ядром для идентификации процесса при сообщении пользователю об изменениях состояния и пользователем при указании процесса в системном вызове. Ядро создает процесс, дублируя контекст другого процесса. Новый процесс назы- называется порожденным процессом (child process) первоначального родительского про- процесса (parent process). Контекст, дублированный в ходе создания процесса, включает как состояние пользователя процесса, так и системное состояние процесса, управляе- управляемое ядром. Важные составляющие состояния ядра описаны в главе 4. Жизненный цикл процесса изображен на рис. 2.1. Процесс может создать новый процесс, являющийся копией оригинального, используя системный вызов fork. Вызов fork возвращается дважды: один раз в родительском процессе, где возвращаемым значением является идентификатор порожденного процесса, и один раз в порожденном процессе, где возвращаемое значение равно 0. Отношение «порожденный - родитель» создает в системе иерархическую структуру набора процессов. Новый процесс разде- разделяет со своим родителем все ресурсы родителя, такие, как дескрипторы файлов, состояние обработки сигналов и распределение памяти. Рис. 2.1. Системные вызовы управления процессами
2.4. Управление процессами 45 Хотя есть случаи, когда новый процесс должен быть копией родительского, более полезным и обычным действием является загрузка и выполнение другой программы. Процесс может перекрыть себя образом памяти другой программы, передав вновь соз- созданному образу набор параметров с использованием системного вызова execve. Одним параметром является имя файла, содержимое которого согласуется с форматом, распо- распознаваемым системой, будучи либо двоичным исполняемым файлом, либо файлом, вызывающим запуск определенной программы интерпретатора для анализа своего содержимого. Процесс может завершиться, выполнив системный вызов exit, посылая 8 бит стату- статуса завершения своему родителю. Если процесс хочет передать своему родителю более одного байта информации, он должен либо установить межпроцессное взаимодейст- взаимодействие, использовав каналы или сокеты, либо использовать вспомогательный файл. Межпроцессное взаимодействие широко обсуждается в главе 11. Процесс может приостановить выполнение до тех пор, пока любой из его порожден- порожденных процессов не завершится, использовав системный вызов wait, который возвращает PID и статус завершения порожденного процесса. Родительский процесс может органи- организовать свое уведомление сигналом, когда порожденный процесс завершается ненормально. Использовав системный вызов wait4, родитель может получить информацию о событии, вызвавшем завершение порожденного процесса, и о ресурсах, использованных процес- процессом в течение времени его жизни. Если процесс оказывается покинутым, поскольку его родители завершились раньше него, ядро организует посылку статуса завершения поро- порожденного процесса назад специальному системному процессу (init: см. разделы 3.1 и 14.5). Подробности того, как ядро создает и уничтожает процессы, приведены в главе 5. Выполнение процессов планируется в соответствии с параметром приоритета процесса. При работе с планировщиком по умолчанию этот приоритет управляется с помощью алгоритма планировки ядра. Пользователи могут повлиять на планировку процесса, указав параметр относительного приоритета (nice), который изменяет вес общих коэффициентов планирования, но по-прежнему принуждает разделять время используемого процессора в соответствии с политикой планировки ядра. FreeBSD имеет также планировщик реального времени. Процессы, работающие под планиров- планировщиком реального времени, сами управляют своим приоритетом, который не изменяет- изменяется ядром. Ядро будет запускать на исполнение процесс реального времени с наивыс- наивысшим среди всех других процессов приоритетом. Таким образом, процессы реального времени не обязаны разделять ресурсы использующегося процессора. Сигналы Система определяет набор сигналов, которые могут быть доставлены процессу. Сигналы в FreeBSD сделаны по образцу аппаратных прерываний. Процесс может указать подпро- подпрограмму уровня пользователя в качестве обработчика, которому должен быть доставлен сигнал. Когда генерируется сигнал, его дальнейшее распространение блокируется до тех
46 Глава 2. Обзор дизайна FreeBSD пор, пока он не будет перехвачен обработчиком. Перехват сигнала включает сохранение контекста текущего процесса и построение нового контекста, в котором будет запущен обработчик. Затем сигнал доставляется обработчику, который может либо аварийно завершить процесс, либо вернуться в выполняющийся процесс (возможно, после уста- установки значения глобальной переменной). Если обработчик возвращается, сигнал разбло- разблокируется и может быть сгенерирован (и перехвачен) снова. В качестве альтернативы процесс может указать, что сигнал должен быть проиг- проигнорирован или что должно быть применено определенное ядром действие по умолчанию. Действием по умолчанию для определенных сигналов является завершение процесса. Это завершение может сопровождаться созданием файла дампа ядра, который содержит образ текущей памяти процесса для использования в отладке после заверше- завершения программы. Некоторые сигналы не могут быть перехвачены или проигнорированы. К этим сиг- сигналам относятся SIGKILL, который завершает вышедшие из-под контроля процессы, и сигнал управления заданиями SIGSTOP. Процесс может выбрать, чтобы сигналы доставлялись в специальный стек таким образом, чтобы были возможны сложные манипуляции стеком программы. Например, язык, поддерживающий сопрограммы, должен предоставить стек для каждой сопро- сопрограммы. Система времени выполнения языка может выделять эти стеки, разделяя один стек, предоставленный FreeBSD. Если ядро не поддерживает отдельный стек сигналов, пространство, выделенное для каждой сопрограммы, должно быть увеличено на раз- размер, необходимый для перехвата сигнала. У всех сигналов один и тот же приоритет. Если ожидают несколько сигналов одновременно, порядок, в котором сигналы доставляются процессу, зависит от реали- реализации. Обработчики сигналов работают с сигналом, который явился причиной бло- блокировки их вызова, но могут иметь место еще и другие сигналы. Предусмотрены такие механизмы, чтобы процессы могли защитить критические разделы кода от появления определенных сигналов. Дизайн и реализация сигналов описаны в разделе 4.7. Группы процессов и сеансы Процессы организованы в группы процессов. Группы процессов используются для управления доступом к терминалам и предоставления средств распределения сигналов по совокупностям связанных процессов. Процесс наследует свою группу процесса от своего родительского процесса. Ядром предоставляются механизмы для изменения процессами своих групп процессов или групп процессов своих потомков. Создание новой группы процессов просто; значением новой группы процессов обычно является идентификатор создающего процесса. Группу процессов иногда называют заданием (job) и управляют с помощью высо- высокоуровневого системного программного обеспечения, такого, как оболочка. Обычной
2.5. Управление памятью 47 разновидностью задания, созданного оболочкой, является конвейер (pipeline) из нескольких процессов, соединенных каналами таким образом, что выход первого процесса является входом второго, выход второго является входом третьего и т.д. Оболочка создает такое задание, создавая с помощью fork процесс для каждой ступени конвейера, помещая затем все эти процессы в отдельную группу процессов. Пользовательский процесс может послать сигнал каждому процессу в группе так же, как отдельному процессу. Процесс в определенной группе может получить программные прерывания, влияющие на группу, заставляющие ее приостанавливать или возобновлять исполнение или прерываться или завершаться. У терминала (или чаще программной эмуляции терминала, называемой псевдо- псевдотерминалом) есть присвоенный ему идентификатор группы процесса. Обычно в качестве этого идентификатора берется идентификатор группы процессов, связанной с термина- терминалом. Управляющая заданиями оболочка может создать несколько групп процессов, свя- связанных с одним и тем же терминалом; терминал является управляющим терминалом для каждого процесса в этих группах. Процесс может читать из дескриптора своего управ- управляющего терминала, только если идентификатор группы процессов терминала совпадает с идентификатором группы этого процесса. Если идентификаторы не совпадают, процесс будет заблокирован, если он попытается прочесть с терминала. Изменяя идентификатор группы процессов терминала, оболочка может разделять (arbitrate) терминал между несколькими различными заданиями. Это разделение называется управлением заданиями (job confrol) и описано вместе с группами процессов в разделе 4.8. Так же как набор связанных процессов может быть собран в группу процессов, набор групп процессов может быть собран в сеанс (session). Главным применением для сеансов является создание изолированной среды для процессов демонов и их потомков и связы- связывание воедино оболочки регистрации пользователя и заданий, которые эта оболочка поро- порождает. 2.5. Управление памятью У каждого процесса есть свое собственное отдельное адресное пространство. Адрес- Адресное пространство изначально разделено на три логических сегмента: кода или текста (text), данных и стека. Сегмент text является сегментом только для чтения и содержит машинные инструкции программы. Сегменты данных и стека можно и читать, и запи- записывать. Сегмент данных содержит разделы инициализированных и неинициализиро- неинициализированных данных программы, тогда как сегмент стека содержит стек приложения време- времени выполнения. Сегмент стека автоматически расширяется ядром по мере выполнения процесса. Процесс может расширять или сокращать свой сегмент данных с помощью системных вызовов, тогда как размер своего сегмента текста он может изменять, лишь когда содержимое сегмента перекрывается данными из файловой системы или при
48 Глава 2. Обзор дизайна FreeBSD отладке. Первоначальное содержание сегментов порожденных процессов является дубликатами сегментов родительского процесса. Для выполнения процесса все содержимое адресного пространства процесса не должно обязательно постоянно находиться в оперативной памяти. Если процесс ссылается на часть своего адресного пространства, которое отсутствует в основной памяти, система подкачивает (pages) необходимую информацию в память. Когда сис- системных ресурсов не хватает, система использует двухуровневый подход к поддержа- поддержанию доступности ресурсов. Если доступно ограниченное количество памяти, система заберет ресурсы памяти от процессов, если эти ресурсы в последнее время не исполь- использовались. Если будет сильный недостаток ресурсов, система прибегнет к подкачке (swapping) всего контекста процесса из вторичного хранилища. Подкачка по требова- требованию страниц и процессов, осуществляемая системой, прозрачна для процессов. Однако процесс может уведомить систему о будущем ожидаемом использовании памяти в качестве поддержки производительности. Проектные решения по управлению памятью BSD Требованием для 4.2ВSD была поддержка больших разрозненных адресных про- пространств, отображенных файлов и разделяемой памяти. Был определен интерфейс, на- названный ттар(), который позволял несвязанным процессам запрашивать разделяемое отображение файла в свои адресные пространства. Если несколько процессов отобра- отображали один и тот же файл в свои адресные пространства, изменения адресного про- пространства, относящиеся к отображенному файлу для одного процесса, были бы отражены в области, отображенной другими процессами, а также в самом файле. В конечном счете 4.2ВSD был выпущен без интерфейса mmapQ из-за необходимости сделать доступ- доступными другие особенности, такие, как работа в сети. Дальнейшая разработка интерфейса ттар() продолжилась во время работы над 4.3BSD. Свыше 40 компаний и исследовательских групп приняли участие в обсужде- обсуждениях, ведущих к пересмотру архитектуры, которая была описана в Руководстве архи- архитектуры программного обеспечения Беркли [McKusick et al., 1994]. Первая UNIX-реали- UNIX-реализация интерфейса была выполнена фирмой Sun Microsystems [Gingell et al., 1987]. Опять-таки спешка не позволила 4.3BSD предоставить реализацию интерфейса. Хотя последний мог быть встроен в существующую систему виртуальной памяти 4.3BSD, разработчики решили не включать ее, поскольку этой реализации было около 10 лет. Более того, первоначальный дизайн виртуальной памяти был основан на пред- предположении, что память компьютера небольшая и дорогая, тогда как диски были под- подключены локально, быстры и недороги. Таким образом, система виртуальной памяти была спроектирована для экономного использования памяти за счет создания лишнего дискового трафика. Вдобавок реализация 4.3BSD была пронизана зависимостями от аппаратного обеспечения управления памятью VAX, что затрудняло ее переносимость
2.5. Управление памятью 49 на другие архитектуры компьютеров. Наконец, система виртуальной памяти не была предназначена для поддержки тесно связанных микропроцессоров, что становилось все более обычным и важным в наши дни. Попытки постепенного усовершенствования старой реализации казались обречен- обреченными на неудачу. С другой стороны, полностью новый дизайн мог использовать пре- преимущества большой памяти, сберечь обмен с диском и получить возможность работы в многопроцессорной системе. В результате в 4.4BSD система виртуальной памяти была полностью заменена. Система виртуальной памяти 4.4BSD была основана на системе виртуальной памяти Mach 2.0 [Tevanian, 1987] с обновлениями от Mach 2.5 и Mach 3.0. Система виртуальной памяти FreeBSD представляет сильно настроенную версию реализации виртуальной памяти 4.4BSD. Она содержит в себе эффективную поддержку для разделения памяти, четкое отделение машинно-независимых и машинно-зависи- машинно-зависимых средств, а также поддержку многопроцессорности. Процессы могут отображать файлы в любом месте своего адресного пространства. Они могут делать общими части своего адресного пространства, создавая разделяемые отображения одного и того же файла. Изменения, сделанные одним процессом, видны в адресном пространстве других процессов, а также записываются обратно в сам файл. Процессы могут также запрашивать отдельные отображения файла, что предотвращает просмотр любых сде- сделанных ими изменений со стороны других процессов, отображающих файл, или обрат- обратную запись в сам файл. Другим вопросом в системе виртуальной памяти является способ передачи ин- информации в ядро при выполнении системных вызовов read или write. Для этих систем- системных вызовов FreeBSD всегда копирует данные из адресного пространства процесса в буфер в ядре. Копия делается по ряду причин. * Часто данные пользователя не выровнены по границе страницы и не являются кратным аппаратному размеру страницы. * Если у процесса будет отобрана страница, на нее больше нельзя будет ссылаться. Некоторые программы зависят от того, что данные остаются в буфере даже после того, как эти данные будут записаны. * Если процессу будет разрешено сохранить копию страницы (как это происходит в текущей семантике FreeBSD), страница должна быть помечена для копирования при записи. Страница для копирования при записи защищена от записи с помо- помощью атрибута только для чтения. Если процесс попытается изменить страницу, ядро получит отказ записи. После этого ядро делает копию страницы, которую процесс может изменить. К сожалению, типичный процесс сразу же попытается записать новые данные в свой выходной буфер, так или иначе форсируя копирова- копирование данных.
50 Глава 2. Обзор дизайна FreeBSD Когда страницы повторно отображаются на новые адреса виртуальной памяти, большая часть аппаратного обеспечения управления памятью требует, чтобы кеш преобразования адресов был выборочно очищен. Очистки кеша часто являются медленными. Общим результатом будет то, что для блоков данных менее 4-8 Кб перераспределение памяти медленнее, чем копирование. Для операций чтения или записи, передающих большие объемы данных, копирова- копирование может требовать большого времени. Альтернативой копированию является пере- переотображение памяти процесса в ядро. Самыми большими стимулами для отображения памяти являются потребности в доступе к большим файлам и в передаче больших объ- объемов данных между процессами. Интерфейс mmapQ предоставляет способ для выпол- выполнения обеих задач без копирования. Системный вызов ттар не поддерживается для процессов, работающих на различных машинах. Такие процессы должны взаимодействовать, используя сокеты, соединенные через сеть. Соответственно пересылка содержимого файла по сети является другой обычной операцией, для которой желательно избегать копирования. Исторически пересылка файла осуществлялась путем чтения файла в буфер приложения, а затем записи этого буфера в сокет. Этот подход требовал двух операций копирования дан- данных: сначала из ядра в буфер приложения, затем из буфера приложения обратно в ядро, чтобы отправить их в сокет. В FreeBSD есть системный вызов sendfile, который посылает данные из файла в сокет, не используя никакого копирования. Управление памятью внутри ядра Ядро часто выделяет память, которая нужна лишь на протяжении одного системного вызова. В процессе пользователя такая кратковременная память была бы выделена в стеке времени выполнения. Поскольку у ядра ограниченный стек времени выполне- выполнения, нереально выделять в нем блоки памяти даже умеренного размера. Соответствен- Соответственно такая память должна выделяться посредством более динамического механизма. Например, когда система должна преобразовать имя пути, она должна выделить для хранения имени буфер в 1 Кб. Другие блоки памяти должны быть более постоянными, чем один системный вызов, и таким образом, не могут выделяться в стеке, даже если бы там было место. Примером являются управляющие блоки протокола, которые сохра- сохраняются на протяжении сетевого соединения. Требования к динамическому выделению памяти в ядре возросли по мере добавле- добавления новых служб. Обобщенный распределитель памяти снижает сложность написания кода внутри ядра. Таким образом, в ядре FreeBSD есть общий распределитель памяти, который может использоваться любой частью системы. У него есть интерфейс, сход- сходный с библиотечными процедурами С mallocQ nfreeQ, которые обеспечивают выделе- выделение памяти прикладным программам [McKusick & Karels, 1988]. Подобно интерфейсу библиотеки С, процедура выделения принимает параметр, указывающий необходи- необходимый размер памяти. Диапазон размеров запрашиваемой памяти не ограничен; однако
2.6. Система ввода/вывода 51 физическая память выделяется и не подкачивается. Процедура освобождения прини- принимает указатель на освобождаемое хранилище, но не требует размера освобождаемого участка памяти. Некоторые большие постоянные выделения, такие как, структуры, содержащие сведения о процессе в течении времене его жизни, не очень хорошо обрабатываются общим распределителем памяти. Для таких типов выделений памяти ядро предостав- предоставляет распределитель областей. Каждому типу памяти дается своя собственная область, из которой осуществляются все выделения. Память, выделенная в одной области, не может использоваться в другой области или общим распределителем. Семантика интерфейса сходна с распределителем общей памяти; память выделяется из области с помощью процедуры zallocQ, а освобождается с помощью процедуры zfreeQ. 2.6. Система ввода/вывода Базовой моделью системы ввода/вывода UNIX является последовательность байтов, к которой можно получить произвольный или последовательный доступ. В обычном процессе пользователя UNIX нет методов доступа и управляющих блоков. Различные программы ожидают различные уровни структуры, но ядро не налагает структуру на ввод/вывод. Например, соглашением для текстовых файлов являются строки символов ASCII, разделяемые одним символом новой строки (ASCII-символ перевода строки), но ядро ничего не знает об этом соглашении. Для задач большинства программ модель упрощается еще больше до потока байтов данных, или потока ввода/ вывода. Именно эта простая общая форма данных заставляет работать характерный базирующийся на инструментах подход UNIX [Kernighan & Pike, 1984]. Поток ввода/ вывода от одной программы может быть подан в качестве ввода почти любой другой программе. Дескрипторы и ввод/вывод Для ссылки на потоки ввода/вывода процессы UNIX используют дескрипторы. Дескрипторы являются небольшими беззнаковыми целыми, получаемыми от систем- системных вызовов open и socket. Системный вызов open принимает в качестве аргументов имя файла и режим доступа для указания, должен ли файл быть открыт для чтения или для записи, или для обоих. Этот системный вызов может также использоваться для создания нового, пустого файла. Для передачи данных к дескриптору могут приме- применяться системные вызовы read и write. Системный вызов close может использоваться для освобождения дескриптора. Дескрипторы представляют лежащие в их основе объекты, поддерживаемые ядром, они создаются при помощи специфичных для типа объекта системных вызовов. В FreeBSD дескрипторы могут представлять четыре разновидности объектов: файлы, каналы, очереди fifo и сокеты.
52 Глава 2. Обзор дизайна FreeBSD Файл является линейным массивом байтов с по крайней мере одним именем. Файл существует до тех пор, пока все его имена не будут явным образом удалены и ни у одного процесса не будет его сохраненного дескриптора. Процесс запрашивает дескриптор для файла, открывая файл по его имени с помощью системного вызова open. Доступ к устройствам ввода/вывода осуществляется, как к файлам. Канал (pipe) является линейным массивом байтов, как и файл, но он используется исключительно как поток ввода/вывода и является однонаправленным. У него также нет имени, поэтому он не может быть открыт с помощью open. Вместо этого он создается с помощью системного вызова pipe, который возвращает два дескриптора, один из которых принимает ввод, который надежно передается другому дескриптору без повторений и в том же порядке. Очередь fifo часто называют именованным каналом. Свойства очереди fifo иден- идентичны свойствам канала, за исключением того, что она появляется в файловой системе; таким образом, ее можно открыть с использованием системного вызова open. Два процесса, желающих взаимодействовать друг с другом, каждый откры- открывает по очереди fifo: один открывает для чтения, другой для записи. Сокет является временным объектом, который используется для межпроцессного взаимодействия; он существует лишь до тех пор, пока какой-нибудь процесс хранит ссылающийся на него дескриптор. Сокет создается с помощью системного вызова socket, который возвращает дескриптор для него. Имеются различные виды сокетов, поддерживающих разную семантику коммуникации, такую, как надежная доставка данных, сохранение порядка сообщений и сохранение рамок сообщения. В системах до 4.2BSD каналы были реализованы с использованием файловой сис- системы; когда в 4.2BSD были введены сокеты, каналы были реализованы заново в виде сокетов. В целях повышения производительности FreeBSD больше не использует сокеты для реализации каналов и очередей fifo. Вместо этого используется отдельная реализация, оптимизированная для локальных взаимодействий. Ядро содержит для каждого процесса таблицу дескрипторов, которая используется для преобразования внешнего представления дескриптора во внутреннее. (Дескриптор является просто индексом для этой таблицы.) Таблица дескрипторов процесса насле- наследуется от родителя этого процесса, а вместе с ней наследуется и доступ к объектам, на которые ссылаются дескрипторы. Главными способами получения дескриптора процессом являются следующие. 1. Открывание или создание объекта. 2. Наследование от родительского процесса. К тому же IPC с использованием сокетов допускает передачу дескрипторов в сооб- сообщениях между несвязанными процессами на одной и той же машине.
2.6. Система ввода/вывода 53 У каждого действительного дескриптора есть связанное с ним смещение файла в байтах от начала объекта. Операции чтения и записи начинаются с этого смещения, которое обновляется после каждой передачи данных. Для объектов, допускающих про- произвольный доступ, смещение файла может также устанавливаться с помощью систем- системного вызова Iseek. Обычно файлы допускают произвольный доступ, так же как и неко- некоторые устройства. Каналы, очереди fifo и сокеты не допускают произвольного доступа. Когда процесс завершается, ядро восстанавливает все дескрипторы, которые использовались этим процессом. Если процесс удерживал последнюю ссылку на объ- объект, уведомляется менеджер объекта таким образом, чтобы он смог выполнить любые необходимые действия по очистке, такие, как окончательное удаление файла или осво- освобождение сокета. Управление дескрипторами Большинство процессов ожидают, что при их запуске будут уже открыты три дескрип- дескриптора. Этими дескрипторами являются 0, 1 и 2, более известные как стандартный ввод, стандартный вывод и стандартная ошибка соответственно. Обычно все три связы- связываются с терминалом пользователя в процессе регистрации (см. раздел 14.5) и насле- наследуются запущенными пользователем процессами через fork и exec. Таким образом, программа может читать то, что набирает пользователь, читая стандартный ввод, и может посылать вывод на экран пользователю, записывая в стандартный вывод. Дескриптор стандартной ошибки также открыт для записи и используется для вывода ошибок, тогда как стандартный вывод используется для обычного вывода. Эти (и другие) дескрипторы могут быть сопоставлены с объектами вместо терми- терминала; такое сопоставление называется перенаправлением ввода/вывода, и все стандарт- стандартные оболочки дают пользователю возможность это сделать. Оболочка может напра- направить вывод программы в файл, закрыв дескриптор 1 (стандартный вывод) и открыв нужный файл таким образом, чтобы его новым дескриптором стал 1. Таким же образом оболочка может перенаправить стандартный ввод, закрыв дескриптор 0 и открыв файл. Каналы дают возможность использовать вывод одной программы в качестве ввода другой, не требуя переписывания или даже перекомпоновки любой из программ. Вместо того чтобы установить дескриптор 1 (стандартный вывод) исходной програм- программы для записи в терминал, в качестве него устанавливается входной дескриптор кана- канала. Сходным образом дескриптор 0 (стандартный ввод) программы назначения ссыла- ссылается на выходной конец канала, а не на клавиатуру терминала. Конечный набор из двух процессов и соединяющего их канала известен как конвейер (pipeline). Конвейеры могут представлять собой относительно длинную последовательность процессов, соеди- соединенных каналами. Системные вызовы open, pipe и socket создают новые дескрипторы с наименьшим доступным неиспользуемым номером для дескриптора. Чтобы конвейеры работали, должен быть предусмотрен некоторый механизм для отображения этих дескрипторов
54 Глава 2. Обзор дизайна FreeBSD в 0 и 1. Системный вызов dup создает копию дескриптора, который указывает на один и тот же элемент таблицы файлов. Новый дескриптор также является наименьшим неиспользуемым, но если нужный дескриптор сначала закрыт, dup может быть исполь- использован для нужного отображения. Однако необходима осторожность: если нужен дескриптор 1, а дескриптор 0 также окажется закрытым, результатом окажется дескриптор 0. Чтобы избежать этой проблемы, система предоставляет системный вызов dup2; он сходен с dup, но принимает дополнительный аргумент, указывающий номер нужного дескриптора (если нужный дескриптор уже открыт, dup2 закрывает его, прежде чем использовать повторно). Устройства У аппаратных устройств есть имена файлов, и пользователь может получить к ним доступ с помощью тех же системных вызовов, которые используются для обычных файлов. Ядро может отличить специальный файл устройства или специальный файл, и оно может определить, на какое устройство он ссылается, но большинству процессов не нужно это различие. Доступ к терминалам, принтерам и устройствам с магнитной лентой осуществляется так, как если бы все они были потоками байтов, подобно дис- дисковым файлам FreeBSD. Таким образом, своеобразие и зависимости устройств по воз- возможности сохраняются в ядре, и даже там большинство из них изолировано в драй- драйверах устройств. Процессы обычно получают доступ к устройствам посредством специальных файлов в файловой системе. Операции ввода/вывода для этих файлов обрабатываются резидентными программными модулями ядра, называемыми драйверами устройств. Доступ к большинству сетевого коммуникационного оборудования возможен лишь через средства межпроцессного взаимодействия, а не через специальные файлы в про- пространстве имен файловой системы, поскольку интерфейс непосредственного сокета (raw-socket) предоставляет более естественный интерфейс, чем специальный файл. Специальные файлы устройств создаются в файловой системе /dev своими драй- драйверами устройств, когда оборудование впервые обнаружено. Системный вызов ioctl манипулирует параметрами лежащего в основе устройства специального файла. Доступ- Доступные для осуществления операции отличаются для каждого устройства. Этот системный вызов позволяет получить доступ к особым характеристикам устройств, не перекрывая семантику других системных вызовов. Например, в звуковой карте существует ioctl для установки формата кодирования аудиоданных вместо наличия специализированной или измененной версии write. Механизм взаимодействия сокетов Ядро 4.2BSD ввело более гибкий по сравнению с каналами механизм IPC, основанный на сокетах. Сокет является конечной коммуникационной точкой, на которую ссылается дескриптор, подобно файлу или каналу. Два процесса могут создать каждый по сокету,
2.6. Система ввода/вывода 55 а затем соединить эти две конечные точки с образованием надежного потока байтов. После соединения дескрипторы сокетов могут использоваться процессами для чтения или записи аналогично их использованию в каналах. Прозрачность сокетов дает ядру возможность перенаправлять вывод одного процесса на ввод другого процесса, находя- находящегося на другой машине. Главным различием между каналами и сокетами является то, что для каналов требуется, чтобы общий родительский процесс установил коммуникаци- коммуникационный канал. Соединение между сокетами может быть установлено двумя несвязанными процессами, возможно находящимися на различных машинах. Очереди fifo выглядят как объекты в файловой системе, которые несвязанные про- процессы могут открыть и послать через них данные тем же способом, как через пару сокетов. Таким образом, для очередей fifo не требуется установка общим родителем; соединение с ними может быть установлено после того, как пара процессов запущена. В отличие от сокетов очереди fifo могут использоваться лишь на локальной машине; они не могут использоваться для взаимодействия между процессами на различных машинах. Механизм сокетов требует расширений традиционных системных вызовов ввода/ вывода UNIX, чтобы предоставить соответствующую семантику именования и соеди- соединения. Вместо перегрузки существующего интерфейса разработчики использовали существующие интерфейсы в той мере, в которой они могли быть использованы без изменений, и разработали новые интерфейсы для работы с добавленной семантикой. Системные вызовы read и write были использованы для соединений типа потока бай- байтов, но для отправки и получения адресованных сообщений, таких, как дейтаграммы, были добавлены шесть новых системных вызовов. Системные вызовы для записи сооб- сообщений включали send, sendto и sendmsg. Системные вызовы для чтения сообщений включали recv, recvfrom и recvmsg. В ретроспективе два первых в каждом классе явля- являлись особыми случаями других; recvfrom и sendto, возможно, должны были бы быть добавлены к recvmsg и sendmsg соответственно в качестве библиотечных интерфейсов. Разбросанный ввод/вывод В дополнение к традиционным системным вызовам read и write 4.2BSD ввело возможность осуществления разбросанного ввода/вывода (scatter/gather I/O). Разбросанный ввод исполь- использует системный вызов readv, чтобы дать возможность поместить результат одной операции чтения в несколько различных буферов. Наоборот, системный вызов writev дает возмож- возможность записать несколько буферов в одной неделимой операции записи. Вместо передачи одного буфера и параметра размера, как делается в read и write, процесс передает указатель на массив буферов и их размеров вместе с числом, указывающим размер массива. Это средство дает буферам в различных частях адресного пространства процесса возможность быть записанными атомарно, без необходимости копирования их в один непрерывный буфер. Атомарные записи необходимы в случае, когда лежащая в основе абстракция основана на записи, такой, как дейтаграммы, которые выдают на каждый
56 Глава 2. Обзор дизайна FreeBSD запрос записи одно сообщение. Удобна также возможность прочтения одним запросом в несколько различных буферов (например, заголовка записи в одно место, а данных в другое). Хотя приложение может имитировать возможность распределения данных, читая данные в один большой буфер, а затем копируя части в соответствующие места назначения, стоимость копирования из памяти в память в таких случаях часто более чем удваивает время выполнения соответствующего приложения. Так же как send и recv могли бы быть реализованы как библиотечные интерфейсы к sendto и recvfrom, можно было бы эмулировать read с использованием readv и write с использованием writev. Однако read и write используются так часто, что добавление издержек по их эмуляции не имело бы смысла. Поддержка нескольких файловых систем С распространением сетевых вычислений стала желательной поддержка как локальных, так и удаленных файловых систем. Чтобы облегчить поддержку нескольких файловых систем, разработчики добавили к ядру новый интерфейс виртуального узла (virtual node, или vnode). Набор операций, экспортируемых из интерфейса vnode, выглядит во многом подобно операциям файловой системы, ранее поддерживаемым локальной файловой системой. Однако они могут поддерживаться широким диапазоном типов файловых сис- систем. * Локальными файловыми системами на основе дисков. Файлами, импортируемыми с использованием различных протоколов удаленных файловых систем. Файловыми системами CD-ROM только для чтения. * Файловыми системами, предоставляющими интерфейсы специального назначе- назначения, - например, файловой системой /ргос. Путем использования загружаемых модулей ядра (см. раздел 14.4) FreeBSD допус- допускает динамическую загрузку файловых систем при первой ссылке на них системным вызовом mount. Интерфейс vnode описан в разделе 6.5; его вспомогательные проце- процедуры поддержки описаны в разделе в.в\ некоторые из файловых систем специального назначения описаны в разделе 6.7. 2.7. Устройства Исторически интерфейс устройств был статическим и простым. Устройства определя- определялись при загрузке системы и впоследствии не изменялись. Типичный дисковый драйвер мог быть написан с использованием нескольких сотен строк кода. По мере развития сис- системы сложность систем ввода/вывода возросла с добавлением новых возможностей. Устройства могли появляться, а затем исчезать при работе системы. С возрастанием
2.8. Файловые системы 57 сложности и разнообразия шин ввода/вывода маршрутизация запросов ввода/вывода стала сложной. Например, в многопроцессорной среде прерывания от устройств должны направляться на наиболее подходящий процессор, который может отличаться от того, который до этого обслуживал устройство. Обзор архитектуры PC дается в разделе 7.1. Логические диски могут больше не ссылаться на раздел одного физического диска, а вместо этого могут объединить несколько участков (slices) и/или разделов для создания виртуального раздела, на котором должна быть построена файловая система, охваты- охватывающая несколько дисков. Объединение таким образом разделов физических дисков в виртуальный раздел называется управлением томами. Вместо встраивания всех этих возможностей во все файловые системы или дисковые драйверы, они были абстрагированы в уровень GEOM (геометрии). Работа уровня GEOM описана в разделе 7.2. Управление двумя главными дисковыми подсистемами в FreeBSD описано в разделах 7.3 и 7.4. Автоконфигурирование является процедурой, выполняемой системой для обнаруже- обнаружения и подключения аппаратных устройств, присутствующих в системе. Исторически автоконфигурирование осуществлялось лишь однажды при загрузке системы. В совре- современных машинах, особенно в портативных компьютерах типа laptop, устройства регу- регулярно подключаются и отключаются во время работы компьютера. Соответственно ядро должно быть готово конфигурировать, инициализировать и делать доступным под- подключаемое оборудование и не производить операции с отключенным оборудованием. FreeBSD использует для управления устройствами в системе инфраструктуру драйверов устройств, которая называется newbus. Архитектура newbus описана в разделе 7.5. 2.8. Файловые системы Обычный файл является линейным массивом байтов, который может быть считан или записан, начиная с любого байта в файле. Ядро не различает границ записей в обычных файлах, хотя многие файлы распознают символы перевода строки как обозначающие концы строк, а другие программы могут устанавливать другую структуру. В самом файле не содержится никакой относящейся к системе информа- информации о файле, но система хранит с каждым файлом небольшое количество сведений о владении, защите и использовании. Компонента имени файла является строкой до 255 символов. Эти имена файлов хранятся в особом типе файла, который называется каталогом. Информация о файле в каталоге называется элементом каталога и включает, в дополнение к имени файла, указатель на сам файл. Элементы каталога могут ссылаться на другие каталоги так же, как на обычные файлы. Иерархия файлов и каталогов, образованная таким способом, называется файловой системой', небольшая файловая система показана на рис. 2.2. Каталоги могут содержать подкаталоги, и нет врожденного ограничения на глубину вложения каталогов. Для защиты целостности файловой системы ядро не разрешает
58 Глава 2. Обзор дизайна FreeBSD процессам записывать непосредственно в каталоги. Файловая система может включать не только простые файлы и каталоги, но также и ссылки на другие объекты, такие, как устройства, сокеты и очереди fifo. Файловая система формирует дерево, началом которого является корневой ката- каталог, на который иногда ссылаются по имени слеш, записывая его в виде одного симво- символа косой черты (/). Корневой каталог содержит файлы; в нашем примере на рис. 2.2 он содержит kernel, копию исполняемого объектного файла ядра. Он содержит также каталоги; в данном примере он содержит каталог usr. Внутри каталога usr находится каталог bin, содержащий главным образом исполняемый объектный код программ, такой, как файлы Is и vi. Рис. 2.2. Дерево небольшой файловой системы Процесс идентифицирует файл, указывая имя пути файла, которое представляет собой строку, составленную из нуля или более имен файлов, разделенных символом слеша (/). Ядро связывает с каждым процессом два каталога для использования при интерпретации имен путей. Корневой каталог процесса является самой верхней точкой в файловой системе, к которой процесс может получить доступ; обычно в таком качестве выступает корневой каталог всей файловой системы. Имя пути, начинающееся со слеша, называется абсолютным путем и интерпретируется ядром, начиная с корне- корневого каталога процесса. Имя пути, которое не начинается со слеша, называется относительным путем и интерпретируется относительно текущего рабочего каталога процесса. (Этот каталог известен также под более короткими именами текущего каталога к рабочего каталога.) На сам текущий каталог можно непосредственно ссылаться по имени точка, которое
2.8. Файловые системы 59 записывается в виде одной точки (.). Имя файла точка-точка (..) ссылается на родитель- родительский каталог данного каталога. Корневой каталог сам является своим родителем. Процесс может установить свой корневой каталог с помощью системного вызова chroot, а свой текущий каталог с помощью системного вызова chdir. Любой процесс в любое время может вызвать chdir, но chroot разрешен лишь процессу с правами суперпользователя. Chroot обычно используется для ограничения доступа к системе. Используя файловую систему, показанную на рис. 2.2, если корневым каталогом процесса является корневой каталог файловой системы, а текущим каталогом является /usr, процесс может ссылаться на файл vi либо из корневого каталога с помощью абсо- абсолютного пути /usr/bin/vi, либо из текущего каталога с помощью относительного пути bin/vi. Системные утилиты и базы данных хранятся в определенных хорошо известных каталогах. Часть хорошо определенной иерархии включает каталог, который содержит домашние каталоги для каждого пользователя (например, /usr/staff/mckusick и /usr/ staff/gnn на рис. 2.2). Когда пользователь регистрируется, текущим рабочим каталогом его оболочки устанавливается его домашний каталог. В пределах своих домашних каталогов пользователи могут создавать каталоги так же легко, как обычные файлы. Таким образом, пользователь может строить свои сравнительно сложные иерархии. Пользователь обычно знает лишь об одной файловой системе, но система может знать, что эта одна файловая система на самом деле составлена из нескольких физиче- физических файловых систем, каждая на своем устройстве. Физическая файловая система не может занимать несколько логических устройств. Поскольку большинство физических дисков делятся на несколько логических устройств, на каждое физическое устройство может приходиться более одной файловой системы, но не более одной на одно логиче- логическое устройство. Одна файловая система - к которой привязаны все абсолютные пути, называется корневой файловой системой, и она всегда доступна. Другие могут мон- монтироваться, т.е. могут быть интегрированы в иерархию каталогов корневой файловой системы. Ссылки на каталог, в котором смонтирована файловая система, прозрачным образом преобразуются ядром в ссылки на корневой каталог смонтированной файло- файловой системы. Системный вызов link принимает имя существующего файла и другое имя, которое создается для этого файла. После успешного выполнения link доступ к файлу может быть осуществлен по любому имени. Имя файла можно удалить с помощью системного вызова unlink. Когда удаляется последнее имя файла (и последний процесс, в котором файл открыт, закрывает его), файл удаляется. Файлы иерархически организованы в каталогах. Каталог является разновидно- разновидностью файла, но в отличие от обычных файлов каталог имеет структуру, наложенную на него системой. Процесс может прочесть каталог как обычный файл, но лишь ядру разрешается изменять каталог. Каталоги создаются с помощью системного вызова mkdir и удаляются с помощью системного вызова rmdir. До 4.2BSD системные вызовы
60 Глава 2. Обзор дизайна FreeBSD mkdir и rmdir были реализованы через ряд системных вызовов link и unlink. Для добав- добавления системных вызовов, явно создающих и удаляющих каталоги, было три причины. 1. Операция должна быть неделимой. Если в системе случится аварийный отказ, ката- каталог не должен остаться сделанным наполовину, что могло бы случиться при исполь- использовании ряда операций link. 2. При запуске сетевой файловой системы создание и удаление файлов и каталогов должны быть неделимыми (атомарными), чтобы их можно было сериализовать. 3. При поддержке файловых систем, не относящихся к UNIX, таких, как файловая система NT, на других разделах диска эти файловые системы могут не поддержи- поддерживать операции link. Хотя другие файловые системы могут поддерживать концеп- концепцию каталогов, они, возможно, не создают и не удаляют каталоги с помощью ссы- ссылок, как делает файловая система UNIX. Соответственно они могли бы создавать и удалять каталоги, лишь если были бы представлены явные запросы создания и удаления каталогов. Системный вызов chown устанавливает владельца и группу файла, a chmod изменя- изменяет атрибуты прав доступа. Stat с именем файла может использоваться для получения свойств файла. Системные вызовы fchown, fchmod и fstat применяются для тех же операций с дескриптором, а не с именем файла. Системный вызов rename можно использовать, чтобы дать файлу в файловой системе новое имя, заменив одно из старых имен файла. Как и операции добавления и удаления каталогов, системный вызов rename был добавлен к 4.2BSD для обеспечения атомарности изменения имени в локальной файловой системе. Позже он явно пригодился для экспортирования операций переименования в другие файловые системы и по сети. Системный вызов truncate был добавлен к 4.2BSD, чтобы обеспечить установку произвольного размера файла. Таким образом, название truncate неудачно1, поскольку оно используется как для укорочения, так и для удлинения файла. Файлы могут содержать в себе дыры. Дыры являются пустыми областями в линейном пространстве файла, в которое никогда не записывались данные. Процесс может создать эти дыры, поместив указатель за текущим концом файла и что-то записав. В качестве альтернативы дыра может быть добавлена в конец файла путем использования системного вызова truncate для увеличения его размера. При чтении дыры интерпретируются системой как заполненные нулевыми байтами. Поскольку у файловой системы была возможность укорачивать файлы, ядро вос- воспользовалось этой возможностью для укорочения больших пустых каталогов. Преиму- Преимущество сокращения пустых каталогов в том, что оно сокращает время, затрачиваемое ядром для поиска при создании или удалении имен. 1 Truncate (англ.) - укорачивать, сокращать. - Примеч. пер
2.8. Файловые системы 61 Вновь созданным файлам назначаются идентификаторы пользователя процесса, который их создал, и идентификатор группы каталога, в котором они были созданы. Для защиты файлов предусмотрен трехуровневый механизм управления доступом. Доступность файла определяют следующие три уровня. 1. Для пользователя, владеющего файлом. 2. Для группы, владеющей файлом. 3. Для всех остальных. У каждого уровня доступа свои собственные указатели прав доступа для чтения, записи и исполнения. Если необходимо более детализированное управление доступом, FreeBSD 5.2 предоставляет также ACL (access control lists - списки управления досту- доступом) для определения прав доступа на чтение, запись и исполнение на уровне отдель- отдельных пользователей или групп. Файлы создаются с нулевым размером и могут увеличиваться при записи в них. Пока файл открыт, система поддерживает файловый указатель, показывающий теку- текущее положение в файле, связанное с дескриптором. Этот указатель может перемещать- перемещаться в файле произвольным образом. Процессы, разделяющие дескриптор файла через системные вызовы fork или dup, разделяют указатель текущего положения. Дескрип- Дескрипторы, созданные отдельными системными вызовами open, имеют отдельные указатели текущего положения. Файловое хранилище Видимая пользователю часть файловой системы представляет собой ее иерархическое именование, блокировки, квоты, управление атрибутами и защиту. Но большая часть реализации файловой системы содержит организацию и управление данными на физическом носителе. За размещение содержимого файлов на физическом носителе отвечает файловое хранилище (filestore). FreeBSD использует традиционный формат быстрой файловой системы Беркли (Berkley fast filesystem). На диске организуются группы протяженных блоков, называемых группами цилиндров. Файлы, к которым вероятен совместный доступ на основе их положения в иерархии файловой системы, хранятся в одной группе цилиндров. Файлы, к которым совместный доступ не ожида- ожидается, помещаются в разные группы цилиндров. Ключевой обязанностью файлового хранилища является обеспечение того, чтобы файловая система всегда поддерживалась в состоянии, в котором имелась бы возмож- возможность восстановления после аппаратных или программных сбоев. Хотя восстанавли- восстанавливаемость можно поддерживать, используя синхронные записи на диск, производитель- производительность файловой системы, использующей эту методику, была бы неприемлемо низкой. FreeBSD использует методику, называемую гибкими корректировками (soft updates) (см. раздел 8.6), для обеспечения восстанавливаемости, по-прежнему предоставляя хорошую производительность и быстрый запуск системы после аварии.
62 Глава 2. Обзор дизайна FreeBSD Другой полезной особенностью файлового хранилища FreeBSD является возмож- возможность быстрых моментальных снимков файловой системы. Моментальные снимки могут создаваться каждые несколько часов и монтироваться в хорошо известных местах таким образом, чтобы пользователи могли восстанавливать неосторожно уда- удаленные файлы, которые они создали или записали ранее в течение дня. Моментальные снимки могут также использоваться, чтобы дать возможность создавать непроти- непротиворечивые архивы файловых систем, которые непрерывно активно используются. Моментальные снимки описаны в разделе 8.7. 2.9. Сетевая файловая система Первоначально сеть использовалась для передачи данных из одной машины в другую. Позже она развилась, давая возможность пользователям удаленно регистрироваться на другой машине. Следующим логическим шагом было перемещение данных к пользо- пользователю, вместо необходимости пользователю идти к данным - так родились сетевые файловые системы. Пользователи, работающие локально, не подвергаются сетевым за- задержкам при каждом нажатии на клавишу, таким образом они имеют более отзывчивое окружение. Доставка файловой системы на локальную машину была первой из главных клиент- серверных приложений. Сервер является удаленной машиной, экспортирующей одну или более из своих файловых систем. Клиент является локальной машиной, им- импортирующей эти файловые системы. С точки зрения локального клиента смонтирован- смонтированная удаленная файловая система выглядит в пространстве имен дерева файлов точно так же, как любая другая локально смонтированная файловая система. Локальные клиенты могут переходить по каталогам удаленной файловой системы и могут читать, записывать и исполнять двоичные файлы внутри этой удаленной файловой системы таким же спосо- способом, как они могут делать эти операции на локальной файловой системе. Когда локальный клиент выполняет операцию на удаленной файловой системе, запрос упаковывается и посылается серверу. Сервер выполняет запрошенную опера- операцию и возвращает либо запрошенную информацию, либо ошибку, указывающую, почему запрос был отклонен. Для обеспечения приемлемой производительности клиент должен кешировать данные, к которым осуществляется частый доступ. Сложность удаленной файловой системы заключается в поддержании согласованности кеша между сервером и множеством клиентов. Хотя с течением времени было разработано много протоколов удаленных файловых систем, наиболее распространенным в системах UNIX является Network Filesystem (NFS), протокол и наиболее широко использующаяся реализация которой были выполнены фирмой Sun Microsystems. Ядро FreeBSD поддерживает протокол NFS, хотя реализация была сделана независимо от спецификации протокола [Macklem, 1994]. Протокол NFS описан в главе 9.
2.10. Терминалы 63 2.10. Терминалы Исторически пользователи взаимодействовали с системой, используя подключенные к компьютеру через жестко запаянные линии связи терминалы. Хотя фиксированные терминалы в значительной степени остались в истории, обработка символов, осуществляе- осуществляемая для ввода/вывода с клавиатуры, по-прежнему важна. Наиболее обычный вид сеанса пользователя в FreeBSD использует псевдотерминал. Псевдотерминал состоит из пары устройств, называемых ведущим (master) и ведомым (slave) устройствами. Ведомое устройство предоставляет процессу интерфейс, идентичный с исторически предоставляв- предоставлявшимся этим устройством интерфейсом. Однако вместо того, чтобы ассоциировать с ап- аппаратным устройством для чтения и записи символов, у ведомого устройства есть другой процесс, который управляет им через ведущую часть псевдотерминала, т.е. все, что запи- записывается в ведущее устройство, предоставляется ведомому устройству в качестве ввода, а все, что записывается в ведомое устройство, представляется в качестве ввода ведущему устройству. Псевдотерминалы используются эмулятором терминала X-window, xterm, а также обычными программами удаленной регистрации, такими, как ssh и telnet. Терминалы поддерживают стандартные системные операции ввода/вывода, а также совокупность специфических для терминала операций управления редактированием вводимых символов и задержек вывода. Обработка символов осуществляется дисципли- дисциплиной линии связи. Дисциплина линии связи по умолчанию выбирается, когда терминал ис- используется для интерактивной регистрации. Дисциплина линии связи работает в кано- каноническом реясиме\ ввод обрабатывается с предоставлением стандартных функций, ори- ориентированных на редактирование строк, и представляется процессу построчно. Экранные редакторы и программы, взаимодействующие с другими компьютерами, обычно работают в неканоническом режиме (обычно называемом также непосредст- непосредственным (raw mode) или посимвольным (character-at-a-time mode) режимом). В данном режиме ввод передается читающему процессу немедленно и без интерпретации. Обработка специальных символов отключена, стирания или другой обработки строки не осуществля- осуществляется, и программе, читающей с терминала, передаются все символы. Терминал можно отконфигурировать тысячами комбинаций между этими двумя крайностями. Например, экранный редактор, которому нужно получать прерывания пользователя асинхронно, мог бы включить специальные символы, генерирующие сигналы, и включить управление потоком вывода, но в остальном работать в некано- неканоническом режиме. Все другие символы передавались бы процессу без интерпретации. На выводе обработчик терминала предоставляет простые службы форматирования, включая следующее. Преобразование символа перевода строки в последовательность из двух символов перевода каретки и перевода строки. * Расширение табуляций.
64 Глава 2. Обзор дизайна FreeBSD * Отображение отраженных неграфических ASCII-символов в виде последователь- последовательности двух символов в форме «АС» (т.е. ASCII-символа вставки, за которым следует ASCII-символ, значение которого является смещением от ASCII-символа «@»). Каждую из этих служб форматирования процесс может индивидуально отменить посредством управляющих запросов. 2.11. Межпроцессное взаимодействие Межпроцессное взаимодействие в FreeBSD организовано в коммуникационные доме- домены. Важнейшие домены, поддерживаемые в настоящее время, включают локальный домен для взаимодействия процессов, выполняющихся на одной машине; домен IPv4 для взаимодействия процессов с использованием набора протоколов TCP/IP (четвертой версии) и домен IPv6, который является новейшей версией протоколов Интернета. В пределах домена коммуникация осуществляется между конечными точками, известными как сокеты. Как упоминалось в разделе 2.6, системный вызов socket соз- создает сокет и возвращает дескриптор; другие системные вызовы описаны в главе 11. У каждого сокета есть тип, который определяет его семантику взаимодействия; эта семантика включает такие свойства, как надежность, порядок и предотвращение дублирования сообщений. У каждого сокета есть связанный с ним коммуникационный протокол. Этот протокол предоставляет семантику, необходимую сокету в соответствии с его типом. Приложения при создании сокета могут запрашивать определенный протокол или могут разрешить системе выбрать протокол, подходящий для создаваемого типа сокета. Сокеты могут быть привязаны к адресам. Форма и значение адресов сокетов зависят от коммуникационного домена, в котором создается сокет. Привязка имени к сокету в локальном домене вызывает создание файла в файловой системе. Обычные данные, передаваемые и получаемые через сокеты, не типизированы. За представление данных отвечают библиотеки, построенные поверх средств межпро- межпроцессного взаимодействия. Реализация сетевых средств в UNIX до 4.2ВSD обычно работала посредством перегрузки интерфейсов символьных устройств. Одной из целей интерфейса сокетов было дать возможность простым программам работать без изменений в соединениях в стиле потоков. Такие программы могут работать, лишь если системные вызовы read и write не изменены. Поэтому оригинальные интерфейсы были оставлены нетронуты- нетронутыми и могли работать с сокетами для потоков. Для более сложных сокетов были добав- добавлены новые интерфейсы, такие, как сокеты, использующиеся для отправки дейта- дейтаграмм, в которых адрес назначения должен был указываться в каждом вызове send. Другим преимуществом является то, что новый интерфейс легко переносимый. Реализации API-сокетов существуют для почти каждой современной операционной системы, включая те, которые сильно отличаются от UNIX.
2.12. Сетевая коммуникация 65 FreeBSD поддерживает также несколько локальных механизмов IPC, не связанных с работой в сети, включая семафоры, очереди сообщений и разделяемую память. Эти механизмы освещаются в разделе 11.8. 2.12. Сетевая коммуникация Некоторые из коммуникационных доменов, поддерживаемые механизмом 1РС-соке- тов, предоставляют доступ к сетевым протоколам. Эти протоколы реализованы в виде отдельного программного уровня, находящегося в ядре логически ниже программного обеспечения сокетов. Ядро предоставляет много дополнительных служб, таких, как управление буферами, маршрутизация сообщений, стандартизированные интерфейсы к протоколам и интерфейсы к драйверам сетевых интерфейсов для использования раз- различных сетевых протоколов. В настоящее время используются две различные версии протоколов Интернета - один признанный, другой в стадии разработки. BSD поддерживал несколько протоко- протоколов с 4.2BSD, предоставляя способность к взаимодействию и разделению ресурсов среди разнотипных наборов машин, существующих в Интернете. Поддержка множест- множества протоколов предусматривает также будущие изменения. Сегодняшние протоколы, разработанные для Ethernet со скоростями 10 и 100 мегабит в секунду, похоже, не под- подходят для завтрашних сетей со скоростями 10 и 100 гигабит в секунду. Поэтому уро- уровень сетевой коммуникации спроектирован для поддержки множества протоколов. Новые протоколы добавляются в ядро без влияния на поддержку более старых. Старые приложения могут продолжать работу с использованием старых протоколов по той же самой физической сети, которая используется новыми приложениями, работающими с более новым сетевым протоколом. 2.13. Реализация работы в сети Первым набором протоколов, реализованным в 4.2BSD, был Протокол управления передачей (ТСР)/Протокол Интернета (IP) (IPv4). CSRG выбрала TCP/IP в качестве первой сети для включения в состав каркаса IPC-сокетов, поскольку основанная на 4.1 BSD реализация была общедоступна из финансируемого DARPA проекта Bolt, Beranek и Newman (BBN). Это был важный выбор: реализация 4.2BSD является глав- главной причиной чрезвычайно широкого распространения использования этого набора протоколов. Позже были также широко приняты функциональные усовершенствова- усовершенствования и улучшения производительности TCP/IP. Реализация TCP/IP подробно описыва- описывается в главе 13. Выпуск FreeBSD 4.0 добавил поддержку для набора протоколов IPv6, построенно- построенного группой КАМЕ в Японии. Этот набор является следующим поколением протоколов Интернета, который в конечном счете заменит Интернет IPv4, ставший популярным частично из-за BSD.
66 Глава 2. Обзор дизайна FreeBSD 2.14. Работа системы Для запуска системы используются механизмы начальной загрузки. Сначала в глав- главную память процессора должно быть загружено ядро FreeBSD. После загрузки оно должно пройти через фазу инициализации для приведения оборудования в известное состояние. Затем ядро должно осуществить зависимую и независимую от устройств инициализацию. Система начинает работу в однопользовательском режиме, пока сце- сценарий запуска проверяет диски и запускает учет системных ресурсов и проверку квот. Наконец, сценарий запуска запускает общие службы системы и приводит систему в полностью многопользовательский режим. В многопользовательском режиме система может действовать как обычная систе- система с разделением времени, поддерживающая непосредственную или сетевую реги- регистрацию пользователей, которые затем запускают свои процессы. FreeBSD часто дей- действует в качестве сервера, предоставляя службы и обслуживая веб-запросы сетевых клиентов. Все эти сетевые службы могут автоматически запускаться во время началь- начальной загрузки. При использовании в качестве сервера в системе редко регистрируется более одного пользователя (администратора). Упражнения 2.1. Как процесс пользователя запрашивает службу ядра? 2.2. Как перемещаются данные между процессом и ядром? Какие альтернативы дос- доступны? 2.3. Как процесс получает доступ к потоку ввода/вывода? Перечислите три вида потоков ввода/вывода. 2.4. Какие четыре шага имеются в жизненном цикле процесса? 2.5. Почему в FreeBSD предусмотрены группы процессов? 2.6. Перечислите четыре машинно-зависимые функции ядра. 2.7. Опишите различие между абсолютным и относительным именем путей. 2.8. Приведите три довода, почему к 4.2BSD был добавлен системный вызов mkdir. 2.9. Определите разбросанный ввод/вывод. Почему он полезен? 2.10. Перечислите пять функций, предоставляемых драйвером терминала. 2.11. В чем отличие между каналом и сокетом? 2.12. Опишите, как создать группу процессов в конвейере. *2.13. Перечислите три системных вызова, которые требовались для создания в текущем каталоге нового каталога foo до добавления системного вызова mkdir. *2.14. Объясните разницу между межпроцессным взаимодействием и использованием сети.
Ссылки 67 Ссылки Accetta et al., 1986. M. Accetta, R. Baron, W. Bolosky, D. Golub, R. Rashid, A. Tevanian, & M. Young, "Mach: A New Kernel Foundation for UNIX Development", USENIXAssociation Con- Conference Proceedings, pp. 93-113, June 1986. Cheriton, 1988. D. R. Cheriton, "The V Distributed System", Comm ACM, vol. 31, no. 3, pp. 314-333, March 1988. Ewensetal., 1985. P. Ewens, D. R. Blythe, M. Funkenhauser, & R. С Holt, "Tunis: A Distributed Multipro- Multiprocessor Operating System", USENIX Association Conference Proceedings, pp. 247-254, June 1985. Gingell et al., 1987. R. Gingell, J. Moran, & W. Shannon, "Virtual Memory Architecture in SunOS", USENIX Association Conference Proceedings, pp. 81-94, June 1987. Kernighan & Pike, 1984. B. W. Kernighan & R. Pike, The UNIX Programming Environment, Prentice-Hall, Englewood Cliffs, NJ, 1984.1 Macklem, 1994. R. Macklem, "The 4.4BSD NFS Implementation", in 4.4BSD System Manager's Manu- Manual, pp. 6:1-14, O'Reilly & Associates, Inc., Sebastopol, CA, 1994. McKusick & Karels, 1988. M. K. McKusick & M. Karels, "Design of a General Purpose Memory Allocator for the 4.3BSD UNIX Kernel", USENIX Association Conference Proceedings, pp. 295-304, June 1988. McKusick et al., 1994. M. K. McKusick, M. Karels, S. J. Leffler, W. N. Joy, & R. S. Fabry, "Berkeley Software Architecture Manual, 4.4BSD Edition", in 4.4BSD Programmers Supplementary Documents, pp. 5:1-42, O'Reilly & Associates, Inc., Sebastopol, CA, 1994. Ritchie, 1988. D. M. Ritchie, "Early Kernel Design", private communication, March 1988. Rozieretal., 1988. M. Rozier, V. Abrossimov, F. Armand, I. Boule, M. Gien, M. Guillemont, F. Herrmann, С Kaiser, S. Langlois, P. Leonard, & W. Neuhauser, "Chorus Distributed Operating Systems", USENIX Computing Systems, vol. 1, no. 4, pp. 305-370, Fall 1988. Tevanian, 1987. A. Tevanian, "Architecture-Independent Virtual Memory Management for Parallel and Distributed Environments: The Mach Approach", Technical Report CMU-CS-88-106, Department of Computer Science, Carnegie-Mellon University, Pittsburgh, PA, Decem- December 1987. 1 Русский перевод: Брайн Керниган, Роб Пайк, UNIX. Программное окружение, С.-Петербург, Символ-Плюс, 2003. — Примеч. науч. ред.
Глава 3 Службы ядра 3.1. Организация ядра Ядро FreeBSD можно рассматривать как провайдер служб для процессов пользовате- пользователей. Процессы обычно получают доступ к этим службам через системные вызовы. Некоторые службы, такие, как планирование процессов и управление памятью, реали- реализованы в виде процессов, которые выполняются в режиме ядра, или в виде процедур, которые периодически выполняются внутри ядра. В данной главе мы опишем, как службы ядра предоставляются процессам пользователя, а также некоторую дополни- дополнительную обработку, осуществляемую ядром. Затем мы опишем основные службы ядра, предоставляемые FreeBSD, и приведем детали их реализации. Системные процессы Все процессы уровня пользователя FreeBSD происходят от одного процесса, который создается ядром при загрузке. В табл. 3.1 перечислены процессы, которые создаются сразу и существуют всегда. Это процессы ядра, они функционируют полностью внутри ядра. (Процессы ядра выполняют код, который скомпилирован в загружаемом образе ядра, и работают в привилегированном режиме выполнения ядра.) Ядро также запус- запускает процесс ядра для каждого устройства для обработки прерываний для этого устройства.
3.1. Организация ядра 69 Табл. 3.1. Постоянные процессы ядра Имя Описание idle Выполняется, когда больше нечего делать swapper Планирует загрузку процессов в главную память из вторичного хранилища, когда становят- становятся доступны системные ресурсы vmdaemon Планирует перенос всего процесса из главной памяти во вторичное хранилище, когда сис- системные ресурсы ограничены pagedaemon Записывает части адресного пространства процесса во вторичное хранилище для стра- страничной поддержки системы виртуальной памяти pagezero Обеспечивает источник обнуленных страниц bufdaemon Обеспечивает источник чистых буферов путем записи грязных буферов, когда число чистых становится слишком маленьким syncer Гарантирует запись грязных данных файла после 30 секунд ktrace Сохраняет трассировочные записи системных вызовов в файл вывода vnlru Обеспечивает источник свободных vnode, очищая реже всего используемые random Собирает случайные данные для источника затравочных чисел для генератора случайных чисел ядра и устройства /dev/random g_event Выполняет задачи конфигурирования, включая обнаружение новых устройств и очистку устройств после их исчезновения g_up Обрабатывает данные, поступающие от драйверов устройств и доставляемые процессам g_down Обрабатывает данные, поступающие от процессов и доставляемые драйверам устройств После создания процессов ядра ядро создает первый процесс для выполнения про- программы в режиме пользователя; он служит в качестве родительского процесса для всех последующих процессов. Первым процессом режима пользователя является ink, исторически - процесс 1. Этот процесс выполняет административные задачи, такие, как порождение процессов getty для каждого терминала на машине, сбор состояния за- завершения покинутых процессов и обработка упорядоченного перевода системы из многопользовательского в однопользовательский режим. Процесс init является про- процессом режима пользователя, работающим вне ядра (см. раздел 14.6). Системный вход Входы в ядро можно категоризировать в соответствии с событием или действием, которое его вызывает. * Аппаратное прерывание (interrupt). * Аппаратное исключение (trap). * Программное исключение.
70 Глава 3. Службы ядра Аппаратные прерывания возникают из-за внешних событий, таких, как необходи- необходимость обработки устройства ввода/вывода или сообщение от часов о наступлении определенного времени. (Например, ядро зависит от наличия часов реального времени или интервального таймера для поддержания текущего времени дня, управления планированием процессов и инициированием и выполнением системных функций тайм-аутов.) Аппаратные прерывания возникают асинхронно и могут не относиться к контексту текущего выполняющегося процесса. Аппаратные исключения могут быть либо синхронными, либо асинхронными, но связаны с контекстом текущего выполняющегося процесса. Примерами аппаратных исключений являются исключения, сгенерированные в результате недопустимой арифметической операции, такой, как деление на ноль. Программные исключения используются системой для принудительного планиро- планирования события, такого, как перепланирование процесса или обработка работы с сетью так быстро, как это возможно. Программные исключения реализуются установкой флага каждый раз, когда процесс собирается выйти из ядра. Если флаг установлен, вместо выхода из ядра запускается код обработки программного прерывания. Системные вызовы являются особым случаем программного прерывания - машинная инструкция, используемая для инициирования системного вызова, обычно вызывает аппаратное исключение, которое обрабатывается ядром особым образом. Организация времени выполнения Ядро можно логически разделить на верхнюю половину и нижнюю половину, как показа- показано на рис. 3.1. Верхняя половина ядра предоставляет службы процессам в ответ на сис- системные вызовы или исключения. Это программное обеспечение можно представить как библиотеку процедур, разделяемую всеми процессами. Верхняя половина ядра выполня- выполняется в привилегированном режиме исполнения, в котором она имеет доступ как к струк- структурам данных ядра, так и к контексту процессов уровня пользователя. Контекст каждого процесса содержится в двух областях памяти, зарезервированной для специфической ин- информации процесса. Первая из этих областей является структурой процесса, которая ис- исторически содержит информацию, необходимую, даже когда процесс сброшен на диск. В FreeBSD эта информация включает идентификаторы, связанные с процессом, права и привилегии процесса, его дескрипторы, распределение памяти, ожидающие внешние события и связанные действия, максимальное и текущее использование ресурсов и многие другие вещи. Вторая является структурой пользователя, которая исторически содержит информацию, которая не нужна, когда процесс сброшен на диск. В FreeBSD информация структуры пользователя каждого процесса включает аппаратный блок управления потоком (thread control block (TCB), учет ресурсов и статистику процесса и незначительную дополнительную информацию для отладки и создания дампов памяти
3.1. Организация ядра 71 (core dump). Решение, что же должно храниться в структуре процесса и структуре поль- пользователя, было более важно в предыдущих системах, чем в FreeBSD. Поскольку память стала менее ограниченным ресурсом, большая часть структуры пользователя была для удобства перенесена в структуру процесса; см. раздел 4.2. Процесс пользователя Верхняя половина ядра Нижняя половина ядра cat READ Г waiting J) interrupts ' Рис. 3.1. Структура ядра времени Г Вытесняющее планирование; не может блокироваться; работает с пользовательским стеком в адресном пространстве пользователя Работает до блокировки или завершения; может блокироваться, ожидая ресурс; работает со стеком ядра для процессов Планируется прерываниями; может блокироваться, ожидая ресурс; работает со стеком ядра для прерываний выполнения Нижняя половина ядра содержит в себе процедуры, которые вызываются для обра- обработки аппаратных прерываний. Деятельность нижней половины ядра синхронна по отношению к источнику прерывания, но асинхронна по отношению к верхней половине, и программное обеспечение не может полагаться на то, что во время прерывания будет (если вообще будет) активен определенный процесс. Таким образом, информация о состоя- состоянии для процесса, который инициировал деятельность, недоступна. Верхняя и нижняя половины ядра взаимодействуют через структуры данных, обычно организованных вокруг рабочих очередей. Ядро FreeBSD никогда не вытесняется для запуска процесса пользователя при выполнении в верхней половине ядра - например, при выполнении системного вызова, - хотя оно явным образом уступит процессор, если нужно ждать событие или разделяе- разделяемый ресурс. Однако его выполнение может быть прервано прерыванием для нижней половины ядра. Когда получено прерывание, процесс ядра, обрабатывающий это устройство, назначается на выполнение. Обычно эти процессы прерываний устройств имеют более высокий приоритет, чем процессы пользователя или процессы, рабо- работающие в верхней половине ядра. Таким образом, когда прерывание делает процесс обработки прерывания устройства работоспособным, он обычно вытесняет текущий выполняющийся процесс. Когда процессу, работающему в верхней половине ядра, нужно добавить элемент в рабочий список устройства, он должен убедиться, что он не будет прерван этим устройством на полпути связывания нового элемента с рабочим
72 Глава 3. Службы ядра списком. В FreeBSD 5.2 рабочий список защищен мьютексом. Любой процесс (из верхней или нижней половины), стремящийся изменить рабочий список, должен сначала получить мьютекс. После его получения другой процесс, также стремящийся получить мьютекс, будет ждать до тех пор, пока процесс, владеющий мьютексом, не закончит изменение списка и не освободит мьютекс. Процессы взаимодействуют при разделении системных ресурсов, таких, как диски и память. Верхняя и нижняя половины ядра также работают вместе в реализации опре- определенных системных операций, таких, как ввод/вывод. Обычно верхняя половина запускает операцию ввода/вывода, а затем уступает процессор; после этого запраши- запрашивающий процесс будет спать, ожидая уведомления от нижней половины о завершении запроса ввода/вывода. Вход в ядро Когда процесс входит в ядро посредством исключения или прерывания, ядро должно сохранить текущее состояние машины до начала обслуживания события. Для PC состояние машины, которое должно быть сохранено, включает счетчик команд, указа- указатель стека пользователя, регистры общего назначения и слово состояния процессора. Инструкция исключения PC сохраняет счетчик команд и слово состояния процессора как часть фрейма стека исключения; указатель стека пользователя и регистры должны быть сохранены программным обработчиком исключения. Если состояние машины не было бы сохранено полностью, ядро могло бы изменить значения в текущей испол- исполняющейся программе неподходящим образом. Поскольку прерывания могут возникать между любыми двумя инструкциями уровня пользователя (а на некоторых архитек- архитектурах и между частями одной инструкции) и поскольку они могут вообще не относить- относиться к текущему выполняющемуся процессу, не полностью сохраненное состояние могло бы вызывать в правильных программах загадочные и трудновоспроизводимые ошибки. Точная последовательность действий, необходимая для сохранения состояния про- процесса, полностью зависит от машины, хотя PC представляет хороший пример обычной процедуры. Исключение или системный вызов запустят следующие события. * Происходит аппаратное переключение в режим ядра (супервизора), так что про- проверки доступа к памяти осуществляются с привилегиями ядра, ссылки на стек используют стек ядра для процессов и могут выполняться привилегированные инструкции. * Аппаратура помещает в стек ядра для процессов счетчик команд, слово состояния процессора и информацию, описывающую тип исключения. (На отличных от PC архитектурах эта информация может включать также номер системного вызова и регистры общего назначения.)
3.1. Организация ядра 73 Процедура на языке ассемблера сохраняет всю информацию о состоянии, не со- сохраненную аппаратным обеспечением. На PC эта информация включает регистры общего назначения и указатель стека пользователя, также сохраняемые в стеке ядра для процессов. После этого предварительного сохранения состояния ядро вызывает процедуру С, которая может свободно использовать регистры общего назначения, как любая другая процедура на С, не беспокоясь об изменении состояния ничего не подозревающего процесса. Есть три главные разновидности обработчиков, соответствующие определенным входам в ядро. 1. SyscallQ для системного вызова. 2. Тгар() для аппаратных и программных исключений, кроме системных вызовов. 3. Соответствующий обработчик прерывания драйвера устройства для аппаратного прерывания. Каждый тип обработчика принимает свой собственный набор параметров. Для сис- системного вызова это номер системного вызова и фрейм исключения. Для исключения это тип исключения, соответствующая информация о плавающей точке и виртуальном адре- адресе, относящаяся к исключению, и фрейм исключения. (Аргументы фрейма исключения для исключения и системного вызова не одни и те же. Аппаратура PC сохраняет раз- различную информацию в зависимости от различных типов исключений.) Для аппаратного прерывания единственным параметром является номер устройства (или платы). Возвращение из ядра Когда обработка системного входа завершена, состояние процесса пользователя вос- восстанавливается и управление возвращается процессу пользователя. Возвращение в процесс пользователя противоположно процессу входа в ядро. * Процедура, написанная на ассемблере, восстанавливает регистры общего назначе- назначения и указатель стека пользователя, помещенные ранее в стек. * Аппаратура восстанавливает счетчик команд и слово состояния процессора и переключается в режим пользователя таким образом, что будущие ссылки на стек используют указатель стека пользователя, привилегированные инструкции не могут выполняться, а проверки доступа к памяти осуществляются с использова- использованием привилегий уровня пользователя. Затем выполнение продолжается со следующей инструкции в процессе пользователя.
74 Глава 3. Службы ядра 3.2. Системные вызовы Самым частым (после часов) исключением для ядра является запрос системного вызо- вызова. Потребности производительности системы требуют, чтобы ядро минимизировало накладные расходы при выполнении системного вызова. Обработчик системного вызова должен выполнить следующую работу. * Проверить, что параметры системного вызова размещены по действительным адресам пользователя, и скопировать их из адресного пространства пользователя в ядро. # Вызвать процедуру ядра, реализующую системный вызов. Обработка результатов В конечном счете системный вызов возвращается в вызывающий процесс либо успеш- успешно, либо неудачно. На архитектуре PC успех или неудача возвращаются в виде бита переноса в слове состояния программы пользовательского процесса: если он равен нулю, возвращение было успешным; в противном случае возвращение безуспешно. На многих машинах возвращаемые значения функций С передаются через регистр общего назначения (для PC регистр данных ЕАХ). Процедуры в ядре, которые реали- реализуют системные вызовы, возвращают значения, которые обычно связаны с глобальной переменной еггпо. После системного вызова обработчик системного вызова ядра оставляет это значение в регистре. Если системный вызов завершился неудачей, биб- библиотечная процедура С помещает это значение в еггпо и устанавливает в возвращае- возвращаемом регистре значение -1. Ожидается, что вызывающий процесс заметит значение в возвращаемом регистре и проверит еггпо. Механизм, включающий бит переноса и глобальную переменную еггпо, существует по историческим причинам, ведя начало от PDP-11. Есть две разновидности неудачного возвращения из системного вызова: те, когда процедуры ядра обнаруживают ошибку, и те, когда системный вызов прерывается. Наиболее обычным случаем является прерывание системного вызова, когда он усту- уступил процессор в ожидании события, которое может не возникать в течение длительно- длительного времени (такого, как ввод с терминала), и в этот промежуток поступает сигнал. Когда обработчики сигнала инициализированы процессом, они указывают, должны ли прерванные ими системные вызовы запускаться повторно, или они должны возвратить ошибку прерванного системного вызова (EINTR). Когда системный вызов прерван, сигнал доставляется процессу. Если процесс запросил, чтобы сигнал прекращал системный вызов, обработчик возвращает ошибку, как описано ранее. Однако, если системный вызов должен быть запущен повторно, обработчик восстанавливает счетчик команд для указания на инструкцию, которая вызы- вызывала исключение системного вызова для ядра. (Это изменение необходимо, поскольку
3.2. Системные вызовы 75 значение счетчика команд, которое было сохранено при исключении системного вызова, содержит указатель на инструкцию после инструкции вызова исключения.) Обработчик замещает сохраненное значение счетчика команд этим адресом. Когда процесс возвращается из обработчика сигнала, он возобновляет выполнение со значения счетчика команд, предоставленного обработчиком, и повторно выполняет системный вызов. Повторный запуск системного вызова путем установки счетчика команд имеет некоторые последствия. Во-первых, ядро не должно изменять какие-либо входные параметры в адресном пространстве процесса (оно может изменить копию параметров в ядре, которые оно делает). Во-вторых, оно должно убедиться, что системный вызов не совершил каких-нибудь действий, которые нельзя повторить. Например, в текущей системе, если с терминала были прочитаны какие-либо символы, результат чтения должен возвращаться с коротким числом. В противном случае, если вызов должен был быть запущен снова, уже прочитанные байты были бы потеряны. Возвращение из системного вызова Пока системный вызов обрабатывается или ожидает с заблокированными сигналами, процессу может быть отправлен сигнал или другой процесс может получить больший приоритет планирования. После завершения системного вызова код выхода из системного вызова проверяет, имело ли место какое-нибудь событие. Код завершения системного вызова проверяет сначала посланные сигналы. Такие сигналы включат сигналы, которые прерывают системный вызов, а также сигналы, которые поступили при обработке системного вызова, но были задержаны до заверше- завершения системного вызова. Игнорируемые сигналы, по умолчанию или по явному про- программному запросу, никогда не посылаются процессу. Сигналы с действием по умолча- умолчанию выполняют это действие до того, как процесс будет запущен снова (т.е. процесс может быть по обстановке остановлен или завершен). Если сигнал должен быть пере- перехвачен (и не блокирован в настоящий момент), код завершения системного вызова организует вызов обработчика соответствующего сигнала вместо непосредственного возвращения процесса из системного вызова. После возвращения из обработчика сиг- сигнала процесс возобновит выполнение возвращения из системного вызова (или выпол- выполнение системного вызова, если последний должен быть возобновлен). После проверки отложенных сигналов код завершения системного вызова проверяет, нет ли какого-либо процесса с приоритетом выше, чем у текущего выполняющегося про- процесса. Если такой процесс существует, код завершения системного вызова вызывает про- процедуру переключения контекста, чтобы запустить процесс с более высоким приорите- приоритетом. Позже у текущего процесса снова будет самый высокий приоритет, и он возобновит исполнение, возвратившись из системного вызова в процесс пользователя.
76 Глава 3. Службы ядра Если процесс запросил профилирование системы, код завершения системного вызова вычисляет также время, потраченное на системный вызов, т.е. системное время, начисленное процессу между вхождением и выходом последнего из ядра. Это время назначается процедуре в процессе пользователя, который сделал системный вызов. 3.3. Исключения и прерывания Исключения Исключения, подобно системным вызовам, происходят для процесса синхронно. Исключения обычно возникают вследствие ненамеренных ошибок, таких, как деление на ноль или ссылка через недействительный указатель. Процесс узнает о проблеме, либо перехватив сигнал, либо путем завершения. Исключения могут возникнуть также вследствие отказа страницы, в этом случае система делает страницу доступной и повторно запускает процесс, не давая ему знать о произошедшем отказе. Обработчик исключения вызывается подобно обработчику системного вызова. Сначала сохраняется состояние процесса. Потом обработчик исключения определяет тип исключения, а затем организует посылку сигнала или загрузку страницы, в зависи- зависимости от ситуации. В заключение он проверяет наличие ожидающих сигналов или про- процессов с более высоким приоритетом и завершается аналогично обработчику систем- системного вызова, за тем исключением, что возвращаемое значение отсутствует. Прерывания от устройств ввода/вывода Прерывания от устройств ввода/вывода и других устройств обрабатываются процедура- процедурами прерываний, которые загружаются в виде части адресного пространства ядра. Эти процедуры обрабатывают интерфейс терминала консоли, одни или несколько часов и несколько программно инициируемых прерываний, используемых системой для обработки часов с низким приоритетом и для сетевых возможностей. В отличие от исключений и системных вызовов прерывания от устройств проис- происходят асинхронно. Процесс, запросивший обслуживание, скорее всего не будет теку- текущим и может даже больше не существовать! Процесс, начавший операцию, будет уведомлен, что операция завершилась, когда он будет вновь запущен. Как и в случае с исключениями и системными вызовами, должно быть сохранено все состояние машины, поскольку любые изменения могли бы вызвать ошибки в текущем выпол- выполняющемся процессе. Обработчики прерываний устройств запускаются лишь по требованию. В отличие от более ранних версий FreeBSD ядро FreeBSD 5.2 создает контекст для каждого драй- драйвера устройства. Таким образом, точно так же как один процесс не может получить доступ к контексту ранее работавшего процесса, обработчики прерываний не могут
3.3. Исключения и прерывания 77 получить доступ к какому-либо из контекстов ранее выполнявшихся обработчиков прерываний. Стек, обычно использующийся ядром, является частью контекста процесса. Поскольку у каждого устройства есть свой собственный контекст, у него есть также свой стек, с которым он работает. Прерывания в ранних системах FreeBSD не имели контекста, поэтому они должны были выполняться до своего завершения, не входя в состояние сна. В FreeBSD 5.2 прерывания могут блокироваться в ожидании ресурсов. Однако в блокированном состоянии они не могут вызваться другим событием, поэтому для снижения вероятно- вероятности потерь прерываний большинство обработчиков по-прежнему работают до своего завершения без ожиданий. Обработчик прерываний никогда не извлекается из верхней половины ядра. Таким образом, он должен получить всю информацию, которая ему нужна, из структур дан- данных, которые он разделяет с верхней половиной ядра - обычно своей глобальной ра- рабочей очереди. Аналогично вся информация, предоставленная верхней половине ядра обработчиком прерывания, должна передаваться таким же способом. Программные прерывания Многие события ядра являются управляемыми аппаратными прерываниями. Для высо- высокоскоростных устройств, таких, как сетевые контроллеры, этим прерываниям назнача- назначается высокий приоритет. Сетевой контроллер должен быстро подтвердить получение пакета и отменить запрет на получение дополнительных пакетов, чтобы избежать потерь близко расположенных пакетов. Однако дальнейшая обработка передачи пакета принимающим процессом, хотя и занимает время, не обязательно должна осуществ- осуществляться сразу же. Таким образом, дальнейшую обработку можно вести с более низким приоритетом, чтобы выполнение критических операций не блокировалось на более продолжительное время, чем это необходимо. Механизм для осуществления обработки с более низким приоритетом называется программным прерыванием. Обычно прерывания с высоким приоритетом создают очередь для выполнения работы на более низком уровне приоритета. Как и с аппарат- аппаратными устройствами в FreeBSD 5.2, у каждого программного прерывания есть связан- связанный с ним контекст процесса. Процессам программных прерываний назначают обычно более низкие приоритеты планирования, чем процессам драйверов устройств, но более высокие приоритеты, чем процессам пользователя. Всякий раз при появлении аппаратного прерывания процесс, связанный с драйвером устройства, достигнет наи- наивысшего приоритета и будет назначен для выполнения. Когда нет готовых к выполне- выполнению процессов драйверов устройств, для выполнения будет назначен процесс про- программного прерывания с наивысшим приоритетом. Если нет готовых для выполнения процессов программных прерываний, будет запущен процесс пользователя с наивысшим
78 Глава 3. Службы ядра приоритетом. Если при возникновении прерывания, которое делает готовым к испол- исполнению процесс драйвера устройства, выполняется либо процесс программного преры- прерывания, либо процесс пользователя, планировщик их вытеснит, чтобы запустить про- процесс драйвера устройства. Доставка сетевых пакетов процессу назначения управляется функцией обработки пакетов, которая работает с более низким приоритетом, чем драйвер устройства сете- сетевого контроллера. По мере поступления пакетов они помещаются в рабочую очередь, и контроллер немедленно становится доступным снова. Между доставками пакетов работает процесс обработки пакетов, доставляющий их. Таким образом, контроллер может принимать новые пакеты без необходимости ждать доставки предыдущего пакета. Дополнительно к обработке сети программные прерывания используются для обработки событий, связанных с временем и перепланировкой процессов. 3.4. Прерывания от часов Система управляется часами, которые генерируют прерывания через регулярные ин- интервалы времени. Каждое прерывание называется тиком. На PC тики возникают 100 раз в секунду. С каждым тиком система обновляет текущее время дня, а также таймеры процессов пользователей и системные таймеры. Прерывания для тиков часов посылаются в виде высокоприоритетных аппаратных прерываний. После переключения на процесс устройства часов вызывается процедура hardclockQ. Важно, чтобы процедура hardclockQ делала работу быстро. Если hardclockQ работает в течение более одного тика, она потеряет следующее прерывание от часов. Поскольку hardclockQ поддерживает для системы время дня, потерянное прерывание вызовет отставание времени системы. * Из-за высокого приоритета прерывания hardclockQ почти вся остальная деятель- деятельность в системе блокируется на время выполнения hardclockQ. Это блокирование может вызвать потерю пакетов сетевыми контроллерами или потерю передачи сектора под головкой диска дисковым контроллером. Поскольку затрачиваемое в hardclockQ время минимизируется, менее критичная относящаяся ко времени обработка выполняется обработчиком программного прерывания softclockQ с более низким приоритетом. К тому же, если доступно несколько часов, часть связанной со временем обработки может осуществляться другими процедурами, поддерживаемыми альтернативными часами. На PC имеются двое дополнительных часов, работающих с отличающимися от системных часов частотами: statclockQ, работающая со 128 тиками в секунду для сбора статистики системы, nprofclockQ, работающая с частотой 1024 тика в секунду для сбора профи- профилирующей информации.
3.4. Прерывания от часов 79 Работа, выполняемая hardclockQ, включает в себя следующее. * Если у текущего выполняющегося процесса есть виртуальный или профи- профилирующий интервальный таймер (см. раздел 3.6), уменьшить значение таймера и доставить сигнал, если время таймера вышло. * Увеличить текущее время дня. * Если в системе нет отдельных часов для обработки профилирования, процедура hardclockQ выполняет действия, обычно осуществляемые profclockQ, как описано в следующем разделе. * Если в системе нет отдельных часов для сбора статистики, процедура hardclockQ выполняет операции, обычно осуществляемые statclockQ, как описано в сле- следующем разделе. * Если нужно запустить softclockQ, подготовить к запуску процесс softclock. Статистика и планирование процессов На старых системах FreeBSD процедура hardclockQ собирала статистику использова- использования ресурсов о том, что происходило при прерывании от часов. Эта статистика исполь- использовалась для учета ресурсов, отслеживания деятельности системы и определения буду- будущих приоритетов планирования. Кроме того, hardclockQ форсировала переключения контекстов так, чтобы процессы могли разделять время процессора. У этого подхода есть недостатки из-за регулярных прерываний часов, поддержи- поддерживающих hardclockQ. Процессы могут синхронизироваться с системными часами, что приведет к неточному измерению использования ресурсов (особенно центрального процессора) и неточному профилированию [McCanne & Torek, 1993]. Можно также на- написать программы, которые сознательно синхронизируются с системными часами, чтобы обмануть планировщик. На архитектурах с несколькими высокоточными программируемыми часами (таких, как PC) часы для статистики работают с частотой, отличающейся от частоты основных часов. Процедура statclockQ FreeBSD работает с частотой 128 тиков в секунду и отвечает за сбор использования ресурсов для процессов. С каждым тиком она добавляет теку- текущему выполняющемуся процессу один тик; если процесс накопил четыре тика, повторно вычисляет его приоритет. Если новый приоритет меньше, чем текущий, орга- организует перепланировку процесса. Таким образом, для процессов, синхронизировав- синхронизировавшихся с системными часами, по-прежнему ведется учет процессорного времени. Процедура statclockQ также собирает статистику о том, что система делает в момент тика (находится в бездействии, выполняется в режиме пользователя или выполняется в системном режиме). Наконец, она.собирает основную информацию о системном вводе/выводе (наример, какие диски в данный момент активны).
80 Глава 3. Службы ядра Для поддержки сбора более точной профилирующей информации FreeBSD под- поддерживает часы профилировки. Когда один или более процессов запрашивают информацию о профилировке, профилирующие часы запускаются с частотой, относи- относительно превосходящей частоту основных системных часов A024 тика в секунду на PC). При каждом тике они проверяют, работает ли один из процессов, который нужно проверять. Если да, получают текущее значение счетчика команд и уве- увеличивают счетчик, связанный с этим местом в буфере профилирования, связанном с процессом. Тайм-ауты Оставшаяся часть относящейся ко времени обработки включает обработку запросов тайм-аутов и периодическое изменение приоритетов готовых к выполнению процессов. Эти функции обрабатываются процедурой softclockQ. Когда hardclockQ завершается, если было бы нужно выполнить какие-нибудь функции softclockQ, hardclockQ назначает для выполнения процесс softclock. Основной задачей процедуры softclockQ является организация выполнения следующих периодических событий. Таймер реального времени процесса (см. раздел 3.6). Повторная передача уничтоженных сетевых пакетов. Сторожевые таймеры периферии, требующие отслеживания. Системные события перепланировки процессов. Важным событием является планировка, которая периодически повышает или понижает приоритет для каждого процесса в системе, основываясь на прошлом использовании этим процессом времени процессора (см. раздел 4.4). Вычисление перепланировки осуществляется один раз в секунду. Планировщик запускается во время начальной загрузки и каждый раз после запуска назначает свой повторный запуск в будущем через 1 секунду. На сильно загруженной системе с множеством процессов планировщику для заверше- завершения работы может потребоваться длительное время. Назначение следующего вызова через 1 секунду после каждого завершения может вызвать осуществление планировки с частотой реже чем один раз в секунду. Однако, поскольку планировщик не отвечает за критичные по отношению ко времени функции, такие, как поддержание времени дня, планировка с частотой менее одного раза в секунду обычно не является проблемой. Структура данных, описывающая ожидающие события, называется очередью меток (callout queue). На рис. 3.2 показан пример очереди меток. Когда процесс пла- планирует событие, он указывает функцию, которая должна быть вызвана, указатель, который должен быть передан в качестве аргумента функции, и число тиков часов до момента, когда должно появиться событие.
3.4. Прерывания от часов 81 Сейчас+ 199 Сейчас Сейчас + 1 Сейчас + 2 Сейчас + 3 Сейчас f(x) Сейчас+ 200 g(y) Сейчас f(z) Сейчас+2 h(a) РИС. 3.2. События таймера в очереди меток Ядро поддерживает массив заголовков очередей, каждая из которых представляет определенное время. Имеется указатель, ссылающийся на заголовок очереди текущего вре- времени, помеченного на рис. 3.2 как «сейчас». Заголовок очереди, следующей за текущей, представляет события, отстоящие на один тик в будущем. За ним следует заголовок очере- очереди, который находится на два тика в будущем. Список замыкается в кольцо, так что если заголовок последней очереди в списке представляет время t, тогда заголовок первой в списке очереди представляет время t + 1. Заголовок очереди, непосредственно предшест- предшествующий текущему, представляет самое отдаленное время в будущем. На рис. 3.2 имеется 200 заголовков очередей, так что заголовок очереди, непосредственно предшествующий помеченному «сейчас», представляет события, отстоящие на 199 тиков в будущем. Каждый раз, когда запускается процедура hardclockQ, она увеличивает указатель заго- заголовка очереди меток. Если очередь не пуста, планируется для запуска процесс softclockQ. Процесс softclockQ сканирует события в текущей очереди. Он сравнивает текущее время со временем, хранящимся в структуре события. Если времена совпадают, событие удаляется из списка и вызывается запрошенная им функция с передачей ей указанного аргумента. Когда должно быть отправлено событие, отстоящее на п тиков в будущем, вычисляется заголовок очереди путем добавления п к индексу очереди, помеченному на рис. 3.2 «сейчас», и взятием остатка по модулю количества заголовков очередей. Если событие должно произойти еще дальше в будущем, чем число заголовков очереди, тогда оно ока- окажется в списке с другими событиями, которые должны случиться раньше. Таким образом, действительное время события хранится в его элементе таким образом, что когда очередь просматривается процедурой softclock(), она может определить, какие события являются текущими, а какие должны случиться в будущем. На рис. 3.2 второй элемент в очереди «сейчас» будет пропущен при текущем просмотре очереди, но он будет обработан про- процедурой softclockQ через 200 тиков в будущем при следующей обработке очереди.
82 Глава 3. Службы ядра Для функции очереди меток при ее вызове предусмотрен аргумент, чтобы одна функция могла использоваться несколькими процессами. Например, имеется одна функция таймера реального времени, которая посылает процессу сигнал, когда срок таймера истекает. Каждый процесс, в котором запущен таймер реального времени, посылает запросы тайм-аутов для этой функции; аргумент, который передается функции, является указателем на структуру процесса для этого процесса. Этот аргумент дает функции тайм-аута возможность доставить сигнал нужному процессу. Обработка тайм-аутов более эффективна, когда тайм-ауты указаны в тиках. Обнов- Обновления времени требуют лишь целых приращений, а проверка времени истечения тай- таймера требует лишь целого сравнения. Если бы таймеры содержали значения времени, уменьшения и сравнения их значений были бы более сложными. Подход, использован- использованный в FreeBSD, основан на работе [Varghese & Lauck, 1987]. Другим возможным под- подходом является поддержка кучи со следующим в очереди сообщением на ее вершине [Barkley& Lee, 1988]. 3.5 Службы управления памятью Организация и распределение памяти, связанные с процессом FreeBSD, показаны на рис. 3.3. Каждый процесс начинает выполнение с тремя сегментами памяти: кода (text), данных и стека. Сегмент данных делится на инициализированные данные и неини- неинициализированные данные (известные также как BSS). Сегмент text является сегментом только для чтения и обычно разделяется между всеми процессами, выполняющими файл, тогда как в области данных и стека может быть осуществлена запись и они индиви- индивидуальны для каждого процесса. Сегмент text и инициализированные данные для процесса считываются из исполняемого файла. Исполняемый файл отличается тем, что это обычный файл (а не каталог, специаль- специальный файл или символическая ссылка) и для него установлены один или более битов с разрешением доступа на исполнение. У каждого исполняемого файла есть заголовок exec, содержащий магическое число, обозначающее тип исполняемого файла. FreeBSD поддерживает несколько исполняемых форматов, включая следующие. 1. Файлы, которые должны читаться интерпретатором. 2. Файлы, которые могут исполняться непосредственно, включая AOUT, ELF и сжатый gzip ELF. Исполняемый файл вначале анализируется структурой активизации образа (imgact). Заголовок файла, который должен быть выполнен, передается по списку зарегистриро- зарегистрированных активаторов образа для обнаружения подходящего формата. Когда подходящий формат найден, соответствующий активатор образа подготавливает файл к выполнению.
3.5 Службы управления памятью 83 kern.ps_strings -н (обычно Oxbfbfffff) ps_strings структура Код сигнала Строки окружения (cnv) Предел размера стека Строки argv Указатели окружения (env) Указатели argv argc Стек пользователя Разделяемые библиотеки Куча bss Инициализированные данные text Образ исполняемого файла на диске Первая страница _^ (обычно 0x00001000) Процесс размещения образа в памяти Таблица идентификаторов Инициализированные данные text Заголовок elf Магическое число elf Рис. 3.3. Расположение процесса FreeBSD в памяти и на диске У файлов, попадающих в первую категорию, магическое число (занимающее 2 первых байта файла) представляет собой последовательность из двух символов «#!» , за которыми следует имя пути к интерпретатору, который должен быть использован. Это имя пути в настоящее время ограничено константой времени компиляции в 128 симво- символов. Например, #!/bin/sh ссылается на оболочку Борна. Активатор образа, который будет выбран, обрабатывает вызов интерпретаторов. Он загрузит и запустит указанный интерпретатор, передав в качестве аргумента имя файла, который должен быть интерпретирован. Для предотвращения зацикливаний FreeBSD допускает лишь один уровень интерпретации, и интерпретатор файла не может сам интерпретироваться. По соображениям производительности большинство файлов попадают во вторую категорию и являются непосредственно выполняемыми. Информация в заголовке непосредственно выполняемого файла включает архитектуру и операционную систе- систему, для которой исполняемый файл был создан, а также указывает, является ли он ста- статически скомпонованным или использующим разделяемые библиотеки. Выбранный активатор образа может использовать такую информацию, как знание операционной системы, для которой исполняемый файл был откомпилирован, чтобы отконфигурировать
84 Глава 3. Службы ядра ядро для использования соответствующей интерпретации системных вызовов при работе программы. Например, исполняемый файл, построенный для запуска под Linux, может безболезненно работать под FreeBSD с использованием вектора перена- перенаправления системных вызовов, предоставляющим эмуляцию системных вызовов Linux. В заголовке указаны также размеры сегментов text, инициализированных данных, неинициализированных данных и дополнительные сведения для отладки. Отладочная информация не используется ядром или выполняющейся программой. За заголовком следует образ сегмента text, за которым идет образ инициализированных данных. Неинициализированные данные не содержатся в исполняемом файле, поскольку они могут создаваться по требованию с использованием заполненной нулями памяти. Чтобы начать выполнение, ядро отображает кодовую часть файла в нижнюю часть адресного пространства, начиная со второй страницы виртуального адресного про- пространства. Первая страница виртуального адресного пространства помечается как недействительная, чтобы попытки читать или записывать с использованием пустого указателя вызывали ошибку. Часть файла с инициализированными данными отобража- отображается в адресное пространство сразу за кодом. После области инициализированных данных создается область с размером неинициализированных данных из заполненной нулями памяти. Стек также создается из заполненной нулями памяти. Хотя стек не обязательно должен быть заполнен нулями, старые системы UNIX делали именно так. В попытке несколько сократить время запуска в 4.2BSD разработчики изменили ядро таким образом, чтобы не обнулять стек, оставляя там предыдущее случайное содержи- содержимое страницы. Однако беспокойство по поводу тайных злоупотреблений данными ранее запущенных программ и невоспроизводимые ошибки ранее работавших про- программ привели к восстановлению обнуления стека ко времени выпуска 4.3BSD. Копирование в память полных областей кода и инициализированных данных боль- больших программ вызывает большую начальную задержку. FreeBSD избегает этого, используя для программы страничную подкачку по требованию вместо ее предвари- предварительной загрузки. При страничной подкачке по требованию программа загружается небольшими частями (страницами) по мере необходимости, а не вся сразу до начала выполнения. Система осуществляет страничную подкачку по требованию, разделяя адресное пространство на области равного размера, называемые страницами. Для каждой страницы ядро записывает смещение в исполняемом файле для соответст- соответствующих данных. Первая попытка доступа по адресу на каждой из страниц вызывает в ядре исключение отказа страницы. Обработчик отказа страницы считывает в память процесса нужную страницу исполняемого файла. Таким образом, ядро загружает лишь те части исполняемого файла, которые нужны. В главе 5 объясняются подробности страничной подкачки.
3.5 Службы управления памятью 85 Может показаться более эффективным загружать сразу весь процесс, чем множество небольших частей. Однако большинство процессов используют менее половины своего адресного пространства в течение всей продолжительности своего жизненного цикла. Причина такого незначительного использования в том, что у типичных команд пользова- пользователя множество опций, лишь немногие из которых используются при каждом конкретном запуске. Код и структуры данных, поддерживающие неиспользующиеся опции, не нужны. Таким образом, цена загрузки подмножества использующихся страниц ниже, чем цена первоначальной загрузки всего процесса. В дополнение ко времени, сэкономленно- сэкономленному за счет избежания загрузки всего процесса, подкачка по требованию снижает также количество физической памяти, необходимой для запуска процесса. Область неинициализированных данных может быть расширена заполненными нулями страницами с помощью системного вызова sbrk, хотя большинство пользова- пользовательских процессов используют библиотечную функцию mallocQ, более дружествен- дружественный для программиста интерфейс к sbrk. Эта выделенная память, которая растет от верхушки первоначального сегмента данных, называется кучей. На PC стек растет от верхушки памяти вниз, тогда как куча растет снизу памяти вверх. Выше стека пользователя находятся области памяти, которые создаются системой при запуске процесса. Непосредственно над стеком пользователя находится число аргументов (argc), массив указателей (вектор) аргументов (argv) и массив указателей (вектор) переменных окружения процесса (envp), устанавливаемых при выполнении программы. За ними следуют сами аргументы и строки переменных окружения. Далее находится код сигнала, использующийся при доставке системой процессу сигналов. На верхушке находится структура ps_strings, использующаяся ps для нахождения argv процесса. Изначально большинство исполняемых файлов компоновались статически. В статиче- статически скомпонованном двоичном файле все библиотечные процедуры и заглушки для системных вызовов загружались в двоичный файл во время компиляции. В настоящее время большинство двоичных файлов используют динамическое связывание. Двоичный файл с динамическим связыванием содержит лишь код откомпилированного приложе- приложения и список нужных процедур (библиотечных и заглушек системных вызовов). Когда исполняемый файл запускается, набор разделяемых библиотек, содержащих проце- процедуры, которые нужно использовать, отображается в его адресное пространство в ходе процесса загрузки. При первом вызове процедуры последняя находится в разделяемой библиотеке, и образуется динамическая связь с ней. Когда динамический загрузчик выполняет системный вызов ттар для выделения памяти для разделяемых библиотек, ядро должно найти место в адресном пространст- пространстве процесса, чтобы их разместить. В FreeBSD принято размещать их непосредственно под нижним административным ограничением для стека. Поскольку стек не может расти вниз ниже установленного административного предела, нет опасности перезапи- перезаписывания разделяемых библиотек. Побочным эффектом такой реализации является то, что после запуска двоичного файла нельзя безопасно изменить ограничение размера
86 Глава 3. Службы ядра стека. В идеале до запуска приложения другим процессом (таким, как оболочка) может быть установлен больший размер стека. Однако приложения, которые на момент запуска знают о том, что им потребуется стек большего размера, могут увеличить размер ограничения своего стека, а затем выполнить для себя системный вызов exec, чтобы перезапуститься с разделяемыми библиотеками, перемещенными ниже нового нижнего предела стека. В качестве альтернативы можно было бы расположить разделяемые библиотеки непосредственно над верхним пределом кучи. Однако это означало бы, что после запуска двоичного файла нельзя было бы увеличить предельный размер кучи. Поскольку при- приложениям значительно чаще приходится увеличивать размер своей кучи, чем размер стека, ограничение размера стека было выбрано в качестве подходящего места для разме- размещения разделяемых библиотек. Процессу требуется использовать некоторые глобальные системные ресурсы. Ядро поддерживает связанный список процессов, в котором имеется элемент для каждого процесса в системе. Помимо других данных в элементах процессов записывается информация о планировании и распределении виртуальной памяти. Поскольку из основ- основной памяти при подкачке на диск может быть сброшено все адресное пространство процесса, включая стек ядра для процесса, в элементе для процесса должно быть запи- записано достаточно информации для обнаружения процесса и закачки его обратно в память. Вдобавок информация, необходимая для сброшенного на диск процесса (т.е. информация о планировании) должна содержаться в элементе списка для процесса, а не в структуре пользователя, чтобы избежать подкачки процесса ядром лишь для выяснения того, что у процесса недостаточно высокий приоритет для запуска. Другие глобальные ресурсы, связанные с процессом, включают память для записи сведений о дескрипторах и таблицах страниц, в которых содержится информация об использовании физической памяти. 3.6. Службы времени Ядро предоставляет процессу несколько различных служб времени. Эти службы включают таймеры, работающие в реальном режиме, и таймеры, работающие лишь во время выполнения процесса. Реальное время Время системы, прошедшее с 1 января 1970 г., по Всеобщему скоординированному времени (Universal Coordinated Time - UTC), известное также как эпоха (Epoch), воз- возвращается системным вызовом gettimeofday. Большинство современных процессоров (включая процессоры PC) поддерживают регистр времени дня с батарейкой. Эти часы продолжают идти, даже когда процессор выключен. Когда система загружается, она использует регистр времени дня процессора для выяснения текущего времени. После
3.6. Службы времени 87 этого время системы поддерживается прерываниями от часов. При каждом прерыва- прерывании система увеличивает значение переменной глобального времени на величину, равную числу микросекунд в тике. Для PC, работающего со 100 тиками в секунду, каждый тик представляет 10 000 микросекунд. Внешнее представление Время всегда экспортируется из системы в виде микросекунд, а не тиков часов, для обеспечения независимого от разрешения формата. Внутренне ядро может выбрать любую частоту тиков, которая лучше всего справляется с издержками по обработке прерываний от часов с разрешением таймера. По мере увеличения частоты тиков в се- секунду разрешение системных таймеров улучшается, но увеличивается время, затрачи- затрачиваемое на обработку прерываний аппаратных часов. По мере ускорения процессоров частота тиков может возрастать для повышения разрешения без неблагоприятного влияния на приложения пользователей. Системы реального времени часто работают с частотой часов 500 или 1000 тиков в секунду. Все временные метки файловой системы (и других подсистем) содержат смещения UTC от начала эпохи. Преобразование в локальное время, включая учет перехода на летнее время, осуществляется извне системы в библиотеке С. Корректировка времени Часто желательно на всех машинах сети поддерживать одно и то же время. Можно также поддерживать более точное время, чем возможно от базовых часов процессора. Например, без труда доступно оборудование, прослушивающее ряд радиостанций, рассылающих в Соединенных Штатах синхронизирующие сигналы UTC. Когда про- процессы на различных машинах приходят к соглашению относительно общего времени, им нужно изменить часы процессора своего хоста, чтобы согласовать их с сетевым значением времени. Одной из возможностей является изменение системного времени на сетевое с помощью системного вызова settimeofday. К сожалению, системный вызов settimeofday приведет к смещению времени назад на машинах, часы которых спешат. Обратное смещение времени может запутать пользовательские программы (такие, как make), которые ожидают неизменного возрастания времени. Чтобы избежать этой про- проблемы, система предоставляет системный вызов adjtime [Mills, 1992]. Системный вызов adjtime принимает разницу во времени (положительную или отрицательную) и изменяет частоту приращения времени на 10 процентов быстрее или медленнее до тех пор, пока время не будет скорректировано. Операционная система осуществляет ускорение путем приращения глобального времени на 11 000 микросекунд для каждого тика, а замедление - путем приращения глобального времени на 9000 микросекунд для каждого тика. Независимо от этого, время возрастает монотонно и пользовательские
88 Глава 3. Службы ядра процессы, зависящие от порядка времени модификации файлов, не затрагиваются. Однако изменения времени, требующие для корректировки десятков секунд, повлияют на программы, измеряющие временные интервалы с помощью повторных вызовов gettimeofday. Интервальное время Система предоставляет каждому процессу три интервальных таймера. Таймер реаль- реального времени увеличивается в реальном времени. Примером использования этого тай- таймера является библиотечная процедура, поддерживающая очередь запуска служб. При истечении времени таймера процессу доставляется сигнал SIGALRM. Таймер реального времени запускается из очереди тайм-аутов, поддерживаемой процедурой softclockQ (см. раздел 3.4). Профилирующий таймер работает как в виртуальное время процесса (в режиме пользователя), так и во время работы системы от имени процесса. Он разработан для того, чтобы использоваться процессами для статистического профилирования своего выполнения. При истечении этого таймера процессу доставляется сигнал SIGPROF. Каждый раз при вызове profclockQ эта функция проверяет, не запросил ли текущий процесс профилирующий таймер; если это так, profclockQ уменьшает значение тай- таймера и посылает процессу сигнал, когда это значение достигает нуля. Виртуальный таймер уменьшает свое значение в виртуальное время процесса. Он работает лишь тогда, когда процесс выполняется в режиме пользователя. При истече- истечении таймера процессу доставляется сигнал SIGVTALRM. Виртуальный таймер так же реализован в profclockQ, как и профилирующий таймер, за тем исключением, что время таймера уменьшается для текущего процесса, лишь если он выполняется в режиме пользователя, а не в режиме ядра. 3.7. Идентификаторы пользователя, группы и другие идентификаторы Одной из важных задач операционной системы является реализация механизмов управления доступом. Большинство из этих механизмов управления доступом осно- основывается на представлениях об отдельных пользователях и группах пользователей. Пользователи обозначаются 32-разрядными числами, которые называются идентифи- идентификаторами пользователей (UID). UID назначаются не ядром, а внешним администра- административным представителем. UID являются основой для ведения учета, ограничения дос- доступа к привилегированным операциям ядра (таким, как запрос перезагрузки рабо- работающей системы), для решения того, каким процессам может быть послан сигнал, и в качестве основы для доступа к файловой системе и выделения дисковой памяти. Один пользователь, который называется суперпользователем (обычно с именем root), пользуется доверием системы, и ему разрешается осуществлять любую поддерживаемую
3.7. Идентификаторы пользователя, группы и другие идентификаторы 89 ядром операцию. Суперпользователь идентифицируется не по какому-либо определенному имени, такому, как root, но по UID, равному нулю. Пользователи организованы в группы. Группы обозначаются 32-разрядными чис- числами, которые называются идентификаторами группы (GID). GID, подобно UID, используются в средствах управления доступом к файловой системе и при выделении дисковой памяти. Состояние каждого процесса FreeBSD включает UID и набор GID. Привилегии процесса для доступа к файловой системе определяются UID и несколькими GID про- процесса (для иерархии файловой системы, начинающейся с корневого каталога процесса). Обычно эти идентификаторы автоматически наследуются от родительского процесса при создании нового процесса. Лишь суперпользователь может изменять действитель- действительный UID или действительный GID процесса. Такая схема проводит в жизнь строгое дробление привилегий и гарантирует, что ни один пользователь, кроме суперпользова- суперпользователя, не может получить привилегии. У каждого файла есть три набора битов прав доступа - для чтения, записи и испол- исполнения для владельца, группы и всех остальных. Эти биты прав доступа проверяются в следующем порядке. 1. Если UID файла такой же, как UID процесса, применяются лишь права доступа вла- владельца; права доступа для группы и остальных не проверяются. 2. Если UID не совпадают, но GID файла совпадает с одним из GID процесса, приме- применяются лишь права доступа для группы; права доступа для владельца и остальных не проверяются. 3. Лишь в том случае, когда UID и все GID процесса не совпали с соответствующими значениями файла, проверяются права доступа для всех остальных. Если эти права доступа не дают разрешения для запрошенной операции, она завершается неудачей. UID и GID процесса наследуются от его родителя. Когда пользователь регистриру- регистрируется, программа регистрации (см. раздел 14.5) устанавливает UID и GID до системного вызова exec для запуска первичной оболочки (login shell) пользователя; таким образом, все последующие процессы унаследуют соответствующие идентификаторы. Часто пользователю нужно предоставить дополнительные ограниченные привиле- привилегии. Например, пользователь, который хочет отправить сообщение, должен иметь воз- возможность добавить сообщение в почтовый ящик другого пользователя. Разрешение записи в почтовый ящик назначения для всех пользователей дало бы возможность пользователю, не являющемуся владельцем ящика, изменять в нем сообщения (злона- (злонамеренно или неумышленно). Чтобы решить эту проблему, ядро допускает создание программ, которым предоставляются при их выполнении дополнительные привиле- привилегии. Программы, работающие с другим UID, называются программами с устанавли- устанавливаемым идентификатором пользователя (setuid); программы, работающие с дополни- дополнительными групповыми привилегиями, называются программами с устанавливаемым
90 Глава 3. Службы ядра идентификатором группы (setgid) [Ritchie, 1979]. Когда выполняется программа setuid, права доступа программы увеличиваются с включением UID, связанных с програм- программой. UID программы обозначается как эффективный UID процесса, тогда как перво- первоначальный UID процесса называется действительным UID. Аналогично выполнение программы setgid добавляет к правам доступа процесса содержащиеся в GID программы, и соответственно определяются эффективный GID и действительный GID. Системы могут использовать программы setuid и setgid для предоставления управ- управляемого доступа к файлам или службам. Например, программа, добавляющая сообщения в ящики пользователей, работает с привилегиями суперпользователя, что позволяет ей записывать в любой файл в системе. Таким образом, пользователям не нужны разреше- разрешения на запись в почтовые ящики других пользователей, но они могут быть нужны при запуске этой программы. Естественно, такие программы должны быть написаны аккуратно, чтобы предоставить лишь ограниченный набор возможностей! UID и GID сохраняются как часть состояния процесса. Исторически GID реали- реализуются как один выделенный GID (эффективный GID) и дополнительный массив GID, который логически рассматривался как один набор GID. В FreeBSD выделенным GID является первый элемент в массиве GID. Дополнительный массив имеет фиксирован- фиксированный размер A6 в FreeBSD), но он может быть изменен при перекомпилировании ядра. FreeBSD реализует возможность setgid путем установки в нулевом элементе мас- массива дополнительных групп процесса, который выполняет программу setgid, значения группы этого файла. После этого можно проверять права доступа, как если бы это был обычный процесс. Из-за дополнительной группы программа setgid может иметь воз- возможность доступа к большему количеству файлов, чем процесс пользователя, запус- запускающий программу без этой особой привилегии. Чтобы избежать потери привилегий, связанных с группой в нулевом элементе массива при запуске программы setgid, про- программа регистрации дублирует нулевой элемент массива в первом элементе массива при инициализации массива дополнительных групп пользователя. Таким образом, когда запускается программа setgid и модифицируется нулевой элемент, пользователь не теряет каких-либо привилегий, поскольку группа, содержавшаяся в нулевом эле- элементе массива, по-прежнему доступна в первом элементе массива. Возможность setuid реализована посредством изменения эффективного UID про- процесса с UID пользователя на UID выполняемой программы. Как и с setgid, механизм защиты разрешит теперь доступ без каких-либо изменений или особого знания, что программа исполняется как setuid. Поскольку у процесса может быть лишь один UID в одно и то же время, при запуске setuid может быть потеря некоторых привилегий. Предыдущий действительный UID по-прежнему сохраняется в качестве действитель- действительного UID при установке нового эффективного U1D. Однако действительный U1D не исполь- используется для проверок прав доступа. Процессу setuid во время выполнения может понадобиться временно отменить свои особые привилегии. Например, ему может понадобиться особая привилегия для
3.7. Идентификаторы пользователя, группы и другие идентификаторы 91 доступа к ограниченному файлу лишь в начале своего выполнения. В ходе дальнейше- дальнейшего выполнения у него должны быть лишь привилегии действительного пользователя. В ранних версиях BSD отмена привилегий осуществлялась путем переключения дей- действительного и эффективного UID. Поскольку для управления доступом используется лишь эффективный UID, такой подход обеспечивал нужную семантику и место для сокры- сокрытия особых привилегий. Недостатком такого подхода было то, что действительный и эффективный UID легко можно было спутать. В FreeBSD для сохранения первоначальных прав программы setuid используется дополнительный идентификатор, сохраненный UID. Когда программа выполняет exec, ее эффективный UID копируется в ее сохраненный UID. Первая строка табл. 3.2 пока- показывает непривилегированную программу, для которой действительный, эффективный и сохраненный UID все содержат действительный UID пользователя. Во второй строке табл. 3.2 показана запущенная программа setuid, в качестве эффективного UID которой установлен связанный с ней UID с особыми привилегиями. UID с особыми привиле- привилегиями скопирован также в сохраненный UID. Табл. 3.2. Действия, влияющие на действительный, эффективный и сохраненный UID Действие Действительный Эффективный Сохраненный 1. exec обычный R R R 2. exec с setuid R S S 3.seteuid(R) R R S 4. seteuid(S) R S S 5.seteuid(R) R R S 6. exec обычный R R R Обозначения: R - действительный идентификатор пользователя; S - идентификатор пользователя с особыми привилегиями. Системный вызов seteuid устанавливает лишь эффективный UID; он не влияет на действительный или сохраненный UID. Системный вызов seteuid может устанавливать в качестве значения эффективного UID значения либо действительного, либо сохра- сохраненного UID. В строках 3 и 4 табл. 3.2 показано, как программа setuid может отказать- отказаться, а затем вернуть обратно свои особые привилегии, непрерывно сохраняя свой пра- правильный действительный UID. В строках 5 и 6 показано, как программа setuid может запустить подпроцесс, не предоставляя последнему особых привилегий. Сначала она устанавливает в качестве эффективного UID значение действительного UID. Затем, когда она вызывает exec для создания подпроцесса, эффективный UID копируется в сохра- сохраненный UID и доступ к UID с особыми привилегиями теряется. Аналогичный механизм сохраненного GID позволяет процессам переключаться между действительным GID и первоначальным эффективным GID.
92 Глава 3. Службы ядра Идентификаторы хостов Ядром определен дополнительный идентификатор для использования на машинах, действующих в сетевом окружении. Ядро хранит строку (до 256 символов), опреде- определяющую имя хоста. Подразумевается, что это значение уникально определено для каждой машины в сети. В дополнение в системе доменных имен Интернета каждой машине присваивается уникальный 32-разрядный номер. Использование этих иденти- идентификаторов дает приложениям возможность использовать уникальные в пределах сети идентификаторы для таких объектов, как процессы, файлы и пользователи, что полезно при конструировании распределенных приложений [Gifford, 1981]. Идентификаторы хоста для машин администрируются извне ядра. Группы процессов и сеансы Каждый процесс в системе связан с группой процессов. Группу процесса иногда назы- называют заданием (job), она управляется как единое целое такими процессами, как обо- оболочка. Некоторые сигналы (например, SIGINT) доставляются всем членам группы процессов, заставляя всю группу в целом приостанавливать или возобновлять выпол- выполнение, прерываться или завершаться. Сеансы были разработаны Рабочей группой IEEE POSIX. 1003.1 с целью исправить давнишнюю проблему безопасности в UNIX, а именно то, что процессы могли изме- изменять состояние терминалов, которым доверяли другие пользовательские процессы. Сеанс является совокупностью групп процессов, и все члены группы процессов яв- являются членами одного и того же сеанса. В FreeBSD, когда пользователь впервые реги- регистрируется в системе, он входит в новый сеанс. У каждого сеанса есть управляющий процесс, которым обычно является регистрационная оболочка пользователя. Все по- последующие процессы, созданные пользователем, являются частью группы процессов внутри этого сеанса, если он не создаст явным образом новый сеанс. У каждого сеанса есть также связанное с ним регистрационное имя, которое обычно является регистра- регистрационным именем пользователя. Это имя может изменить лишь суперпользователь. Каждый сеанс связан с терминалом, известным как управляющий терминал. У каждого управляющего терминала есть группа процессов, связанная с ним. Обычно из терми- терминала читают или записывают в него лишь процессы, входящие в текущую группу про- процессов терминала, позволяя разделять терминал между несколькими различными зада- заданиями. Когда управляющий процесс завершается, для всех оставшихся в сеансе про- процессов доступ к терминалу отбирается. Вновь созданным процессам назначаются ID процессов, отличающиеся от ID уже существующих процессов или групп процессов, и они помещаются в те группы про- процессов и сеансы, что и их родители. Любой процесс может установить в качестве группы своего процесса собственный ID процесса (создавая тем самым новую группу процессов) или значение любой группы процессов в своем сеансе. Вдобавок любой
3.8. Службы ресурсов 93 процесс может создать новый сеанс, если он уже не является лидером группы процес- процессов. Сеансы, группы процессов и связанные с ними темы обсуждаются далее в разделе 4.8 и в разделе 10.5. 3.8. Службы ресурсов У всех систем есть ограничения, налагаемые их аппаратной архитектурой и конфигурацией для обеспечения приемлемой деятельности и удержания пользователей от случайного (или злонамеренного) создания нехватки ресурсов. Как минимум, на работающие в системе процессы должны быть наложены ограничения аппаратного обеспечения. Обычно жела- желательно ограничить процессы и дальше, ниже уровня ограничений оборудования. Система оценивает использование ресурсов и дает возможность ограничить использование ресурсов на уровне или ниже ограничений аппаратного обеспечения. Приоритеты процессов Политика планирования по умолчанию в системе FreeBSD управляется общим пла- планировщиком, который предоставляет преимущество в распределении времени процес- процессора процессам, которые давно не использовали процессорное время. Эта схема при- приоритетов имеет тенденцию благоприятствовать процессам, которые выполняются в течение лишь коротких периодов времени, - например, интерактивным процессам. Приоритет, выбранный для каждого процесса, поддерживается ядром внутренне. На вычисление приоритета влияет имеющееся у каждого процесса значение относи- относительного приоритета (nice). Положительные значения относительного приоритета означают, что процесс хочет получить меньше своей доли процессорного времени1. Отрицательные значения относительного приоритета означают, что процесс хочет больше своей доли процессорного времени. Большинство процессов работают со значением относительного приоритета ноль, не запрашивая ни больше, ни меньше процессорного времени. Можно определить или изменить относительный приори- приоритет, назначенный в настоящее время процессу, группе процессов или процессам опре- определенного пользователя. На планирование оказывают влияние многие факторы помимо относительного приоритета, включая время процессора, недавно использо- использованное процессом, количество памяти, которое процесс недавно использовал, и текущую загрузку системы. В дополнение к описанному здесь общему планировщику в системе FreeBSD дос- доступен также планировщик реального времени. Планировщик реального времени дает процессам возможность точно управлять своим порядком исполнения и количеством времени, данным каждому процессу. Подробности общего алгоритма планирования и алгоритма планирования реального времени описаны в разделе 4.4. 1 nice переводится как «милый», «любезный». Процесс с положительным значением nice любезно предоставляет другим процессам больше времени для работы. -- Примеч. науч. ред.
94 Глава 3. Службы ядра Использование ресурсов В ходе выполнения процесса он использует системные ресурсы, такие, как время процес- процессора и память. Ядро отслеживает ресурсы, используемые каждым процессом, и состав- составляет статистику, описывающую это использование. Управляемая ядром статистика доступна процессу во время его выполнения. Когда процесс завершается, статистика становится доступной родителю через семейство системных вызовов wait. Ресурсы, используемые процессом, возвращаются системным вызовом getrusage. Можно запросить ресурсы, используемые текущим процессом или всеми завершивши- завершившимися потомками текущего процесса. Включаются следующие сведения: количество пользовательского и системного времени, использованное процессом; использование памяти процессом; * деятельность процесса по подкачке и дисковому вводу/выводу; число добровольных и принудительных переключений контекста, предпринятых процессом; число межпроцессных взаимодействий, осуществленных процессом. Информация об использовании ресурсов собирается в различных местах по всему ядру. Время использования процессора собирается функцией statclockQ, которая вызыва- вызывается либо системными часами в hardclockQ, либо, если доступны альтернативные часы, процессом прерывания альтернативных часов. Планировщик ядра вычисляет использо- использование памяти, замеряя количество используемой активным процессом памяти в то же самое время, когда он пересчитывает приоритеты процессов. Процедура vm_faultQ пере- пересчитывает деятельность по подкачке каждый раз, когда она начинает дисковую передачу для выполнения запроса на подкачку (см. раздел 5.11). Статистика деятельности по вводу/выводу собирается каждый раз, когда процесс должен начать передачу для выпол- выполнения запроса ввода/вывода для файла или устройства, а также когда вычисляется общая статистика для системы. Деятельность по межпроцессному взаимодействию обновляет- обновляется каждое время, когда эта информация отправляется или получается. Ограничения ресурсов Ядро поддерживает также ограничение для каждого процесса некоторых ресурсов. Эти ресурсы включают: * максимальное количество процессорного времени, которое можно набрать; * максимальное число байтов, которые процесс может запросить для блокирования в памяти; максимальный размер сегмента данных процесса; * Максимальный размер сегмента стека процесса;
3.8. Службы ресурсов 95 * максимальное количество частной физической памяти, которая может быть у про- процесса в любой данный момент времени; * максимальное количество частной или разделяемой физической памяти, которая может быть у процесса в любой данный момент времени; * максимальное количество физической памяти, которую процесс может выделить буферам сокетов; максимальный размер файла, который может быть создан процессом; * максимальный размер дампа памяти, который может быть создан процессом; максимальное число одновременно открытых для процесса файлов; * максимальное число одновременно запущенных процессов для одного пользова- пользователя. Для каждого ресурса, контролируемого ядром, поддерживаются два ограничения: мягкое ограничение и жесткое ограничение. Все пользователи могут изменять мягкое ограничение в диапазоне от 0 до соответствующего жесткого ограничения. Все пользо- пользователи могут (безвозвратно) уменьшить жесткое ограничение, но лишь суперпользова- суперпользователь может его увеличить. Если процесс превышает какое-нибудь из мягких ограниче- ограничений, процессу доставляется сигнал для его уведомления о том, что было превышено ограничение ресурсов. Обычно этот сигнал вызывает завершение процесса, но про- процесс может либо перехватить, либо игнорировать этот сигнал. Если процесс игнориру- игнорирует сигнал и отказывается освободить удерживаемые ресурсы, дальнейшие попытки по- получить дополнительные ресурсы будут завершаться ошибками. Ограничения ресурсов приводятся в исполнение обычно в местах сбора статисти- статистики использования ресурсов или рядом с ними. Ограничение использования времени процессора применяется в функции переключения контекста. Ограничения размеров сегментов стека и данных осуществляются путем неудачного завершения функций выделения по достижении этих ограничений. Ограничение размера файла приводится в исполнение файловой системой. Квоты файловой системы Кроме ограничений размеров отдельных файлов ядро по выбору может поддерживать ограничение на общее количество памяти, которое пользователь или группа могут использовать в файловой системе. Мы отложим обсуждение реализации этих ограниче- ограничений до раздела 7.4.
96 Глава 3. Службы ядра 3.9. Службы управления системой Есть несколько рабочих функций, которые должны иметь дело с запуском и выключе- выключением системы. Действия по загрузке описаны в разделе 14.2. Выключение системы описано в разделе 14.6. Учет использования ресурсов Система поддерживает простую форму учета использования ресурсов. По завершении каждого процесса учетная запись, описывающая использованные этим процессом ре- ресурсы, записывается в системный файл учета. Система предоставляет следующие све- сведения: имя команды, которая была запущена; количество использованного пользовательского и системного времени процессора; время запуска команды; время работы команды; среднее количество использованной памяти; # число осуществленных дисковых операций ввода/вывода; * UID и GID процесса; терминал, с которого процесс был запущен; флаги, сообщающие, выполнил ли процесс fork без exec, использовал привилегии суперпользователя, работал в режиме совместимости, делал дамп памяти и/или был завершен сигналом. Информация в учетную запись доставляется из статистики времени выполнения, которая была описана в разделе 3.8. Уровень модульности полей времени составляет 1/64 секунды. Для экономии места в учетном файле время сохраняется в 16-разряд- 16-разрядном слове в виде числа с плавающей точкой с использованием 3 битов в качестве вось- восьмеричного порядка, а оставшиеся 13 битов как дробную часть. По историческим причинам та же самая процедура преобразования числа с плавающей точкой обрабаты- обрабатывает число дисковых операций, поэтому число дисковых операций нужно умножить на 64 до его преобразования в представление с плавающей точкой. Суперпользователь запрашивает учет используемых ресурсов, передавая имя файла для использования при учете ядром. Как часть процесса завершения, ядро добавляет в файл учета учетную запись. Ядро не использует учетные записи; сводки и использование записей целиком относятся к области учетных программ уровня поль- пользователя. В качестве меры предохранения от превышения размера файловой системы из-за неконтролируемого роста файла учета система приостанавливает ведение учета,
Упражнения 97 когда в файловой системе остается лишь 2 процента свободного пространства. Учет возобновляется, когда в файловой системе окажется по крайней мере 4 процента сво- свободного места. У учетной информации есть определенные ограничения. Сведения об использова- использовании времени выполнения и памяти лишь приблизительные, поскольку они собираются статистически. Учетная информация записывается лишь при завершении процесса, поэтому процессы, продолжающие выполняться при внезапном отключении системы, не появляются в учетном файле. (Очевидно, к таким процессам относятся долгоживу- щие системные демоны.) Наконец, учетные записи не включают значительную часть сведений, необходимых для составлений точных счетов, включая использование других ресурсов, таких, как ленточные приводы и принтеры. Упражнения 3.1. Опишите три вида деятельности системы. 3.2. Когда процедура, выполняющаяся в верхней половине ядра, может быть вытеснена? Когда она может быть прервана? 3.3. Что мешает процедурам, выполняющимся в нижней половине ядра, исполь- использовать информацию, размещенную в текущих процессах пользователей? 3.4. Почему система переносит как можно большую часть работы из высо- высокоприоритетных прерываний в процессы программных прерываний с мень- меньшим приоритетом? 3.5. Что определяет самый короткий (ненулевой) период времени, который может запросить процесс при установке таймера? 3.6. Как ядро определяет системный вызов, для которого оно было вызвано? 3.7. Как представлены в исполняемом файле инициализированные данные? Как представлены в исполняемом файле неинициализированные данные? Почему они представлены различным образом? 3.8. Опишите, как может использоваться механизм «#!» для представления программ, которым требуется эмуляция, в виде обычных исполняемых фай- файлов. 3.9. Могут ли у файла быть такие права доступа, что его владелец не может его прочесть, даже если группа может? Возможна ли такая ситуация, если владе- владелец является членом группы, которая может читать файл? Объясните ваши ответы. *3.10. Опишите последствия для безопасности, вытекающие из незаполнения области стека во время запуска программы нулевыми значениями. *3.11 Почему преобразование из UTC в местное время осуществляется пользова- пользовательскими процессами, а не в ядре? *3.12. В чем преимущество того, что прерванный системный вызов повторно запус- запускает ядро, а не приложение?
98 Глава 3. Службы ядра *3.13. Опишите сценарий, при котором управляемый таймером алгоритм, исполь- используемый для очереди меток, не работает должным образом. Предложите для своего сценария альтернативную структуру данных, которая работает более быстро, чем алгоритм, управляемый таймером. *3.14. Профилирующий таймер SIGPROF первоначально предназначался для замены системного вызова profit для статистического сбора проб счетчика программы. Приведите два довода, почему возможности profil пришлось сохранить. **3.15. Какой недостаток в механизме учета процессов делает его неподходящим для использования в коммерческом окружении? Ссылки Barkley&Lee, 1988. R. E. Barkley & Т. P. Lee, "A Heap-Based Callout Implementation to Meet Real-Time Needs", USENIXAssociation Conference Proceedings, pp. 213-222, June 1988. Gifford, 1981. D. Gifford, "Information Storage in a Decentralized Computer System", PhD Thesis, Electrical Engineering Department, Stanford University, Stanford, CA, 1981. McCanne & Torek, 1993. S. McCanne & C. Torek, "A Randomized Sampling Clock for CPU Utilization Estima- Estimation and Code Profiling", USENIX Association Conference Proceedings, pp. 387-394, January 1993. Mills, 1992. D. L. Mills, "The NTP Time Synchronization Protocol", RFC 1305, доступный с http:/ /www.faqs.org/rfcs/rfcl305.html, March 1992. Ritchie, 1979. D. M. Ritchie, "Protection of Data File Contents", United States Patent, no. 4, 135, 240, United States Patent Office, Washington, DC, January 16, 1979. Assignee: Bell Tele- Telephone Laboratories, Inc., Murray Hill, NJ, Appl. No.: 377, 591, Filed: July 9, 1973. Varghese & Lauck, 1987. G. Varghese & T. Lauck, "Hashed and Hierarchical Timing Wheels: Data Structures for the Efficient Implementation of a Timer Facility", Proceedings of the Eleventh Sympo- Symposium on Operating Systems Principles, pp. 25-38, November 1987.
Часть II Процессы
Глава 4 Управление процессами 4.1. Введение в управление процессами Процесс представляет собой выполняющуюся программу. У процесса должны быть системные ресурсы, такие, как память и лежащий в основе процессор. Ядро поддержи- поддерживает иллюзию одновременного выполнения множества процессов, распределяя сис- системные ресурсы среди набора процессов, которые готовы к выполнению. На много- многопроцессорной машине несколько процессов могут действительно выполняться парал- параллельно. В данной главе описывается структура процесса, метод, который система ис- использует для переключения между процессами, и политика планирования, которую она использует для содействия совместному использованию процессора. В ней также представлены создание и завершение процесса и подробности возможностей сигналов и отладки процессов. Два месяца спустя после начала разработки первой реализации операционной сис- системы UNIX было два процесса: по одному для каждого терминала PDP-7. В возрасте 10 месяцев, по-прежнему на PDP-7, в UNIX было множество процессов, операция fork и что-то наподобие системного вызова wait. Процесс выполнял новую программу, счи- считывая ее поверх себя. Первая система PDP-11 (первое издание UNIX) увидела внедре- внедрение exec. Все эти системы допускали в одно и то же время лишь один процесс в памяти. Когда была получена PDP-11 с управлением памятью (KS-11), система была изменена, для того чтобы несколько процессов одновременно могли оставаться в памяти с целью уменьшения подкачки. Но это изменение не применялось к многозадачному програм- программированию, поскольку дисковый ввод/вывод был синхронным. Такое положение дел просуществовало до 1972 г. и первой системы PDP-11/45. В конечном счете было введено настоящее многозадачное программирование, когда система была переписана на С. Дисковый ввод/вывод для одного процесса мог при этом продолжаться во время
102 Глава 4. Управление процессами работы другого процесса. Базовая структура управления процессами в UNIX с того времени не изменилась [Ritchie, 1988]. Процесс работает либо в режиме пользователя, либо в режиме ядра. В режиме пользователя процесс выполняет код приложения, когда машина находится в неприви- непривилегированном режиме защиты. При запросе процессом служб операционной системы через системный вызов он переключается посредством защитного механизма в приви- привилегированный режим защиты машины, а затем действует в режиме ядра. Ресурсы, используемые процессом, аналогичным образом разделены на две части. Ресурсы, необходимые для выполнения в режиме пользователя, определяются архи- архитектурой процессора и обычно включают регистры общего назначения процессора, счетчик команд, регистр состояния процессора и регистры, связанные со стеком, а также содержимое сегментов памяти, которые составляют представление FreeBSD о программе (сегменты текста (кода), данных, разделяемых библиотек и стека). Ресурсы уровня ядра включают ресурсы, необходимые для нижележащего обору- оборудования, такие, как регистры, счетчик команд и указатель стека, а также все то, что не- необходимо ядру FreeBSD для предоставления некоторому процессу системных служб. Это состояние ядра включает параметры текущего системного вызова, идентичность пользователя текущего процесса, сведения для планирования и т.д. Как описано в разделе 3.1, состояние ядра для каждого процесса подразделено на несколько отдель- отдельных структур данных, главные из которых: структура процесса и структура пользо- пользователя. Структура процесса содержит сведения, которые должны постоянно находиться в основной памяти вместе со ссылками на другие резидентные структуры, тогда как структура пользователя содержит информацию, которая должна быть резидентной лишь при выполнении процесса (хотя структуры пользователя других процессов также могут быть резидентными). Структуры пользователя динамически выделяются по- посредством средств управления памятью. Исторически в структуре пользователя храни- хранилось более половины состояния процесса. В FreeBSD структура пользователя исполь- используется лишь для пары структур, на которые ссылается структура процесса. Структуры процесса динамически выделяются как часть создания процесса и освобождаются как часть завершения процесса. Многозадачное программирование Система FreeBSD поддерживает прозрачное многозадачное программирование: иллюзию параллельного выполнения нескольких процессов или программ. Она делает это путем переключения контекста, т.е. путем переключения между контекстами выполнения процессов. Предусмотрен также механизм для планирования выполнения процессов, т.е. для решения того, какой из них будет выполняться следующим. Преду- Предусмотрены возможности для обеспечения согласованного доступа к структурам данных, которые разделяются между различными процессами.
4.1. Введение в управление процессами 103 Переключение контекста является аппаратно-зависимой операцией, на реализа- реализацию которой влияют возможности лежащего в основе оборудования. В некоторых архитектурах предусмотрены машинные инструкции для сохранения и восстановле- восстановления аппаратного контекста выполнения процесса, включая виртуальное адресное про- пространство. В других программное обеспечение должно собирать сведения о состоянии оборудования из различных регистров и сохранять их, затем загружать в эти регистры новое состояние оборудования. Все архитектуры должны сохранять и восстанавливать состояние программного обеспечения, используемое ядром. Переключение контекста осуществляется часто, поэтому увеличение скорости переключения контекста заметно снижает расходуемое ядром время и предоставляет больше времени для выполнения приложений пользователя. Поскольку большая часть работы по переключению контекста тратится на сохранение и восстановление рабоче- рабочего контекста процесса, снижение количества информации, необходимой для этого кон- контекста, является эффективным способом обеспечения более быстрых переключений контекста. Планирование Хорошее планирование процессов - сложная задача, которая зависит от видов выпол- выполняемых программ и целей политики планирования. Программы отличаются по объему вычислений и выполняемого ими ввода/вывода. Политики планирования обычно пытаются сбалансировать использование ресурсов со временем, которое требуется для завершения программы. В планировщике FreeBSD по умолчанию, который мы будем называть планировщиком с разделением времени, приоритеты процессов периодиче- периодически пересчитываются, основываясь на различных параметрах, таких, как количество использованного процессорного времени, количество удерживаемых или необходи- необходимых для выполнения ресурсов памяти и т.д. Некоторые задачи требуют более точного контроля над выполняемым процессом, что называется планированием реального вре- времени; оно должно гарантировать, что процессы завершат вычисление своих результа- результатов к определенному предельному сроку или в определенном порядке. Ядро FreeBSD реализует планирование реального времени с помощью отдельной очереди, отличной от использующейся для обычных процессов с разделением времени. Процесс с при- приоритетом реального времени не является предметом снижения приоритета и может вытесняться лишь другим процессом с равным или более высоким приоритетом реаль- реального времени. Ядро FreeBSD реализует также очередь процессов, работающих с при- приоритетом простоя (idle priority). Процесс с приоритетом простоя будет запущен лишь тогда, когда в очереди реального времени или с разделением времени нет готовых к выпол- выполнению процессов, и лишь если его приоритет простоя равен или больше приоритета других готовых к выполнению процессов с приоритетом простоя. Планировщик FreeBSD с разделением времени использует политику планирова- планирования на основе приоритетов, которая способствует первоочередному выполнению
104 Глава 4. Управление процессами интерактивных программ, таких, как текстовые редакторы, по сравнению с долго работающими заданиями пакетного типа. Интерактивные программы имеют тенден- тенденцию проявлять всплески вычислений, перемежающиеся с периодами бездеятельности или ввода/вывода. Политика планирования вначале назначает каждому процессу высокий приоритет выполнения и дает этому процессу возможность выполняться в течение фиксированного кванта времени. Процессы, выполняющиеся в течение своего кванта времени, снижают свой приоритет, тогда как процессам, которые усту- уступают процессор (обычно из-за выполнения ввода/вывода), разрешается сохранить свой приоритет. У бездействующих процессов приоритет повышается. Таким образом, задания, использующие большое количество процессорного времени, быстро снижают свой приоритет, тогда как интерактивные задания, которые большую часть времени бывают бездеятельными, остаются с высоким приоритетом, и, таким образом, когда они готовы к выполнению, они вытесняют долго работающие задания с меньшими приоритетами. Интерактивная задача, така,я как текстовый редактор, занятый поиском строки, может на короткое время стать занятой вычислениями и таким образом сни- снизить приоритет, но она вернется к высокому приоритету, когда снова станет пассивной во время обдумывания пользователем результатов. Некоторые задачи, такие, как компиляция большого приложения, могут осуществ- осуществляться за несколько небольших шагов, когда каждый компонент компилируется в отдель- отдельном процессе. Ни один отдельный шаг не выполняется достаточно долго для снижения его приоритета, поэтому компиляция в целом сильно влияет на интерактивные про- программы. Чтобы обнаружить и не допустить эту проблему, приоритет разделения време- времени порожденного процесса передается обратно его родителю. Когда запускается новый порожденный процесс, он начинает работу с текущего приоритета своего родителя. Таким образом, поскольку программа, координирующая компиляцию (обычно make), запускает несколько шагов компиляции, ее приоритет снижается из-за интенсивного использования процессора ее потомками. Позже шаги компиляции, запущенные make, начинают работать и находиться на более низком уровне приоритета, что дает интерак- интерактивным программам с более высоким приоритетом получить при желании преимущество перед первыми. Политика планирования нужна системе также для работы с проблемами, возникающи- возникающими от недостатка основной памяти для содержания контекста выполнения всех процессов, которые хотят выполняться. Главной целью этой политики планирования является миними- минимизация пробуксовки (thrashing) - феномена, возникающего при таком малом количестве ос- основной памяти, что большая часть времени системы тратится на обработку отказов страниц и планирование процессов, чем на выполнение кода приложения в режиме пользователя. Система должна находить и устранять пробуксовку. Она обнаруживает пробуксовку, отслеживая количество свободной памяти. Когда у системы мало свободных страниц памяти и высока частота запросов новой памяти, она считает, что находится в состоянии пробуксовки. Система снижает пробуксовку, помечая давно не запускавшиеся процессы как запрещенные для запуска. Эта отметка дает демону подкачки возможность сбросить
4.2. Состояние процесса 105 все страницы, связанные с этим процессом, во вторичное хранилище. На большинстве архитектур ядро может сбросить во вторичное хранилище также и структуру пользователя отмеченного процесса. Результатом этих действий является сброс процесса на диск (см. раздел 5.12). Память, освобожденная путем блокирования процесса, может после этого быть распределена между оставшимися процессами, которые обычно могут затем продолжать работать. Если пробуксовка продолжается, выбираются дополнительные про- процессы для блокирования их выполнения, пока для эффективной работы оставшихся про- процессов не окажется достаточно памяти. В конечном счете с освобождением памяти за- завершается достаточное количество процессов, и заблокированные процессы могут возоб- возобновить свое выполнение. Однако, даже если памяти недостаточно, заблокированным про- процессам спустя примерно 20 секунд разрешено возобновить выполнение. Обычно состояние пробуксовки возвратится, что потребует выбрать для блокирования некоторые другие про- процессы (или предпринять административные действия для снижения загрузки). 4.2. Состояние процесса Каждому процессу в системе назначается уникальный идентификатор, который назы- называется идентификатором процесса (PID). PID является обычным механизмом, используемым приложениями и ядром для обозначения процессов. PID используется приложениями, когда последние посылают процессу сигнал и когда они получают статус завершения процесса. Для каждого процесса особенно важны два PID: PID самого процесса и PID его родительского процесса. Схема состояния процесса в FreeBSD 5.2 была полностью реорганизована. Целью этого являлась поддержка множества потоков, разделяющих общее адресное простран- пространство и другие ресурсы. На других системах потоки назывались также облегченными про- процессами. Поток является единицей выполнения процесса; ему требуются адресное про- пространство и другие ресурсы, но он может разделять многие из этих ресурсов с другими потоками. Потоки, разделяющие адресное пространство и другие ресурсы, планируются независимо, они все могут одновременно осуществлять системные вызовы. Реорганиза- Реорганизация состояния процессов в FreeBSD 5.2 была спроектирована для поддержки потоков, способных выбирать набор разделяемых ресурсов, которые известны как процессы с переменным весом [Aral et al., 1989]. Разработчики осуществили реорганизацию, переместив многие компоненты состояния процесса из структур процесса и пользователя в отдельные подструктуры для каждого типа информации о состоянии, как показано на рис. 4.1. Структура про- процесса прямо или косвенно ссылается на все подструктуры. Структура пользователя остается главным образом в виде исторического артефакта ради пользы отладчиков. Структура потока содержит только сведения, необходимые для работы в ядре: информацию о планировании, стек для использования в режиме ядра, блок управления
106 Глава 4. Управление процессами Элемент процесса Список потоков Группа процесса Сеанс Мандат (credential) Пространство виртуальной памяти I Дескрипторы файлов Список областей Элементы файлов Ограничения ресурсов Вектор системных вызовов Статистика Структура пользователя Информация о планировании Блок управления потоком Стек ядра потока Машинно-зависимая информация потока Информация о планировании Блок управления потоком Стек ядра потока Машинно-зависимая информация потока Поток Поток Рис. 4.1. Состояние процесса потоком (thread control block - ТСВ) и другое машинно-зависимое состояние. ТСВ определяется машинной архитектурой; он включает регистры общего назначения, указатели стека, счетчик команд, слово состояния процессора и регистры управле- управления памятью. В своей наиболее облегченной форме потоки FreeBSD разделяют все ресурсы про- процесса, включая PID. Когда необходимо дополнительное параллельное вычисление, новый поток создается с использованием системного вызова kse_create. Все планиро- планирование и управление потоками обрабатывается планировщиком уровня пользователя, который уведомляется о смене потоков через обратные вызовы из ядра. Менеджер по- потоков уровня пользователя должен также отслеживать пользовательские стеки, исполь- используемые каждым потоком, поскольку совместно используется все адресное пространст- пространство, включая области, обычно используемые для стека. Поскольку все потоки разде- разделяют общую структуру процесса, у них единый PID и соответственно они отображают- отображаются в списке ps в виде одного элемента. Многие приложения не хотят использовать совместно все ресурсы процесса. Системный вызов rfork создает элемент процесса, который разделяет выбранный набор ресурсов своего родителя. Обычно действия сигналов, статистика и части адресного пространства со стеком и данными совместно не используются. В отличие
4.2. Состояние процесса 107 от облегченных потоков, созданных с помощью kse_create, системный вызов rfork связывает с каждым потоком PID, который отображается в списке ps и которым можно управлять тем же способом, как для любого другого процесса в системе. Процессы, созданные с помощью fork, vfork или rfork, имеют единственную связанную с ними структуру потока. Структура процесса Кроме ссылок на подструктуры показанный на рис. 4.1 элемент процесса содержит следующие категории информации. * Идентификация процесса: PID и PID родителя. Состояние сигнала: ожидающие доставки сигналы, маска сигналов и сводка дей- действий сигналов. * Трассировка: сведения о трассировке процесса. * Таймеры: таймер реального времени и счетчики использования процессора. Подструктуры процесса, показанные на рис. 4.1, содержат следующие категории сведений. * Идентификация группы процессов: группа процесса и сеанс, к которому относится процесс. * Мандаты пользователя: действительный, эффективный и сохраненный идентифи- идентификаторы пользователя и группы. * Управление памятью: структура, описывающая выделение виртуального адресно- адресного пространства, используемого процессами; пространство виртуальной памяти (VM) и связанные с ним структуры более полно описываются в главе 5. * Дескрипторы файлов: массив указателей на элементы файлов, индексируемые открытыми дескрипторами файлов процесса, а также флаги открытых файлов и текущий каталог. * Вектор системных вызовов: отображение номеров системных вызовов на дейст- действия; кроме текущего и нерекомендуемого собственных форматов исполняемых файлов FreeBSD, ядро может запускать двоичные файлы, откомпилированные для нескольких других вариантов UNIX, таких, как Linux, 0SF/1 и System V Release 4, предоставляя альтернативные векторы системных вызовов при необходимости такого окружения. Учет ресурсов: структуры rlimit, описывающие использование многих ресурсов, предоставляемых системой (см. раздел 3.8).
108 Глава 4. Управление процессами Статистика: сведения, собранные при работе процесса, которые сообщаются при его завершении и записываются в файл учета; включает также таймеры процесса и информацию о профилировании, если она собиралась. * Действия сигналов: действия, которые должны быть предприняты, когда процессу посылается сигнал. Структура потока: содержание структуры потока (описанной в конце данного раздела). Табл. 4.1. Состояния процесса Состояние Описание NEW Создание процесса NORMAL Потоки будут в состояниях RUNNABLE (готовые к запуску), SLEEPING (спящие) или STOPPED (остановленные) ZOMBIE Завершение процесса Элемент состояния структуры процесса содержит текущее значение состояния процесса. Возможные значения состояния показаны в табл. 4.1. Когда процесс впервые создается с помощью системного вызова fork, он вначале помечается как NEW. Состояние изменяется на NORMAL, когда для процесса выделено достаточно ресурсов для начала его выполнения. С этого момента состояние процесса будет дальше колебаться в пределах NORMAL (при этом потоки будут либо RUNNABLE - т.е. готовые к выполнению или на самом деле выполняющиеся, SLEEPING - т.е. ожи- ожидающие события, или STOPPED -т.е. остановленные сигналом или родительским про- процессом) до завершения процесса. Завершившийся процесс помечается как ZOMBIE до тех пор, пока он не освободит ресурсы и не сообщит о своем статусе завершения своему родительскому процессу. Система организует структуры процессов в два списка. Элементы процессов нахо- находятся в списке zombproc, если процесс находится в состоянии ZOMBIE; в противном случае они находятся в списке allproc. Эти две очереди разделяют одни и те же свя- связующие указатели в структуре процесса, поскольку списки являются взаимоис- взаимоисключающими. Отделение завершившихся процессов от работающих снижает время, затрачиваемое как системным вызовом wait, который должен сканировать среди зомби потенциальных кандидатов на возвращение, так и планировщиком и другими функ- функциями, которые должны сканировать все готовые к выполнению процессы. Большинство потоков, за исключением текущего выполняющегося потока (или пото- потоков, если система многопроцессорная), также находятся в одной из двух очередей: очереди запуска или очереди ожидания. Потоки, находящиеся в готовом к выполнению состоя- состоянии, помещаются в очередь запуска, тогда как потоки, заблокированные в ожидании собы- события, размещаются в очереди ожидания. Остановленные потоки, ожидающие события,
4.2. Состояние процесса 109 | Процесс А| pjzhildren Процесс В p_children T^y\ 4_ pjpptr f~~~V р pptr PJ>Ptr РИС. 4.2. Иерархия групп процессов pjpptr P-PP"\ Л щ ^ Проиесг. Р L J Процесс Е pjsibling pjsibling находятся в очереди ожидания или не находятся ни в одной из очередей. Очереди запуска организованы в соответствии с приоритетами планирования потоков и описа- описаны в разделе 4.4. Очереди ожидания организованы в структуры данных, которые хешируются по идентификатору события. Такая организация оптимизирует обнаруже- обнаружение спящих потоков, которые необходимо пробудить при наступлении события. Очереди ожидания описаны в разделе 4.3. Указатель p_pptr и связанные списки (p_children и p_sibling) используются для нахождения связанных процессов, как показано на рис. 4.2. Когда процесс запускает порожденный процесс, последний добавляется к списку pjzhildren своего родителя. Порожденный процесс хранит также в своем указателе p_pptr обратную ссылку на своего родителя. Если у процесса есть несколько активных в одно и то же время порожденных процессов, последние связываются посредством своих элементов списка pjsibling. На рис. 4.2 процесс В является прямым потомком процесса А, тогда как про- процессы С, D и Е являются потомками процесса В и родственными по отношению друг к другу. Процессом В обычно является оболочка, запустившая конвейер (см. разделы 2.4 и 2.6), включающий процессы С, D и Е. Процесс А, возможно, мог бы быть процессом инициализации системы ink (см. разделы 3.1 и 14.6). Процессорное время выделяется потокам в соответствии с их классами планирова- планирования и приоритетами планирования. Как показано в табл. 4.2, ядро FreeBSD имеет два класса планирования ядра и три пользовательских класса планирования. Ядро всегда запускает поток, находящийся в классе с наивысшим приоритетом. Любые потоки прерывания ядра имеют преимущество при запуске по сравнению с другими, за ними следуют любые потоки верхней половины ядра. Любые готовые к выполнению потоки реального времени имеют преимущество перед потоками в классах с разделением вре- времени и ожидающих. Приоритеты потоков в классах реального времени и ожидания устанавливаются приложениями с использованием системного вызова rtprio и никогда не регулируются ядром. Приоритеты прерываний нижней половины устанавливаются при конфигурировании устройств и никогда не изменяются. Приоритеты верхней половины устанавливаются в соответствии с предопределенными приоритетами для каждой подсистемы ядра и никогда не изменяются.
110 Глава 4. Управление процессами Приоритеты потоков, работающих в классе разделения времени, регулируются ядром на основе использования ресурсов и недавнего использования процессора. У потока есть два приоритета планирования: один для планирования выполнения в режиме пользователя, а другой для планирования выполнения в режиме ядра. Поле kg_user_pri, связанное со структурой потока, содержит приоритет планирования режима пользователя, тогда как поле td_priority содержит текущий приоритет пла- планирования. Текущий приоритет может отличаться от приоритета режима пользователя, когда поток выполняется в верхней половине ядра. Приоритеты меняются в диапазоне от 0 до 255, причем чем ниже значение, тем выше приоритет (см. табл. 4.2). Приорите- Приоритеты режима пользователя колеблются от 128 до 255; приоритеты менее 128 используют- используются лишь, когда поток спящий, т.е. ожидает события в ядре, и немедленно после него поток пробуждается. Потокам в ядре предоставляются более высокие приоритеты, по- поскольку они обычно удерживают при пробуждении разделяемые ресурсы ядра. После получения ими ресурса система хочет выполнить их как можно скорее, чтобы они смогли использовать ресурс и вернуть его до того, как его запросит и заблокируется в его ожидании другой поток. Когда поток в ядре засыпает, он должен указать, нужно ли его пробудить и поме- пометить как готовый к выполнению, если ему будет послан сигнал. В FreeBSD поток ядра будет пробужден сигналом, лишь если на время сна он установит флаг РСАТСН. Интерфейс msleepQ также обрабатывает состояния сна, ограниченные максимальной продолжительностью времени и обработкой повторно запускаемых системных вызо- вызовов. Интерфейс msleepQ включает ссылку на строку, описывающую пробуждающее поток событие; эту строку можно увидеть извне, например в ps. Решение о том, ис- использовать ли прерываемый сон, зависит от того, как долго поток может быть забло- заблокированным. Поскольку сложно быть готовым к обработке сигналов в середине выпол- выполнения какой-либо другой операции, многие состояния сна являются непрерываемыми; т.е. выполнение потока не может быть запланировано до тех пор, пока не произойдет событие, которого он ожидает. Например, поток, ожидающий завершения дискового ввода/вывода, будет спать с заблокированными сигналами. Табл. 4.2. Классы планирования потоков Диапазон 0-63 64-127 128-159 160-223 224-255 Класс ITHD KERN REALTIME TIMESHARE IDLE Тип потока Нижняя часть ядра (прерывание) Верхняя часть ядра Пользовательский реального времени Пользовательский с разделением времени Пользовательский ожидания
4.2. Состояние процесса 111 Для быстро возникающих сигналов откладывание обработки сигнала до момента его завершения является незаметным. Однако запросы, которые могут вызвать засыпа- засыпание потока на длительный период, такие, как ожидание ввода с терминала или из сети, должны быть готовы к прерыванию своего сна таким образом, чтобы отправка сигна- сигналов не задерживалась на неопределенный срок. Потоки с прерываемым сном могут снимать свои системные вызовы из-за поступления сигнала до события, которого они ожидают. Для предотвращения постоянного удержания ресурса ядра эти потоки должны проверить, почему они были пробуждены. Если они были пробуждены из-за сигнала, они должны освободить удерживаемые ресурсы. Затем они должны вернуть ошибку, переданную им msleepf), которая будет EINTR, если после сигнала системный вызов должен быть отменен, или ERESTART, если системный вызов должен быть осу- осуществлен повторно. Иногда событие, предполагавшееся кратковременным, такое, как дисковый ввод/вывод, задерживается из-за аппаратного сбоя. Поскольку поток спит в ядре с заблокированными сигналами, он будет глух ко всем попыткам отправить сигнал, даже такой сигнал, который вызвал бы его безусловное завершение. Единственным решением этой проблемы является замена вызовов sleepQ для аппаратных событий таким образом, чтобы они стали прерываемыми. В оставшейся части данной книги мы будем всегда использовать sleepQ при ссылке на процедуру, переводящей поток в состояние сна, даже когда одним из исполь- использующихся интерфейсов является msleepQ. Структура потока Структура потока, показанная на рис. 4.1, содержит следующие категории информа- информации. * Планирование: приоритет потока, приоритет планирования режима пользователя, недавнее использование процессора и количество времени, проведенного в состоя- состоянии сна. Состояние потока: состояние выполнения потока (выполняющийся, спящий); дополнительные флаги состояния; если поток спит, канал ожидания, идентичность события, которого поток ожидает (см. раздел 4.3), и указатель на строку, описы- описывающую событие. * Машинное состояние: машинно-зависимая информация потока. * ТСВ: состояния выполнения режимов пользователя и ядра. * Стек ядра: стек выполнения потока для ядра. Исторически стек ядра отображался в фиксированное положение виртуального адресного пространства. Причиной использования фиксированного отображения явля- является то, что при выполнении родителем fork его стек времени выполнения копируется для его потомка. Если стек ядра отображен по фиксированному адресу, стек порожденного
112 Глава 4. Управление процессами процесса отображается по тем же самым адресам, что и стек ядра его родителя. Таким образом, все его внутренние ссылки, такие, как указатели фреймов и ссылки на локальные переменные, работают ожидаемым образом. На современных архитектурах с кешами виртуальных адресов отображение поль- пользовательской структуры на фиксированные адреса является медленным и неудобным. FreeBSD 5.2 устраняет это ограничение, удаляя из стека порожденного процесса все фреймы вызова, кроме верхнего, после копирования его из родителя таким образом, что он возвращается непосредственно в режим пользователя, избегая тем самым копирования стека и проблем перемещения. Каждый поток, который потенциально может быть запущен, должен иметь стек в резидентной памяти, поскольку одной из задач его стека является обработка отказов страниц. Если бы он не был резидентным, при попытке запуска потока возник бы отказ страницы, а доступного стека ядра для обслуживания отказа страницы не было бы. Поскольку в системе может быть несколько тысяч потоков, стек ядра должен под- поддерживаться небольшим, чтобы избежать чрезмерного расходования физической памяти. В FreeBSD 5.2 на PC стек ядра ограничен двумя страницами памяти. Разра- Разработчики должны быть осторожны при написании кода, выполняющегося в ядре, избе- избегая использования больших локальных переменных и глубоко вложенных вызовов подпрограмм, чтобы не допустить переполнения стека времени выполнения. В качест- качестве меры безопасности в некоторых архитектурах между областью стека времени выполнения и следующими за ней структурами данных оставляют недоступную стра- страницу. Таким образом, переполнение стека ядра вызовет отказ доступа ядра вместо пагубной перезаписи других структур данных. Можно было бы просто завершить про- процесс, который вызвал отказ, и продолжить выполнение. Однако ядро FreeBSD на отказ доступа ядра входит в состояние паники, поскольку такой отказ обнаруживает сущест- существенную проектную ошибку в ядре. Путем паники и создания дампа ядра ошибку обычно можно идентифицировать и исправить. 4.3. Переключение контекста Ядро, стремясь эффективно разделять время процессора, переключается между пото- потоками; эта деятельность называется переключением контекста. Когда поток выполня- выполняется в течение продолжительности его кванта времени или когда он блокируется из-за запрашивания ресурса, который в настоящий момент недоступен, ядро находит для запуска другой поток и переключается на его контекст. Система может также прервать текущий выполняющийся поток, чтобы запустить поток в результате асин- асинхронного события, такого, как прерывание от устройства. Хотя оба сценария включают переключение контекста выполнения процессора, переключение между потоками происходит синхронно по отношению к текущему выполняющемуся потоку, тогда как обслуживание прерываний происходит асинхронно по отношению к текущему
4.3. Переключение контекста 113 потоку. Кроме того, переключения контекста между процессами подразделяются на принудительные и добровольные. Добровольное переключение контекста происходит при блокировании процесса, поскольку он запрашивает недоступный ресурс. Прину- Принудительное переключение контекста имеет место, когда поток выполняется в течение своего кванта времени или когда система обнаруживает для запуска поток с более высоким приоритетом. Каждый из этих видов переключения контекста осуществляется посредством раз- различных интерфейсов. Добровольное переключение контекста инициируется вызовом процедуры sleepQ, тогда как принудительное переключение контекста форсируется непосредственным вызовом низкоуровневого механизма переключения контекста, реализованного в процедурах mi_switch() и setrunnableQ. Асинхронная обработка события запускается нижележащим оборудованием и в сущности прозрачна для системы. Наше обсуждение сконцентрируется на том, как асинхронная обработка события связана с синхронизацией доступа к структурам данных ядра. Состояние потока Переключение контекста между потоками требует, чтобы изменились контексты как режима ядра, так и режима пользователя. Для упрощения этого изменения система обеспечивает размещение всего состояния потока режима пользователя в одной струк- структуре данных: структуре потока (большая часть состояния ядра хранится в другом месте). К этому размещению применяются следующие соглашения. * Аппаратное состояние при выполнении в режиме ядра: переключение контекста может иметь место лишь в режиме ядра. Состояние аппаратного выполнения ядра определяется содержанием ТСВ, который расположен в структуре потока. Аппаратное состояние при выполнении в режиме пользователя: при выполнении в режиме ядра состояние режима пользователя потока (такое, как копии счетчика команд, указатель стека и общие регистры) всегда находится в стеке выполнения ядра, который расположен в структуре потока. Ядро гарантирует такое размеще- размещение состояния режима пользователя, требуя от системных вызовов и обработчиков исключений, чтобы они каждый раз при входе в ядро сохраняли содержимое кон- контекста выполнения режима пользователя (см. раздел 3.1). * Структура процесса: структура процесса всегда остается в памяти. * Ресурсы памяти: ресурсы памяти процесса фактически описываются содержанием регистров управления памятью, расположенных в ТСВ, и значениями, присутст- присутствующими в структурах процесса и потока. Пока процесс остается в памяти, эти значения будут действительными и переключения контекста можно делать без сохранения и восстановления соответствующих таблиц страниц. Однако эти значения необходимо вычислить заново, когда процесс возвращается в основную память после подкачки из вторичного хранилища.
114 Глава 4. Управление процессами Переключение контекста на низком уровне Размещение контекста процесса в структуре его потока дает ядру возможность пере- переключать контекст путем простого изменения представления текущей структуры потока и (при необходимости) структуры процесса и восстановления контекста, опи- описываемого ТСВ, в структуре потока (включая отображение виртуального адресного пространства). Каждый раз, когда требуется переключение контекста, вызов про- процедуры mijswitchQ заставляет запустить поток с наивысшим приоритетом. Процедура mi_switch() сначала выбирает из очередей планирования соответствующий поток, а затем возобновляет выбранный поток, загрузив контекст его процесса из его ТСВ. Когда mijswitchQ загрузила состояние выполнения нового потока, она должна также проверить состояние нового потока на предмет наличия запроса нелокального возвра- возврата (такого, как при первом после fork начале выполнения процессом; см. раздел 4.5). Добровольное переключение контекста Добровольное переключение контекста имеет место всегда, когда поток должен ожи- ожидать доступности ресурса или наступления события. Добровольные переключения контекста при обычной работе системы случаются часто. Например, поток обычно блокируется каждый раз, когда запрашивает данные от устройства ввода, такого, как терминал или диск. В FreeBSD добровольные переключения контекста инициируются посредством процедуры sleepQ. Когда потоку больше не нужен процессор, он вызывает sleepQ с указанными приоритетом планирования и каналом ожидания. Приоритет, ука- указанный в вызове sleepQ, является приоритетом, который должен быть назначен потоку при его пробуждении. Этот приоритет не влияет на приоритет планирования уровня пользователя. Канал ожидания обычно является адресом некоторой структуры данных, которая идентифицирует ожидаемые потоком ресурс или событие. Например, при ожидании потоком заполнения буфера используется адрес дискового буфера. При заполнении буфера будут пробуждены находящиеся в состоянии сна потоки с указанным каналом ожидания. В дополнение к адресу ресурса, который используется в качестве канала ожидания, есть несколько адресов, которые применяются для особых целей. * Глобальная переменная Ibolt проверяется планировщиком раз в секунду. Потоки, которые хотят ждать 1 секунду, могут находиться в состоянии сна, указав эту гло- глобальную переменную. Например, процедуры вывода на терминал ожидают дос- доступности пространства очереди вывода, указав Ibolt. Поскольку пространство очереди кончается редко, проще просто проверять его раз в секунду во время кратких периодов недостатка этого пространства, чем устанавливать механизм уведомле- уведомлений, такой, как использующийся для управления дисковыми буферами. Програм- Программисты могут также использовать канал ожидания Ibolt в качестве грубого сторо- сторожевого таймера во время отладки.
4.3. Переключение контекста 115 * Когда родительский процесс осуществляет системный вызов wait для сбора состояния завершения своих потомков, он должен ждать завершения одного из своих порожденных процессов. Поскольку он не может знать, какой из его потом- потомков завершится первым, и поскольку он может использовать лишь один канал ожидания, возникает затруднение с тем, как ожидать одно из нескольких событий. Решение в том, чтобы родитель ожидал, указав свою собственную структуру про- процесса. Когда порожденный процесс завершается, он использует адрес структуры процесса родителя, а не своего собственного. Таким образом, родитель, вызвав- вызвавший wait, будет пробужден независимо от того, какой из порожденных процессов завершится первым. После запуска он должен проверить список своих порожден- порожденных процессов, чтобы определить, какой из них завершился. При осуществлении потоком системного вызова sigpause он ожидает получения сигнала. Таким образом, он должен войти в состояние прерываемого сна с каналом ожидания, который никогда не будет пробужден. По соглашению в качестве канала ожидания используется адрес структуры пользователя. Спящие потоки организованы в массивы очередей (см. рис. 4.3). Процедуры sleepQ и wakeupQ хешируют каналы ожидания, чтобы вычислить индекс в очереди ожидания. Процедура sleepQ в своей работе осуществляет следующие шаги. 1. Запрещает прерывания, которые могут вызвать изменения состояния потока, запросив мьютекс schedjock (мьютексы объясняются в следующем разделе). 2. Записывает в структуру потока канал ожидания и хеширует значение канала ожи- ожидания, чтобы найти очередь ожидания для потока. 3. Устанавливает в качестве приоритета потока приоритет, который будет у потока при его пробуждении, и устанавливает флаг SLEEPING. 4. Помещает поток в конец очереди ожидания, выбранный на шаге 2. 5. Вызывает mi_switch() для запуска выполнения нового потока; мьютекс schedjock освобождается, как часть переключения на другой поток. Спящий поток не выбирается для выполнения до тех пор, пока он не будет удален из очереди ожидания и не будет помечен как готовый к выполнению. Эта операция осуществляется с помощью процедуры wakeupQ, которая вызывается для сигнали- сигнализирования о том, что возникло событие или что ресурс доступен. WakeupQ вызывается с параметром канала ожидания и пробуждает все потоки, указавшие данный канал ожидания. Пробуждаются все потоки, ожидающие ресурс, чтобы гарантировать, что ни один случайно не оставлен спящим. Если бы пробуждался лишь один поток, он мог бы не запросить ресурс, который он указал, и любые другие потоки, ожидающие этот ресурс, навсегда остались бы в состоянии сна. Примером потока, который может не запросить указанный им ресурс, является поток, которому нужен пустой дисковый буфер для записи данных. Такой поток может использовать любой доступный буфер.
116 Глава 4. Управление процессами Заголовок хеш-таблицы очереди ожидания td_slpq. tqe_next С Поток j С Поток td_slpq. tqe_prev Поток j Г Поток j С Поток С Поток РИС. 4.3. Структура очереди для спящих потоков Если ни один не доступен, он попытается его создать, запросив запись на диск грязного буфера и ожидая завершения операции чтения/записи. Когда ввод/вывод завершится, поток будет пробужден и проверит наличие пустого буфера. Если доступны несколько, он может не использовать тот, который очистил сам, навсегда оставив тем самым все остальные потоки ожидать буфер, который он очистил. В случаях, когда поток всегда будет использовать ставший доступным ресурс, вместо wakeupQ может использоваться wakeup_one(). Процедура wakeup_one() пробуждает лишь первый ожидающий ресурс поток, который она находит. Предполагается, что когда пробужденный поток закончит работу с ресурсом, он еще раз вызовет wakeup_one(), чтобы уведомить о доступности ресурса следующий ожидающий поток. Последователь- Последовательность вызовов wakeup_one() будет продолжаться до тех пор, пока все потоки, ожи- ожидающие ресурс, не будут пробуждены и не будут иметь шанс его использовать. Чтобы избежать чрезмерного числа пробуждаемых потоков, программисты ядра стараются использовать каналы ожидания с достаточно мелкой детализацией, чтобы для одного и того же ресурса не сталкивались не имеющие к нему отношения случаи. Так, на каждый буфер в буферном кеше помещаются отдельные блокировки, а не одна блокировка на буферный кеш в целом. Проблема пробуждения множества потоков для одного ресурса в однопроцессорной системе смягчается далее путем однопоточной по сути работы последней. Хотя в очередь запуска одновременно будет помещено множе- множество потоков, выполняться в каждый данный момент времени может лишь один. Поскольку ядро на однопроцессорной системе не вытесняется, каждый поток будет выполнять до завершения свой системный вызов до того, как другой получит шанс на выполнение. Если предыдущий пользователь ресурса не заблокировался в ядре,
4.3. Переключение контекста 117 пытаясь использовать ресурс, каждый поток, ожидающий ресурс, сможет его получить и использовать при следующем своем запуске. Операция wakeupQ обрабатывает элементы в очереди ожидания от начала к концу. Для каждого потока, который должен быть пробужден, wakeupQ осуществляет следующее. 1. Удаляет поток из очереди ожидания. 2. Вычисляет заново приоритет планирования режима пользователя, если поток нахо- находился в состоянии сна в течение более одной секунды. 3. Помечает поток как готовый к выполнению, если он в состоянии SLEEPING, и поме- помещает поток в очередь запуска, если процесс не выкачан из главной памяти. Если процесс был сброшен, будет пробужден процесс swapin для загрузки его обратно в память (см. раздел 5.12); если поток в состоянии STOPPED, он не помещается в очередь запуска до тех пор, пока он не возобновится явным образом процессом уровня пользователя либо с помощью системного вызова ptrace (см. раздел 4.9), либо с помощью сигнала continue (см. раздел 4.7). Табл. 4.3. Уровень Низший Высший Иерархия блокировок Тип Аппаратный Циклический мьютекс Ожидающий мьютекс Менеджер блокировок Доказательный Ожидание Нет Нет Нет Да Да Описание Сблокированное с памятью проверка-и-уста- новка Циклическая блокировка Циклическая на время, затем ожидающая Ожидающая блокировка частично упорядоченные ожидающие блокировки Если wakeupQ поместила какие-нибудь потоки в очередь запуска и у одного из них приоритет планирования выше, чем у выполняющегося в данный момент времени потока, она потребует также, чтобы процессорное время было перепланировано как можно скорее. Синхронизация Исторически системы BSD работали лишь на однопроцессорных архитектурах. Когда процесс начинал работу в верхней половине ядра, он работал до завершения или пока не входил в состояние сна в ожидании доступности ресурса. Единственный конфликт для доступа к структуре данных возникал в моменты, когда он спал или когда во время прерывания было необходимо обновить структуру данных. Синхронизация с другими процессами осуществлялась путем обеспечения согласованности всех разделяемых структур данных перед вхождением в состояние сна. Синхронизация с прерываниями
118 Глава 4. Управление процессами осуществлялась путем повышения уровня приоритета процессора, чтобы защитить себя от прерывания при работе с разделяемой структурой данных. В FreeBSD 3.0 была добавлена простая поддержка многопроцессорности. Она работала путем создания всеобщей блокировки, защищавшей ядро. Когда процесс входил в ядро, ему приходилось запрашивать всеобщую блокировку до того, как он мог продолжить. Таким образом, в одно и то же время в режиме ядра мог работать лишь один процессор. Все другие процессоры могли работать лишь с процессами в режиме пользователя. Симметричная многопроцессорная обработка (SMP) впервые появилась в FreeBSD 5.0 и потребовала добавления новых схем синхронизации, чтобы устранить предположения об однопроцессорной системе, внутренне присущие ядру FreeBSD 4.0 [Schimmel, 1994]. Некоторые подсистемы в ядре FreeBSD 5.2 еще не были переведены на симметричную многопроцессорную обработку и до сих пор защищаются всеобщей блокировкой. Однако большая часть интенсивно использующихся частей ядра была выведена из-под всеобщей блокировки, включая многое из системы виртуальной памяти, сетевого стека и файловой системы. В табл. 4.3 показана иерархия блокировок, которая необходима для поддержки симметричной многопроцессорности. Столбец ожидания в табл. 4.3 показывает, может ли блокировка этого типа удерживаться, когда поток входит в состояние сна. На самом нижнем уровне аппаратное обеспечение должно предоставить инструкцию сблокиро- сблокированной с памятью проверки-и-установки (test-and-set). Инструкция проверки-и-уста- новки должна обеспечить выполнение для ячейки памяти двух операций - чтения существующего значения, за которой следует запись нового значения - без возможно- возможности для любого другого процессора прочесть или записать в эту ячейку памяти между этими двумя операциями. Некоторые архитектуры поддерживают более сложные версии инструкции проверки-и-установки. Например, PC предоставляет инструкцию сблокированного с памятью сравнения-и-обмена. Циклические блокировки (spin locks) строятся на основе инструкции проверки- и-установки. Для блокировки резервируется ячейка памяти с нулевым значением, указывающим, что блокировка свободна, а значение «один» указывает, что бло- блокировка занята. Значение блокировки проверяется, и она безусловно получает еди- единичное значение. Если проверяемое значение равно нулю, блокировка была успешно получена, и поток может продолжить свою работу. Если значение равно единице, блокировку удерживает какой-то другой поток, поэтому поток должен осуществлять цикл проверки-и-установки до тех пор, пока поток, владеющий блокировкой (и рабо- работающий на другом процессоре), не сменит ее значение на ноль, показывая тем самым, что он закончил работу с ней. Циклические блокировки используются в каче- качестве лишь кратковременных блокировок - например, для защиты списка при добав- добавлении в него или удалении из него элемента.
4.3. Переключение контекста 119 Использование циклических блокировок для ресурсов, которые будут удерживаться в течение длительного времени, является расточительством процессорного времени. Например, циклическая блокировка была бы неподходящей для дискового буфера, который необходимо было заблокировать на время выполнения дискового ввода/вывода. Здесь должна использоваться ожидающая блокировка. Когда поток, пытающийся получить ожи- ожидающую блокировку, обнаруживает, что она занята, он входит в состояние сна таким обра- образом, что другие потоки могут работать до тех пор, пока блокировка не станет доступной. Время получения блокировки может разниться - например, блокировки для поиска и удаления элемента из списка. Список обычно короткий, в этом случае подо- подошла бы циклическая блокировка, но время от времени он становится длинным. Здесь используется гибридная блокировка: циклическая блокировка на некоторое время, но если после определенного числа попыток они окажутся безуспешными, она превраща- превращается в блокировку ожидающего типа. На большинстве архитектур для помещения потока в состояние сна и повторного его пробуждения требуется от 100 до 200 инструк- инструкций. Циклическая блокировка, которую можно получить за меньшее время, будет более эффективной, чем ожидающая блокировка. Гибридная блокировка обычно уста- устанавливается на время, равное половине этого времени, прежде чем перейти в режим ожидающей блокировки. Циклические блокировки не применяются в однопроцессорной системе, посколь- поскольку единственным способом, которым когда-либо будет освобожден ресурс, удерживае- удерживаемый другим потоком, является запуск этого потока на выполнение. Соответственно при запуске на однопроцессорной системе циклические блокировки всегда преобра- преобразуются в ожидающие блокировки. Блокировки высшего уровня предотвращают создание потоками тупиков (deadlocks) при блокировании нескольких ресурсов. Предположим, двум потокам, А и В, требуется исключительный доступ к двум ресурсам, Я\ и R2, для выполнения некоторой операции, как показано на рис. 4.4. Если поток А получил Rb а поток В пытается получить R2, тупик возникнет, когда поток А попытается получить R2, а поток В попытается получить /?j. Чтобы избежать тупиков, FreeBSD 5.2 поддерживает частичное упорядочение всех бло- блокировок. Два правила для частичного упорядочения следующие. 1. Поток может получить лишь одну блокировку в каждом классе. 2. Поток может получить лишь блокировку в классе с большим номером, чем класс с наибольшим номером, блокировка на который у потока уже есть. На рис. 4.4 поток А содержит R\ и может запросить R2, поскольку R\ и R2 в раз- различных классах и R2 в классе с большим номером, чем R\. Однако поток В должен освободить R2 до запроса R\, поскольку R2 в классе с большим номером, чем R^. Таким образом, поток А сможет получить R2, когда он будет освобожден потоком В. После того, как поток А завершит работу и освободит R\ и R2, поток В сможет получить обе эти блокировки и выполняться до завершения, не создавая тупик.
120 Глава 4. Управление процессами Класс 1 РИС. 4.4. Частичное упорядочение ресурсов Класс 2 Исторически члены класса и упорядочение были плохо документированы и необя- необязательны. Были обнаружены нарушения, когда потоки создали бы тупики, и был про- проведен тщательный анализ для выяснения того, какое упорядочение было нарушено. С возрастанием числа разработчиков и ростом ядра специальный метод для поддержа- поддержания частичного упорядочения блокировок стал непригодным. К ядру был добавлен модуль доказательств (witness module), чтобы установить и привести в исполнение частичное упорядочение блокировок. Модуль доказательств отслеживает блокировки, полученные и освобожденные каждым потоком. Он также отслеживает порядок, в котором получаются блокировки друг относительно друга. Каждый раз при получе- получении блокировки модуль доказательств использует эти два списка для проверки того, что блокировка не запрашивается в неправильном порядке. Если обнаружено наруше- нарушение порядка блокировки, в консоль выводится сообщение, подробно описывающее использованные блокировки и данные места. Модуль доказательств проверяет также, что при запросе ожидающей блокировки или добровольном переходе в состояние сна не удерживаются циклические блокировки. Модуль доказательств можно сконфигурировать так, чтобы при возникновении нарушения порядка или неудаче какой-нибудь другой проверки модуля доказательств войти в состояние паники или в отладчик ядра. При запуске отладчика модуль доказа- доказательств может вывести в консоль список блокировок, удерживаемых текущим потоком, вместе с именем файла и номером строки, в которой в последний раз была получена каждая блокировка. Он может также отобразить в консоли текущий список упорядочи- упорядочивания. Код сначала отображает дерево упорядочения для всех ожидающих блокировок. Затем он отображает дерево упорядочения для всех циклических блокировок. Наконец, он отображает список блокировок, которые еще не были получены.
4.3. Переключение контекста 121 Синхронизация с помощью мьютекса Мьютексы - это основной способ синхронизации потоков. Главными соображениями в дизайне мьютексов являются следующие. * Получение и освобождение безальтернативных мьютексов должно быть как можно более дешевым. * Мьютексы должны содержать информацию и область памяти для поддержки передачи приоритетов. * Поток должен иметь возможность рекурсивно получать мьютекс, если мьютекс инициализирован с поддержкой рекурсии. В настоящее время имеются две разновидности мьютексов: ожидающие и неожи- неожидающие. По умолчанию мьютексы будут находиться в состоянии сна, если ими уже владеют. В качестве машинно-зависимой оптимизации они могут в течение некоторого времени находиться в цикле до вхождения в состояние сна. Большая часть кода ядра использует тип блокировки по умолчанию; тип блокировки по умолчанию дает потоку возможность отключиться от процессора, если он не может получить блокировку. Машинно-зависимая реализация может при некоторых обстоятельствах рассматривать блокировку как кратковременную циклическую блокировку. Однако всегда безопасно использовать эти формы блокировок в потоке прерывания, не опасаясь тупиков со стороны прерванных потоков на том же самом процессоре. Если некоторый поток прервал другой поток, который владел мьютексом, а затем попытался получить этот мьютекс, он войдет в состояние сна до тех пор, пока поток, удерживающий мьютекс, не отработает до завершения и не освободит мьютекс. Неожидающие мьютексы называются циклическими мъютексами (spin mutexes). Циклический мьютекс не освобождает процессор при невозможности немедленно получить запрошенную блокировку, но будет вращаться в цикле до освобождения мьютекса другим процессором. Это зацикливание может привести к тупику, если поток прервал другой поток, который владел мьютексом, а затем попытался получить этот мьютекс. Чтобы защитить процедуру обслуживания прерывания от самоблокиро- самоблокирования, при удерживании на локальном процессоре циклических блокировок все преры- прерывания блокируются. Таким образом, во время владения мьютексом прерывание может возникнуть лишь на другом процессоре. Циклические блокировки являются специализированными блокировками, которые предназначены для кратковременного использования; их главным назначением является защита участков кода, которые реализуют блокировки по умолчанию (те. ожидающие). Эти мьютексы должны использоваться лишь для защиты данных, совместно исполь- используемых любыми устройствами, которым требуются невытесняющиеся прерывания и низкоуровневый код планирования. На большинстве архитектур как получение, так и освобождение безальтернативных циклических мьютексов дороже, чем та же самая операция с нециклическим мьютексом. Допускается удержание нескольких циклических
122 Глава 4. Управление процессами мьютексов. В этом случае требуется, чтобы они освобождались в обратном по сравне- сравнению с получением порядке. Входить в состояние сна, удерживая циклический мьютекс, не допускается. Для инициализации мьютекса до передачи его функции mtx_lock() должна исполь- использоваться функция mtx_init(). Функция mtx_init() указывает тип, который код модуля до- доказательств использует для классификации мьютексов при осуществлении проверки упорядочения блокировок. Не допускается несколько раз передавать функции mtx_init() один и тот же мьютекс без промежуточных вызовов mtx_destroy(). Функция mtx_lock() получает взаимно исключающую блокировку для текущего выполняющегося потока ядра. Если мьютекс удерживается другим потоком ядра, вызывающий войдет в состояние сна до тех пор, пока мьютекс не станет доступным. Функция mtx_lock_spin() сходна с mtx_lock(), за тем исключением, что она зациклится до тех пор, пока мьютекс не станет доступным. Прерывания процессора, удержи- удерживающего мьютекс, запрещаются на время цикла ожидания и остаются запрещенными после получения блокировки. Один и тот же поток может рекурсивно получить мьютекс без вредных последст- последствий, если во время инициализации мьютекса функции mtx_init() будет передан бит MTX_RECURSE. Модуль доказательств проверяет, что поток не использует рекурсию с нерекурсивной блокировкой. Рекурсивная блокировка полезна, когда ресурс может быть заблокирован в ядре на двух или более уровнях. Разрешая рекурсивную бло- блокировку, более низкому уровню не нужно проверять, был ли ресурс уже заблокирован более высоким уровнем; он может просто блокировать и освобождать ресурс по мере необходимости. Функция mtxJrylockQ пытается получить взаимоисключающую блокировку для текущего выполняющегося потока ядра. Если мьютекс не может быть получен немед- немедленно, mtxJiylockQ вернет 0; в противном случае будет получен мьютекс и возвраще- возвращено ненулевое значение. Функция mtx_unlock() освобождает взаимоисключающую блокировку; если мьютекс ожидается потоком с более высоким приоритетом, освободивший поток будет помещен в состояние сна, чтобы позволить получить мьютекс и запустить поток с более высоким приоритетом. Мьютекс, допускающий рекурсивное блокирование, содержит счетчик ссылок, который указывает, сколько раз он был заблокирован. Для каждого успешного запроса блокировки должен быть соответствующий запрос освобождения. Мьютекс не освобождается до тех пор, пока не будет выполнен конечный запрос освобождения, обнуляющий счетчик ссылок. Функция mtx_unlock_spin() освобождает взаимоисключающую циклическую бло- блокировку; состояние прерывания, которое было до получения блокировки, восстанавли- восстанавливается. Функция mtx_destroy() разрушает мьютекс таким образом, что связанные с ним данные могут быть освобождены или перезаписаны. Каждый разрушаемый мьютекс
4.3. Переключение контекста 123 должен быть предварительно инициализирован с помощью mtx_init(). При разрушении мьютекса допускается иметь на него единственную ссылку. При разрушении мьютекса не допускается его рекурсивное удержание или блокирование на нем другого потока. Если эти правила нарушаются, ядро входит в состояние паники. Всеобщая блокировка, защищающая подсистемы в FreeBSD 5.2, которые еще не были преобразованы для работы на многопроцессорной системе, должна быть получе- получена до получения других мьютексов. Другими словами, невозможно получить всеоб- всеобщую блокировку, удерживая другой мьютекс. Можно получить другие мьютексы, удерживая всеобщую, а также можно рекурсивно получать всеобщую блокировку, удерживая другие мьютексы. Нахождение в состоянии сна с удерживанием мьютекса (за исключением всеобщего) почти никогда не является безопасным и должно избегаться. Имеются многочислен- многочисленные диагностические проверки, которые потерпят неудачу при такой попытке. Блокировки менеджера блокировок Межпроцессная синхронизация ресурса обычно реализуется посредством ассоцииро- ассоциирования со структурой lock. В ядре есть менеджер блокировок, который управляет ими. Операции, предусмотренные менеджером блокировок, следующие. Запрос разделения: получение одной из нескольких возможных разделяемых бло- блокировок. Если поток, владеющий исключительной блокировкой, запрашивает раз- разделяемую блокировку, его исключительная блокировка будет переведена на уро- уровень разделяемой. Запрос исключительности: прекращение дальнейшего разделения блокировок при их освобождении, предоставление ожидания повышения уровня (см. следующий пункт), если он существует, а затем предоставление исключительнойю блокировки. В одно и то же время может существовать лишь одна исключительная блокировка, за исключением случая, когда поток, владеющий исключительной блокировкой, может получить дополнительные исключительные блокировки, если он явным образом установил флаг canrecurse в запросе блокировки или если флаг canrecurse был установлен при инициализации блокировки. # Запрос повышения уровня: поток должен владеть разделяемой блокировкой, которую он хочет перевести на уровень исключительной блокировки. В промежутке времени между запросом повышения уровня и его предоставлением к ресурсу могут получить исключительный доступ другие потоки. Запрос исключительного повышения уровня: поток должен владеть разделяемой блокировкой, уровень которой он хочет повысить до исключительной блокировки. Если запрос будет успешным, в промежутке времени между запросом повышения уровня и его предоставлением исключительный доступ к ресурсу другие потоки
124 Глава 4. Управление процессами не получат. Однако, если другой поток уже запросил повышение уровня, запрос потерпит неудачу. * Запрос понижения уровня: поток должен владеть исключительной блокировкой, уровень которой он хочет понизить до разделяемой блокировки. Если поток владеет несколькими (рекурсивными) исключительными блокировками, уровень их всех будет снижен до разделяемых блокировок. * Запрос освобождения: освобождает один экземпляр блокировки. Запрос утечки: подождать окончания деятельности с блокировкой до конца и поме- пометить ее как списанную. Эта возможность используется до освобождения блокировки, являющейся участком памяти, которая вскоре будет освобождена. Перед первым использованием блокировки должны быть инициализированы путем вызова функции lockinitQ. Параметры функции lockinitQ включают следующее. * Приоритет верхней половины ядра, с которым поток должен быть запущен после получения блокировки. * Флаги, такие, как canrecurse, дающие потоку, владеющему в настоящий момент блокировкой, возможность получить другую исключительную блокировку вместо паники с сообщением об ошибке «блокирование на себя». * Строка, описывающая ресурс, который блокировка защищает, которая называется сообщением канала ожидания. * Необязательное максимальное время ожидания доступности блокировки. Другие виды синхронизации Помимо общих блокировок менеджера блокировок и мьютексов имеются три других вида доступных блокировок для использования в ядре. Эти другие типы используются не так интенсивно, но они включены здесь для полноты. Разделяемые/исключительные блокировки используются для защиты данных, которые читаются гораздо чаще, чем записываются. Мьютексы в своей основе более эффективны, чем разделяемые/исключительные блокировки. Однако разделяемые/ исключительные блокировки быстрее, чем блокировки менеджера блокировок, поскольку у них отсутствуют многие из особенностей блокировок менеджера блокировок, такие, как возможности повышения или понижения уровня. Для ожидания возникновения условий с мьютексами используются условные пере- переменные. Потоки ожидают с указанием условных переменных, вызвав cv_wait(J, cv_wait_sig() (ожидание до прерывания сигналом), cv_timedwait() (ожидание в пределах указанного максимального количества времени) или cv_timedwait_sig() (ожидание до прерывания сигналом или в пределах максимального указанного времени). Потоки раз- разблокируют ожидающих, вызывая cvjsignalQ для разблокирования одного ожидающего
4.4. Планирование потоков 125 или cv_broadcast() для разблокирования всех ожидающих. Функция cv_waitq_remove() удаляет ожидающий поток из очереди ожидания условной переменной, если он в ней находится. До вызова cv_wait(), cv_wait_sig(), cv_timedwait() или cv_timedwait_sig() поток должен владеть мьютексом. Когда поток ожидает с указанным условием, мьютекс ато- атомарно освобождается, прежде чем поток будет заблокирован, а затем атомарно получа- получается снова, прежде чем функция вернется из вызова. Все ожидающие должны исполь- использовать с условной переменной один и тот же мьютекс. Поток должен владеть мьютек- мьютексом, вызывая cvsignalQ или cv_broadcast(). Счетные семафоры предоставляют механизм для синхронизации доступа к пулу ресурсов. В отличие от мьютексов семафоры не содержат представления о владельце, поэтому они могут также быть полезны в ситуациях, когда одному потоку нужно получить ресурс, а другому освободить его. У каждого семафора есть связанное с ним целое число. Регистрация (увеличение) всегда завершается успешно, но ожидание (уменьшение) может успешно завершиться, лишь если результирующее значение семафора больше или равно нулю. Семафоры не используются, когда мьютексов и услов- условных переменных достаточно. Семафоры являются более сложным механизмом синхро- синхронизации, чем мьютексы и условные переменные, и не так эффективны. 4.4. Планирование потоков Традиционно планировщик FreeBSD содержал неопределенный набор ловушек (hooks), разбросанных по всему ядру. В FreeBSD 5.0 эти ловушки были упорядочены и был создан хорошо определенный API таким образом, чтобы можно было разрабаты- разрабатывать различные планировщики. Начиная с FreeBSD 5.0, в ядре доступны следующие два планировщика: * Исторический планировщик 4.4BSD, который можно найти в файле /sys/kern/ sched_4bsd.c. Этот планировщик описан в начале данного раздела. * Новый планировщик ULE, впервые введенный в FreeBSD, который можно найти в файле /sys/kern/sched_ule.c [Roberson, 2003]. Имя не является акронимом. Если убрать из имени его файла символ подчеркивания, обоснование имени станет оче- очевидным1. Этот планировщик описан в конце данного раздела. Поскольку загруженная система делает тысячи планирующих решений в секунду, скорость, с которой принимаются планирующие решения, является критической для производительности системы в целом. В других системах UNIX добавлен динамиче- динамический переключатель планировщика, который должен проверяться для каждого пла- планирующего решения. Чтобы избежать этих издержек, FreeBSD требует, чтобы пла- планировщик был выбран во время построения ядра. Таким образом, все вызовы кода пла- Schedule (англ.) - «планировать». - Прьииеч. пер.
126 Глава 4. Управление процессами нировщика разрешаются во время компилирования вместо издержек косвенных вызо- вызовов функций для каждого планирующего решения. По умолчанию ядра вплоть до FreeBSD 5.1 используют планировщик 4.4BSD. Начиная с FreeBSD 5.2 по умолча- умолчанию используется планировщик ULE. Планировщик 4.4BSD Всем потокам, готовым для выполнения, назначается приоритет планирования, который определяет, в какую очередь планирования они будут помещены. При выборе для выполнения нового потока система просматривает очереди выполнения с приори- приоритетами от наивысшего до наинизшего и выбирает первый поток в непустой очереди. Если в очереди находятся несколько потоков, система запускает их в порядке карусели, т.е. в том порядке, в каком они находятся в очереди, с равным количеством выделенно- выделенного времени. Если поток блокируется, он не помещается обратно ни в одну очередь выполнения. Если поток использует весь доступный ему квант времени, он помещается в конец той очереди, из которой он поступил, а для выполнения выбирается поток в начале очереди. Чем короче квант времени, тем лучше интерактивный ответ. Однако более дли- длительные кванты времени обеспечивают лучшую производительность системы, по- поскольку у системы будет меньше непроизводительных издержек по осуществлению переключений контекста, а кеши процессора будут очищаться реже. FreeBSD исполь- использует квант времени в 0,1 секунды. Это значение было установлено эмпирически как самый длительный квант времени, который может использоваться без потери нужной чувствительности для интерактивных заданий, таких, как редакторы. Возможно, это неожиданно, но квант времени остался без изменений в течение последних 20 лет. Несмотря на то что квант времени первоначально был выбран на централизованных системах с разделением времени со множеством пользователей, он по-прежнему под- подходит для сегодняшних децентрализованных рабочих станций. Хотя пользователи рабочих станций ожидают более быстрого ответа, чем ожидали пользователи систем с разделением времени 20 лет назад, более короткие очереди выполнения обычных рабочих станций делают укороченный квант времени ненужным. Планирование потоков с разделением времени Алгоритм планирования FreeBSD с разделением времени основан на многоуровневых очередях с обратной связью. Система регулирует приоритет потока динамически, чтобы отражать потребности в ресурсах (например, блокирование в ожидании события) и объем потребляемых потоком ресурсов (например, время процессора). Потоки пере- перемещаются между очередями выполнения, основываясь на их приоритетах планирования (отсюда слово обратная связь в названии многоуровневая очередь с обратной связью). Когда поток, который не является текущим выполняющимся потоком, получает более высокий приоритет (который назначается при его пробуждении), система немедленно
4.4. Планирование потоков 127 переключается на этот поток, если текущий поток находится в режиме пользователя. В противном случае система переключается на поток с более высоким приоритетом, как только текущий поток выйдет из ядра. Система приспосабливает этот алгоритм кратковременного планирования для благоприятствования интерактивным заданиям, повышая приоритеты планирования потоков, которые заблокированы в ожидании ввода/вывода в течение одной или более секунд, и понижая приоритеты потоков, которые набирают значительные количества процессорного времени. Кратковременное планирование потока делится на две части. Следующие два раз- раздела описывают, когда и как изменяется приоритет планирования потока, а также управление очередями выполнения и взаимодействие между планированием потоков и переключением контекста. Вычисления приоритета потоков Приоритет планирования потока непосредственно определяется двумя значениями, связанными со структурой потока: kg_estcpu и kgjiice. Значение kg_estcpu предостав- предоставляет оценку недавнего использования потоком процессора. Значение kgjiice является устанавливаемым пользователем весовым фактором, численно колеблющимся в пре- пределах от- 20 до 20. Обычным значением для kgjiice является 0. Отрицательные значе- значения повышают приоритет потока, тогда как положительные значения понижают его приоритет. Приоритет планирования потока режима пользователя вычисляется после каждых четырех тиков (обычно 40 миллисекунд) выполнения по следующей формуле: kg_user_pri = PRI_NIN_TIMESHARE + Pg~estcpU \ + 2xkg_mce „ ^ Значения, меньшие PRI_MIN_TIMESHARE A60), устанавливаются в PRI_MIN_ TIMESHARE (см. табл. 4.2); значения, большие PRI_MAX_TIMESHARE B23), уста- устанавливаются в PRI_MAX_TIMESHARE. Такое вычисление вызывает линейное сниже- снижение приоритета, основываясь на недавнем использовании процессора. Контролируе- Контролируемый пользователем параметр kgjiice действует как ограниченный весовой фактор. Отрицательные значения снижают эффект интенсивного использования процессора, компенсируя дополнительный член, содержащий kg_estcpu. В противном случае, если мы игнорируем второй член, kgjiice просто смещает приоритет на постоянное значение. Использование процессора, kg_estcpu, увеличивается с каждым тиком системных часов, при котором поток оказывается выполняющимся. Кроме того, kg_estcpu раз в секунду настраивается с использованием дискретного убывающего фильтра. Убывание вызы- вызывает списывание около 90 процентов набранного за 1-секундный интервал времени использования процессора через промежуток времени, который зависит от средней загрузки системы. Точнее, kg_estcpu настраивается в соответствии с
128 Глава 4. Управление процессами Bxbad) , kg _ estcpu = kg _ estcpu +kg_ nice, Bxload+\) D.2) где load является средним суммы длин очереди выполнения и кратковременной очереди ожидания в течение предыдущего 1-минутного интервала работы системы. Чтобы понять влияние фильтра убывания, мы можем рассмотреть случай, когда один связанный с вычислениями поток монополизирует процессор. Использование процессора потоком будет набирать тики часов со скоростью, зависящей от частоты часов. Средняя загрузка будет фактически 1, что даст затухание kg_estcpu = 0,66 х kg_estcpu + kgjiice. Если мы предположим, что поток набирает 7} тиков часов за интервал времени i и что kgjxice равно нулю, тогда использование процессора за каждый временной интервал будет включать текущее значение kg_estcpu в соответствии с kg_estcpu = 0,66 х 7q; kg_estcpu = 0,66 х (Т{ + 0,66 х TQ) = 0,66 хТх + 0,44 х То; kgjestcpu = 0,66 хТ2 + 0,44 х Тх + 0,30 х То; kg_estcpu = 0,66 х Тъ + ... + 0,20 х То; kg_estcpu = 0,66 х Т4 + ... + 0,13 х То. Таким образом, после пяти вычислений затухания в значении текущего использо- использования процессора для потока будет присутствовать лишь 13 процентов Tq. Поскольку фильтр затухания применяется один раз в секунду, мы можем также сказать, что около 90 процентов времени использования процессора списывается спустя 5 секунд. Потокам, готовым для выполнения, периодически регулируют их приоритет, как было только что описано. Однако система игнорирует потоки, заблокированные в ожи- ожидании события: эти потоки не могут аккумулировать время использования процессора, поэтому оценка их отфильтрованного использования процессора может быть вычисле- вычислена за один шаг. Эта оптимизация может значительно снизить накладные расходы пла- планирования системы, когда имеется множество заблокированных потоков. Система пересчитывает приоритеты потоков при пробуждении потока, который находился в со- состоянии сна в течение более 1 секунды. Система хранит значение kgjslptime, являющееся оценкой времени, которое поток провел в заблокированном состоянии в ожидании события. Значение kgjslptime устанавливается в 0 при вызове потоком sleepQ и увеличивается раз в секунду, пока поток остается в состоянии SLEEPING или STOPPED. Когда поток пробуждается, система вычисляет значение kg_estcpu в соот- соответствии с Г Bxload) kg _ slpttme
4.4. Планирование потоков 129 а затем пересчитывает приоритет планирования, используя формулу 4.1. Этот анализ игнорирует влияние kg_nice\ использованный load также является текущей средней загрузкой, а не средней загрузкой в то время, когда поток был заблокирован. Процедуры вычисления приоритета потока Вычисления приоритета, использованные в алгоритме кратковременного планирования, разбросаны в различных частях системы. Периодически запускаются две процедуры, schedcpuQ и roundrobinQ. SchedcpuQ пересчитывает приоритеты потоков раз в секунду, используя формулу 4.2, и обновляет значение kg_slptime для потоков, заблокированных вызовом sleepQ. Процедура roundrobinQ запускается 10 раз в секунду и заставляет систе- систему перепланировать потоки в (непустой) очереди с самым высоким приоритетом в режиме карусели, что дает каждому потоку квант времени в 100 миллисекунд. Оценки использования процессора обновляются в системном модуле обработки времени, hardclockQ который выполняется 100 раз в секунду. Каждый раз, когда про- процесс набирает четыре тика в своей оценке использования процессора, kg_estcpu, система пересчитывает приоритет потока. Этот пересчет использует формулу 4.1 и осуществ- осуществляется процедурой resetpriorityQ. Решение о пересчете после четырех тиков связано с управлением очередями выполнения, описанными в следующем разделе. Кроме вызова из hardclockQ, каждый раз, когда setrunnableQ помещает поток в очередь выпол- выполнения, она также вызывает resetpriorityQ для пересчета приоритета планирования потока. Этот вызов из wakeupQ функции setrunnableQ действует с потоком, не являющимся теку- текущим. Поэтому setrunnableQ до вызова resetpriorityQ вызывает updatepriQ для пересчета оценки использования времени процессора в соответствии с формулой 4.3. Взаимоотно- Взаимоотношения этих функций показаны на рис. 4.5. hardclock{) wakeup() statclock() setrunnableQ) sched_clock{) updatepri{) setpriorityO Рис. 4.5. Интерфейс процедур для вычислений приоритета Очереди выполнения потоков и переключение контекста У ядра есть один набор очередей выполнения для управления всеми классами пла- планирования потоков, приведенными в табл. 4.2. Вычисления приоритета планирования, описанные в предыдущем разделе, используются для упорядочения набора потоков с разделением времени в диапазоне приоритетов между 160 и 223. Приоритеты потоков
130 Глава 4. Управление процессами реального времени и ожидания устанавливаются самими приложениями, но их значе- значения ограничиваются ядром диапазонами от 128 до 159 и от 224 до 255 соответственно. Число очередей, используемых для хранения совокупности всех готовых к выполне- выполнению потоков в системе, влияет на стоимость управления очередями. Если поддержива- поддерживается лишь одна (упорядоченная) очередь, выбор следующего потока для выполнения упрощается, но другие операции становятся более дорогостоящими. Использование 256 различных очередей может значительно повысить стоимость определения сле- следующего потока для выполнения. Система использует 64 очереди выполнения, выбирая для потока очередь выполнения, деля приоритет потока на 4. Для экономии времени потоки в каждой очереди больше не сортируются по их приоритетам. Очереди выполнения Высокий приоритет tdjrunq. tqejiext С Поток j С Поток tdjrunq. tqe_prev С Поток j Поток j T Поток Низкий приоритет Рис. 4.6. Структура очередей для выполняющихся потоков Очереди выполнения содержат все готовые к выполнению потоки в основной памяти, за исключением текущего выполняющегося потока. На рис. 4.6 показано, как каждая очередь организована в виде двойного связанного списка структур потоков. В массиве хранится заголовок каждой очереди выполнения. С этим массивом связан битовый вектор, rq_status, который используется при определении непустых очередей выполнения. Для помещения потока в конец очереди и удаления потока из начала очереди используются две процедуры, runq_add() и runq_remove(). Основой алгоритма планирования является процедура runq_choose(). Функция runq_choose() отвечает за выбор нового потока для запуска; она действует следующим образом. 1. Убедиться, что вызывающий получил schedjock. 2. Найти непустую очередь выполнения, найдя положение первого ненулевого бита в векторе битов rq_status. Если rq_status равен нулю, нет потоков для выполнения, поэтому выбрать поток холостого цикла (idle loop). 3. Для данной непустой очереди удалить из нее первый поток.
4.4. Планирование потоков 131 4. Если эта очередь теперь в результате удаления потока пуста, сбросить соответст- соответствующий бит в rq_status. 5. Вернуть выбранный поток. Код переключения контекста разделен на две части. Машинно-независимый код находится в mijswitchQ; машинно-зависимая часть находится в cpu_switch(). На боль- большинстве архитектур процедура cpu_switch() ради эффективности написана на ассемб- ассемблере. Когда даны процедура mijswitchQ и процедура вычисления приоритетов потоков, единственным недостающим фрагментом в средствах планирования является то, как система осуществляет принудительное переключение контекста. Вспомните, что добровольное переключение контекста происходит, когда поток вызывает процедуру sleepQ. SleepQ может вызываться лишь работающим потоком, поэтому sleepQ нужно лишь поместить поток в очередь ожидания и вызвать mijswitchQ, чтобы запланировать выполнение следующего потока. Часто потоку прерывания нужно не вызвать sleepQ самому, но доставить данные, которые заставят ядро запустить другой поток, а не тот, который работал до прерывания. Таким образом, ядру нужен механизм для запроса принудительного переключения контекста при завершении прерывания. Этот механизм управляется путем установки текущим потоком флага TDFNEEDRESCHED с последующим вызовом асинхронного системного исключе- исключения (asynchronous system trap - AST). AST является ловушкой, которая доставляется потоку в следующий раз, когда поток готовится вернуться из прерывания, исключе- исключения или системного вызова. Некоторые архитектуры обеспечивают непосредствен- непосредственную аппаратную поддержку AST; другие архитектуры эмулируют AST, проверяя флаг AST в конце каждого системного вызова, исключения и прерывания. Когда воз- возникает аппаратный AST или установлен флаг AST, вместо возобновления выполне- выполнения текущего потока вызывается процедура mi_switchQ. Запросы на перепланирова- перепланирование осуществляются процедурами swi_schedQ, resetpriorityQ, setrunnableQ, wakeupQ, roundrobinQ и schedcpuQ. С появлением поддержки многопроцессорности FreeBSD может вытеснять потоки, выполняющиеся в режиме ядра. Однако такое вытеснение обычно не производится, поэтому в худшем случае ответ в реальном времени на события определяется самым длинным путем из верхней половины ядра. Поскольку система гарантирует отсутствие верхних пределов на протяжении системного вызова, FreeBSD, несомненно, не является системой реального времени. Попытки модифицировать BSD с планированием потоков реального времени обращались к этой проблеме различными способами [Ferrin & Langridge, 1980; Sanderson et al., 1986].
132 Глава 4. Управление процессами Планировщик ULE Планировщик ULE был разработан как часть пересмотра FreeBSD для поддержки SMP. Новый планировщик был разработан по нескольким причинам. * Для обращения к потребности притяжения к процессору (processor affinity) в сис- системах SMP. Для предоставления лучшей поддержки симметричной многопоточности (SMT) — процессоры с несколькими ядрами на одном чипе. Для повышения производительности алгоритма планирования таким образом, чтобы он больше не зависел от числа потоков в системе. Целью многопроцессорной системы является применение для решения проблемы или ряда проблем мощи нескольких процессоров, чтобы достичь результата за мень- меньшее время, чем на однопроцессорной системе. Если в системе столько же выпол- выполняющихся потоков, сколько процессоров, достижение этой цели просто. Каждый вы- выполняющийся поток получает для себя свой процессор и выполняется до завершения. Обычно имеется множество выполняющихся потоков, соперничающих за небольшое число процессоров. Одной из задач планировщика является обеспечение того, чтобы процессоры были все время заняты и не расходовали впустую свои циклы. Когда поток завершает свою работу или блокируется в ожидании ресурса, он удаляется из процес- процессора, на котором выполнялся. Пока поток выполняется на процессоре, он заполняет своим рабочим набором - выполняемыми им инструкциями и данными, с которыми он работает, - кеш памяти процессора. Перемещение потока имеет свою цену. Когда поток перемещается от одного процессора на другой, его находящийся в кеше рабочий набор теряется и должен быть удален из процессора, на котором он работал, и загру- загружен в новый процессор, на который он был перемещен. Производительность системы SMP с простым планировщиком, который не принимает в расчет эту стоимость, может оказаться ниже, чем у однопроцессорной системы. Термин притяжение к процессору (processor affinity) описывает планировщик, который перемещает потоки лишь при не- необходимости, чтобы обеспечить работой незанятый процессор. В настоящее время многие микропроцессоры обеспечивают поддержку сим- симметричной многопоточности, когда процессор содержит несколько процессорных ядер, в каждом из которых может выполняться поток. Процессорные ядра в SMT-npo- цессоре разделяют все ресурсы процессора, такие, как кеши памяти и доступ к основ- основной памяти, поэтому они более тесно синхронизированы по сравнению с процессора- процессорами в системе SMP. С точки зрения потока он не знает, что имеются другие потоки, вы- выполняющиеся на том же самом процессоре, поскольку процессор работает с ними не- независимо. Одним из фрагментов кода системы, которому нужно знать о нескольких ядрах, является алгоритм планирования. Случай SMT является слегка отличающейся версией проблемы притяжения к процессору, представленной системой SMP. Каждое процессорное ядро можно рассматривать как процессор со своим собственным
4.4. Планирование потоков 133 набором потоков. В системе SMP, составленной из процессоров, поддерживающих SMT, планировщик рассматривает каждое ядро процессора как менее мощный ресурс, но на который дешевле перемещать потоки. Оригинальный планировщик FreeBSD поддерживает глобальный список процес- процессов, который он просматривает раз в секунду для пересчета их приоритетов. Использо- Использование одного списка для всех потоков означает, что производительность планировщи- планировщика зависит от числа задач в системе, и по мере роста числа задач большее процессорное время должно тратиться в планировщике, поддерживающем список. Целью проекта планировщика ULE - избежать необходимости просмотра всех выполняющихся в сис- системе потоков для принятия планирующих решений. Планировщик ULE создает набор из трех очередей для каждого процессора в сис- системе. Наличие очереди у каждого процессора дает возможность реализовать в системе SMP притяжение к процессору. Одна из очередей является очередью простоя, в которой размещаются все про- простаивающие потоки. Следующие две очереди определены как текущая и следующая. Потоки отбираются на выполнение в порядке приоритетов из текущей очереди, пока она не станет пустой, в этот момент текущая и следующая очереди меняются местами и планирование начинается снова. Потоки в очереди простоя запускаются лишь тогда, когда другие две очереди пусты. Потоки реального времени и прерываний всегда вво- вводятся в текущую очередь таким образом, что у них будет наименьшая возможная за- задержка планирования. Интерактивные потоки также вводятся в текущую очередь, чтобы сохранить интерактивную чувствительность системы на приемлемом уровне. Поток считается интерактивным, если отношение его добровольного времени сна ко времени выполнения меньше некоторого порогового значения. Порог интерактивно- интерактивности определяется в коде ULE и не может конфигурироваться. Для вычисления оценки интерактивности потока ULE использует два уравнения. Для потоков, время сна которых превышает время выполнения, используется уравнение 4.4. _ Фактор масштабирования Оценка интерактивности = — . Сон D.4) Работа Когда время выполнения потока превышает время его сна, используется формула 4.5. _ Фактор масштабирования . г Оценка интерактивности — ь Фактор масштабирования. Работа И ,д с\ Сон Фактор масштабирования является максимальным значением интерактивности, деленным на два. Потоки, оценка интерактивности которых ниже порога, рассматри- рассматриваются интерактивными; все остальные являются неинтерактивными. В течение времени жизни потока в нескольких местах вызывается процедура sched_interact_update() -
134 Глава 4. Управление процессами например, когда поток пробуждается вызовом wakeupQ, - для обновления времени выполнения и времени сна потока. Значения времени сна и времени выполнения могут увеличиваться лишь до определенного предела. Когда сумма времени сна и времени выполнения превышает лимит, они уменьшаются таким образом, чтобы попасть в диа- диапазон. Интерактивный поток, история сна которого вовсе не сохраняется, не остался бы интерактивным, что создает плохое впечатление у пользователя. Сохранение вре- времени сна интерактивного потока в течение слишком длительного периода позволило бы потоку использовать больше положенного времени процессора. Объем сохраняе- сохраняемой истории и порог интерактивности являются двумя значениями, которые сильнее всего влияют на впечатление пользователя от интерактивности системы. Неинтерактивные потоки помещаются в следующую очередь и планируются на вы- выполнение при переключении потоков. Переключение потоков гарантирует, что поток будет выполняться по крайней мере однажды за два переключения очередей независи- независимо от приоритета, что обеспечивает справедливое разделение времени процессора. Имеются два механизма, использующихся для перемещения потоков среди нескольких процессоров. Когда у процессора нет работы ни в одной из его очередей, он отмечает определенный бит в маске битов, которую совместно используют все процес- процессоры, сообщая, что он свободен. Каждый раз, когда активный процессор собирается добавить работу в свою собственную очередь выполнения, он сначала проверяет, не избыточная ли у него работа и нет ли в системе другого простаивающего процессора. Если найден незанятый процессор, поток перемещается на него, используя межпро- межпроцессорное прерывание (interprocessor interrupt- IPI). Принятие решения о перемеще- перемещении путем проверки битовой маски значительно быстрее, чем сканирование очередей выполнения всех других процессоров. Поиск простаивающих процессоров при добав- добавлении новой задачи работает хорошо, поскольку это распределяет работу, когда она появляется в системе. Второй вид перемещения, который называется проталкивающим перемещением (push migration), осуществляется системой на периодической основе и более агрессивно разгружает работу на другие процессоры в системе. Дважды в секунду процедура sched_balance() выбирает наиболее и наименее загруженные процессоры в системе и выравнивает их очереди. Выравнивание осуществляется лишь между двумя процес- процессорами, поскольку предполагалось, что двухпроцессорные системы будут наиболее типичными, и чтобы уберечь алгоритм балансирования от чрезмерного усложнения и неблагоприятного влияния на производительность планировщика. Проталкивающее перемещение обеспечивает равноправие среди выполняющихся потоков. Например, когда на двухпроцессорной системе выполняются три потока, было бы несправедливо, если бы один поток получил для себя процессор, тогда как два других совместно использовали бы второй процессор. За счет перемещения потоков от процессора с двумя потоками на процессор с одним потоком ни один поток не будет выполняться в одиночестве бесконечно.
4.5. Создание процесса 135 ksg_cpus ksg_cpumask ksg_idlemask ksg_mask ksgjoad ksg_transferable ksg_members —' Рис. 4.7. Процессор с двумя ядрами ksq_idle ksq_timeshare[O] ksq_timeshare[l] ksq_next ksq_curr ksq_siblings ksq_group Структура kseq ksq_idle ksq_timeshare[O] ksq_timeshare[l] ksq_next ksq_curr ksq_siblings ksq_group Структура kseq Управление случаем SMT является производной формой балансировки нагрузки среди полноценных процессоров и обрабатывается посредством групп процессоров. Каждое процессорное ядро в процессоре SMT получает свою собственную структуру kseg, и эти структуры группируются под структурой kseg group. Пример одного про- процессора с двумя ядрами показан на рис. 4.7. В системе SMP с несколькими SMT-npo- цессорами на один процессор приходилась бы одна группа процессоров. Когда пла- планировщик решает, на какой процессор или ядро переместить поток, он попытается выбрать ядро на том же самом процессоре, прежде чем выбрать тот или иной процессор, поскольку это путь с наименьшей стоимостью перемещения. 4.5. Создание процесса В FreeBSD новые процессы создаются с помощью семейства системных вызовов fork. Системный вызов fork создает полную копию родительского процесса. Системный вызов rfork создает элемент нового процесса, который вместо копирования всего раз- разделяет со своим родителем выбранный набор ресурсов. Системный вызов vfork отличается от fork в том, как рассматриваются ресурсы виртуальной памяти; vfork гарантирует также, что родитель не будет запущен до тех пор, пока порожденный про- процесс не выполнит системный вызов либо exec, либо exit. Системный вызов vfork описан в разделе 5.6.
136 Глава 4. Управление процессами Процесс, созданный с помощью fork, обозначается как порожденный процесс первоначального родительского процесса. С точки зрения пользователя, порожденный процесс является точным дубликатом родительского процесса, за исключением двух значений: PID порожденного процесса и PID родителя. Вызов fork возвращает PID порожденного процесса в процесс родителя и ноль в порожденный процесс. Таким образом, программа может определить, является ли этот процесс родительским или порожденным, проверив возвращаемое^ог^ значение. Вызов fork включает три главных шага. 1. Выделение и инициализация новой структуры процесса для порожденного процесса. 2. Дублирование для порожденного процесса контекста родителя (включая струк- структуру потока и ресурсы виртуальной памяти). 3. Планирование выполнения порожденного процесса. Второй шаг тесно связан с возможностями управления памяти, описанными в главе 5. Поэтому здесь будут описаны лишь те действия, которые относятся к управ- управлению процессами. Ядро начинает с выделения памяти для нового процесса и элементов потоков (см. рис. 4.1). Эти элементы потоков и процесса инициализируются за три шага: одна часть копируется из соответствующей структуры родителя, другая часть обнуляется, а остальное инициализируется явным образом. Обнуляемые поля включают недавнее использование процессора, канал ожидания, время подкачки и сна, таймеры, трас- трассировку и сведения об ожидающих сигналах. Скопированные части включают все при- привилегии и ограничения, унаследованные от родителя, включая следующее: группу процесса и сеанс; * состояние сигналов (маски игнорируемых, перехватываемых и блокируемых сиг- сигналов); * параметр планирования kg_nice; * ссылку на мандат родителя * ссылку на родительский набор открытых файлов; ссылку на ограничения родителя. Явным образом устанавливаемая информация включает: * элемент в списке всех процессов; * элемент в списке порожденных процессов родителя и обратный указатель на родителя; элемент в списке групп процессов родителя; * элемент в хеш-структуре, которая позволяет искать процессы по их PID;
4.6. Завершение процесса 137 * указатель на структуру статистики процесса, выделенную в его структуре пользо- пользователя; * указатель на структуру действий сигналов процесса, выделенную в его структуре пользователя; * новый PID для процесса. Новый PID должен быть уникальным среди всех процессов. Ранние версии BSD проверяли уникальность PID, осуществляя линейный поиск в таблице процессов. Этот поиск стал на больших системах со множеством процессов неприемлемым. FreeBSD поддерживает диапазон невыделенных PID между lastpid и pidchecked. Она выделяет новый PID, увеличивая и используя значение lastpid. Когда вновь выбранный PID дос- достигает значения pidchecked, система вычисляет новый диапазон неиспользуемых PID, осуществляя единственное сканирование всех существующих процессов (сканируют- (сканируются не только активные - проверяются также зомби и процессы, сброшенные на диск). Последним шагом является копирование адресного пространства родителя. Для дублирования образа процесса ядро использует средства управления памятью, вызы- вызывая vm_forkproc(). Процедуре vm_forkproc() передается указатель на инициализиро- инициализированную структуру порожденного процесса, она должна выделить все ресурсы, которые нужны порожденному процессу для выполнения. Вызов vm_forkproc() возвращается через другой путь выполнения непосредственно в режим пользователя в порожденном процессе и через обычный путь выполнения в процесс родителя. После полного построения порожденного процесса о его потоке сообщается пла- планировщику через помещение его в очередь выполнения. Альтернативный путь возвра- возврата установит в порожденном процессе возвращаемое системным вызовом fork значе- значение в 0. Путь возврата обычного выполнения в родителе установит возвращаемое значение системного ъъпоъъ/огк равным новому PID. 4.6. Завершение процесса Процессы завершаются либо добровольно - посредством системного вызова exit, либо непреднамеренно - в результате сигнала. В любом случае завершение процесса вызы- вызывает возвращение кода состояния родителю завершающегося процесса (если родитель все еще существует). Это состояние завершения возвращается посредством системно- системного вызова wait4. Вызов wait4 дает приложению возможность запрашивать состояние как остановленных, так и завершенных процессов. Запрос wait4 может ожидать любого непосредственного потомка родителя, или он может по выбору ждать один порожденный процесс или только его потомков в определенной группе процессов. Wait4 может также запрашивать статистику, описывающую использование ресурсов завершившимся порожденным процессом. Наконец, интерфейс wait4 дает процессу возможность запрашивать коды состояния, не блокируясь.
138 Глава 4. Управление процессами Внутри ядра процесс завершается путем вызова процедуры exit(). Процедура exitQ сначала уничтожает любые другие связанные с процессом потоки. Завершение других потоков делается следующим образом. Любой поток, входящий в ядро из пространства пользователя, выполнит thread_exitQ, когда попадет в ядро. Любой поток, уже находящийся в ядре и пытающийся войти в состояние сна, немедленно возвратится со значением EINTR или EAGAIN, что вернет их обратно в пространство пользователя, освободив ресурсы. Когда поток пытается вернуться в пространство пользователя, он вместо этого попадет в thread_exit(). Затем процедура exitQ очистит состояние выполнения режима ядра процесса, выполнив следующее: отменив все ожидающие таймеры; освободив ресурсы виртуальной памяти; закрыв открытые дескрипторы; обработав остановленные или трассировавшиеся порожденные процессы. Сбросив состояния режима ядра, процесс далее удаляется из списка активных про- процессов — списка allproc — и помещается в список процессов-зомби, на который указы- указывает zombproc. Состояние процесса изменяется для отображения того, что в текущий момент не выполняется ни один поток. Процедура exitQ делает затем следующее: записывает статус завершения в поле p_xstat структуры процесса; упаковывает копию собранного использования ресурсов процесса (для целей учета) и подвешивает эту структуру к полю р_ги структуры процесса; уведомляет родителя завершившегося процесса. Наконец, после уведомления родителя процедура cpu_exitQ освобождает все машинно- зависимые ресурсы процессора и подготавливает завершающее переключение контекста из данного процесса. Вызов wait4 работает путем поиска потомков процесса, который завершился. Если обнаружен процесс в состоянии ZOMBIE, который подходит к критерию ожидания, система скопирует статус завершения из завершившегося процесса. Затем элемент процесса удаляется из списка зомби и освобождается. Обратите внимание, что ресурсы, используемые потомками процесса, собираются лишь в результате системного вызова wait4. Когда пользователи пытаются анализировать поведение долгоживущих программ, они могли бы счесть для себя полезным иметь возможность получать эти сведения об использовании ресурсов до завершения процесса. Хотя эта информация доступна внутри ядра и в контексте этой программы, интерфейса для ее запроса извне контекста процесса до его завершения нет.
4.7. Сигналы 139 4.7. Сигналы UNIX определяет набор сигналов для программных и аппаратных условий, которые могут возникнуть во время нормального выполнения программы; эти сигналы перечислены в табл. 4.4. Сигналы могут доставляться процессу через определенные приложением обработчики сигналов или могут заканчиваться действиями по умолчанию, такими, как завершение процесса, осуществляемыми системой. Сигналы FreeBSD спроектированы как программные эквиваленты аппаратных прерываний или исключений. У каждого сигнала есть связанное с ним действие, которое определяет, как он должен обрабатываться при доставке процессу. Если процесс содержит более одного потока, каждый поток может указать, хочет ли он предпринимать для каждого из сиг- сигналов действия. Обычно один поток выбирает обработку всех относящихся к процессу сигналов, таких, как прерывание, остановка и продолжение. Все другие потоки в про- процессе запрашивают маскирование относящихся к процессу сигналов. Специфические для потока сигналы, такие, как отказ сегмента, исключение плавающей точки и недей- недействительная инструкция, обрабатываются потоком, который их вызвал. Таким обра- образом, все потоки обычно выбирают получение этих сигналов. Точное размещение сиг- сигналов по потокам приведено в следующем подразделе по отправке сигналов. Сначала мы опишем возможные действия, которые могут быть запрошены. Табл. 4.4. Сигналы, определенные в FreeBSD Имя Действие по умолчанию SIGHUP Завершение процесса SIGINT Завершение процесса SIGQUIT Создание дампа ядра SIGILL Создание дампа ядра SIGTRAP Создание дампа ядра SIGABRT Создание дампа ядра SIGEMT Создание дампа ядра SIGFPE Создание дампа ядра SIGKILL Завершение процесса SIGBUS Создание дампа ядра SIGSEGV Создание дампа ядра SIGSYS Создание дампа ядра SIGPIPE Завершение процесса SIGALRM Завершение процесса Описание Отключение линии терминала Прерывание программы Завершение программы Недействительная инструкция Ловушка трассировки Аварийное завершение Имитирование выполнения инструкции Исключение плавающей точки Уничтожение программы Ошибка шины Нарушение сегментации Ошибочный аргумент в системном вызове Запись в канал при отсутствии его читателя Завершение времени таймера реального вре- времени
140 Глава 4. Управление процессами Табл. 4.4. Сигналы, определенные в FreeBSD Имя SIGTERM SIGURG SIGSTOP SIGTSTP SIGCONT SIGCHLD SIGTTIN SIGTTOU SIGIO SIGXCPU SIGXFSZ SIGVTALRM SIGPROF SIGWINCH SIGINFO SIGUSR1 SIGUSR2 Действие по умол* Завершение процесса Сбрасывание сигнала Остановка процесса Остановка процесса Сбрасывание сигнала Сбрасывание сигнала Остановка процесса Остановка процесса Сбрасывание сигнала Завершение процесса Завершение процесса Завершение процесса Завершение процесса Сбрасывание сигнала Сбрасывание сигнала Завершение процесса Завершение процесса Описание Программный сигнал завершения Срочное событие в канале ввода/вывода Сигнал остановки не от терминала Сигнал остановки от терминала Возобновление остановленного процесса Уведомление родителя об остановке или за- завершении потомка Чтение с терминала фоновым процессом Запись в терминал фоновым процессом Для дескриптора возможен ввод/вывод Превышение предела времени процессора Превышение предела размера файла Завершение времени виртуального таймера Завершение времени таймера профилировки Изменение размера окна Запрос информации Определяемый пользователем сигнал 1 Определяемый пользователем сигнал 2 Описание сигналов указывается для каждого процесса. Если процесс не указал действие на сигнал, ему предоставляется действие по умолчанию (см. табл. 4.4), которое может быть одним из следующих: игнорирование (сбрасывание) сигнала; * завершение всех потоков в процессе; завершение всех потоков в процессе после создания файла core, содержащего состояние выполнения процесса на момент получения сигнала; остановка всех потоков в процессе; * возобновление выполнения всех потоков в процессе. Программа приложения может использовать системный вызов sigaction для указания действия для сигнала, включая следующие возможности: выполнение действия по умолчанию; игнорирование сигнала; * перехват сигнала с помощью обработчика.
4.7. Сигналы 141 Обработчик сигнала является процедурой режима пользователя, которую система вызывает при получении процессом сигнала. Говорят, что обработчик перехватывает сигнал. Два сигнала SIGSTOP и SIGKILL нельзя замаскировать, игнорировать или пере- перехватить; это ограничение гарантирует, что для остановки и завершения вышедшего изпод контроля процесса существует программный механизм. Процесс не может решить, какие сигналы вызовут создание по умолчанию файла core, но процесс может предотвратить создание такого файла путем игнорирования, блокирования или перехвата сигнала. Сигналы отправляются процессу системой, когда она обнаруживает аппаратное событие, такое, как недействительная инструкция, или программное событие, такое, как запрос остановки от терминала. Сигнал может быть также отправлен другим процессом посредством системного вызова kill. Отправляющий процесс может посылать сигналы лишь тем получающим процессам, у которых такой же эффективный идентификатор пользователя (если только отправляющий не является суперпользователем). Единствен- Единственным исключением из этого правила является сигнал продолжения, SIGCONT, который всегда можно отправить любому потомку отправляющего процесса. Причина этого исключения заключается в том, чтобы дать пользователям возможность повторно запус- запустить некоторую setuid-программу, которую они остановили со своей клавиатуры. Подобно аппаратным прерываниям, каждый поток в процессе может маскировать доставку сигналов. Состояние выполнения каждого потока содержит набор сигналов, доставка которых в настоящий момент замаскирована. Если посылаемый потоку сигнал маскируется, он записывается в набор ожидающих сигналов потока, но никакого действия не предпринимается, до того как маска сигнала не будет снята. Системный вызов sigprocmask изменяет набор замаскированных сигналов для потока. Он может добавить к набору замаскированных сигналов, удалить из набора замаскированных сигналов или заменить набор замаскированных сигналов. Хотя доставку сигнала SIG- SIGCONT обработчику сигнала процесса можно замаскировать, действие возобновления этого остановленного процесса не маскируется. Двумя имеющими отношение к сигналам системными вызовами являются sigsus- pend и sigaltstack. Вызов sigsuspend позволяет потоку освободить процессор до тех пор, пока поток не получит сигнал. Эта возможность сходна с системной процедурой sleepQ. Вызов sigaltstack позволяет процессу указать стек времени выполнения для использования в доставке сигнала. По умолчанию система доставит сигналы процессу в его обычный стек времени выполнения. Однако в некоторых приложениях это дейст- действие по умолчанию неприемлемо. Например, если у приложения может быть много потоков, которые разделили обычный стек времени выполнения на множество неболь- небольших фрагментов, гораздо более эффективно для памяти создать один большой стек сигналов, в котором все потоки будут обрабатывать свои сигналы, чем резервировать пространство для сигналов в стеке каждого потока. Последней возможностью, связанной с сигналами, является системный вызов sigreturn. Sigreturn является эквивалентом операции загрузки контекста процессора на уровне пользователя. Ядру передается указатель на (машинно-зависимый) блок контекста,
142 Глава 4. Управление процессами описывающий состояние выполнения уровня пользователя для потока. Системный вызов sigreturn восстанавливает состояние и возобновляет выполнение после обычного возврата из обработчика сигнала пользователя. История сигналов Сигналы первоначально были спроектированы для моделирования исключительных событий, таких, как попытка пользователя завершить вышедшую из-под контроля про- программу. Они не предназначались для использования в качестве общего механизма меж- межпроцессного взаимодействия, поэтому не было сделано попытки сделать их надежными. В более ранних системах каждый раз при перехвате сигнала восстанавливалось его дей- действие по умолчанию. Введение управления заданиями привело к гораздо более частому использованию сигналов и сделало более заметной проблему, которую также усугубили более быстрые процессоры: если были быстро отправлены два сигнала, второй мог бы заставить процесс завершиться, даже если был установлен обработчик сигнала для пере- перехвата первого сигнала. Таким образом, стала желательной надежность, поэтому разра- разработчики создали новую инфраструктуру, которая содержала в качестве подмножества старые возможности, предоставляя в то же время новые механизмы. Возможности сигналов, которые можно найти в FreeBSD, спроектированы вокруг модели виртуальной машины, в которой системные вызовы рассматриваются как сход- сходные с набором аппаратных инструкций машины. Сигналы являются программным эк- эквивалентом исключений и прерываний, а процедуры обработки сигналов выполняют функцию, эквивалентную процедурам обслуживания прерываний или исключений. Так же как машины предоставляют механизм для блокирования прерываний, чтобы обеспечить согласованный доступ к структурам данных, возможности сигналов позво- позволяют маскировать программные сигналы. Наконец, из-за сложного окружения стека времени выполнения, которое может им требоваться, сигналы, подобно прерываниям, могут обрабатываться в альтернативном стеке, предоставленном приложением. Сводка этих машинных моделей приведена в табл. 4.5. Табл. 4.5. Сравнение аппаратно-машинных операций и соответствующих операций виртуальной машины Аппаратная машина Набор инструкций Повторно запускаемые инструкции Прерывания/исключения Обработчики прерываний/исключений Блокирование прерываний Стек прерываний Программная виртуальная машина Набор системных вызовов Повторно запускаемые системные вызовы Сигналы Обработчики сигналов Маскирование сигналов Стек сигналов
4.7. Сигналы 143 Отправка сигнала Реализация сигналов разделена на две части, первая из которых - отправка сигнала процессу, а вторая - распознавание сигнала и доставка его потоку назначения. Сигна- Сигналы может отправлять любой процесс или код, который выполняется на уровне преры- прерывания. Доставка сигнала обычно имеет место в контексте получающего потока. Но когда сигнал форсирует остановку процесса, действие может быть проведено для всех потоков, связанных с этим процессом в тот момент, когда сигнал был отправлен. Сигнал может быть отправлен одному процессу посредством процедуры psignalQ или группе процессов посредством процедуры gsignalQ. Процедура gsignalQ вызывает psignal() для каждого процесса в указанной группе процессов. Действия, связанные с отправкой сигнала, простые, но детали беспорядочны. Теоретически отправка сигна- сигнала процессу вызывает просто добавление соответствующего сигнала в набор ожи- ожидающих сигналов для соответствующего потока в процессе, а выбранный поток уста- устанавливается потом для выполнения (или пробуждается, если он находился в состоянии сна на прерываемом уровне приоритета). Для каждого процесса устанавливается свое поведение сигналов. Поэтому ядро сначала проверяет, не должен ли сигнал игнорироваться - в этом случае он уничтожается. Если процесс указал действие по умолчанию, выбирается действие по умолчанию. Если процесс указал обработчик сигнала, который должен быть запущен, ядро должно выбрать соответствующий поток в процессе, который должен обработать сигнал. Когда сигнал возник в результате действия выполняющегося в настоящий момент потока (например, ошибка сегмента), ядро лишь попытается доставить его этому потоку. Если поток маскирует сигнал, последний будет ожидать до тех пор, пока маска не будет снята. При отправке сигнала, имеющего отношение к процессу (например, прерывания), ядро просматривает все связанные с процессом потоки, отыскивая тот, который не замаскиро- замаскировал сигнал. Сигнал доставляется первому обнаруженному потоку с незамаскированным сигналом. Если сигнал маскируют все связанные с процессом потоки, сигнал оставляется в списке сигналов процесса, ожидающих доставки в последующем. Процедура cursigQ вычисляет следующий сигнал, если он есть, который должен быть доставлен потоку. Она определяет следующий сигнал, проверяя в списке сигналов процесса, p_siglist, нет ли там каких-нибудь сигналов, которые должны передаваться в список сигналов потока, td_siglist. Затем она проверяет в поле tdjsiglist наличие сиг- сигналов, которые должны быть доставлены потоку. Каждый раз по возвращении потока из вызова sleepQ (с установленным флагом РСАТСН) или при подготовке к выходу из системы после обработки системного вызова или исключения она проверяет, нет ли ожидающих доставки сигналов. Если есть ожидающий сигнал, который должен быть доставлен в контексте потока, он удаляется из списка ожидания, а поток вызывает про- процедуру postingQ для выполнения соответствующего действия.
144 Глава 4. Управление процессами Работа psignalQ представляет собой мешанину частных случаев, необходимых средствам отладки процессов и управления заданиями и собственными свойствами, связанными с сигналами. Шаги, вовлеченные в отправку сигнала, следующие. 1. Определение действия, которое получающий процесс должен предпринять при дос- доставке сигнала. Эта информация хранится в поляхpjsigignore и p_sigcatch структуры процесса. Если процесс не игнорирует или не перехватывает сигнал, подразумева- подразумевается применение действия по умолчанию. Если процесс трассируется его родителем, т.е. отладчиком, родительскому процессу всегда разрешается вмешиваться до доставки сигнала. Если процесс игнорирует сигнал, работа psignalQ сделана, и процедура может возвращаться. 2. Если указано действие, psignalQ выбирает соответствующий поток и добавляет сигнал к списку ожидающих сигналов потока, td_siglist, а затем выполняет любые неявные действия, специфичные для этого сигнала. Например, если сигнал являет- является сигналом продолжения, SIGCONT, все ожидающие сигналы, которые обычно вызвали бы остановку процесса, такие, как SIGTTOU, удаляются. 3. Затем psignalQ проверяет, замаскирован ли сигнал. Если поток в настоящий момент маскирует доставку сигнала, работа процедуры psignalQ завершена и она может возвращаться. 4. Если, однако, сигнал не маскируется, процедурapsignaIQ должна либо непосредст- непосредственно выполнить действие, либо организовать выполнение потока таким образом, чтобы поток выполнил действие, связанное с сигналом. Перед переводом потока в состояние выполнения psignalQ должна предпринять различные образы действия в зависимости от состояния потока следующим образом SLEEPING Поток заблокирован в ожидании события. Если поток находится в со- состоянии непрерываемого сна, больше ничего не должно предприни- предприниматься. В противном случае ядро может предпринять действие - либо непосредственно, либо косвенно, - пробудив поток. Есть два дейст- действия, которые можно применить непосредственно. Для сигналов, вызывающих остановку процесса, все потоки в процессе помещаются в состояние STOPPED и родительский процесс уведомляется об из- изменении состояния посредством отправки ему сигнала SIGCHLD. Для сигналов, которые по умолчанию игнорируются, сигнал удаляется из списка сигналов и работа завершается. В противном случае в контексте получающего потока должно быть осуществлено дейст- действие, связанное с сигналом, и поток помещается в очередь выполнения с помощью вызова setrunnableQ.
4.7. Сигналы 145 STOPPED RUNNABLE, NEW, ZOMBIE Процесс остановлен сигналом или из-за того, что он отлаживается. Если процесс отлаживается, ничего не нужно делать до тех пор, пока управляющий процесс не разрешит запустить его снова. Если про- процесс остановлен сигналом, а отправленный сигнал снова вызвал бы остановку процесса, ничего не нужно делать, и посланный сигнал уничтожается. В противном случае сигнал является либо сигналом продолжения, либо сигналом, который обычно вызвал бы заверше- завершение процесса (если он не перехвачен). Если это сигнал SIGCONT, все потоки процесса, которые ранее выполнялись, снова становятся выполняющимися. Все потоки в процессе, которые были заблокиро- заблокированы в ожидании события, возвращаются в состояние SLEEPING. Если это сигнал SIGKILL, все потоки в процессе независимо ни от чего становятся выполняющимися таким образом, чтобы они могли завершиться, когда в следующий раз будет запланировано их выпол- выполнение. В противном случае сигнал переводит все процессы в состоя- состояние готовности к выполнению (runnable), но они не помещаются в очередь выполнения, поскольку они должны ждать сигнала продол- продолжения. Если поток, запланированный для получения сигнала, не является текущим выполняющимся потоком, устанавливается флаг TDF_NEEDRESCHED таким образом, что сигнал будет замечен получающим потоком как можно быстрее. Доставка сигнала Большинство действий, связанных с доставкой сигнала потоку, выполняется в контек- контексте этого потока. Поток проверяет свое поле td_siglist на предмет наличия ожидающих сигналов по крайней мере один раз при входе в систему, вызывая cursigQ. Если cursigQ обнаруживает, что в списке сигналов потока имеются какие-либо незамаскированные сигналы, она вызывает issignalQ, чтобы найти первый немаскиро- немаскированный сигнал в списке. Если доставка сигнала должна вызвать обработчик сигнала или создать дамп ядра, вызывающий уведомляется о наличии ожидающего сигнала, а доставка осуществляется посредством вызоваpostingQ, т.е.: if(sig = cursig(curthread)) postsig(sig); В противном случае действие, связанное с сигналом, осуществляется внутри issignalQ (эти действия имитируют действия, выполняемые psignalQ). ПроцедураpostsigQ обрабатывает два случая. 1. Создание дампа ядра. 2. Вызов обработчика сигнала.
146 Глава 4. Управление процессами Первая задача осуществляется процедурой coredumpQ, и за ней всегда следует вызов exitQ для форсирования завершения процесса. Для вызова обработчика сигнала postsigQ сначала вычисляет набор замаскированных сигналов и устанавливает этот набор в td_sigmask. Этот набор обычно включает сигналы в процессе доставки, так что обработчик сигнала не будет рекурсивно вызван одним и тем же сигналом. Также будут включены все сигналы, указанные в системном вызове sigaction в момент уста- установки обработчика. Процедура postsigO вызывает затем процедуру sendsigO для орга- организации немедленного выполнения обработчика сигнала после возвращения потока в режим пользователя. Наконец, сигнал в td_siglist очищается и postsigO возвращается в предположении, что за ним последует возврат в режим пользователя. Шаг 1- sendsigO , Фреймп Контекст сигнала Фреймп Шаг 4- sigreturn{) Шаг 2 - вызвана sigtrampQ Фреймп Контекст сигнала Обработчик сигнала Фреймп Контекст сигнала ' Шаг 3 — sigreturnQ возвращается Рис. 4.8. Доставка сигнала процессу. Шаг 1: ядро помещает контекст сигнала в стек пользователя. Шаг 2: ядро помещает фрейм обработчика сигнала в стек пользователя и организует запуск процесса пользователя в коде sigtrampQ. Когда процедура sigtrampQ начинает выполнение, она вызывает пользовательский обработчик сигнала. Шаг 3: пользователь- пользовательский обработчик сигнала возвращается в процедуру sigtrampQ, которая выталкивает контекст обработчика сигнала из стека пользователя. Шаг 4: процедура sigtrampQ завершается путем системного вызова sigreturn, который восстанавливает из контекста сигнала предыдущий контекст пользователя, выталкивает из стека контекст сигнала и возобновляет выполнение процесса пользователя с той точки, в которой он выполнялся до появления сигнала Реализация процедуры sendsigQ машинно-зависимая. На рис. 4.8 показан поток управления, связанный с доставкой сигнала. Если был запрошен альтернативный стек, указатель стека пользователя переключается на этот стек. Список аргументов в текущем контексте выполнения потока режима пользователя сохраняется ядром в (возможно
4.8. Группы процессов и сеансы 147 новом) стеке. Состояние потока управляется таким образом, что по возвращении в режим пользователя будет немедленно сделан вызов в тело кода, который обознача- обозначается как код трамплина сигнала (signal-trampoline code). Этот код вызывает обра- обработчик сигнала (на рис. 4.8 между шагами 2 и 3) с соответствующим списком аргумен- аргументов и, если обработчик возвращается, осуществляет системный вызов sigreturn для восстановления состояния сигнала потока в то состояние, которое существовало до сигнала. 4.8. Группы процессов и сеансы Группа процессов является совокупностью связанных процессов, таких, как конвейер оболочки, каждому из которых присвоен один и тот же идентификатор группы про- процессов. Идентификатор группы процессов равен PID начального участника группы процессов; таким образом, идентификаторы группы процессов разделяют пространст- пространство имен идентификаторов процессов. Когда создается новая группа процессов, ядро выделяет структуру группы процессов, которая будет с ней связана. Эта структура группы процессов записывается в хеш-таблицу таким образом, чтобы ее можно было быстро найти. Процесс всегда является членом одной группы процессов. При своем создании каждый процесс помещается в группу своего родительского процесса. Новые группы процессов создают такие программы, как оболочки, обычно помещая в группу связан- связанные порожденные процессы. Процесс может изменить свою группу процессов или группу порожденного процесса, создав новую группу процессов или переместив про- процесс в существующую группу с использованием системного вызова setpgid. Например, когда оболочка хочет установить новый конвейер, ей необходимо поместить процессы в конвейере в группу процессов, отличающуюся от ее собственной, таким образом, чтобы конвейер можно было контролировать независимо от оболочки. Оболочка начи- начинает с создания первого процесса в конвейере, который вначале имеет тот же самый идентификатор группы процессов, как у оболочки. До выполнения программы на- назначения первый процесс вызывает setpgid, чтобы установить в качестве идентифика- идентификатора своей группы процессов свой собственный PID. Этот системный вызов создает новую группу процессов с порожденным процессом в качестве лидера группы процессов. Когда оболочка запускает для конвейера дополнительные процессы, каждый порож- порожденный процесс использует setpgid, чтобы присоединиться к существующей группе процессов. В нашем примере оболочки, создающей новый конвейер, имеется состояние гонки (race condition). По мере запуска оболочкой в конвейере дополнительных процессов каждый помещается в группу процессов, созданную первым процессом в конвейере. Эти соглашения проводятся в жизнь системным вызовом setpgid. Он ограничивает набор идентификаторов группы процессов, которые могут быть установлены для про-
148 Глава 4. Управление процессами цесса, либо равным собственному PID процесса, либо значением идентификатора другой группы процессов в его сеансе. К сожалению, если процесс конвейера, не являющийся лидером группы процессов, создан до того, как лидер группы процессов завершил свой вызов setpgid, вызов setpgid для присоединения к группе процессов завершится неудачей. Поскольку вызов setpgid разрешает родителям устанавливать группу процессов своих потомков (в пределах ограничений, налагаемых интересами безопасности), оболочка может избежать этого состязания, заставив вызов setpgid изменить группу процессов потомка как во вновь порожденном процессе, так и в роди- родительской оболочке. Этот алгоритм гарантирует, что вне зависимости от того, какой процесс запустится первым, будет существовать группа процесса с нужным лидером группы процессов. Оболочка может также избежать состязания, используя вариант vfork системного вызова fork, который заставляет процесс родителя ожидать до тех пор, пока порожденный процесс либо не выполнит системный вызов exec, либо не завершится. Кроме того, если первоначальные члены группы процессов завершатся до того, как все члены конвейера присоединятся к группе (например, если лидер группы процессов завершится до того, как к группе присоединится второй процесс), вызов setpgid может завершиться неудачей. Оболочка может избежать этого состязания, обеспечив помещение в группу процессов всех порожденных процессов без использования сис- системного вызова wait, обычно путем блокирования сигнала SIGCHLD таким образом, что оболочка не будет еще уведомлена, если потомок завершится. Пока существуют члены группы процессов, даже в виде процессов зомби, к группе процессов могут при- присоединяться дополнительные процессы. Имеются дополнительные ограничения на системный вызов setpgid. Процесс может присоединиться к группе процессов лишь в рамках своего текущего сеанса (который обсуждается в следующем разделе), и он не должен до этого делать систем- системный вызов exec. Последнее ограничение предназначено для избежания неожиданного поведения, если процесс будет перемещен в другую группу процессов после начала его выполнения. Поэтому, когда оболочка вызывает setpgid как в родительском, так и в порожденном процессе после fork, вызов, сделанный родителем, завершится неудачей, если порожденный процесс уже выполнил вызов exec. Однако потомок будет уже успешно присоединен к группе процессов, и эта неудача безопасна. Сеансы Так же как наборы связанных процессов собираются в группу процессов, наборы групп процессов собираются в сеансы. Сеанс является набором одной или более групп процессов и может быть связан с устройством терминала. Основным использованием для сеансов является соединение в одно целое оболочки регистрации пользователя и заданий, которые он запускает, и создание изолированного окружения для процесса демона и его потомков. Любой процесс, не являющийся уже лидером группы процес- процессов, может создать сеанс, используя системный вызов setsid и становясь лидером сеанса и единственным членом сеанса. Создание сеанса создает также новую группу
4.8. Группы процессов и сеансы 149 процессов, ID группы процессов которой является PID процесса, создающего сеанс, а процесс является лидером группы процессов. По определению все члены группы процессов являются членами одного сеанса. Сеанс Управляющий процесс 3 Группа процессов Процесс 4 Г^~П Процесс 5 Группа процессов 4 3 Процесс 8 Группа процессов > о Рис. 4.9. Сеанс и его процессы. В данном примере процесс 3 является начальным членом сеанса - лидером сеанса - и называется управляющим процессом, если у него есть управляющий терминал. Он содержится в своей собственной группе процессов 3. Процесс 3 запустил две задачи: одна является конвейером, составленным из процессов 4 и 5, сгруппиро- сгруппированных в группе процессов 4, а другая является процессом 8, который находится в своей собственной группе процессов 8. Ни один лидер группы процессов не может создать новый сеанс; таким образом, процессы 3, 4 или 8 не могли бы начать свой соб- собственный сеанс, но процессу 5 это было бы разрешено У сеанса может быть связанный с ним управляющий терминал, который использует- используется по умолчанию для взаимодействия с пользователем. Только лидер сеанса может выде- выделять для сеанса управляющий терминал, становясь управляющим процессом, когда он это делает. Устройство в одно и то же время может быть управляющим терминалом лишь для одного сеанса. Система ввода/вывода терминала (описанная в главе 10) синхронизирует доступ к терминалу, разрешая в любой данный момент времени лишь одной группе про- процессов быть приоритетной (foreground) группой процессов для управляющего терминала. Некоторые операции терминала ограничены участниками сеанса. Сеанс может иметь самое большее один управляющий терминал. При создании сеанса лидер сеанса отделя- отделяется от своего управляющего терминала, если он у него был. Сеанс регистрации создается программой, подготавливающей терминал для реги- регистрации пользователя в системе. Этот процесс обычно запускает для пользователя обо- оболочку, и соответственно оболочка создается в качестве управляющего процесса. Пример типичного сеанса регистрации показан на рис. 4.9. Структуры данных, использованные для поддержки сеансов и групп процессов в FreeBSD, показаны на рис. 4.10. Этот рисунок соответствует расположению процес- процессов, показанному на рис. 4.9. Поле pgjnembers структуры группы процессов озаглав- озаглавливает список процессов-членов; эти процессы соединены вместе через элемент списка p_pglist в структуре процесса. Кроме того, у каждого процесса есть ссылка на свою структуру группы процессов в поле p_pgrp структуры процесса. У каждой
150 Глава 4. Управление процессами структуры группы процессов есть указатель на содержащий его сеанс. Структура сеанса отслеживает сведения о регистрации, включая процесс, создавший и управ- управляющий сеансом, управляющий терминал сеанса и регистрационное имя, связанное с сеансом. Два процесса, желающие определить, находятся ли они в одном сеансе, могут проследить свои указатели p_pgrp, чтобы обнаружить свои структуры групп процессов, а затем сравнить, имеют ли указатели pg_session одно и то же значение. ЗАГОЛОВОК СПИСКА pgrphashtbl ^struct pgrp pgjd = 8 pgjobc = 1 P-Pgrp 'ist V pgjiash struct pgrp pgjd = 4 pgjobc = 2 P-Pgrp p_pgrp Процесс 5 p_pglist pgjiash pgjd = 3 pgjobc = 0 t_pgrp (приоритетная группа процессов) P-Pgrp pg_members\ Процесс L V pgjiash struct session struct pgrp s count = 3 pg_session pg_sesswn pgjsession sjogin sjttyvp struct tty t_session sjtyp t termios t_winsize Рис. 4.10. Организация групп процессов Управление заданиями Управление заданиями является возможностью, впервые предоставленной оболочкой С [Joy, 1994], а сегодня предоставляемой большинством оболочек. Оно позволяет поль- пользователю управлять деятельностью групп процессов, называемых заданиями (jobs). Наиболее важными средствами, предоставляемыми управлением заданиями, являются возможности приостанавливать и повторно запускать задания и осуществлять мульти-
4.8. Группы процессов и сеансы 151 плексирование доступа к терминалу пользователя. Управление терминалом в одно и то же время предоставляется лишь одному заданию, которое может читать с терминала и записывать в него. Эта возможность предоставляет некоторые преимущества окон- оконных систем, хотя управление заданиями достаточно отличается, чтобы использоваться в комбинации с оконными системами. Управление заданиями реализуется поверх средств групп процессов, сеансов и сигналов. Каждое задание - это группа процессов. Вне ядра оболочка управляет заданиями, посылая группе процессов задания сигналы с помощью системного вызова killpg, ко- который доставляет сигнал всем процессам в группе. Внутри системы двумя основными пользователями групп процессов являются обработчик терминала (глава 10) и средст- средства межпроцессного взаимодействия (глава 11). Оба средства записывают идентифика- идентификаторы групп процессов в своих собственных структурах данных и используют их при доставке сигналов. Кроме того, обработчик сигнала использует группы процессов для мультиплексирования доступа к управляющему терминалу. Например, специальные символы, набираемые на клавиатуре терминала (например, control-C или controlA), вызывают отправку сигналов всем процессам в одном задании внутри сеанса; это задание приоритетное (foreground), тогда как все остальные зада- задания в сеансе фоновые (background). Оболочка может изменить фоновое задание, ис- использовав функцию tcsetpgrpQ, реализованную ioctl TIOCSPGRP управляющего терминала. Если фоновые задания сделают попытку чтения с терминала, им будет отправлен сигнал SIGTTIN, что обычно приводит к остановке задания. Сигнал SIGTTOU посылается фоновым заданиям при попытке выполнить системный вызов ioctl, который изменил бы состояние терминала, а если для терминала установлена опция TOSTOP, то и при попытке записи в терминал. Приоритетная группа процессов сеанса хранится в поле t_pgrp структуры tty управляющего терминала сеанса (см. главу 10). Все другие группы процессов в преде- пределах сеанса фоновые. На рис. 4.10 лидер сеанса установил в качестве приоритетной группы процессов для своего управляющего терминала свою собственную группу про- процессов. Таким образом, два его задания находятся в фоновом режиме, а ввод и вывод терминала управляется оболочкой лидера сеанса. Управление заданиями ограничено процессом, который относится к тому же сеансу и терминалу, связанному с данным се- сеансом. Переназначать управляющий терминал среди групп процессов сеанса разреша- разрешается лишь участникам сеанса. Если управляющий процесс завершается, система отменяет дальнейший доступ к управляющему терминалу и посылает фоновой группе процессов сигнал SIGHUP. Если завершается такой процесс, как управляющая заданиями оболочка, каждая группа процессов, которую эта оболочка создала, становится осиротевшей (orphaned) группой процессов: группой процессов, в которой ни у одного члена нет родителя, который является членом того же сеанса, но другой группы процессов. Таким родителем обычно является управляющая заданиями оболочка, способная возобновить остановлен- остановленный порожденный процесс. Поле pg_jobc на рис. 4.10 подсчитывает число процессов
152 Глава 4. Управление процессами внутри группы, родителем которых является управляющий процесс. Когда это значе- значение достигает нуля, группа процессов оказывается осиротевшей. Если бы система не предприняла никаких действий, осиротевшие группы процессов, которые были оста- остановлены в то время, когда они стали осиротевшими, вряд ли когда-либо возобнови- возобновились. Исторически система распоряжалась с такими процессами решительно: они уничтожались. В POSIX и FreeBSD зависшей группе процессов посылаются сигналы зависания и продолжения, если какие-нибудь из ее членов останавливаются, когда она становится осиротевшей из-за завершения родительского процесса. Если процессы перехватывают или игнорируют сигнал зависания, они могут продолжить работу после того, как станут осиротевшими. Система ведет подсчет процессов в каждой группе, у которых родительский процесс находится в другой группе процессов в том же сеансе. Когда процесс завершается, это число настраивается по числу групп всех порожденных процессов. Если число достигает нуля, группа процессов стала осиро- осиротевшей. Обратите внимание, что процесс может быть членом осиротевшей группы процессов, даже если его собственный первоначальный родительский процесс по- прежнему действует. Например, если оболочка начинает задание в качестве одного процесса А, затем этот процесс разветвляется с созданием процесса В, а родительская оболочка существует, то процесс В является членом осиротевшей группы процессов, но не является осиротевшим процессом. Чтобы избежать остановки членов осиротевших групп процессов, когда они пытаются читать или записывать в свой управляющий терминал, ядро не посылает им сигналы SIGTTIN и SIGTTOU и предотвращает их остановку в ответ на эти сигналы. Вместо этого попытки чтения или записи терминала создают ошибку. 4.9. Тюрьмы Механизм управления доступом FreeBSD спроектирован для окружения с двумя видами пользователей: с административными привилегиями и без них. Часто жела- желательно делегировать некоторые, но не все административные функции сторонам, не пользующимся доверием или пользующимся меньшим доверием, и в то же время уста- установить обязательные для всей системы политики на взаимодействие и совместное использование процессов. Исторически попытки создания такого окружения были как трудными, так и дорогими. Главным механизмом для частичного делегирования адми- административных полномочий является написание программы с устанавливаемым иден- идентификатором пользователя, которая тщательно контролирует, какие из административ- административных привилегий можно использовать. Эти программы с устанавливаемым идентифи- идентификатором сложны для написания, трудны в сопровождении, ограничены в своей гибкости и подвержены ошибкам, которые позволяют получать нежелательные административные привилегии.
4.9. Тюрьмы 153 Многие операционные системы пытались снять эти ограничения, предоставляя управление системными ресурсами на более урезанном уровне [РЮОЗЛе, 1998]. Эти усилия отличаются в степени успешности, но почти все они страдают от трех серьез- серьезных ограничений. 1. Повышение степени детализации управления безопасностью увеличивает сложность процесса администрирования, что в свою очередь повышает как возможности непра- неправильного конфигурирования, так и требования к времени и ресурсам администриро- администрирования. Часто возрастание сложности ведет к значительной неудовлетворенности администратора, что может привести к двум видам пагубной политики: работе с отключенными возможностями безопасности и работе с конфигурацией по умолчанию в предположении, что она будет безопасной. 2. Возможности полезного разделения и приписывания их работающему коду и пользо- пользователям сложны. Многие привилегированные операции в FreeBSD выглядят неза- независимыми, но на самом деле взаимосвязаны. Выдача одной привилегии может быть транзитивным по отношению ко многим другим. Например, возможность монтировать файловые системы создает возможность сделать доступными новые программы с устанавливаемым идентификатором пользователя, что в свою очередь может непреднамеренно предоставить другие возможности в плане безопасности. 3. Введение новых возможностей безопасности часто включает введение новых интерфейсов управления безопасностью. Когда для замещения механизма уста- установки идентификатора пользователя в FreeBSD вводятся возможности более точного уровня модульности, приложения, которые раньше осуществляли перед своим выполнением проверку своей работы с правами суперпользователя, теперь должны быть изменены таким образом, чтобы не требовать для работы привиле- привилегий суперпользователя. Для приложений, работающих с привилегиями и выпол- выполняющих другие программы, теперь есть набор привилегий, от которых нужно добровольно отказаться перед выполнением другой программы. Такие изменения могут создать для существующих приложений значительную несовместимость и усложнить жизнь для разработчиков приложений, которые могут не знать о раз- различной семантике безопасности на различных системах. Этот абстрактный риск становится более ясным при использовании реального практического примера: многие провайдеры веб-служб используют FreeBSD для раз- размещения веб-сайтов заказчиков. Эти провайдеры должны защитить целостность и кон- конфиденциальность своих собственных файлов и служб от своих заказчиков. Они должны также защищать файлы и службы одного заказчика от (случайного или умыш- умышленного) доступа другим заказчиком. Провайдер хотел бы предоставить заказчикам значительную автономию, позволяя им устанавливать и поддерживать свое собственное
154 Глава 4. Управление процессами программное обеспечение и управлять своими собственными службами, такими, как веб-серверы и другие программы-демоны, связанные с содержанием. Это проблемное пространство решительно указывает в направлении решения раз- разбиения на разделы. При помещении каждого заказчика в отдельный раздел они изо- изолируются от случайной или умышленной модификации данных или раскрытия ин- информации процессов заказчиками в других разделах. Делегирование функций управ- управления внутри системы должно быть возможно без нарушения целостности и защиты конфиденциальности между разделами. Управление доступом в стиле FreeBSD делает общеизвестно трудным возможно- возможности разделения на отсеки. Хотя такие механизмы, как chroot, предоставляют умерен- умеренный уровень обособления, у этого механизма есть серьезные недостатки как в масшта- масштабах его возможностей, так и предоставляемой эффективности. Системный вызов chroot впервые был добавлен для предоставления альтернативной среды построения для системы. Затем он был приспособлен для изолирования анонимного доступа ftp к системе. Первоначальной целью chroot не было обеспечение безопасности. Даже при использовании для обеспечения безопасности для анонимного ftp набор допускаемых ftp операций тщательно контролировался, чтобы не допустить те из них, которые разрешали ускользать из среды chroot. С годами были обнаружены три класса ухода за границы созданной chroot файловой системы. 1. Рекурсивные уходы chroot. 2. Уходы с использованием ... 3. Уходы с использованиемусЫ/г. Все эти уходы использовали отсутствие наблюдения за применением нового корневого каталога. Для обнаружения и пресечения этих уходов были сделаны два изменения в chroot. Для предотвращения первых двух уходов записывался каталог первого уровня chroot, известный процессу. Любые попытки проходить назад через этот каталог запреща- запрещались. Третий уход с использованиемус/zd/r предотвращается возвращением системным вызовом chroot ошибки, если у процесса были какие-либо открытые дескрипторы файлов, ссылающиеся на каталоги. Даже с более строгой семантикой системного вызова chroot недостаточно для обеспечения полного разделения. Его обособление не простирается на пространства процессов или сетей. Следовательно, возможно как наблюдение, так и вмешательство в процессы за пределами своих отделений. Для предоставления окружения безопасной виртуальной машины FreeBSD добавило новое средство «тюрьма» (jail), построенное поверх chroot. Процессам в тюрьме предоставляется полный доступ к файлам, которыми
4.9. Тюрьмы 155 они могут манипулировать, процессам, на которые они могут влиять, и сетевым службам за пределами их тюрьмы [Kamp & Watson, 2000]. В отличие от других решений проблемы безопасности с точным уровнем модуль- модульности тюрьма не повышает в существенной степени требования к управлению полити- политикой для системного администратора. Каждая тюрьма является виртуальной средой FreeBSD, которая допускает независимое управление локальной политикой. Среда внутри тюрьмы имеет те же свойства, как для центральной системы. Таким образом, среда тюрьмы знакома администратору и совместима с приложениями [Норе, 2002]. Семантика тюрьмы Двумя важными целями реализации тюрьмы являются следующие. 1. Сохранение семантики существующего механизма избирательного контроля за дос- доступом. 2. Возможность иметь для каждой тюрьмы своего собственного администратора суперпользователя, деятельность которого ограничена процессами, файлами и сетью, связанной с этой тюрьмой. Первая цель поддерживает совместимость с большинством приложений. Вторая цель позволяет администратору компьютера с FreeBSD разделить хост на отдельные тюрьмы и предоставить доступ к учетной записи суперпользователя в каждой из этих тюрем без потери контроля над окружением хоста. Говорят, что процесс в разделе находится «в тюрьме». Когда FreeBSD загружается впервые, ни один процесс не будет заключен в тюрьму. Тюрьмы создаются, когда при- привилегированный процесс осуществляет системный вызов с аргументами файловой системы, для которой он должен вызвать chroot, и IP-адресом и именем хоста, которые должны быть связаны с тюрьмой. Процесс, создающий тюрьму, будет первым и един- единственным процессом, помещенным в тюрьму. Все будущие потомки заключенного в тюрьму процесса будут находиться в его тюрьме. Процесс никогда не может поки- покинуть тюрьму, которую он создал или в которой он был создан. Процесс может быть лишь в одной тюрьме. Единственным способом для нового процесса войти в тюрьму является наследование доступа к тюрьме из другого процесса, уже находящегося в этой тюрьме. Каждая тюрьма привязана к одному IP-адресу. Процессы в тюрьме не могут использовать для исходящих или входящих соединений любые другие адреса. Тюрьма имеет возможность ограничить набор сетевых служб, которые она выберет для пред- предложения по своему адресу. Запросы приложения по привязке всех IP-адресов перена- перенаправляются по индивидуальным адресам, связанным с тюрьмой, в которой работает запрашивающий процесс. Тюрьма использует преимущества существующего поведения chroot для ограничения доступа к пространству имен файловой системы для заключенных в тюрьму процессов.
156 Глава 4. Управление процессами При создании тюрьмы она привязывается к определенному корню файловой системы. Процессы не могут манипулировать файлами, к которым они не могут обращаться. Таким образом защищается целостность и конфиденциальность файлов вне корня файловой системы тюрьмы. Процессы внутри тюрьмы обнаружат, что они не способны взаимодействовать или даже проверить существование процессов вне тюрьмы. Процессы внутри тюрьмы оберегаются от доставки сигналов процессам вне тюрьмы, от соединения с процесса- процессами вне тюрьмы с помощью отладчиков или даже от наблюдения за процессами вне тюрьмы с помощью обычных механизмов мониторинга системы. Тюрьмы не предот- предотвращают и не должны предотвращать использование скрытых каналов или механиз- механизмов взаимодействия посредством общепринятых интерфейсов. Например, два процес- процесса в различных тюрьмах могут взаимодействовать по сети через сокеты. Тюрьмы не пытаются предоставить службы планирования, основанные на разделении. Заключенные в тюрьму процессы являются предметом обычных ограничений, имеющихся для любых процессов, включая ограничения ресурсов и ограничения, накладываемые сетевым кодом, включая правила брандмауэров (firewall). Определяя правила брандмауэра для IP-адресов, привязанных к тюрьме, можно наложить на эту тюрьму ограничения на возможности соединения и пропускной способности, огра- ограничивая службы, которые она может использовать или предлагать. Окружение тюрьмы является подмножеством окружения хоста. Файловая система тюрьмы выглядит как часть файловой системы хоста и может непосредственно изме- изменяться процессами в среде хоста. Процессы внутри тюрьмы появляются в листингах процессов хоста, им могут посылаться сигналы, и их можно отлаживать. Процессы, работающие без привилегий суперпользователя, заметят несколько отличий между заключенным и не заключенным в тюрьму окружением. Стандартные системные службы, такие, как удаленная регистрация и почтовые службы, ведут себя обычным способом, как большинство сторонних приложений, включая популярный веб-сервер Apache. Процессы, работающие с привилегиями суперпользователя, обнаружат, что на привилегированные вызовы, которые они могут делать, накладывается множество ограничений. Большинство ограничений спроектировано для ограничения деятельно- деятельности, которая повлияла бы на ресурсы вне тюрьмы. Эти ограничения включают сле- следующие запреты на: изменение работающего ядра путем непосредственного доступа или загрузки модулей ядра; 9 монтирование и демонтирование файловых систем; 9 создание узлов устройств; изменение параметров ядра времени выполнения, таких, как большинство установок sysctl;
4.9. Тюрьмы 157 9 изменение флагов уровня безопасности; 9 изменение любых сетевых настроек, интерфейсов, адресов и элементов таблицы маршрутизации; 9 доступ к непосредственным (сырым) (raw), переключающим (divert) или маршру- маршрутизирующим (routing) сокетам. Эти ограничения предотвращают доступ к воз- возможностям, которые дают возможность имитировать IP-номера или создавать «подрывной» трафик; доступ к сетевым ресурсам, не связанным с тюрьмой. Конкретнее, попытка привя- привязать зарезервированный номер порта для всех доступных адресов приведет к при- привязке лишь адреса, связанного с тюрьмой; 9 влияющие на систему хоста административные действия, такие, как перезагрузка. Другие привилегированные действия разрешаются постольку, поскольку они огра- ограничены пределами тюрьмы. 9 Разрешается отправка сигналов любому процессу в пределах тюрьмы. 9 Разрешается удаление или изменение владельца и режима любого файла внутри тюрьмы, пока флаги файла допускают запрошенное изменение. 9 Суперпользователь может читать файл владельца с любым UID, если он доступен через пространство имен файловой системы тюрьмы. 9 Разрешается привязка номеров портов TCP и UDP к IP-адресу тюрьмы. Эти ограничения на доступ суперпользователя ограничивают область действия процессов, работающих с привилегиями суперпользователя, позволяя беспрепятствен- беспрепятственно выполняться большинству приложений, но предотвращая вызовы, которые могли бы позволить приложению получить доступ за пределами тюрьмы и повлиять на другие процессы или общесистемную конфигурацию. Реализация тюрьмы Реализация системного вызова jail проста. Выделяется и заполняется предоставленными аргументами структура данных prison *. Структура prison связывается со структурой про- процесса вызывающего процесса. Значение счетчика ссылок структуры prison устанавлива- устанавливается в единицу, и для установки корневого каталога тюрьмы осуществляется системный вызов chroot. После своего создания структура prison не может быть изменена. Ловушки в коде, реализующем создание и разрушение процесса, поддерживают счетчик ссылок на структуру prison и освобождают ее при освобождении последней ссылки. Любые новые процессы, созданные процессом в тюрьме, унаследуют ссылку на структуру prison, что помещает новый процесс в ту же самую тюрьму. Тюрьма, темница (англ.) - Примеч. пер.
158 Глава 4. Управление процессами Потребовались некоторые изменения для ограничения видимости и взаимодейст- взаимодействия процессов. Интерфейсы ядра, сообщающие о работающих процессах, были изме- изменены, чтобы сообщать лишь о процессах, находящихся в той же самой тюрьме, что и запрашивающий процесс. Определение того, может ли один процесс отправить сигнал другому, основывается на значениях UID и GID отправляющего и получающего процессов. При наличии тюрьмы ядро добавляет требование, чтобы получающий процесс находился в той же самой тюрьме, если отправляющий процесс заключен в тюрьму. Несколько изменений было добавлено в реализацию работы в сети. 9 Ограничение доступа через TCP и UDP лишь одним IP-адресом было сделано почти полностью в коде, который управляет управляющими блоками протоколов (см. раздел 13.1). Когда процесс, заключенный в тюрьму, привязывается к со кету, предоставленный процессом IP-адрес использоваться не будет; вместо этого используется предварительно сконфигурированный IP-адрес тюрьмы. 9 Для подключения к серверам на локальной машине процессами используется интерфейс обратной петли, имеющий IP-адрес 127.0.0.1. Когда к адресу 127.0.0.1 подключается процесс, работающий в тюрьме, ядро должно перехватить и пере- перенаправить запрос соединения по IP-адресу, связанному с тюрьмой. 9 Интерфейсы, посредством которых можно запросить состояние соединения и сетевую конфигурацию, были изменены таким образом, чтобы предоставлять лишь сведения, имеющие отношение к сконфигурированному IP-адресу находя- находящегося в тюрьме процесса. Потребовалось изменить драйверы устройств для совместно используемых устройств, такие, как драйвер псевдотерминала (см. раздел 10.1), чтобы обеспечить невозможность доступа к определенному терминалу в одно и то же время более чем из одной тюрьмы. Простейшим, но наиболее утомительным изменением была проверка всего ядра на предмет наличия мест, допускавших дополнительные привилегии суперпользователя. Лишь 35 из 300 проверенных в FreeBSD 5.2 были открыты для заключенных в тюрьму процессов, работающих с привилегиями суперпользователя. Поскольку по умолчанию находящиеся в тюрьме суперпользователи не получают привилегий, новый код или драйверы автоматически оказываются осведомленными о тюрьме: они будут отвергать привилегии суперпользователей, находящихся в тюрьме. Ограничения тюрьмы В существующем виде код тюрьмы предоставляет окружению тюрьмы точное подмно- подмножество системных ресурсов, основываясь на доступе к процессам, файлам, сетевым ресурсам и привилегированным службам. Представление окружения тюрьмы в виде пол- полнофункциональной системы FreeBSD предоставляет максимальную поддержку приложе- приложениям и возможность предложить широкий диапазон служб в рамках окружения тюрьмы.
4.10. Отладка процессов 159 Однако в текущей реализации есть ограничения. Устранение этих ограничений увеличит возможности по предложению служб в окружении тюрьмы. Тремя областями, заслужи- заслуживающими большего внимания, являются набор доступных сетевых ресурсов, управление планированием ресурсов и поддержка упорядоченного отключения тюрьмы. В настоящее время каждой тюрьме может быть выделен лишь один адрес IP версии 4, и вся коммуникация из тюрьмы ограничена этим IP-адресом. Была бы жела- желательной поддержка для каждой тюрьмы множества адресов или возможно различных семейств адресов. Доступ к непосредственным сокетам в настоящее время запрещен, поскольку текущая реализация непосредственных сокетов позволяет прямой доступ к пакетам IP, связанным со всеми интерфейсами. Ограничение области действия непо- непосредственного сокета позволило бы безопасно использовать его внутри тюрьмы, позволяя таким образом использовать ping и другие сетевые отладочные и диагно- диагностические инструменты. Другой областью, представляющей большой интерес для текущих пользователей кода тюрьмы, является возможность ограничить влияние одной тюрьмы на ресурсы процессора, доступные для других тюрем. Конкретнее, они требуют, чтобы у системы были способы распределять планирование ресурсов между группами процессов в каждой из тюрем. Можно было бы воспользоваться работой в области лотерейного планирования, чтобы допустить некоторую степень разделения между окружениями тюрем [Petrou & Milford, 1997]. Управление окружениями тюрем в настоящий момент представляет собой нечто специальное. Создание и запуск тюрем - хорошо документированная процедура, но остановка тюрьмы требует идентификации и завершения всех процессов, работающих в пределах тюрьмы. Одним из подходов к очистке этого интерфейса было бы назначе- назначение уникального идентификатора тюрьмы во время ее создания. Новый системный вызов jailkill дал бы возможность направить сигналы определенным идентификаторам тюрем, давая возможность эффективно завершать все процессы в тюрьме. FreeBSD использует процесс init для запуска системы в процессе загрузки и для содействия при ее выключении (см. раздел 14.6). Действующий сходным образом процесс jailinit, действующий в каждой тюрьме, представил бы основное местоположение для дос- доставки запросов управления своей тюрьме от окружения хоста или изнутри тюрьмы. Процесс jailinit координировал бы корректное выключение тюрьмы перед обраще- обращением к завершающимся процессам в таком же стиле, как выключение окружения хоста перед уничтожением всех процессов и остановкой ядра. 4.10. Отладка процессов FreeBSD предусматривает упрощенную возможность управления и отладки выполне- выполнения процесса. Эта возможность, доступ к которой осуществляется посредством сис- системного вызова ptrace, позволяет родительскому процессу контролировать выполнение
160 Глава 4. Управление процессами порожденного процесса, манипулируя состоянием выполнения режимов пользователя и ядра. В частности, с помощью ptrace родительский процесс может выполнить в отношении порожденного процесса следующие операции: 9 присоединиться к существующему процессу, чтобы начать его отладку; 9 читать и записывать в адресное пространство и регистры; 9 перехватывать сигналы, отправляемые процессу; осуществлять пошаговое выполнение и возобновление выполнения процесса; завершить выполнение процесса. Вызов ptrace почти исключительно используется отладчиками программ, такими, как gdb. При трассировке процесса все отправляемые процессу сигналы заставляют его войти в состояние STOPPED. Родительский процесс уведомляется с помощью сигнала SIGCHLD и может запросить состояние порожденного процесса с помощью системно- системного вызова wait4. На большинстве машин исключения-ловушки трассировки (trace traps), генерируемые при пошаговом выполнении процесса, и исключения-отказы точек останова (breakpointfaults)^, вызываемые выполнением процессом инструкции точки останова, преобразуются FreeBSD в сигналы SIGTRAP. Поскольку сигналы, от- отправляемые трассируемому процессу, заставляют его остановиться и вызывают уве- уведомление родителя, выполнение программы можно легко контролировать. Чтобы запустить программу, которую нужно отладить, отладчик сначала создает порожденный процесс с помощью системного вызова fork. После разветвления (fork) порожденный процесс использует вызов ptrace, который вызывает пометку процесса как трассируемого посредством установки бита P_TRACED в полер_ flag структуры процесса. Порожденный процесс устанавливает затем бит ловушки трассировки в слове состояния процессора для процесса и вызывает execve, чтобы загрузить образ программы для отладки. Установка этого бита гарантирует, что первая выполненная порожденным процессом инструкция после загрузки нового образа приведет к аппарат- аппаратному исключению-ловушке трассировки, которое преобразуется системой в сигнал SIGTRAP. Поскольку родительский процесс уведомляется обо всех сигналах порож- порожденному процессу, он может перехватить сигнал и получить управление программой до того, как она выполнит первую инструкцию. В качестве альтернативы отладчик может взять управление над существующим процессом путем присоединения к нему. Успешный запрос присоединения заставляет процесс войти в состояние STOPPED, а в поле p_flag его структуры процесса будет 1 Две разновидности исключений - ловушки (traps) и отказы (faults) - отличаются тем, что при возврате из ловушки выполнение программы возобновляется с инструкции, следующей за вызвавшей исключение инструкцией. В случае отказа выполнение возобновляется с инструк- инструкции, вызвавшей исключение. - Примеч. пер.
4.10. Отладка процессов 161 установлен бит PTRACED. Затем отладчик может действовать в отношении процесса тем же самым способом, как в случае с явным его запуском. Альтернативой системному вызову ptrace является файловая система/ргос. Возмож- Возможности, предоставляемые файловой системой /ргос, те же самые, которые предоставляет ptrace; они отличаются лишь по интерфейсу. Файловая система /ргос реализует пред- представление системной таблицы процессов внутри файловой системы и называется так, потому что обычно монтируется в /ргос. Она предоставляет двухуровневое представ- представление пространства процессов. На высшем уровне названы сами процессы в соответствии с их ID процессов. Имеется также особый узел, который называется curproc и который всегда ссылается на процесс, осуществляющий запрос поиска. Каждый узел - это каталог, содержащий следующие элементы. ctl [Frame I ] dbregs Установка отладочных регистров, как определено машинной архитектурой. etype Тип исполняемого файла, на который ссылается элемент file. file Ссылка на vnode, из которого был считан текст (код) процесса. Этот эле- элемент может использоваться для получения доступа к таблице идентифи- идентификаторов процесса или для запуска другой копии процесса. fpregs Регистры с плавающей точкой в соответствии с машинной архитектурой. Реализуется лишь на машинах, имеющих отдельные наборы регистров общего назначения и регистров с плавающей точкой. тар Карта виртуальной памяти процесса. mem Полный образ виртуальной памяти процесса. Можно получить доступ лишь к тем адресам, которые существуют в процессе. Чтение и запись в этот файл изменяют процесс. Записи в сегмент текста остаются частными для процесса. Поскольку доступ к адресному пространству другого процесса можно получить с помощью системных вызовов read и write, отладчик может получить доступ к отлаживаемому процессу с гораздо большей эффективно- эффективностью, чем при помощи системного вызова ptrace. Представляющие интерес страницы в отлаживаемом процессе отображаются в адресное простран- пространство ядра. Данные, запрошенные отладчиком, можно потом скопировать непосредственно из ядра в адресное пространство отладчика. regs Дает возможность получить доступ для чтения или записи набора реги- регистров процесса. rlimit Файл только для чтения, содержащий текущие и максимальные ограничения процесса.
162 Глава 4. Управление процессами status Состояние процесса. Этот файл только для чтения, он возвращает одну строку, содержащую несколько разделенных пробелами полей, включающих имя команды, Ю процесса, ID родительского процесса, Ю группы процесса, ID сеанса, управляющий терминал (если имеется), список флагов процесса, время запуска процесса, системное и пользовательское время, сообщение канала ожидания и мандаты процесса. Владельцем каждого узла является пользователь процесса, группой - первичная группа пользователя, за исключением узла тет, который принадлежит группе ктет. В обычном отладочном окружении, когда целевой процесс выполняет fork, за которым следует exec отладчика, отладчику следует выполнить fork, а порожденный процесс должен остановиться (например, послав себе SIGSTOP). Родитель должен вызвать команды wait, а затем attach через соответствующий файл ей. Порожденный процесс получит SIGTRAP сразу после вызова exec. Упражнения 4.1. Для каждого перечисленного в табл. 4.1 состояния перечислите системные очереди, в которых можно обнаружить процесс в этом состоянии. 4.2 . Почему производительность механизма переключения контекста критична для производительности высокомногозадачной системы? 4.3. Какое влияние на интерактивную чувствительность и общую пропускную способность системы оказало бы увеличение кванта времени? 4.4. Какое влияние оказало бы снижение числа очередей выполнения с 64 до 32 на накладные расходы планирования и на производительность системы? 4.5. Приведите пять причин для выбора системой для вьшолнения нового процесса. 4.6. Опишите три вида политики планирования, предусмотренные FreeBSD. 4.7. Каким видам заданий благоприятствует политика планирования с разделе- разделением времени? Предложите алгоритм для идентификации этих пользующихся преимуществом заданий. 4.8. Когда и как планирование потоков взаимодействует со средствами управле- управления памятью? 4.9. После завершения процесса он может войти в состояние ZOMBIE, прежде чем полностью исчезнуть из системы. В чем назначение состояния ZOMBIE? Какое событие заставляет процесс выйти из состояния ZOMBIE? 4.10. Предположим, структуры данных, показанные на рис. 4.2, не существуют. Вместо них предположим, что у каждого процесса есть лишь его собствен- собственный PID и PID его родителя. Сравните потребности в памяти и времени для поддержки каждой из следующих операций. a. Создание нового процесса. b. Поиск родителя процесса.
Упражнения 163 c. Поиск всех порожденных родителем данного процесса процессов. d. Поиск всех потомков процесса. e. Уничтожение процесса. 4.11. В чем разница между мьютексом и блокировкой, управляемой менеджером? 4.12. Приведите пример, когда следует использовать блокировку мьютекса. Приве- Приведите пример, когда следует использовать блокировку менеджера. 4.13. Процесс, заблокированный без установки флага РСАТСН, может никогда не быть пробужденным сигналом. Опишите две проблемы, которые может вызвать непрерываемый сон, если при работе системы станет недоступным диск. 4.14. Опишите ограничения, которые тюрьма налагает на пространство имен фай- файловой системы, доступ к сети и процессы, действующие в тюрьме. *4.15. В FreeBSD сигнал SIGTSTP доставляется процессу, когда пользователь набирает «символ приостановки». Почему процессу нужно перехватить этот сигнал перед остановкой? *4.16. До того как был добавлен механизм сигналов FreeBSD, обработчики сигна- сигналов для перехвата сигнала SIGTSTP были написаны так: catchstop() { prepare to stop; signal(SIGTSTP, SIG_DFL); kill (getpidO , SIGTSTP) ; signal(SIGTSTP, catchstop); } Этот код в FreeBSD вызывает бесконечный цикл. Почему так происходит? Как нужно переписать код? *4.17. Вычисления приоритетов процессов и статистики учета ресурсов все осно- основываются на дискретных данных. Опишите аппаратную поддержку, которая дала бы возможность для более точной статистики и вычислений приоритетов. *4.18. Почему сигналы - плохое средство межпроцессного взаимодействия? **4.19. Исключение-ловушка недействительного стека ядра возникает, когда аппаратным обеспечением обнаружено недействительное значение для указа- указателя стека режима ядра. Как система могла бы элегантно завершить процесс, получающий такое исключение при выполнении в своем стеке времени выполнения в ядре? **4.20. Опишите альтернативы инструкции проверки-и-установки, которые дали бы вам возможность создать механизм синхронизации для многопроцессорной системы FreeBSD.
164 Глава 4. Управление процессами **4.21. Облегченный процесс является потоком выполнения, действующим в контек- контексте обычного процесса FreeBSD. В одном процессе FreeBSD могут существо- существовать и разделять память несколько облегченных процессов, но каждый из них способен выполнять блокирующие операции, такие, как системные вызовы. Опишите, как облегченные процессы могли бы быть полностью реализованы в режиме пользователя. Ссылки Araletal., 1989. Z. Aral, J. Bloom, T. Doeppner, I. Gertner, A. Langerman, & G. Schaffer, "Variable Weight Processes with Flexible Shared Resources", USENIX Association Conference Proceedings, pp. 405-412, January 1989. Ferrin & Langridge, 1980. T. E. Ferrin & R. Langridge, "Interactive Computer Graphics with the UNIX Time-Shar- Time-Sharing System", Computer Graphics, vol. 13, pp. 320-331, 1980. Hope, 2002. P. Hope, "Using Jails in FreeBSD for Fun and Profit", login: The USENIX Association Newsletter, vol. 27, no. 3, pp. 48-55, available from http://www.usenix.org/publications/ login/2002-06/pdfs/hope.pdf, USENIX Association, Berkeley, CA, June 2002. Joy, 1994. W. N. Joy, "An Introduction to the С Shell", in 4.4BSD Users Supplementary Docu- Documents, pp. 4:1-46, O'Reilly & Associates, Inc., Sebastopol, CA, 1994. Kamp & Watson, 2000. P. Kamp & R. Watson, "Jails: Confining the Omnipotent Root", Proceedings of the Second International System Administration and Networking Conference (SANE), avail- available from http://docs.freebsd.org/44doc/papers/jail/, May 2000. РЮОЗЛе, 1998. РЮОЗЛе, Unpublished Draft Standard for Information Technology—Portable Operat- Operating System Interface (POSIX)—Part I: System Application Program Interface—Amend- Interface—Amendment: Protection, Audit and Control Interfaces [C Language] IEEE Standard J003.Ie Draft 17 Editor Casey Schaufler, Institute of Electrical and Electronic Engineers, Piscat- away, NJ, 1998. Petrou & Milford, 1997. D. Petrou & J. Milford, Proportional-Share Scheduling: Implementation and Evaluation in a Widely-Deployed Operating System, available from http://www.cs.cmu.edu/~dpe-
Ссылки 165 trou/papers/freebsd_lottery_writeup98.ps and http://www.cs.cmu.edu/~dpetrou/code/ freebsd_lottery_code.tar.gz, 1997. Ritchie, 1988. D. M. Ritchie, "Multi-Processor UNIX", private communication, April 25, 1988. Roberson, 2003. J. Roberson, "ULE: A Modern Scheduler For FreeBSD", Proceedings ofBSDCon 2003, September 2003. Sanderson et al., 1986. T. Sanderson, S. Ho, N. Heijden, E. Jabs, & J. L. Green, "Near-Realtime Data Transmis- Transmission During the ICE-Comet Giacobini-Zinner Encounter", ESA Bulletin, vol. 45, no. 21, 1986. Schimmel, 1994. C. Schimmel, UNIX Systems for Modern Architectures, Symmetric Multiprocessing, and Caching for Kernel Programmers, Addison-Wesley, Reading, MA, 1994.
Глава 5 Управление памятью 5.1. Терминология Центральным компонентом любой операционной системы является система управле- управления памятью. Как говорит само имя, средства управления памятью отвечают за управ- управление доступными на машине ресурсами памяти. Эти ресурсы обычно расположены иерархическим образом, причем время доступа к памяти обратно пропорционально их близости к CPU (см. рис. 5.1). Основной памятью системы является оперативная па- память', следующим уровнем является вторичное хранилище, или резервное хранилище. Системы оперативной памяти обычно построены из памяти с произвольным доступом, тогда как вторичные хранилища размещаются на дисковых приводах с перемещающи- перемещающимися головками. В условиях определенных рабочих станций обычная двухуровневая иерархия становится трехуровневой с добавлением машин файловых серверов или сетевых хранилищ, подсоединенных к рабочей станции через локальные сети [Gingell, Moran, & Shannon, 1987]. В многопрограммном окружении для операционной системы критично эффектив- эффективное разделение доступных ресурсов памяти между процессами. Работа любой политики управления памятью непосредственно связана с памятью, требующейся для выполне- выполнения процесса; т.е., если процесс для выполнения должен полностью находиться в оперативной памяти, система управления памятью должна быть ориентирована на выделение больших блоков памяти. С другой стороны, если процесс может выполнять- выполняться, когда он лишь частично находится в оперативной памяти, политики управления па- памятью, вероятно, будут существенно отличаться. Средства управления памятью обычно пытаются оптимизировать число работоспособных процессов, постоянно находящихся в оперативной памяти. Эта цель должна быть согласована с целями пла- планировщика процессов (глава 4) таким образом, чтобы избежать конфликтов, которые могут неблагоприятно повлиять на общую производительность системы.
5.1. Терминология 167 Контроллер ethernet К сети Рис. 5.1. Иерархическое распределение памяти на уровни Хотя доступность вторичного хранилища допускает существование большего числа процессов, которые постоянно могут находиться в основной памяти, это требует также дополнительных алгоритмов, которые могут быть запутанными. Для управле- управления хранилищем обычно требуются алгоритмы и политики, отличающиеся от тех, которые используются для основной памяти, и должна быть разработана политика для решения того, когда перемещать процессы между основной памятью и вторичным хра- хранилищем. Процессы и память Каждый процесс действует в виртуальной машине, которая определяется архитек- архитектурой лежащего в основе оборудования, на котором он выполняется. Мы заинтересо- заинтересованы лишь в тех машинах, которые включают понятие виртуального адресного про- пространства. Виртуальное адресное пространство является областью участков памяти, на которые процесс ссылается независимо от присутствующей в системе физической памяти. Другими словами, виртуальное адресное пространство процесса независимо от физического адресного пространства процессора. Для того чтобы машина под- поддерживала виртуальную память, нам также требуется, чтобы для выполнения процесса не требовалось постоянное присутствие в оперативной памяти всего виртуального адресного пространства процесса. Ссылки на виртуальное адресное пространство - виртуальные адреса - транс- транслируются аппаратным обеспечением в ссылки на физическую память. Эта операция, называемая трансляцией адресов, дает возможность загружать программы в память в любое место без необходимости изменения в программе зависимых от расположения адресов. Это перемещение в памяти зависимой от расположения адресации возможно благодаря тому, что известные программе адреса не изменяются. Трансляция адресов и виртуальная адресация важны также для эффективного разделения процессора,
168 Глава 5. Управление памятью поскольку независимость от размещения обычно допускает более быстрое выполне- выполнение переключения контекста. Большинство машинных архитектур предоставляют процессам непрерывные виртуальные адресные пространства. Однако некоторые машинные архитектуры вы- выбирают видимое разделение адресного пространства процесса на области, называемые сегментами [Intel, 1984]. Такие сегменты обычно должны быть физически непрерыв- непрерывными в основной памяти и должны начинаться по фиксированным адресам. Мы будем интересоваться лишь теми системами, которые не сегментируют явным образом свое виртуальное адресное пространство. Такое использование слова сегмент отличается от его прежнего использования в разделе 3.5, когда мы описывали сегменты процесса FreeBSD, такие, как сегменты текста и данных. Когда в основной памяти одновременно находятся несколько процессов, мы должны защитить физическую память, связанную с виртуальным адресным простран- пространством каждого процесса, чтобы гарантировать, что один процесс не может изменить содержимое виртуального адресного пространства другого процесса. Такая защита реализована аппаратно и обычно тесно связана с реализацией трансляции адресов. Поэтому эти две операции обычно определяются и реализуются в аппаратуре совместно, обозначаясь термином блок управления памятью (memory-management unit). Виртуальная память может быть реализована множеством способов, некоторые из которых, такие, как оверлеи, являются программными. Однако наиболее эффективные схемы виртуальной памяти основываются на аппаратном обеспечении. В этих схемах виртуальное адресное пространство делится на блоки фиксированного размера, назы- называемые страницами, как показано на рис. 5.2. Ссылки на виртуальную память пре- преобразуются блоком трансляции адресов в страницу оперативной памяти и смещение в пределах этой страницы. Аппаратная защита применяется блоком управления памя- памятью на постраничной основе. Некоторые системы предоставляют двухуровневую систему виртуальной памяти, при которой страницы сгруппированы в сегменты [Organick, 1975]. В этих системах защита обычно на уровне сегментов. В оставшейся части данной главы мы будем интересоваться лишь системами виртуальной памяти на основе страниц. Страничная подкачка Трансляция адресов обеспечивает реализацию виртуальной памяти путем отделения пространства виртуальных адресов процесса от физического адресного пространства процессора. Каждая страница виртуальной памяти помечается как присутствующая или отсутствующая в оперативной памяти. Если процесс ссылается на адрес вирту- виртуальной памяти, который отсутствует в оперативной памяти, генерируется аппаратное исключение, которое называется отказом страницы (page fault). Обслуживание отка- отказов страниц, или страничная подкачка (paging), дает процессам возможность выпол- выполняться, даже когда они находятся в оперативной памяти лишь частично.
5.1. Терминология 169 Виртуальное адресное пространство Страница О Страница 1 Страницап / / ч / / Страница п мми Страница О Страница 1 Физическое адресное пространство Обозначение: MMU - блок управления памятью РИС. 5.2. Страничная схема виртуальной памяти Coffman & Denning [1973] характеризуют системы страничной подкачки тремя важными политиками. 1. Когда система загружает страницы в память - политика выборки (fetch policy). 2. Куда система помещает страницы в памяти - политика размещения (placement policy). 3. Как система выбирает страницы для удаления из оперативной памяти, когда страни- страницы по запросу размещения недоступны - политика замещения (replacementpolicy). Производительность современных компьютеров сильно зависит от одного или более высокоскоростных кешей для снижения потребности в доступе к более медлен- медленной основной памяти. Политика размещения должна гарантировать, что смежные страницы в виртуальной памяти используют кеш процессора наилучшим образом. FreeBSD использует для обеспечения хорошего размещения алгоритм раскраски (coloring algorithm). В системе чистой подкачки по требованию (pure demand-paging) используется выборка по требованию, при которой выбирается лишь отсутствующая страница, а замещение происходит, лишь когда основная память заполнена. Поэтому производительность системы с чистой подкачкой по требованию зависит лишь от политики замещения системы. На практике системы страничной подкачки не реали- реализуют алгоритм чистой подкачки по требованию. Вместо этого политика выборки часто преобразуется в опереэ/сающую подкачку (prepaging) - выборку страниц памяти, отличающихся от той, которая вызвала отказ страницы, - и вызов политики замещения до того, как основная память заполнится.
170 Глава 5. Управление памятью Алгоритмы замещения Политика замещения является самым критическим аспектом любой системы стра- страничной подкачки. Имеется широкий диапазон алгоритмов, из которых мы можем выбирать при проектировании стратегии замещения для системы страничной под- подкачки. Множество исследований было проведено при оценке производительности различных алгоритмов замещения страниц [Bansal & Modha, 2004; Belady, 1966; King, 1971; Marshall, 1979]. Поведение страничной подкачки процесса для данного ввода описывается в терминах страниц, на которые происходят ссылки в ходе выполнения процесса. Эта последовательность страниц, обозначаемая как цепочка доступа (reference string), представляет поведение процесса в отдельные моменты времени в течение времени жизни процесса. Соответствующими дискретным ссылкам, которые состав- составляют цепочку доступа процесса, являются значения реального времени, которые отражают то, вызвали ли связанные ссылки отказ страницы. Полезным показателем поведения процесса является частота отказов, представляющая собой число отка- отказов страниц, возникших при обработке цепочки доступа, нормализованной в соот- соответствии с длиной этой цепочки доступа. Алгоритмы замещения страниц обычно оцениваются на основе их эффективности для цепочек доступа, которые были собраны в результате выполнения реальных про- программ. Можно также использовать формальный анализ, хотя это трудно осуществить, не применив множество ограничений к среде исполнения. Наиболее обычным показа- показателем, используемым в измерении эффективности алгоритма замещения страниц, является частота отказов. Алгоритмы замещения страниц определяются критерием, который они исполь- используют для выбора восстанавливаемых страниц. Например, политика оптимального замещения [Denning, 1970] устанавливает, что «лучшим» выбором страницы для заме- замещения является страница с максимальным временем ожидания следующей ссылки на нее. Очевидно, эта политика неприменима к динамическим системам, поскольку она требует априорного знания специфики подкачки процесса. Однако эта политика полез- полезна в целях оценки, поскольку она предусматривает критерий для сравнения производи- производительности других алгоритмов замещения страниц. Практические алгоритмы замещения страниц требуют определенного количества информации о состоянии, которую система использует при выборе замещаемых стра- страниц. Это состояние обычно включает структуру ссылок процесса, отобранных через дискретные интервалы времени. На некоторых системах собирать эту информацию может оказаться дорого [Babaoglu & Joy, 1981]. В результате «наилучший» алгоритм замещения страниц может не быть самым эффективным.
5.1. Терминология 171 Модель рабочего набора Модель рабочего набора предполагает, что процесс демонстрирует медленно изме- изменяющееся месторасположение ссылок. В течение определенного периода времени процесс действует в ряде подпрограмм или циклов, заставляя все обращения к памяти ссылаться на фиксированное подмножество своего адресного пространства, обо- обозначаемого как рабочий набор. Процесс периодически меняет свой рабочий набор, оставляя определенные области памяти и начиная доступ к новым. После переходного периода процесс определяет новый набор страниц в своем рабочем наборе. Вообще, если система может предоставить процессу достаточно страниц для поддержания этого рабочего набора процесса, процесс будет иметь низкую частоту отказов страниц. Если система не может предоставить процессу достаточно страниц для рабочего набора, процесс будет работать медленно и иметь большую частоту отказов страниц. Точное вычисление рабочего набора процесса невозможно без предварительного знания структуры ссылок этого процесса на память. Однако рабочий набор можно при- приблизительно вычислить различными средствами. Одним из методов аппроксимации является отслеживание числа страниц, удерживаемых процессом, и частоты отказов страниц процесса. Если частота отказов страниц увеличивается выше верхней границы, предполагается, что рабочий набор нужно увеличить, и разрешается увеличить число страниц, удерживаемых процессом. Наоборот, если число отказов страниц падает ниже нижней границы, предполагается, что рабочий набор должен быть уменьшен, и число страниц, удерживаемых процессом, уменьшается. Подкачка процессов Подкачка процессов (swapping) является термином, используемым для описания поли- политики управления памятью, при которой в случае недостатка оперативной памяти в и из вторичного хранилища процессы перемещаются целиком. Системы управления памя- памятью на основе подкачки процессов обычно менее сложны, чем системы с подкачкой страниц по требованию, поскольку при этом меньше накладных расходов по учету. Однако системы с чистой подкачкой процессов менее эффективны, чем системы со страничной подкачкой, поскольку уровень многозадачности снижается в соответствии с требованием полного присутствия процесса в оперативной памяти во время выпол- выполнения. Подкачку процессов иногда сочетают со страничной подкачкой в двухуровне- двухуровневой схеме, посредством чего страничная подкачка удовлетворяет запросам памяти до тех пор, пока значительная нехватка памяти не потребует радикальных действий. В этом случае используется подкачка процессов. В данной главе часть вторичного хранилища, которая используется для подкачки процессов, называется областью подкачки или пространством подкачки. Аппарат- Аппаратные устройства, в которых находятся эти области, называются устройствами под- подкачки.
172 Глава 5. Управление памятью Преимущества виртуальной памяти Есть несколько преимуществ использования виртуальной памяти на компьютерах, спо- способных поддерживать эту возможность должным образом. Виртуальная память позво- позволяет запускать большие программы на машинах с конфигурацией оперативной памяти, которая меньше, чем размер программы. На машинах с умеренным количеством памяти она дает возможность большему количеству программ постоянно находиться в основ- основной памяти для конкуренции за процессорное время, поскольку программам не нужно полностью находиться в оперативной памяти. Когда программы в течение некоторого времени используют секции своего пространства программы или данных, оставляя другие секции неиспользующимися, последние могут не присутствовать в основной памяти. Использование виртуальной памяти дает также программам возможность запускаться быстрее, поскольку им обычно требуется загрузить лишь небольшую секцию до начала обработки аргументов и определения того, какие действия следует предпринять. Другие части программ при отдельных запусках могут вообще не понадо- понадобиться. В ходе работы программы дополнительные секции пространства ее кода и данных могут подкачиваться по требованию (подкачка по требованию). Наконец, есть множество алгоритмов, которые легче программировать с использованием разре- разреженных участков большого адресного пространства, чем при помощи тщательной упа- упаковки структур данных в небольшие области. Такие методики слишком дороги для использования без виртуальной памяти, но они могут работать быстрее, когда эта воз- возможность доступна, без использования чрезмерного количества физической памяти. С другой стороны, использование виртуальной памяти может снизить производи- производительность. Эффективнее полностью загрузить всю программу разом, чем загружать ее в небольшие секции по требованию. Для каждой операции имеется конечная стои- стоимость, включающая сохранение и восстановление состояния и определение того, какая страница должна быть загружена, поэтому некоторые системы используют подкачку по требованию лишь для тех программ, которые превышают некоторый минимальный размер. Аппаратные требования для виртуальной памяти Почти все версии UNIX требовали для поддержки прозрачной многозадачности опре- определенной разновидности аппаратного обеспечения управления памятью. Чтобы пре- предотвратить изменение процессов другими процессами, аппаратура управления памя- памятью должна предотвращать изменение программами отображений их собственных адресов. Ядро FreeBSD работает в привилегированном режиме (режиме ядра или сис- системном режиме), при котором можно управлять отображением памяти, тогда как про- процессы работают в непривилегированном режиме (режиме пользователя). Для под- поддержки виртуальной памяти имеются несколько дополнительных архитектурных тре- требований. Процессор должен различать присутствующие и отсутствующие части адрес- адресного пространства, должен приостанавливать программы, когда они ссылаются на
5.2. Обзор системы виртуальной памяти FreeBSD 173 отсутствующие адреса, и возобновлять работу программ, когда операционная система разместила в памяти необходимую секцию. Поскольку процессор может обнаружить отсутствующие данные в различное время в ходе выполнения инструкции, он должен предусмотреть механизм для сохранения состояния машины таким образом, чтобы ин- инструкцию можно было впоследствии завершить или начать с начала. Процессор может реализовать повторный запуск путем сохранения при начале инструкции достаточной информации о состоянии, чтобы это состояние можно было при обнаружении отказа восстановить. В качестве альтернативы инструкции могли бы отложить любые изме- изменения или побочные эффекты до тех пор, пока не были обнаружены какие-нибудь отказы, чтобы не нужно было резервировать состояние перед повторным запуском инструкции. На некоторых компьютерах резервное сохранение инструкции требует помощи со стороны операционной системы. Большинство машин, спроектированных для поддержки виртуальной памяти с подкачкой страниц по требованию, включают аппаратную поддержку для сбора све- сведений о ссылках программы на память. Когда система выбирает страницу для замеще- замещения, она должна сохранить содержимое этой страницы, если оно было изменено с того момента, как страница была загружена в память. Аппаратура для каждой страницы обычно содержит флаг, показывающий, была ли эта страница модифицирована. Многие машины включают также флаг, записывающий любой доступ к странице для использования алгоритмом замещения. 5.2. Обзор системы виртуальной памяти FreeBSD Система виртуальной памяти FreeBSD основана на системе виртуальной памяти Mach 2.0 [Tevanian, 1987], с обновлениями из Mach 2.5 и Mach 3.0. Виртуальная система Mach была принята, поскольку она характеризуется эффективной поддержкой разделе- разделения памяти и чистым разделением машинно-независимых и машинно-зависимых воз- возможностей, а также поддержкой многопроцессорности. От оригинального интерфейса системных вызовов Mach не осталось ничего. Он был заменен интерфейсом, вначале предложенным для 4.2BSD, который был широко принят в индустрии UNIX; интерфейс FreeBSD описан в разделе 5.5. Система виртуальной памяти реализует защищенные адресные пространства, в которые могут отображаться источники данных (объекты), такие, как файлы или индивидуальные анонимные участки пространства подкачки. Физическая память используется в качестве кеша недавно использованных страниц из этих объектов и управляется алгоритмом глобального замещения страниц. Виртуальное адресное пространство большинства архитектур разделено на две части. Для FreeBSD, работающей на 32-разрядных архитектурах, верхний 1 Гбайт адресного пространства зарезервирован для использования ядром. Системы со множе- множеством небольших процессов, интенсивно использующих возможности ядра, такие, как
174 Глава 5. Управление памятью использование сети, могут быть сконфигурированы для использования ядром верхних 2 Гбайт. Оставшееся адресное пространство доступно для использования процессами. Традиционная планировка UNIX показана на рис. 5.3. Здесь ядро и связанные с ним структуры данных находятся на вершине адресного пространства. Начальные области текста и данных процесса пользователя начинаются в начале памяти или около него. Обычно первые 4 или 8 Кбайт памяти являются запрещенными для процесса. Причина этого ограничения в том, чтобы облегчить отладку программы; обращение через ука- указатель null вызовет отказ недействительного адреса вместо чтения или записи кода программы. Выделение памяти, сделанное работающим процессом с использованием библиотечной процедуры mallocQ (или системного вызова sbrk), осуществляется из кучи, которая начинается сразу после области данных и растет по направлению к старшим адресам. Массив аргументов и массивы окружения находятся на вершине пользовательской части адресного пространства. Стек пользователя начинается сразу под этими массивами и растет по направлению к младшим адресам. Подвергаясь лишь административным ограничениям, стек и куча могут расти до тех пор, пока они не встретятся. В этот момент процесс, работающий на 32-разрядной машине, будет исполь- использовать около 3 Гбайт адресного пространства. Вершина, 4 Гбайта - Ядро, 3 Гбайта—* Пользователь, 0 Гбайт Память, выделенная mallocQ, стек прерываний data text argv, envp Стек пользователя t Куча data text Ядро Процесс пользователя Рис. 5.3. Схема виртуального адресного пространства В FreeBSD и других современных системах UNIX, поддерживающих системный вызов ттар, использование адресного пространства структурировано меньше. Реали- Реализации разделяемых библиотек могут размещать код или данные произвольным обра- образом, делая представление о предопределенных областях устаревшим. Для совмести- совместимости FreeBSD по-прежнему поддерживает вызов sbrk, который mallocQ использует для предоставления непрерывной области кучи, а область стека автоматически уве- увеличивается до своего предельного размера. По умолчанию разделяемые библиотеки помещаются сразу под сконфигурированной во время выполнения максимальной областью стека.
5.2. Обзор системы виртуальной памяти FreeBSD 175 В любом случае текущий выполняющийся процесс отображается на виртуальное адресное пространство. Когда система решает выполнить переключение контекста на другой процесс, она должна сохранить информацию об отображении адресов текущего процесса, затем загрузить отображения адресов для нового запускаемого процесса. Детали этого переключения отображения адресов зависят от архитектуры. В неко- некоторых архитектурах нужно изменить лишь несколько регистров отображения памяти, которые указывают на базу и содержат размер постоянно находящихся в памяти таблиц страниц. В других архитектурах дескрипторы таблиц страниц хранятся в спе- специальной высокоскоростной статической RAM. Переключение этих отображений может потребовать выгрузки и повторной загрузки сотен отображаемых элементов. Как ядро, так и процессы пользователя используют для управления своей вирту- виртуальной памятью одни и те же базовые структуры данных. Структуры данных, исполь- используемые для управления виртуальной памятью, следующие. vmspace Структура, включающая как машинно-зависимые, так и машинно-неза- машинно-независимые структуры, описывающие адресное пространство процесса. vmjnap Структура данных самого верхнего уровня, которая описывает машинно-независимое виртуальное адресное пространство. vmjnap_entry Структура, описывающая виртуально смежную область адресов, которые разделяют атрибуты защиты и наследования и которые используют один и тот же объект резервного хранения. object Структура, описывающая источник данных для диапазона адресов. shadow object Специальный объект, представляющий модифицированную копию ори- оригинальных данных. vm_page Структура данных самого нижнего уровня, представляющая исполь- используемую системой виртуальной памяти физическую память. В оставшейся части данного раздела мы кратко опишем, как совмещаются все эти структуры данных. В оставшейся части данной главы описываются подробности структур и то, как эти структуры используются. На рис. 5.4 показано типичное адресное пространство процесса и связанные с ним структуры данных. Структура vmspace инкапсулирует состояние виртуальной памяти определенного процесса, включая как машинно-зависимые, так и машинно-независи- машинно-независимые структуры данных, а также статистику. Машинно-зависимая структурартар явля- является непрозрачной для всех уровней системы, кроме самого нижнего, и содержит всю необходимую информацию для управления аппаратным обеспечением управления па- памятью. Уровень ртар является предметом раздела 5.13 ив оставшейся части текущего обсуждения игнорируется. Машинно-независимые структуры данных включают адресное пространство, которое представлено структурой vmjnap. vmjnap содержит связанный список структур vmjnap _entry, подсказки для ускорения поиска во время
176 Глава 5. Управление памятью выделений памяти и обработки отказов страниц, и указатель на связанную машинно- зависимую структуру ртар, содержащуюся в vmspace. Структура vm_map_entry опи- описывает виртуально смежный диапазон адресов, имеющих одни и те же атрибуты защиты и наследования. Каждый vm_map_ent?y указывает на цепочку структур vm_object, которая описывает источники данных (объекты), отображенные на указан- указанный диапазон адресов. В конце цепочки находится оригинальный отображенный объект данных, обычно представляющий постоянный источник данных, такой, как файл. Между этим объектом и элементом отображения вставлены один или более вре- временных теневых объектов (shadow objects), представляющих модифицированные копии первоначальных данных. Эти теневые объекты подробно обсуждаются в разделе 5.5. vmjnap vmjpmap Статистика Теневой объект vm map entry vmspace vmjpage vnode / объект vmjpage vmjpage ь vm_jnap_entry Теневой объект vmjpage vm map entry vnode / объект vnode / объект vm_page vmjpage ± Рис. 5.4. Структуры данных, описывающие адресное пространство процесса vmjpage Каждая структура vmjobject содержит связанный список структур vm_page, пред- представляющих кеш физической памяти объекта, так же как тип и указатель на структуру pager, содержащую сведения о том, как загружать данные с запоминающего устройства или выгружать данные на него. Для каждой страницы физической памяти, управляе- управляемой системой виртуальной памяти, имеется выделенная структура vm_page, где стра- страница может быть набором нескольких смежных аппаратных страниц, которые машин- машинно-зависимым уровнем будут рассматриваться, как если бы они были одной единицей. Структура содержит также состояние страницы (например, модифицирована или осу- осуществлялся доступ) и ссылки на различные очереди подкачки. Структуру vm_page чаще и быстрее всего находят через глобальный хеш, ключом которого служит объект и логическая страница внутри этого объекта, содержимое которого хранится в странице. Все структуры содержат необходимые блокировки для многопоточности в много- многопроцессорном окружении. Блокировка мелкоструктурная, с по крайней мере одной блокировкой на экземпляр структуры данных. Многие из структур содержат несколько блокировок для защиты отдельных полей.
5.3. Управление памятью ядра 177 5.3. Управление памятью ядра Имеется два способа, которыми может быть организована память ядра. Наиболее обычным для ядра является постоянное отображение в верхнюю часть адресного про- пространства каждого из процессов. В этой модели переключение с одного процесса на другой не затрагивает часть адресного пространства ядра. Альтернативной организа- организацией является переключение между ядром, владеющим всем адресным пространст- пространством, и отображением в адресное пространство текущего выполняющегося процесса. Постоянное отображение ядра снижает количество адресного пространства, доступно- доступного большим процессам (и ядру), но это снижает также стоимость копирования данных. Многим системным вызовам требуется перемещение данных между текущим выпол- выполняющимся процессом и ядром. Когда ядро отображено постоянно, данные можно копировать посредством эффективных инструкций блочного копирования. Если ядро отображено с процессом альтернативно, копирование данных требует особых инструкций, которые копируют в и из предварительно отображенного адресного про- пространства. Эти инструкции обычно медленнее на порядок степени 2 по сравнению со стандартными инструкциями блочного копирования. Поскольку вплоть до одной трети времени ядра тратится на копирование между ядром и процессами пользователя, замедление этой операции на степень 2 значительно уменьшает производительность системы. Хотя ядро может свободно читать и записывать в адресное пространство пользова- пользователя, обратное неверно. Диапазон виртуального адресного пространства ядра помечен как недоступный для всех процессов пользователя. Запись ограничена так, что процессы пользователя не могут вмешиваться в структуры данных ядра. Чтение ограничено таким образом, что процессы пользователя не могут просмотреть важные структуры данных ядра, такие, как входные очереди терминалов, которые включают такие вещи, как набираемые пользователями пароли. Обычно то, какая организация может быть использована, определяет аппаратное обеспечение. Все архитектуры, поддерживаемые FreeBSD, отображают ядро на вершину адресного пространства. Отображения и подотображения ядра При загрузке системы первой задачей, которую должно выполнить ядро, является ус- установка структур данных для описания и управления его адресным пространством. Подобно любому процессу, у ядра есть vmjnap с соответствующим набором структур vm_map_entjy, описывающих использование диапазона адресов. Подотображения (submaps) являются особыми конструкциями, которые используются только ядром для изоляции и ограничения выделения адресного пространства для подсистем ядра. Одним из применений являются подотображения, которым требуются смежные участки адресного пространства ядра. Чтобы избежать перемешивания в диапазоне адресов не
178 Глава 5. Управление памятью связанных друг с другом выделений, этот диапазон охватывается подотображением, и выделять из этого отображения может лишь соответствующая подсистема. Например, несколько макросов манипулирования сетевым буфером (mbuj) используют для генерирования уникальных индексов арифметику адресов, требуя тем самым смежности областей буфера. Частям ядра могут также требоваться адреса с определенным вырав- выравниванием или даже с особыми адресами. Обе эти задачи можно выполнить с использо- использованием подотображений. Наконец, подотображения можно использовать для статиче- статического ограничения размера адресного пространства и тем самым физической памяти, потребляемой подсистемой. vmjnap vm_map_entry Рис. 5.5. Отображения адресного пространства ядра Типичная схема отображения ядра показана на рис. 5.5. Адресное пространство ядра описывается структурой vmjnap, показанной в верхнем левом углу рисунка. Части адресного пространства описываются структурами vmjnap_entryr> которые свя- связаны в восходящем порядке адресов от КО до К8 в структуре vmjnap. Здесь в диапазоне от КО до К1 находятся код ядра, инициализированные данные, неинициализированные данные и первоначально выделенные структуры данных; они представлены первой vmjnap_entry. Следующая vmjnap_entry связана с диапазоном адресов от К2 до Кб. Эта часть адресного пространства ядра управляется посредством подотображения,
5.3. Управление памятью ядра 179 имеющего в качестве заголовка указанную структуру vmjnap. В настоящее время у этого подотображения используются две части его адресного пространства: диапазон адресов от К2 до КЗ и диапазон адресов от К4 до К5. Эти два подотображения пред- представляют арену mall ос и арену сетевого буфера соответственно. Заключительная часть адресного пространства ядра управляется в главном отображении ядра, диапазоне адресов от К7 до К8, представляющем область подготовки ввода/вывода ядра. Выделение адресного пространства ядра Система виртуальной памяти реализует набор примитивных функций для выделения и освобождения выровненных по границе страницы и округленных до размера страни- страницы диапазонов виртуальной памяти, которые использует ядро. Эти диапазоны могут выделяться либо из главного отображения адресов ядра, либо из подотображения. Про- Процедуры выделения принимают в качестве параметров отображение и размер, но не принимают адрес. Таким образом, нельзя выбирать определенные адреса внутри ото- отображения. Для получения выгружаемых и невыгружаемых диапазонов существуют различные процедуры выделения. Невыгружаемый, или переданный (wired), диапазон имеет физическую память, назначенную во время вызова, и эта память не может быть замещена демоном удаления страниц. Переданные страницы никогда не должны вызывать отказ страницы, который может привести к блокированию операции. Переданная память выделяется с помощью kmem_alloc() и kmemjnallocQ. kmem_alloc() возвращает память, заполненную нулями, и может блокироваться, если для выполнения запроса доступно недостаточно физиче- физической памяти. Она вернет ошибку, лишь если в указанном отображении нет доступного адресного пространства. kmemjnallocQ является разновидностью kmem_alloc(), использующейся лишь общим распределителем, mallocQ, описанным в следующем подразделе. Эта процедура имеет неблокирующий вариант, который защищает вызы- вызывающего от неосторожного блокирования структур данных ядра; он завершится неудачей, если для заполнения запрошенного диапазона недоступно достаточно физической памяти. Неблокирующий вариант выделяет память в ходе прерывания и во время других критических разделов кода. Вообще, переданная память должна выде- выделяться через распределитель ядра общего назначения. kmem_alloc() должна использо- использоваться лишь для выделения памяти из различных подотображений ядра. Выгружаемая виртуальная память может выделяться с помощью kmem_alloc_pageable() и kmem_alloc_wait(). Выгружаемый диапазон имеет физиче- физическую память, выделенную по требованию, и эта память может быть переписана демо- демоном удаления страницы на запоминающее устройство как часть обычной политики замещения демона. kmem_alloc_pageable() возвратит ошибку, если для нужного выде- выделения доступно недостаточно адресного пространства; kmem_alloc_wait() будет забло- заблокирована до тех пор, пока пространство не станет доступным. В настоящее время страничная память ядра используется лишь для временного хранения аргументов exec.
180 Глава 5. Управление памятью kmemJreeQ освобождает переданную память ядра и выгружаемую память, выде- выделенную с помощью kmem_alloc_pageable(). kmemJree^akeupO должна использоваться с kmem_alloc_wait(), поскольку она пробуждает любой процесс, ожидающий адресное пространство в указанном отображении. Malloc ядра Ядро предоставляет также обобщенный механизм выделения и освобождения невы- невыгружаемой памяти, который может обрабатывать запросы с произвольным выравнива- выравниванием или размером, так же как выделять память во время прерывания. Поэтому это является предпочтительным способом выделения памяти ядра, кроме больших струк- структур фиксированного размера, которые лучше обрабатываются зональным распредели- распределителем областей, описанным в следующем подразделе. У этого механизма интерфейс, сходный с интерфейсом хорошо известного распределителя памяти, предоставляемого прикладным программистам через библиотечные процедуры С mallocQ и freeQ. Подобно интерфейсу библиотеки С, процедура распределения принимает параметры, определяющие размер необходимой памяти. Диапазон размеров для запросов памяти не ограничен. Процедура освобождения принимает указатель на освобождаемую память, но она не требует размера освобождаемого участка памяти. Часто ядру требуется выделение памяти в течение длительности одного системно- системного вызова. В процессе пользователя такую временную память выделили бы в стеке. Поскольку у ядра стек времени выполнения ограничен, он не годится для выделения в нем даже умеренных блоков памяти. Поэтому такая память должна выделяться дина- динамически. Например, когда система должна транслировать имя пути, она должна для хранения имени выделить 1-кило байтный буфер. Другие блоки памяти должны быть более продолжительными, чем один системный вызов, и должны выделяться из дина- динамической памяти. Примеры включают управляющие блоки протоколов, которые остаются в течение продолжительности сетевого соединения. Технические требования дизайна для распределителя памяти ядра аналогичны, но не идентичны критериям дизайна для распределителя памяти уровня пользователя. Одним из критериев для распределителя памяти является то, что он хорошо использует физическую память. Использование памяти измеряется количеством памяти, необхо- необходимой для хранения набора распределений в любой момент времени. Процент исполь- использования выражается так: _. Запрошено Использование — - . Требуется Здесь запрошено является общим объемом памяти, которая запрошена и еще не освобождена; требуется является объемом памяти, которая была выделена для пула, из которого удовлетворяются запросы. Распределитель требует больше памяти, чем запрошено, из-за фрагментации и необходимости предоставления свободной
5.3. Управление памятью ядра 181 памяти для будущих запросов. Совершенный распределитель памяти имел бы использование 100 процентов. На практике хорошим считается использование 50 про- процентов [Когп & Vo, 1985]. Хорошее использование памяти в ядре более важно, чем в процессах пользователя. Поскольку процессы пользователя работают в виртуальной памяти, неиспользующие- ся части их адресного пространства могут быть сброшены на диск. Таким образом, страницы в адресном пространстве процесса, которые являются частью необходимого пула, но не являются запрошенными, не должны занимать физическую память. Поскольку арена malloc ядра не подвержена страничному замещению, все страницы в назначенном пуле удерживаются ядром и не могут использоваться для других целей. Чтобы поддерживать процент использования ядра как можно более высоким, ядро должно освобождать неиспользующуюся память в назначенном пуле, а не удерживать ее, как обычно делается в процессах пользователя. Поскольку ядро может манипулиро- манипулировать своими отображениями страниц непосредственно, освобождение неисполь- зующейся памяти происходит быстро; процесс пользователя для освобождения памяти должен осуществлять системный вызов. Самым важным критерием для распределителя памяти ядра является скорость. Медленный распределитель памяти будет снижать производительность системы, по- поскольку выделение памяти осуществляется часто. Скорость выделения более критична при выполнении в ядре, чем в коде пользователя, поскольку ядро должно выделять множество структур данных, которые процесс пользователя может дешево выделять в своем стеке времени выполнения. Кроме того, ядро представляет платформу, на ко- которой действуют процессы пользователя, и, если оно медленное, оно будет снижать производительность каждого работающего процесса. Другой проблемой с медленным распределителем памяти является то, что програм- программисты часто используемых интерфейсов ядра подумают, что они не могут позволить себе использовать распределитель памяти в качестве своего главного распределителя. Вместо этого они построят свой собственный распределитель поверх первоначального, под- поддерживая свой собственный пул блоков памяти. Несколько распределителей снижают эффективность использования памяти. Ядро окажется с множеством свободных списков памяти вместо одного, из которого осуществляются все выделения. Например, рас- рассмотрите случай с двумя подсистемами, которым нужна память. Если у них есть свои собственные списки свободной памяти, количество связанной в двух списках памяти будет равно сумме наибольшего количества памяти, которую каждая из этих двух под- подсистем когда-либо использовала. Если они разделяют общий список свободной памяти, количество связанной памяти может быть равно всего лишь наибольшему количеству памяти, которую использовала любая из подсистем. По мере увеличения числа подсистем экономия от наличия одного списка свободной памяти растет. Распределитель памяти ядра использует смешанную стратегию. Небольшие выде- выделения осуществляются с использованием стратегии степени 2. Используя зональный распределитель (zone allocator, описанный в следующем подразделе), ядро создает
182 Глава 5. Управление памятью набор зон, по одной для каждой степени двойки между 16 и размером страницы. Рас- Распределение просто запрашивает блок памяти из соответствующей зоны. Обычно зона будет иметь доступный участок памяти, которую она возвратит. Лишь если все участки памяти в зоне используются, зональному распределителю придется осуществлять полное выделение. При необходимости сделать дополнительное распределение он выделяет целую страницу и разрезает ее на участки соответствующих размеров. Такая стратегия ускоряет будущие распределения, поскольку в результате вызова распреде- распределителя становятся доступными несколько участков памяти. Освобождение небольшого блока также быстро. Память просто возвращается в зону, из которой она поступила. Вследствие неэффективности стратегии выделения степени 2 для больших распре- распределений метод выделения для больших блоков основывается на выделении участков памяти, кратных размерам страницы. Алгоритм переключается на более медленную, но более эффективную стратегию выделения размеров, превышающих страницу. Это значение выбрано, потому что алгоритм степени 2 дает размеры в 1, 2, 4, 8, ..., п стра- страниц, тогда как алгоритм больших блоков, выделяющий кратное число страниц, дает размеры в 1, 2, 3,4,..., п страниц. Таким образом, для выделения более чем одной стра- страницы алгоритм больших блоков будет использовать меньшее или равное по сравнению с алгоритмом степени 2 количество страниц, поскольку порог между большим и малым распределителем установлен в одну страницу. Большие выделения сначала округляются по направлению к кратному размеру страницы. Затем распределитель использует алгоритм «первого попадания» для нахождения пространства в арене адресов ядра, отложенных для динамического распределения. На машине с размером страницы 4 Кбайта запрос на участок памяти в 20 Кбайт использует ровно пять страниц памяти вместо восьми, используемых стра- стратегией распределения степени 2. Когда освобождается большой участок памяти, стра- страницы памяти возвращаются в пул свободной памяти и структура vm_map_entry удаля- удаляется из подотображения, эффективно соединяя освобожденный участок с любым смежным свободным пространством. Поскольку при освобождении блока памяти размер не указывается, распредели- распределитель должен отслеживать размеры участков, которые он выделил. Многие распредели- распределители увеличивают запрос выделения на несколько байтов, чтобы создать пространство для хранения размера блока в заголовке непосредственно перед выделением. Однако эта стратегия удваивает потребность в памяти для выделений, требующих блока раз- размера степени 2. Следовательно, вместо хранения размера каждого участка памяти с самим участком зональный распределитель связывает информацию о размере со страницей памяти. Расположение размера выделения вне выделенного блока улучшило использование памяти значительно больше, чем ожидалось. Причина в том, что многие распределители в ядре предназначены для блоков памяти, размеры которых точно равны степени 2. Размер этих запросов был бы удвоен, если бы использовалась более привычная стратегия. Теперь они могут быть обеспечены без потери памяти.
5.3. Управление памятью ядра 183 Распределитель можно вызвать как из верхней половины ядра, которая готова ждать, пока память станет доступной, так и из процедур прерываний в нижней полови- половине ядра, которые не могут ждать, пока память станет доступной. Клиенты показывают свою готовность (и возможность) ждать посредством флага процедуры распределения. Для клиентов, которые хотят ждать, распределитель гарантирует, что их запрос завершится успешно. Таким образом, этим клиентам не нужно проверять возвращае- возвращаемое из распределителя значение. Если память недоступна и клиент не может ждать, распределитель возвращает пустой указатель (null). Эти клиенты должны быть готовы справиться с этим (обычно нечастым) условием (обычно путем отказа в надежде на успех позже). Более тонкие детали распределителя памяти ядра описаны в McKusick & Karels[1988]. Зональный распределитель ядра Некоторые обычно выделяемые элементы в ядре, такие, как структуры процессов, потоков, vnode и управляющих блоков, недостаточно хорошо обрабатываются интерфейсом malloc общего назначения. У этих структур есть несколько общих характеристик. * Они имеют тенденцию быть большими и, следовательно, неэкономными по объему. Например, структура процесса занимает примерно 550 байт, которые при округле- округлении до размера степени 2 требуют 1024 байта памяти. * Они имеют тенденцию быть часто используемыми. Поскольку они по отдельности не экономны, вместе они тратят значительно больше пространства по сравнению с более тесным представлением. * Они часто связаны вместе в длинные списки. Если выделение каждой структуры начинается на границе страницы, указатели списков все будут по одному и тому же смещению с начала страницы. При прохождении этих структур связывающие указатели все будут расположены в одной и той же строке аппаратного кеша, вызы- вызывая на каждом шаге по списку промах кеша, что замедляет прохождение списка. * Эти структуры часто содержат множество списков и блокировок, которые должны быть до использования инициализированы. Если для каждой структуры имеется выделенный пул памяти, эти подструктуры нужно инициализировать, лишь когда пул создается впервые, а не после каждого выделения. По этим причинам FreeBSD предоставляет зональный распределитель (zone allocatorI. Зональный распределитель предоставляет эффективный интерфейс для управления набором элементов одного размера и типа [Bonwick, 1994; Bonwick & Adams, 2001]. Создание различных зон ведет счетчик в стремлении сохранить всю память в одном пуле, чтобы максимизировать эффективность использования. Однако преимущества отделения памяти для небольшого набора структур, для которых подходит зональный Иногда его еще называют slab allocator. - Примеч. авт.
184 Глава 5. Управление памятью распределитель, превосходит выигрыш в эффективности от сохранения их в общем пуле. Зональный распределитель минимизирует влияние отдельных пулов, освобож- освобождая память из зоны на основе снижения требований для объектов из области и при уве- уведомлении о недостатке памяти демоном удаления страниц. Зона является наращиваемым набором элементов одного размера. Зональный рас- распределитель отслеживает активные и свободные элементы и предоставляет функции для выделения из зоны элементов и для обратного их освобождения, чтобы сделать доступными для последующего использования. Память выделяется для зоны по мере необходимости. У элементов пула постоянный тип. Память в пуле не будет использоваться для каких-нибудь других целей. Структуру в пуле нужно инициализировать лишь при первом использовании. При последующем использовании можно считать, что инициа- инициализированные значения сохранят свое содержание, как при предыдущем освобожде- освобождении. Предоставляется функция обратного вызова до освобождения памяти из области, чтобы подчистить любое сохраненное состояние. Новая область создается функцией uma_zcreate(). Она должна указать размер выделяемых элементов и зарегистрировать два набора функций. Первый набор вызы- вызывается каждый раз при выделении или освобождении из области элемента. Они обычно отслеживают число выделенных элементов. Второй набор вызывается каждый раз при выделении или освобождении памяти из зоны. Они предоставляют ловушки для ини- инициализации или разрушения подструктур внутри элементов, таких, как блокировки или мьютексы, когда они выделяются впервые или готовы к освобождению. Элементы выделяются посредством uma_zalloc(), которая принимает идентификатор зоны, возвращенный uma_zcreateQ. Элементы освобождаются с помощью uma_zfree(), которая принимает идентификатор зоны и указатель на освобождаемый элемент. При выделении или освобождении размер не требуется, поскольку размер элемента был установлен при создании зоны. Чтобы способствовать повышению производительности на многопроцессорных системах, зона может поддерживаться страницами из кешей каждого из процессоров для объектов, которые могут иметь процессорное притяжение (affinity). Например, если ожидается, что определенный процесс будет запускаться на одном и том же процессоре, было бы целесообразно выделять его структуры из кеша для этого про- процессора. Зональный распределитель предоставляет функцию uma_zone_set_max() для уста- установки верхнего предела элементов в зоне. Предел общего числа элементов в зоне включает выделенные и свободные элементы, включая элементы в кешах отдельных процессоров. На многопроцессорных системах может оказаться невозможным выде- выделить новый элемент для определенного процессора, поскольку был достигнут предел и все свободные элементы находятся в кешах других процессоров.
5.4. Ресурсы процесса 185 5.4. Ресурсы процесса Как мы уже видели, процессу требуются элемент процесса (process entry) и стек ядра. Следующим важным ресурсом, который должен быть выделен, является его виртуаль- виртуальная память. Первоначальные требования к виртуальной памяти определены в заголовке исполняемого файла процесса. Эти требования включают пространство, необходимое для кода программы, инициализированных данных и стека времени выполнения. Во время начального запуска программы ядро построит структуры данных, необходимые для описания этих четырех областей. Большинству программ требуется выделение допол- дополнительной памяти. Ядро обычно предоставляет эту дополнительную память, расширяя область неинициализированных данных. Большинство программ FreeBSD используют разделяемые библиотеки. В заго- заголовке исполняемого файла будут описаны необходимые библиотеки (обычно биб- библиотеки С, возможно, и другие). Ядро не отвечает за нахождение и отображение этих библиотек во время начального выполнения программы. Нахождение, отображение и создание динамических ссылок на эти библиотеки выполняется кодом запуска уровня пользователя, присоединенным к началу исполняемого файла. Этот код запуска обычно действует до передачи управления главной точке входа программы [Gingell et al., 1987]. Виртуальное адресное пространство процесса FreeBSD Первоначальная схема адресного пространства процесса показана на рис. 5.6. Как об- обсуждалось в разделе 5.2, адресное пространство для процесса описывается структурой vmspace этого процесса. Содержание адресного пространства определяется списком структур mv_map_entiy, причем каждая структура описывает область виртуального адресного пространства, которая находится между начальным и конечным адресом. Область описывает блок памяти, который будет трактоваться единым образом. Напри- Например, код программы является областью с доступом только для чтения и с подкачкой по требованию из содержащего его файла на диске. Таким образом, vm_map_entry содержит также режим защиты, который должен быть применен к описываемой ею области. Каждая структура vm_map_entiy имеет также указатель на объект, который предоставляет области начальные данные. Объект хранит также модифицированное содержание либо временно, когда память возвращается обратно, либо более постоянно, когда область больше не нужна. Наконец, каждая структура mv_map_entry содержит смещение, описывающее, где в пределах объекта начинается отображение. Пример, показанный на рис. 5.6, представляет процесс сразу после того, как он начал свое выполнение. Два первых элемента отображения указывают на один и тот же объект; здесь этим объектом является исполняемый файл. Исполняемый файл состоит из двух частей: кода программы, который находится в начале файла, и области инициа- инициализированных данных, которые следуют после кода. Таким образом, первая
186 Глава 5. Управление памятью vm_map_entry описывает область только для чтения, которая отображает код про- программы. Вторая vm_map_entry описывает область с атрибутом копирования при записи, которая отображает инициализированные данные программы, следующие в файле за кодом программы (копирование при записи описывается в разделе 5.6). Поле смещения в элементе отражает это различное начальное расположение. Третья и четвертая структуры vm_map_entry описывают области неинициализированных данных и стека соответственно. Обе эти области представлены анонимными объек- объектами. Анонимный объект предоставляет при первом использовании заполненную нулями страницу и организует сохранение модифицированных страниц в области подкачки, если памяти становится мало. Анонимные объекты более подробно описаны далее в этом разделе. Передача отказов страниц Когда процесс пытается получить доступ к участку своего адресного пространства, который не присутствует в настоящий момент в оперативной памяти, возникает отказ страницы. Обработчику отказов страниц в ядре передается виртуальный адрес, вызвавший отказ, и тип доступа, попытка которого была предпринята (чтение или запись). Отказ обрабатывается за следующие четыре шага. 1. Находится структура vmspace для процесса, вызвавшего отказ; из этой структуры находится начало списка vm_map_entry. 2. Проходится список vm_map_entry, начиная с элемента, указанного в подсказке ото- отображения; для каждого элемента проверяется, попадает ли адрес, вызвавший отказ, в диапазон между началом и концом адресов элемента. Если ядро достигает конца списка, не найдя ни одного действительного диапазона, адрес, вызвавший отказ, не находится в действительной части адресного пространства для процесса, поэтому процессу посылается сигнал отказа сегмента. 3. После обнаружения vm_map_entry, содержащей адрес, вызвавший отказ, этот адрес преобразуется в смещение внутри нижележащего объекта. Смещение внутри объекта вычисляется как смещение_объекта = адрес_отказа - vm_map_entry->нaчaльный_aдpec + vm_map_entry->CMeiueHHe_o6beKTa Вычитается начальный адрес, чтобы получить смещение в область, отображенную посредством vm_map_entiy. Прибавляется смещение объекта, чтобы получить абсолютное смещение страницы внутри объекта.
5.4. Ресурсы процесса 187 vmspace vmjnap vm_pmap statistics vm_map_entry start addr end addr obj offset vnode / объект vm_map_entry start addr end addr obj offset vm_map_entry vnode / объект РИС. 5.6. Схема адресного пространства vm_page vm_page vm_page 4. Передать абсолютное смещение в объекте нижележащему объекту, который выде- выделяет структуру vm_page и использует пейджер (pager) для заполнения страницы. Объект возвращает затем указатель на структуру vm_page, которая отображена на участок адресного пространства процесса, вызвавший отказ. Когда соответствующая страница была отображена на участок, вызвавший отказ, обработчик отказов страниц возвращается и повторно выполняет инструкцию, вызвав- вызвавшую отказ.
188 Глава 5. Управление памятью Отображение на объекты Объекты используются для хранения информации либо о файле, либо об области ано- анонимной памяти. Отображен ли файл единственным процессом в системе или множест- множеством процессов в системе, он всегда будет представлен одним объектом. Таким обра- образом, объект отвечает за поддержание всех состояний всех тех страниц файла, которые являются резидентными. Все ссылки на этот файл будут описываться структурами vm_map_entry, которые ссылаются на один и тот же объект. Объект никогда не хранит одну и ту же страницу файла более чем в одной странице физической памяти, поэтому все отображения получат непротиворечивое представление файла. Объект хранит следующую информацию. Список страниц для этого объекта, которые присутствуют в настоящий момент в оперативной памяти; страница может быть отображена на множество адресных пространств, но она всегда заявляется ровно одним объектом. * Подсчет числа структур vm_map_entry или других объектов, которые ссылаются на объект. Размер файла или анонимной области, описываемой объектом. * Число резидентных в памяти страниц, удерживаемых объектом. Указатель на теневые объекты (описаны в разделе 5.5). Тип пейджера для объекта; пейджер отвечает за предоставление данных для заполнения страницы и за предоставление места для хранения страницы, когда она была модифицирована (пейджеры рассматриваются в разделе 5.10). В системе имеются три типа объектов. Именованные объекты представляют файлы; они могут также представлять аппаратные устройства, которые могут предоставлять отображаемую память, такие, как буферы кадров. Анонимные объекты представляют области памяти, которые при первом использо- использовании заполняются нулями; когда они больше не нужны, от них отказываются. Теневые объекты содержат индивидуальные копии модифицированных страниц; когда на них больше не ссылаются, от них отказываются. Эти объекты часто называют в исходном коде «внутренними» объектами. Тип объ- объекта определяется типом пейджера, который этот объект использует для завершения запросов отказов страниц. Именованный объект использует либо пейджер устройства, если он отображает аппаратное устройство, либо пейджер vnode, если он поддерживается файлом в файло- файловой системе. Пейджер устройства обслуживает отказ страницы, возвращая соответст- соответствующий адрес для отображаемого устройства. Поскольку память устройства обособлена
5.4. Ресурсы процесса 189 от основной памяти на машине, она никогда не будет выбрана демоном выгрузки страниц. Таким образом, пейджеру устройства никогда не приходится обрабатывать запрос сброса страницы. Пейджер vnode предоставляет интерфейс для объектов, которые представляют файлы в файловой системе. Пейджер vnode отслеживает ссылку на vnode, которая представляет отображаемый в объект файл. Пейджер vnode обслуживает запрос загрузки страницы, осуществляя чтение с vnode; запрос сброса страницы он обслужи- обслуживает, осуществляя запись в vnode. Таким образом, файл сам сохраняет модифицирован- модифицированные страницы. В случаях, когда непосредственно модифицировать файл неуместно, как в случае исполняемого файла, которому не нужно модифицировать свои страницы инициализированных данных, ядро должно вставить между vm_map_entiy и объектом, представляющим файл, анонимный теневой объект; см. раздел 5.5. Анонимные объекты используют пейджер подкачки. Анонимный объект обслужи- обслуживает запросы загрузки страницы, получая страницу памяти из списка свободных стра- страниц и обнуляя эту страницу. Когда для страницы в первый раз делается запрос выгруз- выгрузки из памяти, пейджер подкачки отвечает за нахождение неиспользующейся страницы в области подкачки, запись содержимого страницы в это пространство и запись информации о том, где эта страница сохранена. Если запрос загрузки страницы посту- поступает для страницы, которая была ранее выгружена, пейджер подкачки отвечает за нахождение места, где сохранена эта страница, и считывание ее содержимого обратно в свободную страницу в памяти. Последующий запрос выгрузки для этой страницы вызовет ее запись в ранее выделенное место. Теневые объекты также используют пейджер подкачки. Они работают так же, как анонимные объекты, за тем исключением, что пейджер подкачки предоставляет им начальные страницы, копируя существующие страницы в ответ на отказы копирования при записи, вместо заполненных нулями страниц. Дальнейшие подробности по пейджерам приведены в разделе 5.10. Объекты У каждого объекта виртуальной памяти есть тип пейджера, обработчик пейджера и свя- связанные с ним индивидуальные данные пейджера. С объектами, отображающими файлы, ассоциирован тип пейджера vnode. Описателем (handle) для типа пейджера vnode является указатель на vnode, для которого следует осуществить ввод/вывод, а индивидуальные данные представляют размер vnode в момент осуществления отображения. Каждый vnode, который отображает файл, имеет связанный с ним объект. Когда для файла, ото- отображенного в память, возникает отказ, можно проверить объект, связанный с файлом, на предмет наличия отказавшей страницы в оперативной памяти. Если страница присутст- присутствует, ее можно использовать. Если страница отсутствует, выделяется новая страница и запрашивается пейджер vnode для заполнения новой страницы.
190 Глава 5. Управление памятью Кеширование в системе виртуальной памяти идентифицируется объектом, который ассоциирован с файлом или областью, которую он представляет. Каждый объект содержит страницы, которые представляют собой кешированное содержание связанного с ним файла или области. Объекты возвращаются обратно, как только счетчик их ссылок падает до нуля. Страницы, связанные с возвращенными объектами, перемещаются в список свободных. Объекты, представляющие анонимную память, возвращаются в ходе очистки мусора после завершения процесса. Однако объекты, которые ссылаются на файлы, являются постоянными. Когда счетчик ссылок vnode падает до нуля, он сохраняется в списке наиболее давно использованных (least-recently used - LRU), известном как кеш vnode; см. раздел 6.6. vnode не освобождает свой объект до тех пор, пока не будет возвращен или использован для другого файла сам vnode. Если не будет недостатка в памяти, объект, связанный с vnode, сохранит свои страницы. Если vnode повторно активируется и возникнет отказ страницы до того, как будет освобождена соответствующая страница, эта страница может быть присоединена повторно, а не считана с диска. Этот кеш напоминает кеш кода, который был в более ранних версиях BSD, в том, что он обеспечивает повышение производительности быстро завершающихся, но часто запускаемых программ. Часто запускаемые программы включают те, которые используются для перечисления содержимого каталогов, отображения состояния сис- системы или осуществления промежуточных шагов, вовлеченных в компилирование про- программы. Например, рассмотрите типичное приложение, которое построено из несколь- нескольких исходных файлов. Каждый из нескольких шагов компилятора должен быть по очереди запущен для каждого файла. В первый раз, когда запускается компилятор, исполняемые файлы, связанные с различными его компонентами, считываются с диска. Для каждого компилируемого после этого файла находятся ранее созданные исполняе- исполняемые файлы, так же как и ранее считанные файлы заголовков, смягчая необходимость каждый раз повторно загружать их с диска. Объекты к страницам Когда система впервые загружается, ядро просматривает физическую память на маши- машине, чтобы выяснить, сколько страниц доступно. После того как вычтено количество физической памяти, которая будет выделена самому ядру, все оставшиеся страницы физической памяти описываются структурами vmjpage. Эти структуры vm_page первоначально помещаются в список свободной памяти. По мере запуска системы и начала выполнения процессов они генерируют отказы страниц. Каждый отказ стра- страницы сопоставляется с объектом, который охватывает участок адресного пространства, вызвавшего отказ. В первый раз, когда участок объекта вызвал отказ, он должен выде- выделить страницу из списка свободных и инициализировать эту страницу, либо заполнив ее нулями, либо считав ее содержимое из файловой системы. Эта страница становится затем связанной с объектом. Таким образом, у каждого объекта есть текущий набор связанных с ним структур vmjpage. Страница в одно и то же время может быть связана
5.5. Разделяемая память 191 самое большее с одним объектом. Хотя файл может быть отображен сразу в несколько процессов, все эти отображения ссылаются на один и тот же объект. Наличие лишь одного объекта для каждого файла гарантирует, что все процессы будут ссылаться на одни и те же физические страницы. Если памяти становится недостаточно, демон подкачки будет искать страницы, которые не использовались активно. До того как эти страницы смогут быть использо- использованы новым объектом, они должны быть удалены из всех процессов, которые в настоя- настоящее время их отображают, и любое модифицированное содержимое должно быть сохранено объектом, который им владеет. После очистки страницы можно удалить из объекта, который ими владел, и поместить в список свободных для повторного исполь- использования. Подробности системы страничной подкачки описаны в разделе 5.12. 5.5. Разделяемая память В разделе 5.4 мы объяснили, как организовано адресное пространство процесса. В данном разделе показаны дополнительные структуры данных, необходимые для поддержки разделяемого между процессами адресного пространства. Традиционно адресное пространство каждого процесса было полностью изолировано от адресного пространства всех других процессов, работающих в системе. Единственным исключе- исключением было разделение с доступом только для чтения кода программы. Все межпро- межпроцессное взаимодействие осуществлялось посредством хорошо определенных путей, которые проходили через ядро: каналы, сокеты, файлы и специальные устройства. Преимуществом этого изолированного подхода является то, что независимо от каких бы то ни было разрушительных действий процесса в его собственном адресном про- пространстве он не мог повлиять на адресное пространство любых других работающих в системе процессов. Каждый процесс может строго контролировать отправку и по- получение данных; он может также точно определять места внутри своего адресного пространства, которые читаются или записываются. Недостатком этого подхода явля- является то, что все межпроцессные взаимодействия требуют по крайней мере двух сис- системных вызовов: одного для отправляющего процесса и одного для получающего про- процесса. Для больших объемов межпроцессного взаимодействия, особенно при обмене небольшими пакетами данных, в стоимости коммуникации преобладают издержки системных вызовов. Разделяемая память предоставляет способ значительно снизить стоимость меж- межпроцессной коммуникации. Два или более процессов, которые хотят взаимодейство- взаимодействовать, отображают один и тот же участок читаемой-записываемой памяти в свое адрес- адресное пространство. Когда все процессы отобразили память в свое адресное пространст- пространство, любые изменения этого участка памяти видны всем другим процессам без всякого вмешательства со стороны ядра. Таким образом, межпроцессное взаимодействие может быть достигнуто без всяких издержек на системные вызовы, кроме стоимости
192 Глава 5. Управление памятью первоначального отображения. Недостатком этого подхода является то, что, если про- процесс, отобразивший данные, повредит структуры данных в этой памяти, все другие процессы, отображающие эту память, также будут повреждены. Кроме того, перед разработчиком приложения возникает сложность относительно того, кто должен разрабатывать структуры данных для управления доступом к разделяемой памяти и справляться с условиями гонки (race condition), присущими манипулированию и управлению такими структурами данных, доступ к которым осуществляется парал- параллельно. В некоторых вариантах UNIX есть механизм основанного на ядре семафора для предоставления необходимого упорядочивания доступа к разделяемой памяти. Однако как получение, так и установка таких семафоров требуют системных вызовов. Издержки по использованию таких семафоров сравнимы с издержками использования традици- традиционных методов межпроцессного взаимодействия. К сожалению, эти семафоры имеют все сложности разделяемой памяти, давая к тому же лишь незначительную часть пре- преимуществ в скорости. Главной причиной для введения сложности разделяемой памяти является соразмерный выигрыш в скорости. Если должен быть получен этот выигрыш, большая часть потребностей в блокировке структур данных должна осуществляться в самом сегменте разделяемой памяти. Основанные на ядре семафоры должны исполь- использоваться лишь в тех редких случаях, когда имеется соперничество за блокировку и один процесс должен ждать. Поэтому современные интерфейсы, такие, как POSIX Pthreads, спроектированы таким образом, что семафоры могут быть расположены в области разделяемой памяти. В общем случае установка или освобождение неос- париваемого семафора могут осуществляться процессом пользователя без вызова ядра. Имеются два случая, когда процесс должен сделать системный вызов. Если про- процесс пытается установить уже заблокированный семафор, он должен вызвать ядро, чтобы заблокироваться, пока семафор не станет доступным. Этот системный вызов мало влияет на производительность, поскольку блокировка оспаривается, поэтому невозможно продолжать работу и в любом случае должно быть вызвано ядро для пере- переключения контекста. Если процесс освобождает семафор, который хочет получить другой процесс, он должен вызвать ядро, чтобы пробудить этот процесс. Поскольку большинство блокировок не оспариваются, приложения могут работать с полной скоростью без вмешательства ядра. Модель mmap Когда два процесса хотят создать область разделяемой памяти, они должны иметь какой-нибудь способ обозначения участка памяти, который они хотят разделять, и они должны иметь возможность описать его размер и начальное содержание. Системный интерфейс, описывающий область разделяемой памяти, выполняет все эти задачи, используя в качестве основы для описания сегмента разделяемой памяти файлы. Процесс создает сегмент разделяемой памяти, используя
5.5. Разделяемая память 193 caddr_t addr - mmap( caddr_t address, /* базовый адрес */ size_t length, /* размер области */ int protection, /* защита области */ int flags, /* флаги отображения */ int fd, /* файл для отображения */ off_t offset); /* смещение для начала отображения */ чтобы отобразить файл, на который ссылаются посредством дескрипторами, начиная с файлового смещения offset в свое адресное пространство, начиная с addr и в продол- продолжение len байтов с правами доступа/?/х>/. Параметр flags дает процессу возможность указать, хочет ли он сделать разделяемое или индивидуальное отображение. Измене- Изменения, сделанные в разделяемом отображении, записываются обратно в файл и видны другим процессам. Изменения, сделанные в индивидуальном отображении, обратно в файл не записываются и не видны другим процессам. Два процесса, которые хотят разделять участок памяти, запрашивают разделяемое отображение одного и того же файла в свое адресное пространство. Таким образом, разделяемые объекты идентифи- идентифицирует существующее и хорошо понятное пространство имен файловой системы. Содержимое файлов используется в качестве начальных значений сегмента памяти. Все изменения, сделанные в отображении, отражаются обратно в содержании файла, поэтому в области разделяемой памяти можно поддерживать долгосрочное состояние даже между различными вызовами разделяющих процессов. Некоторые приложения хотят использовать разделяемую память исключительно в качестве краткосрочного механизма межпроцессного взаимодействия. Им нужна область памяти, которая изначально заполнена нулями и содержание которой игнориру- игнорируется после ее использования. Такие процессы не хотят после завершения работы с памятью ни платить относительно высокую цену на старте, связанную с подкачкой содержи- содержимого файла для инициализации сегмента разделяемой памяти, ни платить цену при завершении, связанную с записью модифицированных страниц обратно в файл. Хотя FreeBSD на самом деле предоставляет в качестве механизма взаимодействия для такой краткосрочной разделяемой памяти ограниченную и причудливую схему именования интерфейса System V shmem, дизайнеры в конечном счете решили, что все именование объектов для ттар должно использовать пространство имен файловой системы. Чтобы предоставить эффективный механизм для краткосрочной разделяемой памяти, они создали резидентную файловую систему виртуальной памяти для временных объектов. Если нет недостатка в памяти, файлы, созданные в резидентной файловой системе виртуальной памяти, целиком находятся в оперативной памяти. Таким обра- образом, устраняется цена как начальной страничной подкачки, так и последующей обрат- обратной записи. Обычно резидентная файловая система виртуальной памяти монтируется в /tmp. Два процесса, желающие создать временную область разделяемой памяти, создают в /tmp файл, который они оба могут потом отобразить в свои адресные пространства.
194 Глава 5. Управление памятью Когда отображение больше не нужно, его можно удалить, используя munmap(caddr_t address, size_t length); Системный вызов munmap удаляет любые отображения, которые существуют в адресном пространстве, начиная с addr и на протяжении len байтов. Между предыду- предыдущими отображениями и последующим munmap ограничений нет. Указанный диапазон может быть подмножеством предыдущего ттар, или он может заключать область, содержащую множество созданных ттар файлов. При завершении процесса система выполняет неявное munmap для всего его адресного пространства. В ходе первоначального отображения процесс может установить защиту страни- страницы, чтобы разрешить чтение, запись и/или выполнение. Процесс может изменить эти права доступа, используя mprotect(caddr_t address, int length, int protection); Эта особенность может использоваться отладчиками, когда они пытаются отсле- отследить ошибку повреждения памяти. Запретив запись на странице, содержащей струк- структуру данных, которая повреждается, отладчик может перехватить все записи в страницу и проверить их правильность, перед тем как позволить их выполнение. Традиционно программирование для систем реального времени осуществлялось посредством специально написанных операционных систем. В интересах снижения стоимости приложений реального времени и использования навыков большого количе- количества программистов UNIX компании, разрабатывающие приложения реального време- времени, выразили повышенный интерес в использовании основанных на UNIX систем для написания этих приложений. Двумя фундаментальными требованиями систем реаль- реального времени являются гарантированные максимальные задержки и предсказуемое время выполнения. Обеспечить предсказуемое время выполнения в системе, основан- основанной на виртуальной памяти, трудно, поскольку отказ страницы может возникнуть в любой момент выполнения программы, вызвав потенциально большую задержку, пока вызвавшая отказ страница не будет получена с диска или из сети. Чтобы избежать задержек страничной подкачки, система допускает форсирование процессом резидент- резидентное™ своих страниц и отмену выгрузки путем использования mlock(caddr_t address, size_t length); Пока процесс ограничивает свой доступ к заблокированной области своего адрес- адресного пространства, он может быть уверен, что задержки из-за отказов страниц не воз- возникнет. Чтобы предотвратить получение одним процессом всей физической памяти на машине с ущербом для всех других процессов, система налагает ограничение ресурсов для контролирования объема памяти, которая может быть заблокирована. Обычно это ограничение устанавливается в размере не более одной трети физической памяти, и оно может быть установлено в ноль системным администратором, который не хочет, чтобы случайные процессы монополизировали системные ресурсы.
5.5. Разделяемая память 195 Когда процесс завершил работу с критичным ко времени использованием забло- заблокированной mlock области, он может освободить страницы, используя munlock(caddr_t address, size_t length); После вызова munlock страницы в указанном диапазоне адресов по-прежнему дос- доступны, но они могут быть выгружены, если требуется память и к ним не осуществля- осуществляется доступ. Приложению может понадобиться обеспечить, чтобы на диске были зафиксиро- зафиксированы определенные записи без форсирования записи всех грязных страниц файла, осуществляемого системным вызовом fsync. Например, программе базы данных может понадобиться зафиксировать отдельную область метаданных без обратной записи всех «грязных» блоков в своем файле базы данных. Процесс выполняет эту выборочную синхронизацию, используя msync(caddr_t address, int length); Обратно в файловую систему записываются лишь модифицированные страницы внутри указанного диапазона адресов. Системный вызов msync не оказывает никакого воздействия на анонимные области. Разделяемое отображение Когда несколько процессов отображают в свои адресные пространства один и тот же файл, система должна гарантировать, что все процессы видят один и тот же набор стра- страниц памяти. Как показано в разделе 5.4, каждый файл, который активно используется клиентом системы виртуальной памяти, представлен объектом. Каждое отображение, которое есть у процесса на участок файла, описывается структурой vm_map_entry. Пример двух процессов, отображающих в свои адресные пространства один и тот же файл, показан на рис. 5.7. Когда в одном из этих процессов возникает отказ страницы, vm_map_entiy процесса обращается к объекту, чтобы найти соответствующую страницу. Поскольку все отображения ссылаются на один и тот же объект, все процессы получат ссылки на одно и то же множество физической памяти, что обеспечит таким образом то, что сделанные одним процессом изменения будут видимы также в адресных про- пространствах всех других процессов. Индивидуальное отображение Процесс может запросить индивидуальное отображение файла. Индивидуальное ото- отображение имеет два главных следствия. 1. Изменения, сделанные в отображенном в память файле, не отражаются обратно в ото- отображенном файле.
196 Глава 5. Управление памятью Процесс А: vm_map_entry Процесс В: vm_map_entry Рис. 5.7. Множественные отображения файла Объект файла 2. Изменения, сделанные в памяти, на которую отображен файл, невидимы другим процессам, отображающим файл. Примером использования индивидуального отображения было бы отображение во время отладки программы. Отладчик запрашивает индивидуальное отображение кода программы таким образом, что при установке точки останова изменение не записыва- записывается обратно в исполняемый файл, хранящийся на диске, и не видно другим (предпо- (предположительно неотлаживаемым) процессам, выполняющим программу. Для предотвращения отражения изменений, сделанных процессом, обратно в ни- нижележащий объект ядро использует теневые объекты. Использование теневого объек- объекта показано на рис. 5.8. Когда запрашивается первоначальное индивидуальное отобра- отображение, объект файла отображается в адресное пространство запрашивающего процес- процесса с семантикой копирования при записи (copy-on-write). Если процесс пытается запи- записать в страницу объекта, возникает отказ страницы с вовлечением ядра. Ядро делает копию модифицируемой страницы и представляет ее из теневого объекта. В данном примере процесс А модифицировал страницу 0 объекта файла. Ядро скопировало стра- страницу 0 в теневой объект, который используется для предоставления индивидуального отображения для процесса А. Если свободная память ограничена, было бы лучше просто переместить модифи- модифицированную страницу из объекта файла в теневой объект. Перемещение снизило бы немедленную потребность в свободной памяти, поскольку не нужно было бы выделять новую страницу. Недостатком этой оптимизации является то, что, если к объекту файла есть последующий доступ со стороны некоторого другого процесса, ядру при- придется выделять новую страницу. Ядру придется также заплатить стоимость осуществ- осуществления операции ввода/вывода для повторной загрузки содержимого страницы. В FreeBSD система виртуальной памяти никогда не перемещает страницу вместо ее копирования.
5.5. Разделяемая память 197 Процесс А: vm_map_entry Отображение 0 N > Теневой объект t |Страница 0| > Объект файла t |Страница 1| |Страница 0| (изменена (процессом) А) Д-?. (не изменена) Рис. 5.8. Использование теневого объекта для индивидуального отображения Когда для индивидуального отображения возникает отказ страницы, ядро прохо- проходит через список объектов, возглавляемый vm_map_entry, в поиске страницы, вызвав- вызвавшей отказ. Используется первый объект в цепочке, содержащий нужную страницу. Если поиск доходит до последнего объекта в цепи без обнаружения нужной страницы, она запрашивается из этого последнего объекта. Таким образом, преимущественно будут использоваться теневые объекты вместо тех же страниц в самом объекте файла. Подробности обработки отказа страницы приводятся в разделе 5.11. Когда процесс удаляет отображение из своего адресного пространства (либо явно из запроса типтар, либо неявно, когда адресное пространство освобождается при за- завершении процесса), страницы, удерживаемые его теневым объектом, не записывают- записываются обратно в объект файла. Страницы теневого объекта просто помещаются обратно в список свободной памяти для немедленного повторного использования. Когда процесс разветвляется (fork), он не хочет, чтобы изменения его индивиду- индивидуальных отображений, сделанные после разветвления, были видны в порожденном про- процессе; так же и порожденный процесс не хочет, чтобы его изменения были видны родителю. В результате каждому процессу нужно создать теневой объект, если он про- продолжает делать изменения в индивидуальном отображении. Когда процесс А на рис. 5.8 разветвляется, создается ряд цепей теневых объектов, как показано на рис. 5.9. В данном примере процесс А изменил страницу 0 до разветвления, а затем изменил страницу 1. Его модифицированная версия страницы 1 фиксируется его новым тене- теневым объектом, поэтому эти изменения не будут видны порожденному им процессу. Таким же образом порожденный процесс изменил страницу 0. Если бы потомок изме- изменил страницу 0 в первоначальном теневом объекте, это изменение было бы видно его родителю. Поэтому порожденный процесс должен сделать новую копию страницы 0 в своем собственном теневом объекте.
198 Глава 5. Управление памятью Процесс А: Теневой vm_map_entry объект Отображение 0 N —> Объект 3 [Страница Ц (изменена родителем) Процесс, порожденный А: Теневой vm_map_entry объект Отображение О N Объект 2 Теневой объект Объект файла Объект 1 ± [Страница 01 | Страница 01 l " N/ [Страница l[ (изменен потомком) (изменен А ^у до разветвления) (не изменен) Рис. 5.9. Цепи теневых объектов Если система испытывает недостаток памяти, ядру может потребоваться вернуть неактивную память, удерживаемую в теневом объекте. Ядро назначает пейджеру под- подкачки задачу резервирования теневого объекта. Пейджер подкачки устанавливает структуры данных (описанные в разделе 5.10), которые могут описать все содержимое теневого объекта. Затем он выделяет достаточное количество пространства подкачки, чтобы вместить затребованные теневые страницы и записать их в эту область. После этого страницы могут быть использованы для других нужд. Если последующий отказ страницы затребует выгруженные страницы, тогда выделяется новая страница памяти, а ее содержимое повторно загружается путем ввода/вывода из области подкачки. Сворачивание теневых цепочек Когда процесс с индивидуальным отображением удаляет это отображение либо явно посредством системного вызова типтар, либо неявно путем завершения, его родитель или порожденный процесс могут остаться с цепочкой теневых объектов. Обычно эти цепочки теневых объектов можно сократить до одного теневого объекта, часто освобож- освобождая память как часть сокращения. Рассмотрите, что случится, когда процесс А на
5.5. Разделяемая память 199 рис. 5.9 завершится. Сначала может быть освобожден теневой объект 3 вместе со свя- связанной с ним страницей памяти. Это освобождение оставляет теневые объекты 1 и 2 в цепочке без промежуточных ссылок. Таким образом, эти два объекта можно сокра- сократить до одного теневого объекта. Поскольку они оба могут содержать копию страницы 0 и поскольку остающиеся порожденные процессы в теневом объекте 2 могут получить доступ лишь к странице 0, страницу 0 в теневом объекте 1 можно ос- освободить вместе с самим теневым объектом 1. Если бы процесс порожденный процессом А, завершился, тогда можно было бы освободить теневой объект 2 и связанную с ним страницу памяти. Теневые объекты 1 и 3 оказались бы в цепочке, подходящей для сокращения. Здесь нет общих страниц, поэтому объект 3 сохранил бы свою собственную страницу 1 и получил бы страницу О от теневого объекта 1. Объект 1 был бы затем освобожден. Помимо объединения стра- страниц от двух объектов операция сокращения требует сходного объединения любого пространства подкачки, которое было выделено этими двумя объектами. Если страница 2 была скопирована в объект 3, а страница 4 была скопирована в объект 1, но эти страницы позже были возвращены, пейджер для объекта 3 сохранил бы блок под- подкачки для страницы 2, а пейджер для объекта 1 сохранил бы блок подкачки для страницы 4. До освобождения объекта 1 его блок подкачки для страницы 4 нужно было бы передать объекту 3. Может возникнуть проблема производительности, если либо сам процесс, либо порожденные им процессы повторно разветвляются. Без какого-нибудь вмешательства они могут создать длинные цепочки теневых объектов. Если процессы долгоживущие, система не получает возможности сократить эти цепочки теневых объектов. Прохож- Прохождение по этим длинным цепочкам теневых объектов для разрешения отказов страниц является времязатратным, и может накопиться множество недоступных страниц, заставляя систему без нужды выгружать их для их повторного использования. Одной из альтернатив было бы вычисление числа существующих ссылок на стра- страницу после каждого отказа копирования при записи. Когда осталась лишь одна суще- существующая ссылка, страницу можно было бы переместить в теневой объект, который по-прежнему ссылается на нее. Когда все страницы были бы перемещены из теневого объекта, его можно было удалить из цепи. Например, на рис. 5.9, когда порожденный процессом А процесс записал в страницу 0, в теневом объекте 2 была сделана копия страницы 0. В этот момент единственной существующей ссылкой на страницу 0 в объекте 1 была ссылка от процесса А. Таким образом, страницу 0 в процессе 1 можно было бы переместить в объект 3. Это оставило бы объект 1 без страниц, поэто- поэтому его можно было вернуть, оставив объекты 2 и 3 указывать непосредственно на объект файла. К сожалению, эта стратегия добавила бы значительные накладные рас- расходы к процедуре обработки отказа страниц, что значительно уменьшило бы общую производительность системы. Поэтому FreeBSD не делает такой оптимизации. FreeBSD использует менее затратную эвристику для уменьшения длины теневых цепочек. При получении отказа копирования при записи она проверяет, полностью ли
200 Глава 5. Управление памятью экранирует объект, вызвавший отказ, нижележащий объект в цепочке. Когда достига- достигается полное перекрытие, она может обойти этот объект и ссылаться на следующий объект в цепочке. Удаление его ссылки дает тот же результат, как если бы процесс за- завершился и позволил осуществить нормальное сокращение теневой цепочки с единст- единственной остающейся ссылкой на объект. Например, на рис. 5.9, когда потомок процесса А записал в страницу 0, в теневом объекте 2 была сделана копия страницы 0. В этот момент объект 2 полностью экранирует объект 1, поэтому объект 2 может удалить свою ссылку на объект 1 и указать непосредственно на объект файла. С оставшейся лишь одной ссылкой на объект 1 произойдет сокращение объекта 3 и объекта 1, как уже описывалось. Стоимость определения того, полностью ли один объект покрывает другой ниже себя, значительно дешевле, чем отслеживание существующих ссылок. Поэтому этот подход используется FreeBSD. На практике эта эвристика работает хорошо. Даже в случае интенсивно загружен- загруженных родительских или порожденных процессов, что имеет место во многих сетевых серверах, глубина теневой цепочки обычно стабилизируется на глубине от трех до че- четырех уровней. Индивидуальные моментальные снимки Когда процесс осуществляет доступ чтения к индивидуальному отображению объекта, он продолжает видеть изменения, сделанные в этом объекте другими процессами, которые записывают в объект через файловую систему или имеют разделяемое отобра- отображение объекта. Когда процесс осуществляет доступ записи к индивидуальному ото- отображению объекта, делается моментальный снимок (snapshot) соответствующей стра- страницы объекта и сохраняется в теневом объекте, и изменения осуществляются с этим снимком. Таким образом, дальнейшие изменения этой страницы, сделанные другими процессами, которые записывают в страницу через файловую систему или имеют раз- разделяемое отображение объекта, больше не видны. Однако изменения немодифициро- ванных страниц объекта продолжают быть видимыми. Эта смесь изменяющихся и неизменяющихся частей файла может сбивать с толку. Для предоставления более непротиворечивого вида файла процесс может сделать моментальный снимок файла в тот момент, когда он был впервые отображен индиви- индивидуально. Изначально как Mach, так и 4.4BSD предоставили объект копирования, целью которого было изготовление моментального снимка объекта в момент установки индиви- индивидуального отображения. Объект копирования отслеживал изменения объекта другими процессами и сохранял оригинальные копии всех страниц, которые изменялись. Ни один из других производителей UNIX не реализовал объекты копирования, и нет больших приложений, которые от этого зависят. Код объекта копирования в системе виртуальной памяти был большим и сложным, и он заметно уменьшал производитель- производительность виртуальной памяти. Поэтому объекты копирования были сочтены ненужными и удалены из FreeBSD как часть ранней очистки и работы по повышению производи-
5.6. Создание нового процесса 201 тельности, сделанной с системой виртуальной памяти. Приложения, которым нужно получить моментальный снимок файла, могут сделать это, прочитав его в свое адрес- адресное пространство или сделав его копию в файловой системе и ссылаясь впоследствии на эту копию. 5.6. Создание нового процесса Процессы создаются посредством системного вызова fork. За fork вскоре обычно сле- следует системный вызов exec, который накладывает на виртуальное адресное простран- пространство порожденного процесса содержимое исполняемого образа, который находится в файловой системе. Затем процесс выполняется до тех пор, пока не завершится, либо добровольно, либо принудительно путем получения сигнала. В разделах 5.6 и 5.9 мы проследим управление за ресурсами памяти, использующееся на каждом шаге этого цикла. Системный вызов fork дублирует адресное пространство существующего процес- процесса, создавая идентичный порожденный процесс. Набор системных вызовов fork явля- является единственным способом создания новых процессов в FreeBSD. fork дублирует все ресурсы оригинального процесса и копирует адресное пространство этого процесса. Ресурсы виртуальной памяти процесса, которые должны быть выделены для по- потомка, включают структуру процесса и связанные с ней подструктуры, а также струк- структуру пользователя и стек ядра. Кроме того, ядро должно забронировать хранилище (либо память, либо пространство файловой системы, либо пространство подкачки), ис- использующееся для резервирования процесса. Общий набросок реализации fork сле- следующий. * Резервирование адресного пространства для порожденного процесса. Выделение структуры потока и элемента процесса и ее заполнение. * Копирование в порожденный процесс группы процесса родителя, мандатов (credentials), дескрипторов файлов, квот (limits) и действий сигналов. * Выделение новой структуры пользователя и стека ядра, копирование в них содержи- содержимого из текущего процесса для их инициализации. Выделение структуры vmspace. * Дублирование адресного пространства путем создания копий структур vm_map_entry родителя, помеченных для копирования при записи. Организация возвращения порожденным процессом 0, чтобы отличить его возвра- возвращаемое значение от нового PID, который возвращается в процесс родителя.
202 Глава 5. Управление памятью Выделение и инициализация структуры процесса и организация возвращаемого значения были рассмотрены в главе 4. В оставшейся части данного раздела обсуж- обсуждаются другие шаги, вовлеченные в дублирование процесса. Резервирование ресурсов ядра Первым ресурсом, который должен быть зарезервирован при дублировании адресного пространства, является необходимое виртуальное адресное пространство. Чтобы избе- избежать выхода за пределы памяти, ядро должно убедиться, что оно не обещает предоста- предоставить больше виртуальной памяти, чем оно способно предоставить. Общий размер виртуальной памяти, которая может быть предоставлена системой, ограничен количе- количеством доступной для страничной подкачки физической памяти плюс объем преду- предусмотренного пространства подкачки. Несколько страниц сохраняются в резерве для организации ввода/вывода между областью подкачки и основной памятью. Причина этого ограничения в том, что процессы получают синхронные уведомле- уведомления об ограничениях памяти. В частности, процесс должен получить сообщение об ошибке из системного вызова (такого, как sbrk, fork или ттар), если недостаточно ресурсов для выделения необходимой виртуальной памяти. Если ядро обещает больше виртуальной памяти, чем оно может обеспечить, оно может попасть в тупик, пытаясь обслужить отказ страницы. Проблема возникает, когда нет свободных страниц для обслуживания отказа и нет доступного пространства подкачки для сохранения активной страницы. Здесь у ядра нет выбора, кроме посылки сигнала завершения (kill) процес- процессам, неудачно вызвавшим отказ страницы. Такое асинхронное уведомление о недоста- недостаточности ресурсов памяти является неприемлемым. Из этого ограничения исключаются лишь те части адресного пространства, которые отображаются с правом только для чтения (такие, как код программ). Любые страницы, которые используются для части адресного пространства с правом доступа только для чтения, можно вернуть без сохранения, поскольку их содержимое можно восстановить из первоначального источника. Из этого ограничения исключаются также части адресного пространства, которые отображают разделяемые файлы. Ядро может вернуть любые страницы, которые используются для разделяемого отображе- отображения, после записи их содержимого обратно в файловую систему, из которой они были отображены. Здесь файловая система используется как расширение области подкачки. Наконец, любой участок памяти, который используется более чем одним процессом (такой, как область анонимной памяти, разделяемой несколькими процессами), следу- следует учитывать лишь один раз в подсчете предела виртуальной памяти. Ограничение на размер виртуального адресного пространства, которое можно выделить, вызывает проблемы для приложений, которые хотят выделять большие уча- участки адресного пространства, но использовать их лишь изредка. Например, процесс может захотеть сделать индивидуальное отображение большой базы данных, лишь к небольшой части которой будет осуществляться доступ. Поскольку у ядра нет способа
5.6. Создание нового процесса 203 гарантировать, что доступ будет редким, оно принимает пессимистическую точку зре- зрения, что будет модифицироваться весь файл, и отвергнет запрос. Одним из расшире- расширений, которое сделали многие производные из BSD-системы к системному вызову ттар, является добавление флага, который сообщает ядру, что процесс готов к приему асинхронных отказов в отображении. Такому отображению было бы позволено ис- использовать такой объем виртуальной памяти, который не был обещан другим процес- процессам. Если затем процесс модифицирует большую часть файла, чем эта доступная па- память, или если возможности уменьшены другими процессами, выделяющими гаран- гарантированную память, ядро может послать процессу отказ сегментации или более специ- специфичный сигнал. Получив сигнал, процесс должен выполнить типтар для ненужной части файла, чтобы вернуть ресурсы обратно в систему. Процесс должен гарантиро- гарантировать, что структуры кода, стека и данных, необходимые для обработки сигнала отказа сегмента, не находятся в той части адресного пространства, которая подвержена таким отказам. Точное отслеживание невыделенной (outstanding) виртуальной памяти и определе- определение того, когда следует ограничить дальнейшие распределения, является сложной за- задачей. Поскольку большинство процессов используют лишь около половины своего виртуального адресного пространства, ограничение невыделенной виртуальной памяти суммой адресных пространств процесса является чрезмерно консервативным. Однако допущение больших выделений создает риск выхода за пределы виртуальной памяти. Хотя FreeBSD вычисляет загрузку невыделенной памяти, она не проводит в жизнь какое-нибудь ограничение общей памяти, поэтому ее можно заставить пообе- пообещать больше, чем она может предоставить. Когда ресурсы памяти заканчиваются, она выбирает процесс для завершения, предпочитая процессы с большим использованием памяти. Важным будущим усовершенствованием будет разработка эвристики для определения того, когда ресурсы виртуальной памяти близки к завершению и нуж- нуждаются в ограничении. Дублирование адресного пространства пользователя Следующим шагом в/oj'k является выделение и инициализация структуры нового про- процесса. Эта операция должна быть сделана до того, как адресное пространство текуще- текущего процесса будет дублировано, поскольку она записывает состояние в структуру про- процесса. С момента времени, когда выделяется структура процесса, до тех пор, пока не будут выделены все необходимые ресурсы, родительский процесс заблокирован от подкачки, чтобы избежать тупиков. Порожденный процесс находится в противоречи- противоречивом состоянии и еще не может быть запущен или выгружен, поэтому необходим роди- родитель, чтобы завершить копирование его адресного пространства. Чтобы гарантировать игнорирование порожденного процесса планировщиком, ядро устанавливает состоя- состояние процесса в течение всей процедуры fork в NEW.
204 Глава 5. Управление памятью Исторически системный вызов/or/: действовал путем копирования всего адресно- адресного пространства родительского процесса. Когда разветвляются большие процессы, копирование всего адресного пространства пользователя дорого. Все страницы, нахо- находящиеся во вторичном хранилище, должны быть для копирования считаны обратно в память. Если памяти для обеих полных копий процесса недостаточно, этот недоста- недостаток памяти заставит систему начать подкачку, чтобы создать достаточно памяти для копирования (см. раздел 5.12). Операция копирования может привести к выгрузке частей родительского и порожденного процессов, а также к выгрузке частей не имеющих к этому отношения процессов. Методика, используемая FreeBSD для создания процессов без этих издержек, называется копирование при записи (copy-on-write). Вместо того чтобы копировать каждую страницу родительского процесса, и родительскому, и порожденному процес- процессам, проистекающим из разветвления, даются ссылки на одни и те же физические стра- страницы. Таблицы страниц изменяются, чтобы предотвратить изменение любым из про- процессов разделяемой страницы. Вместо этого, когда процесс пытается изменить страни- страницу, осуществляется вход в ядро с отказом защиты. После определения того, что отказ был вызван попыткой модификации разделяемой страницы, ядро просто копирует страницу и изменяет поле защиты для страницы, чтобы снова разрешить изменение. Нужно копировать лишь страницы, измененные одним из процессов. Эта методика значительно повышает производительность fork, поскольку разветвляющиеся процессы обычно перекрывают порожденный процесс новым образом при выполнении exec вскоре после fork. Следующим шагом в fork является прохождение по списку структур vm_map_entry в родителе и создание соответствующих элементов в порожденном. Каждый элемент должен быть проанализирован, и должно быть предпринято соответствующее действие. Если элемент отображает область только для чтения или разделяемую область, потомок может получить ссылку на нее. * Если элемент отображает индивидуально отображенную область (такую, как область данных или стек), потомок должен создать отображение области с атрибу- атрибутом копирования при записи. Отображение области в родителе должно быть пре- преобразовано в копирование при записи. Если любой из процессов впоследствии попытается осуществить запись в эту область, он создаст теневое отображение, чтобы сохранить модифицированные страницы. Элементы отображения для процесса никогда не объединяются (упрощаются). Объединяться могут лишь элементы для отображения самого ядра. Элементы отобра- отображения ядра нуждаются в упрощении таким образом, чтобы избежать чрезмерного рас- расширения. Может оказаться целесообразным сделать такое объединение элементов ото- отображения для процесса, когда он разветвляется, особенно для больших или долгоживу- щих процессов.
5.6. Создание нового процесса 205 С выделением ресурсов виртуальной памяти система устанавливает состояния ре- режимов ядра и пользователя нового процесса, включая аппаратные регистры управле- управления памятью, структуру пользователя и стек. Затем она сбрасывает флаг NEW и поме- помещает поток процесса в очередь запуска; после этого новый процесс может начать выполнение. Создание нового процесса без копирования Когда процесс (такой, как оболочка) хочет запустить другую программу, он обычно выполняет fork, делает несколько простых операций, таких, как перенаправление дескрипторов ввода/вывода и изменение действий сигналов, а затем запускает новую программу посредством exec. Между тем родительская оболочка приостанавливает себя с помощью wait до тех пор, пока новая программа не завершится. Для таких операций нет необходимости одновременной работы как родителя, так и потомка, и поэтому требуется лишь одна копия адресного пространства. Этот часто возникающий набор системных вызовов привел к реализации системного вызова vfork. Хотя он чрез- чрезвычайно эффективен, vfork имеет своеобразную семантику и обычно рассматривается как архитектурный изъян. Реализация vfork всегда будет более эффективной, чем реализация копирования при записи, поскольку ядро избегает копирования адресного пространства для потомка. Вместо этого ядро просто передает адресное пространство потомку и приостанавли- приостанавливает родителя. Порожденному процессу не нужно выделять какие-либо структуры виртуальной памяти, так как он получает структуру vmspace и все ее части от своего родителя. Порожденный процесс возвращается из системного вызова vfork, когда родитель все еще приостановлен. Потомок делает все обычные действия, готовясь запустить новую программу, затем вызывает exec. Теперь адресное пространство пере- передается обратно родителю, вместо того чтобы выбрасывать его, как в случае обычного exec. В качестве альтернативы, если порожденный процесс сталкивается с ошибкой и не способен выполнить новую программу, он вызовет exit. Адресное пространство снова передается родителю, вместо того чтобы быть выброшенным. С vfork элементы, описывающие адресное пространство, копировать не нужно, а в элементах таблицы страниц не нужно сначала помечать, а затем сбрасывать атри- атрибут копирования при записи, vfork, вероятно, остается более эффективным, чем копирование при записи или другие схемы, которые должны дублировать виртуальное адресное пространство процесса. Архитектурная особенность вызова vfork в том, что порожденный процесс может изменять содержание и даже размер адресного про- пространства родителя, когда контроль имеет порожденный процесс. Хотя изменение адресного пространства родителя является плохой практикой программирования, известно, что некоторые программы используют эту особенность.
206 Глава 5. Управление памятью 5.7. Исполнение файла Системный вызов exec был описан в разделах 2.4 и 3.1; он замещает адресное про- пространство процесса содержимым новой программы, полученной из исполняемого файла. Во время exec проверяется целевой исполняемый образ, а затем аргументы и переменные окружения копируются из текущего процесса во временную область выгружаемой виртуальной памяти ядра. Чтобы выполнить exec, система должна выделить ресурсы для сохранения нового содержимого виртуального адресного пространства, установить отображение в это адресное пространство нового образа и освободить ресурсы, использовавшиеся для существующей виртуальной памяти. Первым шагом является резервирование ресурсов памяти для нового исполняемо- исполняемого образа. Алгоритм для вычисления количества виртуального адресного пространст- пространства, которое должно быть зарезервировано, был описан в разделе 5.6. Для исполняемого файла, который не отлаживается (и пространство кода которого поэтому не будет изме- изменяться), резервирование пространства должно быть выполнено лишь для пространства данных и стека нового исполняемого файла, exec осуществляет это резервирование без предварительного освобождения текущего назначенного пространства, поскольку сис- система должна иметь возможность продолжать выполнение старого исполняемого файла, пока она не будет уверена, что она сможет запустить новый. Если бы система освободила текущее пространство, а резервирование памяти завершилось неудачей, exec не смог бы вернуться в первоначальный процесс. После осуществления резервирования адресное пространство и ресурсы виртуальной памяти текущего про- процесса освобождаются, как если бы процесс завершался; этот механизм описан в разделе 5.9. Теперь у процесса есть только структура пользователя и стек ядра. Ядро выделяет новую структуру vmspace и создает список четырех структур vm_map_entiy. 1. Элемент с копированием при записи, с заполнением из файла отображает сегмент кода. Используется копирование при записи, а не доступ только для чтения, чтобы дать активным кодовым сегментам возможность иметь установленные отладочные точки останова, не влияя на других пользователей двоичного файла. В FreeBSD неко- некоторый унаследованный код в отладочном интерфейсе ядра не позволяет устанавли- устанавливать точки останова в двоичных файлах, используемых более чем одним процессом. Этот унаследованный код не разрешает использовать особенность копирования при записи и требует, чтобы код отображался с доступом только для чтения. 2. Индивидуальный (копирование при записи) элемент с заполнением из файла ото- отображает сегмент инициализированных данных. 3. Анонимный заполняемый по требованию нулями элемент отображает сегмент неинициализированных данных.
5.8. Манипулирование процесса своим адресным пространством 207 4. Анонимный заполняемый по требованию нулями элемент отображает сегмент стека. Для создания нового адресного пространства во время системного вызова exec не требуется дальнейших операций; оставшаяся часть работы включает копирование аргументов и переменных окружения в вершину нового стека. Для регистров устанав- устанавливаются начальные значения: счетчик команд устанавливается на точку входа, а ука- указатель стека на вектор аргументов. После этого образ нового процесса готов к запуску. 5.8. Манипулирование процесса своим адресным пространством После того как процесс начинает свое выполнение, у него есть несколько способов ма- манипулирования своим адресным пространством. Система всегда разрешает процессам расширять свою область неинициализированных данных (обычно осуществляемую с помощью библиотечной процедуры mallocQ). Стек наращивается по мере необходи- необходимости. Система FreeBSD позволяет также процессу отображать файлы и устройства в произвольные части своего адресного пространства и изменять защиту различных частей своего адресного пространства, как описано в разделе 5.5. В данном разделе описывается, как осуществляются эти манипуляции с адресным пространством. Изменение размера процесса Процесс может изменить свой размер в ходе выполнения, явным образом запросив до- дополнительное пространство данных с помощью системного вызова sbrk. Также сег- сегмент стека будет автоматически расширен, если произойдет столкновение с ошибкой защиты из-за попытки увеличения стека ниже конца области стека. В любом случае размер адресного пространства процесса должен быть изменен. Размер запроса всегда округляется в большую сторону до ближайшего значения, кратного размеру страницы. Новые страницы помечаются для заполнения нулями, поскольку нет содержимого, первоначально связанного с новыми разделами адресного пространства. Первым шагом в увеличении размера процесса является проверка того, не будет ли новый размер нарушать ограничение размера для этого сегмента процесса. Если новый размер в пределах нормы, для увеличения области данных предпринимаются следующие шаги. 1. Проверяется, что ресурсы виртуальной памяти доступны. 2. Проверяется, что адресное пространство запрошенного размера, следующее непо- непосредственно за текущим концом области данных, еще не отображено.
208 Глава 5. Управление памятью 3. Если существующий vmjnap_entiy является единственной ссылкой на объект под- подкачки, увеличить конечный адрес vmjnapjentry на запрошенный размер и уве- увеличить размер объекта подкачки на тот же размер. Если у объекта подкачки есть две или более ссылок (как было бы после разветвления процесса), должен быть создан новый vmjnapjentry с начальным адресом, следующим сразу после конца предыдущего элемента фиксированного размера. Вычисляется его конечный адрес, чтобы дать ему запрашиваемый размер. Он будет зарезервирован новым объектом подкачки. Пока этот процесс не разветвится снова, новый элемент и его объект подкачки смогут продолжать расти. Если изменение должно уменьшить размер сегмента данных, операция проста: любая память, выделенная страницам, которые больше не будут частью адресного пространства, освобождается. Конечный адрес vmjnap_entiy уменьшается на этот раз- размер. Если запрошенное уменьшение размера превышает диапазон, определенный vmjnapjentry, освобождается весь элемент, а оставшееся уменьшение применяется к vmjnapjentry, который предшествует ему. Этот алгоритм применяется до тех пор, пока не будет выполнено все уменьшение. Будущие ссылки на эти адреса вызовут ошибки защиты, поскольку доступ запрещен, когда диапазон адресов был освобожден. Отображение файлов Системный вызов ттар запрашивает отображение файла в адресное пространство. Системный вызов может запросить либо отображение по определенному адресу, либо позволить ядру выбрать неиспользуемую область. Если это запрос для определенного диапазона адресов, ядро сначала проверяет, не используется ли уже эта часть адресного пространства. Если она используется, ядро сначала вызывает типтар для существующего отображения, затем продолжает с новым отображением. Ядро реализует системный вызов ттар, проходя по списку структур vmjnapjentry для процесса. Различные условия перекрывания, которые нужно рас- рассмотреть, показаны на рис. 5.10. Пять случаев следующие. 1. Новое отображение точно перекрывает существующее. Старое отображение освобож- освобождается, как описано в разделе 5.9. На его месте создается новое отображение, как описано в абзаце, следующем после данного списка. 2. Новое отображение является подмножеством существующего. Существующее ото- отображение разделяется на три части (две части, если новое отображение начинается с начала или заканчивается на конце существующего отображения). Сущест- Существующая структура vmjnapjentry увеличивается одной или двумя дополнительны- дополнительными структурами vmjnapjentry. одной, отображающей оставшуюся часть сущест-
5.8. Манипулирование процесса своим адресным пространством 209 вующего отображения до нового отображения и другой, отображающей остав- оставшуюся часть существующего отображения, следующего за новым отображением. Его перекрывающийся участок замещается новым отображением, как описано в параграфе, следующем за данным списком. Случай Существующее Новое Становится 1 \ \ у УУУУУ У У/А УУУ у Рис. 5.10. Пять типов перекрывания, которые должно учесть ядро при добавлении нового отображения адресов 3. Новое отображение является надмножеством существующего. Старое отображе- отображение освобождается, как описано в разделе 5.9, и создается новое отображение, как описано в абзаце, следующем за данным списком. 4. Новое отображение частично захватывает и расширяет старое за его концом. Длина существующего отображения уменьшается на размер неотображенной области. Его перекрывающийся участок замещается новым отображением, как описано в абзаце, следующем за данным списком. 5. Новое отображение простирается до начала существующего и захватывает часть начала. Начальный адрес существующего отображения увеличивается, а его раз- размер уменьшается на размер покрытой области. Его перекрывающаяся часть замещается новым отображением, как описано в абзаце, следующем за данным списком. Кроме этих пяти основных типов перекрывания запрос нового отображения может охватывать несколько существующих отображений. В частности, новый запрос может быть составлен из нуля или одного типа 4, нуля или нескольких типа 3 и нуля или одного типа 5. Когда отображение сокращается, все теневые страницы, связанные с ним, освобождаются, поскольку они больше не нужны. После заполнения адресного пространства нулями ядро создает новый vm_map_entiy для описания нового диапазона адресов. Если отображаемый объект уже отображается другим процессом, новый элемент получает ссылку на существующий объ- объект. Эта ссылка получается тем же способом, как описано в разделе 5.6 при создании
210 Глава 5. Управление памятью нового процесса и необходимости отображения каждой из областей его родителя. Если этот запрос является отображением файла, ядро устанавливает новый vm_map_entry для ссылки на его объект. Если это отображение в анонимную область, тогда должен быть создан новый объект подкачки. Сначала выделяется новый объект с пейджером типа подкачки (пейджеры описаны в разделе 5.10). Затем устанавливается vm_map_entiy, ссылающийся на объект. Изменение защиты Процесс может изменить защиту, связанную с областью его виртуальной памяти, использовав системный вызов mprotect. Размер защищаемой области может быть всего в одну страницу. Поскольку ядро для осуществления защиты прав доступа зависит от аппаратного обеспечения, степень детализации защиты ограничена нижележащим оборудованием. Для области можно установить любую комбинацию прав доступа на чтение, запись и исполнение. Во многих архитектурах не различаются права доступа на чтение и исполнение; на таких архитектурах право на исполнение рассматривается как право на чтение. Ядро реализует системный вызов mprotect, находя существующую структуру vmjnap_entry или структуры, которые охватывают область, указанную при вызове. Если существующие права доступа те же самые, что и в запросе, дальнейшие действия не требуются. В противном случае новые права доступа сравниваются с максималь- максимальным значением защиты, связанным с vm_map_entry. Максимальное значение устанав- устанавливается во время вызова ттар и отражает максимальное значение, допускаемое ле- лежащим в основе файлом. Если новые права доступа действительны, для описания новых прав доступа должны быть подготовлены одна или более структур vm_map_entry. Набор перекрывающихся условий, которые должны быть учтены, сходен с тем, который описан в предыдущем подразделе. Вместо замещения объекта, лежащего в основе новых структур vjn_map_entry, эти структуры vm_map_entiy попрежнему ссылаются на тот же самый объект; различие в том, что они предостав- предоставляют ему другие права доступа. 5.9. Завершение процесса Завершающим изменением состояния процесса, которое имеет отношение к работе системы виртуальной памяти, является exit; этот системный вызов завершает процесс, как описано в главе 4. Та часть exit, которая здесь обсуждается, является освобождением ресурсов виртуальной памяти процесса. Есть два набора ресурсов виртуальной памяти, которые нужно освободить. 1. Пользовательская часть адресного пространства, как памяти, так и пространства подкачки.
5.9. Завершение процесса 211 2. Структура пользователя и стек ядра. Первый набор ресурсов освобождается в exit. Второй набор ресурсов освобожда- освобождается в wait. Освобождение второго набора ресурсов откладывается, поскольку стек ядра должен использоваться до тех пор, пока процесс не освободит процессор в послед- последний раз. Первый шаг - освобождение адресного пространства пользователя - идентичен шагу, который происходит во время exec для освобождения старого адресного про- пространства. Операция освобождения переходит от элемента к элементу через список структур vm_map_entry, связанных с адресным пространством. Первым шагом в осво- освобождении элемента является прохождение по списку его теневых элементов. Если эле- элемент является последней ссылкой на теневой объект, любая память или пространство подкачки, связанные с объектом, могут быть освобождены. Кроме того, вызываются машинно-зависимые процедуры для снятия отображения и освобождения любых таблиц или структур данных, которые связаны с объектом. Если на теневой объект попрежнему есть ссылки со стороны других структур vm_map_entry, его ресурсы не могут быть освобождены, но ядру все равно нужно вызывать машинно-зависимые про- процедуры для снятия отображения и освобождения ресурсов, связанных с отображением текущего процесса. Наконец, если нижележащий объект, на который ссылается vm_map_entry, теряет свою последнюю ссылку, тогда этот объект является кандидатом для удаления. Если это объект, у которого никогда не будет ни единого шанса на повторное использование в будущем (такой, как анонимный объект, связанный со сте- стеком, или область неинициализированных данных), тогда его ресурсы освобождаются, как если бы он был теневым объектом. Однако, если объект связан с vnode (например, он отображает файл, такой, как исполняемый), объект будет сохраняться до тех пор, пока vnode не будет использован повторно для другой цели. Пока vnode не использован повторно, объект и связанные с ним страницы будут доступны для повторного исполь- использования вновь выполняющимися процессами или процессами, отображающимися в файл. Когда все ресурсы освобождены, завершающийся процесс финиширует, отсоеди- отсоединяя себя от своей группы процессов и уведомляя своего родителя, что он закончил. Теперь процесс стал процессом зомби - процессом без ресурсов. Его родитель получа- получает статус завершения посредством вызова wait. Поскольку структура процесса, струк- структура пользователя и стек ядра выделяются с использованием зонального распределите- распределителя, они обычно будут сохранены для будущего использования другим процессом, а не разрушены с возвращением их страниц памяти. Таким образом, системе виртуальной памяти нечего делать, когда вызывается wait: все ресурсы виртуальной памяти процес- процесса удалены, когда завершен exit. В wait система просто возвращает вызывающему статус процесса, возвращает структуру процесса, структуру пользователя и стек ядра обратно в зональный распределитель и освобождает пространство, в котором храни- хранилась информация об использовании ресурсов.
212 Глава 5. Управление памятью 5.10. Интерфейс пейджера Интерфейс пейджера (pager mteiface) предоставляет механизм, посредством которого данные перемещаются между резервным хранилищем и физической памятью. Интерфейс пейджера FreeBSD является развитием интерфейса, имеющегося в Mach 2.0 и развившего- развившегося к 4.4BSD. Интерфейс основан на страницах, все запросы данных осуществляются в раз- размерах, кратных программным размерам страницы. В качестве дескрипторов передаются структуры vmjpage, предоставляя смещение резервного хранилища и адрес физической памяти нужных данных. Этот интерфейс не следует смешивать с внешним страничным ин- интерфейсом Mach 3.0 [Young, 1989], в котором пейджерами обычно являются пользователь- пользовательские приложения вне ядра, вызываемые посредством асинхронных вызовов удаленных процедур с использованием механизма межпроцессного взаимодействия Mach. Интерфейс FreeBSD является внутренним в том смысле, что пейджеры встроены в ядро и процедуры пейджера вызываются через простые вызовы функций. У каждого объекта виртуальной памяти есть тип пейджера, обработчик пейджера и индивидуальные данные пейджера, связанные с ним. Концептуально пейджер опи- описывает логически непрерывный участок резервного хранилища, такого, как порция пространства подкачки или дискового файла. Тип пейджера идентифицирует пейджер, ответственный за предоставление содержимого страниц в объекте. Каждый пейджер регистрирует набор функций, которые определяют его операции. Эти наборы функций хранятся в массиве, индексируемом по типу пейджера. Когда ядру нужно выполнить операцию пейджера, оно использует тип пейджера в качестве индекса массива функ- функций пейджера, а затем выбирает процедуру, которая ему нужна, такую, как получение или помещение страниц. Например, (*pagertab [object->type] ->pgo_jputpages) (object, vmpage, count, flags, rtvals); помещает count страниц, начиная со страницы vmpage, из object. Тип пейджера определяется при создании объекта для отображения в адресное пространство процесса файла, устройства или участка анонимной памяти. Пейджер управляет объектом в течение всего времени его жизни. Когда возникает отказ страни- страницы для виртуального адреса, отображающего определенный объект, код обработки отказа выделяет структуру vm_page и преобразует адрес, вызвавший отказ, в смещение внутри объекта. Это смещение записывается в структуру vm_page, и страница добавля- добавляется к списку страниц, копированных объектом. Затем кадр страницы и объект пере- передаются в нижележащую процедуру пейджера. Процедура пейджера отвечает за запол- заполнение структуры vmjpage соответствующим начальным значением для этого смеще- смещения объекта, который она представляет. Пейджер отвечает также за сохранение содержимого грязной страницы, если сис- система решает сбросить последнюю в резервное хранилище. Когда демон выгрузки решает, что определенная страница больше не нужна, он запрашивает у объекта, вла-
5.10. Интерфейс пейджера 213 деющего страницей, ее освобождение. Объект сначала передает страницу со связан- связанным логическим смещением нижележащему пейджеру, чтобы сохранить для будущего использования. Пейджер отвечает за нахождение подходящего места для сохранения страницы, осуществление необходимого для сохранения ввода/вывода, а затем уве- уведомления объекта о том, что страница была освобождена. Когда это сделано, пейджер помечает страницу как чистую таким образом, что демон выгрузки может переместить структуру vm_page в кеш или список свободных страниц для будущего использования. Табл. 5.1. Операции, определенные пейджером Операция Описание pgo_init() Инициализирует пейджер pgo_alloc() Выделяет пейджер pgo_dealloc() Удаляет пейджер pgo_getpages() Читает страницу(-ы) из резервного хранилища pgo_putpages() Записывает страницу(-ы) в резервное хранилище pgo_haspage() Проверяет наличие в резервном хранилище страницы pgo_pageunswapped() Удаляет страницу из резервного хранилища (топько пейджер подкачки (swap) Имеется семь процедур, связанных с каждым типом пейджера (см. табл. 5.1). Процедура pgo_init() вызывается во время загрузки для выполнения единовременной, зависящей от типа инициализации, такой, как выделение пула индивидуальных структур пейджера. Процедура pgo_alloc() связывает пейджер с объектом как часть создания объекта. Процедура pgo_dealloc() разъединяет пейджер от объекта как часть разрушения объекта. Объекты создаются либо системным вызовом ттар, либо внутренне, как часть создания процесса в ходе системных вызовов fork или exec. pgo_getpages() вызывается для возвращения от пейджера одной или более страниц данных. Эту функцию использует главным образом обработчик отказа страниц. pgojputpagesQ записывает обратно одну или более страниц данных. Эта процедура вызывается демоном выгрузки для асинхронного резервирования одной или более страниц и функцией msync для синхронного или асинхронного резервирования одной или более страниц. Как процедуры получения, так и записи вызываются с помощью массива структур vmjpage и счетчика, указывающего затронутые страницы. Процедура pgojiaspage() запрашивает пейджер, есть ли у него в резервном храни- хранилище данные по определенному смещению. Эта процедура используется в коде кла- кластеризации обработчика отказов страниц для определения того, можно ли прочесть любые страницы, смежные со страницей, вызвавшей отказ, в ходе одной операции ввода/вывода. Она используется также при сокращении объектов для определения того, полностью ли покрывают выделенные страницы теневого объекта выделенные страницы нижележащего объекта.
214 Глава 5. Управление памятью Четыре типа пейджеров, поддерживаемые системой, описываются в следующих четырех подразделах. Пейджер vnode Пейджер vnode управляет объектами, которые отображают файлы в файловой системе. Каждый раз, когда файл открывается либо явно посредством open, либо неявно посред- посредством exec, система должна найти существующий vnode, который его представляет, или, если для файла не существует vnode, выделить для него новый vnode. Частью выде- выделения нового vnode является выделение объекта для хранения страниц файла и для свя- связывания с объектом пейджера vnode. Описатель объекта устанавливается для указания на vnode, а индивидуальные данные хранят размер файла. Каждый раз при изменении размера vnode объект информируется через вызов vnodejpager_setsize(). Когда процедурой пейджера vnode pgo_getpages() получен запрос загрузки страни- страницы, ему передается массив физических страниц, размер массива и индекс в массиве страницы, которая нужна. Должна быть прочитана только нужная страница, но под- поддерживается предоставление процедурой pgo_getpages() и других страниц, которые можно легко прочесть в то же самое время. Например, если запрошенная страница в середине блока файла, файловая система обычно читает весь блок файла, поскольку блок файла можно прочесть в ходе одной операции ввода/вывода. Большее чтение за- заполнит запрошенную страницу вместе с окружающими ее страницами. Ввод/вывод осуществляется с использованием буфера физического ввода/вывода, отображающего страницы, которые нужно прочесть, в адресное пространство ядра достаточно долго, чтобы пейджер вызвал процедуру стратегии драйвера устройства для загрузки страниц с содержимым файла. Когда страницы заполнены, отображение ядра может быть уда- удалено, буфер физического ввода/вывода может быть освобожден, а страницы могут быть возвращены. Когда у пейджера vnode запрашивают сохранение освобождаемых страниц, он просто организует запись страницы обратно в ту часть файла, из которой она поступила. Запрос осуществляется процедурой pgo_getpages(), которой передается массив физиче- физических страниц, размер массива и индекс в массиве страницы, которая должна быть запи- записана. Должна быть записана лишь нужная страница, но поддерживается запись проце- процедурой pgojputpagesQ и других страниц, которые можно легко обработать в то же самое время. Файловая система запишет все страницы, которые находятся в одном блоке фай- файловой системы вместе с нужной страницей. Как в случае с процедурой pgo_getpages(), страницы отображаются в ядро лишь на время операции записи. Если файл отображается индивидуально, модифицированные страницы не могут быть записаны обратно в файловую систему. Такое индивидуальное отображение должно использовать для всех модифицированных страниц теневой объект с пейджером подкачки. Таким образом, индивидуально отображенный объект никогда не будет запрошен для сохранения каких-либо грязных страниц в нижележащем файле.
5.10. Интерфейс пейджера 215 Исторически ядро BSD имело отдельные кеши для файловых систем и виртуаль- виртуальной памяти. FreeBSD устранило буферный кеш файловой системы, заменив его кешем виртуальной памяти. У каждого vnode есть связанный с ним объект, а блоки файла хра- хранятся в страницах, связанных с объектом. Доступ к данным файла осуществляется с использованием тех же самых страниц независимо от того, отображены ли они в адресное пространство, или доступ к ним осуществляется посредством чтения или записи. Дополнительной выгодой такого дизайна является то, что кеш файловой систе- системы больше не ограничен адресным пространством в ядре, которое может быть ему выделено. При отсутствии других требований на системную память она вся может быть выделена для кеширования данных файловой системы. Пейджер устройств Пейджер устройств управляет объектами, представляющими отображенные на память аппаратные устройства. Отображенные на память устройства предоставляют интерфейс, который выглядит как участок памяти. Примером отображенного на память устройства является буфер кадра, который представляет диапазон адресов памяти с одним словом на пиксел на экране. Ядро предоставляет доступ к отображенным на память устройствам, отображая память устройства в адресное пространство процесса. После этого процесс может получить доступ к этой памяти без дополнительного вмешательства операционной системы. Запись слова в буфер кадра памяти вызывает соответствующие изменения цвета и яркости пиксела. Пейджер устройств фундаментально отличается от других трех типов пейджеров в том, что он не заполняет предоставленные страницы физической памяти данными. Вместо этого он создает и управляет своими собственными структурами vm_page, каждая из которых описывает страницу пространства устройства. Заголовок списка этих страниц хранится в области индивидуальных данных объекта. Такой подход заставляет память устройства выглядеть подобно переданной (wired) физической памяти. Таким образом, для управления памятью устройства в оставшейся части системы виртуальной памяти не требуется никакого специального кода. Когда устройство отображается впервые, процедура выделения пейджера устройств проверит нужный диапазон, вызвав процедуру устройства djnmapQ. Если устройство разрешает запрошенный доступ для всех страниц в этом диапазоне, в области индивиду- индивидуальных данных объекта, который управляет отображением устройства, создается пустой список страниц. Процедура выделения пейджера устройства не создает структуры vm_page сразу же - они создаются по отдельности процедурой pgo_ getpagesQ по мере ссылок на них. Причиной для такого позднего выделения является то, что некоторые устройства экспортируют большие диапазоны памяти, в которых либо не все страницы действительны, либо не может быть осуществлен доступ к страницам для обычных операций. Полное выделение структур vm_page для этих устройств с редким доступом было бы расточительным.
216 Глава 5. Управление памятью Первый доступ к странице устройства вызовет отказ страницы и вызов процедуры пейджера устройства pgo_getpages(). Пейджер устройства создает структуру vmjjage, инициализирует ее соответствующим смещением объекта и физическим адресом, воз- возвращенным процедурой устройства djnmapQ, и помечает страницу как фиктивную. Эта структура vmjpage добавляется в список всех таких выделенных для объекта стра- страниц. Поскольку у кода отказа страницы нет особого знания о пейджере устройств, он заранее выделил страницу физической памяти для заполнения и связал эту структуру vm_page с объектом. Процедура пейджера устройств удаляет эту структуру vm_page из объекта, возвращает структуру в список свободных и вводит на то же самое место свою собственную структуру vmjpage. Процедура пейджера устройств pgo _putpages() ожидает, что она никогда не будет вызвана, и, если это случится, войдет в состояние паники. Такое поведение основыва- основывается на предположении, что страницы пейджера устройств никогда не вводятся ни в какую из очередей страничной подкачки и поэтому никогда не будут видны демону выгрузки. Однако можно вызвать msync для диапазона памяти устройства. Эта опера- операция является исключением в общем незнании о памяти устройства вышележащей сис- системой виртуальной памяти: процедура очистки страниц объекта пропустит страницы, которые отмечены как фиктивные. В конечном счете, когда отображение устройства снято, вызывается процедура уда- удаления пейджера устройств. Эта процедура удаляет все структуры vm_page, которые были выделены. Пейджер физической памяти Пейджер физической памяти управляет объектами, которые содержат невыгружаемую память. Они используются лишь в интерфейсе разделяемой памяти System V, который можно сконфигурировать для использования невыгружаемой памяти вместо исполь- использующейся по умолчанию выгружаемой памяти. Первый доступ к странице пейджера физической памяти вызовет отказ страни- страницы и вызов процедуры pgo_getpages(). Подобно пейджеру подкачки, пейджер физической памяти заполняет страницы нулями, когда отказ вызван впервые. В отличие от пейджера подкачки страница помечается как неуправляемая таким образом, что она не будет приниматься во внимание для замещения демоном выгрузки. Пометка своих страниц как неуправляемых заставляет память для пейджера физической памяти выглядеть подобно переданной физической памяти. Поэтому не требуется особого кода в оставшейся части системы виртуальной памяти для управления памятью пейджера физической памяти. Процедура pgojputpagesQ пейджера физической памяти не ожидает вызова, и если это произойдет, она вызовет панику. Такое поведение основывается на предположе- предположении, что страницы пейджера физической памяти никогда не вводятся ни в одну из очередей подкачки и поэтому никогда не будут видны демону выгрузки. Однако для
5.10. Интерфейс пейджера 217 диапазона памяти устройства можно вызвать msync. Эта операция является исключением в общем незнании о памяти пейджера физической памяти со стороны вышележащей системы виртуальной памяти: процедура очистки страниц объекта будет пропускать страницы, помеченные как неуправляемые. Наконец, когда объект, использующий пейджер физической памяти, освобождается, у всех его страниц сбрасывается флаг неуправляемости, и они возвращаются обратно в список свободных страниц. Пейджер подкачки Термин пейджер подкачки (swap pager) обозначает два функционально различных пейджера. Чаще всего пейджер подкачки ссылается на пейджер, который используется объектами, отображающим анонимную память. Этот пейджер иногда называют пей- пейджером по умолчанию, поскольку это пейджер, который используется, если не был за- запрошен другой пейджер. Он предоставляет то, что обычно известно как пространство подкачки: нерезидентное резервное хранилище, которое заполняется при первом использовании нулями. Когда анонимный объект создается впервые, ему назначается пейджер по умолчанию. Пейджер по умолчанию не выделяет ресурсов и не предостав- предоставляет поддержки хранения. Пейджер по умолчанию обрабатывает отказы страниц (pgo_getpage()) путем заполнения нулями, а на запросы о страницах (pgo_haspageQ) отвечает отказом. Ожидается, что свободной памяти будет достаточно много, чтобы не было потребности в выгрузке каких-либо страниц. Объект будет просто создавать в течение времени жизни процесса заполненные нулями страницы, которые можно возвратить в список свободных после завершения процесса. Когда объект освобожда- освобождается пейджером по умолчанию, не требуется никакой очистки пейджером, поскольку не было выделено никаких ресурсов пейджера. Однако при первом запросе демона выгрузки страниц по удалению активной стра- страницы из анонимного объекта пейджер по умолчанию замещает себя пейджером под- подкачки. Ролью пейджера подкачки является управление пространством подкачки: выяс- выяснение того, где хранить грязные страницы и как их находить, когда они снова понадо- понадобятся. Теневым объектам нужно, чтобы эти операции были эффективными. Типичный теневой объект заполнен негусто: он может охватывать большой диапазон страниц, но лишь те страницы, которые были изменены, будут находиться в резервном хранилище теневого объекта. Кроме того, длинные цепи теневых объектов могут потребовать для нахождения нужной копии страницы объекта для удовлетворения отказа страницы многочисленных запросов пейджера. Поэтому определение того, содержит ли пейджер определенную страницу, должно быть быстрым, предпочтительно без использования операций ввода/вывода. Завершающим требованием к пейджеру подкачки является то, чтобы он мог делать асинхронные обратные записи грязных страниц. Это требуется демону выгрузки, который является однопоточным процессом. Если демон выгрузки блокируется в ожидании завершения операции очистки страницы перед началом еле-
218 Глава 5. Управление памятью дующей операции, маловероятно, что он сможет сохранить достаточно памяти свобод- свободной в моменты высокой необходимости в ней. Теоретически любой пейджер, удовлетворяющий этим критериям, может исполь- использоваться в качестве пейджера подкачки. В Mach 2.0 в качестве пейджера подкачки использовался пейджер vnode. В любой файловой системе могли быть созданы и заре- зарегистрированы ядром особые файлы подкачки. Затем пейджер подкачки выделял отдельные участки этих файлов для резервирования определенных анонимных объектов. Одним очевидным преимуществом использования пейджера vnode является то, что пространство подкачки может быть расширено путем динамического добавления дополнительных файлов подкачки или расширением существующих (т.е. без переза- перезагрузки или переконфигурирования ядра). Основным недостатком является то, что фай- файловая система не предоставляет такой пропускной способности, как непосредственный доступ к диску. Желание предоставить как можно большую пропускную способность диска привело к созданию для использования в качестве пейджера подкачки FreeBSD специального пейджера непосредственного раздела (г aw-partition pager). Другие версии BSD также использовали выделенные дисковые разделы, общеизвестные как разделы подкачки, поэтому этот пейджер раздела стал пейджером подкачки. Оставшаяся часть данного раздела описывает, как реализован пейджер подкачки и как он предоставляет необхо- необходимые возможности для резервирования анонимных объектов. В 4.4BSD пейджер подкачки для описания объекта заранее выделял структуру фиксированного размера. Для большого объекта структура была бы большой, даже если в резервное хранилище помещалось всего несколько страниц объекта. Хуже того, размер объекта был заморожен во время выделения. Поэтому, если анонимная область продолжала расти (как в случае стека или кучи процесса), приходилось создавать новый объект для описания расширившейся области. На системе с недостатком памяти результатом было то, что большой процесс мог получить множество анонимных объ- объектов. Изменение пейджера подкачки для управления растущими объектами разитель- разительно снизило это разрастание объектов. Другой проблемой с пейджером подкачки 4.4BSD было то, что его упрощенное управление пространством подкачки вело к фраг- фрагментации, медленному выделению в загруженном состоянии и тупикам, к которым приводила необходимость выделять память ядра в периоды нехватки. По всем этим причинам в FreeBSD 4.0 пейджер подкачки был полностью переписан. Пространство подкачки имеет тенденцию выделяться нечасто. В среднем процесс в течение времени своей жизни получает доступ лишь примерно к половине своего выделенного адресного пространства. Таким образом, лишь около половины страниц в объекте когда-либо вступают в действие. Если только машина не находится под боль- большим давлением нехватки памяти и процесс не является долгоживущим, большая часть страниц в объекте, которые появляются, никогда не будут помещены в резервное хра- хранилище. Поэтому новый пейджер подкачки заместил старое отображение блоков фик- фиксированного размера для каждого объекта методом, который выделяет структуру для
5.10. Интерфейс пейджера 219 каждого набора блоков подкачки, которые выделяются. Каждая структура может отслеживать набор до 32 смежных блоков подкачки. Большой объект с двумя выгру- выгруженными страницами использует самое большее две из этих структур, и лишь одну, если эти две выгруженные страницы расположены рядом друг с другом (как это часто бывает). Объем памяти, который требуется для отслеживания пространства подкачки для объекта, пропорционален числу страниц, которые были выгружены, а не размеру объекта. Размер объекта больше не замораживается при выгрузке его первой страницы, поскольку могут предоставляться любые страницы, являющиеся частью его большого размера. Структуры, которые отслеживают использование пространства подкачки, хранятся в глобальной хеш-таблице, управляемой пейджером подкачки. Хотя могло бы пока- показаться логичным хранить структуры отдельно в списках, связанных с объектом, частью которого они являются, у одной глобальной хеш-таблицы есть два важных преимуще- преимущества. 1. Она гарантирует короткое время определения того, была ли выгружена страница объекта. Если бы структуры были связаны в список, начинающийся с объекта, тогда объекты с множеством выгруженных страниц потребовали бы прохождения по длин- длинному списку. Длинный список можно было бы сократить, создав для каждого объекта хеш-таблицу, но это потребовало бы значительно больше памяти, чем простое выде- выделение одной большой хеш-таблицы, которую могли бы использовать все объекты. 2. Она допускает операции, которым нужно просматривать все выделенные блоки подкачки, чтобы иметь централизованное место для их поиска, вместо того чтобы просматривать все анонимные объекты в системе. Примером является системный вызов swapoff, который прекращает использование раздела подкачки. Ему нужно загрузить все блоки из устройства, работа которого завершается. Свободное пространство в области подкачки управляется битовой картой с одним битом для каждого блока пространства подкачки с размером страницы. Битовая карта для всей области подкачки выделяется, когда пространство подкачки впервые добавля- добавляется в систему. Это начальное выделение позволяет избежать необходимости выделять память ядра во время критических операций подкачки при недостатке памяти. Хотя при первом выделении битовой карте требуется больше места, чем при первом выде- выделении списка блоков, его размер при использовании не изменяется, тогда как список блоков растет до размера, превышающего битовую карту, по мере фрагментации облас- области подкачки. Система имеет тенденцию использовать подкачку, когда испытывает недос- недостаток памяти. Чтобы избежать потенциальных тупиков, в это время память ядра не должна выделяться. Осуществление линейного сканирования битовых карт блоков подкачки для поиска свободного пространства было бы недопустимо медленным. Поэтому битовая карта организована в структуру базисного дерева (radix tree) с подсказками о свободном
220 Глава 5. Управление памятью пространстве в структурах базисных узлов. Использование структур базисных деревьев делает выделение и освобождение пространства подкачки операцией с постоянным временем. Для уменьшения фрагментации базисное дерево может вьщелять сразу большие смежные порции, пропуская меньшие фрагментированные куски. Дальнейшим усовершенствованием было бы отслеживание свободных областей с различными размерами, поскольку распределение подкачки осуществляется сход- сходным образом с тем, как файловые системы отслеживают различные размеры свободно- свободного пространства. Эти сведения о свободном пространстве повысили бы вероятность непрерывных выделений и улучшили бы локальность ссылки. Блоки подкачки выделяются в то время, когда осуществляется выгрузка. Они осво- освобождаются, когда страница загружается обратно и становится грязной или когда объект освобождается. Пейджер подкачки отвечает за управление вводом/выводом, связанным с запросом pgo_putpages(). Когда он идентифицирует набор страниц в запросе pgo_putpages(), который он сможет записать, он должен выделить буфер и отобразить в него эти стра- страницы. Поскольку пейджер подкачки не ожидает синхронно, пока будет выполнен ввод/ вывод, он не восстанавливает контроль после завершения операции ввода/вывода. Поэтому он помечает буфер флагом обратного вызова и устанавливает в качестве соот- соответствующей процедуры обратного вызова swp_pager_async_iodone(). Когда выгрузка завершается, вызывается swp_pager_async_iodone(). Каждая запи- записанная страница помечается как чистая, ее бит занятости сбрасывается, и вызывается процедура vm_page_iojinish() для уведомления демона выгрузки о том, что запись завершена и нужно пробудить ожидающие этого процессы. Затем пейджер подкачки снимает отображение страниц из буфера и освобождает его. Число выполняющихся операций выгрузки сохраняется для пейджера, связанного с каждым объектом; это число уменьшается, когда выгрузка страницы завершается, и, если число достигает нуля, вы- вызывается wakeupQ. Эта операция осуществляется таким образом, что объект, который удаляет пейджер подкачки, может ожидать завершения всех операций выгрузки стра- страниц до освобождения ссылок пейджера на связанное с ним пространство подкачки. Поскольку число буферов подкачки постоянно, пейджер подкачки должен предпри- предпринять меры, чтобы убедиться, что он не использует большую долю, чем ему положено. По достижении этого лимита операции pgojputpagesQ блокируются до тех пор, пока не завершится одна из незаконченных записей пейджера подкачки. Это неожиданное блокирование демона выгрузки страниц является неудачным побочным эффектом передачи управления буфером пейджерам. Любой отдельный пейджер, достигший своего предела буфера, останавливает демон выгрузки. Хотя демон выгрузки мог бы захотеть выполнить дополнительные операции по вводу/выводу, используя другие ресурсы ввода/вывода, такие, как сеть, ему не дают это сделать. Хуже того, отказ любого отдельного пейджера может привести систему в тупик, предотвратив запуск демона выгрузки.
5.11. Страничная подкачка 221 5.11. Страничная подкачка Когда аппаратура управления памятью обнаруживает недействительный виртуальный адрес, она генерирует для системы исключение. Это исключение отказа страницы может возникать по нескольким причинам. Большинство программ BSD создано в формате, который допускает страничную подкачку в оперативную память непосред- непосредственно из файловой системы. Когда программа в формате подкачки по требованию запускается впервые, ядро помечает в качестве недействительных страницы для кода и области инициализированных данных выполняющегося процесса. Области кода и инициализированных данных разделяют объект, который предоставляет заполнение по требованию из файловой системы. Как часть процесса отображения в объект, ядро проходит список страниц, связанных с объектом, и помечает их как резидентные и для копирования при записи во вновь созданном процессе. Для интенсивно использующе- использующегося исполняемого файла, у которого большинство его страниц уже резидентные, эта опережающая подкачка уменьшает большое число начальных отказов страниц. Когда впервые происходят ссылки на отсутствующие страницы областей кода или инициали- инициализированных данных или осуществляется попытка записи на страницы в области ини- инициализированных данных, возникает отказ страницы. Отказы страниц могут также возникнуть, когда процесс впервые ссылается на страницу в области неинициализированных данных программы. В этом случае ано- анонимный объект, управляющий областью, автоматически выделяет процессу память и инициализирует нулями вновь назначенную страницу. Процесс idle тратит часть своего времени, обнуляя страницы из списка свободных таким образом, чтобы системе не приходилось выполнять обнуление для анонимного объекта при обслуживании первой ссылки на страницу. Другие виды отказов страниц происходят, когда прежде резидентные страницы были возвращены системой в ответ на нехватку памяти. Обработка отказов страниц осуществляется процедурой vm_Jault()\ эта процедура обслуживает все отказы страниц. Каждый раз при вызове vm_fault() ей предоставляется виртуальный адрес, вызвавший отказ. Первым действием vm_Jault() является прохожде- прохождение списка vm_map_entry процесса, вызвавшего отказ, чтобы найти элемент, связан- связанный с отказом. Затем процедура вычисляет логическую страницу внутри нижележаще- нижележащего объекта и проходит по списку объектов, чтобы найти или создать нужную страницу. Найдя страницу, vm_Jault() должна вызвать машинно-зависимый уровень для проверки страницы, вызвавшей отказ, и возвращения для повторного запуска процесса. Детали вычисления адреса внутри объекта описаны в разделе 5.4. Вычислив сме- смещение внутри объекта и определив права доступа к объекту и список объектов из vm_map_entiy9 ядро готово найти или создать связанную страницу. Алгоритм обработки отказов страниц показан на рис. 5.11. В следующем обзоре обозначенные буквами пункты являются ссылками на отметки в нижней левой части кода.
222 Глава 5. Управление памятью /* * Обработка отказа страницы, возникшей по данному адресу, запрос * данных прав доступа в указанном отображении. В случае успеха * вставить страницу в соответствующее физическое отображение. */ int vm_fault( vm_map_t map, vm_offset_t addr, vm_prot_t type) RetryFault: lookup address in map returning object/offset/prot; first_object = object; [A] for (;;) { page = lookup page at object/offset; [B] if (page found) { if (page busy) block and goto RetryFault; remove from paging queues; mark page as busy; break; [C] if (object has nondefault pager or object == first_object) { page = allocate a page for object/offset; if (no pages available) block and goto RetryFault; D] if (object has nondefault pager) { scan for pages to cluster; call pager to fill page(s); if A0 error) return an error; if (pager has page) break; if (object != first_object) free page; /* пейджера нет, или у него нет страницы */ [E] if (object == first_object) first_page = page; next_object = next object; [F] if (no next object) { if (object != first_object) { object = first_object; page = first_page; first_page = NULL; zero fill page; break; object = next_object; } РИС. 5.11. Обработка отказа страницы
5.11. Страничная подкачка 223 [[G] /* нужная страница найдена или выделена */ orig_page = page; [Н] if (object != first_object) { if (fault type == WRITE) { copy page to first_page; deactivate page; page = first_page; object = first_object; } else { prot &= ~WRITE; mark page copy-on-write; } } [I] if (prot & WRITE) mark page not copy-on-write; enter mapping for page; enter read-only mapping for clustered pages; [J] activate and unbusy page; if (first_page != NULL) unbusy and free first_page; } РИС. 5.11. Обработка отказа страницы (Окончание) A. Цикл обходит список теневых, анонимных и файловых объектов до тех пор, пока либо не найдет объект, хранящий пользующуюся спросом страницу, либо не достигнет последнего объекта в списке. Если страница не найдена, за ее соз- создание будет отвечать последний объект. B. Объект с нужной страницей найден. Если страница занята, другой процесс может быть в середине процесса обработки отказа этой страницы, поэтому этот процесс блокируется до тех пор, пока страница не освободится. Поскольку с затронутым объектом может случаться множество вещей, пока процесс заблокирован, он должен повторно запустить весь алгоритм обработки отказа. Если страница не была занята, алгоритм выходит из цикла со страницей. C. Анонимные объекты (такие, как использующиеся для представления теневых объектов) не заменяют пейджер по умолчанию на пейджер подкачки до тех пор, пока им в первый раз не потребуется выгрузить страницу в резервную память. Таким образом, если у объекта пейджер не по умолчанию, есть шанс, что страница ранее существовала, но была выгружена. Если у объекта пейджер не по умолчанию, тогда ядру нужно выделить страницу, чтобы передать для запол- заполнения пейджеру (см. D). Особый случай для объекта, являющегося первым, - это избежание условия гонки с двумя процессами, пытающимися получить одну и ту же страницу. Первый процесс беспрепятственно создаст в первом объекте запрашиваемую страницу, но пометит ее как занятую. Когда второй процесс попытается обработать отказ той же самой страницы, он обнаружит страницу, созданную первым процессом, и заблокируется на ней (см. В). Когда первый процесс завершит обработку загрузки страницы, он разблокирует первую страницу, заставив второй процесс пробудиться, повторно обработать отказ страницы и найти страницу, созданную первым процессом.
224 Глава 5. Управление памятью D. До вызова пейджера проверить, можно ли в это же время загрузить любые из восьми страниц с любой стороны страницы, вызвавшей отказ. Чтобы страницу можно было загрузить, она должна быть частью объекта и не быть уже в памяти, а также не участвовать в другой операции ввода/вывода. Пейджеру дается диапазон доступных страниц и сообщается, какая из них является нужной страницей. Он дол- должен вернуть нужную страницу, если у него есть ее копия. Другие страницы создаются лишь в случае, если они хранятся в объекте и могут быть легко считаны вто же самое время. Если нужная страница присутствует в файле или области подкачки, пейджер загрузит ее обратно во вновь выделенную страницу. Если загрузка будет успешной, запрошенная страница найдена. Если страница никогда не существовала, тогда запрос загрузки завершится неудачей. Если только этот объект не является первым, страница освобождается, и поиск продолжается. Если этот объ- объект является первым, страница не освобождается, поэтому она будет действовать в качестве блока для дальнейшего поиска другими процессами (как описано в С). E. Если ядро создало страницу в первом объекте, но не использовало эту страницу, ему придется запомнить эту страницу таким образом, чтобы иметь возможность освободить ее, когда будет завершена загрузка (см. J). F. Если поиск достиг конца списка объектов, а страница не была найдена, тогда отказ находится в цепи анонимных объектов и первый объект в списке обработает отказ страницы, используя выделенную в С страницу. Элемент first_page устанавлива- устанавливается в NULL, чтобы показать, что его не нужно освобождать, если процесс idle еще не сделал так, что страница заполнена нулями, и цикл прерывается. G. Поиск прерывает цикл с раде в качестве страницы, которая была найдена или выделена и инициализирована, и object в качестве владельца этой страницы. На данный момент страница была заполнена правильными данными. Н. Если объект, предоставляющий страницу, не является первым, тогда это отображе- отображение должно быть индивидуальным, причем первый объект является теневым объ- объектом объекта, предоставившего страницу. Если подкачка (pagein) обрабатывает отказ записи, тогда содержимое найденной страницы должно быть скопировано в страницу, которая была выделена для первого объекта. Сделав копию, можно освободить объект и страницу, которая копировалась, поскольку для завершения обслуживания отказа страницы будут использоваться первый объект и первая страница. Если подкачка обрабатывает отказ чтения, она может использовать най- найденную страницу, но должна установить для нее атрибут копирования при записи, чтобы избежать модификации страницы в будущем. I. Если подкачка (pagein) обрабатывает отказ записи, тогда она сделала все копии, которые были необходимы, так что она может безопасно сделать страницу запи- записываемой. Поскольку все страницы вокруг нужной, которые были занесены в память как часть кластеризации, не были скопированы, они отображаются с доступом только для чтения таким образом, что, если в одну из них будет произведена запись, будет выполнен полный анализ отказа страницы и при необходимости сделана в это время копия.
5.11. Страничная подкачка 225 J. Поскольку страница и, возможно, первая страница освобождены, все процессы, ожидающие этой страницы объекта, получат шанс запуска для получения своих собственных ссылок. Обратите внимание, что блокировка страниц и объектов на рис. 5.11 была опущена, чтобы упростить объяснение. Дизайн аппаратного кеша Поскольку скорость процессоров увеличилась значительно быстрее, чем скорость оперативной памяти, большинству машин сегодня требуется использовать кеш памяти, чтобы дать процессору возможность работать близко к своим полным воз- возможностям. struct cache { vm_offset_t key; /* адрес данных */ char data[LINESIZE]; /* данные кеша */ } cache[CACHELINES][SETSIZE]; /* * Получить данные из кеша для addr, если они есть. В противном * случае выбрать данные из оперативной памяти, поместить их в кеш и * вернуть. */ hardware_cache_fetch(vm_offset_t addr) vm_offset_t key, line; line = (addr / LINESIZE) % CACHELINES; for (key = 0; key < SETSIZE; key++) if (cache[line][key].key == addr) break; if (key < SETSIZE) return (cache[line][key].data); key = select_replacement_key(line); cache[line][key].key = addr; return (cache[line][key].data = fetch_from_RAM(addr)); Рис. 5.12. Алгоритм аппаратного кеша. Обозначения: LINESIZE - число байтов в каждой строке кеша, обычно 64 или 128 байт; CACHELINES - число строк в кеше, обычный размер 8192; SETSIZE - 1 для кеша прямого отображения, 2 для 2-ка- нального наборно-ассоциативного или 4 для 4-канального наборно-ассоциатив- ного кеша Код, описывающий работу аппаратного кеша, приведен на рис. 5.12. Реальный кеш полностью реализован аппаратно, поэтому цикл, показанный на рис. 5.12, на самом деле осуществлялся бы параллельными сравнениями, а не итеративно. Изначально
226 Глава 5. Управление памятью у большинства машин был кеш прямого отображения (direct-mapped cache). В случае кеша прямого отображения доступ к байту N, за которым следовал доступ к байту N + (СТРОКИКЭША х РАЗМЕРСТРОКИ), вызвал бы потерю копированных данных для байта N. Более новые кеши являются либо 2-канальными наборно-ассоциативными, либо 4-канальными наборно-ассоциативными BD)-way set associative). 4-канальный наборно-ассоциативный кеш обеспечивает доступ к четырем различным областям памяти, которые перекрывают ту же самую память кеша без разрушения предварительно кешированных данных. Но при пятом доступе по этому смещению кешированное ранее значение теряется. Есть несколько вариантов дизайна кеша, которые требуют взаимодействия с систе- системой виртуальной памяти. Выбором дизайна с наибольшим значением является то, ис- использует ли кеш виртуальную или физическую адресацию. Физически адресуемый кеш берет адрес из процессора, пропускает его через блок управления памятью (MMU) для получения адреса физической страницы, затем использует этот физический адрес для выяснения того, доступна ли в кеше запрошенная ячейка памяти. Хотя буфер пре- преобразования адреса (описанный в разделе 5.13) значительно уменьшает среднюю задержку преобразования, в прохождении MMU по-прежнему остается задержка. Кеш с виртуальной адресацией для выяснения того, доступна ли в кеше запрошенная ячейка памяти, использует виртуальный адрес в том виде, как он поступает от процес- процессора. Кеш с виртуальной адресацией быстрее, чем кеш с физической адресацией, поскольку ему не требуется время на прохождение адреса через MMU. Однако после каждого переключения контекста содержимое кеша с виртуальной адресацией должно быть полностью сброшено, поскольку виртуальные адреса одного процесса неотличи- неотличимы от виртуальных адресов другого процесса. В отличие от этого, кешу с физической адресацией нужно сбрасывать лишь отдельные элементы при переназначении связан- связанной с ним физической страницы. В системе с множеством кратковременно дейст- действующих процессов кеш с виртуальной адресацией сбрасывается так часто, что он редко оказывается полезным. Дальнейшим усовершенствованием кеша с виртуальной адресацией является добавление к ключевому полю каждой строки кеша тега процесса. При каждом пере- переключении контекста ядро загружает аппаратный регистр контекста тегом, назначен- назначенным для процесса. Каждый раз при записи в кеш элемента в ключевое поле строки кеша записываются как виртуальный адрес, так и тег процесса, вызвавшего его отказ. Кеш ищет виртуальный адрес, как прежде, но когда он находит элемент, он сравнивает тег, связанный с этим элементом, с аппаратным регистром контекста. Если они совпа- совпадают, возвращается кешированное значение. Если они не совпадают, правильное значение и тег текущего процесса замещают старое кешированное значение. При использовании этой методики кеш не нужно сбрасывать полностью при каждом пере- переключении контекста, поскольку в кеше могут быть элементы нескольких процессов. Недостатком является то, что ядро должно управлять тегами процессов. Обычно тегов меньше (от 8 до 16), чем процессов. Ядро должно назначать теги активному набору
5.11. Страничная подкачка 227 процессов. Когда старый процесс выпадает из активного набора, чтобы дать возмож- возможность войти туда новому процессу, ядро должно сбросить элементы кеша, связанные с тегом, который собираются использовать повторно. В заключение следует сравнить кеши со сквозной записью (write-through) и с обратной записью (write-back). Кеш со сквозной записью записывает данные обрат- обратно в оперативную память в то же самое время, когда осуществляется запись в кеш, заставляя процессор ждать завершения доступа к памяти. Кеш с обратной записью записывает данные лишь в кеш, откладывая запись в память до тех пор, пока не посту- поступит явный запрос или пока элемент кеша не будет использован повторно. Кеш с обрат- обратной записью дает процессору возможность быстрее продолжить выполнение и объеди- объединить несколько записей в один и тот же блок кеша при одной записи в память. Однако при этом необходимо форсировать записи каждый раз, когда данные должны быть видны другим процессорам в многопроцессорной системе. Раскраска страниц (page coloring) Раскраска страниц является оптимизацией производительности, предназначенной для обеспечения того, что доступ к смежным страницам в виртуальной памяти наилучшим образом использует кеш с физической адресацией. В архитектуре Intel используется кеш с физической адресацией, который обозначают кешем L1. Производительность системы тесно связана с процентным соотношением ссылок памяти, которые могут быть обслужены кешем L1, поскольку кеш L1 работает на частоте процессора. Промах в кеше L1 требует нескольких циклов задержки процессора в ожидании данных из кеша L2 или десятков циклов ожидания динамической RAM, в которой хранятся данные основной памяти. Процесс в FreeBSD работает с пространством виртуальной памяти, не с про- пространством физической памяти. Физические страницы, лежащие в основе виртуаль- виртуального адресного пространства, редко бывают смежными. В худшем случае две после- последовательные виртуальные страницы могли бы быть сохранены в двух физических страницах, относящихся к одной и той же ячейке в кеше L1. Рассмотрим процесс, рабо- работающий на процессоре с 1 Мбайт физического кеша. Если процесс отобразил свою первую виртуальную страницу на физическую страницу 1 Мбайт, а свою вторую виртуальную страницу отобразил на физическую страницу 2 Мбайт, несмотря на сохранение локальности ссылки между двумя его смежными виртуальными страни- страницами, процесс ненамеренно вызывает промахи в кеше L1. Эффект несколько смягча- смягчается многоканальными наборно-ассоциативными кешами, но проблема по-прежнему сохраняется. Роль алгоритма раскраски страниц в том, чтобы гарантировать, что смежные стра- страницы в виртуальной памяти будут смежными с точки зрения кеша. Вместо назначения виртуальным адресам случайных физических страниц, раскраска страниц назначает виртуальным адресам смежные в кеше физические страницы. Две физические страницы
228 Глава 5. Управление памятью являются смежными в кеше, если они попадают в последовательные страницы в кеше. Эти страницы в физической памяти могут быть далеко друг от друга. На рис. 5.13 показан образец назначения страниц процессу. На рис. 5.13 кеш содержит 4 страницы, и страница 10 физической памяти назначена странице 0 виртуальной памяти процесса. Страница физической памяти 10 имеет цвет 2 кеша (остаток деления 10 на 4 равен 2). Код раскраски страниц назначил страницу 15 физической страницы странице 1 вирту- виртуальной памяти процесса, поскольку он имеет цвет 3. Код раскраски страницы пытается избежать назначения страниц 6 или 14, поскольку они отображаются на ту же самую область памяти кеша, что и страница 10, и привели бы к неоптимальному кешированию, тогда как любая из страниц 7, 11 или 15 является оптимальной, поскольку каждая из нихследуют за памятью кеша, использованной для страницы 10. Виртуальный Физический адрес адрес Цвет Физический адрес 10 15 14 19 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 • • • Физическая память Процесс Рис. 5.13. Назначение физических страниц процессу Раскраска страниц реализуется созданием стольких свободных списков, сколько страниц в кеше. Каждой странице в кеше назначается цвет. Машина с 1 Мбайт кеша и 4-килобайтными страницами имела бы 256 страниц кеша - следовательно, 256 цве- цветов. При запуске системы каждая vmjpage записывает свой цвет в кеше L1. Когда стра- страница освобождается, она помещается в свободный список, соответствующий ее цвету. При создании объекта ему назначается начальный цвет. Чтобы гарантировать, что запрос цветов достаточно сбалансирован, начальный цвет, который будет использован для следующего объекта, корректируется наименьшим из размера объекта и трети раз- размера кеша L1. Когда в объекте возникает отказ страницы, его предпочтительный цвет для новой страницы вычисляется путем добавления логического номера страницы объекта, вызвавшей отказ, к начальному цвету объекта по модулю числа цветов. Страница берется из свободного списка этого цвета, если он доступен. В противном случае проверяются цвета, отстоящие дальше всего от запрошенного цвета, до тех пор, пока не будет найдена доступная страница. Самая далеко отстоящая страница исполь- используется, потому что алгоритм хочет минимизировать возможность коллизий кеша с сосед- соседней страницей в виртуальном адресном пространстве. Например, если бы использова-
5.12. Замещение страниц 229 лась страница со следующим цветом, она скорее всего конфликтовала бы со страницей, которую алгоритм выбрал для следующего большего виртуального адреса. Раскраска страниц делает виртуальную память такой же детерминистической, как физическая память, с точки зрения производительности кеша. Таким образом, про- программы можно писать в предположении, что характеристики нижележащего аппарат- аппаратного кеша для их виртуального адресного пространства такие же, какими они были бы, если программа была запущена непосредственно в физическом адресном пространстве. 5.12. Замещение страниц Обслуживание отказов страниц и других требований памяти может осуществляться в течение некоторого времени из списка свободных страниц, но в конечном счете память должна быть возвращена для повторного использования. Некоторые страницы возвращаются при завершении процесса. На системах с большим объемом памяти и низкими ее запросами завершающиеся процессы могут предоставить достаточно свободной памяти для удовлетворения запросов. Такой случай возникает, когда для ядра и всех страниц, которые когда-либо использовались любым из текущих процес- процессов, памяти достаточно. Очевидно, на многих компьютерах недостаточно оперативной памяти, чтобы сохранять в памяти все страницы. Таким образом, в конце концов ста- становится необходимо переместить некоторые страницы во вторичное хранилище- обратно в файловую систему и пространство подкачки. Загрузки страниц управляются запросами. Однако для их выгрузки нет непосредственного указания, когда страница больше не нужна процессу Ядро должно реализовать некоторую стратегию для реше- решения, какие страницы выгрузить из памяти таким образом, чтобы оно могло заместить эти страницы другими, которым память нужна в настоящее время. В идеале эта стратегия будет выбирать те страницы для замещения, которые вскоре не понадобятся. Приближе- Приближением к этой стратегии является нахождение страниц, которые не использовались активно или в последнее время. Система 4.4BSD реализовала страничную подкачку по требованию с алгоритмом замещения страниц, который аппроксимирует глобальное наименее активное исполь- использование (global least-actively used) [Easton & Franaszek, 1979]. В FreeBSD однобитное поле использования для каждой страницы было дополнено счетчиком активности, чтобы аппроксимировать глобальное наименее активное использование. Оба этих алгоритма являются примерами алгоритма глобального замещения (global replacement algorithm): алгоритма, в котором выбор страницы для замещения осуществляется на основе критерия в масштабе всей системы. Алгоритм локального замещения (local replacement algorithm) выбрал бы процесс, для которого нужно заместить страницу, а затем выбрал бы страницу на основе критерия процесса. Хотя алгоритм в FreeBSD сходен по природе с алгоритмом в 4.4BSD, его реализация значительно отличается.
230 Глава 5. Управление памятью Ядро сканирует физическую память на регулярной основе, рассматривая страни- страницы для замещения. Использование общесистемного списка страниц заставляет все процессы соперничать за память на равной основе. Обратите внимание, что это совмес- совместимо также со способом обработки FreeBSD других предоставляемых системой ресурсов. Обычной альтернативой тому, чтобы разрешить всем процессам в равной степени состязаться за память, является разделение памяти на множество независи- независимых областей, каждая из которых относится к набору процессов, которые состязают- состязаются друг с другом за память. Эта схема используется, например, операционной систе- системой VMS [Kenah & Bate, 1984]. При такой схеме системные администраторы могут гарантировать, что у процесса или набора процессов всегда будет минимальная доля памяти. К сожалению, такой схемой может быть тяжело управлять. Выделение раз- разделу слишком маленького числа страниц может привести к недоиспользованию памяти и избыточной деятельности по вводу/выводу вторичных устройств хранения, тогда как установка этого числа слишком большим может привести к избыточной подкачке [Lazowska & Kelsey, 1978]. Ядро делит оперативную память на пять списков. 1. Переданная (wired): переданные страницы блокируются в памяти и не могут быть выгружены. Обычно эти страницы используются ядром или пейджером физической памяти, или они были заблокированы посредством mlock. Кроме того, все страницы, использующиеся для хранения структуры пользователя и стеков потоков загружен- загруженных (т.е. невыгруженных) процессов, также являются переданными. Переданные страницы нельзя выгрузить. 2. Активная', активные страницы используются одной или несколькими областями виртуальной памяти. Хотя ядро может их выгрузить, осуществление этого скорее всего вызовет отказы страниц активного процесса и обратную загрузку. 3. Неактивная', неактивные страницы имеют содержимое, которое по-прежнему из- известно, но они обычно не являются частью какой-либо активной области. Если содержание страниц грязное, до повторного использования страницы оно должно быть записано в резервную память. После очистки страницы она помещается в список кеша. Если система начинает испытывать недостаток памяти, демон выгрузки может попытаться переместить активные страницы в список неактивных в надежде найти страницы, которые на самом деле не используются. Критерии вы- выбора, которые используются демоном выгрузки для выбора страниц для перемеще- перемещения из активного списка в неактивный список, описаны далее в данном разделе. Когда списки свободной памяти и кеша становятся слишком маленькими, демон выгрузки проходит по неактивному списку, чтобы создать дополнительные стра- страницы кеша и свободные страницы.
5.12. Замещение страниц 231 4. Кеш: страницы кеша имеют содержание, которое по-прежнему известно, но они обычно не являются частью какой-либо активной области. Если они отображаются в активную область, они должны быть помечены только для чтения таким образом, чтобы любая попытка записи вызвала их перемещение из списка кеша. Они сходны с неактивными страницами, за тем исключением, что они не грязные, либо потому, что не были модифицированы, поскольку они были загружены, либо потому, что они были записаны в свое резервное хранилище. При необходимости их можно переместить в свободный список. 5. Свободные: свободные страницы не содержат полезного содержания и будут использоваться для удовлетворения запросов на новые страницы. Процесс idle пы- пытается хранить около 75 процентов страниц в списке свободных обнуленными таким образом, чтобы их не пришлось обнулять при обслуживании отказа страницы анонимной области. Страницы с неизвестным содержанием помещаются перед началом свободного списка. Обнуленные страницы помещаются в конец свобод- свободного списка. Процесс idle берет страницы из начала свободного списка, обнуляет их, помечает как обнуленные и помещает в конец свободного списка. Отказы страниц, которые будут заполнять страницы, берут их из начала свободного списка. Отказы страниц, которым нужны заполненные нулями страницы, берут их из конца свободного списка. Если страница помечена как обнуленная ее не нужно обнулять в ходе обслуживания отказа. Страницами оперативной памяти, которые могут использоваться процессами пользователя, являются страницы в списках активных, неактивных, кеша и свободных. Запросы на новые страницы берутся сначала из свободного списка, если в нем есть доступные страницы, в противном случае из списка кеша. Свободный список и список кеша подразделяются на отдельные списки, организованные в соответствии с цветом страницы. В идеале ядро поддерживало бы рабочий набор для каждого процесса в системе. При этом оно знало бы, сколько памяти предоставить каждому процессу, чтобы мини- минимизировать в последнем отказы страниц. Система виртуальной памяти FreeBSD не ис- использует модель рабочего набора, поскольку ему недостает точной информации о пат- паттерне ссылок процесса. Она отслеживает число сохраняемых процессом страниц посредством размера резидентного набора (resident-set size), но не знает, какая из рези- резидентных страниц составляет рабочий набор. В 4.3BSD число резидентных страниц использовалось в принятии решений о том, достаточно ли памяти для загрузки процесса, когда он хотел начать работать. Эта особенность не была перенесена в систему вирту- виртуальной памяти FreeBSD. Поскольку она хорошо работала в периоды большой потреб- потребности в памяти, эту особенность следует включить в будущих системах FreeBSD.
232 Глава 5. Управление памятью Параметры страничной подкачки Потребности в выделении памяти процессов постоянно конкурируют посредством обработчика отказов страниц, причем общей целью системы является сохранение минимального порога страниц в неактивном, свободном списках и списке кеша. Когда система работает, она следит за использованием оперативной памяти и пытается дос- достаточно часто запускать демон выгрузки, чтобы поддерживать объем неактивной, сво- свободной памяти и памяти кеша на уровне или чуть выше минимального порога, приве- приведенного в табл. 5.2. Когда процедура выделения страниц, vm_page_alloc(), определяет, что нужно больше памяти, она пробуждает демон выгрузки страниц. Табл. 5.2. Пороги допустимой памяти Пул Минимум Цель Свободная 0,7% 3% Кеш 3% 6% Неактивная 0% 4,5% Число страниц, которое должно быть возвращено демоном выгрузки, является функцией памяти, необходимой системе. Чем больше памяти нужно системе, тем больше памяти сканируется. Это сканирование вызывает увеличение числа освобож- освобожденных страниц. Демон выгрузки определяет необходимый объем памяти, сравнивая число доступных страниц памяти с несколькими параметрами, которые вычисляются во время запуска системы. Желательные значения параметров страничной подкачки сообщаются демону страничной выгрузки через глобальные переменные, которые можно просмотреть или изменить с помощью sysctl. Демон выгрузки также записыва- записывает ход своей работы в глобальных счетчиках, которые можно просмотреть или сбро- сбросить с помощью sysctl. Ход работы измеряется по числу просканированных страниц через каждый интервал его запуска. Целью демона выгрузки является поддержание неактивной и свободной очередей и очереди кеша между минимальным и пороговым значениями, приведенными в табл. 5.2. Демон выгрузки достигает этой цели, перемещая страницы из более актив- активных очередей в менее активные, чтобы достичь указанных диапазонов. Он перемещает страницы из списка кеша в свободный список, чтобы поддержать минимальное значение свободного списка, а также перемещает страницы из неактивного списка в список кеша, чтобы сохранить нужные параметры для списка кеша. Он перемещает страницы из актив- активного списка в неактивный, чтобы сохранить нужные параметры для неактивного списка. Демон выгрузки страниц Замещение страниц осуществляется демоном выгрузки страниц (процесс 2). Политика страничной подкачки демона выгрузки страниц воплощена в процедурах vm_pageout() и vm_pageout_scan(). Когда демон выгрузки возвращает страницы, которые были изме-
5.12. Замещение страниц 233 нены, он отвечает за запись их в область подкачки. Таким образом, демон выгрузки должен иметь возможность использовать обычные механизмы синхронизации ядра, такие, как sleepQ. Поэтому он запускает отдельный процесс со своей собственной структурой процесса и стеком ядра. Подобно init, демон выгрузки создается внутрен- внутренней операцией fork в ходе запуска системы (см. раздел 14.4); однако в отличие от init он остается после разветвления в режиме ядра. Демон выгрузки просто входит в vm_pageout() и никогда не возвращается. В отличие от некоторых других пользовате- пользователей процедур дискового ввода/вывода процесс выгрузки страниц нуждается в асин- асинхронном выполнении своих дисковых операций таким образом, чтобы он мог продол- продолжать сканирование параллельно с записью на диск. Изначально страницы обрабатывались с использованием алгоритма наиболее дав- давнего использования. Недостатком этого алгоритма является то, что неожиданная вспышка активности памяти может сбросить из кеша многие полезные страницы. Чтобы смягчить такое поведение, FreeBSD использует для предохранения страниц, имеющих предысторию использования, алгоритм наименее активного использования, чтобы они имели преимущество перед однократно использованными страницами, за- загруженными в память в период большой потребности в памяти. Когда страница впервые загружается в память, ей предоставляется начальный счетчик использования, равный трем. Дальнейшие сведения об использовании собираются демоном выгрузки во время его периодических просмотров памяти. При сканировании каждой страницы памяти проверяется его бит ссылки. Если бит установ- установлен, он сбрасывается, а счетчик использования для страницы увеличивается (вплоть до предела в 64) на число ссылок на страницу. Если бит ссылки сброшен, счетчик исполь- использования уменьшается. Когда счетчик использований достигает нуля, страница переме- перемещается из активного списка в неактивный. Повторно используемые страницы набирают большие значения счетчика использований, которые заставляют их оставаться в активном списке намного дольше, чем страницы, использованные лишь однажды. Целью демона выгрузки является сохранение числа страниц неактивного, свобод- свободного списков и списка кеша в пределах их желательных диапазонов. Каждый раз, когда использующая страницы операция вызывает падение количества свободной памяти ниже минимального порога, пробуждается демон выгрузки. Алгоритм обработки выгрузки страницы показан на рис. 5.14. В следующем обзоре обозначенные буквами пункты ссылаются на метки в левой части кода. А. Демон выгрузки вычисляет число страниц, которые нужно переместить из неак- неактивного списка в список кеша. Поскольку некоторые будут позже перемещены в список свободных, в список кеша должно быть перемещено достаточно страниц, чтобы сохранить в нем минимальный уровень после заполнения сво- свободного списка. Чтобы избежать насыщения системы ввода/вывода, демон выгрузки ограничивает число операций ввода/вывода, которые он запускает параллельно.
234 Глава 5. Управление памятью /* * Vm_pageout_scan выполняет грязную работу для демона pageout. */ void vm_pageout_scan (void) { [A] page_shortage = free_target + cache_minimum - (free_count + cache_count); max_writes_in_progress = 32; [B] for (page = FIRST(inactive list); page; page = next) { next = NEXT(page); if (page_shortage < 0) break; if (page busy) continue; [C] if (page's object has ref_count > 0 && page is referenced) { update page active count; move page to end of active list; continue; } if (page is invalid) { move page to front of free list; page_shortage--; continue; } if (page is clean) { move page to end of cache list; page_shortage--; continue; } [D] if (first time page seen dirty) { mark page as seen dirty; move to end of inactive list; continue; } check for cluster of dirty pages around page; start asynchronous write of page cluster; move to end of inactive list; page_shortage--; max_writes_in_progress--; if (max_writes_in_progress == 0) break; } [E] page_shortage - free_target + cache_minimum + inactive_target - (free_count + cache_count + inactive_count); [F] for (page = FIRST(active list); page; page = next) { next = NEXT(page); if (page_shortage < 0) break; if (page busy) continue; if (page's object has ref_count > 0) { update page active count; move page to end of active list; continue; } Рис. 5.14. Обработка выгрузки страниц
5.12. Замещение страниц 235 [G] decrement page active count; if (page active count > 0) { move page to end of active list; continue; } page_shortage--; if (page is clean) move page to end of cache list; else move page to end of inactive list; } [H] page_shortage = free_minimum - free_count; for (page = FIRST(cache list); page; page = next) { next = NEXT(page); if (page_shortage < 0) break; if (page busy) continue; move page to front of free list; } [I] if (targets not met) request swap-out daemon to run; [J] if (nearly all memory and swap in use) kill biggest process; } Рис. 5.14. Обработка выгрузки страниц (Окончание) B. Сканирование неактивного списка до тех пор, пока не будет перемещено нуж- нужное количество страниц. Занятые страницы пропускаются, поскольку они, вероятно, уже выгружаются и смогут быть перемещены позже, когда будут очищены. C. Если мы находим страницу, которая является частью объекта, который начал использоваться, она была перемещена в неактивный список преждевременно. Поэтому нужно обновить ее счетчик использования и переместить обратно в активный список. Страницы, не имеющие связанного с ними объекта, не содержат полезные данные и могут быть перемещены в свободный список. Чистые страницы можно переместить в список кеша. D. Грязные страницы нужно выгрузить, но сбрасывание страницы чрезвычайно дорого по сравнению с освобождением чистой страницы. Поэтому грязным страницам предоставляется дополнительное время в неактивной очереди путем двукратного прохождения их через очередь перед сбрасыванием. Они проходят через список еще один раз, когда очищаются. Это дополнительное время в неак- неактивной очереди снизит ненужный ввод/вывод, вызванный преждевременной выгрузкой активной страницы. Группирование проверяет до восьми грязных страниц с каждой стороны от выбранной страницы. Пейджер необходим лишь для записи выбранной страницы. Однако он может записать столько сгруппирован- сгруппированных грязных страниц, сколько сочтет подходящим. Сканирование неактивного спи- списка останавливается, если число выполняющихся выгрузок страниц достигает
236 Глава 5. Управление памятью своего предела. В 4.4BSD завершение ввода/вывода обрабатывалось демоном выгрузки. FreeBSD требует, чтобы пейджеры отслеживали свои операции по вводу/выводу, включая соответствующее обновление записанных страниц. Обновление записанной страницы при завершении ввода/вывода не перемещает страницу из неактивного списка в список кеша. Вместо этого страница остается в неактивном списке до тех пор, пока она не будет окончательно перемещена в список кеша в ходе следующего прохода демона выгрузки. E. Демон выгрузки вычисляет число страниц, которые нужно переместить из активного списка в неактивный список. Поскольку некоторые в конечном счете нужно переместить в список кеша и свободный список, в неактивный список нужно переместить достаточное число страниц, чтобы сохранить его с нужным уровнем после того, как будут заполнены список кеша и свободный список. F. Сканировать активный список до тех пор, пока не будет перемещено нужное количество страниц. Пропускать занятые страницы, поскольку они, вероятно, активно используются. Если мы находим страницу, которая является частью активного объекта, обновить счетчик ее использования и переместить в конец активного списка. G. Страница неактивна, поэтому уменьшить счетчик ее использования. Если значение счетчика все еще больше нуля, переместить ее в конец активного спи- списка. В противном случае переместить ее в неактивный список, если она грязная, или в список кеша, если она чистая. Н. Вычислить число страниц, которые нужно переместить в свободный список. Свободный список заполняется лишь до своего минимального уровня. Предпочтение отдается хранению страниц в списке кеша, поскольку их можно вернуть, если их объект или файл снова активируются. Попав в свободный спи- список, идентичность страницы теряется. Поэтому демон выгрузки откладывает перемещение страниц в свободный список до тех пор, пока они непосредст- непосредственно не понадобятся. I. Если цели числа страниц не были достигнуты, демон выгрузки начинает (см. следующий подраздел) пытаться освободить дополнительную память. J. Ядро FreeBSD 5.2 не налагает каких-либо ограничений на количество виртуаль- виртуальной памяти, которую оно предоставит. Если оно обнаружит, что почти запол- заполнило свою память и пространство подкачки, оно избегает попадания в ловушку, завершая самый большой процесс. Когда-нибудь в систему будут добавлены учет и ограничения виртуальной памяти, чтобы избежать этого непродуманного механизма контроля за ресурсами. Обратите внимание, что на рис. 5.14 были опущены блокировки страниц и объектов, чтобы упростить объяснение.
5.12. Замещение страниц 237 Подкачка процессов Хотя подкачки процессов (swapping) обычно избегают, есть несколько случаев, когда она используется в FreeBSD для разрешения серьезной нехватки памяти. Подкачка процессов в FreeBSD происходит, когда случается что-нибудь из следующего. * В системе становится так мало памяти, что процесс страничной подкачки не может достаточно быстро освободить память, чтобы удовлетворить потребности. Например, недостаток памяти может произойти, когда несколько больших процес- процессов запускаются на машине, в которой не хватает памяти для минимальных рабочих наборов процессов. * Процессы полностью неактивны в течение более 10 секунд. Иначе такие процес- процессы сохранили бы лишь несколько страниц памяти, связанной с их структурой пользователя и стеками потоков. Операции подкачки процессов полностью удаляют процесс из оперативной памя- памяти, включая таблицы страниц процесса, страницы сегментов данных и стека, которые не находятся уже в пространстве подкачки, и структуру пользователя и стеки потоков. Подкачка процессов запускается, лишь когда страничная подкачка не может под- поддерживать потребности в памяти или когда кратковременные потребности в памяти служат основанием для выгрузки процесса. Вообще механизм планирования под- подкачки процессов не работает достаточно хорошо под большой нагрузкой; производи- производительность системы гораздо выше, когда планирование памяти можно осуществлять с помощью алгоритма замещения страниц, а не когда используется алгоритм под- подкачки процессов. Выгрузка процесса осуществляется демоном выгрузки процессов, vmdaemon (про- (процесс 3). Политика выгрузки vmdaemon воплощена в процедуре vm_daemon(). Если демон выгрузки страниц может найти любые процессы, которые находились в состоя- состоянии сна в течение более 10 секунд (swap_idle_threshold2, порог для того, чтобы считать время сна «длительным»), он выгрузит тот, время сна которого было наибольшим. У таких процессов наименьшая вероятность хорошо использовать память, которую они занимают; поэтому они выгружаются, даже если они небольшие. Если ни один из этих процессов не доступен, демон выгрузки страниц выгрузит процесс, который спал всего лишь 2 секунды (swap_idle_thresholdl). Этот критерий пытается избежать полной выгрузки до тех пор, пока демон выгрузки страниц безусловно не сможет со- сохранить достаточно свободной памяти. В 4.4BSD, если памяти по-прежнему очень мало, демон выгрузки выбрал бы работо- работоспособный процесс, который был резидентным дольше всех. После начала выгрузки работоспособных процессов процессы, подходящие для выгрузки, занимали бы очереди в памяти таким образом, чтобы ни один процесс не был выброшен полностью. Демон выгрузки FreeBSD не будет выбирать для выгрузки работоспособный процесс. Поэтому, если набор работоспособных процессов не помещается в памяти, машина попадет
238 Глава 5. Управление памятью в тупик. В современных машинах достаточно памяти, чтобы это условие не возникало. Если оно возникнет, алгоритм 4.4BSD нужно будет восстановить. Механика осуществления выгрузки процесса проста. Флаг загрузки процесса PINMEM сбрасывается, чтобы показать, что процесс не находится в памяти. В ходе выполнения выгрузки устанавливается флаг PS_SWAPPINGOUT, чтобы в то же самое время не предпринимались ни повторная попытка выгрузки, ни попытка загрузки. Если работоспособный процесс должен быть подкачан (что в настоящее время никогда не происходит), его нужно удалить из очереди готовых к запуску процессов. Структура пользователя процесса и стек ядра его потоков помечаются после этого как выгружае- выгружаемые, что дает возможность выгрузить страницы структуры пользователя и стека вместе с любыми другими оставшимися в процессе страницами посредством стан- стандартного механизма выгрузки страниц. Выгруженный процесс не может быть запущен до тех пор, пока не будет подкачан обратно в память. Процесс подкачки Операции подкачки выполняются процессом подкачки, swapper (процесс 0). Этот про- процесс является первым, который система создает при своем запуске. Политика подкачки swapper воплощена в процедуре schedulerQ. Эта процедура подкачивает процессы обратно в память, когда она доступна и когда процессы готовы к запуску. В любое время планировщик подкачки (swapper) находится в одном из трех состояний. 1. Простой (idle): нет готовых к запуску выгруженных процессов. Простой является нормальным состоянием. 2. Подкачка (swapping in): по крайней мере один процесс выгружен и schedulerQ пытается найти для него память. 3. Выгрузка (swapping out): система испытывает недостаток памяти или недоста- недостаточно памяти для подкачки процесса. В этих условиях schedulerQ пробуждает демон выгрузки страниц, чтобы освободить страницы и выгружать остальные про- процессы до тех пор, пока недостаток памяти не ослабнет. Если готово к запуску более одного выгруженного процесса, первой задачей swap- swapper является решение вопроса о том, какой процесс загрузить. Это решение может повлиять на решение о том, нужно ли выгружать другой процесс. Каждому выгружен- выгруженному процессу назначается приоритет, основанный на: * продолжительности времени нахождения его в выгруженном состоянии; * значении его относительного приоритета (nice); количестве времени, которое он провел в состоянии сна с момента последнего запуска.
5.13. Переносимость 239 Вообще процесс, который больше всего находился в выгруженном состоянии или был выгружен из-за того, что долго спал перед этим, будет закачан в первую очередь. Когда процесс выбран, swapper проверяет, достаточно ли свободной памяти, чтобы за- закачать процесс. Исторически система требовала такого количества доступной памяти, которое процесс занимал до своей выгрузки. Для FreeBSD это требование было сниже- снижено до требования, чтобы число страниц в свободном списке и списке кеша было равно по крайней мере минимальному порогу свободной памяти. Если есть достаточно сво- свободной памяти, процесс загружается обратно в память. Сразу же подкачивается струк- структура пользователя, но оставшуюся часть своего рабочего набора процесс загружает из резервного хранилища через подкачку по требованию. Таким образом, не вся память, которая нужна процессу, используется немедленно. Более ранние системы BSD отсле- отслеживали предстоящие запросы памяти и загрузили бы готовые к исполнению процессы, лишь когда для выполнения их ожидающихся потребностей становилось достаточно свободной памяти. FreeBSD разрешает подкачивать все выгруженные готовые к выпол- выполнению процессы, как только будет достаточно памяти для загрузки их структуры поль- пользователя и стеков потоков. Процедура подкачки процесса обратна процедуре его выгрузки. 1. Выделяется память для структуры пользователя и стека ядра для каждого из его потоков, и они считываются обратно из пространства подкачки. 2. Процесс помечается как резидентный, а его готовые в выполнению потоки возвра- возвращаются в очередь запуска (т.е. те потоки, которые не остановлены и не являются спящими). После завершения подкачки процесс готов к запуску, как любой другой, за исключением того, что у него нет резидентных страниц. Он будет загружать нужные страницы, вызывая отказы страниц. 5.13. Переносимость Все, что обсуждалось в данной главе вплоть до этого раздела, было частью машинно- независимых структур данных и алгоритмов. Эти части системы виртуальной памяти не требуют больших изменений, когда FreeBSD переносится на новую архитектуру. В данном разделе будут описаны машинно-зависимые части системы виртуальной памяти; те части системы виртуальной памяти, которые должны быть написаны как часть переноса FreeBSD на новую архитектуру. Машинно-зависимые части системы виртуальной памяти управляют аппаратурой блока управления памятью (memory-manage- (memory-management unit - MMU). MMU реализует трансляцию адресов и управление доступом при отображении виртуальной памяти в физическую память. Одна из распространенных организаций MMU использует постоянно находящиеся в памяти таблицы страниц прямого отображения (foi'ward-mapped page tables).
240 Глава 5. Управление памятью Эти таблицы страниц являются большими непрерывными массивами, индексируемы- индексируемыми виртуальным адресом. Для каждой виртуальной страницы в адресном пространстве в массиве есть один элемент — элемент таблицы страниц (page-table entiy — РТЕ). Этот элемент содержит физическую страницу, на которую отображена виртуальная страница, а также права доступа, биты состояния, сообщающие о том, была ли ссылка на страницу или ее модификация, и бит, указывающий, содержит ли элемент действи- действительную информацию. Для 4-гигабайтного адресного пространства с 4-килобайтными виртуальными страницами и 32-разрядным элементом таблицы страниц для описания всего адресного пространства потребовался бы 1 миллион элементов, или 4 Мбайта. Поскольку большинство процессоров используют небольшую часть своих адресных пространств, большая часть элементов была бы недействительной, и выделение 4 Мбайтов физической памяти каждому процессу было бы расточительно. Поэтому большинство структур таблиц страниц являются иерархическими, использующими два или более уровней отображения. При иерархической структуре различные части виртуального адреса используются для индексирования различных уровней таблиц страниц. Промежуточные уровни таблицы содержат адреса следующего нижележаще- нижележащего уровня таблицы страниц. Ядро может пометить неиспользующиеся большие непрерывные области адресного пространства, вводя недействительные элементы в вышележащих уровнях таблицы страниц, устраняя необходимость в недействитель- недействительных дескрипторах для каждой отдельной неиспользующейся виртуальной страницы. Эта иерархическая структура таблиц страниц требует от аппаратного обеспечения делать частые ссылки на память для трансляции виртуальных адресов. Чтобы ускорить процесс трансляции, большинство основанных на таблицах страниц MMU имеют также небольшой, быстрый, полностью ассоциативный аппаратный кеш недавних транслированных адресов, структуру, обычно известную как буфер быстрого преобра- преобразования адреса (translation lookaside buffer - TLB). Когда транслируется ссылка на па- память, сначала просматривается TLB, и лишь если действительный элемент там не най- найден, проходится структура таблиц страниц для текущего процесса. Поскольку большин- большинство программ демонстрируют пространственную близость своих паттернов доступа к памяти, TLB не нужно быть большим; многие включают всего лишь 128 элементов. Когда адресные пространства вырастают из 32 до 48, а недавно и до 64 разрядов, простые индексированные структуры данных становятся громоздкими с тремя или более уровнями таблиц, необходимых для обработки трансляции адреса. Ответом на этот рост таблиц страниц является инвертированная таблица страниц, известная также как таблица страниц с обратным отображением. В инвертированной табли- таблице страниц аппаратура по-прежнему поддерживает резидентную в памяти таблицу, но эта таблица содержит один элемент на физическую страницу и индексируется по физическому адресу вместо виртуального. Элемент содержит виртуальный адрес, на который в настоящее время отображена физическая страница, а также атрибуты защиты и состояния. Аппаратура осуществляет трансляцию адреса с виртуального в физический путем вычисления хеш-функции виртуального адреса, чтобы выбрать
5.13. Переносимость 241 Регистр текущего адресного пространства V V V V м м м м R R R R АСС АСС АСС АСС • • • V м R АСС м м м м АСС АСС АСС АСС V М R АСС Таблица каталога V V V V м м м м R R R R АСС АСС АСС АСС • • • V м R АСС Страницы Таблицы страниц Рис. 5.15. Двухуровневая организация таблицы страниц. Обозначения: V - бит действитель- действительности страницы; М - бит модификации страницы; R - бит ссылки на страницу; „ nnoDO п/лг^тл/гто ту РтпоишгР ности р ф АСС - права доступа к странице элемент в таблице. Система разрешает коллизии путем связывания вместе элементов таблиц и осуществления линейного поиска в этой цепи до обнаружения подходящего виртуального адреса. Преимуществом инвертированной таблицы страниц является то, что размер таблицы пропорционален количеству физической памяти и что необходима лишь одна глобаль- глобальная таблица, а не по одной таблице на процесс. Недостатком этого подхода является то, что в любой момент времени на любой данный физический адрес может быть отобра- отображен лишь один виртуальный адрес. Это ограничение делает совмещение виртуальных адресов - наличие нескольких виртуальных адресов для одной и той же физической страницы - труднодостижимым. Как и в случае таблицы страниц прямого отображе- отображения, для ускорения процесса трансляции обычно используется TLB. Последняя распространенная организация MMU состоит лишь из TLB. Эта архи- архитектура представляет простейший аппаратный дизайн. Она дает программному обес- обеспечению максимум гибкости, давая ему возможность управлять информацией о транс- трансляции с помощью любых структур, которыми пожелает. Часто перенос на другую архитектуру со сходной организацией управления памя- памятью может использоваться в качестве отправного пункта для нового переноса. Архи- Архитектура PC использует типичную двухуровневую организацию таблиц страниц, пока- показанную на рис. 5.15. Адресное пространство разделено на 4-килобайтные виртуальные страницы, причем каждая страница идентифицируется 32-разрядным элементом
242 Глава 5. Управление памятью в таблице страниц. Каждый элемент таблицы страниц содержит номер физической страницы, назначенной виртуальной странице, права доступа, сведения о модифика- модификации и ссылках и бит, показывающий, что элемент содержит действительную информа- информацию. 4 Мбайта элементов таблиц страниц таким же образом делятся на 4-килобайтные страницы таблицы страниц, каждая из которых описывается одним 32-разрядным элементом в каталоге страниц (directory table). Элементы каталога страниц почти идентичны элементам таблицы страниц: они содержат биты доступа, биты изменения и ссылок, бит действительности и номер физической страницы описываемой страницы таблицы страниц. Одна 4-килобайтная страница - 1024 элемента каталога страниц - охватывает адресное пространство с максимальным размером 4 Гбайта. Аппаратный регистр CR3 содержит физический адрес каталога страниц для текущего активного процесса. На рис. 5.15 трансляция виртуального адреса в физический во время доступа про- процессора происходит следующим образом. 1. 10 самых старших битов виртуального адреса используются в качестве индекса в активном каталоге страниц. 2. Если выбранный элемент каталога страниц действительный и биты прав доступа разрешают произвести доступ, следующие 10 битов виртуального адреса исполь- используются в качестве индекса в странице таблицы страниц, на которую ссылается эле- элемент каталога страниц. 3. Если выбранный элемент таблицы страниц действительный и биты прав доступа совпадают, последние 12 битов виртуального адреса объединяются с физической страницей, на которую ссылается элемент таблицы страниц, для получения физического адреса доступа. Роль модуля ртар Машинно-зависимый код описывает, как осуществляется физическое отображение между виртуальными адресами процессов пользователя и ядра и физическими адреса- адресами оперативной памяти. Эта функция отображения включает вдобавок к трансляции адресов управление правами доступа. В FreeBSD модуль физического отображения (physical-mapping module - ртар) управляет машинно-зависимой трансляцией и табли- таблицами доступа, которые используются аппаратурой управления памятью либо непо- непосредственно, либо опосредованно. Например, на PC ртар поддерживает для каждого процесса, а также для ядра постоянно находящийся в памяти каталог и таблицы стра- страниц. Машинно-зависимое состояние, необходимое для описания трансляции и прав доступа к отдельной странице, часто называют отображением (mapping) или струк- структурой отображения. Интерфейс ртар FreeBSD почти идентичен интерфейсу в Mach 3.0 и разделяет многие особенности дизайна с Mach 3.0. Модуль ртар предназначен для того, чтобы
5.13. Переносимость 243 быть логически независимым от более высоких уровней системы виртуальной памяти. Интерфейс имеет дело строго с машинно-независимыми выровненными по границе страницы виртуальными и физическими адресами и машинно-независимыми правами доступа. Машинно-независимый размер страницы может быть кратным поддерживае- поддерживаемому архитектурой размеру страницы. Таким образом, действия ртар должны иметь возможность влиять более чем на одну физическую страницу в логической странице. Машинно-независимая защита является простым кодированием битов разрешений на чтение, запись и исполнение, ртар должен отображать все возможные комбинации в действительные специфичные для архитектуры значения. ртар процесса рассматривается как кеш информации об отображении, хранящейся в машинно-зависимом формате. Ему как таковому не нужно содержать полное состоя- состояние для всех действительных отображений. Состояние отображения является ответст- ответственностью машинно-независимого уровня, за одним исключением: модуль ртар может по своему усмотрению отбросить состояние отображения для возвращения ре- ресурсов. Исключением являются переданные (wired) отображения, которые никогда не должны вызывать отказ, который достигает машинно-независимую процедуру vm_fault(). Таким образом, состояние для переданных отображений должно быть со- сохранено в ртар до тех пор, пока оно не будет удалено явным образом. Вообще, процедуры ртар могут действовать либо с набором отображений, опреде- определенных диапазоном виртуальных адресов, либо со всеми отображениями для опреде- определенного физического адреса. Способность действовать с отдельными или всеми вирту- виртуальными отображениями для физической страницы требует, чтобы сведения об ото- отображении, поддерживаемые модулем ртар, легко находились как по виртуальному, так и по физическому адресам. Для таких архитектур, как PC, которые поддерживают резидентные таблицы страниц, преобразование виртуального в физический, или прямой поиск, может быть простой эмуляцией прохождения аппаратной таблицы стра- страниц. Преобразование физического в виртуальный, или обратный поиск, использует список структур pv_entiy, описанный в следующем подразделе, для нахождения всех элементов таблицы страниц, ссылающихся на страницу. Список может содержать мно- множество элементов, только если разрешено совмещение виртуальных адресов. Есть две стратегии, которые можно использовать для управления ресурсами памяти ртар, такими, как каталог пользователя или память таблицы страниц. Тради- Традиционным и самым простым подходом является управление модулем ртар своей собст- собственной памятью. При этой стратегии модуль ртар может захватить во время загрузки системы фиксированное количество зарезервированной (wired) физической памяти, отобразить эту память в адресное пространство ядра и выделять участки памяти для своих собственных структур данных по мере необходимости. Основное преимущество в том, что этот подход изолирует потребности в памяти модуля ртар от оставшейся системы и ограничивает зависимости модуля ртар от других частей системы. Этот дизайн согласуется с уровневой моделью системы виртуальной памяти, в которой ртар является самым нижним и, следовательно, самостоятельным уровнем.
244 Глава 5. Управление памятью Недостаток в том, что этот подход требует дублирования многих из функций управления памятью. У модуляртар есть свои распределитель и освободитель памяти для своей индивидуальной кучи — кучи с фиксированным размером, которую нельзя подгонять под изменяющиеся требования к памяти в масштабах всей системы. Для архитектуры с резидентными таблицами страниц она должна отслеживать несмежные участки таблиц страниц процесса, поскольку процесс может заполнять свое адресное пространство разбросанным образом. Удовлетворение этого требования ведет к дуб- дублированию большой части стандартного кода управления списками, который исполь- используется, например, кодом vmjnap. Альтернативный подход, используемый PC, заключается в применение рекурсив- рекурсивного использования кода виртуальной памяти более высокого уровня для управления некоторыми ресурсами ртар. Здесь 4-килобайтный каталог страниц для каждого про- процесса пользователя отображается в адресное пространство ядра как часть настройки процесса и остается резидентной до тех пор, пока процесс не завершится. Пока процесс работает, элементы его таблиц страниц отображены в виртуально непрерыв- непрерывный 4-мегабайтный массив элементов таблиц страниц в адресном пространстве ядра. Такая организация ведет к невидимой оптимизации сохранения памяти, исполь- использующейся в модуле ртар PC, где страница таблицы страниц ядра, описывающая 4-мегабайтный диапазон таблиц страниц пользователя, может дублироваться как каталог страниц пользователя. Ядро поддерживает также альтернативные отображе- отображения для хранения отдельных страниц таблицы страниц других неработающих процес- процессов, если ему нужно получить доступ к их адресному пространству. Использование одних и тех же процедур выделения страниц, как и всех других частей системы, гарантирует, что физическая память выделяется лишь при необходи- необходимости и из системного пула свободной памяти. Таблицы страниц и другие ресурсы ртар также могут выделяться из выгружаемой памяти ядра. Этот подход легко и эффек- эффективно поддерживает большие разрозненные адресные пространства, включая собст- собственное адресное пространство ядра. Основным недостатком является то, что этот подход нарушает независимую природу интерфейса. В частности, рекурсивная структура ведет к проблемам тупиков с глобальными многопроцессорными циклическими блокировками, которые могут удерживаться, пока ядро вызывает процедуру ртар. Структуры данных ртар содержатся в каталоге машинно-зависимых включаемых файлов в файле pmap.h. Большая часть кода для этих процедур находится в каталоге машинно-зависимых исходных файлов в файле ртар.с. Главными задачами модуля ртар являются следующие. Инициализация и запуск системы (ртар_bootstrap(), pmap_init(), pmapjpvwkemelQ). Выделение и освобождение отображений физических страниц на виртуальные (pmap_enter(), pmap_remove(), ртар _qenter(), ртар_qremove()).
5.13. Переносимость 245 * Изменение прав доступа и других атрибутов отображения (pmap_change_wiring(), pmap_page_protect(), pmap_protect()). * Поддержание информации об использовании физических страниц (pmap_clearjnodify(), pmap_clear_reference(), pmap_is_modified(), pmap_ts_referenced()). * Инициализация физических страниц (pmap_copy_page(), pmap_zero_page()). * Управление внутренними структурами данных (ртар _pinit(), pmap_release()). В следующих подразделах описана каждая из этих задач. Инициализация и запуск Первым шагом в запуске системы является перенос загрузчиком образа ядра с диска или из сети в физическую память машины. Образ загрузки ядра во многом сходен с образом любого другого процесса; он содержит сегмент кода, сегмент инициали- инициализированных данных и сегмент неинициализированных данных. Загрузчик помещает ядро непрерывно в начале физической памяти. В отличие от процесса пользователя, страницы которого загружаются в память по требованию, код и данные для ядра счи- тываются в память полностью. За этими двумя сегментами загрузчик обнуляет область памяти, равную размеру сегмента неинициализированной памяти ядра. После загрузки ядра загрузчик передает управление по начальному адресу, указанному в исполняемом образе ядра. Когда ядро начинает выполнение, оно выполняется с выключен- выключенным MMU. Соответственно вся адресация осуществляется с использованием прямых физических адресов. Первой задачей, предпринимаемой ядром, является установка ртар ядра и любых других структур данных, необходимых для описания виртуального адрес- адресного пространства ядра. На PC начальная установка включает выделение и инициа- инициализацию каталогов и таблиц страниц, которые отображают статически загруженный образ ядра и отображенное в память адресное пространство ввода/вывода, выделяя для страниц таблиц страниц ядра фиксированный объем памяти, выделяя и инициа- инициализируя структуру пользователя и стек ядра для начального процесса, резервируя особые области для адресного пространства ядра и инициализируя систематизиро- систематизированные критические внутренние структуры данных ртар. После осуществления этого можно включить MMU. После подключения MMU ядро начинает работать в контексте нулевого процесса. После того как ядро начало работать в своем виртуальном адресном пространстве, оно продолжает инициализировать оставшуюся часть системы. Оно определяет размер физической памяти, затем вызывает ртар Jbootstrap() и vm_page_startup() для установки начальных структур данных ртар, для выделения структур vm_page и для создания небольших пулов памяти фиксированного размера, которые распределители памяти ядра могут использовать таким образом, что они могут начать отвечать на запросы выделения памяти. Затем оно делает вызов для установки машинно-независимой
246 Глава 5. Управление памятью части системы виртуальной памяти. Оно завершает вызовом pmapjnit(), который вы- выделяет все ресурсы, необходимые для управления множеством адресных пространств пользователя, и синхронизирует структуры данных виртуальной памяти ядра более высокого уровня с ртар ядра. pmap_init() выделяет минимальное количество зарезервированной (wired) памяти для использования страницами таблицы страниц ядра. Пространство таблиц страниц динамически расширяется процедурой pmap_growkernel() по мере необходимости в ходе работы ядра. После выделения она никогда не освобождается. Ограничение на размер адресного пространства ядра выбирается во время загрузки. На PC ядру обычно предоставляется максимум 1 Гбайт адресного пространства. В 4.4BSD память, управляемая буферным кешем, была отделена от памяти, управ- управляемой системой виртуальной памяти. Поскольку все страницы виртуальной памяти использовались для отображения областей процессов, было целесообразно создать ин- инвертированную таблицу страниц. Эта таблица представляла собой массив структур pv_entiy. Каждая pv_entry описывала одну трансляцию адреса и включала виртуаль- виртуальный адрес, указатель на связанную с этим виртуальным адресом структуру ртар, ссылку для соединения в цепь нескольких элементов, отображающих этот физический адрес, и дополнительную информацию, специфичную для элементов, отображающих страницы таблицы страниц. Построение выделенной таблицы было разумно, посколь- поскольку ртар ссылался на все действительные страницы, некоторые даже имели несколько отображений. С поглощением в FreeBSD буферного кеша системой виртуальной памяти многие страницы памяти используются для кеширования файловых данных, которые не ото- отображены в адресное пространство какого-либо процесса. Поэтому предварительное выделение таблицы структур pv_entry является расточительным, поскольку многие из них не были бы использованы. Поэтому FreeBSD выделяет структуры pv_entry по тре- требованию, когда страницы отображаются в адресное пространство процесса. На рис. 5.16 показаны ссылки pv_entiy для набора страниц, имеющих по одному отображению. Назначением структур pv_entiy является идентификация адресного про- пространства, которое отобразило страницу. Машинно-зависимая часть каждой структуры vm_page содержит заголовок списка структур pv_entry и число элементов в этом спи- списке. На рис. 5.16 объект использует страницы 5, 18 и 79. Заголовки списков в машинно- зависимых структурах этих структур vm_page указывали бы каждый на отдельную структуру pv_entjy, обозначенную на рисунке номером ссылающейся на нее структуры vm_page. На рис. 5.16 не показано, что каждая структура физического отображения поддерживает также список всех структур pv_entiy, которые на нее ссылаются. Каждая pv_entry может ссылаться лишь на одно физическое отображение. Когда объект начинает разделяться двумя или более процессами, каждая физическая страница памяти становится отображенной в два или более наборов таблиц страниц. Для отслежи- отслеживания этих множественных ссылок модуль ртар должен создать цепочки структур
5.13. Переносимость 247 pv entry 5 pv_entry 18 pv_entry 79 vmjnap vmjpmap Статистика Начальный адрес Конечный адрес Смещение объекта :">. vmjpage 5 i vmjpage 18 vm_page 79 vnode / объект JL vmspace vm_map_entry Рис. 5.16. Физические страницы с одним отображением pv_entry, как показано на рис. 5.17. Копирование при записи является примером необ- необходимости нахождения всех отображений страницы, поскольку для него требуется уста- установить атрибут только для чтения для таблиц страниц во всех процессах, разделяющих этот объект. Модуль ртар может реализовать этот запрос, обходя список страниц, свя- связанных с объектом, который должен получить атрибут копирования при записи. Для каждой страницы он проходит список структур pv_entry этой страницы. Затем он делает соответствующее изменение в элементе таблицы страниц, связанной с каждой структурой pv_entry. pv_entry 5 — Ў 1 nv pntrv ' I J pv entry 18 1— pv entry p^ ь pv_entry!9 1—¦ pv_entry —> vmspace vm map vmjpmap Статистика —> —> ь vmspace vm_map vmjpmap Статистика vm map entry —> Начальный адрес Конечный Смещение объекта Л vnode/объект V 1 r vmjnap_entry j —> - Начальный адрес Конечный адрес Смещение объекта • • • -* Рис. 5.17. Физические страницы с несколькими отображениями vmjpage 5 vmjpage 18 т vmjpage 79 Ь
248 Глава 5. Управление памятью Системе со множеством разделяемых объектов может потребоваться множество структур pv_entry, которые могут использовать чрезмерное количество памяти ядра. Альтернативой было бы хранение списка, связанного с каждым объектом всех струк- структур vm_map_entiy, ссылающихся на него. Когда возникает необходимость в изменении отображения всех ссылок на страницу, ядро могло бы обойти этот список, проверяя адресное пространство, связанное с каждым vm_map_ent?y, на предмет содержания ссылки на эту страницу. Для каждой найденной страницы оно могло бы сделать соот- соответствующее изменение. Структуры pv_entiy занимают больше памяти, но снижают время, необходимое для осуществления обычных операций. Например, рассмотрите систему, запустившую тысячи процессов, которые все разделяют общую библиотеку Без списка pv_entiy изменение атрибута страницы на копирование при записи потребовало бы проверки всех тысяч процессов. Со списком pv_entry нужно было бы проверить лишь те процессы, которые используют эту страницу. Выделение и освобождение отображения Главной ответственностью модуля ртар является утверждение (выделение) и отмена (освобождение) отображений физических страниц на виртуальные адреса. Физические страницы представляют кешированные части объекта, который предоставляет данные из файла или анонимной области памяти. Физическая страница связана с виртуальным адресом, поскольку этот объект отображен в адресное пространство процесса либо явно посредством ттар, либо неявно посредством^ог/: или exec. Отображения физиче- физических адресов в виртуальные не создаются во время отображения этого адреса; вместо этого этот критерий откладывается до первой ссылки на определенную страницу. В этот момент возникнет отказ доступа и будет вызван ртар_enter(). pmap_enter() от- отвечает за все необходимые побочные эффекты, связанные с созданием нового отобра- отображения. Такие побочные эффекты в значительной степени являются результатом введе- введения второй трансляции для уже отображенной физической страницы - например, в ре- результате операции копирования при записи. Обычно эта операция требует сброса эле- элементов TLB или кеша одного или нескольких процессоров для сохранения непротиворечивости. Кроме своего использования для создания новых отображений pmap_entiy() может также вызываться для изменения атрибутов резервирования или защиты сущест- существующего отображения или для повторной привязки существующего отображения для виртуального адреса к новому физическому адресу. Ядро может осуществлять измене- изменение атрибутов путем вызова процедуры соответствующего интерфейса, описанного в следующем подразделе. Изменение конечного физического адреса отображения яв- является просто вопросом удаления сначала старого отображения, а затем обработки его подобно любому другому запросу нового отображения.
5.13. Переносимость 249 pmap_enter() является единственной процедурой, которая не может терять состоя- состояние или отсрочивать свое действие. При вызове она должна создать запрошенное ото- отображение и проверить это отображение до возвращения вызывающему. На PC pmap_enter() должна сначала проверить, существует ли для запрошенного адреса эле- элемент таблицы страниц. Если в запрошенном для нового отображения месте таблице страниц процесса еще не была выделена физическая страница, выделяется заполнен- заполненная нулями страница, резервируется и вводится в каталог страниц процесса. После проверки того, что для введения отображения все ресурсы таблиц страниц существуют, pmap_enter() проверяет или модифицирует запрошенное отображение следующим образом. 1. Проверяет, существует ли уже структура отображения для данного преобразования виртуального адреса в физический. Если да, должен быть сделан вызов для измене- изменения атрибутов защиты или резервирования отображения; он осуществляется, как описано в следующем подразделе. 2. В противном случае, если для этого виртуального адреса отображение существует, но он ссылается на другой физический адрес, это отображение удаляется. 3. Число ссылок на страницу таблицы страниц увеличивается каждый раз, когда добавляется ссылка на новую страницу, и уменьшается каждый раз, когда удаляется ссылка на старую страницу. Когда удаляется последняя действительная страница, число ссылок сравнивается с нулем, и страница таблицы страниц освобождается, поскольку она не содержит полезной информации. 4. Создается и проверяется элемент таблицы страниц, при необходимости с очище- очищением элементов кеша и TLB. 5. Если физический адрес находится вне диапазона, управляемого модулем ртар (например, страница буфера кадра), структура pv_entiy не нужна. В противном случае для случая нового отображения для физической страницы, которая отобра- отображена в адресное пространство, создается структураpv_entiy. 6. Для машин с виртуально индексируемым кешем делается проверка, содержит ли уже данная физическая страница другие отображения. Если да, может потребо- потребоваться пометить все отображения для запрещения кеширования, чтобы избежать несогласованности кеша. Когда отменяется отображение объекта в адресное пространство, либо явно посредством типтар, либо неявно при завершении процесса, вызывается модупьртар для отмены и удаления отображений для всех физических страниц, кеширующих данные для объекта. В отличие от ртар_enter(), pmap_remove() можно вызвать с диапа-
250 Глава 5. Управление памятью зоном виртуальных адресов, включающих более одного отображения. Поэтому ядро осуществляет снятие отображения путем прохождения в цикле всех виртуальных стра- страниц в диапазоне с игнорированием тех, для которых нет отображения, и удалением тех, для которых оно есть. pmap_remove() на PC проста. Она просматривает в цикле указанный диапазон адресов, отменяя отображения отдельных страниц. Поскольку ртар_remove() может вызываться с большими сильно разбросанными выделенными областями, такими, как весь диапазон виртуальных адресов процесса, она должна разумно пропускать недей- недействительные элементы в пределах диапазона. Она пропускает недействительные эле- элементы, проверяя сначала в каталоге таблиц определенный адрес и, если элемент недействительный, переходя к границе следующих 4 Мбайт. Когда все отображения страниц были отменены, осуществляется необходимая глобальная очистка кеша. Чтобы отменить отдельное отображение, ядро находит и помечает как недейст- недействительный соответствующий элемент таблицы страниц. Биты ссылок и изменений для этой страницы сохраняются в структуре vm_page страницы для будущего вос- восстановления. Если это отображение было пользовательским, счетчик ссылок на страницу таблицы страниц уменьшается. Когда счетчик достигает нуля, страницу таблицы страниц можно вернуть, поскольку она больше не содержит действитель- действительных отображений. Когда страница таблицы страниц удаляется из адресного про- пространства ядра (т.е. в результате удаления из этой страницы последнего действитель- действительного отображения пользователя), должен быть обновлен каталог страниц процесса. Ядро осуществляет это обновление, делая недействительным соответствующий эле- элемент каталога страниц. Если физический адрес из отображения находится за преде- пределами управляемого диапазона, ничего не делается. В противном случае находится и освобождается структура pv_entry. pmap_qenter() и ртар_qremove() являются более быстрыми версиями функций pmap_enter() и pmap_remove(), которые могут использоваться ядром для быстрого создания и удаления временных отображений. Они могут использоваться лишь для невыгружаемых отображений в адресном пространстве ядра. Например, процедуры управления буферным кешем используют эти процедуры для отображения страниц файлов в память ядра таким образом, что они могут читаться или записываться фай- файловой системой. Изменение для отображений атрибутов доступа и резервирования Важной ролью модуля ртар являются действия по управлению аппаратными правами доступа к страницам. Эти действия могут применяться ко всем отображениям, охваты- охватываемым диапазоном виртуальных адресов в ртар, посредством pmap_protect(), или они могут применяться ко всем отображениям определенной физической страницы через ртар посредством pmap_page_protect(). У обоих вызовов есть два общих свой- свойства. Во-первых, любой из них может быть вызван со значением права доступа
5.13. Переносимость 251 VMPROTNONE для удаления диапазона виртуальных адресов или для удаления всех отображений для определенной физической страницы. Во-вторых, эти процедуры никогда не должны добавлять право на запись для соответствующих отображений. Таким образом, вызовы, включающие VMPROTWRITE, не должны делать никаких изменений. Это ограничение необходимо для нормальной работы механизма копиро- копирования при записи. Запрос на право записи в страницу делается лишь в структуре vm_map_entjy. Когда позже процесс сделает попытку записи в страницу, возникнет отказ страницы. Обработчик отказа страницы исследует vm_map_entiy и определит, что запись должна быть разрешена. Если это страница с атрибутом копирования при записи, обработчик отказа сделает перед вызовом pmap_enter() любые необходимые для обеспечения записи на странице копии. Поэтому право записи на страницу добав- добавляется лишь через вызовы pmap_enter(). pmap_protect() используется главным образом системным вызовом mprotect для из- изменения прав доступа для области адресного пространства процесса. Стратегия сходна cpmap_remove(): просмотр в цикле всех виртуальных страниц в диапазоне и примене- применение изменений ко всем действительным найденным отображениям. Недействительные отображения пропускаются. Для PC pmap_protect() сначала проверяет особые случаи. Если запрошено право доступа VMPROTNONE, она вызывает pmap_remove(), чтобы отменить все права доступа. Если включено VMPROTWRITE, она просто сразу же возвращается. Для обычного значения права доступа pmap_remove() проходит в цикле данный диапазон адресов, пропуская недействительные отображения. Для действительных отображе- отображений отыскивается элемент таблицы страниц, и если новое значение права доступа отличается от текущего значения, элемент изменяется, a TLB и кеш сбрасываются. Как и в случае cpmap_remove(), любые глобальные действия с кешем отсрочиваются до тех пор, пока не будет модифицирован весь диапазон. pmap_page_protect() внутренне используется системой виртуальной памяти в двух целях. Она вызывается для установки права доступа только для чтения, когда устанав- устанавливается операция копирования при записи (например, в ходе fork). Она также удаляет все права доступа перед замещением страницы, чтобы заблокировать все ссылки на страницу до завершения операции. В Mach эта процедура используется в виде двух от- отдельных процедур - pmap_clear_ptes() и pmap_remove_all()- и многие модули ртар реализуют pmap_page_protect() в виде вызова одной или другой из этих функций в зави- зависимости от аргументов прав доступа. В реализации ртар_рage_protect() PC, если запрошен VM_PROT_WRITE, она воз- возвращается, ничего не делая. Добавление возможности записи должно быть сделано на постраничной основе процедурой обработки отказа страницы, как описано для ртар_protect(). В противном случае она обходит список структур pv_entry для этой страницы, отменяя отдельные отображения, как описано в предыдущем подразделе. Как и в случае cpmap_protect(), элемент проверяется, чтобы гарантировать его измене- изменение, до того как будут осуществлены дорогостоящие очистки TLB и кеша. Обратите
252 Глава 5. Управление памятью внимание, что очистка TLB и кеша отличаются по сравнению с pmap_remove(), поскольку они должны сделать недействительными элементы в контекстах нескольких процессов, а не несколько элементов в контексте одного процесса. pmap_change_wiring() вызывается для резервирования (wire) или снятия резервиро- резервирования (unwire) единственной машинно-независимой виртуальной страницы внутри ртар. Как описано в предыдущем подразделе, резервирование информирует модуль ртар о том, что отображение не должно вызвать аппаратный отказ, который достигнет машинно-независимого кода vm_fault(). Резервирование обычно является программ- программным атрибутом, который не влияет на аппаратное состояние MMU: оно просто сообща- сообщает ртар не отбрасывать состояние отображения. По существу, если модуль ртар нико- никогда не сбрасывает состояние, для модуля не является обязательным даже отслеживать состояние резервирования страниц. Единственным побочным эффектом неотслежи- неотслеживания информации о резервировании в ртар является то, что системный вызов mlock не может быть полностью реализован без статистики подсчета зарезервированных страниц. Реализация ртар PC поддерживает сведения о резервировании. Неиспользуемый бит в структуре элемента таблицы страниц записывает состояние резервирования страницы. pmap_change_wiring() устанавливает или сбрасывает этот бит, когда вызы- вызывается с действительным виртуальным адресом. Поскольку бит резервирования иг- игнорируется аппаратурой, нет необходимости в изменении TLB или кеша при измене- изменении этого бита. Управление информацией об использовании страницы Машинно-независимому коду управления страницами нужно иметь возможность по- получать от нижележащего оборудования основную информацию об использовании и из- изменениях страниц. Модуль ртар способствует сбору этой информации, не требуя от машинно-независимого кода понимания деталей таблиц отображения, предоставляя набор интерфейсов для запроса и сбрасывания битов ссылок и модификации. Демон выгрузки может вызвать vmjpageJestjdirtyQ для определения того, грязная ли страни- страница. Если страница грязная, демон выгрузки может записать ее в резервное хранилище, а затем вызвать ртар_clearjnodifyQ, чтобы сбросить бит модификации. Таким же образом, когда демон выгрузки сбрасывает или инактивирует страницу, он использует pmap_clear_reference() для сбрасывания бита ссылки на страницу. Когда он хочет обно- обновить счетчик активности страницы, он использует pmap_ts_referenced(), чтобы подсчи- подсчитать число использований страницы с момента последнего просмотра. Одной важной особенностью процедур запросов является то, что они должны воз- возвращать действительную информацию, даже если в настоящее время для нужной стра- страницы нет отображений. Таким образом, информация о ссылках и модификациях не
5.13. Переносимость 253 может быть просто собрана из аппаратно поддерживаемых битов различных элементов таблиц страниц или TLB; скорее, должно быть место, где информация сохраняется, когда отображение удаляется. Для PC информация о модификации для страницы хранится в поле dirty структуры vm_page. Первоначально сброшенная информация обновляется каждый раз, когда рас- рассматривается отображение страницы для удаления. Процедура vm_pageJestjdirtyQ сначала проверяет поле dirty и, если бит установлен, сразу возвращает TRUE. Посколь- Поскольку этот массив атрибутов содержит лишь прошлые сведения, ей по-прежнему нужно проверить состояние битов в элементах таблиц страниц для текущих отображений страницы. Эта информация проверяется путем вызова процедуры ртар _is jnodifiedQ, которая сразу же возвращает FALSE, если ей не передана управляемая ею физическая страница. В противном случае pmap_is_modified() обходит структуры pv_entry, связан- связанные с физической страницей, проверяя бит модификации для связанных с элементом таб- таблицы страниц pv_entiy. Она может вернуть TRUE, как только встретит установленный бит, или FALSE, если этот бит не установлен ни в одном элементе таблицы страниц. Сведения о ссылках на страницу хранятся в поле act_count и в качестве флага ее структуры vm_page. Первоначально сброшенная, эта информация периодически обновляется демоном выгрузки. Во время сканирования памяти демон выгрузки вызы- вызывает процедуру pmapjts_referenced() для сбора числа ссылок на страницу. Процедура pmap_ts_reference() возвращает ноль, если ей не передали управляемую физическую страницу. В противном случае она обходит структуры pv_entry, связанные с физиче- физической страницей, проверяя и сбрасывая бит ссылки для связанных с элементом таблицы страниц pv_entry. Она возвращает найденное количество битов ссылки. Процедуры очистки также сразу же возвращаются, если им не передана управляе- управляемая ими физическая страница. В противном случае в массиве атрибутов бит ссылки или модификации сбрасывается, и они обходят в цикле все структуры pv_entry, связан- связанные с физической страницей, сбрасывая поддерживаемые аппаратно биты элементов таблицы страниц. Этот последний шаг может вызывать очистку TLB или кеша по ходу дела или потом. Инициализация физических страниц Чтобы дать процедурам виртуальной памяти более высокого уровня возможность ини- инициализировать физическую память, предусмотрено два интерфейса. pmap_zero_page() принимает физический адрес и заполняет страницу нулями, ртар _сору_р age () прини- принимает два физических адреса и копирует содержимое первой страницы во вторую. По- Поскольку обе процедуры принимают физические адреса, модулю ртар скорее всего при- придется сначала отобразить эти страницы в адресное пространство ядра, прежде чем он сможет получить к ним доступ.
254 Глава 5. Управление памятью В реализации PC есть пара глобальных виртуальных адресов ядра, зарезервиро- зарезервированных для обнуления и копирования страниц. pmap_zero_page() отображает указан- указанный физический адрес в зарезервированный виртуальный адрес, вызывает bzeroQ, чтобы очистить страницу, а затем удаляет временное отображение с помощью единст- единственного примитива отмены трансляции, используемой pmap_remove(). Таким же обра- образом ртар_сору_р age () создает отображения для обоих физических адресов, использует Ъсору() для копирования, а затем удаляет оба отображения. Управление внутренними структурами данных Оставшиеся процедуры интерфейса ртар используются для управления и синхрониза- синхронизации внутренних структур данных, ртар_pinit() создает экземпляр машинно-зависимой структуры ртар. Она используется процедурами vmspace J'orkQ и vmspace_exec() при создании новых адресных пространств в ходе fork или exec, ртар _release() освобожда- освобождает ресурсы ртар. Она используется процедурой vmspace_free() при очистке vmspace после завершения процесса. Упражнения 5.1. Что означает для машины поддержка виртуальной памяти? Какие четыре аппаратных средства обычно требуются машине для поддержки виртуальной памяти? 5.2. Каковы взаимоотношения между страничной подкачкой (paging) и подкачкой процессов (swapping) в системе виртуальной памяти с подкачкой страниц по требованию? Объясните, желательно ли обеспечивать оба механизма на одной и той же системе. Можете вы предложить альтернативу предостав- предоставлению обоих механизмов? 5.3. Какими тремя политиками характеризуются системы страничной подкачки? 5.4. Что такое копирование при записи! В большинстве приложений UNIX за сис- системным вызовом fork почти сразу же следует системный вызов exec. Почему эта особенность делает более привлекательным использование копирования при записи в реализации/br/:? 5.5. Объясните, почему системный вызов vfork всегда будет более эффективным, чем умелая реализация системного вызова fork. 5.6. Когда процесс завершается, все его страницы не могут быть сразу же поме- помещены в список свободной памяти. Объясните это. 5.7. Почему в ядре есть как традиционный интерфейс mallocQ и free(), так и зональный распределитель? Объясните, когда полезен каждый из этих интерфейсов. 5.8. В чем назначение раскраски страниц? На какого рода аппаратном обеспечении она необходима?
Упражнения 255 5.9. Какой цели служит процесс демона выгрузки страниц в системе виртуальной памяти? 5.10. Что такое кластеризация? Где она используется в системе виртуальной памяти? 5.11. Почему историческое использование липкого бита (sticky bit) для блокировки образа процесса в памяти не является больше полезным в FreeBSD? 5.12. Назовите две причины для начала подкачки процесса. *5.13 В системе виртуальной памяти 4.3BSD был кеш кода, в котором сохранялась идентичность кодовых страниц от одного выполнения программы до другого. Как кеширование объектов vnode в FreeBSD повышает производительность KemaKona4.3BSD? **5.14 FreeBSD снижает длину теневых цепочек, проверяя при каждом отказе копирования при записи, не перекрывает ли полностью вызвавший отказ объ- объект нижележащий объект в цепочке. Если да, может быть сделано сокраще- сокращение. Одной из альтернатив могло бы быть вычисление числа действующих ссылок на страницу после каждого отказа копирования при записи. При этом, если остается лишь одна ссылка, можно переместить эту страницу в объект, который на нее ссылается. Когда удаляется последняя страница, цепочку можно свернуть. Реализуйте этот алгоритм и сравните его затраты с текущим алгоритмом. **5.15. Структуры pv_entry можно было бы заменить, сохранив список, связанный с каждым объектом всех структур vm_map_entry, которые ссылаются на него. Если бы каждая структура vm_map_entiy содержала в себе только один указа- указатель на список, лишь последний объект смог бы на него ссылаться. Теневым объектам пришлось бы находить свой последний объект, чтобы найти свою обращающуюся структуру vm_map_entry. Реализуйте алгоритм для нахожде- нахождения всех ссылок на страницы теневого объекта, используя эту схему. Сравните его стоимость со стоимостью текущего алгоритма, использующего структуры pv_entiy. **5.16. Перенесите код из 4.3BSD, который принудительно выгружал бы работоспо- работоспособные процессы, когда частота страничной подкачки становится слишком большой. Запустите три или более процессов, у каждого из которых рабочий набор в 40 процентов доступной памяти. Сравните производительность этой контрольной задачи с использованием алгоритма 4.3BSD и текущего алгоритма.
256 Глава 5. Управление памятью Ссылки Babaoglu & Joy, 1981. О. Babao gglu & W. N. Joy, "Converting a Swap-Based System to Do Paging in an Architecture Lacking Page-Referenced Bits", Proceedings of the Eighth Symposium on Operating Systems Principles, pp. 78-86, December 1981. Bansal & Modha, 2004. S. Bansal & D. Modha, "CAR: Clock with Adaptive Replacement", Proceedings of the Third Usenix Conference on File and Storage Technologies, pp. 187-200, April 2004. Belady, 1966. L. A. Belady, "A Study of Replacement Algorithms for Virtual Storage Systems", IBM Systems Journal, vol. 5, no. 2, pp. 78-101, 1966. Bonwick, 1994. J. Bonwick, "The Slab Allocator: An Object-Caching Kernel Memory Allocator", Pro- Proceedings of the 1994 Usenix Annual Technical Conference, pp. 87-98, June 1994. Bonwick & Adams, 2001. J. Bonwick & J. Adams, "Magazines and Vmem: Extending the Slab Allocator to Many CPUs and Arbitrary Resources", Proceedings of the 2001 Usenix Annual Technical Con- Conference, pp. 15-34, June 2001. Coffman & Denning, 1973. E. G. Coffman, Jr. & P. J. Denning, Operating Systems Theory, p. 243, Prentice-Hall, Englewood Cliffs, NJ, 1973. Denning, 1970. P. J. Denning, "Virtual Memory", Computer Surveys, vol. 2, no. 3, pp. 153-190, Sep- September 1970. Easton & Franaszek, 1979. M. C. Easton & P. A. Franaszek, "Use Bit Scanning in Replacement Decisions", IEEE Transactions on Computing, vol. 28, no. 2, pp. 133-141, February 1979. Gingell et al., 1987. R. Gingell, M. Lee, X. Dang, & M. Weeks, "Shared Libraries in SunOS", USENIX Association Conference Proceedings, pp. 131-146, June 1987.
Ссылки 257 Gingell, Moran, & Shannon, 1987. R. Gingell, J. Moran, & W. Shannon, "Virtual Memory Architecture in SunOS", USENIXAssociation Conference Proceedings; pp. 81-94, June 1987. Intel, 1984. Intel, "Introduction to the iAPX 286", Order Number 210308, Intel Corporation, Santa Clara, CA, 1984. Kenah&Bate, 1984. L. J. Kenah & S. F. Bate, VAX/VMS Internals and Data Structures, Digital Press, Bedford, MA, 1984. King, 1971. W. F. King, "Analysis of Demand Paging Algorithms", IFIP, pp. 485^90, North Holland, Amsterdam, 1971. Korn&Vo, 1985. D. Korn & K. Vo, "In Search of a Better Malloc", USENIX Association Conference Proceedings, pp. 489-506, June 1985. Lazowska & Kelsey, 1978. E. D. Lazowska & J. M. Kelsey, "Notes on Tuning VAX/VMS.", Technical Report 78-12-01, Department of Computer Science, University of Washington, Seattle, WA, December 1978. Marshall, 1979. W. T. Marshall, "A Unified Approach to the Evaluation of a Class of'Working Set Like' Replacement Algorithms", PhD Thesis, Department of Computer Engineering, Case Western Reserve University, Cleveland, OH, May 1979. McKusick & Karels, 1988. M. K. McKusick & M. Karels, "Design of a General Purpose Memory Allocator for the 4.3BSD UNIX Kernel", USENIX Association Conference Proceedings, pp. 295-304, June 1988. Organick, 1975. E. I. Organick, The Multics System: An Examination of Its Structure, MIT Press, Cambridge, MA, 1975.
258 Глава 5. Управление памятью Tevanian, 1987. A. Tevanian, "Architecture-Independent Virtual Memory Management for Parallel and Distributed Environments: The Mach Approach", Technical Report CMU-CS-88-106, Department of Computer Science, Carnegie-Mellon University, Pittsburgh, PA, Decem- December 1987. Young, 1989. M. W. Young, Exporting a User Interface to Memory Management from a Communica- Communication-Oriented Operating System, CMU-CS-89-202, Department of Computer Science, Carnegie-Mellon University, November 1989'.
Часть III Система ввода/вывода
Глава 6 Обзор системы ввода/вывода 6.1. Отображение ввода/вывода от пользователя на устройство Компьютеры сохраняют и получают данные через поддерживаемые периферийные устройства ввода/вывода. К этим устройствам обычно относятся запоминающие устройства большой емкости, такие, как дисковые приводы, архивные запоминающие устройства и сетевые интерфейсы. Доступ к таким запоминающим устройствам, как диски, осуществляется через контроллеры ввода/вывода, которые управляют работой подключенных к ним устройств в соответствии с запросами ввода/вывода от централь- центрального процессора. Множество особенностей аппаратных устройств скрыто от пользователя такими высокоуровневыми средствами ядра, как файловые системы и интерфейсы сокетов. Другие такие же особенности скрыты от большей части самого ядра системой ввода/ вывода. Система ввода/вывода состоит из буферирующе-кеширующих систем, общего кода драйверов устройств и драйверов для определенных аппаратных устройств, ко- которые должны в конечном счете обращаться к особенностям этих самых устройств. Обзор всего ядра показан на рис. 6.1. Нижняя треть рисунка содержит различные сис- системы ввода/вывода. В FreeBSD имеется три главные разновидности ввода/вывода: интерфейс символь- символьных устройству файловая система и интерфейс сокетов с относящимися к нему сете- сетевыми устройствами. Символьный интерфейс появляется в пространстве имен файло- файловой системы и предоставляет неструктурированный доступ к нижележащему обору- оборудованию. Сетевые устройства не появляются в файловой системе; доступ к ним осуще- осуществляется лишь через интерфейс сокетов. Символьные устройства описаны в разделе 6.2. Файловая система описана в главе 8. Сокеты описаны в главе 11.
262 Глава 6. Обзор системы ввода/вывода Интерфейс системных вызовов ядра Активные элементы файлов Уровень VNODE Специальные устройства tty Дисциплина линии связи Непосред- ственные устрой- устройства Драйверы символьных устройств Непосред- Непосредственный диск Активные элементы файлов Уровень ОБЪЕКТОВ / VNODE VM Управле- Управление прост- пространством подкачки Локальные имена (UFS) FFS NFS Сокет Кеш страниц Сетевые протоколы GEOM-уровень САМ-уровень САМ-драйверы устройств АТА-уровень АТА-драйверы устройств Драйверы сетевого интерфейса Новая шина Аппаратное обеспечение Рис. 6.1. Структура ввода/вывода ядра Интерфейс символьных устройств используется в двух стилях, которые зависят от характеристик нижележащего аппаратного устройства. Для некоторых символьных аппаратных устройств, таких, как мультиплексоры терминалов, интерфейс на самом деле является символьно-ориентированным, хотя программное обеспечение более высокого уровня, такое, как драйвер терминала, может предоставить приложениям строчно-ори- ентированный интерфейс. Однако для блочно-ориентированных устройств, таких, как диски, интерфейс символьных устройств является неструктурированным или непо- непосредственным (raw). Для этого интерфейса операции ввода/вывода не проходят через файловую систему или кеш страниц; вместо этого они осуществляются непосредствен- непосредственно между устройством и буферами в виртуальном адресном пространстве приложения. Поэтому размер операций должен быть кратным лежащему в основе размеру блока, требуемому устройством, а на некоторых машинах буфер ввода/вывода приложения должен быть выровнен по подходящей границе. Доступ к устройствам ввода/вывода, внутренним для системы, осуществляется через набор точек входа, предоставляемых для каждого устройства драйвером устрой- устройства. Для интерфейса символьных устройств он получает доступ к структуре cdevsw. Структура cdevsw создается для каждого устройства, когда оно конфигурируется либо во время загрузки системы, либо позже, когда устройство подключается к системе. Устройства идентифицируются по номеру устройства, который составляется из старшего (major) и младшего (minor) номеров устройства. Старший номер устройства уникально идентифицирует тип устройства (на самом деле, драйвера устройства).
6.1. Отображение ввода/вывода от пользователя на устройство 263 Исторически он использовался в качестве индекса элемента устройства в таблице сим- символьных устройств. В FreeBSD 5.2 нет таблицы символьных устройств. Когда устрой- устройства конфигурируются, в файловой системе /dev создаются элементы для устройства. Каждый элемент в файловой системе /dev имеет прямую ссылку на его соответст- соответствующий элемент cdevsw. FreeBSD 5.2 назначает каждому устройству при его конфи- конфигурировании уникальный старший номер устройства, чтобы обеспечить совмести- совместимость для приложений, которые его проверяют. Но внутри ядра или драйвера устрой- устройства он не используется. Младший номер устройства выбирается и интерпретируется исключительно драй- драйвером устройства и используется драйвером для определения того, к которому из несколь- нескольких аппаратных устройств относится запрос ввода/вывода. Например, для дисков младшие номера устройства определяют конкретный контроллер, дисковый привод и раздел. Младший номер устройства может также определять раздел устройства - напри- например, канал в мультиплексированном устройстве или параметры обработки опций. Драйверы устройств Драйвер устройства разделен на три главные секции. 1. Процедуры автоконфигурирования и инициализации. 2. Процедуры для обслуживания запросов ввода/вывода (верхняя половина). 3. Процедуры обслуживания прерываний (нижняя половина). Блок автоконфигурирования драйвера отвечает за зондирование аппаратного устройства на предмет его наличия и за инициализацию устройства и состояния про- программного обеспечения, которое требуется драйверу устройства. Эта часть драйвера обычно вызывается лишь однажды - либо при инициализации системы, либо, для вре- временных объектов, при их подключении к системе. Автоконфигурирование описано в разделе 14.4. Секция драйвера, обслуживающая запросы ввода/вывода, вызывается как резуль- результат системных вызовов или системой виртуальной памяти. Эта часть драйвера устрой- устройства выполняется синхронно в верхней половине ядра и может блокироваться путем вызова процедуры sleepQ. Мы обычно ссылаемся на этот массив кода как на верхнюю половину драйвера устройства. Процедуры обслуживания прерываний вызываются, когда система получает прерывание от устройства. Поэтому эти процедуры не могут зависеть от состояния какого-либо процесса. Исторически у них не было собственного контекста потока, поэтому они не могли блокироваться. В FreeBSD 5.2 у прерывания есть свой собствен- собственный контекст потока, поэтому оно может блокироваться, если это ему надо. Однако стоимость дополнительных переключений контекстов потоков достаточно высока, так что драйверы устройств должны пытаться избегать блокирования ради высокой произ-
264 Глава 6. Обзор системы ввода/вывода водительности. Обычно мы ссылаемся на процедуры обслуживания прерываний драй- драйвера устройства как на нижнюю половину драйвера устройства. Кроме этих трех секций драйвера устройства может быть предусмотрена необяза- необязательная процедура аварийного дампа. Эта процедура, если она присутствует, вызыва- вызывается, когда система распознает неисправимую ошибку и хочет записать содержимое физической памяти для использования в последующем анализе. Большинство драй- драйверов устройств для дисковых контроллеров предусматривают процедуру аварийного дампа. Использование процедуры аварийного дампа описано в разделе 14.6. Очередь ввода/вывода Драйверы устройств при своей нормальной работе обычно управляют одной или более очередями для запросов ввода/вывода. Когда верхняя половина драйвера получает запрос ввода или вывода, он записывается в структуру данных, которая помещается в очередь соответствующего устройства для обработки. Когда операция ввода или вывода завершается, драйвер устройства получает от контроллера прерывание. Проце- Процедура обслуживания прерывания удаляет соответствующий запрос из очереди устройства, уведомляет запрашивающего, что команда выполнена, а затем начинает обработку сле- следующего запроса в очереди. Очереди ввода/вывода являются основными средствами коммуникации между верхней и нижней половиной драйвера устройства. Поскольку очереди ввода/вывода разделяются среди асинхронных процедур, доступ к очередям должен быть синхронизирован. Процедуры как в верхней, так и в нижней половинах драйвера устройства должны запрашивать мьютекс, связанный с очередью, до манипулирования ею, чтобы избежать повреждений из-за одновремен- одновременных изменений (мьютексы были описаны в разделе 4.3). Например, прерывание нижней половины может пытаться удалить элемент, который не был еще полностью связан верхней половиной. Синхронизация между несколькими процессами, начи- начинающими запросы ввода/вывода, также сериализуется посредством мьютекса, связан- связанного с очередью. Обработка прерываний Прерывания генерируются устройствами для сигнализации о том, что операция завершена, или о том, что возникло изменение в состоянии. По получении прерывания от устрой- устройства система планирует процедуру обслуживания прерывания соответствующего драйвера устройства с одним или несколькими параметрами, которые уникально иден- идентифицируют устройство, требующее обслуживания. Эти параметры необходимы, поскольку драйверы устройств обычно поддерживают несколько устройств одного и того же типа. Если бы идентификатор прерывающего устройства не передавалася с каждым прерыванием, драйвер был бы вынужден опрашивать все потенциальные устройства, чтобы выяснить, какое из них вызвало прерывание.
6.2. Символьные устройства 265 Система организует передачу номера устройства процедуре обслуживания преры- прерывания каждого устройства путем установки в таблице векторов прерываний адреса дополнительной связующей процедуры. Для обслуживания прерывания вызывается эта связующая процедура, а не действительная процедура обслуживания прерывания; она выполняет следующие действия'. 1. Собирает нужные параметры аппаратного обеспечения и помещает их в место, заре- зарезервированное для них устройством. 2. Обновляет статистику по прерываниям устройства. 3. Планирует поток обслуживания прерывания для устройства. 4. Сбрасывает флаг ожидающего прерывания в аппаратном устройстве. 5. Возвращается из прерывания. Поскольку связующая процедура вставлена между таблицей векторов прерываний и процедурой обслуживания прерывания, инструкции специального назначения, которые невозможно генерировать из С и которые необходимы аппаратуре для под- поддержки прерываний, можно оставить вне драйвера устройства. Это посредничество связующей процедуры дает возможность писать драйверы устройств без использова- использования языка ассемблера. 6.2. Символьные устройства Почти вся периферия системы, за исключением сетевых интерфейсов, имеет интерфейс символьных устройств. Символьное устройство обычно отображает аппаратный ин- интерфейс в поток байтов, сходный с потоком байтов файловой системы. Символьные устройства данного типа включают терминалы (например, /dev/ttyOO), построчно-печа- построчно-печатающие устройства (например, /dev/lpO), интерфейс физической оперативной памяти (/dev/mem) и бездонный приемник для данных и бесконечный источник признаков конца файла (/dev/null). Некоторые из этих символьных устройств, такие, как устройства терминалов, могут демонстрировать особое поведение на границах строк, но в общем по-прежнему рассматриваются как потоки байтов. Устройства, эмулирующие терминалы, используют буферы, которые меньше тех, которые используются для дисков. Эта система буферирования включает небольшие (обычно 64-байтные) блоки символов, хранящихся в связанных списках. Хотя все сво- свободные символьные буферы хранятся в одном свободном списке, большинство драй- драйверов устройств, которые их используют, ограничивают число символов, которые можно единовременно поместить в очередь для одного порта терминала.
266 Глава 6. Обзор системы ввода/вывода Такие устройства, как высокоскоростные графические интерфейсы, могут иметь свои собственные буферы или могут всегда осуществлять непосредственный ввод/ вывод в адресное пространство пользователя; они так же классифицируются, как сим- символьные устройства. Некоторые из этих драйверов распознают особые типы записей и поэтому могут быть дальше от простой модели потока байтов. Символьный интерфейс для дисков называется также интерфейсом непосредст- непосредственного доступа (raw device interface)', он предоставляет неструктурированный ин- интерфейс к устройству. Его основной задачей является организация непосредственного ввода/вывода к и от устройства. Драйвер устройства управляет асинхронной природой ввода/вывода, поддерживая и упорядочивая активную очередь ожидающих передач. Каждый элемент в очереди определяет, для чтения она или для записи, адрес памяти для пересылки, адрес устройства для пересылки (обычно номер дискового сектора) и размер пересылаемых данных (в байтах). Все прочие ограничения нижележащего оборудования передаются через сим- символьный интерфейс своим клиентам, делая интерфейс символьных устройств самым отдаленным от модели потока байтов. Таким образом, процесс пользователя должен претерпеть ограничения разделения на секторы, налагаемые нижележащим обору- оборудованием. Для магнитных дисков файловое смещение и размер передачи должны быть кратными размеру сектора. Символьный интерфейс не копирует данные поль- пользователя в буфер ядра до помещения их в очередь ввода/вывода. Вместо этого он организует непосредственное выполнение ввода/вывода в или из адресного про- пространства процесса. Размер и выравнивание передачи ограничиваются физическим устройством. Однако размер передачи не ограничен максимальным размером внутренних буферов системы, поскольку эти буферы не используются. Символьный интерфейс обычно используется лишь теми вспомогательными сис- системными программами, которые обладают внутренним знанием структур данных на диске. Символьный интерфейс дает также возможность создавать прототипы на уровне пользователя; например, реализация файловой системы 4.2BSD была напи- написана и почти полностью протестирована в виде процесса пользователя, использующе- использующего непосредственный дисковый интерфейс, до того как код был перемещен в ядро. Символьные устройства описываются элементами в структуре cdevsw. Элементы этой структуры (см. табл. 6.1) используются для поддержки непосредственного досту- доступа к блочно-ориентированным устройствам, таким, как диск, а также для нормального доступа к символьно-ориентированным устройствам через драйвер терминала. Непо- Непосредственные устройства (raw devices) поддерживают подмножество точек входа, которые соответствуют точкам входа в блочно-ориентированных устройствах. В данном разделе описывается основной набор точек входа для всех драйверов устройств; дополнительный набор точек входа для блочно-ориентированных устройств приведен в разделе 6.3.
6.2. Символьные устройства 267 Табл. 6.1. Точки входа для драйверов символьных и непосредственных устройств Точка входа Функция ореп() Открытие устройства closeQ Закрытие устройства readQ Выполнение операции ввода writeQ Выполнение операции вывода ioctlQ Выполнение операции управления вводом/выводом pollQ Опрос устройства о готовности к вводу/выводу stop() Остановка вывода на устройство ттар() Отображение смещения устройства в ячейку памяти reset() Восстановление устройства после сброса шины Непосредственные устройства и физический ввод/вывод Большинство непосредственных устройств отличаются от файловых систем лишь способом выполнения ввода/вывода. В то время как файловые системы читают и за- записывают данные в и из буферов ядра, непосредственные устройства передают данные в и из буферов пользователя. Обход буферов ядра устраняет необходимость копирования одних участков памяти в другие, что должны делать файловые системы, но лишает приложений также и преимуществ кеширования данных. Кроме того, для устройств, поддерживающих как непосредственный доступ, так и доступ файловой системы, приложения должны позаботиться о сохранении согласованности данных в буферах ядра и данных, записанных непосредственно в устройство. Непосредствен- Непосредственное устройство должно использоваться лишь тогда, когда файловая система демон- демонтирована или монтирована с правом только для чтения. Прямой доступ используется многими утилитами файловой системы, такими, как программа проверки файловой системы, fsck, и программами, которые читают и записывают на резервные носители (например, tar, dump и restore). Поскольку непосредственные устройства обходят буферы ядра, они отвечают за управление собственными структурами буферов. Большинство устройств для описа- описания своего ввода/вывода занимают буферы подкачки. Процедуры чтения и записи используют процедуру physioQ, чтобы начать операцию непосредственного ввода/ вывода (см. рис. 6.2). Параметр strategy идентифицирует процедуру стратегии блочного устройства, которое начинает операцию ввода/вывода на устройство. Буфер использу- используется physioQ при конструировании запроса(-ов), делаемых процедуре стратегии. Устройство, флаг чтения-записи и параметры то полностью определяют операцию ввода/вывода, которая должна быть сделана. Процедурой physioQ проверяется макси- максимальный размер передачи для устройства, для того чтобы настроить размер каждой пересылки ввода/вывода, до того как последняя будет передана процедуре стратегии.
268 Глава 6. Обзор системы ввода/вывода Эта проверка позволяет осуществить пересылку в блоках соответственно максимальному размеру передач, поддерживаемых устройством. void physio( device dev, struct uio *uio, int ioflag); { allocate a swap buffer; while (uio is not exhausted) { mark the buffer busy for physical I/O; set up the buffer for a maximum-sized transfer; use device maximum I/O size to bound the transfer size; check user read/write access at uio location; lock the part of the user address space involved in the transfer into RAM; map the user pages into the buffer; call dev->strategy() to start the transfer; wait for the transfer to complete; unmap the user pages from the buffer; unlock the part of the address space previously locked; deduct the transfer size from the total number of data to transfer; } free swap buffer; } РИС. 6.2. Алгоритм для физического ввода/вывода Операции непосредственного ввода/вывода требуют от аппаратного устройства переноса данных непосредственно в или из буфера данных в адресном пространстве программы пользователя, описанном параметром uio. Таким образом, в отличие от операций ввода/вывода, которые осуществляют непосредственный доступ к памяти (DMA) из буферов в адресном пространстве ядра, непосредственные операции ввода/ вывода должны проверить, что буфер пользователя доступен устройству, и заблокиро- заблокировать его в памяти на время выполнения переноса данных. Символьно-ориентированные устройства Символьно-ориентированные устройства олицетворяются портами терминалов, хотя они включают также принтеры и другие символьно- или построчно-ориентированные устройства. Доступ к этим устройствам обычно осуществляется через драйвер терми- терминала, описанный в главе 10. Тесная связь с драйвером терминала сильно повлияла на структуру драйверов символьных устройств. Например, в структуре cdevsw существует несколько точек входа для взаимодействия между общим обработчиком терминала и аппаратными драйверами мультиплексора терминала.
6.2. Символьные устройства 269 Точки входа для драйверов символьных устройств Драйвер устройства для символьного устройства определяется его элементами в структуре cdevsw. open Открывает устройство при подготовке к операциям ввода/вывода. Точка входа open устройства будет вызываться для каждого системного вызова open для файла специального устройства или внутренне, когда устройство готовится для монтирования файловой системы с помощью системного вызова mount. Процедура ореп() обычно проверяет целостность соответст- соответствующего устройства. Например, она проверит, что устройство было иден- идентифицировано во время фазы автоконфигурирования (для дисковых приво- приводов - что носитель присутствует и готов к приему команд). close Закрывает устройство. Процедура closeQ вызывается после завершения по- последнего клиента, заинтересованного в использовании устройства. Эта семантика определена средствами ввода/вывода более высокого уровня. Дисковые устройства ничего не должны делать, когда устройство закрыва- закрывается, поэтому они используют пустую процедуру closeQ. Устройства, под- поддерживающие доступ лишь одного клиента, должны пометить устройство как снова доступное. read Прочесть данные с устройства. Для непосредственных устройств эта точка входа обычно просто вызывает процедуру physio() со специфическими для устройства параметрами. Для ориентированных на терминал устройств запрос чтения немедленно передается драйверу терминала. Для других устройств запрос чтения требует, чтобы указанные данные были скопиро- скопированы в адресное пространство ядра, обычно посредством процедуры uio- moveQ (см. конец раздела 6.4), а затем переданы устройству. write Записать данные в устройство. Эта точка входа является прямым аналогом точки входа чтения: непосредственные устройства используют physioQ, ориентированные на терминал устройства вызывают для выполнения этой операции драйвер терминала, а другие устройства обрабатывают запрос внутренне. ioctl Выполнение операции, отличной от чтения или записи. Эта точка входа первоначально предоставляла механизм для получения и установки пара- параметров устройства для устройств терминалов; ее использование было распространено также и на другие виды устройств. Исторически операции ioctlQ широко варьировались от устройства к устройству.
270 Глава 6. Обзор системы ввода/вывода poll Проверить, готовы ли в устройстве данные для чтения, или доступно ли пространство для записи данных. Точка входа poll используется системны- системными вызовами select и poll при проверке дескрипторов файлов, связанных со специальными файлами устройств. Для непосредственных устройств операция poll бессмысленна, поскольку данные не буферируются. Здесь в качестве точки входа устанавливается seltrue(), процедура, которая воз- возвращает true в ответ на любой запрос poll. Для устройств, использующихся с драйвером терминала, в качестве этой точки входа устанавливается ttselectQ, процедура, описанная в главе 10. ттар Отображает смещение устройства в адрес памяти. Эта точка входа вызыва- вызывается системой виртуальной памяти для преобразования логического отображения в физический адрес. Например, она преобразует смещение в /dev/mem в адрес ядра. kqfilter Добавляет устройство в список событий ядра для вызывающего потока. 6.3. Дисковые устройства Дисковые устройства выполняют основную роль в ядре UNIX и поэтому имеют допол- дополнительные особенности и возможности, выходящие за рамки типичного драйвера сим- символьного устройства. Изначально UNIX предоставлял дискам два интерфейса. Первым был интерфейс символьного устройства, который предоставлял прямой доступ к диску в его непосредственном виде. Этот интерфейс по-прежнему доступен в FreeBSD 5.2 и описан в разделе 6.2. Вторым был интерфейс блочных устройств, который преобра- преобразовывал из пользовательской абстракции диска как массива байтов в структуру, нала- налагаемую нижележащим физическим носителем. Доступ к блочным устройствам был возможен через соответствующие специальные файлы устройств. Блочные устройства были удалены из FreeBSD 5.2, поскольку они не были нужны каким-либо из обычных приложений и значительно усложняли ядро. Точки входа для драйверов дисковых устройств Драйверы устройств для дисков содержат все обычные точки входа символьных устройств, описанные в разделе 6.2. Кроме них имеются три точки входа, которые исполь- используются только для дисковых устройств.
6.3. Дисковые устройства 271 strategy Начинает операцию чтения или записи и немедленно возвращается. Запросы ввода/вывода к или от файловых систем, размещенных на устройстве, транслируются системой в вызовы процедур блочного ввода/ вывода breadQ и bwriteQ. Эти процедуры блочного ввода/вывода, в свою очередь, вызывают процедуру стратегии устройства, чтобы прочесть или записать данные, не находящиеся в кеше памяти. Каждый вызов проце- процедуры стратегии содержит указатель на структуру buf, содержащую пара- параметры для запроса ввода/вывода. Если запрос синхронный, вызывающий должен ждать (на адресе структуры buf) до завершения ввода/вывода. dump Записать в устройство всю физическую память. Точка входа dump сохра- сохраняет содержимое памяти во вторичном хранилище. Система автоматиче- автоматически выполняет дамп, когда обнаруживает неисправимую ошибку и готова к аварийному останову (crash). Дамп используется в последующем ана- анализе проблемы, которая вызвала сбой системы. Процедура dump вызыва- вызывается с переключением контекста и с запретом прерываний; таким обра- образом, драйвер устройства должен опрашивать состояние устройства, а не ждать прерываний. Предполагается, что по крайней мере одно дисковое устройство поддерживает эту точку входа. Сортировка запросов дискового ввода/вывода Ядро предоставляет общую процедуру disksortQ ^ которая может использоваться всеми драйверами дисковых устройств для сортировки запросов ввода/вывода в очереди запросов устройства, используя лифтовый алгоритм сортировки. Этот алгоритм сортирует запросы в циклическом восходящем блочном порядке таким образом, что запросы можно обслужить с минимальным однонаправленным сканированием диска. Это упорядочение было первоначально спроектировано для поддержки обычных упреж- упреждающих запросов файловой системы, а также для противодействия случайному разме- размещению данных файловой системы на диске. С использованием усовершенствованных алгоритмов размещения в современных файловых системах эффект процедуры disksortQ менее заметен; disksortQ дает наибольший эффект, когда имеются несколько одновременно действующих пользователей диска. Алгоритм disksortQ приведен на рис. 6.3. Очередь запросов диска составлена из двух списков запросов, упорядоченных по номерам блоков. Первый является актив- активным списком; второй является списком следующего прохода. Запрос в начале активно- активного списка показывает текущее положение диска. Если список следующего прохода не пуст, он составлен из запросов, которые находятся до текущей позиции. Каждый новый запрос сортируется либо в активном списке, либо в списке следующего прохода в соответствии с местом запроса. Когда головки достигают конца активного списка, активным становится список следующего прохода, создается пустой список сле- следующего прохода, а диск начинает обслуживать новый активный список.
272 Глава 6. Обзор системы ввода/вывода void disksort( drive queue *dq, buffer *bp); { if (active list is empty) { place the buffer at the front of the active list; return; } if (request lies before the first active request) { locate the beginning of the next-pass list; sort bp into the next-pass list; } else sort bp into the active list; } PliC. 6.3. Алгоритм для disksortQ Сортировка диска может также быть важной на машинах, у которых быстрый про- процессор, но которые не сортируют запросы в драйвере устройства. Здесь, если запись нескольких мегабайтов осуществляется в порядке очереди, она может заблокировать доступ к диску со стороны других процессов до завершения этой операции. Запросы сортировки предусматривают некоторое планирование, которое более справедливо распределяет доступ к дисковому контроллеру. Наиболее современные дисковые контроллеры принимают несколько одновремен- одновременных запросов ввода/вывода. Затем контроллер сортирует эти запросы для минимиза- минимизации времени, необходимого для их обслуживания. Если бы контроллер мог всегда управлять всеми ожидающими выполнения запросами ввода/вывода, ядру не нужно было делать какую бы то ни было сортировку. Однако большинство контроллеров могут обработать лишь около 15 ожидающих запросов. Поскольку занятая система легко может создавать вспышки активности, превышающие число запросов, которые дисковый контроллер может обработать одновременно, дисковая сортировка ядром по-прежнему необходима. Метки дисков Диск может быть разделен на несколько разделов, каждый из которых может использо- использоваться для отдельной файловой системы или области подкачки. Метка диска содержит информацию о расположении и использовании разделов, включая сведения о типе файловой системы, разделе подкачки или о том, что раздел не используется. Для быстрой файловой системы использование разделов содержит достаточно дополни- дополнительной информации, чтобы дать программе проверки файловой системы (fsck) воз- возможность находить альтернативные суперблоки для файловой системы. Метка диска содержит также любую другую специфичную для драйвера информацию. Наличие меток на каждом диске означает, что сведения о разделах могут отличаться для каждого диска и что они переносятся вместе с диском, когда последний перемещается
6.3. Дисковые устройства 273 из одной системы в другую. Это также означает, что когда к системе подключаются ранее неизвестные типы дисков, системный администратор может их использовать, не меняя драйвер диска, не перекомпилируя и не перезагружая систему. Метка расположена рядом с началом каждого диска — обычно в нулевом блоке. Она должна располагаться вначале диска, чтобы позволить ее использовать при первоначаль- первоначальной загрузке. На многих архитектурах код начальной аппаратной загрузки (или началь- начальной загрузки первого уровня) хранится в постоянной памяти (ROM). Когда машина включается в сеть или когда нажимают кнопку перезагрузки (reset), процессор выполняет код начальной аппаратной загрузки из ROM. Код начальной аппаратной загрузки обычно считывает в оперативную память несколько первых секторов диска, затем передает управление по первому считанному адресу. Программа, хранящаяся в этих нескольких первых секторах, является начальным загрузчиком второго уровня. Сохранение метки диска в той части диска, которая прочитывается в ходе начальной аппаратной загрузки, дает начальному загрузчику второго уровня возможность получить информацию о метке диска. Эти сведения дают ему возможность найти корневую файловую систему и, следо- следовательно, файлы, такие, как ядро, необходимые для загрузки FreeBSD. Размер и располо- расположение начального загрузчика второго уровня зависит от требований кода начального аппаратного загрузчика. Поскольку нет стандарта для форматов меток дисков и поскольку код начальной аппаратной загрузки обычно понимает лишь метки разработчика оборудо- оборудования, обычно необходимо поддерживать метки дисков как производителя оборудова- оборудования, так и FreeBSD. В этом случае метка производителя должна быть помещена в том месте, где ее ожидает код начальной аппаратной загрузки ROM; метка FreeBSD должна быть помещена в стороне от метки производителя, но в пределах области, которая считы- вается кодом начальной аппаратной загрузки таким образом, чтобы она была доступна загрузчику второго уровня. Например, на архитектуре PC BIOS ожидает, что сектор 0 диска содержит код загрузки, таблицу слайсов (slicesI и магическое число. Слайсы можно использовать для разделения диска на несколько частей. BIOS загружает сектор 0 и проверяет магическое число. Затем код загрузки сектора 0 отыскивает таблицу слайсов, чтобы определить, какой из слайсов помечен как активный. Этот код загрузки вносит затем из активного слайса специфичный для операционной системы загрузчик и, если он помечен как загружаемый, запускает его. Этот специфичный для операционной системы загрузчик включает описанную выше метку диска и код для ее интерпретации. К сожалению, во FreeBSD принята своя терминология, касающаяся разбиения диска, не соответствующая общепринятой для PC. Разделом (partition) в FreeBSD называется раздел с файловой системой или областью подкачки (swap partition), а то, что принято называть разделом, и то, что существует на уровне BIOS, называется слайсом (slice - «часть», «ломоть»). Один раздел в смысле BIOS в точности соответствует одному слайсу. Таким образом, с точки зрения BIOS диск содержит несколько разделов, а с точки зрения FreeBSD диск содержит несколько слайсов, каждый из которых содержит или файловую систему другой операционной системы (NTFS, FAT, ext2fs, etc.), или один или более разделов FreeBSD, каждый из которых является отдельной файловой системой или областью подкачки. - Примеч. науч. ред.
274 Глава 6. Обзор системы ввода/вывода 6.4. Управление дескрипторами и службы дескрипторов Для процессов пользователя весь ввод/вывод осуществляется через дескрипторы. Пользовательский интерфейс дескрипторов был описан в разделе 2.6. В данном разделе описывается, как ядро управляет дескрипторами и как оно предоставляет службы дескрипторов, такие, как блокировка и опрос. Системные вызовы, ссылающиеся на открытые файлы, принимают в качестве аргумента для указания файла дескриптор файла. Дескриптор файла используется ядром в качестве индекса в таблице дескрипторов для текущего процесса (хранящегося в структуре filedesc, подструктуре структуры процесса), чтобы найти элемент файла (file entry), или структуру файла. Взаимоотношения этих структур данных показаны на рис. 6.4. Дескриптор файла Процесс пользователя Таблица дескрипторов Подструктура процесса filedesc Элемент файла Список ядрг \* i Ф айл или устройство Межпроцессное взаимодействие Виртуальная память Рис. 6.4. Ссылка дескриптора файла на элемент файла Элемент файла предусматривает тип файла и указатель на нижележащий объект для дескриптора. В FreeBSD поддерживаются шесть типов объектов. * Для файлов данных элемент файла указывает на структуру vnode, которая ссыла- ссылается на подструктуру, содержащую специфическую для файловой системы ин- информацию, описанную в главах 8 и 9. Уровень vnode описан в разделе 6.5. Специ- Специальные файлы не содержат размещенных на диске блоков данных; они управляют- управляются файловой системой специальных устройств, которая вызывает для выполнения запросов ввода/вывода для них соответствующие драйверы. * Для доступа к межпроцессному взаимодействию, включая сетевые возможности, элемент файла FreeBSD может ссылаться на сокет. * Для высокоскоростной локальной коммуникации без использования имен элемент файла будет ссылаться на канал (pipe). Ранее системы FreeBSD использовали для локальной коммуникации сокеты, но для каналов была добавлена оптимизирован- оптимизированная поддержка для повышения их производительности.
6.4. Управление дескрипторами и службы дескрипторов 275 * Для высокоскоростной локальной коммуникации с использованием имен элемент файла будет ссылаться на фаш/ifo. Как и в случае с каналами, для повышения производительности была добавлена оптимизированная поддержка fifo. * Для систем, имеющих аппаратную криптографическую поддержку, дескриптор может предоставить непосредственный доступ к соответствующему оборудованию. * Для предоставления уведомлений о событиях ядра дескриптор будет ссылаться на kqueue. Система виртуальной памяти поддерживает отображение файлов в адресное про- пространство процесса. В этом случае дескриптор файла должен ссылаться на vnode, ко- который будет частично или полностью отображен в адресное пространство пользователя. Открытые элементы файлов Набор элементов файлов является средоточием активности для дескрипторов файлов. Они содержат информацию, необходимую для доступа к нижележащим объектам и для поддержания общих сведений. Элемент файла является объектно-ориентированной структурой данных. Каждый элемент содержит тип и массив указателей функций, которые преобразуют общие операции с дескрипторами файлов в специфические действия, связанные с их типом. Операции, которые должны быть реализованы для каждого типа, следующие: * чтение из дескриптора; запись в дескриптор; опрос (poll) дескриптора; * выполнение для дескриптора операций ioctl; сбор статистической (stat) информации для дескриптора; проверка наличия для дескриптора каких-нибудь ожидающих событий kqueue; * закрытие и возможное удаление объекта, связанного с дескриптором. Обратите внимание, что в объектной таблице не определена процедура open. FreeBSD рассматривает дескрипторы на объектно-ориентированный манер лишь после их создания. Такой подход был избран, поскольку у различных типов дескрип- дескрипторов различные свойства. Обобщение интерфейса для обработки всех типов дескрипторов во время открытия усложнило бы простой в других отношениях интерфейс. Дескрипторы vnode создаются системным вызовом open; дескрипторы сокетов создаются системным вызовом socket; дескрипторы fifo создаются системным вызовом pipe. У каждого элемента файла есть указатель на структуру данных, которая содержит информацию, специфическую для экземпляра нижележащего объекта. Эта структура
276 Глава 6. Обзор системы ввода/вывода данных непрозрачна для процедур, которые манипулируют элементом файла. Ссылка на структуру данных передается при каждом вызове функции, которая реализует фай- файловую операцию. Все состояние, связанное с экземпляром объекта, должно храниться в структуре данных этого экземпляра; нижележащим объектам не разрешается самим управлять элементом файла. Системные вызовы read и write не принимают в качестве аргумента смещение в файле. Вместо этого каждое чтение или запись обновляют текущее файловое смеще- смещение в соответствии с числом переданных байтов. Смещение определяет положение в файле для следующего чтения или записи. Смещение может быть установлено непо- непосредственно системным вызовом Iseek. Поскольку один и тот же файл могут открыть более чем один процесс и для каждого процесса нужно свое собственное смещение для файла, смещение не может храниться в структуре данных объекта. Поэтому каждый системный вызов open выделяет новый элемент файла, а открытый элемент файла содержит смещение. Некоторая семантика, связанная со всеми дескрипторами файлов, проводится в жизнь на уровне дескриптора до извлечения нижележащего системного вызова. Эта семантика сохраняется в наборе флагов, связанных с дескриптором файла. Например, флаги фиксируют, открыт ли дескриптор для чтения, записи или и для чтения, и для записи. Если дескриптор помечен как открытый только для чтения, попытка записи в него будет перехвачена кодом дескриптора. Таким образом, функциям, определенным для осущест- осуществления чтения и записи, не нужно проверять действительность запроса; мы можем реа- реализовать их, зная, что они никогда не получат недействительный запрос. Видимые приложению флаги описаны в следующем подразделе. Кроме види- видимых приложению флагов поле флагов содержит также информацию о том, удержи- удерживает ли дескриптор разделяемую или исключительную блокировку нижележащего файла. Примитивы блокировки могли бы быть расширены для работы с сокетами так же, как с файлами. Однако дескрипторы для сокета редко ссылаются на один и тот же элемент файла. Единственным способом для двух процессов разделять один и тот же дескриптор сокета является разделение дескриптора родителя с потомком посредством разветвления процессов или путем передачи дескриптора одним про- процессом другому в сообщении. У каждого элемента файла есть счетчик ссылок. У одного процесса может быть несколько ссылок на элемент в результате использования системных вызовов dup или fcntl. Структуры файла наследуются также порожденными процессами после fork, поэтому несколько различных процессов могут ссылаться на один и тот же элемент файла. В результате чтение или запись любым процессом в спаренные дескрипторы из- изменит файловое смещение. Эта семантика дает двум процессам возможность читать один и тот же файл или чередовать вывод в тот же самый файл. Другой процесс, неза- независимо открывший файл, будет ссылаться на этот файл через другую структуру файла
6.4. Управление дескрипторами и службы дескрипторов 277 с другим файловым смещением. Эта возможность была исходной причиной существова- существования структуры файла; структура файла предоставляет место для файлового смещения между дескриптором и нижележащим объектом. Каждый раз при создании новой ссылки счетчик ссылок увеличивается. Когда дескриптор закрывается (в любом из случаев, когда A) закрывается явно посредством close, B) неявно после exec, поскольку дескриптор был помечен для закрытия при исполнении, или C) при завершении процесса), счетчик ссылок уменьшается. Когда счетчик ссылок достигает нуля, элемент файла освобождается. Флаг закрытия при исполнении (close-on-exec) хранится в таблице дескрипторов, а не в элементе файла. Этот флаг не разделяется между всеми ссылками на элемент файла, поскольку он является атрибутом самого дескриптора файла. Флаг закрытия при исполнении является лишь частью информации, которая хранится в таблице дескрипторов, а не разделяется в элементе файла. Управление дескрипторами Системный вызов fcntl манипулирует структурой файла. Он может использоваться для осуществления следующих изменений в дескрипторе. * Дублирования дескриптора аналогично системному вызову dup. * Получения или установки флага закрытия при исполнении. Когда процесс раз- разветвляется, все дескрипторы родителя дублируются в порожденном процессе. Порожденный процесс затем выполняет exec для нового процесса. Любые дескрипторы порожденного процесса, которые были помечены для закрытия при исполнении, закрываются. Оставшиеся дескрипторы доступны вновь выпол- выполняющемуся процессу. Установки флага без отсрочки (no-delay) (ONONBLOCK), чтобы перевести дескриптор в неблокирующий режим. В неблокирующем режиме, если для опера- операции чтения доступны какие-либо данные или если для операции записи доступно какое-либо пространство, выполняется немедленное частичное чтение или запись. Если для операции чтения недоступны никакие данные или если операция записи вызвала бы блокирование, системный вызов возвращает ошибку (EAGAIN), ука- указывающую, что операция вызвала бы блокирование, вместо перевода процесса в спящее состояние. Эта возможность не реализована в FreeBSD для локальных файловых систем, поскольку ожидается, что ввод/вывод локальной файловой сис- системы всегда завершится в течение нескольких миллисекунд. Установки флага синхронности (OFSYNC) для форсирования синхронной записи на диск всех записей в файл. Установки флага непосредственности (ODIRECT), чтобы запросить ядро попы- попытаться записать данные на диск непосредственно из приложения пользователя, не копируя их через буферы ядра.
278 Глава 6. Обзор системы ввода/вывода * Установки флага добавления (O_APPEND), чтобы заставить все записи добавлять данные в конец файла, а не в текущем положении дескриптора в файле. Эта осо- особенность полезна, когда, например, несколько процессов записывают в один и тот же файл журнала. Установки флага асинхронности (O_ASYNC), чтобы попросить ядро отслеживать изменения в состоянии дескриптора и организовать отправку сигнала (SIGIO), когда станут возможными чтение или запись. * Отправки сигнала процессу, когда появится условие исключения, такое, как посту- поступление срочных данных в канал межпроцессного взаимодействия. Установки или получения идентификатора процесса или идентификатора группы процесса, которым должны быть посланы два предыдущих относящихся к вводу/ выводу сигнала. Тестирования или изменения состояния блокировки для диапазона байтов в ниже- нижележащем файле. Операции блокировки описаны далее в данном разделе. Реализация системного вызова dup проста. Если процесс достиг своего предела открытых файлов, ядро возвращает ошибку. В противном случае ядро сканирует таб- таблицу дескрипторов текущего процесса, начиная с нулевого дескриптора, до тех пор, пока не найдет неиспользующийся элемент. Ядро выделяет элемент, указывающий на тот же элемент файла, что и дублируемый дескриптор. Затем ядро увеличивает счетчик элемента файла и возвращает индекс выделенного элемента таблицы дескрипторов. Системный вызов fcntl предусматривает схожую функцию, за тем исключением, что он указывает дескриптор, с которого нужно начать сканирование. Иногда процесс хочет выделить определенный элемент таблицы дескрипторов. Такой запрос делается с помощью системного вызова dup2. Процесс указывает индекс таблицы дескрипторов, в который должна быть помещена дублированная ссылка. Реализация ядра та же самая, как для dup, за тем исключением, что сканирование для обнаружения свободного элемента заменяется на закрытие запрошенного элемента, если он открыт, а затем выделения его, как прежде. Если старый и новый дескрипторы совпадают, не предпринимается никаких действий. Система реализует получение или установку флага закрытия при исполнении через системный вызов fcntl, делая соответствующее изменение в поле флагов связан- связанного элемента таблицы дескрипторов. Другие атрибуты, которыми манипулирует^с/i//, работают с флагами в элементе файла. Однако реализация различных флагов не может обрабатываться общим кодом, который управляет элементом файла. Вместо этого флаги файла должны передаваться через объектный интерфейс специфическим для их типа процедурам, чтобы выполнить соответствующую операцию с нижележащим объ- объектом. Например, манипулирование флагом блокировки для сокета должно осуществ- осуществляться на уровне сокета, поскольку лишь этот уровень знает о том, может ли операция блокироваться.
6.4. Управление дескрипторами и службы дескрипторов 279 Реализация системного вызова ioctl разделена на два главных уровня. Верхний уровень обрабатывает сам системный вызов. Вызов ioctl включает дескриптор, коман- команду и указатель на область данных. Аргумент команды кодирует, каков размер области данных для параметров и являются ли параметры входными, выходными или и теми и другими. Верхний уровень отвечает за декодирование аргументов команды, выделе- выделение буфера и копирование туда любых входных данных. Если должно быть создано возвращаемое значение, а входных данных нет, буфер обнуляется. В заключение ioctl отсылается через функцию ioctl элемента файла вместе с буфером ввода/вывода той процедуре нижележащего уровня, которая реализует запрошенную функцию. Нижележащий уровень выполняет запрошенную операцию. Вместе с аргументами команды он получает указатель на буфер ввода/вывода. Вышележащий уровень уже проверил действительность ссылок памяти, но нижележащий уровень может сделать более точную проверку аргументов, поскольку он больше знает об ожидающемся характере аргументов. Однако ему не нужно копировать аргументы в или из процесса пользователя. Если команда успешна и создает вывод, нижележащий уровень помеща- помещает результаты в буфер, предусмотренный верхним уровнем. Когда нижний уровень возвращается, верхний уровень копирует результаты в процесс. Асинхронный ввод/вывод Изначально системы UNIX не имели возможности осуществлять асинхронный ввод/ вывод, помимо возможности осуществлять фоновую запись в файловую систему. Интерфейс асинхронного ввода/вывода был определен группой POSIX.lb-1993 realtime. Вскоре после его принятия к FreeBSD была добавлена реализация. Асинхронное чтение начинается aio_read\ асинхронная запись начинается aio_write. Ядро строит структуру запроса асинхронного ввода/вывода, которая содержит все нужные для выполнения запрошенной операции сведения. Если запрос не может быть удовлетворен немедленно из буферов ядра, структура запроса помещается в очередь для обработки расположенным в ядре демоном асинхронного ввода/вывода и систем- системный вызов возвращается. Следующий доступный демон асинхронного ввода/вывода обрабатывает запрос, используя обычный путь синхронного ввода/вывода ядра. Когда демон завершает ввод/вывод, структура асинхронного ввода/вывода помеча- помечается как завершенная, с возвращаемым значением или кодом ошибки. Приложение использует системный вызов aio_error для запроса о том, завершен ли ввод/вывод. Этот вызов реализован путем проверки состояния структуры запроса асинхронного ввода/вывода, созданной ядром. Если приложение доходит до места, с которого оно не может продолжить выполнение, пока ввод/вывод не будет завершен, оно использует системный вызов aio_suspend, чтобы подождать завершения ввода/вывода. В этом месте приложение помещается в спящее состояние в ожидании структуры запроса асинхронного ввода/вывода и пробуждается демоном асинхронного ввода/вывода,
280 Глава 6. Обзор системы ввода/вывода когда последний завершается. В качестве альтернативы приложение может потребо- потребовать, чтобы ему после завершения ввода/вывода был послан определенный сигнал. Системный вызов aio_return получает возвращаемое значение из асинхронного запроса, когда aioerror, aio_suspend или поступление сигнала завершения известят о том, что ввод/вывод завершен. FreeBSD добавил также нестандартный системный вызов aio_waitcomplete, который объединяет функции aio_suspend и aio_return в одну операцию. Как для aio_return, так и для aio_waitcomplete возвращаемая информация копируется в приложение из структуры запроса асинхронного ввода/вывода, а затем структура запроса асинхронного ввода/вывода освобождается. Блокировка дескриптора файла На ранних UNIX-системах не было обеспечения блокировки файлов. Процессам, которым нужно было синхронизировать доступ к файлу, приходилось использовать отдельный файл блокировки (lock file). Процесс пытался создать файл блокировки. Если попытка была успешной, процесс мог продолжить обновление; если попытка завершалась неудачей, процесс должен был ждать, а затем пытаться снова. У этого меха- механизма было три недостатка. 1. Процессы потребляли время процессора в цикле, пытаясь создать блокировки. 2. Блокировки, оставшиеся разбросанными вокруг в результате сбоя системы, прихо- приходилось удалять (обычно в командном сценарии запуска системы). 3. Процессам, работающим от имени специального пользователя администратора системы, суперпользователя, всегда разрешалось создавать файлы, поэтому они были вынуждены использовать другой механизм. Хотя все эти проблемы можно решить, решения не являются простыми, поэтому в 4.2BSD был добавлен механизм для блокировки файлов. Наиболее общие схемы блокировки позволяют нескольким процессам одновре- одновременно обновлять файл. Некоторые из этих методик обсуждаются в Peterson [1983]. Более простой методикой является упорядочивание доступа к файлу с помощью бло- блокировок. Для стандартных системных приложений достаточно механизма, который блокирует на уровне файла. Поэтому 4.2BSD и 4.3BSD предоставляли лишь быстрый механизм блокировки всего файла. Семантика этих блокировок включала возмож- возможность наследования блокировок порожденным процессом и освобождение блокировок лишь при последнем закрытии файла. Некоторым приложениям требуется возможность блокировки участков файла. Средства блокировки, поддерживающие степень детализации на уровне байтов, хорошо поняты. К сожалению, они недостаточно мощны, чтобы использоваться системами баз данных, которым требуются вложенные иерархические блокировки, но достаточно сложны, чтобы требовать больших и громоздких реализаций по сравнению с более
6.4. Управление дескрипторами и службы дескрипторов 281 простыми блокировками всего файла. Поскольку стандарт POSIX предоставляет бло- блокировки диапазонов байтов, разработчики неохотно добавили их к BSD. Семантика блокировок диапазонов байтов пришла из первоначальной реализации блокировок в System V, которая включала освобождение всех блокировок, удерживаемых процес- процессом для файла, каждый раз, когда для дескриптора, ссылающегося на файл, выполнял- выполнялся системный вызов close. Блокировки 4.2BSD для всего файла удаляются лишь при последнем закрытии. Проблема с семантикой POSIX в том, что приложение может заблокировать файл, затем вызвать библиотечную процедуру, которая открывает, читает и закрывает заблокированный файл. Вызов библиотечной процедуры даст не- неожиданный эффект освобождения блокировки, удерживаемой приложением. Другая проблема в том, что файл должен быть открыт на запись, чтобы получать исключитель- исключительную блокировку. Процесс, у которого нет разрешения для открывания файла для запи- записи, не может получить исключительную блокировку для этого файла. Чтобы избежать этих проблем, все еще оставаясь совместимой с POSIX, FreeBSD предоставляет отдельные интерфейсы для блокировок диапазонов байтов и блокировок файлов цели- целиком. Блокировки диапазонов байтов следуют семантике POSIX; блокировки файлов целиком следуют традиционной семантике 4.2BSD. Эти два вида блокировок могут использоваться одновременно; они будут соответствующим образом упорядочены друг относительно друга. Как блокировки всего файла, так и блокировки диапазонов байтов используют одну и ту же реализацию; блокировки всего файла реализованы в виде блокировки диа- диапазона всего файла. Ядро управляет отличающейся между этими двумя реализациями семантикой, применяя блокировки диапазонов байтов к процессам, тогда как бло- блокировки всего файла применяются к дескрипторам. Поскольку дескрипторы разде- разделяются с порожденными процессами, блокировки всего файла наследуются. Посколь- Поскольку порожденный процесс получает свою собственную структуру процесса, блокиров- блокировки диапазонов байтов не наследуются. Семантика последнего закрытия против семан- семантики каждого закрытия является небольшим куском кода особого случая в процедуре закрытия, который проверяет, является ли нижележащий объект процессом или де- дескриптором. Он освобождает блокировки при каждом вызове, если блокировка связана с процессом, и лишь когда счетчик ссылок падает до нуля, если блокировка связана с дескриптором. Схемы блокировок можно классифицировать в соответствии со степенью их обяза- обязательности. Говорят, что схема, при которой блокировки приводятся в исполнение для каждого процесса без возможности выбора, использует обязательные (mandatory) бло- блокировки, тогда как схема, при которой блокировки приводятся в исполнение лишь для тех процессов, которые их запрашивают, использует необязательные (advisorty) бло- блокировки. Очевидно, необязательные блокировки эффективны лишь тогда, когда все программы, получающие доступ к файлу, используют схему блокировки. При обяза- обязательных блокировках в ядре должна быть реализована некоторая политика аннулиро- аннулирования. При необязательных блокировках эта политика оставлена пользовательским
282 Глава 6. Обзор системы ввода/вывода программам. В системе FreeBSD программам с привилегией суперпользователя разре- разрешено аннулировать любую схему защиты. Поскольку многие из программ, которым нужно использовать блокировки, должны также работать от имени суперпользователя, 4.2BSD реализовала обязательные блокировки вместо создания дополнительной схемы защиты, которая была бы несовместимой с философией UNIX или не могла ис- использоваться привилегированными программами. Использование обязательных бло- блокировок перешло в спецификацию POSIX блокировок диапазонов байтов и осталось в FreeBSD. Средства блокировки файлов FreeBSD дают взаимодействующим программам воз- возможность применять обязательные разделяемые или исключительные блокировки на диапазоны байтов внутри файла. Лишь один процесс может иметь исключительную блокировку на диапазон байтов, тогда как разделяемых блокировок может быть несколько. Разделяемая и исключительная блокировки не могут присутствовать для диапазона байтов в одно и то же время. Если запрошена любая блокировка, в то время когда другой процесс удерживает исключительную блокировку, или когда запрошена исключительная блокировка, когда другой процесс удерживает любую блокировку, запрос блокировки будет заблокирован до тех пор, пока неблокировка не сможет быть получена. Поскольку разделяемые и исключительные блокировки являются лишь необя- необязательными, даже если процесс получил блокировку на файл, другой процесс может получить доступ к файлу, если он игнорирует механизм блокировки. Поскольку между созданием и блокированием файла нет состязания, блокировка может быть запрошена как часть открытия файла. Когда процесс открыл файл, он может манипулировать блокировками без необходимости закрыть и повторно открыть файл. Эта особенность полезна, например, когда процесс хочет применить разделяе- разделяемую блокировку для считывания информации, чтобы определить, нужно ли обновле- обновление, а затем использовать исключительную блокировку и обновить файл. Запрос блокировки заставит процесс заблокироваться, если блокировка не может быть получена немедленно. В отдельных случаях это блокирование является неподхо- неподходящим. Например, процессу, который хочет лишь проверить, присутствует ли бло- блокировка, потребовался бы отдельный механизм для получения этой информации. Поэтому процесс может обозначить, что его запрос блокировки должен вернуть ошиб- ошибку, если блокировка не может быть получена немедленно. Возможность запрашивать блокировки условно полезна процессам демонов, которые хотят обслуживать область спулинга. Если первый экземпляр демона блокирует каталог, в котором происходит спулинг, следующие процессы демонов легко могут проверить, существуют ли актив- активные демоны. Поскольку блокировки существуют, лишь пока существует блокирующий процесс, блокировки никогда не могут оставаться активными после завершения про- процесса или при аварии системы. Реализация блокировок сделана на основе отдельных файловых систем. Реализа- Реализация для локальных файловых систем описана в разделе 7.5. Сетевая файловая система должна координировать блокировки с главным менеджером блокировок, который
6.4. Управление дескрипторами и службы дескрипторов 283 обычно расположен на сервере, экспортирующем файловую систему. Запросы бло- блокировок клиентов должны посылаться менеджеру блокировок. Менеджер блокировок выносит решения по запросам блокировок от процессов, работающих на его сервере, и от различных клиентов, для которых экспортируется файловая система. Наиболее сложной операцией для менеджера блокировок является восстановление состояния блокировки, когда клиент или сервер перезагружаются или становятся отделенными от остальной сети. Менеджер сетевых блокировок FreeBSD описан в разделе 9.3. Мультиплексирование ввода/вывода для дескрипторов Иногда процессу нужно обработать ввод/вывод для более чем одного дескриптора. Например, рассмотрите программу удаленной регистрации, которая хочет прочесть данные с клавиатуры и послать их через сокет на удаленную машину. Эта программа хочет также прочесть данные из сокета, подключенного к удаленной машине, и вы- вывести их на экран. Если процесс делает запрос чтения, когда нет доступных данных, он обычно блокируется в ядре до тех пор, пока данные не станут доступны. В нашем примере блокирование неприемлемо. Если процесс читает из клавиатуры и блокиру- блокируется, он будет не способен прочесть данные от удаленной машины, которые предна- предназначены для вывода на экран. Пользователь не знает, что набрать, до тех пор, пока не поступят дополнительные данные с удаленного конца, поэтому сеанс попадает в тупик. И наоборот, если процесс читает с удаленного конца, когда нет данных для экрана, он будет заблокирован и не сможет ничего прочесть с терминала. Опять возник бы тупик, если на удаленном конце ожидали вывода до отправки каких-либо данных. Есть аналогичный набор проблем блокирования записей на экран или на удаленном конце. Если пользователь остановил вывод на свой экран, набрав символ остановки, запись будет заблокирована до тех пор, пока пользователь не введет символ запуска. Между тем процесс не может читать с клавиатуры, чтобы выяснить, что пользователь хочет сбросить вывод. FreeBSD предусматривает три механизма, которые позволяют мультиплексировать ввод/вывод для дескриптора: опрос ввода/вывода, неблокирующий ввод/вывод и управ- управляемый сигналами ввод/вывод. Опрос осуществляется посредством системных вызовов select или poll, описанных в следующем подразделе. Операции для неблокирующих де- дескрипторов завершаются немедленно, частично завершают операцию ввода или вывода и возвращают частичный результат или же ошибку, указывающую, что опера- операция вовсе не может быть завершена. Дескрипторы с включенной сигнализацией вызы- вызывают уведомление соответствующего процесса или группы процесса, когда состояние ввода/вывода дескриптора изменяется. Есть четыре возможные альтернативы, позволяющие избежать проблемы блокиро- блокирования. 1. Установка всех дескрипторов в неблокирующий режим. После этого процесс может пытаться выполнить операцию для каждого из дескрипторов по очереди, чтобы
284 Глава 6. Обзор системы ввода/вывода выяснить, какой из них готов к вводу/выводу. Проблема с этим подходом ожидания в ходе работы в том, что процесс должен непрерывно работать, чтобы обнаружить, можно ли выполнить какой-нибудь ввод/вывод, растрачивая циклы процессора впустую. 2. Дать всем представляющим интерес дескрипторам возможность сигнализировать, когда можно выполнить ввод/вывод. Процесс может затем ожидать сигнал, чтобы определить, когда можно делать ввод/вывод. Недостаток этого подхода в том, что перехват сигналов дорог. Поэтому управляемый сигналами ввод/вывод непрак- непрактичен для приложений, объем ввода/вывода которых колеблется от умеренного до большого. 3. Заставить систему предоставить метод опроса того, какие дескрипторы могут выполнить ввод/вывод. Если ни один из запрошенных дескрипторов не готов, сис- система может перевести процесс в состояние сна до тех пор, пока дескриптор не будет готов. Эта проблема позволяет избежать проблемы тупика, поскольку про- процесс будет пробужден каждый раз, когда будет возможен ввод/вывод, и ему будет сообщено, какой дескриптор готов. Недостаток в том, что процесс должен делать два системных вызова на одну операцию: один для опроса готовности дескриптора выполнить ввод/вывод, а другой для выполнения самой операции. 4. Заставить процесс уведомить систему о всех дескрипторах, которые он хочет прочесть, а затем выполнить блокирующее чтение для этого набора дескрипторов. Когда опера- операция чтения возвращается, процесс уведомляется о том, для какого дескриптора было выполнено чтение. Преимуществом этого подхода является то, что процесс выполняет единственный системный вызов для указания набора дескрипторов, а затем выполняет в цикле лишь чтение [Accetta et al., 1986; Lemon, 2001]. Первый подход доступен в FreeBSD в виде неблокирующего ввода/вывода. Он обычно используется для вывода в дескрипторы, поскольку эта операция обычно не блокируется. Вместо выполнения select или poll, которые почти всегда завершаются успешно и за которыми сразу же следует write, более эффективно попытаться выпол- выполнить write и вернуться к использованию select или poll лишь в те периоды, когда write возвращает ошибку блокирования. Второй подход доступен в FreeBSD в виде управ- управляемого сигналами ввода/вывода. Обычно он используется для редких событий, таких как поступление внеполосных данных сокета. Для таких редких событий стоимость обработки случайного сигнала ниже, чем стоимость постоянной проверки с помощью select или poll, есть ли какие-либо ожидающие данные. Третий подход доступен в FreeBSD посредством системных вызовов select или poll. Хотя он менее эффективен, чем четвертый подход, это более общий интерфейс. Кроме обработки чтения от нескольких дескрипторов он управляет записью в несколько дескрипторов, уведомлением об исключительных ситуациях и тайм-аутом, когда никакой ввод/вывод невозможен.
6.4. Управление дескрипторами и службы дескрипторов 285 Интерфейсы select к poll предоставляют одну и ту же информацию. Они отличают- отличаются лишь в своем интерфейсе программирования. Интерфейс select был впервые разра- разработан в 4.2BSD с введением основанного на сокетах межпроцессного взаимодействия. Интерфейс poll был введен в System V несколькими годами позже с их конкурирующим межпроцессным взаимодействием на основе потоков (STREAMS). Хотя потоки STREAMS оказались неиспользуемыми, интерфейс poll оказался достаточно популярным, чтобы остаться. Ядро FreeBSD поддерживает оба интерфейса. Системный вызов select выглядит так: int error = select( int numfds, fd_set *readfds, fd_set *writefds/ fd_set *exceptfds, struct timeval *timeout); Он принимает три маски для отслеживаемых дескрипторов, соответствующие чте- чтению, записи и исключительным условиям. Кроме того, он принимает значение тайм- аута для возвращения из select, если ни один из запрошенных дескрипторов не будет готов, когда истечет указанный промежуток времени. Вызов select возвращает те же самые три маски дескрипторов после их изменения таким образом, чтобы указать, какие дескрипторы могут выполнить чтение, запись или в каких из них возникли исключительные условия. Если ни один из дескрипторов не стал готов за время интервала тайм-аута, select возвращается, указывая, что ни один из дескрипторов не готов для ввода/вывода. Если значение тайм-аута указано и дескриптор становится готовым до истечения указанного периода времени, время, которое select проводит в ожидании ввода/вывода, вычитается из указанного времени. Интерфейс poll копирует массив структур pollfd, по одному элементу массива для каждого представляющего интерес дескриптора. Структура pollfd содержит три эле- элемента: * дескриптор файла для опроса; набор флагов, описывающих разыскиваемую информацию; набор установленных ядром флагов, обозначающих найденную информацию. Флаги определяют готовность обычных или дополнительных (внеполосных - out- of-band) данных для чтения и доступность буферного пространства для записи обычных или дополнительных (внеполосных) данных. Возвращаемые флаги могут также указывать, что в дескрипторе возникла ошибка, что дескриптор был отсоединен или что дескриптор не открыт. Эти условия ошибок устанавливаются select путем ука- указания, что дескриптор с ошибкой готов к выполнению ввода/вывода. Когда приложе- приложение пытается выполнить ввод/вывод, системным вызовом read или write возвращается ошибка. Подобно вызову select, вызов poll принимает значение тайм-аута для указания
286 Глава 6. Обзор системы ввода/вывода максимального времени ожидания. Если ни один из запрошенных дескрипторов не становится готовым до истечения указанного промежутка времени, вызов poll возвра- возвращается. Если дано время тайм-аута и дескриптор становится готовым до истечения указанного периода времени, время, которое poll провел в ожидании готовности ввода/ вывода, вычитается из указанного времени. Реализация select Реализация select разделена на общий верхний уровень и множество специфических для устройства или сокета нижних частей. На верхнем уровне select или poll рас- расшифровывает запрос пользователя, а затем вызывает соответствующие запраши- запрашивающие функции нижнего уровня. Системные вызовы select и poll имеют различные верхние уровни для определения наборов дескрипторов, которые следует опрашивать, но используют одни и те же специфические для устройства или сокета нижние части. Здесь будет описан лишь верхний уровень select. Верхний уровень poll реализован полностью сходным образом. Верхний уровень select предпринимает следующие шаги. 1. Копирует и проверяет маски дескрипторов для чтения, записи и исключительных условий. Осуществление подтверждения правильности требует проверки того, чтобы каждый запрошенный дескриптор был в настоящее время открыт процессом. 2. Устанавливает для потока флаг selecting. 3. Для каждого дескриптора в каждой маске вызывает процедуру опроса устройства. Если дескриптор не может выполнить запрошенную операцию ввода/вывода, про- процедура опроса устройства отвечает за запись того, что поток хочет выполнить ввод/вывод. Когда для дескриптора становится возможным ввод/вывод, - обычно в результате прерывания нижележащего устройства - для выбирающего потока должно быть выдано уведомление. 4. Поскольку процесс выбора может занять много времени, ядро не хочет блокиро- блокировать ввод/вывод в то время, пока оно осуществляет опрос всех запрошенных дескрипторов. Вместо этого ядро организует обнаружение ввода/вывода, который может повлиять на состояние опрашиваемых дескрипторов. Когда возникает такой ввод/вывод, процедура уведомления select, selwakeupQ, сбрасывает флаг selecting. Если код select верхнего уровня обнаруживает, что флаг selecting для данного пото- потока был сброшен, пока выполнялся опрос, и он не нашел каких-либо дескрипторов, которые готовы выполнить операцию, тогда верхний уровень знает, что результаты опроса неполные и должны быть повторены, начиная с шага 2. Другим условием, требующим повтора опроса, является коллизия. Коллизии возникают, когда несколько потоков пытаются опросить в одно и то же время один и тот же дескриптор. Поскольку
6.4. Управление дескрипторами и службы дескрипторов 287 у процедур опроса достаточно места для записи идентификатора лишь одного потока, они не могут отслеживать несколько потоков, которые необходимо пробу- пробудить, когда появилась возможность ввода/вывода. В таких редких случаях должны пробуждаться все ожидающие потоки. 5. Если ни один дескриптор не готов и select определила тайм-аут, ядро записывает тайм-аут на запрошенный промежуток времени. Поток входит в состояние сна, передав адрес глобальной переменной ядра selwait. Обычно дескриптор становится готовым, и поток будет уведомлен посредством selwakeupQ. Когда поток пробужда- пробуждается, он повторяет процесс опроса и возвращает доступные дескрипторы. Если ни один из дескрипторов не стал готовым до истечения срока таймера, поток возвра- возвращается с ошибкой тайм-аута и пустым списком доступных дескрипторов. Если приведено значение тайм-аута и дескриптор становится готовым до истечения ука- указанного периода времени, время, которое select провел в ожидании готовности ввода/ вывода, вычитается из указанного времени. Каждая из низкоуровневых процедур опроса в драйверах терминалов и сетевых протоколах следует примерно одному и тому же набору шагов. Часть кода процедуры опроса для драйвера терминала показана на рис. 6.5. Шаги, связанные с процедурой опроса устройства, следующие. 1. Элемент запроса сокета или устройства вызывается с одним или несколькими флагами POLLIN, POLLOUT или POLLRDBAND (исключительное условие). Пример на рис. 6.4 показывает случай POLLIN; другие случаи аналогичны. 2. Процедура опроса устанавливает флаги, указывающие доступные операции. На рис. 6.5 можно прочесть символ, если число непрочитанных символов больше нуля. Кроме того, если носитель удален, можно получить ошибку чтения. Возвра- Возвращение из select необязательно означает, что есть данные для чтения; скорее, оно означает, что чтение не будет блокировано. 3. Если запрошенная операция невозможна, с сокетом или устройством записывается идентификатор потока для последующего уведомления. На рис. 6.5 запись осуще- осуществляется процедурой selrecordQ. Эта процедура сначала проверяет, записаны ли для этого сокета или устройства какие-нибудь потоки. Если ничего не записано, записывается текущий поток и структура selinfo добавляется в список событий, связанных с потоком. Второй оператор if проверяет наличие коллизий. Если запи- записанный в структуру поток не является нашим собственным, возникла коллизия.
288 Глава 6. Обзор системы ввода/вывода struct selinfo { TAILQ_ENTRY(selinfo) si_thrlist;/* список подвешенных потоков */ struct thread *si_thread; /* ожидающий поток */ short si_flags; /* SI_COLL - возникла коллизия*/ struct tty *tp; if (events & POLLIN) { if (nread > 0 || (tp->t_state & TS_CARR_ON) == 0) revents |= POLLIN; else selrecord(curthread, &tp->t_rsel); void selrecord( struct thread ^selector, struct selinfo *sip); { if (sip->si_thread == NULL) { sip->si_thread = selector; TAILQ_INSERT_TAIL(&selector->td_selq/ sip, si_thrlist) } else if (sip->si_thread != selector) { sip->si_flags |= SI_COLL; Рис. 6.5. Код select из драйвера псевдотерминала для проверки готовности данных для чтения 4. Если в том же самом сокете или устройстве выбирают несколько потоков, для соке- та или устройства записывается коллизия, поскольку в структуре достаточно места для записи лишь одного идентификатора потока. На рис. 6.5 коллизия возникает, когда истинно значение второго оператора if в функции selrecordQ. Для каждого канала терминала (или псевдотерминала) на машине есть структура tty, содержа- содержащая структуру selinfo. Обычно в одно и то же время лишь один поток выбирает для чтения из терминала, поэтому коллизии здесь редки. Выбирающий поток должен быть уведомлен, когда ввод/вывод становится воз- возможным. Шаги, связанные с изменением статуса с побуждением потока, следующие. 1. Устройство или сокет обнаруживают изменение состояния. Изменения состояния обычно возникают в результате прерывания (например, поступления символа от кла- клавиатуры или прибытия пакета из сети).
6.4. Управление дескрипторами и службы дескрипторов 289 2. Вызывается selwakeupQ с указателем наструктуру selinfo, использующейся selrecordQ для записи идентификатора потока, и с флагом, указывающим на возникновение коллизии. 3. Если поток ожидает в selwait, он делается готовым к запуску (или помечается готовым, если он остановлен). Если поток ожидает какого-нибудь другого события помимо selwait, он не делается готовым к запуску. Может возникнуть ложный вызов selwakeupQ, когда поток возвращается из select, чтобы начать обработку одного дескриптора, а затем другой дескриптор, который он выбирал, также становится готовым. 4. Если у потоков установлены их флаги selecting, они сбрасываются таким образом, чтобы ядро знало, что их результаты опроса недействительные и должны быть получены снова. 5. Если возникла коллизия, все ожидающие в selwait пробуждаются для повторного сканирования, не готов ли один из их дескрипторов. Пробуждение всех выбирающих потоков необходимо, поскольку процедура selrecordQ не могла бы записать все потоки, которые необходимо пробудить. Поэтому ей приходится про- пробуждать все потоки, которые могут быть в этом заинтересованы. Как показывает опыт, коллизии возникают нечасто. Если бы они были частыми, стоило бы хранить в структуре selinfo несколько идентификаторов потоков. Перемещение данных внутри ядра Внутри ядра данные ввода/вывода описываются массивом векторов. Каждый вектор ввода/вывода, или iovec1, имеет базовый адрес и размер. Эти векторы ввода/вывода иден- идентичны векторам ввода/вывода, использующимся системными вызовами readv и wiitev. Ядро поддерживает другую структуру, называемую структурой uio, которая содержит дополнительные сведения об операции ввода/вывода. Пример структуры uio показан на рис. 6.6; она содержит следующее. Указатель на массив iovec. * Число элементов в массиве iovec. Смещение в файле, с которого должна начаться операция. Сумма размеров векторов ввода/вывода. * Флаг, указывающий, находились ли источник и место назначения оба внутри ядра, или они разнесены между пространством пользователя и ядром. Флаг, указывающий, копируются ли данные из структуры uio в ядро или из ядра в структуру uio (UIO_READ). От I/O vector - «вектор ввода/вывода». - Примеч. пер.
290 Глава 6. Обзор системы ввода/вывода struct uio i 1 struct iov[] UlOJLOVCnt uio_offset uiojresid uw_seg- g uiojrw uiojtd iov_ iov_ iov_ iov_ iov_ iov_ base len base Jen base Jen Рис. б.б. Структура uio * Указатель на поток, область данных которого описывается структурой uio (указа- (указатель равен NULL, если структура uio описывает область внутри ядра). Весь ввод/вывод внутри ядра описывается структурами iovec и uio. Системные вызовы, такие, как read и write, которым не передается iovec, создают uio для описания своих аргументов; эта структура uio передается нижележащим уровням ядра для ука- указания параметров операции ввода/вывода. В конечном счете структура uio достигает той части ядра, которая отвечает за перемещение данных в и из адресного пространст- пространства процесса: файловой системы, сети или драйвера устройства. Вообще эти части ядра не интерпретируют структуру uio непосредственно. Вместо этого они организуют буфер ядра для хранения данных, а затем используют uiomoveQ для копирования данных в или из буфера или буферов, описываемых структурой uio. Процедура uiomoveQ вызывается с указателем на область данных ядра, объемом данных и структурой uio. Когда она перемещает данные, она обновляет счетчики и указатели структур iovec и uio на соответствующие величины. Если буфер ядра не настолько большой, как об- области, описываемые структурой uio, структура uio будет указывать на часть адресного пространства сразу за расположением, заполненным последним. Таким образом, при обслуживании запроса ядро может вызывать uiomoveQ несколько раз, каждый раз пре- предоставляя указатель на новый буфер ядра для следующего блока данных. Драйверы символьных устройств, которые, как правило, не копируют данные из процесса, не интерпретируют структуру uio. Вместо этого имеется одна низкоуровне- низкоуровневая процедура ядра, которая организует прямую передачу в или из адресного про- пространства процесса. В этом случае для каждого элемента iovec выполняется отдельная операция ввода/вывода с обратным вызовом драйвера с одним участком за раз.
6.5. Интерфейс виртуальной файловой системы 291 6.5. Интерфейс виртуальной файловой системы На ранних UNIX-системах элементы файлов непосредственно ссылались на inodex локаль- локальной файловой системы, inode представляет собой структуру данных, которая описыва- описывает содержимое файла; более полно он описан в разделе 8.2. Этот подход замечательно работал, когда была единственная реализация файловой системы. Однако с появлением множества типов файловых систем архитектуру пришлось обобщить. Новая архитек- архитектура должна была поддерживать импортирование файловых систем с других машин, включая машины, которые работали с другими операционными системами. Одной из альтернатив могло бы быть объединение множества файловых систем в систему в виде различных типов файлов. Однако этот подход потребовал бы значи- значительного реструктурирования внутренней работы системы, поскольку текущие катало- каталоги, ссылки на исполняемые файлы и несколько других интерфейсов использовали в качестве точки привязки inode вместо элементов файлов. Таким образом, было проще и логичнее добавить к системе новый объектно-ориентированный уровень ниже эле- элемента файла и выше inode. Этот новый уровень впервые был реализован компанией Sun Microsystems, которая назвала его уровнем виртуального узла или уровнем vnode2. Интерфейсы в системе, которые ранее ссылались на inode, были изменены для ссылки на общие vnode. vnode, используемый локальной файловой системой, ссылался бы на inode. vnode, используемый удаленной файловой системой, ссылался бы на управ- управляющий блок протокола, который описывал расположение и сведения об именовании, необходимые для доступа к удаленному файлу. Содержимое vnode vnode является расширяемым объектно-ориентированным интерфейсом. Он содержит информацию, которая обычно полезна вне зависимости от нижележащего объекта файловой системы, который он представляет. Информация, хранящаяся в vnode, включает следующее. Флаги используются для идентификации общих атрибутов. Примером общего атрибута является флаг, указывающий, что vnode представляет объект, являющий- являющийся корнем файловой системы. # Различные счетчики ссылок включают число элементов файлов, открытых для чтения и/или записи, которые ссылаются на vnode; число элементов файлов откры- открытых для записи, которые ссылаются на vnode, и число страниц и буферов, связан- связанных с vnode. 1 От index node - «узел индекса». К сожалению, общепринятого перевода для этого термина не существует. - Примеч. науч. ред. ~ От virtual node - «виртуальный узел». - Примеч. пер.
292 Глава 6. Обзор системы ввода/вывода # Указатель на структуру монтирования описывает файловую систему, содержащую объект, представленный vnode. Различные сведения для упреждающего чтения файла. # Ссылка на связанный с vnode vm_object. Ссылка на состояние специальных устройств, сокетов и fifo. Мьютекс для защиты флагов и счетчиков внутри vnode. # Блокировка, управляемая менеджером блокировок, для защиты частей vnode, которые могут измениться, когда выполняется операция ввода/вывода. Поля, используемые кешем имен для отслеживания имен, связанных с vnode. # Указатель на набор операций vnode, определенных для объекта. Эти операции описываются в следующем подразделе. Указатель на индивидуальные данные, необходимые для нижележащего объекта. Для локальной файловой системы этот указатель будет ссылаться на inode; для NFS1 он будет ссылаться на nfsnode. Тип нижележащего объекта (например, обычный файл, каталог, символьное устройство и т.д.). Информация о типе не является строго обязательной, поскольку клиент vnode всегда мог бы вызвать операцию vnode для получения типа нижеле- нижележащего объекта. Однако поскольку тип нужен часто, он не изменяется, а для вызова через интерфейс vnode требуется время, тип объекта кешируется в vnode. Есть чистые и грязные буферы, связанные с vnode. Каждый действительный буфер в системе идентифицируется связанным с ним vnode и начальным смеще- смещением его данных внутри объекта, который представляет vnode. Все буферы, которые были модифицированы, но еще не были записаны обратно, хранятся в списке грязных буферов своего vnode. Все буферы, которые не были модифи- модифицированы или были записаны обратно после последней модификации, хранятся в чистом списке своего vnode. Группировка всех грязных буферов для vnode в один список делает стоимость системного вызова fsync для очистки всех гряз- грязных блоков, связанных с файлом, пропорциональной количеству грязных данных. На некоторых UNIX-системах эта стоимость пропорциональна меньшему значе- значению между размером файла и размером буферного пула. Список чистых буферов используется для освобождения буферов, когда файл удаляется. Поскольку файл никогда не будет прочитан снова, ядро может немедленно отменить все ожи- ожидающие операции ввода/вывода в его грязных буферах и вернуть все его чистые и грязные буферы, поместив в начало списка свободных буферов для немедленно- немедленного повторного использования. Сетевая файловая система. - Примеч науч. ред.
6.5. Интерфейс виртуальной файловой системы 293 * Сохраняется число буферов выполняющихся операций записи. Чтобы ускорить очистку грязных данных, ядро осуществляет эту операцию путем асинхронных записей всех грязных буферов одновременно. Для локальных файловых систем это одновременное проталкивание заставляет поместить все буферы в очередь диска таким образом, что их можно отсортировать в оптимальном порядке для ми- минимизации поиска. Для удаленных файловых систем это одновременное протал- проталкивание заставляет послать все данные в сеть сразу же таким образом, что это может максимизировать ее пропускную способность. Системные вызовы, которые не могут вернуться до тех пор, пока данные не будут в постоянной памяти (такие, как fsync), могут находиться в состоянии сна, ожидая, пока число ожидающих завершения операций вывода не достигнет нуля. Положение vnode в рамках системы было показано на рис. 6.1. Сам vnode под- подключен к нескольким другим структурам внутри ядра, как показано на рис. 6.7. Каждая смонтированная файловая система внутри ядра представлена общей структурой мон- монтирования, которая включает указатель на специфичный для файловой системы блок управления. Все vnode, связанные со специфической точкой монтирования, связаны вместе в один список, возглавляемый этой общей структурой монтирования. Таким образом, при выполнении системного вызова sync для файловой системы ядро может пройти по этому списку, чтобы посетить все активные файлы внутри этой файловой системы. На этом рисунке также показаны списки чистых и грязных буферов, связан- связанные с каждым vnode. Наконец, есть свободный список, связывающий вместе все vnode в системе, которые неактивны (на которые в настоящий момент нет ссылок). Свобод- Свободный список используется, когда файловой системе нужно выделить новый vnode таким образом, чтобы последний мог открыть новый файл (см. раздел 6.4). Операции vnode vnode разработаны как объектно-ориентированный интерфейс. Соответственно ядро манипулирует ими, передавая запросы нижележащему объекту посредством набора определенных операций. Из-за множества различающихся файловых систем, которые поддерживаются в FreeBSD, набор операций, определенных для vnode, и большой, и расширяемый. В отличие от первоначальной реализации vnode фирмой Sun Micro- Microsystems, реализация в FreeBSD допускает динамическое добавление операций vnode либо во время загрузки системы, либо в ходе динамической загрузки в ядро новой фай- файловой системы. В процессе активации файловая система регистрирует набор операций vnode, которые она поддерживает. Затем ядро строит таблицу, которая перечисляет объединение всех операций, поддерживаемых любой файловой системой. Из этой таб- таблицы оно строит вектор операций для каждой файловой системы. Поддерживаемые операции заполняются точками входа, зарегистрированными системой. Файловые сис- системы могут предпочесть, чтобы неподдерживаемые операции заполнялись либо про- процедурой по умолчанию (обычно процедурой для передачи операции на следующий
294 Глава 6. Обзор системы ввода/вывода Смонтированные файловые системы Свободный список struct mount struct vnode - Специфичная для файловой системы информация Специфичная для vnodc информация struct vnode struct mount Специфичная для файловой системы информация struct mount struct j-—^ vnode I Г\ V Специфичная для vnodc информация Специфичная для vnodc информация Специфичная для vnodc информация Специфичная для vnodc информация struct vnode Z \ Специфичная для файловой системы информация Специфичная для vnodc информация Рис. 6.7. Взаимосвязи vnode. Обозначения: D - грязный буфер; С - чистый буфер более нижний уровень; см. раздел 6.7), либо процедурой, возвращающей типичную ошибку «операция не поддерживается» [Heidenamm & Popek, 1994]. В 4.3BSD код локальной файловой системы предоставлял как семантику иерархиче- иерархического именования файловой системы, так и детали управления дисковой памятью. Эти функции связаны лишь незначительно. Чтобы дать возможность экспериментировать с другими методиками дисковой памяти без необходимости воспроизводить всю семантику именования, 4.4BSD разнесла код именования и код хранения в отдельные модули. Операции уровня vnode определяют набор иерархических операций файловой системы. Ниже уровня именования находится отдельный набор операций, определен- определенных для хранения объектов различного размера с использованием плоского простран- пространства имен. Около 60 процентов традиционного кода файловой системы стали кодом
6.5. Интерфейс виртуальной файловой системы 295 управления пространством имен, а оставшиеся 40 процентов стали кодом реализации дискового хранилища файлов. Система 4.4BSD использовала это разделение для под- поддержки двух отдельных схем дисков: традиционной быстрой файловой системы и фай- файловой системы, структурированной для журналирования. В FreeBSD поддержка фай- файловой системой со структурированием для журналирования была опущена из-за отсут- отсутствия тех, кто хотел бы ее поддерживать, но она остается основной файловой системой в NetBSD. Именование и схема дисковой памяти описаны в главе 8. Трансляция имен путей Трансляция имени пути требует ряда взаимодействий между интерфейсом vnode и ниже- нижележащими файловыми системами. Процесс трансляции имени пути проходит сле- следующим образом. 1. Имя пути для трансляции копируется из процесса пользователя или, для запроса уда- удаленной файловой системы, извлекается из сетевого буфера. 2. Определяется начальная точка имени пути либо как корневой каталог, либо как теку- текущий каталог (см. раздел 2.7). vnode для соответствующего каталога становится ката- каталогом поиска (lookup directory), использующимся на следующем шаге. 3. Уровень vnode вызывает специфическую операцию файловой системы lookupQ и передает ей оставшиеся компоненты имени пути и текущий каталог поиска. Обычно нижележащая файловая система будет искать в каталоге поиска сле- следующий компонент имени пути и возвратит получившийся vnode (или ошибку, если имя на самом деле не существует). 4. Если возвращена ошибка, верхний уровень возвращает ошибку. Если имя пути исчерпано, поиск имени пути сделан и возвращенный vnode является результатом поиска. Если имя пути не исчерпано, а возвращенный vnode не является каталогом, тогда уровень vnode возвращает ошибку «это не каталог». Если ошибок не было, верхний уровень проверяет, не является ли возвращенный каталог точкой мон- монтирования другой файловой системы. Если да, тогда каталог поиска становится смонтированной файловой системой; в противном случае каталог поиска стано- становится vnode, возвращенным нижележащим уровнем. Затем поиск продолжается с шага 3. Хотя вызов через интерфейс vnode для каждого компонента имени пути может показаться неэффективным, это обычно необходимо. Причина в том, что нижележа- нижележащая файловая система не знает, какие каталоги использовались в качестве точек мон- монтирования. Поскольку точка монтирования перенаправит поиск в новую файловую систему, важно, чтобы текущая файловая система не продолжила поиск после смон- смонтированного каталога. Хотя локальная файловая система могла бы знать о том, какие каталоги являются точками монтирования, для сервера почти невозможно узнать,
296 Глава 6. Обзор системы ввода/вывода какие из каталогов внутри его экспортированных файловых систем используются в качестве точек монтирования его клиентами. Поэтому используется консервативный подход с проходом лишь одного компонента имени пути на один вызов lookupQ. Есть несколько случаев, когда файловая система будет знать, что в оставшейся части пути больше нет точек монтирования, и будет проходить оставшуюся часть имени пути. Примером является переход в портал, описанный в разделе 6.7. Службы экспортированных файловых систем У интерфейса vnode есть набор служб, которые ядро экспортирует из всех файловых систем, поддерживаемых этим интерфейсом. Первой из них является способность под- поддержки корректировки общих опций монтирования. Эти опции включают следующее. поехес Не исполнять в файловой системе никаких файлов. Эта опция часто исполь- используется, когда сервер экспортирует двоичные файлы для другой архитектуры, которые не могут быть исполнены на самом сервере. Ядро будет отказывать- отказываться исполнять даже сценарии оболочки; если должен быть запущен сценарий оболочки, его интерпретатор должен быть вызван явным образом. nosuid He учитывать флаги установки идентификатора пользователя или установки идентификатора группы для любых исполняемых файлов в файловой систе- системе. Эта опция полезна, когда монтируется файловая система неизвестного происхождения. nodev He разрешать открывать какие-либо специальные устройства в файловой системе. Эта опция часто используется, когда сервер экспортирует каталоги устройств для другой архитектуры. Файловая система была бы смонтирова- смонтирована на сервере с опцией nodev, поскольку значения старшего и младшего но- номеров для сервера бессмысленны. Старший и младший номера имеют смысл лишь для импортирующих их клиентов. noatime При чтении файла не обновлять время последнего доступа к нему. Эта опция полезна в файловых системах, где есть множество часто читаемых файлов и где производительность более важна, чем обновление времени доступа к файлу (которое редко когда бывает важным). sync Запрос синхронного осуществления всего ввода/вывода для файловой системы. Для изменения этих флагов не нужно демонтировать и повторно монтировать фай- файловую систему; их можно изменить в то время, когда файловая система смонтирована. Кроме того, файловую систему, которая смонтирована только для чтения, можно модернизировать, чтобы разрешить запись. И наоборот, файловую систему, которая допускает запись, можно ограничить доступом только для чтения при условии, что нет открытых для изменения файлов. Системный администратор может принудительно
6.6. Службы, независимые от файловой системы 297 ограничить доступ к системе правом только для чтения, затребовав, чтобы все откры- открытые для записи файлы аннулировали свой доступ. Другой службой, экспортируемой из интерфейса vnode, является возможность получения информации о смонтированной файловой системе. Системный вызов statfs возвращает буфер, который предоставляет числа использованных и свободных диско- дисковых блоков и inode, вместе с точкой монтирования файловой системы и устройством, ячейкой памяти или программой, из которой файловая система смонтирована. Систем- Системный вызов getfsstat возвращает сведения обо всех смонтированных файловых систе- системах. Этот интерфейс позволяет избежать необходимости отслеживать набор смонтиро- смонтированных файловых систем извне ядра, что делается во многих других вариантах UNIX. 6.6. Службы, независимые от файловой системы Интерфейс vnode не только предоставляет нижележащим файловым системам объектно- ориентированный интерфейс, но предусматривает также набор управляющих проце- процедур, которые могут использоваться файловыми системами клиентов. В данном разделе описаны эти средства. Когда закрывается последняя ссылка на файл элемента файла, счетчик использова- использования vnode достигает нуля и интерфейс vnode вызывает операцию vnode inactiveQ. Вызов inactiveQ уведомляет нижележащую файловую систему, что файл больше не используется. Файловая система часто будет использовать этот вызов для записи грязных данных обратно в файл, но обычно не будет возвращать память, хранящую данные. Файловой системе разрешено кешировать файл таким образом, чтобы последний можно было быстро активировать повторно (т.е. без дискового или сетевого ввода/вывода), если файл будет открыт снова. Кроме вызова операции vnode inactiveQ, когда счетчик ссылок достигает нуля, vnode помещается в системный свободный список. В отличие от реализаций vnode многих производителей, в которых фиксированное число vnode, выделенных для каж- каждого типа файловой системы, ядро FreeBSD хранит одну системную коллекцию vnode. Когда приложение открывает файл, который в настоящий момент не имеет vnode в памяти, файловая система клиента вызывает для выделения нового vnode процедуру getnewvnodeQ. Ядро поддерживает два списка свободных vnode: у которых страницы данных кешированы в памяти и у которых нет кешированных в памяти страниц дан- данных. Предпочтение отдается повторному использованию vnode без кешированных страниц, поскольку повторное использование vnode с кешированными страницами вы- вызовет потерю всеми кешированными страницами, связанными с этим vnode, своей под- подлинности. Если бы vnode не систематизировались по отдельности, приложение, которое обходило бы дерево файловой системы с вызовом stat для каждого встречен- встреченного файла, в конечном счете очистило бы все vnode, ссылающиеся на страницы дан- данных, потеряв тем самым подлинность всех кешированных в ядре страниц. Поэтому
298 Глава 6. Обзор системы ввода/вывода при выделении нового vnode процедура getnewvnodeQ в первую очередь проверяет начало свободного списка vnode без кешированных страниц, и лишь если этот список пуст, она выбирает из начала списка vnode с кешированными страницами. Выбрав vnode, процедура getnewvnodeQ затем вызывает операцию vnode reclaimQ для уведомления файловой системой, использующей в настоящий момент этот vnode, что он будет использован повторно. Операция reclaimQ записывает обратно все гряз- грязные данные, связанные с нижележащим объектом, удаляет нижележащий объект из всех списков, в которых он находится (таких, как списки хешей, использующиеся для его поиска), и освобождает любую вспомогательную память, которая использовалась объектом. Затем vnode возвращается для использования файловой системой нового клиента. Преимуществом наличия единой глобальной таблицы vnode является то, что память ядра, выделенная vnode, используется более эффективно, чем с несколькими специфическими для файловых систем коллекций vnode. Рассмотрите систему, которая хочет выделить память для 1000 vnode. Если система поддерживает 10 типов файловых систем, тогда каждый тип файловой системы получит 100 vnode. Если боль- большая часть активности перемещается в одну файловую систему (например, во время компиляции ядра, расположенного в локальной файловой системе), все активные файлы придется хранить в 100 vnode, выделенных этой файловой системе, тогда как оставшиеся 900 vnode сидят без дела. В системе FreeBSD для активной системы могли бы использоваться все 1000 vnode, позволяя кешировать в памяти значительно боль- больший набор файлов. Если фокус активности перемещается в другую файловую систему (например, компилирование программы на смонтированной файловой системе NFS), vnode переместились бы из ранее активной локальной файловой системы в файловую систему NFS. В этом случае также был бы значительно больший набор кешированных файлов, чем если бы были доступны лишь 100 vnode при использовании разделенного набора vnode. Операция reclaimQ является отделением нижележащего объекта файловой систе- системы от самого vnode. Эта возможность, в сочетании с возможностью связывать с vnode новые объекты, предоставляет функциональные возможности с полезностью, которая далеко превосходит простое разрешение перемещать vnode из одной файловой систе- системы в другую. Путем замещения существующего объекта объектом из файловой систе- системы dead1 - файловой системы, в которой все операции, кроме close, завершаются не- неудачей, - ядро аннулирует объект. Внутренне это аннулирование объекта обеспечива- обеспечивается процедурой vgoneQ. Эта служба аннулирования используется для управления сеансами, когда все ссылки на управляющий терминал аннулируются, когда лидер сеанса завершает работу. Аннулирование работает следующим образом. Все дескрипторы открытых термина- терминалов внутри сеанса ссылаются на vnode для специального устройства, представляющего Dead (англ.) - здесь: заблокированный. - Примеч. пер.
6.6. Службы, независимые от файловой системы 299 терминал сеанса. Когда для этого vnode вызывается vgoneQ, нижележащее специаль- специальное устройство отсоединяется от vnode и замещается файловой системой dead. Любые последующие операции для vnode будут завершаться ошибкой, поскольку открытые дескрипторы больше не ссылаются на терминал. В конечном счете все процессы завершатся и закроют свои дескрипторы, вызвав уменьшение счетчика ссылок до нуля. Процедура inactiveQ для файловой системы dead возвращает vnode в начало свободно- свободного списка для немедленного повторного использования, поскольку никогда не будет возможно снова получить ссылку на vnode. Служба аннулирования поддерживает принудительное демонтирование файловых систем. Если ядро находит при демонтировании файловой системы активный vnode, оно просто вызывает процедуру vgoneQ, чтобы отсоединить активный vnode от объек- объекта файловой системы. Процесс с открытыми файлами или текущими каталогами внутри файловой системы обнаружит, что они просто исчезли, как если бы были уда- удалены. Можно также уменьшить права доступа смонтированной файловой системы с доступа чтения-записи на доступ только для чтения. Вместо аннулирования доступа для каждого активного файла внутри файловой системы, доступ будет аннулирован лишь для тех файлов, у которых ненулевое число ссылок для записи. В заключение возможность аннулировать объекты экспортируется процессам через системный вызов revoke. Этот системный вызов может использоваться для обес- обеспечения контролируемого доступа к устройству, такому, как порт псевдотерминала. Сначала владение устройством изменяется на нужного пользователя, а право доступа устанавливается только для владельца. Потом имя устройства аннулируется, чтобы устранить ненужных посредников, которые его уже открыли. После этого только новый владелец может открыть устройство. Кеш имен Управление кешем имен является еще одной службой, которая предоставляется процедура- процедурами управления vnode. Интерфейс предоставляет возможность добавлять имя и соот- соответствующий ему vnode, искать имя для получения соответствующего vnode и удалять определенное имя из кеша. Кроме предоставления возможности удаления определен- определенных имен, интерфейс предусматривает также эффективный способ отменять все имена, которые ссылаются на определенный vnode. У каждого vnode есть список, свя- связывающий все свои элементы в кеше имен. Когда ссылки на vnode должны быть удалены, каждый элемент в списке удаляется. У каждого vnode каталога есть также второй список всех элементов кеша для имен, которые содержатся внутри него. Когда должен быть удален vnode каталога, он должен удалить все элементы кеша имен в этом втором списке. Элементы кеша имен должны удаляться каждый раз, когда он исполь- используется повторно посредством getnewvnodeQ или когда это специально запрошено клиентом (например, когда каталог переименовывается).
300 Глава 6. Обзор системы ввода/вывода Процедуры управления кешем допускают также отрицательное кеширование. Если в каталоге ищется имя и не находится, это имя может быть введено в кеш вместе с нулевым указателем для его соответствующего vnode. Если впоследствии ищется это имя, оно будет найдено в таблице имен, и поэтому ядро сможет избежать просмотра всего каталога для определения того, что имени там нет. Если имя добавляется в ката- каталог, тогда следует поискать это имя в кеше и удалить его, если будет найден отрица- отрицательный элемент. Отрицательное кеширование обеспечивает значительное повышение производительности из-за поисков путей в командной оболочке. При выполнении команды многие оболочки будут по очереди просматривать каждый путь в поисках исполняемого файла. Обычно запускаемые исполняемые файлы будут повторно искаться в каталогах, в которых их нет. Отрицательное кеширование ускоряет эти поиски. Управление буферами Изначально системы UNIX делили оперативную память на два главных пула. Первым был пул виртуальной памяти, который использовался для кеширования страниц про- процесса. Вторым был буферный пул, который использовался для кеширования данных файловой системы. Оперативная память распределялась между этими двумя пулами при загрузке системы, и после их создания перемещение памяти между пулами не осу- осуществлялось. С добавлением системного вызова ттар ядро стало поддерживать отображение файлов в адресное пространство процесса. Если файл отображен с атрибутом MAP_SHARED, изменения, сделанные в отображенном файле, должны быть записаны обратно на диск и отображаться системными вызовами read, делаемыми другими про- процессами. Трудно обеспечить эту семантику, если копии файла есть как в буферном кеше, так и в кеше виртуальной памяти. Поэтому FreeBSD объединила буферный кеш и кеш виртуальной памяти в единый кеш страниц. Как описано в главе 5, виртуальная память разделена на пул страниц, хранящих содержимое файлов, и пул анонимных страниц, хранящих части процесса, которые не резервируются файлом, такие, как стек и куча. Страницы, резервируемые файлом, идентифицируются их vnode и логическим номером блока. Вместо того чтобы переде- переделать все файловые системы для поиска страниц в пуле виртуальной памяти, был создан уровень эмуляции буферного кеша. Уровень эмуляции имеет тот же интерфейс, как и старые процедуры буферного кеша, но он работает путем поиска запрошенных страниц в кеше виртуальной памяти. Когда файловая система запрашивает блок файла, уровень эмуляции вызывает систему виртуальной памяти, чтобы посмотреть, не нахо- находится ли он в памяти. Если нет, система виртуальной памяти организует его считыва- считывание. Обычно страницы в кеше виртуальной памяти не отображаются в адресное про- пространство ядра. Однако файловой системе часто нужно обследовать запрашиваемые ею блоки - например, если это каталог или метаданные файловой системы. Таким образом, уровень эмуляции буферного кеша должен не только найти запрошенный
6.6. Службы, независимые от файловой системы 301 блок, но также и выделить некоторую часть адресного пространства ядра и отобразить в него запрошенный блок. Затем файловая система использует буфер для чтения, записи или манипулирования данными, а закончив, освобождает буфер. После освобо- освобождения буфер в течение короткого времени может удерживаться, но вскоре разрушает- разрушается путем удаления отображения ядра, обнуления счетчика использования страниц виртуальной памяти и освобождения заголовка. Система виртуальной памяти не имеет какого-либо способа для описания блоков, которые идентифицируются как блоки, связанные с диском. Небольшой пережиток буферного кеша остается для хранения этих дисковых блоков, которые используются для хранения вспомогательных данных файловой системы, таких, как суперблок, битовые карты и inode. Внутренний интерфейс ядра к уровню эмуляции буферного кеша прост. Файловая система выделяет и заполняет буферы, вызывая процедуру breadQ. breadQ принимает vnode, логический номер блока и размер, а возвращает указатель на заблокированный буфер. Подробности того, как создается буфер, приведены в следующем подразделе. Любой поток, который пытается получать буфер, будет помещен в состояние сна до тех пор, пока буфер не освободится. Буфер может быть освобожден одним из четырех способов. Если буфер не был мо- модифицирован, он может быть просто освобожден посредством brelseQ, который про- проверяет наличие каких-либо ожидающих его освобождения потоков. Если есть ждущие потоки, они пробуждаются. В противном случае буфер разрушается путем возвраще- возвращения его содержимого обратно в систему виртуальной памяти с освобождением его ото- отображения в адресное пространство ядра и освобождением буфера. Если буфер был модифицирован, он называется грязным. Грязные буферы должны быть в конечном счете записаны обратно в свою файловую систему. В зависимости от срочности, с которой должны быть записаны данные, доступны три процедуры. В обычном случае используется bdwriteQ. Поскольку вскоре буфер может быть моди- модифицирован снова, он должен быть помечен как грязный, но не должен записываться сразу. После того как буфер помечен грязным, он возвращается в список грязных буферов, и любой поток, который его ожидает, пробуждается. Эвристика заключается в том, что, если буфер будет вскоре снова модифицирован, ввод/вывод будет теряться даром. Поскольку буфер обычно хранится до записи от 20 до 30 секунд, поток, осуще- осуществляющий множество небольших записей, не будет осуществлять повторный доступ к диску или сети. Если буфер был полностью заполнен, маловероятно, что он будет вскоре снова записан, поэтому его нужно освободить с помощью bawriteQ. bawriteQ назначает ввод/ вывод для буфера, но дает вызывающему возможность продолжать работать, пока завершится вывод. Последним случаем является bwiiteQ, который гарантирует, что запись будет заверше- завершена до продолжения работы. Поскольку bwriteQ может создать для записывающего
302 Глава 6. Обзор системы ввода/вывода долгую задержку, он используется лишь когда операция требовательна к обеспечению согласованности файловой системы после системного сбоя или когда обслуживается протокол удаленной файловой системы без поддержки состояний, такой, как NFS. Буфер, записываемый с помощью bawriteQ или bwriteQ, помещается в соответст- соответствующую очередь вывода. Когда вывод завершается, вызывается процедура brelseQ, чтобы пробудить все потоки, которые ее ожидают, или, если немедленной потребности в ней нет, чтобы разрушить буфер. Некоторые буферы, хотя и чистые, могут вскоре снова потребоваться. Чтобы избе- избежать издержек по повторному созданию и разрушению буферов, уровень эмуляции буферов предоставляет процедуру bqrelseQ, чтобы дать файловой системе возмож- возможность уведомить, что вскоре ожидается повторное использование буфера. Процедура bqrelseQ вместо разрушения помещает буфер в чистый список. bfreelist заблокированные грязные чистые Рис. 6.8. Моментальный снимок буферного пула. Обозначения: V - vnode; X - файловое смещение На рис. 6.8 показан моментальный снимок буферного пула. Буфер с действитель- действительным содержимым содержится ровно в одной хеш-цепи buflmsh. Ядро использует хеш- цепочки для быстрого определения того, находится ли блок в буферном пуле, и если да, его нахождения. Буфер удаляется, лишь когда его содержимое становится недейст- недействительным или когда он повторно используется для других данных. Поэтому даже если буфер используется одним потоком, другой поток его по-прежнему может найти, хотя он будет заблокирован таким образом, что не будет повторно использоваться до тех пор, пока его содержимое не будет согласованным. Кроме появления в списке хешей каждый незаблокированный буфер появляется ровно в одном свободном списке. Первый свободный список является списком ЗАБЛО- ЗАБЛОКИРОВАННЫХ (LOCKED). Буферы в этом списке не могут сбрасываться из кеша.
6.6. Службы, независимые от файловой системы 303 Этот список первоначально был предназначен для хранения данных суперблока; в FreeBSD он хранит лишь буферы, которые записываются в фоновом режиме. При фоновой записи содержимое грязного буфера копируется в другой анонимный буфер. Затем анонимный буфер записывается на диск. Пока записывается анонимный буфер, первоначальный буфер можно продолжать использовать. Фоновые записи исполь- используются главным образом для быстро и постоянно меняющихся блоков, таких, как хра- хранящих битовые карты распределения файловой системы. Если бы блок, хранящий битовую карту, записывался обычным образом, он был бы заблокирован и недоступен во время ожидания в очереди диска на запись. Таким образом, исполнение приложе- приложений, пытающихся записать файлы в области, описываемой битовой картой, было бы заблокировано в ожидании завершения записи битовой карты таким образом, чтобы они могли обновить эту битовую карту. Используя для записей битовых карт фоновый режим, приложениям редко приходится ждать обновления битовой карты. Вторым списком является ГРЯЗНЫЙ (DIRTY) список. В этом списке хранятся буферы, которые были модифицированы, но еще не записаны на диск. ГРЯЗНЫЙ список управляется посредством алгоритма наиболее давнего использования. Когда в ГРЯЗНОМ списке находится буфер, он удаляется и используется. Затем буфер воз- возвращается в конец ГРЯЗНОГО списка. Когда грязными являются слишком много буферов, ядро запускает демон буферов. Демон буферов записывает буферы, начиная с начала ГРЯЗНОГО списка. Таким образом, повторно записываемые буферы будут продолжать перемещаться в конец ГРЯЗНОГО списка и скорее всего не будут прежде- преждевременно записаны или повторно использованы для новых блоков. Третьим свободным списком является ЧИСТЫЙ (CLEAN) список. Этот список хранит блоки, которые файловая система в настоящий момент не использует, но использование которых в скором времени ожидается. ЧИСТЫЙ список также управля- управляется посредством алгоритма наиболее давнего использования. Если запрошенный блок найден в ЧИСТОМ списке, он возвращается в конец списка. Последним списком является список пустых буферов - ПУСТОЙ список. Пустые буферы - это просто заголовки без связанной с ними памяти. Они хранятся в этом списке в ожидании еще одного запроса отображения. Когда нужен новый буфер, ядро сначала проверяет, сколько памяти выделено существующим буферам. Если используемая память ниже допустимого порога, из ПУСТОГО списка создается новый буфер. В противном случае из начала ЧИСТОГО списка удаляется самый старый буфер. Если ЧИСТЫЙ список пуст, пробуждается демон буферов для очистки и освобождения буфера из ГРЯЗНОГО списка. Реализация управления буферами Рассмотрев использующиеся для управления буферным пулом функции и алгоритмы, мы можем теперь обратить свое внимание на требования к реализации для обеспече- обеспечения согласованности данных в буферном пуле. На рис. 6.9 показаны процедуры под-
304 Глава 6. Обзор системы ввода/вывода Взять буфер из свободного JL bread () |—**\VOPJSTRATEGYQ I Выполнить ввод списка и пометить, как занятый I bremfree () р~| getblk () Проверить наличие буфера в свободном списке Найти следующий доступный буфер в свободном списке getnewbuf() allocbuf{) Настроить запрошенный размер памяти в буфере Рис. 6.9. Процедурный интерфейс системы выделения буферов держки, которые реализуют интерфейс для получения буферов. Главным интерфейсом для получения буфера является bread(), который вызывается с запросом блока данных указанного размера для указанного vnode. Есть также связанный с ним интерфейс, breadnQ, который как получает запрошенный блок, так и начинает упреждающее чтение дополнительных блоков. breadQ сначала вызывает getblkQ, чтобы выяснить, доступен ли блок данных в существующем буфере. Если блок в буфере доступен, getblkQ вызывает bremfreeQ, чтобы забрать буфер из любого свободного списка и заблокировать его; затем breadQ может вернуть буфер вызывающему. Если буфер не находится уже в существующем буфере, getblkQ вызывает getnewbufQ, чтобы выделить новый буфер, используя алгоритм, описанный в предыдущем подраз- подразделе. Затем новый буфер передается allocbufQ, которая отвечает за определение того, как составить содержание буфера. Обычным случаем является, когда буфер должен содержать логический блок файла. В этом случае allocbufQ должен запросить необходимый блок от системы виртуальной памяти. Если в системе виртуальной памяти еще нет нужного блока, она организует его загрузку в свой кеш страниц. Затем процедура allocbufQ выделяет уча- участок адресного пространства ядра соответствующего размера и запрашивает у системы виртуальной памяти отображение нужного блока файла в это адресное пространство. Потом буфер помечается как заполненный и возвращается через getblkQ и breadQ. Другим случаем является, когда буфер должен содержать блок вспомогательных данных файловой системы, таких, как битовая карта или блок inode, которые связаны с дисковым устройством, а не с файлом. Поскольку у виртуальной памяти нет (в настоя- настоящий момент) возможности отследить такие блоки, они могут содержаться в памяти лишь внутри буферов. В этом случае allocbufQ должен вызвать процедуру ядра mallocQ, чтобы выделить память для хранения блока. Затем процедура allocbufQ воз- возвращает буфер getblkQ и breadQ, пометив его занятым и незаполненным. Заметив, что буфер не заполнен, breadQ передает его процедуре strategyQ для нижележащей файловой системы, чтобы прочесть в него данные. Когда чтение завершается, буфер возвращается. Для поддержания согласованности файловой системы ядро должно гарантировать, что дисковый блок отображен самое большее в один буфер. Если бы один и тот же дис- дисковый блок присутствовал в двух буферах и оба буфера были помечены как грязные,
6.7. Наращиваемые файловые системы 305 система не смогла бы определить, какой буфер содержит более свежую информацию. На рис. 6.10 показан пример выделения. В середине рисунка блоки на диске. Над диском изображен старый буфер, содержащий 4096-байтный фрагмент файла, который предположительно был удален или укорочен. Собираются использовать новый буфер для хранения 4096-байтного фрагмента файла, который предположительно создается и который повторно использует часть памяти, ранее занятой старым файлом. Ядро поддерживает согласованность, очищая старые буферы, когда файлы укорачиваются или удаляются. Каждый раз, когда файл удаляется, ядро проходит по его списку гряз- грязных буферов. Для каждого буфера ядро отменяет его запрос чтения и разрушает буфер таким образом, чтобы его нельзя было снова найти в буферном пуле. Для частично уре- урезанного файла отменяются лишь буферы, следующие за точкой усечения. Потом систе- система может выделить новый буфер, зная, что он уникально отображает соответствующие дисковые блоки. ,.,,,,, Старый буфер 132|33|34]35 36l37|38l39l40|41 42|43|44145|4б|47| Диск Новый буфер i i i i i i i РИС. 6.10. Потенциальное перекрывание выделения буферов 6.7. Наращиваемые файловые системы Начальный интерфейс vnode был просто объектно-ориентированным интерфейсом к нижележащей файловой системе. По мере роста требований к возможностям новой файловой системы стало желательным найти способы их предоставления без необхо- необходимости модификации существующего стабильного кода файловой системы. Одним из подходов является предоставление механизма для наращивания нескольких файловых систем одну поверх другой [Rosenthal, 1990]. Идеи наращивания были усовершенство- усовершенствованы и реализованы в системе 4.4BSD [Heidemann & Popek, 1994]. Реализация наращи- наращиваемости была в FreeBSD усовершенствована, но семантика осталась в основном неиз- неизменной с 4.4BSD. Нижний элемент стека vnode имеет тенденцию быть дисковой фай- файловой системой, тогда как уровни, использующиеся выше него, обычно преобразуют свои аргументы и передают их более низкому уровню. Во всех UNIX-системах команда mount принимает в качестве источника специаль- специальное устройство и отображает это устройство в точку монтирования каталога в сущест- существующей файловой системе. Когда файловая система смонтирована в каталоге, преды- предыдущее содержимое этого каталога оказывается скрытым; видимо только содержимое корня вновь смонтированной файловой системы. Для большинства пользователей
306 Глава 6. Обзор системы ввода/вывода результатом ряда команд монтирования, выполняемых при запуске системы, является создание единого цельного дерева файловой системы. Наращивание также использует для создания новых уровней команду mount. Команда mount помещает новый уровень в стек vnode; команда amount удаляет уро- уровень. Подобно монтированию файловой системы, стек vnode видим всем работающим в системе процессам. Команда mount идентифицирует нижележащий уровень в стеке, создает новый уровень и присоединяет этот уровень к пространству имен файловой системы. Новый уровень может быть присоединен к тому же месту, что и старый уро- уровень (покрывая старый уровень), или в другое место на дереве (делая видимыми оба уровня). Пример приведен в следующем подразделе. Если уровни присоединены к различным местам в пространстве имен, один и тот же файл будет виден в нескольких местах. Доступ к файлу под именем пространства имен нового уровня будет идти в новый уровень, а под пространством имен старого уровня - только в старый уровень. Когда осуществляется доступ к файлу (например, open, read, stat или close) к vnode в стеке, у этого vnode есть несколько возможностей. * Выполнить запрос и вернуть результат. * Передать операцию без изменений vnode следующего уровня в стеке. Когда опера- операция возвращается из нижележащего уровня, он может изменить результаты или просто вернуть их. * Изменить операнды, предоставленные с запросом, а затем передать их vnode сле- следующего нижележащего уровня. Когда операция возвращается из нижележащего vnode, он может изменить результаты или просто вернуть их. Если операция передается до конца стека без обработки ее каким-либо уровнем, тогда интерфейс вернет ошибку «операция не поддерживается». Интерфейсы vnode, выпущенные до 4.4BSD, реализовывали операции vnode в виде косвенных вызовов функций. Требования, чтобы промежуточные уровни стека передавали операции нижележащим уровням и чтобы в систему можно было добав- добавлять новые операции во время загрузки системы или модуля, означают, что этот подход больше не годится. Файловые системы должны иметь возможность передавать операции, которые могут не быть определены в то время, когда была реализована фай- файловая система. Кроме передачи функции уровень файловой системы должен также передавать параметры функции, тип и число которых неизвестны. Чтобы разрешить эти две проблемы ясным и переносимым способом, ядро поме- помещает в структуру аргументов имя операции vnode и ее аргументы. Эта структура аргу- аргументов передается затем операции vnode как один параметр. Таким образом, все вызовы операции vnode всегда будут иметь ровно один параметр, который является указателем на структуру аргументов. Если операция vnode поддерживается файловой системой, она будет знать, чем являются аргументы и как их интерпретировать. Если
6.7. Наращиваемые файловые системы 307 /* * Проверить право на чтение для файла х xvp''. */ if (error = VOP_ACCESS(vp, VREAD, cred, td)) return (error); /* * Проверить права доступа для файла. */ int ufs_access( struct vop_access_args { struct vnodeop_desc *a_desc; /* описание операции */ struct vnode *a_vp; /* файл для проверки */ int a_mode; /* искомый режим доступа */ struct ucred *a_cred; /* пользователь, производящий поиск */ struct thread *a_td; /* связанный поток */ } *ap); { if (permission granted) return A); return @); } РИС. 6.11. Вызов и заголовок функции для операции vnode access это неизвестная операция vnode, тогда общая процедура обхода может вызвать ту же самую операцию в следующем нижележащем уровне, передав операции ту же самую структуру аргументов, которую получила. Кроме того, первым аргументом каждой операции является указатель на описание операции vnode. Это описание предоставля- предоставляет процедуре обхода информацию об операции, включая имя операции и размещение параметров операции. На рис. 6.11 показан пример вызова проверки прав доступа и его реализация для файловой системы UFS. Обратите внимание, что структура vop_access_args объявляется обычно в заголовочном файле, но здесь она объявлена на месте функции, чтобы упростить пример. Простые уровни файловых систем Простейшим уровнем файловой системы является nullfs. Она не делает преобразова- преобразований своих аргументов, просто передавая все запросы, которые получает, и возвращая все результаты, которые получает, обратно. Хотя она не предоставляет полезных функ- функциональных возможностей, если она просто наложена поверх существующего vnode,
308 Глава 6. Обзор системы ввода/вывода nullfs может предоставить файловую систему обратной связи, смонтировав файловую систему с корнем в своем исходном vnode в каком-нибудь другом месте внутри дерева файловой системы. Код для nullfs является также отличной отправной точкой для разработчиков, которые хотят построить свои собственные уровни файловой системы. Примеры, которые можно построить, включают уровень сжатия данных или уровень шифрования. Внешний административный экспорт 1 Сервер NFS /export uid/gid mapping Локальный административный экспорт Сервер NFS /local ] с UFS FFS Операция не поддерживается РИС. 6.12. Расширяемые vnode Пример стека vnode показан на рис. 6.12. Рисунок изображает локальную файло- файловую систему на дне стека, которая экспортируется из /local через уровень NFS. Клиен- Клиенты в пределах административного домена сервера могут импортировать файловую систему /local непосредственно, поскольку предполагается, что они все используют обычное отображение UID на имена пользователей. Файловая система umapfs работает во многом подобно файловой системе nullfs. В частности, том, что она предоставляет отображение дерева файлов с корнем в фай- файловой системе /local в точке монтирования /export. Кроме предоставления копии фай- файловой системы /local в точке монтирования /export она преобразует мандаты каждого системного вызова, сделанного для файлов в пределах файловой системы /export. Ядро осуществляет преобразование, используя отображение, которое было предостав- предоставлено как часть системного вызова mount, создавшего уровень umapfs. Файловая система /export может быть экспортирована клиентам из внешнего административного домена, который использует другие UID и GID. Когда для файло- файловой системы /export поступает запрос NFS, уровень umapfs изменяет мандат внешнего клиента, отобразив UID, использующийся во внешнем клиенте, на соответствующий UID, использующийся в локальной системе. Запрошенная операция с измененным мандатом передается нижележащему уровню, соответствующему файловой системе /local, где он обрабатывается идентично локальному запросу. Когда результат возвра- возвращается отображающему уровню, все возвращенные мандаты отображаются в обрат- обратном порядке таким образом, чтобы они были преобразованы из локальных UID во внешние UID, и этот результат отсылается в виде ответа NFS.
6.7. Наращиваемые файловые системы 309 У такого подхода три преимущества. 1. Нет накладных расходов по преобразованию для локальных клиентов. 2. Для поддержки отображений не требуется изменять код локальной файловой сис- системы или код NFS. 3. У каждого внешнего домена может быть свое отображение. Домены с простыми отображениями потребляют значительное количество памяти и работают быстро; домены с большими и сложными отображениями могут поддерживаться без сни- снижения производительности более простых окружений. Наложение vnode является эффективным подходом для добавления расширений, таких, как служба umapfs. Файловая система union Файловая система union является другим примером промежуточного уровня файловой системы. Подобно nullfs, она не хранит данные, но просто предоставляет преобразова- преобразование пространства имен. Она неточно моделируется в работе файловой системы 3-D [Когп & Krell, 1989], файловой системы Translucent [Hendricks, 1990] и Automounter [Pendry & Williams, 1994]. Файловая система union берет существующую файловую систему и прозрачным образом перекрывает ее другой файловой системой. В отличие от большинства других файловых систем монтирование union не закрывает каталог, в который файловая система монтируется. Вместо этого она отображает логическое объединение обоих каталогов и дает возможность доступа к обоим деревьям каталогов одновременно [Pendry & McKusick, 1995]. /usr/src /tmp/src /usr/src src I 1 src I выглядит как РИС. 6.13. Файловая система со смонтированной union. Файловая система /usr/src внизу, а файловая система /tmp/src наверху Небольшой пример стека монтирования union показан на рис. 6.13. Здесь нижним уровнем стека является файловая система src, которая включает исходный код для про- программы shell. Будучи простой программой, она содержит лишь один исходный и один заголовочный файл. Верхний уровень, поверх которого был смонтирован union src, первоначально содержал лишь каталог src. Когда пользователь изменяет каталог на shell, в верхнем уровне создается каталог с тем же самым именем. Каталоги на верхнем уровне, соответствующие каталогам на нижнем уровне, создаются лишь в том случае, если они встречаются при прохождении верхнего уровня. Если пользователь должен
310 Глава 6. Обзор системы ввода/вывода был запустить рекурсивное прохождение дерева с корнем в вершине места монтирова- монтирования union, результатом было бы полное дерево каталогов, соответствующих нижележа- нижележащей файловой системе. В нашем примере пользователь теперь набирает make в ката- каталоге shell. На верхнем уровне в стеке union создается исполняемый файл sh. Для поль- пользователя листинг каталога показывает исходные файлы и исполняемый файл вместе, как показано справа на рис. 6.13. Все уровни файловых систем, за исключением верхнего, рассматриваются, как если бы они были только для чтения. Если файл, находящийся на нижнем уровне, открыт для чтения, для этого файла возвращается дескриптор. Если файл, находящий- находящийся на нижнем уровне, открыт для записи, ядро сначала копирует весь файл на верхний уровень, а затем возвращает дескриптор, ссылающийся на копию файла. В результате есть две копии файла: оригинальный неизмененный файл в нижнем уровне и модифи- модифицированная копия этого файла на верхнем уровне. Когда пользователь делает листинг каталога, все дублирующиеся имена на нижнем уровне скрываются. Когда файл открыт, возвращается дескриптор для этого файла на самом верхнем уровне, на котором появляется это имя. Таким образом, после копирования файла на верхний уровень экзем- экземпляры файла на нижних уровнях становятся недоступными. Сложной частью файловой системы union является обработка и удаление файлов, которые находятся на нижнем уровне. Поскольку нижние уровни не могут быть модифи- модифицированы, единственным способом удалить файл является скрыть его путем создания элемента каталога whiteout на верхнем уровне, whiteout является элементом в каталоге, у которого нет соответствующего файла; он отличается тем, что имеет номер inode, равный 1. Если ядро находит элемент whiteout при поиске имени, поиск останавливается, и возвращается ошибка «файл или каталог отсутствует». Таким образом, файл с тем же самым именем на нижнем уровне выглядит удаленным. Если файл удален из верхнего уровня, элемент whiteout для него нужно создать лишь в том случае, если в нижнем уровне есть файл с тем же именем, который мог бы вновь появиться. Когда процесс создает файл с тем же именем, что и элемент whiteout, элемент whiteout замещается обычным именем, которое ссылается на новый файл. Поскольку новый файл создается на верхнем уровне, он будет маскировать все файлы с тем же именем на нижнем уровне. Когда пользователь делает листинг каталога, элементы whiteout и файлы, которые они маскируют, обычно не видны. Однако существует опция, которая заставляет их появиться. Одной возможностью, которая долго отсутствовала в системах UNIX, является способность восстановления файлов после того, как они были удалены. Для файловой системы union ядро может реализовать восстановление файла очень просто, путем уда- удаления элемента whiteout для раскрытия нижележащего файла. Для файловых систем, которые предоставляют восстановление файлов, пользователи могут восстанавливать файлы путем использования специальной опции команды удаления. Процессы могут восстанавливать файлы, используя системный вызов undelete.
6.7. Наращиваемые файловые системы 311 Когда удаляется каталог, чье имя появляется на нижнем уровне, создается элемент whiteout, как если бы это было для файла. Однако, если позже пользователь попытает- попытается создать каталог с тем же самым именем, как ранее удаленный каталог, файловая сис- система union должна трактовать новый каталог особым образом, чтобы избежать повторного появления предыдущего содержания из нижележащего каталога. Когда создается каталог, замещающий элемент whiteout, файловая система union устанавли- устанавливает в метаданных каталога флаг, показывающий, что этот каталог должен рассматри- рассматриваться особым образом. При осуществлении сканирования каталога ядро возвращает сведения только о каталоге верхнего уровня; оно подавляет перечисление файлов из каталогов с теми же самыми именами на более низких уровнях. Файловую систему union можно использовать для различных целей. * Она дает возможность компиляции для нескольких различных архитектур из общей базы исходного кода. Пул исходного кода смонтирован в NFS на каждой из нескольких машин. На каждой машине хоста локальной файловой системой является union, смонтированная поверх импортированного дерева исходного кода. Когда начинается построение, объектные и бинарные файлы появляются в локаль- локальной файловой системе, которая наложена поверх дерева исходного кода. Этот подход не только позволяет избежать загрязнения пула исходного кода двоичными файлами, но также ускоряет компиляцию, поскольку большая часть трафика фай- файловой системы происходит в локальной файловой системе. * Она дает возможность компилировать исходный код на носителях только для чте- чтения, таких, как CD-ROM. Локальной файловой системой является union, смон- смонтированная поверх исходного кода на CD-ROM. После этого можно переходить в каталоги на CD-ROM и изобразить видимость возможности редактирования и компилирования в этом каталоге. * Она дает возможность создавать индивидуальные каталоги исходного кода. Поль- Пользователь создает каталог исходного кода в своей собственной рабочей области, а затем union монтирует исходные коды системы под этим каталогом. Эта особен- особенность возможна, поскольку ограничения на команду mount были ослаблены. Если была включена опция vfs.usermout вызова sysctl, любой пользователь может про- произвести монтирование, если он владеет каталогом, в котором осуществляется мон- монтирование, и если у него есть соответствующие права доступа на монтируемые устройство или каталог (для монтирования с доступом только для чтения необхо- необходимо право чтения, права чтения и записи требуются для монтирования с досту- доступом для чтения и записи). Демонтировать файловую систему могут лишь пользо- пользователь, который ее смонтировал, или суперпользователь.
312 Глава 6. Обзор системы ввода/вывода Другие файловые системы Есть несколько других файловых систем, включенных как часть FreeBSD. Файловая система portal монтирует процесс в каталог в дереве файлов. Когда используется имя пути, которое пересекает расположение портала, оставшаяся часть пути передается процессу, смонтированному в этой точке. Процесс интерпретирует путь любым спосо- способом, который считает подходящим, затем возвращает дескриптор вызывающему про- процессу. Этот дескриптор может быть для сокета, соединенного с процессом портала. Если да, дальнейшие операции для дескриптора будут передаваться процессу портала, чтобы последний его проинтерпретировал. В качестве альтернативы дескриптор может быть для файла в другом месте файловой системы. Рассмотрите процесс портала, смонтированный в /dialout, использующийся для управления банком набирающих номера модемов. Если бы процессу было нужно под- подключиться к внешнему номеру, он открыл бы /diaIout/15105551212/28800, чтобы ука- указать, что он хочет звонить по номеру 1-510-555-1212 на скорости 28 800 бод. Процесс портала получил бы две последние компоненты имени пути. Используя последний ком- компонент, он определил бы, что ему нужно найти неиспользующийся модем 28 800 бод. Другой компонент он использовал бы в качестве номера для набора. Затем он сделал бы запись для последующего счета и вернул процессу дескриптор для модема. Интересным применением файловой системы портала является предоставление каталога службы Интернета. Например, если процесс портала Интернета смонтирован в /net, открытие /net/tcp/McKusick.COM/smtp возвратит дескриптор сокета TCP вы- вызывающего процесса, который соединен с сервером SMTP на McKusick.COM. Поскольку доступ предоставлен через обычную файловую систему, вызывающему процессу не нужно знать о специальных функциях, необходимых для создания сокета TCP и для установления TCP-соединения [Stevens & Pendry, 1995]. Есть несколько файловых систем, которые разработаны для предоставления удобного интерфейса к информации ядра. Файловая система proofs обычно монтируется в /ргос и предоставляет отображение работающих в системе процессов. Она используется главным образом для отладки, но предоставляет также удобный интерфейс для сбора информации о процессах в системе. Листинг каталога /ргос выдает числовой список всех процессов в системе. Интерфейс /ргос более полно описан в разделе 4.9. Файловая систематике монтируется обычно в /dev/fd и предоставляет список всех активных дескрипторов файлов для текущих работающих процессов. Пример, когда это полезно, - указание приложению, что оно должно прочесть ввод из стандартного ввода. Здесь можно использовать имя пути /dev/fd/O вместо специального соглашения, такого, как использование имени, чтобы сообщить приложению о необходимости прочесть со своего стандартного вывода. Файловая система kernfs обычно монтируется в /kern и содержит файлы, которые содержат различную информацию о системе. Она включает такую информацию, как имя хоста, время дня и версия системы.
Упражнения 313 Наконец, есть файловая система cd9660. Она дает возможность монтировать фай- файловые системы, совместимые с ISO-9660, с или без расширений Rock Ridge. Формат файловой системы ISO-9660 чаще всего используется на CD-ROM. Упражнения 6.1. Где хранятся атрибуты чтения и записи дескриптора открытого файла? 6.2. Почему бит закрытия при исполнении (close-on-exec) находится в таблице дескрипторов процесса, а не в системной таблице файлов? 6.3. Почему подсчитывается число ссылок в элементах таблицы файлов? 6.4. Какие три недостатка блокировок с помощью файлов решаются средствами блокирования дескрипторов FreeBSD? 6.5. Какие две проблемы возникают с обязательными блокировками? 6.6. Почему реализация select разделена между кодом управления дескриптором и низкоуровневыми процедурами? 6.7. Опишите, как флаг выбора процесса используется в реализации select. 6.8. Демон syncer запускается в ходе загрузки системы. Раз в секунду он выпол- выполняет fsync для всех vnode, которые были грязными в течение 30 секунд. Какая проблема возникла бы, если бы этот демон не был запущен? 6.9. Когда vnode помещается в свободный список? 6.10. Почему процедура поиска должна вызывать через интерфейс vnode каждый компонент в имени файла? 6.11. Назовите три причины для аннулирования доступа к vnode. 6.12. Почему заголовки буферов выделяются отдельно от памяти, в которой хранится содержание буфера? 6.13. Асинхронный ввод/вывод предоставляется через системные вызовы aiojread и aiojwrite, а не через традиционные системные вызовы read и write. Какие проблемы возникают с предоставлением асинхронного ввода/вывода в суще- существующем интерфейсе чтения-записи? *6.14. Почему имеются как ЧИСТЫЙ, так и ГРЯЗНЫЙ списки, вместо того чтобы управлять всеми буферами в одном списке? *6.15. Если процесс читает большой файл, блоки файла полностью заполнят кеш виртуальной памяти, вытеснив все остальное содержание. Тогда всем другим процессам в системе придется обращаться к диску для доступа к файловой системе. Напишите алгоритм для управления очисткой буферного кеша. *6.16. Параметры операции vnode передаются между уровнями в структурах. Какие альтернативы есть этому подходу? Объясните, почему ваш подход более или менее эффективен по сравнению с текущим подходом, когда в стеке менее пяти уровней. Сравните также эффективность вашего решения, когда в стеке более пяти уровней.
314 Глава 6. Обзор системы ввода/вывода Ссылки Accetta et al., 1986. М. Accetta, R. Baron, W. Bolosky, D. Golub, R. Rashid, A. Tevanian, & M. Young, "Mach: A New Kernel Foundation for UNIX Development", USENIX Association Conference Proceedings, pp. 93-113, June 1986. Heidemann & Popek, 1994. J. S. Heidemann & G. J. Popek, "File-System Development with Stackable Layers", ACM Transactions on Computer Systems, vol. 12, no. 1, pp. 58-89, February 1994. Hendricks, 1990. D. Hendricks, "A Filesystem for Software Development", USENIX Association Conference Proceedings, pp. 333-340, June 1990. Korn&Krell, 1989. D. Korn &E. Krell, "The 3-D File System", USENIX Association Conference Proceedings, pp. 147-156, June 1989. Lemon, 2001. J. Lemon, "Kqueue: A Generic and Scalable Event Notification Facility", Proceedings of the Freenix Track at the 2001 Usenix Annual Technical Conference, pp. 141-154, June 2001. Pendry & McKusick, 1995. J. Pendry & M. K. McKusick, "Union Mounts in 4.4BSD-Lite", USENIX Association Conference Proceedings, pp. 25-33, January 1995. Pendry & Williams, 1994. J. Pendry & N. Williams, "AMD: The 4.4BSD Automounter Reference Manual", in 4.4BSD System Manager s Manual, pp. 13:1-57, O'Reilly & Associates, Inc., Sebastopol, CA, 1994. Peterson, 1983. G. Peterson, "Concurrent Reading While Writing", A CM Transactions on Programming Languages and Systems, vol. 5, no. 1, pp. 46-55, January 1983. Rosenthal, 1990. D. Rosenthal, "Evolving the Vnode Interface", USENIX Association Conference Proceedings, pp. 107-118, June 1990. Stevens & Pendry, 1995. R. Stevens & J. Pendry, "Portals in4.4BSD", USENIX Association Conference Proceedings, pp. 1-10, January 1995.
Глава 7 Устройства 7.1. Обзор устройств В данной главе описывается часть системы, которая служит средством связи с обору- оборудованием компьютера, как показано в нижней части рис. 6.1. Исторически интерфейс устройств был статическим и простым. Устройства обнаруживались при загрузке сис- системы и впоследствии не изменялись. Файловые системы строились в разделе одного диска. Когда драйвер диска получал запрос от файловой системы на запись блока, он добавлял базовое смещение раздела и осуществлял проверку границ на основе информации из метки своего диска. Затем он выполнял ввод/вывод и возвращал файло- файловой системе результат или ошибку. Типичный драйвер диска мог быть создан в несколько сот строк кода. По мере развития системы с добавлением новых функциональных возможностей сложность системы ввода/вывода возросла. Новые функциональные возможности можно разделить на две категории: * управление диском; * маршрутизация и контроль ввода/вывода. Каждая из этих областей управляется в FreeBSD новой подсистемой. Управление диском состоит в организации множества способов, которыми диск может быть использован для построения файловой системы. Диск можно разделить на несколько слайсов1 (slices), каждый из которых может использоваться для поддержки различных операционных систем. Каждый из этих слайсов может быть разделен дальше на разделы (partitions), которые могут использоваться для поддержки файловых систем, как это дела- делалось исторически. Однако можно также объединить несколько слайсов и/или разделов для 1 О различиях в терминологии, относящейся к разбиению диска, в FreeBSD и других источниках см. примечание в разделе 6.3. - Примеч. науч. ред.
316 Глава 7. Устройства создания виртуального раздела, на котором можно построить файловую систему, зани- занимающую несколько дисков. Виртуальный раздел может объединить несколько разделов в виде отдельных полос файловой системы в нескольких дисках, давая тем самым файло- файловую систему с высокой пропускной способностью. Либо лежащие в основе разделы могут соединяться в RAID (Redundant Array of Independent Disks - матрица независимых дисков с избыточностью) для обеспечения более высокого уровня надежности и доступности по сравнению с одним диском. Либо разделы могут быть организованы в две группы одинако- одинакового размера и зеркально отображены для обеспечения еще более высокого уровня надеж- надежности и доступности по сравнению с RAID. Объединение подобным образом разделов физического диска в виртуальные разделы называется управлением томами. Вместо встраивания всех этих возможностей во все файловые системы или дисковые драйверы, они были абстрагированы в уровень GEOM (геометрии). Уровень GEOM прини- принимает в качестве входных данных набор доступных в системе дисков. Он отвечает за управ- управления томами. На низком уровне управление томами создает, поддерживает и интерпре- интерпретирует таблицы слайсов и метки дисков, определяя внутри каждого слайса разделы. На более высоком уровне GEOM сочетает путем образования полос, RAID или зеркального отображения разделы физического диска, создавая виртуальные разделы, которые экс- экспортируются вышележащему уровню файловой системы. Виртуальный раздел выглядит для файловой системы как один большой диск. Когда файловая система осуществляет ввод/вывод внутри виртуального раздела, уровень GEOM определяет, какой(ие) диск(и) используется, и разделяет и рассылает запросы ввода/вывода по соответствующим физиче- физическим дискам. Работа уровня GEOM описана в разделе 7.2. Архитектура ввода/вывода PC Изначально архитектуры имели лишь одну или две шины и пару типов дисковых контрол- контроллеров. Как описано в следующем подразделе, современный PC сегодня может иметь несколько типов дисков, подключенных к машине через пять или более различных типов интерфейсов. Сложность самих дисковых контроллеров соперничает со сложно- сложностью самих ранних операционных систем UNIX. Старые контроллеры могли обраба- обрабатывать в одно и то же время лишь один дисковый ввод/вывод. Сегодняшние контрол- контроллеры обычно могут жонглировать до 64 одновременными запросами через схему, называемую помеченной очередью (tagged queueing). При получении запроса контрол- контроллером он назначается на выполнение, завершается, и о его завершении сообщается обратно запрашивающему. Ввод/вывод может также кешироваться в контроллере, чтобы дать возможность обрабатывать будущие запросы быстрее. Другой задачей, выполняемой контроллером, является обеспечение замены дискового сектора с посто- постоянной ошибкой другим, хорошим сектором. Текущая архитектура ввода/вывода PC показана на рис. 7.1. Большие подробности можно найти в Arch [2003]. В левой части рисунка находится процессор, имеющий высокоскоростное соединение через северный мост с оперативной памятью системы
7.1. Обзор устройств 317 Графика ATA PCI USB Звук I \ I / I / Клавиатура Процессор - Северный Южный мост иоЗвывода ^ I / I \ I \ м Память i ' х Встроенный Мышь Firewire (A)PIC Cardbus контроллер Рис. 7.1. Архитектура ввода/вывода PC. Обозначения (перевод см. в тексте): ATA - Advanced Tech- Technology Attachment; USB - Universal Serial Bus; PCI - Peripheral Component Interconnect; (A)PIC - (Advanced) Programmable Interrupt Controller и графической памятью, управляющей системным дисплеем. Обратите внимание, что кеши L1 и L2 на данном рисунке не показаны, поскольку они считаются частью про- процессора. Далее вправо от северного моста находится соединение с умеренной скоро- скоростью с южным мостом. Южный мост объединяет все шины ввода/вывода системы с высокой пропускной способностью. Эти шины включают следующее. * Шина PCI (Peripheral Component Interconnect - соединение периферийных компо- компонентов). Современными реализациями стандарта PCI с обратной совместимостью являются шины PCI-X и PCI Express. Большинство современных карт ввода/ вывода рассчитаны на подключение через какой-нибудь вариант шины PCI, по- поскольку эта архитектура предусматривает высокие скорости и поддерживает пол- полностью автоматическое конфигурирование. У этой шины также то преимущество, что она доступна на многих других архитектурах компьютеров помимо PC. * Шина ATA (Advanced Technology Attachment - расширенная технология под- подключения). Первоначальная параллельная шина АТА остается рудиментом более ранних конструкций PC. Она поддерживает максимум два диска на канал, не под- поддерживает горячее подключение (изменение дисков во включенной работающей системе) и имеет максимальную скорость передачи 133 Мбайта в секунду по срав- сравнению с максимальной скоростью передачи 320 Мбайт в секунду параллельного интерфейса SCSI (Small Computer System Interface - интерфейс малых ком- компьютерных систем). Поддержка параллельного АТА сохраняется из-за большого количества дешевых дисковых приводов, доступных для подключения к ней. В более новый последовательный АТА добавлена поддержка горячего подключе- подключения и передача данных со скоростями, сравнимыми с параллельным интерфейсом SCSI. * Шина USB (Universal Serial Bus - универсальная последовательная шина). Шина US В предоставляет умеренную скорость ввода (вплоть до 60 Мбайт в секундуI, обычно используемую для видеокамер, карт памяти, сканеров, принтеров и некоторых 1 Скорость USB 1.1 составляет 12 Мбит/с, т.е. 1.5 Мбайт/с. USB 2.0 имеет скорость 480 Мбит/с, т.е. 60 Мбайт/с, так что речь идет о USB 2.O. - Примеч. науч. ред.
318 Глава 7. Устройства более новых вариантов устройств для ввода человеком, таких, как клавиатуры, мыши и джойстики. Шина Firewire (IEEE 1394). Firewire быстрее шины USB, действуя на скорости вплоть до 100 Мбайт в секунду1. Она используется устройствами чтения карт памяти, внешними дисками и некоторыми профессиональными цифровыми камерами. Firewire была создана Apple Computer, но в настоящее время ее обычно можно найти на машинах с архитектурой PC. * Карты CardBus или их более медленные родственники PC card, исторически извест- известные как карты PCMCIA (Personal Computer Memory Card International Association - Международная ассоциация карт памяти персональных компьютеров). Карты CardBus являются вездесущими устройствами размером приблизительно с кредитную карту, которые используются главным образом в портативных ком- компьютерах для предоставления доступа к Ethernet, модему, факсу, беспроводной технологии 802.11, мини-дискам, дисковым контроллерам SCSI и АТА и многим другим функциям. * PIC (Programmable Interrupt Controller - программируемый контроллер прерыва- прерываний). PIC отображает прерывания устройства в значения IRQ (Interrupt ReQuest - запрос прерывания) для процессора. Большинство современных машин исполь- используют IOAPIC (I/O Advanced Programmable Interrupt Controller- усовершенствован- усовершенствованный программируемый контроллер прерываний ввода/вывода), который предос- предоставляет более точный контроль над прерываниями устройств. Все процессоры, начиная с Pentium Pro A997), имели LAPIC (Local Advanced Programmable Interrupt Controller-локальный расширенный программируемый контроллер прерываний), который работал совместно с IOAPIC для поддержки распределения прерываний среди процессоров. Также подключенным к южному мосту является суперчип ввода/вывода, который предоставляет низко скоростной доступ ко многим традиционным интерфейсам PC. Эти интерфейсы включают следующее. * Порты клавиатуры имыши PS2. Более новые машины подключают клавиатуру и мышь через порт USB, но остается много традиционных клавиатур и мышей. * Поддержка звукового стандарта АС97 (Audio CODEC). Этот стандарт дает воз- возможность использовать один DSP (Digital Signal Processor - цифровой процессор сигналов) для поддержки как модема, так и звука. * Встроенный контроллер. Встроенный контроллер присутствует на многих мобиль- мобильных системах и контролирует различные компоненты, включая кнопки питания и спящего режима, фоновой интенсивности свечения экрана и световые индикаторы 1 Вообще-то предельная скорость интерфейса Firewire составляет 400 Мбит/с, т.е. 50 Мбайт/с, а не 100Мбайт/с. Видимо, авторы тут ошиблись. - Примеч. науч. ред.
7.1. Обзор устройств 319 состояния. На современных системах доступ к встроенному контроллеру осуществ- осуществляется через ACPI (Advanced Configuration and Power Interface - усовершенствован- усовершенствованный интерфейс конфигурирования и управления питанием). Стандарт ACPI развивает существующий набор кода BIOS (Basic Input Output System - базовая система ввода-вывода) управления питанием, многочисленных API (Application Programming Interface - интерфейс прикладного программирования) АРМ (Advanced Power Management - расширенное управление питанием) API PNPBIOS (Plug and Play BIOS - самонастраивающийся BIOS) в хорошо определенный механизм конфи- конфигурирования и управления энергопитанием. Спецификация ACPI предоставляет поддержку для организованного перехода от традиционного аппаратного обеспече- обеспечения к аппаратному обеспечению ACPI и допускает существование и использование при необходимости на одной машине множества механизмов [ACPI, 2003]. * Поддержка унаследованной периферии, основанной на шине ISA (Industry Standard Architecture - промышленная стандартная архитектура). Шина ISA была в течение многих лет главной опорой PC, а множество встроенных систем до сих пор используют эту шину. Из-за своих небольших размеров и некогда больших объемов выпуска эта периферия имеет тенденцию к дешевизне. Сегодня она передается к медленным функциям, таким, как модем для коммутируемых линий, 10-мегабитный Ethernet и 16-разрядные звуковые карты. Структура подсистемы ввода/вывода запоминающих устройств большой емкости FreeBSD В ранних версиях FreeBSD было несколько дисковых подсистем. Первая поддержка для дисков АТА и SCSI пришла от Mach 2.5. Поддержка обоих была высокоспецифичной для устройства. Попытка заменить их привела к САМ (Common Access Method - стандарт- стандартный метод доступа), введенному в FreeBSD 3.0, и АТА, введенному в FreeBSD 4.0. Поскольку работы по АТА продолжались, разработчики, ответственные за САМ, попыта- попытались сделать его дополнением к САМ. Однако странные правила резервирования и бло- блокирования модели файла регистров АТА плохо подходили для реализации САМ, поэтому реализация АТА за одним исключением осталась отдельной. САМ является стандартом ANSI (American National Standards Institute - Нацио- Национальный институт стандартизации США) (ХЗ.232-1996). Группой ХЗТ10 была предло- предложена пересмотренная и улучшенная версия САМ, но она никогда не была одобрена [ANSI, 2002]. Хотя САМ главным образом используется для SCSI, он является спосо- способом согласования драйверов НВА (Host Bus Adapter - адаптер главной шины) (в терми- терминологии САМ - драйверов SIM (Software Interface Module - модуль программного интерфейса), транспортного связующего элемента среднего уровня и драйверов пери- периферии. Вряд ли САМ когда-нибудь будет принят в качестве стандарта, но он по-преж- по-прежнему предоставляет полезный каркас для реализации подсистемы хранения.
320 Глава 7. Устройства Реализация САМ FreeBSD поддерживает SPI (SCSI Parallel Interface - параллель- параллельный интерфейс SCSI), волоконно-оптические каналы [ANSI, 2003], UMASS (USB Mass Storage - запоминающие устройства USB), IEEE 1394 (Firewire) и ATAPI (Advanced Technology Attachment Packet Interface - пакетный интерфейс для АТ-совместимых компьютеров). Она содержит периферийные драйверы для дисков (da), CD-ROM (cd), ленточных накопителей (sa), устройств смены лент (tape changers; ch), процессорных устройств (pt) и сервисов шасси (sesI. Кроме того, есть целевой эмулятор, позво- позволяющий компьютеру имитировать любое из поддерживаемых устройств, и промежу- промежуточный интерфейс, дающий пользовательским приложениям возможность посылать запросы ввода/вывода любой управляемой САМ периферии. Работа уровня САМ опи- описана в разделе 7.3. Устройства АТА обладают своей собственной параллельной системой, которая более интегрирована, но менее гибка, чем САМ. Она реализует свой собственный транспорт среднего уровня, который поддерживает диски АТА (ad), RAID-массивы (ar), CD-ROM (acd), гибкие диски (afd) и ленточные накопители (ast). Она поддержи- поддерживает также работу драйверов устройств АТА, подключенных к CardBus или шине PCI и к шине ISA. На время написания в АТА была реализована блокировка на мелком уровне для поддержки многопроцессорности, которая еще не завершена для САМ. Работа уровня АТА описана в разделе 7.4. Структура дисковой подсистемы ввода/вывода FreeBSD показана на рис. 7.2. Как можно видеть, дисковые приводы могут быть подключены к системе через множество шин. Файловая система DEVFS GEOM САМ Периферия т SIM/HBA АТА PCI/ahc ber/isp I Диск АТА ata-all . I ata-pci, ata-isa Чипс~ДТД Firewire/sbp гт cpci n no и Петля или устройство г, ttcd Параллельная шина АТА Параллельная шина SCSI Порт 1394 волоко|шо.уоптРического ПоРт USB последовательный поот А последовательный порт АТА I Приводы SCSI Привод Привод(ы) Привод АТА-приводы волоконно-оптического канала Рис. 7.2. Структура подсистемы дискового ввода/вывода FreeBSD 1 Имеются в виду «умные» корпуса и шасси, сообщающие свою конфигурацию и управляемые по шине SCSI. - Примеч. науч. ред.
7.1. Обзор устройств 321 Самым быстрым и наиболее дорогим соединением является соединение через волоконно-оптический канал. Такие дисковые системы обычно используются на боль- больших серверах или когда данные должны передаваться на расстояние большее корпуса компьютера или стоящего рядом дискового массива. Более обычным быстрым решением является контроллер, который вставляется в шину PCI, такой, как контроллер параллельного SCSI, который может поддерживать до 15 диско- дисковых и ленточных накопителей. Диски SCSI обычно быстрее и надежнее при большой нагрузке, чем ориентированные больше на настольные клиентские системы диски АТА. Диски с параллельным интерфейсом АТА являются самыми дешевыми и повсеме- повсеместными. Диски с последовательным интерфейсом АТА имеют более быстрый доступ и возможности, сопоставимые с возможностями своих более дорогих родственников SCSI. Начало поддержки САМ для АТА стало выявлять то, как САМ может работать с дисковыми и ленточными приводами ATAPI. Протокол ATAPI является подмножест- подмножеством спецификации SCSI MMC (Multi Media Command - управление мультимедиа) и используется лишь для CD-ROM, DVD и ленточных приводов. Диски могут быть также подключены через другие шины, доступные в архитек- архитектуре PC. Сюда входят CardBus, Firewire и USB. Обычно диски подключаются через ин- интерфейс, который действует в качестве моста от интерфейса с шинами PCI или ISA. Шины CardBus, Firewire и USB могут также поддерживать другие виды устройств, которые будут подключены непосредственно к своим драйверам устройств, а не управ- управляться уровнем САМ. Драйверы сетевых устройств предоставляют другую важную часть функциональных возможностей ядра. Вместо их обсуждения в данной главе мы отложим их описание до начала обсуждения сетевого стека в разделе 12.1. Автоконфигурирование является процедурой, проводимой системой для распозна- распознавания и включения аппаратных устройств, присутствующих в системе. Исторически автоконфигурирование проводилось лишь однажды при загрузке системы. В совре- современных машинах, в особенности в портативных, таких, как ноутбуки, устройства регу- регулярно появляются и исчезают во время работы машины. Таким образом, ядро должно быть готово конфигурировать, инициализировать и делать доступными аппаратные средства, когда они появляются, и прекращать операции с устройствами, которые отключены. FreeBSD использует для управления устройствами в системе инфраструк- инфраструктуру драйверов устройств, которая называется newbus («новая шина»). Newbus строит дерево с корнем в абстрактном узле rootO, от которого спускается древоподобная структура вдоль по различным путям ввода/вывода и завершается на различных под- подключенных к машине устройствах. На однопроцессорной системе узел rootO является синонимом центрального процессора. На многопроцессорной машине узел rootO логически подключен к каждому из процессоров. На время написания не все устройст- устройства были переведены под newbus. Пока не поддерживаются, но вскоре должны быть добавлены процессоры, таймеры и PIC. Большинство драйверов, которые не понимают
322 Глава 7. Устройства newbus, предназначено для старых существующих устройств, поддержка которых, вероятно, будет прекращена. Наиболее примечательным примером отсутствующей поддержки являются диски, у которых есть проблемы из-за того, что они групповые (multihomed) (например, доступны через множество шин). Newbus в настоящее время спроектирована для работы в форме древовидной структуры. Ее нужно будет рас- расширить, чтобы разрешить структуру направленного ациклического графа, прежде чем будет возможна поддержка групповых дисков. Автоконфигурирование описано в разделе 7.5, в котором приводятся подробности конфигурирования устройств при их появлении и очистке после их исчезновения. Именование устройств и доступ к ним Исторически FreeBSD использовала для предоставления доступа к аппаратным устройствам в системе статические узлы устройств, расположенные в /dev. У этого подхода есть несколько проблем. Узлы устройств являются постоянными элементами в файловой системе и необяза- необязательно представляют аппаратные средства, действительно подключенные к машине и доступные на ней. * Когда к ядру добавляется новое оборудование, системному администратору нужно создать новые узлы устройств для получения доступа к аппаратному обес- обеспечению. * Если оборудование позже удаляется, узлы устройств остаются, даже если их нельзя больше использовать. Узлам устройств требуется согласование схемы старших и младших номеров в таблицах драйверов устройств в ядре и сценариях оболочки для их создания. В FreeBSD 5.2 статический каталог /dev был заменен новой файловой системой DEVFS, которая монтируется под /dev при загрузке ядра. Когда устройства обнару- обнаруживаются либо при загрузке, либо в ходе работы системы, их имена появляются в файловой системе /dev. Когда устройства исчезают или становятся недоступными, их элементы в /dev исчезают. DEVFS имеет несколько преимуществ перед старым каталогом /dev. В /dev появляются лишь устройства, доступные в настоящее время. * Добавление устройства к системе вызывает появление в /dev его узла устройства, избавляя необходимость создания нового устройства системным администра- администратором. Больше не нужно согласовывать старшие и младшие номера устройств в ядре и сценариях создания устройств или узлах устройств файловой системы.
7.2. Уровень GEOM 323 Одним из преимуществ старой статической /dev было то, что узлам устройств можно было давать нестандартные имена, права доступа, владельцев или группы. Чтобы пре- предоставить ту же самую гибкость, DEVFS имеет механизм установки правил, который по- позволяет автоматизировать эти изменения в новой реализации /dev. Эти наборы правил можно применить при загрузке системы, и их можно создавать или изменять в любое время во время работы системы. Каждое правило предусматривает шаблон для иденти- идентификации затрагиваемых узлов устройств. Для каждого подходящего узла устройства он определяет одно или несколько действий, которые должны быть сделаны. Действия включают создание символической ссылки для предоставления нестандартного имени, а также установки нестандартных прав доступа, владельца или группы. Наборы правил проверяются и применяются каждый раз при создании или уничтожении нового узла устройства. Их можно также проверять и применять при явном запросе системным адми- администратором либо вручную, либо посредством сценария, инициируемого системой. Драйверами устройств каждый раз при создании deviceJ может быть создано в /dev несколько (или ни одного) элементов devj (старших и младших номеров) в качестве части процесса авто конфигурирования. Большинство драйверов устройств создают единственный элемент /dev, но драйверы сетевых устройств не создают никаких устройств, тогда как дисковые устройства могут создавать десятки. В результате клонирования устройств в /dev могут появиться дополнительные элементы. Например, клонирование такого устройства, как псевдотерминал, каждый раз при его открытии соз- создает новое устройство. 7.2. Уровень GEOM Уровень GEOM предоставляет структуру модульного преобразования для запросов дискового ввода/вывода. Эта структура поддерживает инфраструктуру, в которой классы могут делать почти произвольные преобразования запросов дискового ввода/ вывода на их пути от верхней части ядра до драйверов устройств и обратно. GEOM может поддерживать как автоматическую направляемую данными конфигурацию, так и ручную или основанную на сценариях. Преобразования в GEOM включают следующее. Простые вычисления базы и границ, необходимые для деления диска на разделы. Объединение дисков для создания томов RAID, зеркальных или перемежающихся (stripped) логических томов. * Криптографически защищенные логические тома. Сбор статистики ввода/вывода. Оптимизация ввода/вывода, такая, как сортировка диска. В отличие от многих своих предшественников GEOM как расширяема, так и топо- топологически агностична.
324 Глава 7. Устройства Терминология и топологические правила Уровень GEOM является объектно-ориентированным и, следовательно, занимает многое из контекста и семантики объектно-ориентированной терминологии. Преобразо- Преобразование является понятием особого способа изменения запроса ввода/вывода. Примеры включают разбиение диска, зеркальное отображение двух или более дисков и совместную работу нескольких дисков в RAID. Класс реализует одно определенное преобразование. Примерами классов являются дисковый раздел главной загрузочной записи (MBR), метка диска BSD и RAID-массив. Экземпляр класса называется geom. В типичной системе FreeBSD для каждого диска будет один geom класса MBR. MBR делит диск на слайсы, вплоть до четырех штук. Для каждого слайса будет также по одному geom класса BSD с меткой диска BSD. Провайдер является главными воротами, через которые geom предоставляет службу. Типичным поставщиком является логический диск, например /dev/daOsl. Все провайдеры имеют три главных свойства: имя, размер носителя и размер сектора. Потребитель является черным ходом, через который geom подключается к друго- другому провайдеру geom и через который посылаются запросы ввода/вывода. Например, метка MBR обычно будет потребителем диска и провайдером слайсов диска. Топологические отношения между этими элементами следующие. Класс имеет ноль или более экземпляров geom. * Geom выводится ровно из одного класса. Geom имеет ноль или более потребителей. * Geom имеет ноль или более провайдеров. Потребитель может быть подключен лишь к одному провайдеру. Провайдер может иметь несколько подключенных потребителей. * Структура GEOM не может содержать циклы; она должна быть ациклическим направленным графом. Все geom имеют назначенные ранговые номера, которые обнаруживают и предот- предотвращают циклы в ациклическом направленном графе. Этот ранговый номер назначается следующим образом. Geom без подсоединенных потребителей имеет ранг 1. * Geom с подсоединенными потребителями имеет ранг, на один больший, чем наи- наивысший ранг geom провайдера, к которому подключены его потребители. На рис. 7.3 показана простая конфигурация GEOM. В нижней части находится geom, который взаимодействует с уровнем САМ и создает диск daO. У него два потре- потребителя. Справа находится файловая система DEVFS, которая экспортирует полный
7.2. Уровень GEOM 325 образ диска в виде /dev/daO. Слева расположен geom MBR, который интерпретирует метку MBR, найденную в первом секторе диска, для образования двух слайсов - daOsl и da0s2. У обоих этих слайсов есть потребители DEVFS, которые экспортируют их как /dev/daOsl и /dev/da0s2. Первый из этих двух слайсов имеет второго потребителя, geom. метки BSD, который интерпретирует метку BSD, найденную рядом с началом слайса. Метка BSD подразделяет слайс до восьми (могущих перекрываться) разделов с daOsla no daOslh. Все определенные разделы имеют потребителей DEVFS, которые экспортируют их как элементы с /dev/daOsla no /dev/daOslh. Когда смонтирован один из этих разделов, файловая система, в которой он смонтирован, также становится потребителем этого раздела. /dev/daOsla /dev/daOslh daOsla... daOslh Метка BSD /dev/daOsl /dev/da0s2 daOsl da0s2 lMeTKaMBRl /dev/daO Диск РИС. 7.3. Пример конфигурации GEOM Изменение топологии Базовыми операциями являются attach, которая подключает абонента к провайдеру, и detach, которая разрывает связь. Для упрощения автоматической конфигурации дос- доступно несколько более сложных операций. Проба (tasting) представляет собой процесс, происходящий каждый раз при созда- создании нового класса или нового провайдера. Он предоставляет классу шанс автоматиче- автоматически отконфигурировать экземпляр для провайдеров, которые он распознает в качестве своих собственных. Типичным примером является класс дисковых разделов MBR, ко- который будет искать метку MBR в первом секторе и, если она найдена и действительна, инстанциирует geom, чтобы мультиплексировать в соответствии с содержанием MBR. GEOM не определяет, что именно делает класс для распознавания того, должен ли он принять предложенного провайдера, но практичным набором возможностей являются: проверка определенных структур данных на диске; проверка свойств провайдера, наподобие размера сектора или размера носителя; проверка рангового номера geom провайдера; проверка имени метода geom провайдера.
326 Глава 7. Устройства Новый класс будет предложен всем существующим провайдерам, а новый провайдер будет предложен всем классам. Конфигурирование является процессом, когда администратор выдает определенно- определенному классу инструкции инстанциировать себя. Имеется множество способов выразить намерение. Например, может быть указан модуль метки BSD с уровнем замещения (override), заставляющий geom метки диска BSD подключиться к провайдеру, который не был найден подходящим в ходе операции пробы. Операция конфигурирования обычно нужна при первой разметке диска. Покидание (orphaning) является процессом, посредством которого провайдер уда- удаляется во время, когда потенциально он по-прежнему используется. Когда geom остав- оставляет провайдера, все будущие запросы ввода/вывода будут отражаться от провайдера с кодом ошибки, установленным geom. Все абоненты, подключенные к провайдеру, получат уведомления о покидании, и ожидается, что они будут вести себя соответст- соответственно. Geom, который появился в результате обычной операции пробы, должен уничтожить себя, если только у него нет способа продолжать функционировать без покинутого провайдера. Geom с единственной операционной точкой, подобно интерпретирующему метку диска, должен уничтожить себя. Geom с избыточными операционными точками, такие, как поддерживающие RAID или зеркальное отраже- отражение, будут способны продолжать до тех пор, пока не потеряют кворум. Покинутый провайдер может не приводить к немедленному изменению тополо- топологии. Все подключенные абоненты по-прежнему подключены. Любые открытые пути по-прежнему открыты. Любые невыполненные запросы ввода/вывода по-прежнему ожидают выполнения. Типичным сценарием является следующий. * Драйвер устройства обнаруживает диск, который был удален и покинул своего провайдера. * Geom, представляющий диск, получает событие покидания и оставляет всех своих провайдеров. Провайдеры, которые не используются, обычно немедленно уничтожают сами себя. Этот процесс продолжается рекурсивным образом до тех пор, пока все имеющие отношение к делу участки дерева не ответят на событие. В конечном счете обход останавливается, когда он достигает geom устройства на вершине дерева. Geom откажется принимать любые новые запросы, возвращая ошибку. Он будет спать до тех пор, пока не будут возвращены все ожидающие выполнения запросы ввода/вывода (обычно в виде ошибок). Затем он явным образом закроет, отсоединит и разрушит свой geom. Когда все geom выше провайдера исчезли, провайдер отсоединится и разрушит свой geom. Этот процесс распространяется вниз по всему дереву до тех пор, пока очистка не завершится.
7.2. Уровень GEOM 327 Хотя этот процесс кажется окольным, он действительно предоставляет максималь- максимальную гибкость и устойчивость при обработке исчезающих устройств. Обеспечение того, что дерево не «распутывается» до тех пор, пока все отложенные запросы ввода/ вывода не вернутся, гарантирует, что ни одно приложение не будет оставлено завис- зависшим из-за исчезновения аппаратного устройства. Порча (spoiling) является особым случаем покидания, использующимся для защиты против устаревших вспомогательных данных. Пожалуй, проще всего понять порчу на примере. Рассмотрите конфигурацию, показанную на рис. 7.3, в которой сверху есть диск daO, который является geom MBR, предоставляющим daOsl и da0s2. На вершине daOsl geom BSD предоставляет элементы с daOsl a no daOsl h. Как geom MBR, так и geom BSD автоматически конфигурируются на основе структур данных на дисковом носителе. Теперь рассмотрите случай, когда daO открыт для записи, a MBR изменен или переписан. Geom MBR теперь работал бы с устаревшими дан- данными, если только какая-нибудь система уведомления не смогла бы информировать его об обратном. Чтобы избежать устаревших данных, открывание daO для случаев записи вызывает уведомление всех подключенных абонентов, что ведет к конечному саморазрушению geom MBR и geom BSD. Когда daO закрывается, будет снова пред- предложена проба, и, если структуры данных MBR и BSD по-прежнему там, новые geom инстанциируют себя. Чтобы избежать разрушительного изменения метки диска для действующей фай- файловой системы, изменение размера открытых geom может осуществляться лишь при их взаимодействии. Если бы был открыт любой из путей через geom MBR или BSD (например, в виде смонтированной файловой системы), они распространили бы донизу флаг исключительного открытия, делая невозможным открывание daO для записи. Напротив, флаг исключительного открытия, запрошенный при открытии daO для перезаписи MBR, сделал бы невозможным открывание пути через geom MBR до тех пор, пока daO не будет закрыт. Порча возникает лишь тогда, когда счет записей из- изменяется с нулевого на ненулевое значение, а проба возникает, лишь когда счет запи- записей изменяется с ненулевого на нулевое значение. Вставка (insert) является операцией, которая дает возможность инстанциировать новый geom между существующим абонентом и провайдером. Удаление является операцией, которая дает возможность устранить geom между существующим абонен- абонентом и провайдером. Эти возможности можно использовать для перемещения дейст- действующей файловой системы. Например, мы могли бы вставить модуль зеркального ото- отображения в стек GEOM, изображенный на рис. 7.3, как показано на рис. 7.4. Зеркаль- Зеркальное отображение действует на daOsl и dalsl между абонентом метки BSD и его про- провайдером метки MBR daOsl. Зеркальное отображение первоначально конфигурируется daOsl в виде его единственной копии и соответственно является прозрачным для запро- запросов ввода/вывода на этом пути. Далее мы запрашиваем у него отображение daOsl в dalsl. Когда зеркальная копия готова, мы сбрасываем зеркальную копию с daOsl.
328 Глава 7. Устройства В конечном счете мы удаляем зеркальный geom из пути, поручая абоненту метки BSD использовать dalsl. Результатом является то, что мы переместили смонтированную файловую систему из одного диска на другой во время его использования. /dev/daOsla /dev/daOslh daOsla... daOslh [Метка BSDJ Зеркало /dev/daOsl /dev/da0s2 dalsl dals2 daOsl da0s2 Метка MBR[ [МеткаМВя! /dev/da0 T dal L Диск J [ Диск J Рис. 7.4. Использование модуля зеркального отображения для копирования действующей файловой Функционирование Система GEOM должна быть способна действовать в многопроцессорном ядре. Обычным методом для обеспечения должной работы является использование мьютек- сов для всех структур данных. Из-за большого размера и сложности кода и структур данных, реализующих классы GEOM, GEOM использует однопоточный подход вместо традиционного блокирования мьютексами для обеспечения согласованности структур данных. GEOM использует два потока для работы со своим стеком: поток gjdown для обработки запросов, двигающихся от потребителей наверху к производителям внизу, и поток g_up для обработки запросов, двигающихся от производителей внизу к потре- потребителям наверху. Запросы, входящие на уровень GEOM наверху, ставятся в очередь ожидания потока gjdown. Поток gjdown вынимает каждый запрос из очереди, перемещает его вниз по стеку и выводит через производителя. Сходным образом результаты, возвра- возвращающиеся от производителей, помещаются в очередь ожидания потока g_up. Поток g_up вынимает каждый запрос из очереди, перемещает его вверх по стеку и возвращает обратно потребителю. Поскольку всегда имеется лишь один поток, поднимающийся и спускающийся по стеку, единственной блокировкой, которая требуется, является блокировка нескольких структур данных, координирующих пути наверх и вниз. Есть два правила, необходимых для эффективной работы однопоточного метода.
7.2. Уровень GEOM 329 1. Geom никогда не может находиться в состоянии сна. Если geom когда-нибудь помес- поместит поток g_up или g_down в спящее состояние, остановится вся система ввода/выво- ввода/вывода, до тех пор пока geom не пробудится снова. Инфраструктура GEOM проверяет, что его рабочие потоки никогда не спят, входя в состояние паники, если они пытают- пытаются это сделать. 2. Ни один geom не может производить чрезмерные вычисления. Если geom работает чрезмерно, ожидающие запросы или результаты могут быть задержаны на непри- неприемлемо длительное время. Есть некоторые geom, такие, как предоставляющие криптографическую защиту для файловых систем, которые используют интенсив- интенсивные вычисления. Эти geom с интенсивными вычислениями должны преду- предусмотреть свои собственные потоки. Когда поток g_up или gjdown входит в geom с интенсивными вычислениями, он просто ставит запрос в очередь, запланировав на исполнение собственный рабочий поток geom, и продолжает процесс со следующего запроса в своей очереди. Получив процессорное время, поток geom с интенсивными вычислениями проделает нужную работу, а потом поставит результат в очередь потока g_up или gjdown, чтобы завершить прохождение запроса через стек. Набором команд, которые могут передаваться через стек GEOM, являются чтение, запись и удаление. Команды чтения и записи имеют ожидаемую семантику. Удаление устанавливает, что определенный диапазон данных больше не используется и что он может быть удален или освобожден. Технологии, подобные уровням поддержки Flash- носителей, могут организовывать удаление соответствующих блоков до того, как их можно будет повторно распределить, а криптографические устройства могут запол- заполнять диапазон случайными битами для снижения количества данных, доступных атаке. Запрос на удаление не дает гарантии того, что данные действительно будут уда- удалены или сделаны недоступными, если это не гарантируется определенными geom в графе. Если требуется семантика безопасного удаления, должен быть задействован geom, который преобразует запрос удаления в последовательность запросов записей. Топологическая гибкость GEOM является как расширяемым, так и топологически агностичным. Расширяемость GEOM упрощает написание нового класса преобразования. Например, если вам было бы нужно смонтировать диск MVS IBM, класс, распознающий и конфигурирующий его информацию оглавления тома, был бы тривиальным. В отличие от многих предыдущих менеджеров томов GEOM является топологиче- топологически агностичной. У большинства реализаций управления томами есть определенное представление о том, как классы совмещаются друг с другом, но часто предоставляет- предоставляется лишь одна фиксированная иерархия. На рис. 7.5 показана типичная иерархия. Она требует, чтобы диски сначала были разделены на разделы, а затем разделы были сгруп- сгруппированы в зеркала, которые экспортируются в виде томов.
330 Глава 7. Устройства daOs I .da 1 s 1 .mirror da0s2.da 1 s2.mirror Зеркало daOsl da0s2 « Метка MBR daO Диск Зеркало " 1 dalsldals2 Метка MBR dal ПДиск Рис. 7.5. Фиксированная иерархия классов С фиксированными иерархиями невозможно эффективно выражать намерения. В фиксированной иерархии на рис. 7.5 невозможно зеркально отобразить два физиче- физических диска, а затем разделить зеркало на слайсы, как сделано на рис. 7.6. Вместо этого один вынужден сделать слайсы на физических томах, затем создать зеркала для каждо- каждого из соответствующих слайсов, получая в результате более сложную конфигурацию. Топологическая агностичность означает, что другое упорядочивание классов не трак- трактуется как отличное от существующих упорядочиваний. GEOM не заботится о том, в каком порядке расположены вещи. Единственным ограничением является то, что в графе не допускаются циклы. daO. dal. mirror, si daO. dal. mirrors 2 I Метка MBR I daO.dal.mirror I Зеркало I daO dal Диск Диск Рис. 7.6. Гибкая иерархия классов 7.3. Уровень САМ Чтобы снизить сложность отдельных драйверов дисков, значительная часть сложности управления современным контроллером была абстрагирована в отдельный уровень САМ, который находится под уровнем GEOM и над уровнем драйвера устройства. Уровень САМ управляет независимыми от устройства задачами выделения ресурсов и маршрутизации команд. Эти задачи включают отслеживание запросов и уведомле- уведомлений между контроллером и его клиентами. Они включают также маршрутизацию
7.3. Уровень САМ 331 запросов через множество шин ввода/вывода, чтобы запрос поступил на нужный кон- контроллер. Уровень САМ оставляет драйверу устройства специфические для устройства операции, такие, как установка и освобождение отображений DMA (Direct Memory Access - прямой доступ к памяти), необходимых для осуществления ввода/вывода. Драйверы некоторых устройств по-прежнему могут быть сложными. Например, драй- драйвер устройства волоконно-оптического канала содержит большое количество кода для обработки таких операций, как асинхронные изменения топологии по мере удаления и добавления приводов. Драйвер отвечает на запрос САМ, преобразуя виртуальный адрес для сохранения данных в соответствующий физический адрес. Затем он пере- переправляет независимые от устройства параметры наподобие запроса ввода/вывода, физический адрес для сохранения данных и размер передачи в специфическом для устройства формате и выполняет команду. Когда ввод/вывод завершается, драйвер воз- возвращает результаты обратно на уровень САМ. Помимо дисков уровень САМ управляет любыми другими устройствами хране- хранения, которые могут быть подключены к системе, такими, как ленточные приводы и брелки флеш-памяти. Для других устройств, таких, как клавиатура и мышь, САМ использоваться не будет. USB-клавиатура будет подключена непосредственно к драйверу клавиатуры. Подсистема SCSI Подсистема САМ SCSI предоставляет единообразную и модульную систему для реа- реализации драйверов, управляющих различными устройствами SCSI и использующих различные адаптеры хостов SCSI посредством драйверов хост-адаптеров. Система САМ состоит из трех уровней. 1. Периферийный уровень САМ, который предоставляет поддерживаемым устройст- устройствам SCSI операции открытия, закрытия, стратегии, подключения и отключения. Под- Поддерживаемые САМ устройства включают прямой доступ (direct access - da) - диско- дисковые приводы, CD-ROM (cd), последовательный доступ (sequential access - sa) - лен- ленточные приводы и чейнджеры (changer - ch) - дисководы с автоматической сменой дисков. 2. Транспортный уровень САМ, который строит, выполняет и интерпретирует результаты команд SCSI. CAM начинает с построения общей команды ввода/выво- ввода/вывода с использованием ССВ (САМ Control Block - управляющего блока САМ). ССВ содержит CDB (Command Descriptor Block - блок дескриптора команды), состав- составленный из 6-16-байтной команды SCSI. Например, команда «READ_10, block_offset, count» получает обратно статус успеха или различные коды ошибок. Если есть ошибка, привод может также включить данные считывания, чтобы пре- предоставить больше информации о причине ошибки.
332 Глава 7. Устройства 3. Уровень интерфейса SIM (Software Interface Module - модуль программного ин- интерфейса) или НВА (Host Bus Adapter - адаптер главной шины) САМ предоставля- предоставляет устройствам маршрутизацию по шинам. Его задачей является определение пути к нужному устройству, отправка устройству запроса действия ССВ, а затем сбор уведомлений от устройства о завершении ввода/вывода. Работу уровня САМ проще всего понять, проследив прохождение через него запроса ввода/вывода. Путь запроса ввода/вывода через подсистему САМ Путь запроса через подсистему ввода/вывода САМ показан на рис. 7.7. В рамках FreeBSD файловая система видит один непрерывный диск. Запросы ввода/вывода ос- основываются на номерах блоков в пределах этого идеализированного диска. На рис. 7.7 файловая система определяет набор блоков, для которых она хочет выполнить ввод/ вывод, и передает этот запрос вниз на уровень GEOM, вызвав процедуру strategyQ. strategy biodone Уровень GEOM dastrategy dadone Периферийный уровень САМ xpt_schedule camisr 1 1 а Транспортный уровень , * методов общего aasfart доступа (xpt) CAM xpt_action xpt_done ahc_action ahc_done Уровень драйверов устройств (выполнить Рис. 7.7. Путь запроса ввода/вывода через подсистему САМ Уровень GEOM принимает запрос и определяет диск, которому должен быть отправлен запрос. В данном примере запрос предназначен диску SCSI da. Иногда запрос может охватывать несколько дисков. Когда это случается, уровень GEOM разде- разделяет первоначальный запрос на ряд отдельных запросов ввода/вывода для каждого из дисков, на которых находятся оригинальные данные. Каждый из этих новых запросов передается вниз на уровень САМ путем вызова соответствующей процедуры strategyQ для соответствующего диска (на рис. 7.7 процедура dastrategyQ). Процедура САМ dastrategyQ получает запрос и вызывает bioqjdisksortQ, которая помещает запрос в очередь указанного диска SCSI. Процедура dastrategyQ завершается вызовом функции xpt_scheduleQ.
7.3. Уровень САМ 333 Функция xpt_schedule() выделяет и конструирует ССВ (САМ Control Block - управляющий блок САМ) для описания операции, которая должна быть сделана. Если диск поддерживает помеченные (tagged) очереди, выделяется неиспользуемый тег (метка), если он доступен. Если помеченные очереди не поддерживаются или если тег недоступен, запрос остается ожидать в очереди запросов диска. Если диск готов к приему новой команды, процедура xpt_schedule() вызывает начальную процедуру диска, установленную для этого (в данном примере dastartQ). Процедура dastartQ берет первый запрос из очереди диска и начинает его обслужи- обслуживать, используя ССВ, который был создан dastrategyQ. Поскольку команда предна- предназначена для диска SCSI, dastartQ нужно построить команду SCSI READ_10, основыва- основываясь на информации в ССВ. Результирующая команда SCSI, которая включает заголо- заголовок READ_10, указатель на виртуальный адрес, который ссылается на данные для передачи, и размер передаваемых данных помещаются в ССВ, и им приписывается тип XPB_SCSI_IO. Затем процедура dastartQ вызывает процедуру xpt_actionQ для опреде- определения шины и контроллера (адаптера), которому должна быть отправлена команда. Процедура xptjxctionQ возвращает указатель на структуру cam_path, описывающую контроллер, который должен быть использован, и содержит указатель на процедуру действия контроллера. В данном примере мы используем контроллер SCSI Adaptec, процедурой действия которого является ahc_actionQ. Процедура xpt_actionQ ставит ССВ с его cam_path в очередь и назначает его исполнение. Запрос исполняется путем вызова процедуры действия, специфической для кон- контроллера, ahc_actionQ. Процедура ahc_actionQ получает ССВ и преобразует его общую команду SCSI в специфический для устройства SCB (SCSI Control Block - управляющий блок SCSI) для обработки команды. SCB заполняется данными, взятыми из ССВ. Он заполняется также дополнительной специфической для устройства информацией, и устанавливается дескриптор запроса DMA. Затем SCB передается для исполнения программе драйвера. После завершения задачи уровень САМ возвращается назад вызвавшему dastrategyQ. Контроллер выполняет запрос и пересылает данные через DMA в или из области, указанной в SCB. Когда это сделано, в контроллере возникает прерывание завершения задачи. Прерывание вызывает запуск процедуры ahcjdoneQ. Процедура ahcjdoneQ обновляет ССВ, связанный с завершенным SCB, информацией из SCB (статус заверше- завершения команды или считанная информация, если была ошибка). Затем она освобождает выделенные ранее ресурсы DMA и выполненный SCB и передает завершенный ССВ обратно на уровень САМ, вызвав xpt_done(). Процедура xptjdoneQ вводит соответствующий ССВ в очередь уведомлений о завершении и посылает запрос программного прерывания для camisrQ, процедуры обработки прерываний САМ. Когда запускается camisrQ, она удаляет ССВ из очереди уведомлений о завершении и вызывает указанную функцию завершения, которая в данном случае отображается на dadoneQ.
334 Глава 7. Устройства Процедура dadoneQ вызовет процедуру biodoneQ, которая уведомляет уровень GEOM, что один из его запросов ввода/вывода завершен. Уровень GEOM объединяет все отдельные запросы дискового ввода/вывода воедино. Когда завершается последняя операция ввода/вывода, он обновляет первоначальный запрос ввода/вывода, передан- переданный ему файловой системой, для отражения результата (либо успешное завершение, либо подробности о каких-либо возникших ошибках). Затем файловая система уведом- уведомляется о конечном результате путем вызова процедуры biodoneQ. 7.4. Уровень АТА Чтобы снизить сложность драйверов отдельных устройств, значительная часть кода, общего для поддержки дисков АТА, была абстрагирована в отдельный уровень АТА, который находится ниже уровня GEOM и выше уровня драйверов устройств. Уровень АТА управляет независимыми от устройств задачами отслеживания запросов и уве- уведомлений между контроллером и их клиентами. Уровень АТА также контролирует управление отображением DMA для контроллеров, которые его поддерживают. Специфические для устройства операции оставлены драйверу устройства. Драй- Драйвер устройства отвечает на запрос от АТА, передавая независимые от устройства пара- параметры наподобие запроса ввода/вывода, виртуального адреса для сохранения данных и размера передаваемых данных в формате, специфичном для устройства, а затем ис- исполняя команду. Когда ввод/вывод завершается, драйвер возвращает результаты обрат- обратно на уровень АТА. Помимо дисков уровень АТА управляет любыми другими устройствами хранения, которые могут быть подключены к системе, такими, как ленточные приводы и CD-ROM. Работу уровня АТА проще всего понять, проследив прохождение через него запроса ввода/вывода. Путь запроса ввода/вывода через подсистему АТА Путь запроса через подсистему ввода/вывода АТА показан на рис. 7.8. Как описано в предыдущем разделе, файловая система определяет ряд блоков внутри своего ло- логического диска, для которых она хочет выполнить ввод/вывод, и передает этот запрос вниз уровню GEOM, вызвав процедуру strategy(). Уровень GEOM принимает запрос и определяет диск, которому должен быть направлен запрос. В данном примере запрос предназначен диску АТА ad. Иногда запрос может охватывать несколько дисков. Когда это происходит, уровень GEOM делит первоначальный запрос на ряд отдельных запросов ввода/вывода для каждого из дисков, на которых находятся оригинальные данные. Каждый из этих новых запросов передается вниз на уровень АТА путем вызова соответствующей процедуры strategyQ для диска (см. процедуру adstrategyQ на рис. 7.8).
7.4. Уровень ATA 335 strategy biodone Уровень GEOM adstrategy ad_done ata_start ata_completed Уровень ATA ata_transaction ata_ nish I _| ___ ad_start ata_interrupt Уровень драйвера (выполнить к устройства ^ 1 Рис. 7.8. Путь запроса ввода/вывода через подсистему АТА Процедура ATA adstrategy() получает запрос и вызывает bioq_disksort(), которая помещает запрос в очередь диска для указанного диска АТА. Процедура adstrategy() завершается вызовом функции ata_start(). Процедура ata_start() отвечает за управле- управление очередью канала. Очередь канала используется для объединения запросов от веду- ведущего и ведомого дисков, подключенных к каналу, поскольку в одно и то же время может выполняться лишь один запрос. Процедура ata_start() будет чередовать пере- передачу между двумя дисками, если у обоих есть ожидающие запросы. Когда будет добав- добавлена поддержка помеченных очередей, станет возможным иметь ожидающие запросы для обоих дисков. Если диск еще не занят, ata_start() берет из очереди следующий запрос передачи и вызывает ata_transaction(). Процедура ata_transaction() управляет низкоуровневыми деталями установки ото- отображения DMA, если контроллер его поддерживает. Когда диск готов к приему новых команд, процедура atajransaction() вызывает начальную процедуру диска (в данном случае ad_start()), установленную для него процедурой adstrategyQ. Процедура ad_start() берет запрос из очереди канала и передает его прошивке (firmware) диска для исполнения. Завершив свою задачу, уровень АТА возвращается назад к вызывавшему adstrategy(). Контроллер выполняет запрос. После этого от контроллера поступает прерывание завершения операции. Прерывание вызывает запуск процедуры ataJnteniiptQ. Процедура ata_interrupt() обновляет запрос статусом завершения команды или считанными данны- данными, если была ошибка. Затем она освобождает все ранее выделенные ресурсы DMA, освобождает канал для новой работы и передает завершенный запрос обратно уровню АТА, вызвав ataJinishQ. Процедура ataJinishQ помещает выполненный запрос ввода/ вывода в рабочую очередь АТА для запуска потоком g_up и возвращается из прерывания.
336 Глава 7. Устройства Когда поток g_up запускает рабочую очередь АТА, завершенный запрос ввода/ вывода обрабатывается ata_completed(). Процедура ata_completed() удаляет запрос из его рабочей очереди, делает отметку о возникших ошибках и вызывает указанную функцию завершения, которая в данном случае отображается на ad_done(). Уровень GEOM объединяет все отдельные запросы дискового ввода/вывода воедино. Когда завершается последняя операция ввода/вывода, он обновляет первоначальный запрос ввода/вывода, переданный ему файловой системой, чтобы отразить результат (либо успешное завершение, либо подробности о любых возникших ошибках). Затем файловая система уведомляется о конечном результате посредством вызова процедуры biodoneQ. 7.5. Конфигурирование устройств Автоконфигурирование является процедурой, выполняемой системой для распознава- распознавания и включения аппаратных устройств, присутствующих в системе. Автоконфи- Автоконфигурирование работает путем систематического зондирования возможных шин ввода/ вывода на машине. Для каждой найденной шины ввода/вывода интерпретируется каждый тип подключенного к ней устройства, и в зависимости от его типа предприни- предпринимаются необходимые действия для инициализации и конфигурирования устройства. Первая реализация автоконфигурирования FreeBSD вела происхождение от ориги- оригинального кода 4.2BSD с добавлением множества кусков для особых случаев. Выпуск 4.4BSD представил новую, более машинно-независимую систему конфигурирования, которая была рассмотрена для использования в FreeBSD, но в конечном счете была от- отвергнута в пользу использующейся сегодня схемы newbus. Newbus впервые появилась в FreeBSD 3.0 для поддержки архитектуры Alpha. Для FreeBSD 4.0 она перенесена на платформу PC. Newbus включает машинно-независимые процедуры и структуры данных для использования машинно-зависимыми уровнями и предоставляет каркас для динамического выделения структур данных для каждого устройства. Ключевой целью дизайна системы newbus было предоставление разработчикам драй- драйверов стабильного двоичного интерфейса приложений (application binary interface - ABI). Стабильный ABI особенно важен для внешних или поддерживаемых производителем загружаемых модулей ядра, поскольку их исходный код часто недоступен для переком- перекомпилирования, если интерфейс изменится. Чтобы помочь достичь стабильности ABI, устройство newbus и структуры devclass скрыты от остальной части ядра простым API вызовов функций для доступа к их содержимому. Если бы структуры передавались драйверам устройств непосредственно, любое изменение структуры потребовало бы, чтобы все драйверы, которым она пере- передается, были перекомпилированы. Изменения этих структур данных newbus не требуют перекомпилирования всех драйверов. Перекомпилировать нужно лишь функции доступа к структурам данных.
7.5. Конфигурирование устройств 337 К сожалению, цель построения стабильного документированного ABI ядра для разработчиков драйверов еще не была сделана вне newbus. Например, в большинство драйверов по-прежнему передается структура uio. Каждый раз, когда в структуре uio добавляется, изменяется или удаляется поле, все драйверы нужно перекомпилировать. Некоторые аппаратные устройства, такие, как интерфейс консольного терминала, необходимы для работы системы. Однако другие устройства могут быть не нужны, и их включение в систему может оказаться ненужным расточительством системных ресурсов. Устройства, которые могут присутствовать в различном количестве, по раз- различным адресам или в различных сочетаниях, трудно конфигурировать заранее. Однако система должна их поддерживать, если они присутствуют, и должна аккуратно завершаться ошибкой, если их нет. Для решения этих проблем FreeBSD поддерживает как процедуру статического конфигурирования, которая выполняется при создании за- загружаемого образа системы, так и возможности динамической загрузки, которая дает возможность добавлять драйверы и модули ядра в работающую систему по мере необ- необходимости. Таким образом, статически отконфигурированное ядро может быть небольшим с возможностями, достаточными лишь для запуска системы. После начала работы при необходимости можно добавлять дополнительные функциональные воз- возможности. Разрешение динамической загрузки кода в ядро создает множество проблем безо- безопасности. Код, работающий вне ядра, ограничен в возможности причинения ущерба, который он может сделать, поскольку он не работает в привилегированном режиме и не может получить непосредственный доступ к оборудованию. Ядро работает, имея все привилегии и полный доступ к оборудованию. Поэтому, если оно загружает модуль, содержащий злонамеренный код, оно может причинить внутри системы широ- широкомасштабные повреждения. Ядра могут загружаться через сеть из центрального сервера. Если ядро допускает динамическую загрузку модулей, они также могли бы поступать через сеть, поэтому добавляется множество возможностей для злодеяний. Важным соображением в решении, включать ли динамическую загрузку модулей ядра, является разработка схемы проверки источника и отсутствия каких-либо повреж- повреждений кода до того, как можно будет этот код загрузить и использовать. Группа произ- производителей образовала Группу доверенных вычислений (Trusted Computing Group - TCG), чтобы специфицировать аппаратный модуль, названный модулем доверенной платформы (Trusted Platform Module - ТРМ), который хранит вычисленные хеши SHA-1 установленного в системе программного обеспечения для обнаружения загрузки плохих программ или модулей. Он реализован в виде основанного на микроконтрол- микроконтроллере устройства, сходного со смарт-картой, которое присоединяется к системной плате [TCG, 2003]. Другие группы проводят работу по ограничению потенциального вреда модулей ядра, запуская их с правами доступа страниц, которые ограничивают их доступ к оставшейся части ядра [Chiueh et al., 2004]. Недостатком отключения дина- динамической загрузки является то, что любое аппаратное обеспечение, которое не включе- включено в файл конфигурации ядра, будет невозможно использовать.
338 Глава 7. Устройства Начальное конфигурирование ядра осуществляется программой /usr/sbin/config. Системным администратором создается файл конфигурации, который содержит список драйверов и опций ядра. Изначально файл конфигурации определял как набор аппарат- аппаратных устройств, которые могли присутствовать на машине, так и место, где можно было найти каждое из устройств. В FreeBSD 5.2 большая часть действительных аппаратных устройств определяется динамически, когда драйверы различных шин выполняют зон- зондирование и подключение. Размещение традиционных устройств для шин, не являющих- являющихся plug-and-play (несамоидентифицирующихся), приведено в файле /boot/device.hints, который загружается с ядром. Другим использованием подсказок (hints) является жест- жесткая привязка номера устройства к месту расположения. В настоящее время лишь САМ может жестко задавать номера устройств, хотя жесткое занятие номеров может быть реализовано для любой шины. Процедура конфигурирования создает множество фай- файлов, которые определяют начальную конфигурацию ядра. Эти файлы управляют ком- компиляцией ядра. Фаза автоконфигурирования впервые осуществляется во время инициализации системы для определения набора устройств, присутствующих на машине. Вообще автоконфигурирование рекурсивно проходит дерево соединений устройств, таких, как шины и контроллеры, к которым подключены другие устройства. Например, система могла бы быть сконфигурирована с двумя хост-адаптерами (контроллерами) SCSI и че- четырьмя дисковыми приводами, которые могут быть соединены в любой из показанных на рис. 7.9 конфигураций. Автоконфигурирование на каждом уровне дерева работает одним из двух способов. 1. Идентификация каждого возможного места, где может присутствовать устройство, и проверка наличия там соответствующего типа устройства. 2. Зондирование устройств в каждом из возможных мест, где может быть подключено устройство. Первый подход идентификации предопределенных мест для устройств необходим для более старых шин наподобие ISA, которые не были спроектированы для поддержки автоконфигурирования. Второй механизм зондирования устройств может использо- использоваться только тогда, когда возможен лишь ограниченный мест и когда устройства в этих местах являются самоидентифицирующимися, такими, как устройства, подключен- подключенные к шинам SCSI или PCI. Устройства, которые распознаны на фазе автоконфигурирования, подключаются и делаются доступными для использования. Функция attach для устройства инициали- инициализирует устройство и выделяет для него ресурсы. Функция attach для шины или кон- контроллера должна прозондировать устройства, которые могут быть подключены в этой точке. Если функция attach завершается неудачей, устройство было обнаружено, но является недействующим, что приводит к выводу в консоли сообщения. Устройства, которые присутствуют, но не распознаны, могут быть отконфигурированы после
7.5. Конфигурирование устройств 339 Контроллер SCSI Контроллер SCSI Контроллер SCSI Контроллер SCSI Контроллер SCSI Контроллер SCSI конфигурация 1 конфигурация 2 Рис. 7.9. Альтернативные конфигурации дисков конфигурация 3 запуска системы и загрузки других модулей ядра. Функции attach для шин разрешено резервировать ресурсы для устройств, которые обнаружены на шине, но для которых в настоящее время в системе не загружен драйвер устройства. Эта схема разрешает драйверам устройств выделять системные ресурсы лишь для тех устройств, которые присутствуют в работающей системе. Она допускает измене- изменение топологии физических устройств без необходимости восстановления образа загру- загруженной системы. Она также предотвращает сбои, возникающие от попыток доступа к несуществующему устройству. В оставшейся части данного раздела мы рассмотрим возможности авто конфигурирования с точки зрения разработчика драйверов устройств. Мы исследуем поддержку драйверов устройств, необходимую для идентификации аппаратных устройств, которые присутствуют в машине, и шаги, необходимые для подключения устройства после установления его наличия. Идентификация устройств Чтобы принять участие в автоконфигурировании, драйвер устройства должен зареги- зарегистрировать приведенный в табл. 7.1 набор функций. Устройства в FreeBSD являются абстрактным понятием. Помимо традиционных дисков, ленточных накопителей, кла- клавиатур, линий терминалов и т.д. в FreeBSD будут устройства, которые работают со всеми частями, составляющими инфраструктуру ввода/вывода, такими, как контрол- контроллер шины SCSI, контроллер моста шины PCI и контроллер моста шины ISA. Устройст- Устройством верхнего уровня является корень системы ввода/вывода, который называется rootO. На однопроцессорной системе rootO логически находится в контактах ввода/
340 Глава 7. Устройства вывода процессора. На многопроцессорной системе rootO логически соединен с кон- контактами ввода/вывода каждого из процессоров. Устройство rootO создается во время загрузки для каждой архитектуры, поддерживаемой FreeBSD. Табл. 7.1. Функции, определенные для автоконфигурирования Функция Описание device_probe Зондирование наличия устройства device_identify Добавление к шине нового устройства device_attach Подключение устройства device_detach Отключение устройства device_shutdown Систему собираются отключить device_suspend Запрос приостановки device_resume Возобновление работы Автоконфигурирование начинается с запроса к шине rootO отконфигурировать всех ее потомков. Когда шину запрашивают отконфигурировать всех своих потомков, она вызывает процедуру device_identify() драйвера каждого из возможных устройств. Результатом является набор потомков, которые были добавлены к шине либо самой шиной, либо процедурами deviceJdentifyQ одного из ее драйверов. Далее вызывается процедура device_probe() каждого из потомков. Процедура device_probe(), которая заявит наибольшую ставку за устройство, вызовет затем свою процедуру device_attach(). Результатом является набор устройств, соответствующих каждой из шин, которые не- непосредственно доступны rootO. Затем каждому из этих новых устройств дается воз- возможность прозондировать или идентифицировать находящиеся под собой устройства. Процесс идентификации продолжается до тех пор, пока не будет определена топология системы ввода/вывода. Современные шины могут непосредственно идентифицировать устройства, которые к ним подключены. Более старые шины, такие, как ISA, используют проце- процедуру deviceJdentifyQ для установления устройств, которые обнаружены только благо- благодаря подсказкам. В качестве примера иерархии устройств можно привести устройство, управ- управляющее шиной PCI, которое может зондировать контроллер SCSI, который в свою очередь будет зондировать возможные устройства, которые могут быть подключены, такие, как дисковые и ленточные приводы. Механизм автоконфигурирования newbus предоставляет значительную гибкость, давая контроллеру возможность определять подходящий способ, с помощью которого можно зондировать дополнительные устрой- устройства, подключенные к контроллеру.
7.5. Конфигурирование устройств 341 По мере продолжения автоконфигурирования для каждого найденного устройства вызывается процедура драйвера устройства device_pmbe(). Система передает процедуре device_probe() описание размещения устройства, а также, возможно, другие детали, такие, как место регистрации ввода/вывода, размещение памяти и векторы прерываний. Процедура devicejprobeQ обычно просто проверяет, распознает ли она устройство. Возможно, что имеется более одного драйвера, который может работать с устрой- устройством. В этом случае каждый подходящий драйвер возвращает значение приоритета, которое показывает, насколько хорошо они подходят устройству. Кодами успеха являются значения меньшие или равные нулю, причем наивысшее значение представ- представляет лучшее соответствие. Коды ошибок представлены положительными значениями, использующими обычные коды ошибок ядра. Если драйвер возвращает код успеха, который меньше нуля, он не должен предпо- предполагать, что он будет тем самым драйвером, процедура device_attach() которого будет вызвана. В частности, он не должен предполагать, что какие-либо значения, сохранен- сохраненные в области локальной памяти устройства, будут доступны для его процедуры device_attach(). Все ресурсы, выделенные в ходе зондирования, должны быть освобож- освобождены и выделены повторно, если вызывается процедура device_attach(). Возвратив ну- нулевой код успеха, драйвер может предполагать, что он будет подключен. Однако хорошо написанные драйверы не будут применять процедуру device_attach(), исполь- использующую область локальной памяти устройства, поскольку однажды возвращаемое ими значение может быть уменьшено до значения меньше нуля. Обычно ресурсы, исполь- используемые устройством, идентифицируются шиной (родительским устройством), и именно шина их выдает, когда осуществляется зондирование устройств. После того как у процедуры devicejproheQ была возможность идентифицировать устройство и выбрать наиболее подходящий драйвер для работы с ним, вызывается процедура device_attach(). Подключение устройства отделено от зондирования таким образом, чтобы драйверы могли «торговаться» за устройства. Зондирование и под- подключение разделены также потому, чтобы драйверы могли отделить часть идентифи- идентификации при конфигурировании от части подключения. Большинство драйверов устройств используют для инициализации аппаратного устройства и любого про- программного состояния процедуру device_attach(). Процедура device_attach() отвечает также либо за создание элементов dev_t (для дисков и символьных устройств), либо (для сетевых устройств) за регистрацию устройства в сетевой системе. Устройства, которые представляют части оборудования, такие, как контроллер SCSI, будут отвечать за проверку того, что устройство присутствует, и за установку или по крайней мере идентификацию вектора прерывания устройства. Для дисковых устройств процедура device_attach() может сделать драйвер доступным более высоким уровням ядра, таким как GEOM. GEOM позволит своим классам попробовать диско- дисковое устройство, чтобы идентифицировать его геометрию и, возможно, инициализиро- инициализировать таблицу разделов, которая определяет расположение на диске файловой системы.
342 Глава 7. Устройства Структуры данных автоконфигурирования Система автоконфигурирования newbus в FreeBSD включает машинно-независимые структуры данных и процедуры поддержки. Структуры данных позволяют хранить ма- шинно- и шинно-зависимую информацию обычным способом. Они также позволяют процессу автоконфигурирования управляться конфигурационными данными, а не встроенными правилами. Программа /usr/sbin/config конструирует многие из таблиц из информации в файле конфигурирования ядра и из файла описания машины. Программа /usr/sbin/config также является таким образом управляемой данными и не содержит машинно-зависимого кода. На рис. 7.10 показаны структуры данных, используемые при автоконфигурирова- автоконфигурировании. Базовым строительным блоком является структура device. Каждая часть иерархии ввода/вывода будет иметь свою собственную структуру устройства. Поля имени и описания идентифицируют часть оборудования, представленную структурой device. На рис. 7.10 именем устройства является pcil. Имена устройств являются глобально уникальными. В системе может быть лишь одно устройство pcil. Знания его имени достаточно для его обнаружения, в отличие от файловых систем, в которых может быть множество файлов с одним и тем же именем в различных путях. Это пространство имен связано по соглашению с пространством имен, которое имеют элементы /dev, но такое отношение не требуется. ч "pcil" Описание Ссылка Родитель Драйвер Драйвер " Класс struct device Драйвер ~ "pci" Число устройств Устройства[] • • • "рсГ Методы Ссылки struct driver struct driverlink struct driverlink pcm Методы Ссылки "atapci" Методы Ссылки struct devclass struct driver Рис. 7.10. Автоконфигурирование структур данных для pci I struct driver
7.5. Конфигурирование устройств 343 Каждое устройство является членом класса устройства, представленного струк- структурой devclass, у которой две важные роли. Первой ролью структуры devclass является отслеживание списка драйверов для устройств в этом классе. Устройства, на которые ссылаются из devclass, не должны использовать один и тот же драйвер. Каждая струк- структура устройства ссылается на наиболее подходящий драйвер из списка доступных для devclass. Обходится список кандидатов в драйверы, давая возможность каждому драй- драйверу прозондировать каждое устройство, которое идентифицируется как член класса. К устройству будет подключен наиболее подходящий драйвер. Например, devclass pci содержит список драйверов, подходящих для зондирования устройств, которые могут быть подключены к шине PCI. На рис. 7.10 есть драйверы, подходящие ддярст (звуковые карты) и atapci (контроллеры АТА-дисков на основе PCI). Второй ролью структуры devclass является управление отображением дружествен- дружественного к пользователю имени устройства, такого, как pci 1, на его структуру устройства. Поле имени в структуре devclass содержит корень семейства имен - в данном примере pci. Число, следующее за корнем имени, - в данном примере 1 - служит индексом в указателе на структуры устройств, содержащихся в devclass. Имя в указанной струк- структуре device является полным именем, pci 1. Когда структура device появляется впервые, предпринимаются следующие шаги. 1. Родительское устройство определяет существование нового подсоединенного устройства, обычно посредством сканирования шины. Новое устройство создается в виде потомка родителя. На рис. 7.10 код автоконфигурирования начал бы сканиро- сканирование pci 1 и обнаружил бы контроллер диска АТА. 2. Родительское устройство стартует для нового устройства-потомка последователь- последовательность зондирования-и-подключения. При зондировании перебираются драйверы в devclass родительского устройства до тех пор, пока не будет найден драйвер, который претендует на устройство (т.е. зондирование завершается успешно). В поле драйвера в структуре device устанавливается указатель на структуру выбранного драйвера и увеличивается счетчик ссылок в выбранном драйвере. На рис. 7.10 к контроллеру диска АТА подходит драйвер atapci atadisk. 3. Когда найден пригодный к использованию драйвер, новое устройство регистриру- регистрируется с помощью devclass с тем же именем, что и у драйвера. Регистрация осущест- осуществляется путем выделения следующего доступного номера устройства и установки обратного указателя на соответствующий элемент в массиве devclass указателей структур устройств. На рис. 7.10 подошел драйвер atapci, поэтому устройство должно быть связано с devclass atapci. Получившаяся конфигурация устройства показана на рис. 7.11. Ключевым наблюдением является то, что в этот трехшаго- вый процесс вовлечены два различных devclass.
344 Глава 7. Устройства "atapciO" Описание Ссылка Родитель Потомки Драйвер Класс struct device Драйвер ~" "atapci" Счетчик устройств Устройства^ • • "atapci" Методы Ссылки struct driver struct driverlink struct driverlink "atadisk" Методы Ссылки "atapicd" Методы Ссылки struct devclass struct driver РИС. 7.11. Автоконфигурирование структур данных для atapciO struct driver Иерархия структур устройств показана на рис. 7.12. У каждой структуры устройст- устройства есть указатель на родителя и список потомков. На рис. 7.12 устройство pci, которое управляет шиной PCI, изображено наверху и имеет в качестве единственного потомка устройство atapci, которое работает с дисками АТА на шине PCI. Устройство atapci имеет в качестве родительского устройство pci, а также двух потомков, по одному для каждого из подключенных дисков АТА. Устройства, представляющие два диска, имеют в качестве своего родителя устройство atapci. Поскольку они являются концевыми вершинами, у них нет потомков. Чтобы получить лучшее представление об иерархии ввода/вывода, на рис. 7.13 по- показана снабженная примечаниями копия вывода программы /usr/sbin/devinfo из тесто- тестовой машины первого автора. Вывод был сокращен с его первоначальных 250 строк, чтобы показать только ответвление от корня дерева на два АТА-диска системы. Дерево начинается с rootO, представляющего контакты ввода/вывода процессора. Они ведут к высокоскоростной шине, которая подключается к памяти и соединению nexusO (например, северного моста) с шиной ввода/вывода. Для машин, на которых ACPI недоступна, по-прежнему есть прокладка legacyO, чтобы помочь идентифицировать шины ввода/вывода на другой стороне северного моста. Одной из этих шин является соединение pcibO (например, южного моста) с шиной PCI. Шина PCI управляется устройством pciO, которое, как вы можете видеть из рисунка, имеет множество доступ- доступных драйверов для бесчисленных устройств, которые могут быть подключены к нему.
7.5. Конфигурирование устройств 345 "pcil" Описание Ссылка Родитель _ Потомки ~ Драйвер Класс * xj •—> struct device "atapciO" Описание Ссылка Родитель Потомки Драйвер Класс _ —1 struct device РИС. 7.12. Пример иерархии структур данных "atadiskO" Описание Ссылка Родитель Потомки Драйвер Класс "atadiskl" Описание Ссылка Родитель Потомки Драйвер Класс struct device struct device В данном примере одним из устройств, которые мы видим, является устройство atapciO, представляющее основанный на PCI контроллер диска АТА. Последними двумя устройствами, показанными на рис. 7.13, являются atadiskO и atadiskl, которые управляют работой самих приводов. Управление ресурсами В качестве части конфигурирования и работы с устройствами коду автоконфигуриро- автоконфигурирования нужно управлять аппаратными ресурсами, такими, как линии запросов прерыва- прерываний, порты ввода/вывода и память устройств. Чтобы помочь разработчикам драйверов в решении этой задачи, newbus предоставляет инфраструктуру для управления этими ресурсами. Чтобы принять участие в управлении ресурсами шины newbus, драйвер устройства шины должен зарегистрировать набор функций, показанных в табл. 7.2. Низкоуровневые устройства, такие как работающие с отдельными дисковыми приво- приводами, не имеют глобального знания об использовании ресурсов, необходимого для выделения редких системных ресурсов, таких, как линии запросов прерываний. Они могут зарегистрировать общую обходную процедуру для ресурсов, для выделения которых у них нет необходимой информации. При своем вызове обходная процедура просто вызывает соответствующую процедуру, зарегистрированную своим родителем. В результате запрос будет подниматься по дереву устройств до тех пор, пока не достиг- достигнет достаточно высокого уровня, на котором он может быть выполнен. Часто высокоуровневый узел на дереве будет иметь недостаточно информации о том, какое количество ресурсов вьщелить. Поэтому он буцет резервировать диапазон ресурсов, оставляя узлам на нижележащих уровнях выделение и активацию определенных ресурсов, которые им нужны, из резервов, сделанных узлом на более высоком уровне.
346 Глава 7. Устройства rootO description: System root bus devclass: root, drivers: nexus children: nexusO nexus0 devclass: nexus, drivers: acpi, legacy, npx children: npxO, legacy0 legacyO description: legacy system devclass: legacy, drivers: eisa, isa, pcib children: eisaO, pcibO pcibO /* южный мост */ description: Intel 82443BX D40 BX) host to PCI bridge devclass: pcib, drivers: pci children: pciO pcibl description: PCI-PCI bridge devclass: pcib, drivers: pci children: pcil pciO description: PCI bus devclass: pci, drivers: agp, ahc, amr, asr, atapci, bfe, bge, ciss, csa, domino, dpt, eisab, emujoy, fxp, fixup_pci, hostb, ignore_pci, iir, ips, isab, mly, modeO, pcib, pcm, re, sio, uhci, xl children: agpO, pcibl, isabO, atapciO, ahcO, xlO atapciO description: Intel 82371AB PIIX4 IDE controller devclass: atapci, drivers: atadisk, atapicd children: atadiskO, atadiskl class: mass storage, subclass: ATA I/O ports: OxffaO-Oxffaf atadiskO devclass: atadisk, drivers: none interrupt request lines: Oxe I/O ports: 0xlf0-0xlf7, 0x3f6 atadiskl devclass: atadisk, drivers: none interrupt request lines: Oxf I/O ports: 0x170-0x177, 0x376 Рис. 7.13. Образец вывода конфигурации
7.5. Конфигурирование устройств 347 Действительное управление выделенными ресурсами осуществляется менед- менеджером ресурсов ядра, который был описан в разделе 5.10. Обычные процедуры выде- выделения и освобождения были расширены, чтобы позволить различным уровням на дереве управлять различными частями этих двух функций. Таким образом, распреде- распределение подразделяется на три шага. 1. Установка диапазона для ресурса. 2. Начальное распределение ресурса. 3. Активация ресурса. Табл. 7.2. Функции, определенные для распределения ресурсов устройства Функция bus_alloc_resource bus_set_resource bus_activate_resource bus_deactivate_resource bus_release_resource bus_delete_resource bus_get_resource bus_get_resource_list bus_probe_nomatch bus_driver_added bus_add_child bus_child_detached bus_setup_intr bus_config_intr bus_teardown_intr bus_read_ivar bus_write_ivar bus_child_present bus_print_child Описание Выделяет ресурс шины Устанавливает диапазон для ресурса Активирует выделенный ресурс Деактивирует выделенный ресурс Освобождает владение ресурсом Очищает освобожденный ресурс Получает диапазон для ресурса Получает список ресурсов Вызов после неудачного зондирования К devclass добавлен новый драйвер Подключение идентифицированного устройства Уведомление родителя об отсоединении потомка Инициализация прерывания Установка полярности и режима запуска прерывания Запрет прерывания Чтение переменной экземпляра Запись переменной экземпляра Проверка наличия устройства Вывод описания устройства Аналогично освобождение ресурса осуществляется за три шага. 1. Деактивация ресурса. 2. Освобождение владения ресурсом родительской шиной. 3. Освобождение ресурса.
348 Глава 7. Устройства Для более высокоуровневой части дерева обычно выделяется ресурс, который затем активируется и используется низкоуровневым драйвером. Некоторые шины резервируют пространство для своих потомков, у которых нет связанных с ними драй- драйверов. Разделение выделения и освобождения на три шага дает максимальную гиб- гибкость при разделении распределения и освобождения процессов. Функции bus_driver_added(), bus_add_child() и bus_child_detached() позволяют устройству знать об изменениях в оборудовании ввода/вывода, так что оно может отвечать соответствующим образом. Функция bus_driver_added() вызывается системой при загрузке нового драйвера. Драйвер добавляется к некоторому devclass, а затем все устройства в этом devclass вызывают bus_driver_added(), чтобы дать им возможность подобрать любые незатребованные устройства, использующие новый драйвер. Функ- Функция bus_add_child() используется во время фазы идентификации конфигурирования некоторых шин. Она дает устройству шины возможность создать и инициализировать новое дочернее устройство (например, установку значений для переменных экземп- экземпляров). Функция bus_child_detached() вызывается драйвером, когда он решает, что его аппаратное обеспечение больше не присутствует (например, удалена карта CardBus). Он вызывает bus_child_detached() своего родителя, чтобы дать ему возможность отсо- отсоединить потомка. Процедура bus_probe_nomatch() дает устройству последнюю возможность пред- предпринять какое-нибудь действие после безуспешного завершения автоконфигурирова- автоконфигурирования. Она может попытаться найти общий драйвер, который может запустить устройст- устройство в ухудшенном режиме, или она может просто отключить устройство. Если она не способна найти драйвер, который может запустить устройство, она уведомляет демон devd, процесс уровня пользователя, запускаемый при загрузке системы. Демон devd использует для обнаружения и загрузки соответствующего драйвера таблицу. Загрузка модулей ядра описана в разделе 14.4. Процедуры bus_read_ivar() и bus_write_ivar() управляют набором специфических для шины переменных экземпляров дочернего устройства. Цель в том, чтобы каждый отдельный тип шины определял набор соответствующих переменных экземпляров, таких, как порты и линии запросов прерываний для шины ISA. Упражнения 7.1. Назовите три уровня между файловой системой и диском. Кратко опишите назначение каждого уровня. 7.2. Опишите различия между шинами PCI и USB. 7.3. Почему в FreeBSD 5.2 была добавлена файловая система /dev? 7.4. Объясните разницу между dev_t и device_t. 7.5. Приведите пример провайдера GEOM и абонента GEOM.
Ссылки 349 7.6. Что случится, если два абонента GEOM попытаются действовать в одно и то же время? 7.7. Нарисуйте последовательность схем, показывающих, что происходит с кон- конфигурацией GEOM на рис. 7.3, когда диск становится недоступным. 7.8. Назовите три уровня внутри САМ. Кратко опишите сервисы, которые предос- предоставляет каждый из этих уровней. 7.9. Может ли уровень САМ устанавливать или снимать отображения DMA для одного из своих драйверов устройств? Поясните свой ответ. 7.10. В чем назначение программы /usr/sbin/conflg? 7.11. Приведите две причины того, почему небезопасно позволить ядру загружать код динамически. 7.12. Почему зондирование устройств и их подсоединение сделаны в виде двух отдельных шагов? 7.13. Опишите назначение структуры device. 7.14. Запустите программу /usr/sbin/devinfo на машине с FreeBSD и иденти- идентифицируйте аппаратное обеспечение, связанное с каждым концевым узлом. 7.15. Назовите три шага, использующихся для выделения и освобождения ресурсов. Почему эти функции разделены на три различных шага? **7.16. В настоящее время все устройства подсоединяются один раз при поиске в «глубину». Но некоторые устройства могут предоставлять службы, необхо- необходимые устройствам, находящимся выше по дереву. Опишите устройства, которые попадают в этот класс, и приведите план построения подхода с мно- множеством проходов с подключением к newbus для работы с ними. Ссылки ACPI, 2003. ACPI, "Advanced Configuration and Power Interface Specification',' Web Reference, http://www.acpi.info/, November 2003. ANSI, 2002. ANSI, "Common Access Method draft standard',' X3T10, available from http:// www.tl0.org, January 2002. ANSI, 2003. ANSI, "Fibre Channel draft standard',' Til, available from http://www.tll.org, January 2003. Arch, 2003. Arch, "PC Architecture',' Web References, http://www.intel.com/design/chipsets/ 865PE/pix/865PE_schematic.gif, http://www.just2good.co.uk/chipset.htm, and http:// www.gotogeek.com/mobopics/, November 2003.
350 Глава 7. Устройства Chiuehetal.,2004. Т. Chiueh, P. Pardhan, & G. Venkitachalam, Intra-Address Space Protection Using Segmentation Hardware, http://www.ecsl.cs.sunysb.edu/palladium.html, March 2004. TCG, 2003. TCG, Trusted Computing Group TPM Specification Version 1.2, http://www.trusted- computinggroup.org/ and http://www.infineon.conVcgi^cптl.dll/ecп^^/scripts/prod_ov.jsp?oid=29049, October 2003.
Глава 8 Локальные файловые системы 8.1. Управление иерархическими файловыми системами Операции, определенные для локальных файловых систем, разделены на две части. Обычными для всех локальных файловых систем являются иерархическое именова- именование, блокировки, квоты, управление атрибутами и защита. Эти свойства, которые неза- независимы от способа хранения данных, предоставляются кодом UFS, описанным в первых семи разделах данной главы. Другая часть локальной файловой системы, хранилище файлов, занимается организацией и управлением данных на носителе ин- информации. Хранилище управляется операциями хранилища данных файловой систе- системы, описанными в последних двух разделах данной главы. Операции vnode, определенные для осуществления иерархических операций фай- файловой системы, показаны в табл. 8.1. Наиболее сложной из этих операций является операция для осуществления поиска. Независимая от файловой системы часть поиска описана в разделе 6.5. Алгоритм, использующийся для поиска компонента имени пути в каталоге, описан в разделе 8.3. Имеется пять операторов для создания имен. Используемый оператор зависит от типа создаваемого объекта. Оператор create создает обычные файлы, а также исполь- используется сетевым кодом для создания сокетов домена AF_LOCAL. Оператор link создает дополнительные имена для существующих объектов. Оператор symlink создает симво- символические ссылки (обсуждение символических ссылок см. в разделе 8.3). Оператор mknod создает специальные символьные устройства (для совместимости с другими системами UNIX, которые их по-прежнему используют); он используется также для создания fifo. Оператор mkdir создает каталоги.
352 Глава 8. Локальные файловые системы Табл. 8.1. Операции иерархической файловой системы Производимая операция Имена операторов Поиск имени пути lookup Создание имени create, mknod, link, symlink, mkdir Изменение/удаление имени rename, remove, rmdir Манипулирование атрибутами access, getattr, setattr Интерпретация объекта open, readdir, readlink, mmap, close Управление процессом advlock, ioctl, poll Управление объектом lock, unlock, inactive, reclaim Имеется три оператора для изменения или удаления существующих имен. Опера- Оператор rename удаляет имя для объекта в одном месте и создает новое имя для объекта в другом месте. Реализация этого оператора сложна, когда ядро имеет дело с переме- перемещением каталога из одной части дерева файловой системы в другую. Оператор remove удаляет имя. Если удаляемое имя является последней ссылкой на объект, память, свя- связанная с нижележащим объектом, возвращается обратно. Оператор remove работает со всеми типами объектов, кроме каталогов; последние удаляются с использованием оператора rmdir. Три оператора предназначены для атрибутов объекта. Ядро получает атрибуты от объекта, используя оператор getattr, и сохраняет их, используя оператор setattr. Про- Проверки прав доступа для данного пользователя осуществляются оператором access. Пять операторов предоставлены для интерпретирования объектов. Операторы open и close для обычных файлов имеют лишь частное использование, но когда они используются со специальными устройствами, они уведомляют соответствующий драйвер устройства об активации или выключении устройства. Оператор readdir пре- преобразует специфический для файловой системы формат каталога в стандартный список элементов каталога, ожидаемый приложением. Обратите внимание, что интерпретация содержимого каталога предоставляется уровнем управления иерархической файловой системой; код хранилища файлов рассматривает каталог просто как еще один объект, содержащий данные. Оператор readlink возвращает содержи- содержимое символической ссылки. Как и в случае с каталогами, код хранилища файлов рас- рассматривает символическую ссылку лишь как еще один объект, содержащий данные. Опера- Оператор ттар подготавливает объект к отображению в адресное пространство процесса. Чтобы позволить процессу управлять объектами, предусмотрены три оператора. Оператор poll позволяет процессу выяснить, готов ли объект к чтению или записи. Оператор ioctl передает управляющие запросы специальному устройству. Оператор advlock дает процессу возможность запрашивать или освобождать необязательные блокировки объекта. Ни один из этих операторов не изменяет объект в файловом хранилище. Они просто используют объект для именования или направления нужной операции.
8.2. Структура mode 353 Для управления объектами имеется четыре операции. Операторы inactive и reclaim были описаны в разделе 6.6. Операторы lock и unlock позволяют коду, вызывающему интерфейс vnode, предоставлять подсказки коду, который реализует операции с ниже- нижележащими объектами. Файловые системы, не имеющие состояния, такие, как NFS, игнорируют эти подсказки. Однако файловые системы с состояниями могут использо- использовать подсказки для избежания лишней работы. Например, системный вызов open, запрашивающий создание нового файла, требует двух шагов. Во-первых, выполняется вызов lookup, чтобы проверить, не существует ли файл. Перед началом поиска для каталога, в котором производится поиск, осуществляется вызов lock. В ходе сканиро- сканирования каталога при проверке имени код поиска идентифицирует также место внутри каталога, которое содержит достаточно пространства для сохранения нового имени. Если поиск завершается успешно (что означает, что имя еще не существует), код open проверяет, что у пользователя есть права доступа на создание файла. Если вызы- вызывающий не имеет права создавать новый файл, ожидается вызов unlock для освобожде- освобождения блокировки, которая была запрошена в ходе поиска. В противном случае вызыва- вызывается операция create. Если файловая система имеет состояния и смогла заблокировать каталог, тогда она просто создает имя в ранее обнаруженном месте, поскольку она знает, что ни один другой процесс не имел доступа к каталогу. После создания имени для каталога делается запрос unlock. Если файловая система не имеет состояния, она не может разблокировать каталог, поэтому оператор create должен выполнить повторное сканирование каталога, чтобы найти место и убедиться, что с момента поиска данное имя не было создано. 8.2. Структура inode Чтобы иметь возможность одновременно размещать файлы, а также предоставлять произ- произвольный доступ внутри файлов, FreeBSD использует понятие индексного узла, или inode (index node - inode). Inode содержит информацию о содержании файла (см. рис. 8.1). Эта информация включает в себя следующее: * Тип и режим доступа для файла. * Идентификаторы владельца и группы файла. * Время, когда файл был создан, когда с ним осуществлялись последние чтение или запись и когда его inode в последний раз обновлялся системой. * Размер файла в байтах. * Число физических блоков, используемых файлом (включая блоки, используемые для хранения косвенных указателей и расширенных атрибутов). * Число элементов каталогов, которые ссылаются на файл. * Устанавливаемые ядром и пользователем флаги, описывающие свойства файла.
354 Глава 8. Локальные файловые системы Режим Владельцы B) Временные отметки Размер Прямые блоки Простые косвенные Двойные косвенные Тройные косвенные Число блоков Число ссылок Флаги B) Номер поколения Размер блока Размер расширенных атрибутов Блоки расширенных атрибутов Рис. 8.1. Структура inode * Номер поколения файла (случайно выбранный номер, присваиваемый inode каждый раз, когда последний выделяется для файла; номер поколения используется NFS для обнаружения ссылок на удаленные файлы). * Размер блоков данных, на которые ссылается inode (обычно такой же, как размер блока файловой системы, но иногда больше). * Объем информации о расширенных атрибутах. Следует обратить внимание на то, что в структуре inode отсутствует имя файла. Имена файлов хранятся в каталогах, а не в inode, поскольку файл может иметь множество имен, или, ссылок, а имя файла может быть большим (вплоть до 255 байтов длиной). Каталоги описываются в разделе 8.3. Чтобы создать для файла новое имя, система увеличивает счетчик числа имен, ссылающихся на этот inode. Затем в каталог вводится новое имя вместе с номером
8.2. Структура inode 355 inode. Наоборот, когда имя удаляется, элемент удаляется из каталога, а затем счетчик имен для inode уменьшается. Когда счетчик имен достигает нуля, система освобождает inode, помещая все блоки inode обратно в список свободных блоков. Inode содержит также массив указателей на блоки файла. Система может преобра- преобразовать логический номер блока в номер физического сектора путем индексирования массива с использованием логического номера блока. Пустой элемент массива показы- показывает, что блок не был выделен, и вызывает возврат при чтении блока, заполненного нулями. При записи в такой элемент выделяется новый блок, элемент массива обнов- обновляется номером нового блока, и данные записываются на диск. Inode имеют фиксированный размер, а большинство файлов небольшие, поэтому массив указателей должен быть маленьким для эффективного использования про- пространства. Первые 12 элементов массива выделяются в самом inode. Для типичных файловых систем эта реализация позволяет определять первые от 96 до 192 Кбайт данных непосредственно через простой индексированный поиск. Для несколько более крупных файлов на рис. 8.1 показано, что inode содержит про- простой косвенный указатель, который указывает на простой косвенный блок указателей на блоки данных. Чтобы найти 100-й логический блок файла, система сначала получает блок, идентифицируемый косвенным указателем, а затем индексирует 88-й блок A00 минус 12 прямых указателей) и получает этот блок данных. Для файлов, превышающих несколько мегабайт, простой косвенный блок в конечном счете расходуется полностью; эти файлы должны обратиться к использованию двойного косвенного блока, который является указателем на блок указателей на указатели на блоки данных. Для файлов в несколько гигабайт система использует тройной косвенный блок, который содержит три уровня указателей до достижения блоков данных. Хотя может показаться, что косвенные блоки увеличивают число доступов к диску, необходимых для получения блока данных, эти издержки обычно намного меньше, чем кажется. В разделе 6.6 мы обсуждали управление кешем, в котором хранятся недавно использованные дисковые блоки. В первый раз, когда нужен блок косвенных указателей, он заносится в кеш. Дальнейшие доступы к косвенным указателям находят блоки уже резидентными в памяти; таким образом, для получения данных им требуется лишь один доступ к диску. Изменения формата inode Традиционно быстрая файловая система FreeBSD (на которую мы будем ссылаться в данной книге как на UFS1) [McKusick et al., 1984] и ее производные использовали 32-разрядные указатели для ссылок на блоки, использованные файлами на диске. Файловая система UFS1 была спроектирована в начале 1980-х гг., когда самые боль- большие диски имели размер 330 Мбайт. В то время были дебаты по поводу того, стоило ли тратить 32 бита на указатель блока вместо использования 24-разрядных указате- указателей блоков файловой системы, которую она заменяла. К счастью, футуристическая
356 Глава 8. Локальные файловые системы точка зрения возобладала и в проекте были использованы 32-разрядные указатели блоков. За 20 лет, прошедших с момента развертывания USF1, системы запоми- запоминающих устройств выросли до размеров терабайтов данных. В зависимости от кон- конфигурации размера блока, 32-разрядные указатели блоков UFS1 иссякают в диапазо- диапазоне от 1 до 4 терабайт. Хотя для расширения максимального размера систем хранения данных могут быть использованы некоторые временные приспособления, к 2002 г. стало ясно, что единственным долговременным решением было бы использование 64-разрядных указателей блоков. Таким образом, мы решили построить новую фай- файловую систему, UFS2, которая использовала бы 64-разрядные указатели блоков. Мы рассмотрели альтернативы между попытками делать постепенные изменения в существующую файловую систему UFS1 в сравнении с импортированием другой существующей файловой системы, такой, как XFS [Sweeney et al., 1996] или ReiserFS [Reiser, 2001]. Мы также рассмотрели возможность написания новой файловой системы с нуля таким образом, чтобы можно было использовать преимущества недавних иссле- исследований и опыта файловых систем. Мы выбрали расширение файловой системы UFS1, поскольку этот подход позволяет нам повторно использовать большую часть сущест- существующего кода UFS1. Преимуществами этого решения было то, что UFS2 разрабатыва- разрабатывалась и развертывалась быстро, она быстро стала стабильной и надежной, и для под- поддержки форматов файловых систем как UFS1, так и UFS2 могла быть использована одна и та же база кода. Около 90 процентов кода являются общими, поэтому исправле- исправления ошибок и усовершенствования возможностей и производительности обычно при- применяются к обоим форматам файловых систем. Расположенные на диске inode, используемые UFS1, имеют размер 128 байтов и лишь два неиспользующихся 32-разрядных поля. Было бы невозможно конвертировать 64-разрядные указатели блоков, не уменьшив число прямых указателей блоков с 12 до 5. Это значительно увеличило бы объем расходуемого впустую пространства, поскольку лишь прямые указатели блоков могут ссылаться на фрагменты, поэтому единственной альтернативой является увеличение размера inode на диске до 256 байтов. После принятия решения о переходе на новый формат inode на диске можно включить и другие касающиеся inode изменения, которые были невозможны в рамках ограничений старых inode. Хотя было заманчиво добавить все, что было когда-либо предложено за последние 20 лет, мы чувствовали, что было лучше ограничить добав- добавление новых возможностей теми, которые могли принести явную пользу. Каждое ново- нововведение добавляет сложность, которая имеет свою цену и для сопровождения, и для производительности. Неопределенные или малоиспользуемые возможности могут доба- добавить условные проверки в часто исполняющиеся пути кода, такие, как чтение и запись, замедляя общую производительность файловой системы, даже если они не используются.
8.2. Структура mode 357 Расширенные атрибуты Значительным добавлением в UFS2 является поддержка расширенных атрибутов. Расширенные атрибуты являются частью памяти вспомогательных данных, связанных с inode, которые можно использовать для хранения вспомогательных данных отдельно от содержания файла. Идея сходна с концепцией «вилок» данных и ресурсов, используемых в файловой системе Apple [Apple, 2003]. Интегрируя расширенные атрибуты в сам inode, можно предоставить те же самые гарантии целостности, которые применяются для содержания самого файла. Конкретно, успешное завершение системного вызова fsync гарантирует, что данные файла, расширенные атрибуты и все имена и пути, ведущие к именам файла, находятся в стабильном хранилище. В текущей реализации в inode есть место для хранения вплоть до двух блоков рас- расширенных атрибутов. В новом формате UFS2 имелось место для пяти дополнительных 64-разрядных указателей. Таким образом, число блоков расширенных атрибутов могло бы быть от одного до пяти. Мы выбрали выделение двух блоков для расширенных атрибутов, остальные три блока были оставлены в качестве запасных для будущего ис- использования. Так как сейчас есть два блока, весь код должен быть готов иметь дело с массивом указателей, поэтому, если в будущем число будет расширено на оставший- оставшийся резерв, существующая реализация будет без изменений работать с исходным кодом. Сохраняя три запасных указателя, мы предусмотрели обоснованный объем памяти для будущих нужд. И если решение использовать лишь два блока выявит, что места слиш- слишком мало, для расширения размера расширенных атрибутов в будущем можно исполь- использовать один или более запасных указателей. Если потребуется значительно большее пространство расширенных атрибутов, резерв можно использовать для косвенных указа- указателей на блоки расширенных атрибутов данных. На рис. 8.2 показан формат, используемый для расширенных атрибутов. Первым полем заголовка каждого из атрибутов является его длина. Приложения, которые не понимают пространство имен или имя, могут просто пропустить неизвестный атрибут, добавив размер поля к текущему положению указателя, чтобы получить следующий атрибут. Таким образом, множество различных приложений могут разделять использование пространства расширенных атрибутов, даже если они не понимают типы данных друг друга. Размер Длина ПростраН' ство имен 1 Длина дополнения содержния 1 Дшна имени 1 1 - Кратно 8 н <Со держан и с> РИС. 8.2. Формат расширенных атрибутов. Заголовок каждого атрибута имеет 4-байтную длину, 1-байтный класс пространства имен, 1-байтную длину дополнения содержания, 1-байт- 1-байтную длину имени и имя. Имя дополняется таким образом, чтобы содержание начина- начиналось на 8-байтной границе. Содержание дополняется до размера, показанного в поле «длина дополнения содержания». Размер содержания можно вычислить, вычтя из длины размер заголовка (включая имя) и длину дополнения содержания
358 Глава 8. Локальные файловые системы Первым из двух первоначальных применений расширенных атрибутов является поддержка списка контроля доступа (access control list), обозначаемого обычно как ACL. ACL замещает групповые права доступа к файлу более конкретным списком пользователей, которым разрешен доступ к файлам. ACL включает также список разрешений, который предоставляется каждому из пользователей. Эти разрешения включают традиционные права на чтение, запись и исполнение вместе с другими свой- свойствами, такими, как право на переименование или удаление файла [Rhodes, 2003]. Ранние реализации ACL осуществлялись посредством одного вспомогательного файла для каждой файловой системы, который индексировался по номеру inode и имел небольшую область фиксированного размера для хранения разрешений ACL. Неболь- Небольшой размер должен был сохранить вспомогательный файл в разумных пределах, по- поскольку он должен был иметь место для каждого возможного inode в файловой систе- системе. В этой реализации были две проблемы. Фиксированный размер пространства для каждого inode для хранения сведений ACL означал, что было невозможно дать доступ длинным спискам пользователей. Вторая проблема заключалась в том, что было трудно атомарно фиксировать изменения для файла в списке ACL, поскольку обновле- обновление требовало записи как inode файла, так и файла ACL, чтобы изменение возымело действие [Watson, 2000]. Обе проблемы с реализацией ACL через вспомогательный файл были разрешены посредством сохранения информации ACL непосредственно в области данных расширен- расширенных атрибутов inode. Из-за большого размера области данных расширенных атрибутов (минимум 8 Кбайт, обычно 32 Кбайта) можно легко сохранять длинные списки сведений ACL. Пространство, использующееся для хранения информации расширенных атрибу- атрибутов, пропорционально числу inode с расширенными атрибутами и размеру списков ACL, которые они используют. Атомарное обновление информации значительно проще, поскольку запись inode обновит атрибуты inode и набор данных, на которые он ссылается, включая расширенные атрибуты, в ходе одной дисковой операции. Хотя можно было бы обновлять старый вспомогательный файл на каждый вызов fsync, который делается в файловой системе, стоимость этого была бы чрезмерной. В этом же случае ядро знает, является ли блок данных расширенных атрибутов для inode грязным, и в ходе вызова fsync для inode может записать лишь этот блок данных. Другим применением для расширенных атрибутов является маркирование данных. Метки данных предоставляют разрешения для схемы обязательного контроля досту- доступа (mandatory access control - MAC), осуществляемого ядром. Структура MAC ядра позволяет динамически введенным модулям системной безопасности изменять функ- функциональные возможности безопасности системы. Эта инфраструктура может быть использована для поддержки различных новых служб безопасности, включая традици- традиционные модели обязательного контроля доступа, основанные на метках. Инфраструк- Инфраструктура предоставляет ряд точек входа, которые вызываются кодом, поддерживающим различные службы ядра, в особенности кодом контроля доступа и создания объектов. Затем модель вызывает модули безопасности для предоставления им возможности из-
8.2. Структура inode 359 менить поведение безопасности в этих точках входа MAC. Таким образом, файловая система не кодифицирует, как используются или приводятся в исполнение метки; она просто сохраняет метки, связанные с inode, и выдает их, когда они необходимы модулям безопасности для запроса прав доступа [Watson, 2001; Watson et al., 2003]. Мы рассмотрели хранение символических ссылок в области расширенных атрибутов, но решили так не делать по четырем причинам. 1. Во-первых, большинство символических ссылок умещается в пределах 120 байтов, обычно используемых для хранения прямых и косвенных указателей, и тем самым не нуждается в выделении дискового блока для своего хранения. 2. Если символическая ссылка достаточно большая, чтобы требовать хранения в дис- дисковом блоке, время доступа к расширенному блоку хранения такое же, как время доступа к обычному блоку данных. 3. Поскольку у символических ссылок редко есть какие-нибудь расширенные атрибу- атрибуты, не было бы экономии памяти, поскольку потребовался бы фрагмент файловой системы, будь то обычный блок данных или расширенный блок памяти. 4. Если бы она хранилась в области расширенных атрибутов, потребовалось бы больше времени при прохождении списка атрибутов для ее нахождения. Возможности новой файловой системы При создании формата увеличенного inode было сделано несколько других улучше- улучшений. Мы решили заранее решить проблему 2038 г., когда переполнятся 32-разрядные поля времени (конкретно - во вторник 19 января 2038 г. в 14:08 по Гривичу, что было бы скверным способом возвещения о 84-м дне рождения первого автора). Мы рас- расширили поля времени (в которых хранятся секунды, прошедшие с 1970 г.) для доступа, модификации и изменения inode с 32 битов до 64 битов. До плюс или минус 136 мил- миллиардов лет, которые должны перенести нас из времени задолго до того, как была соз- создана вселенная, до времени гораздо позже того, когда погаснет наше солнце. Мы оста- оставили поля наносекунд для этих значений времен 32-битными, поскольку не чувствова- чувствовали, что увеличение разрешения было бы полезным в обозримом будущем. Мы рас- рассмотрели расширение времени лишь до 48 битов. Были выбраны 64 бита, поскольку 64 бита являются естественным размером, которым можно манипулировать с помо- помощью существующих и возможных будущих архитектур. Использование 48 битов потребовало бы дополнительного шага по упаковке или распаковке каждый раз при чтении или записи поля. Также 64 бита гарантируют достаточное число разрядов для всех возможных измерений времени, так что его не придется увеличивать. Мы добавили также новое поле времени (тоже 64-битное) для хранения времени происхождения (обычно называемого также временем создания) файла. Время созда- создания устанавливается, когда впервые выделяется inode, и впоследствии не изменяется. Оно было добавлено в структуру, возвращаемую системным вызовом stat, чтобы при-
360 Глава 8. Локальные файловые системы ложения могли определить его значение и чтобы архивирующие программы, такие, как dump, tar и pax, могли сохранять это значение вместе с другими значениями вре- времени файла. Время создания было добавлено в ранее зарезервированное поле в струк- структуре системного вызова stat таким образом, что размер структуры не изменился. Поэтому старые версии программ, использующие вызов stat, продолжают работать. К настоящему времени лишь программа dump была изменена для сохранения значения времени создания. Эта новая версия dump, которая может создавать дампы файловых систем как UFS1, так и UFS2, создает новый формат дампа, который не может быть прочитан старыми версиями restore. Обновленная версия restore может идентифицировать и восстанавливать как из старого, так и из нового формата дампа. Время создания является доступным для просмотра и установки лишь из нового формата дампа. Системный вызов utimes устанавливает в качестве времени доступа и изменения файла указанный набор значений. Он используется преимущественно файлами извлечения из архива для установки тех значений времен вновь извлеченных файлов, которые были установлены в архиве. С добавлением времени создания мы добавили новый системный вызов, который позволяет установить время доступа, изменения и создания. Однако мы понимали, что многие существующие приложения не будут изменены для использования нового системного вызова utimes. Результатом будет то, что файлы, которые они будут извлекать из архивов, будут иметь более раннее время создания, чем время доступа или модификации. Чтобы обеспечить осмысленное время создания для приложений, которые не знают об атрибуте времени создания, мы изменили семантику системного вызова utimes таким образом, что, если время создания было более ранним, чем значение уста- устанавливаемого времени модификации, он устанавливает в качестве время создания то же значение, что и время модификации. Приложение, которое знает об атрибуте време- времени создания, может установить как время создания, так и время модификации, сделав два вызова utimes. Сначала оно вызывает utimes с временем модификации, равным сохраненному времени создания, а затем вызывает utimes второй раз с временем моди- модификации, равным (преимущественно более недавнему) сохраненному времени моди- модификации. Для файловых систем, которые не сохраняют время создания, второй вызов может переписать результаты первого, что ведет к тем же значениям для времени дос- доступа и модификации, которые они получили бы до этого. Больше всего подходит соз- создателям приложений то, что им не придется условно компилировать имя utimes для BSD и He-BSD-систем. Они просто создают свои приложения для вызова стандартного интерфейса дважды, зная, что на всех системах и во всех файловых системах все будет идти как надо. Те приложения, которым более важна скорость выполнения, а не пере- переносимость, могут использовать новую версию системного вызова utimes, которая позволяет устанавливать все значения времени за один вызов.
8.2. Структура inode 361 Флаги файлов В FreeBSD есть два системных вызова, chflags и fchflags, которые устанавливают в inode 32-разрядное слово флагов пользователя. Флаги включены в структуру stat, поэтому их можно проверять. Владелец файла или суперпользователь может установить младшие 16 битов. В настоящее время имеются флаги, определенные для пометки файла как неизменного и только для добавления и не нуждающегося в резервном копировании. Неизменный файл не может быть изменен, перемещен или удален. Файл только для добавления является неизменным, за тем исключением, что к нему могут быть добавлены данные. Пользовательские флаги неизменности и только для добавления могут быть изменены владельцем файла или суперпользователем. Старшие 16 битов может установить только суперпользователь. В настоящее время имеются флаги, определенные для пометки файла как неизменного и только для добавления. После установки флаги неизменности и только для добавления в старших 16 битах не могут быть сброшены, когда система безопасна. Ядро работает с четырьмя различными уровнями безопасности. Любой процесс суперпользователя может увеличить уровень безопасности, но лишь процесс init может понизить этот уровень (программа init описана в разделе 14.6). Уровни безопас- безопасности определены следующим образом. -'{.Постоянно небезопасный режим: всегда запускает систему в режиме уровня О (должен быть встроен в ядро). 0. Небезопасный режим: флаги неизменности и только для добавления могут быть отключены. Можно читать из всех устройств или записывать в них с проверкой прав доступа. 1. Безопасный режим: устанавливаемые суперпользователем флаги неизменности и только для чтения не могут быть сброшены; диски для смонтированных файловых систем и памяти ядра (/dev/mem и /dev/kmem) только для чтения. 2. Высокобезопасный режим: этот режим такой же, как безопасный режим, за тем ис- исключением, что диски всегда имеют доступ только на чтения, независимо от того, смонтированы они или нет. Этот уровень препятствует даже процессам суперполь- суперпользователя вмешиваться в файловые системы, демонтируя их, но в то же время не да- дает форматировать и новые файловые системы. Обычно система работает с уровнем безопасности 0 в однопользовательском режиме и с уровнем безопасности 1 в многопользовательском. Если нужен уровень безопасности 2 при работе системы в многопользовательском режиме, он должен быть установлен в сценарии запуска /etc/rc (сценарий /etc/rc описан в разделе 14.6). Файлы, помеченные суперпользователем как неизменные, не могут быть изменены кем-либо, за исключением того, кто имеет физический доступ либо к машине, либо
362 Глава 8. Локальные файловые системы к системной консоли. Файлы, помеченные как неизменные, включают те, которые являются частыми объектами атак злоумышленников (например, login и su). Флаг только для добавления обычно используется для критических системных журналов. Если злоумышленник вломится в систему, он не сможет скрыть свои следы. Хотя эти два свойства идейно просты, они разительно увеличивают безопасность системы. Одним изменением в формате inode UFS2 было разделение поля флагов на два отдельных 32-разрядных поля: одно для флагов, которые могут устанавливать прило- приложения (как в UFS1), и новое поле для флагов, поддерживаемых исключительно ядром. Примером флага ядра является флаг SNAPSHOT, использующийся для пометки файла, как являющегося моментальным снимком (snapshot). Другим флагом ядра является OPAQUE, который используется файловой системой union для пометки каталога, который не должен делать уровни ниже себя видимыми. После перемещения этих флагов ядра из старших 16 разрядов поля флагов пользователя в отдельное поле флагов ядра они не смогут быть случайно установленными или сброшенными наивным или злонамеренным приложением. Динамические inode Обычной жалобой на файловую систему UFS1 является то, что она заранее выделяет все inode во время создания файловой системы. Для файловой системы с миллионами файлов инициализация файловой системы может занять несколько часов. Кроме того, программе создания файловой системы, newfs, приходилось предполагать, что каждая файловая система будет заполнена множеством маленьких файлов, и выделять значи- значительно больше inode, чем, возможно, будет когда-либо использовано. Если файловая система UFS1 использует все свои inode, единственным способом получить еще явля- является резервирование данных, повторное построение и восстановление файловой систе- системы. Файловая система UFS2 разрешает эти проблемы, выделяя inode динамически. Обычная реализация динамически выделяемых inode требует отдельной структуры данных файловой системы (обычно называемой файлом inode), которая отслеживает текущий набор inode. Управление и сопровождение этой дополнительной структуры данных добавляет издержки и усложнения и часто снижает производительность. Чтобы избежать этих затрат, UFS2 заранее выделяет диапазон номеров inode и набор блоков для каждой группы цилиндров (группы цилиндров описаны в разделе 8.9). Перво- Первоначально каждая группа цилиндров имеет два блока выделенных inode (в типичном блоке содержится от 32 до 64 inode). Когда блоки заполняются, выделяется и инициали- инициализируется следующий блок inode в наборе. Набор блоков, которые могут быть выделены для inode, хранятся как часть резерва свободного пространства до тех пор, пока не будет выделено все остальное пространство в файловой системе. Лишь после этого они могут использоваться для данных файлов. Теоретически файловая система могла бы заполниться, использовав все блоки, отложенные для inode. Позже после того, как были удалены большие файлы и создано
8.2. Структура inode 363 множество небольших файлов для их замещения, файловая система могла бы обнару- обнаружить, что она не может выделить необходимые inode, поскольку все отложенное для inode пространство по-прежнему используется. В этом случае было бы необходимым повторно распределить существующие файлы, чтобы переместить их в новые места за пределами области inode. Такой код не был создан, поскольку мы не ожидаем, что эта ситуация возникнет на практике, так как резерв свободного пространства, использо- использованный в большинстве файловых систем (8 процентов) превышает объем памяти, необходимый для inode (обычно менее 6 процентов). На таких системах лишь процесс, работающий с правами root, смог бы когда-нибудь выделить блоки inode. Если такой код окажется необходимым в реальном использовании, его можно будет написать. Пока он не написан, файловые системы, попавшие в эту ситуацию, возвратят ошибку «недостаточное число inode» при попытке создания новых файлов. Побочной выгодой динамического выделения inode является то, что время созда- создания файловой системы в UFS2 составляет около 1 процента времени, которое требуется для этого в UFS1. Файловая система, которая заняла бы один час для построения в формате UFS1, может быть построена в течение минуты в формате UFS2. Хотя соз- создание файловой системы не является обычной операцией, быстрое ее построение имеет значение для системных администраторов, которым приходится выполнять такие задачи довольно регулярно. Платой за динамическое выделение inode является одна лишняя дисковая запись для каждых 64 вновь создаваемых inode. Хотя эта цена низка по сравнению с другими издержками создания 64 новых файлов, некоторые системные администраторы могли бы захотеть предварительно выделить больше минимального числа inode. Если воз- возникнет подобное требование, добавление флага к программе newfs для предваритель- предварительного выделения дополнительных inode во время создания файловой системы было бы тривиальным. Управление массивом inode Большая часть деятельности в локальной файловой системе вращается вокруг inode. Как описано в разделе 6.6, ядро хранит список активных vnode и vnode, к которым недавно был произведен доступ. Решения, касающиеся того, сколько и какие файлы следует кешировать, принимаются уровнем vnode на основе информации о деятельно- деятельности всех файловых систем. Каждая локальная файловая система будет иметь для управления подмножество системных vnode. Каждая использует inode с добавленной дополнительной информацией для идентификации и локализации набора файлов, за которые она отвечает. На рис. 8.3 показано размещение inode внутри системы. Повторяя материал раздела 6.4, напомним, что у каждого процесса есть таблица открытых файлов процесса, в которой есть слоты для дескрипторов файлов в количе- количестве, ограниченном системой; эта таблица поддерживается как часть состояния про- процесса. Когда процесс пользователя открывает файл (или сокет), в таблице открытых
364 Глава 8. Локальные файловые системы Резидентные структуры ядра Дескриптор процесса Элемент открытого файла vnode inode "^ 1 ^ Диск inode Данные Рис. 8.3. Схема таблиц ядра файлов процесса выделяется не использующийся слот; небольшой целый дескриптор файла, возвращаемый при успешном вызове open, является значением индекса в этой таблице. Элемент таблицы файлов процесса указывает на системный элемент открытого файла, который содержит информацию о нижележащем файле или сокете, представ- представленном дескриптором. Для файлов таблица файлов указывает на vnode, представ- представляющий открытый файл. Для локальной файловой системы vnode ссылается на inode. Именно inode идентифицирует сам файл. Хеш для <HOMep_inode, номер_устройства> Рис. 8.4. Структура таблицы inode Первым из шагов в открытии файла является обнаружение связанного с файлом inode. Запрос поиска передается файловой системе, связанной с каталогом, в котором в настоящий момент осуществляется поиск. Когда локальная файловая система находит в каталоге имя, она получает номер inode соответствующего файла. Сначала файловая система ищет в своем наборе inode, чтобы посмотреть, не находится ли уже запрошенный
8.3. Именование 365 inode в памяти. Чтобы избежать линейного сканирования всех своих элементов, система хранит набор хеш-цепочек, ключами которых служат номера inode и идентификаторы файловой системы; см. рис. 8.4. Если inode не находится в таблице, как в случае первого открытия файла, файловая система должна запросить новый vnode. Когда локальной файловой системе выделяется новый vnode, выделяется новая структура для inode. Следующим шагом является определение дискового блока, содержащего inode, и чтение этого блока в буфер системной памяти. Когда дисковый ввод/вывод заверша- завершается, inode копируется из буфера диска во вновь выделенный элемент для inode. Кроме информации, содержащейся в дисковой части inode, сама таблица inode поддерживает дополнительные сведения, пока inode находится в памяти. Эти сведения включают описанные ранее хеш-цепочки, а также флаги, показывающие состояние inode, счетчи- счетчики ссылок его использования и информацию для управления блокировками. Содержат- Содержатся также указатели на другие структуры данных ядра, часто представляющие интерес, такие, как суперблок для файловой системы, содержащей inode. Когда закрывается последняя ссылка на файл, локальная файловая система уведом- уведомляется, что файл стал не активным. После инактивации обновляются значения време- времени inode, и inode может быть записан на диск. Однако он остается в списке хешей, чтобы его можно было найти, если он открывается повторно. После определенного периода неактивности, определяемого уровнем vnode на основе запросов vnode во всех файловых системах, vnode будет возвращен. При возвращении vnode локального файла его inode удаляется из цепочки хешей предыдущей файловой системы, и, если inode грязный, его содержимое записывается на диск. Затем освобождается память, занимаемая inode, чтобы vnode был готов для использования новым клиентом файло- файловой системы. 8.3. Именование Файловые системы содержат файлы, большинство из которых содержат обычные дан- данные. Некоторые файлы различаются, как каталоги, и содержат указатели на файлы, которые сами могут быть каталогами. Эта иерархия каталогов и файлов организована в древовидную структуру; на рис. 8.5 показано дерево небольшой файловой системы. Каждый из кругов на рисунке представляет inode с соответствующим номером inode внутри. Каждая из стрелок представляет имя в каталоге. Например, inode 4 является каталогом /usr с элементом ., который указывает на себя, и элементом .., который указы- указывает на своего родителя, inode 2, корень файловой системы. Он содержит также имя bin, которое ссылается на inode каталога 7, и имя foo, которое ссылается на inode файла 6.
366 Глава 8. Локальные файловые системы Рис. 8.5. Небольшое дерево файловой системы Каталоги Каталоги выделяются блоками, которые называются порциями (chunks); на рис. 8.5 показана типичная порция каталога. Размер порции выбирается таким, чтобы выделение каждой порции можно было перенести на диск за одну операцию. Возможность изме- изменения каталога за одну операцию делает обновление каталога атомарным. Порции делятся на элементы каталогов переменной длины, чтобы имена файлов могли иметь почти произвольную длину. Ни один элемент каталога не может занимать несколько порций. Первые четыре поля элемента каталога имеют фиксированный размер и содержат следующее. 1. Номер inode-файла, индекс в таблице структур inode на диске; выбранный элемент описывает файл (inode были описаны в разделе 8.2). 2. Размер элемента в байтах. 3. Тип элемента. 4. Длину имени файла, содержащегося в элементе, в байтах. Оставшаяся часть элемента имеет переменный размер и содержит завершающееся нулем имя файла, дополненное до 4-байтной границы. Максимальная длина имени файла в каталоге равна 255 символам. Файловая система учитывает свободное пространство в каталоге, заставляя эле- элементы собирать свободное пространство в их полях размеров. Таким образом, неко- некоторые элементы каталогов могут быть больше, чем нужно для хранения имени плюс поля фиксированного размера. Память, выделенная каталогу, всегда должна полностью
8.3. Именование 367 учитываться в сумме размеров элементов каталога. Когда из каталога удаляется эле- элемент, система объединяет пространство элемента с предыдущим элементом в той же самой порции каталога, увеличивая размер предыдущего элемента на размер удаленного элемента. Если первый элемент порции каталога свободен, тогда указатель на inode элемента устанавливается в ноль, чтобы показать, что элемент не выделен. # FILE 5 foo.c # \ DIR 3 bar # \ DIR 6 mumble Блок каталога с тремя элементами Пустой блок каталога Рис. 8.6. Формат порций каталога При создании нового элемента каталога ядро должно сканировать весь каталог, чтобы убедиться, что имя еще не существует. При осуществлении этого сканирования оно также проверяет каждый блок каталога, достаточно ли в нем места, в котором можно разместить новый элемент. Пространство необязательно должно быть не- непрерывным. Ядро уплотнит действительные элементы внутри блока каталога, чтобы объединить несколько небольших неиспользующихся частей воедино в одно простран- пространство, которое достаточно для сохранения нового элемента. Используется первый блок, в котором достаточно места. Ядро не будет ни уплотнять пространство в разных блоках каталогов, ни создавать элемент, который занимает два блока каталога, поскольку оно хочет всегда иметь возможность выполнять обновления каталогов путем записи единственного блока каталога. Если при сканировании каталога место не найдено, в конце каталога выделяется новый блок. Приложения получают порции каталогов от ядра, используя системный вызов getdirentries. Для локальной файловой системы формат каталогов на диске идентичен формату, ожидаемому приложением, поэтому порции возвращаются без обработки. Когда каталоги читаются через сеть или из файловых систем, не относящихся к BSD, таких, как HFS Apple, системный вызов getdirentries должен преобразовать представ- представление каталога на диске в описываемое представление. Обычно программы хотят читать по одному элементу каталога за раз. Этот интерфейс предоставляется процедурами доступа к каталогу. Функция opendirQ воз- возвращает указатель структуры, который используется readdirQ для получения порций каталогов с помощью getdirentries', readdirQ при каждом вызове возвращает сле- следующий элемент из порции. Функция closedirQ освобождает память, выделенную opendirQ, и закрывает каталог. Кроме этого, имеется функция rewinddirQ для установления
368 Глава 8. Локальные файловые системы положения чтения в начало; функция telldirQ, которая возвращает структуру, описы- описывающую текущее положение в каталоге; функция seekdirQ, которая возвращает поло- положение, полученное ранее с помощью telldirQ. Файловая система UFS1 использует 32-разрядные номера inode. Хотя было заманчиво увеличить в UFS2 номера inode до 64 битов, это потребовало бы измене- изменения формата каталога. Имеется большое количество кода, который работает непо- непосредственно с элементами каталога. Изменение формата каталога повлекло бы за собой создание значительно большего количества специфичных для файловой системы функций, которые увеличили бы проблемы сложности и удобства сопрово- сопровождения кода. Более того, текущие API для ссылок на элементы каталогов исполь- используют 32-разрядные номера inode. Поэтому даже если лежащая в основе файловая система поддерживает 64-разрядные номера inode, они не могут быть в настоящее время сделаны видимыми пользовательским приложениям. В ближайшее время при- приложения не столкнутся с ограничением в 4 миллиарда файлов на файловую систему, которую накладывают 32-разрядные номера inode. Если мы предположим, что скорость роста числа файлов на файловую систему за прошлые 20 лет останется такой же, можно прикинуть, что 32-разрядного номера inode должно быть доста- достаточно в течение следующих 10-20 лет. Однако предел будет достигнут до того, как будет достигнут предел 64-разрядного блока UFS2, поэтому файловая система UFS2 зарезервировала в суперблоке флаг для указания того, что она является файловой системой с 64-разрядными номерами inode. Когда придет время начать использовать 64-разрядные номера inode, флаг может быть включен и может быть использован новый формат каталогов. Ядра, которые предшествуют введению 64-разрядных номеров inode, будут проверять этот флаг и знать, что они не могут монтировать такие файловые системы. Отыскание имен в каталогах Обычным запросом к файловой системе является поиск определенного имени в ката- каталоге. Ядро обычно выполняет поиск, начиная с начала каталога и проходя через него, сравнивая по очереди с каждым элементом. Сначала длина запрошенного имени срав- сравнивается с длиной проверяемого имени. Если длины совпадают, выполняется строко- строковое сравнение запрашиваемого имени и элемента каталога. Если они совпадают, поиск завершен; если нет, будь то длина имени или сравнение строк, поиск продолжается со следующего элемента. Каждый раз, когда находится имя, то имя и содержащий его ка- каталог вводятся к системный кеш имен, описанный в разделе 6.6. Каждый раз, когда поиск был безуспешным, в кеше создается элемент, показывающий, что имя не суще- существует в определенном каталоге. Перед началом просмотра каталога ядро ищет имя в кеше. Если находится положительный или отрицательный элемент, поиска в каталоге можно избежать.
8.3. Именование 369 Другой обычной операцией является поиск всех элементов в каталоге. Например, многие программы выполняют системный вызов stat для каждого имени в каталоге в том порядке, в каком имена появляются в каталоге. Чтобы повысить производитель- производительность для этих программ, ядро поддерживает для каждого каталога смещение послед- последнего успешного поиска. Каждый раз при поиске в этом каталоге поиск начинается со смещения, на котором было найдено предыдущее имя (вместо того, чтобы начать с начала каталога). Для программ, которые последовательно проходят через каталог с п файлами, время поиска снижается с порядка (п ) до порядка (п). Быстрым контрольным тестом, демонстрирующим максимальную эффективность кеша, является запуск команд Is -1 в каталоге, содержащем 600 файлов. В системе, сохраняющей недавнее смещение каталога, объем системного времени для этого теста снижается на 85 процентов. К сожалению, максимальная эффективность много боль- больше, чем средняя эффективность. Хотя кеш эффективен на 90 процентов при попада- попадании, он применим лишь примерно для 25 процентов искомых имен. Несмотря на значительное уменьшение количества времени, которое тратит сама процедура поиска, улучшение уменьшается, поскольку больше времени тратится в процедурах, которые вызывают данную процедуру. Каждый промах кеша вызывает доступ к каталогу дважды - один раз для поиска из середины до конца, а другой раз для поиска от начала до середины. Эти кеши обеспечивают хорошую производительность поиска в каталоге, но являются неэффективными для больших каталогов с высокой частотой создания и уда- удаления элементов. Каждый раз при создании нового элемента каталога ядро должно сканировать весь каталог, чтобы убедиться, что такой элемент еще не существует. Когда удаляется существующий элемент, ядро должно сканировать каталог, чтобы найти удаляемый элемент. Для каталогов с множеством элементов на эти линейные поиски тратится значительное время. Подход к решению этой проблемы в FreeBSD заключается во введении динамиче- динамического хеширования каталогов, что подгоняет систему индексирования каталогов к UFS [Dowse & Mai one, 2002]. Чтобы избежать повторных линейных поисков больших ката- каталогов, при динамическом хешировании каталогов хеш-таблица элементов каталога строится на ходу, при первом доступе к каталогу. Эта таблица позволяет избежать ска- сканирования каталога при последующих поисках, созданиях и удалениях элементов. В отличие от файловых систем, изначально спроектированных для работы с большими каталогами, эти индексы не сохраняются на диске, и поэтому система является обратно совместимой. Недостатком является то, что индексы необходимо создавать при первой встрече с большим каталогом после каждой перезагрузки системы. Следствием дина- динамического хеширования каталогов является то, что большие каталоги в UFS вызывают минимальные проблемы производительности. Когда мы строили UFS2, мы обдумывали проблему обновления большого каталога путем перехода на более сложную структуру каталога, такую, как использование дво- двоичных деревьев. Эта методика используется во многих современных файловых системах,
370 Глава 8. Локальные файловые системы таких, как XFS [Sweeney et al., 1996], JFS [Best & Kleikamp, 2003], ReiserFS [Reiser, 2001 ] и последние версии Ext2 [Phillips, 2001]. Мы решили не делать изменений вовремя первой реализации UFS2 по нескольким причинам. Во-первых, у нас были ограничены время и ресурсы, а нужно было получить что-то работающее и стабильное, что можно было бы использовать в интервале времени, отведенном для выпуска FreeBSD 5.2. Сохраняя тот же формат каталогов, мы могли повторно использовать весь код катало- каталогов из UFS1, не должны были менять многочисленные утилиты файловой системы для поддержки нового формата каталога и могли создать стабильную и надежную файло- файловую систему в доступный нам интервал времени. Другой причиной того, что мы чувст- чувствовали, что можем сохранить существующую структуру каталога, является добавле- добавление динамического хеширования каталогов в FreeBSD. Заимствуя методику, используемую файловой системой Ext2, был также добавлен флаг, показывающий, что для каталогов поддерживается структура индексирования на диске [Phillips, 2001]. Этот флаг в существующей реализации UFS безоговорочно отключен. В будущем, если будет добавлена реализация структуры индексирования ката- каталогов на диске, реализации, ее поддерживающие, не будут выключать флаг. Поддержи- Поддерживающие индексацию ядра будут поддерживать индексы и оставлять флаг включенным. Если будет запущено старое ядро, не поддерживающее индексирование, оно отключит флаг таким образом, что когда файловая система снова будет запущена под новым ядром, то новое ядро обнаружит, что флаг индексирования был выключен, и будет знать, что индексы могут быть просрочены и что их нужно перестроить заново перед началом исполь- использования. Единственным ограничением на реализацию индексации является то, что это должны быть структуры данных, которые ссылаются на старый линейный формат каталога. Преобразование имен путей Теперь мы готовы описать, как файловая система отыскивает имена путей. Небольшая файловая система, представленная на рис. 8.5, на рис. 8.7 расширена, чтобы показать ее внутреннюю структуру. Каждый из файлов на рис. 8.5 показан развернутым в состав- составляющие его inode и блоки данных. В качестве примера того, как работают эти структуры данных, рассмотрите, как система находит файл /usr/bin/vi. Сначала она должна оты- отыскать в корневом каталоге файловой системы каталог usr. Вначале она находит inode, описывающий корневой каталог. По соглашению inode 2 всегда зарезервирован для корневого каталога файловой системы; поэтому система находит и загружает в память inode 2. Этот inode показывает, где находятся блоки данных для корневого каталога. Эти блоки данных также необходимо загрузить в память, чтобы в них можно было отыскать элемент usr. Найдя элемент для usr, система знает, что содержание usr описывается inode 4. Вернувшись снова к диску, система выбирает inode 4, чтобы найти, где расположены блоки данных для usr. Осуществляя поиск в этих блоках, она находит элемент для bin. Элемент bin указывает на inode 7. Далее система загружает с диска inode 7 и связанные с ним блоки данных, чтобы искать элемент для vi. Найдя, что vi описывается inode 9, сис- система может выбрать этот inode и блоки, содержащие двоичные данные vi.
8.3. Именование 371 root i wheel i drwxr-xr-x Apr 1 2004 0 root i wheel i drwxr-xr-x Apr 1 2004 root i wheel rwxr-xr-x Apr 15 2004 kirk i staff rw-rw-r— Jan 19 2004 root i wheel i drwxr-xr-x Apr 1 2004 bin i bin i rwxr-xr-x Apr 15 2004 Список inode i #2 usr vmunix • • • 2 2 4 5 #7 Hello World! groff 10 #9 Блоки данных Каталог / .. bin foo • • • 4 2 7 6 Каталог /usr текст данные Файл /vmunix Файл /usr/foo Каталог /usr/bin text data Файл /usr/bin/vi Рис. 8.7. Внутренняя структура небольшой файловой системы
372 Глава 8. Локальные файловые системы Ссылки Как показано на рис. 8.8, у каждого файла есть единственный inode, но на этот inode может ссылаться множество элементов каталогов в той же самой файловой системе (т.е. inode может иметь множество имен). Каждый элемент каталога создает прямую ссылку (hard link) имени файла на inode, который описывает содержание файла. Понятие ссылок является фундаментальным; inode не находятся в каталогах, но существуют отдельно, и на них ссылаются ссылки. Когда все ссылки на inode удалены, inode осво- освобождается. Если одна ссылка на файл удалена и имя файла создано заново с новым содержанием, другие ссылки будут продолжать указывать на старый inode. На рис. 8.8 показаны два различных элемента каталога, foo и bar, которые ссылаются на один и тот же файл; таким образом, inode для файла содержит количество ссылок, равное 2. /usr/joe Счетчик ссылок = 2 inode файла Каталоги РИС. 8.8. Прямые ссылки на файл Система поддерживает также символические ссылки (symbolic link), или гибкие ссылки (soft link). Символическая ссылка реализована в виде файла, содержащего имя пути. Когда система сталкивается при поиске компонента имени пути с символической ссылкой, содержимое символической ссылки присоединяется спереди к оставшейся части имени пути; поиск продолжается с получившимся именем пути. Если симво- символическая ссылка содержит имя абсолютного пути, используется этот абсолютный путь. В противном случае содержимое символической ссылки оценивается относи- относительно расположения ссылки в иерархии файлов (не относительно текущего рабочего каталога или вызывающего процесса). Символическая ссылка показана на рис. 8.9. Здесь есть прямая ссылка, foo, которая указывает на файл. Другая ссылка, bar, указывает на другой inode, содержание которого представляет собой имя пути ссылаемого файла. Когда процесс открывает bar, система интерпретирует содержание символической ссылки как имя пути для обнаружения
8.3. Именование 373 файла, на который ссылается ссылка. Символические ссылки рассматриваются систе- системой как файлы данных, а не как часть структуры файловой системы; поэтому они могут указывать на каталоги или файлы в других файловых системах. Если имя файла удаляется и заменяется, все символические ссылки, указывающие на него, получат доступ к новому файлу. Наконец, если имя файла не заменяется, символическая ссылка будет указывать в никуда, и любые попытки получить к ней доступ будут возвращать ошибку. /usr/joe /usr/sue bar Каталоги Число ссылок = ' Описание файла Число ссылок = 1 /usr/joe/foo Рис. 8.9. Символическая ссылка на файл Когда к символической ссылке применяется open, он возвращает дескриптор файла для указываемого файла, а не для самой ссылки. В противном случае было бы необхо- необходимо использовать перенаправление для получения доступа к указываемому файлу - а обычно нужен этот файл, а не ссылка. По этой же причине большинство других сис- системных вызовов, которые принимают аргументы имени пути, также следуют по симво- символической ссылке. Иногда бывает полезно иметь возможность определить символиче- символическую ссылку при прохождении файловой системы или при выполнении архивирования на ленте. Поэтому доступен системный вызов Istat, чтобы получить состояние симво- символической ссылки вместо объекта, на который эта ссылка указывает. У символической ссылки есть несколько преимуществ перед прямой ссылкой. Поскольку символическая ссылка сохраняется как имя пути, она может ссылаться на каталог или файл в другой файловой системе. Для предотвращения циклов в иерархии файловой системы непривилегированным пользователям не разрешается создавать прямые ссылки (кроме . и ..), которые ссылаются на каталог. Дизайн прямых ссылок предотвращает ссылки на файлы в другой файловой системе.
374 Глава 8. Локальные файловые системы Есть несколько интересных последствий символических ссылок. Рассмотрите про- процесс, у которого текущий рабочий каталог /usr/keith и который выполняет cd src, где src является символической ссылкой на каталог /usr/src. Если процесс выполнит после этого cd .., текущим рабочим каталогом для процесса буцет /usr, вместо /usr/keith, как было бы, если src был бы обычным каталогом, а не символической ссылкой. Ядро можно изме- изменить, чтобы оно отслеживало символические ссылки, через которые проходит процесс, и интерпретировало .. по-другому, если доступ к этому каталогу был осуществлен через символическую ссылку. С реализацией этого есть две проблемы. Во-первых, ядру пришлось бы поддерживать потенциально неограниченный объем информации. Во-вторых, ни одна программа не должна зависеть от способности использовать .., поскольку она не может быть уверена, как может интерпретироваться имя. Многие оболочки отслеживают прохождения символических ссылок. Когда поль- пользователь изменяет каталог через .. из каталога, в который он вошел через символиче- символическую ссылку, оболочка возвращает пользователя в каталог, из которого он пришел. Хотя оболочке могло бы понадобиться поддерживать неограниченное количество ин- информации, худшее, что может случиться, - это то, что оболочка израсходует память. Неудача оболочки повлияла бы лишь на пользователя, достаточно глупого, чтобы бес- бесконечно проходить через символические ссылки. Отслеживание символических ссылок в оболочке влияет лишь на команды изменения каталога; программы могут продолжать зависеть от .. для ссылки на своих истинных родителей. Таким образом, отслеживание символических ссылок в оболочке извне ядра является приемлемым. Поскольку символические ссылки могут вызывать в файловой системе циклы, ядро предотвращает зацикливания, допуская максимум восемь прохождений симво- символических ссылок в одном преобразовании имени пути. Если достигнут предел, ядро выдает ошибку (ELOOP). 8.4. Квоты Разделение ресурсов всегда было целью проекта для системы BSD. По умолчанию любой отдельный пользователь может выделить все доступное пространство в файло- файловой системе. В определенных окружениях неконтролируемое использование дискового пространства является неприемлемым. Поэтому FreeBSD включает механизм квот для ограничения количества ресурсов файловой системы, которые пользователь или члены группы могут получить. Механизм квот устанавливает лимиты как на число файлов, так и на число дисковых блоков, которые пользователь или члены группы могут выде- выделить. Квоты могут быть установлены отдельно для каждого пользователя и группы в каждой файловой системе.
8.4. Квоты 375 Квоты поддерживают как жесткие, так и мягкие ограничения. Когда процесс пре- превышает свой мягкий лимит, на терминале пользователя отображается предупрежде- предупреждение; процесс-нарушитель не предотвращается от выделения памяти, если только он не превысил свой жесткий лимит. Идея в том, что пользователи должны оставаться в пре- пределах своих мягких лимитов между сеансами регистрации, но они могут использовать больше ресурсов, когда они активны. Если пользователь не устранит проблему за время отсрочки, мягкий лимит начнет проводиться в жизнь как жесткий лимит. Время отсрочки устанавливается системным администратором и по умолчанию составляет семь дней. Эти квоты происходят из более крупного пакета ограничений ресурсов, который был разработан в университете Мельбурна в Австралии Робертом Эльцем (Robert Elz) [Elz, 1984]. Квоты подключаются к системе главным образом в качестве дополнения к про- процедурам выделения ресурсов. Когда у процедуры выделения запрашивается новый блок, запрос сначала проверяется системой квот в следующем порядке. 1. Если имеется квота пользователя, связанная с файлом, система квот справляется о квоте, связанной с владельцем файла. Если владелец достиг или превысил свой лимит, запрос отвергается. 2. Если есть групповая квота, связанная с файлом, система квот запрашивает квоту, связанную с группой файла. Если группа достигла или превысила свой лимит, запрос отвергается. 3. Если проверка квот проходит, разрешается выполнение запроса, и он добавляется в статистику использования для файла. Когда достигаются квоты пользователя или группы, распределитель возвращает ошибку, как если бы файловая система была заполнена. Ядро доводит эту ошибку до процесса, выполняющего системный вызов write. Квоты назначаются файловой системе после того, как она смонтирована. Систем- Системный вызов связывает файл, содержащий квоты, со смонтированной файловой систе- системой. По соглашению файл с квотами пользователя называется quota.user, а файл с квотами группы называется quota.group. Эти файлы обычно находятся либо в корне смонтированной файловой системы, либо в каталоге /var/quotas. Для наложения каждой квоты система открывает соответствующий файл квот и сохраняет ссылку на него в элементе таблицы монтирования, связанной со смонтированной файловой сис- системой. На рис. 8.10 показана ссылка таблицы монтирования. Здесь в корневой файло- файловой системе есть квоты для пользователей, но нет для групп. Файловая система /usr накладывает квоты как на пользователей, так и на группы. Когда нужны квоты для раз- различных пользователей или групп, они берутся из соответствующего файла квот.
376 Глава 8. Локальные файловые системы Рис. 8.10. Ссылки на файлы квот struct mount ДЛЯ / struct ufsjnount * struct mount для /usr struct mount для /arch struct ufsjnount struct nfsjnount vnode для /quota.user vnode для /usr/quota.user [ vnode для/usr/quota.group Файлы квот поддерживаются в виде массива записей квот, индексируемых по иденти- идентификаторам пользователей или групп; на рис. 8.11 показана типичная запись в файле квот пользователей. Чтобы найти квоту для идентификатора пользователя /, система ищет по смещению / х sizeof (структура квоты) в файле квот и считьшает структуру квоты по этому смещению. Каждая структура квоты содержит ограничения, наложенные на пользователя для соответствующей файловой системы. Эти ограничения включают жесткие и мягкие лимиты на число блоков и inode, которые может иметь пользователь, число блоков и inode, которые пользователь выделил к настоящему моменту, и время, по истечении которого мягкий лимит должен перерасти в жесткий лимит. Файл групповых квот работает таким же способом, за тем исключением, что он индексируется по идентификатору группы. uidO: uidl: uid uid n: Квота на блоки (мягкое ограничение) Лимит блоков (жесткое ограничение) Текущее число блоков Время начала применения квоты на блоки Квота на число inode (мягкое ограничение) Лимит inode (жесткое ограничение) Текущее число inode Время начала применения квоты на inode Файл quota.user РИС. 8.11. Содержание записи квоты Блок квот для uid / Активные квоты хранятся в системной памяти в структуре данных, известной как эле- элемент dquot; на рис. 8.12 показаны два типичных элемента. Кроме ограничений квот и ис- использования, извлеченного из файла квот, элемент dquot содержит информацию о том, когда квота используется. Эта информация включает поля для обеспечения быстрого дос- доступа и идентификации. Квоты проверяются процедурой chkdqQ. Поскольку квоты может
8.4. Квоты 377 понадобиться обновлять при каждой записи на диск, chkdq() должна иметь возможность находить и манипулировать ими быстро. Таким образом, задача нахождения структуры dquot, связанной с файлом, решается, когда файл впервые открывается для записи. Когда сделана проверка прав доступа на запись, система проверяет, есть ли связанные с файлом квоты пользователя или группы. Если существуют одна или более квот, в mode устанавли- устанавливается одна или более ссылок на соответствующие структуры dquot в течение времени при- присутствия mode в памяти. Процедура chkdq() может определить, что у файла есть квоты, просто проверив, не равен ли null указатель dquot; если это не так, ко всей необходимой информации можно получить прямой доступ. Если у пользователя и группы открыто мно- множество файлов в одной и той же файловой системе, все inode, описьшающие эти файлы, указывают на один и тот же элемент dquot. Таким образом, число блоков, выделенных определенному пользователю или группе, всегда можно легко и согласованно узнать. 1 fs - /usr uid = 8 • fs - /usr uid - 8 • pX | fs /usr gid-12 —j fs = /usr uid = 8 Число ссылок = 2 Тип = user КгТПТС JCROT ППЯ HlH 8 fs = /usr gid=12 Число ссылок = 1 Тип - group Блок квот для gid 12 dqhash inode -элементы РИС. 8.12. Элементы dquot dquot- Число элементов dquot в системе может значительно вырасти. Чтобы избежать ли- линейного поиска среди всех элементов dquot, система хранит набор хеш-цепочек, ключами которых выступают файловые системы и идентификаторы пользователей или групп. Даже с сотнями элементов dquot ядру нужно обследовать лишь около пяти элементов, чтобы определить, присутствует ли в памяти запрошенный элемент dquot. Если элемент dquot не является резидентным, как в случае открытия файла для записи в первый раз, система должна повторно выделить элемент dquot и прочесть квоту с диска. Элемент dquot повторно выделяется из дольше всех не использовавшегося элемента dquot. Чтобы можно было быстро найти самый старый элемент dquot, систе-
378 Глава 8. Локальные файловые системы ма хранит неиспользуемые элементы dquot связанными в цепочку LRU. Когда счетчик ссылок структуры dquot падает до нуля, система помещает эту dquot в конец цепочки LRU. Структура dquot не удаляется из своей хеш-цепочки, поэтому, если она вскоре снова понадобится, ее по-прежнему можно найти. Лишь когда структура dquot исполь- используется повторно для новой записи квоты, она удаляется и повторно связывается с хеш- цепочкой. Элемент dquot в начале цепочки LRU выступает в качестве дольше всех не использовавшегося элемента dquot. Часто использующиеся элементы dquot забирают- забираются из середины цепочки LRU и повторно помещаются после использования в конец. Хеширующая структура позволяет быстро находить структуры dquot. Однако она не решает проблему того, как определить, что у пользователя нет квоты в определен- определенной файловой системе. Если у пользователя нет квоты, поиск квоты завершится неудачей. Затраты на обращение к диску и чтение файла квот для определения, что у пользователя нет наложенных квот, были бы чрезмерными. Чтобы избежать выпол- выполнения этой работы каждый раз при доступе к новому файлу для записи, система хранит элементы dquot без квот. Когда осуществляется первый доступ к inode, которым владе- владеет пользователь или группа, у которых нет еще элемента dquot, создается фиктивный элемент dquot, в котором в качестве ограничений квот установлены бесконечные значения. Когда процедура chkdqQ встречается с таким элементом, она обновляет поля использования, но не накладывает никаких ограничений. Когда позже пользователь записывает другие файлы, будет найден тот же самый элемент dquot, позволяя тем самым избежать дополнительного доступа к файлу квот на диске. Обеспечение того, что файл всегда будет иметь элемент dquot, повышает производительность записи дан- данных, поскольку chkdqQ может предполагать, что указатель dquot всегда действитель- действительный, вместо необходимости проверять его каждый раз перед использованием. Квоты записываются обратно на диск, когда они выбывают из кеша при выполне- выполнении файловой системой sync или когда файловая система демонтируется. Если система терпит аварию, оставляя квоты в несогласованном состоянии, системный администратор должен запустить программу quotacheck, чтобы восстановить информацию об исполь- использовании ресурсов в файлах квот. 8.5. Блокировки файлов Блокировки могут быть помещены на любой произвольный диапазон байтов внутри файла. Эта семантика поддерживается в FreeBSD посредством списка блокировок, каждый из которых описывает блокировку определенного диапазона байтов. Пример файла, содержащего несколько диапазонов блокировок, показан на рис. 8.13. Список удерживаемых в настоящее время или активных блокировок показан наверху рисунка, возглавляемого полем ijockfv inode, связанным через поля Ifjtext структур блокиро- блокировок. Каждая структура блокировки идентифицирует тип блокировки (исключительная или разделяемая), диапазон байтов, на который накладывается блокировка, и иденти-
8.5. Блокировки файлов 379 фикатор держателя блокировки. Блокировка может идентифицироваться либо указате- указателем на элемент процесса, либо указателем на элемент файла. Указатель процесса ис- используется для блокировок диапазонов в стиле POSIX; указатель элемента файла ис- используется для блокировок всего файла в стиле BSD. Примеры в данном разделе изо- изображают идентификатор в виде указателя на элемент процесса. В данном примере есть три активные блокировки: исключительная блокировка, удерживаемая процессом 1 на байты с 1-го по 3-й; разделяемая блокировка, удерживаемая процессом 2 на байты с 7-го по 12-й; и разделяемая блокировка, удерживаемая процессом 3 на байты с 7-го по 14-й. Рис. 8.13. Набор блокировок диапазонов для файла Помимо активных блокировок имеются другие процессы, которые находятся в состоя- состоянии сна в ожидании получения примененной блокировки. Ожидающие блокировки возглавлены полем Ifjblock активной блокировки, которая предотвращает их от приме- применения. Если имеется несколько ожидающих блокировок, они связываются через поля Ifjblock. Запросы новых блокировок помещаются в конец списка; таким образом, про- процессы имеют тенденцию получить блокировки в том порядке, в каком они их запраши- запрашивают. Каждая ожидающая блокировка использует для идентификации активной бло- блокировки, которая в настоящее время ее блокирует, поле \f_next. В примере на рис. 8.13 первая активная блокировка имеет две другие ожидающие блокировки. Есть также
380 Глава 8. Локальные файловые системы ожидающий запрос для диапазона от 9 до 12, который в настоящее время связан со вторым активным элементом. Он мог бы с таким же успехом быть связан с третьим активным элементом, поскольку третий элемент также блокирует его. Когда активная блокировка освобождается, все ожидающие элементы для этой блокировки пробуж- пробуждаются, чтобы они могли повторить свой запрос. Если была бы освобождена вторая активная блокировка, результатом было бы то, что ее текущий ожидающий запрос переместился бы в заблокированный список для последнего активного элемента. Проблемой, которую должна решить реализация блокировки, является обнаруже- обнаружение потенциальных тупиков. Чтобы увидеть, как обнаруживается тупик, рассмотрите добавление запроса блокировки процессом 2, обведенным на рис. 8.13 пунктирной ли- линией. Поскольку запрос заблокирован активной блокировкой, процесс 2 должен нахо- находиться в состоянии сна в ожидании очистки активной блокировки на диапазоны с 1-го по 3-й. Мы следуем по указателю Ifjiext из запрашивающей блокировки (в пунк- пунктирном прямоугольнике), чтобы идентифицировать активную блокировку для диапа- диапазона с 1-го по 3-й как удерживаемую процессом 1. Канал ожидания для процесса 1 пока- показывает, что процесс 2 спит в ожидании освобождения блокировки, и определяет струк- структуру ожидающей блокировки, как ожидающую блокировку (диапазон от 9 до 12), подвешенную к полю Ifjblock второй активной блокировки (диапазон от 7 до 12). Мы следуем за полем Ifjiext этой структуры ожидающей блокировки (диапазон от 9 до 12) до второй активной блокировки (диапазон от 7 до 12), которая удерживается инициа- инициатором запроса блокировки, процессом 2. Таким образом, запрос на блокировку отвергается, поскольку он привел бы к тупику между процессами 1 и 2. Этот алгоритм работает с циклами блокировок и процессов произвольного размера. Производитель- Производительность приемлемая при условии, что имеется не более 50 процессов, пытающихся получить блокировки в пределах одного и того же диапазона файла. Как мы заметили, ожидающий запрос для диапазона с 9 до 12 мог бы с таким же успехом быть связан с третьей активной блокировкой для диапазона от 7 до 14. Если бы это было так, запрос для добавления блокировки в пунктирном прямоугольнике завершился бы успешно, поскольку третья активная блокировка удерживается процес- процессом 3, а не процессом 2. Если бы следующим запросом блокировки для этого файла было бы освобождение третьей активной блокировки, тогда возникло бы обнаружение тупика, когда ожидающая блокировка процесса 1 переместилась бы во вторую актив- активную блокировку (диапазон от 7 до 12). Разница в том, что сообщение об ошибке тупика вместо процесса 2 получил бы процесс 1. Когда делается запрос новой блокировки, он должен быть сначала проверен, не блокируется ли он существующими блокировками, удерживаемыми другими процес- процессами. Если он не блокируется другими процессами, нужно проверить, не перекрывает ли он какие-нибудь существующие блокировки, уже удерживаемые запрашивающим процессом. Есть пять возможных случаев перекрывания, которые нужно рассмотреть; эти возможности показаны на рис. 8.14. На рисунке предполагается, что тип нового
8.5. Блокировки файлов 381 Случай Существующая Новая Становится У/ У//УУ/ У/ У/У УУУ \ \ Рис. 8.14. Пять типов перекрывания, учитываемых ядром при добавлении блокировки диапазона запроса отличается от типа существующей блокировки (т.е. исключительный запрос против разделяемой блокировки или наоборот). Если существующая блокировка и запрос относятся к одному типу, анализ несколько проще. Пять случаев следующие. 1. Новый запрос точно перекрывает существующую блокировку. Он заменяет сущест- существующую блокировку. Если новый запрос понижает уровень от исключительного до разделяемого, пробуждаются все запросы, ожидающие старую блокировку. 2. Новый запрос является подмножеством существующей блокировки. Сущест- Существующая блокировка делится на три части (на две, если новая блокировка начинает- начинается в начале или заканчивается в конце существующей блокировки). Если тип ново- нового запроса отличается от типа существующей блокировки, все запросы, ожи- ожидающие старую блокировку, пробуждаются^ чтобы их можно было переназначить на исправленные новые куски и в результате заблокировать на блокировке, удерживаемой каким-нибудь другим процессом, или удовлетворить их запрос. 3. Новый запрос является надмножеством существующей блокировки. Он замещает существующую блокировку. Если новый запрос понижает уровень от исключи- исключительного до разделяемого, пробуждаются все процессы, ожидающие старую бло- блокировку. 4. Новый запрос расширяется за пределы конца существующей блокировки. Сущест- Существующая блокировка укорачивается, а ее перекрывающаяся часть заменяется новым запросом. Пробуждаются все запросы, ожидающие существующей блокировки, чтобы их можно было переназначить на исправленные новые куски и в результате заблокировать на блокировках, удерживаемых какими-нибудь другими процессами, или удовлетворить их запрос.
382 Глава 8. Локальные файловые системы 5. Новый запрос расширяется за пределы до начала существующей блокировки. Существующая блокировка укорачивается, а ее перекрывающаяся часть заменяет- заменяется новым запросом. Пробуждаются все запросы, ожидающие существующей бло- блокировки, чтобы их можно было переназначить на исправленные новые куски и в результате заблокировать на блокировках, удерживаемых какими-нибудь дру- другими процессами, или удовлетворить их запрос. Кроме пяти базовых типов описанных перекрываний запрос может охватывать несколько существующих блокировок. В частности, новый запрос может быть состав- составлен из нуля или одного запроса типа 4, нуля или более типа 3 и нуля или одного типа 5. Чтобы понять, как обрабатывается перекрывание, мы можем рассмотреть при- пример, показанный на рис. 8.15. На этом рисунке показан файл, в котором все активные блокировки диапазонов удерживаются процессом 1 плюс ожидающая блокировка для процесса 2. ijockf " • • > Ifnext Тип = EX ID=1 Диапазон = 1..3 Ifjblock ] inode f Ifjiext Тип = EX ID = 2 Диапазон = 3.. 12 Ifjblock Ifnext Тип = SH ID = 1 Диапазон = 5..10 Ifjblock V Ifjiext Тип = SH ID= 1 Диапазон = 12.. 19 Ifjblock V V Рис. 8.15. Блокировки до добавления запроса исключительной блокировки процессом 1 на диапазон 3..13 Теперь рассмотрите запрос процессом 1 исключительной блокировки на диапазон от 3 до 13. Этот запрос не конфликтует ни с одной из активных блокировок (поскольку все активные блокировки уже удерживаются процессом 1). Запрос перекрывает все три активные блокировки, поэтому они представляют перекрытия типа 4, типа 3 и типа 5 соответственно. Результат обработки запроса показан на рис. 8.16. Первая и третья активные блокировки усечены до границы нового запроса, а вторая блокировка заме- заменена полностью. Пробуждается запрос, который ожидал первую блокировку. Он больше не заблокирован первой блокировкой, но он заблокирован вновь установ- установленной блокировкой. Поэтому он теперь прикрепляется к заблокированному списку второй блокировки. Первую и вторую блокировки можно было бы объединить,
8.5. Блокировки файлов 383 поскольку они одного типа и удерживаются одним и тем же процессом. Однако теку- текущая реализация не делает попыток таких объединений, поскольку блокировки диапа- диапазонов обычно освобождаются через тот же самый диапазон, посредством которого они были созданы. Если бы было сделано объединение, ее пришлось бы снова расщепить, когда было бы запрошено освобождение. [node Ifjiext Тип= EX ID=1 Диапазон = 1..2 // block "Л Ifjiext Тип = EX ID=1 Диапазон = 3..13 Ifjlock ~ 1 Ifjiext Тип = SH ID=1 Диапазон = 14.. 19 Ifjlock Ifjiext тип = EX ID = 2 диапазон = lf_bloc 3..12 к Рис. 8.16. Блокировки после добавления запроса исключительной блокировки процессом 1 на диапазон 3..13 Запросы на освобождение блокировок проще, чем запросы на добавление; им нужно рассмотреть лишь существующие блокировки, удерживаемые запрашивающим процессом. На рис. 8.17 показаны пять возможных способов того, как могут перекры- перекрываться запросы освобождения с блокировками запрашивающего процесса. Случай Существующая P азблокирование 1 Становится: нет У нет У/ У у \ У/ У/ Нет Рис. 8.17. Пять типов перекрытия, учитываемых ядром при удалении блокировки диапазона
384 Глава 8. Локальные файловые системы 1. Запрос разблокирования точно перекрывает существующую блокировку. Сущест- Существующая блокировка удаляется, все ожидавшие эту блокировку запросы пробуждаются. 2. Запрос разблокирования является подмножеством существующей блокировки. Она делится на две части (на одну, если запрос разблокирования начинается в начале или заканчивается в конце существующей блокировки). Пробуждаются все запросы, ожидавшие эту блокировку, чтобы их можно было переназначить на исправленные новые части и в результате заблокировать на блокировках, удержи- удерживаемых другими процессами, или удовлетворить их запрос. 3. Запрос разблокирования является надмножеством существующей блокировки. Существующая блокировка удаляется, все ожидавшие ее запросы пробуждаются. 4. Запрос разблокирования простирается дальше конца существующей блокировки. Конец ее укорачивается. Все ожидавшие эту блокировку запросы пробуждаются, чтобы их можно было переназначить на исправленные новые части и в результате заблокировать на блокировках, удерживаемых другими процессами, или удовле- удовлетворить их запрос. 5. Запрос разблокирования простирается до начала существующей блокировки. Начало существующей блокировки укорачивается. Все ожидавшие эту блокировку запросы пробуждаются, чтобы их можно было переназначить на исправленные но- новые части, и, в результате, заблокировать на блокировках, удерживаемых другими процессами, или удовлетворить их запрос. Кроме пяти базовых типов описанных перекрытий запрос разблокирования может охватывать несколько существующих блокировок. В частности, новый запрос может быть составлен из нуля или одного запроса типа 4, нуля или более типа 3 и нуля или одного типа 5. 8.6. Мягкие обновления В файловых системах метаданные (например, каталоги, inode и карты свободных блоков) образуют структуру необработанного объема хранилища (жесткого диска), то есть некоторым образом упорядочивают это хранилище. Метаданные предоставляют ука- указатели и описания для связывания нескольких дисковых секторов в файлы и для иден- идентификации этих файлов. Чтобы быть полезной в качестве постоянного хранилища, файловая система должна поддерживать целостность своих метаданных перед лицом непредсказуемых системных аварий, таких как перебои в электроснабжении и сбои операционной системы. Поскольку такие аварии обычно ведут к потере всей информа- информации в энергозависимой оперативной памяти, информация в энергонезависимом храни- хранилище (т.е. на диске) должна всегда быть достаточно непротиворечивой, чтобы детерминистически реконструировать согласованное состояние файловой системы.
8.6. Мягкие обновления 385 В частности, образ файловой системы на диске не должен иметь висячих указателей на неинициализированную память, противоречивого распределения ресурсов, вызванно- вызванного множеством указателей, и действующих ресурсов, на которых нет указателей. Под- Поддержание этих инвариантов обычно требует упорядочивания (или атомарной груп- группировки) обновлений в небольших располагающихся на диске объектах метаданных. Традиционно файловая система UFS использовала для должного упорядочивания стабильных изменений хранилища синхронные записи. Например, создание файла включает в себя сначала выделение и инициализацию нового inode, а затем заполнение нового элемента каталога, указывающего на него. При подходе с синхронной записью файловая система заставляет приложение, создающее файл, ждать дисковой записи, которая инициализирует inode на диске. В результате операции файловой системы наподобие создания и удаления файла выполняются на скорости работы диска, а не на скорости работы процессора/памяти [McVoy & Kleiman, 1991; Ousterhout, 1990; Seltzer et al., 1993]. Поскольку время доступа к диску длительно по сравнению со скоростями других компонентов компьютера, синхронные записи уменьшают производительность системы. Проблему обновления метаданных можно также решать с помощью других меха- механизмов. Например, можно устранить необходимость в хранении согласованности состояния на диске, использовав технологии энергонезависимой RAM (NVRAM), такие, как бесперебойные источники питания или Flash RAM [Moran et al., 1990; Wu & Zwaenepoel, 1994]. Файловые операции могут продолжаться, как только блок для записи будет скопирован в стабильное хранилище, и обновления могут передаваться на диск в любом порядке и в любое удобное время. Если система испытает сбой, неза- незавершенные дисковые операции могут быть завершены из стабильного хранилища, когда система будет перезагружена. Другим подходом является группирование каждого набора зависимых обновлений в единую неделимую операцию с помощью какой-нибудь формы упреждающей реги- регистрации [Chutani et al., 1992; Hagmann, 1987] или теневых страниц [Chamberlin &Astrahan 1981; Rosenblum & Ousterhout, 1992; Stonebraker, 1987]. Эти подходы добавляют к состоянию на диске журнал обновлений файловой системы на отдельном диске или в стабильном хранилище. Затем операции файловой системы можно про- продолжить, как только операция, которая должна быть выполнена, зарегистрирована в журнале. Если система испытает сбой, незавершенную операцию файловой системы можно завершить из журнала, когда система будет перезагружена. Многие современ- современные файловые системы успешно используют упреждающую регистрацию для повы- повышения производительности по сравнению с подходом синхронной записи. В Ganger & Patt [1994] был предложен и оценен в контексте опытного исследователь- исследовательского образца альтернативный подход, названный мягкими обновлениями (soft updates). За успешной оценкой для FreeBSD была создана производственная версия мягких обновлений. При мягких обновлениях файловая система использует отложенные записи (т.е. кеширование с обратной записью) для изменений метаданных, отслежива-
386 Глава 8. Локальные файловые системы ет зависимости между обновлениями и приводит эти зависимости в исполнение в ходе отложенной записи. Поскольку большинство блоков метаданных содержат множество указателей, часто возникают циклические зависимости, когда зависимости записы- записываются лишь на уровне блока. Поэтому мягкие обновления отслеживают зависимости на уровне отдельных указателей, что позволяет записывать блоки в любом порядке. Все по-прежнему зависимые обновления в блоке метаданных откатываются назад до записи блока и повторяются впоследствии. Таким образом проблема циклов зависимо- зависимостей устраняется. При мягких обновлениях приложения всегда видят наиболее свежие копии блоков метаданных, а диск всегда видит копии, которые согласуются с его другим содержимым. Зависимости обновлений в файловой системе Несколько важных операций файловых систем состоят из ряда связанных модифика- модификаций отдельных структур метаданных. Чтобы обеспечить восстанавливаемость в при- присутствии непредсказуемых сбоев, модификации часто должны быть переданы в ста- стабильное хранилище определенным образом. Например, при создании нового файла файловая система выделяет inode, инициализирует его и создает элемент каталога, который указывает на него. Если система терпит неудачу после того, как новый эле- элемент каталога был записан на диск, но до того, как был записан инициализированный inode, может быть нарушена согласованность, поскольку содержимое inode на диске неизвестно. Чтобы обеспечить согласованность метаданных, инициализированный inode должен достичь стабильного хранилища раньше нового элемента каталога. Мы называем это требование зависимостью обновления, поскольку безопасная запись элемента каталога зависит от предварительной записи inode. Ограничения упорядочива- упорядочивания отображаются в три простые правила. 1. Никогда не указывать на структуру до того, как она будет инициализирована (напри- (например, inode должен быть инициализирован до того, как элемент каталога может ссы- ссылаться на него). 2. Никогда не использовать ресурс повторно до обнуления всех предыдущих указателей на него (например, указатель inode на блок данных должен быть обнулен до того, как дисковый блок может быть выделен для нового inode). 3. Никогда не сбрасывать старый указатель на существующий ресурс до того, как был установлен новый указатель (например, при переименовании файла не удалять старое имя для inode до тех пор, пока не будет записано новое имя). Имеется восемь видов деятельности файловой системы, которым требуется упоря- упорядочивание обновления для обеспечения восстанавливаемости после сбоев: создание файла, удаление файла, создание каталога, удаление каталога, переименование файла/ каталога, выделение блока, косвенные манипуляции с блоком и управление свободной картой.
8.6. Мягкие обновления 387 Двумя основными ресурсами, которыми управляет файловая система, являются inode и блоки данных. Для хранения информации о распределении этих ресурсов исполь- используются две битовые карты. Для каждого inode в файловой системе битовая карта inode со- содержит бит, который устанавливается, если inode используется, и сбрасывается, если он свободен. Для каждого блока в файловой системе битовая карта блоков данных содержит бит, который устанавливается, если блок свободен, и сбрасывается, если он используется. Каждая файловая система делится на куски фиксированного размера, которые называют- называются группами цилиндров (более полно описанные в разделе 8.9). Каждая группа цилин- цилиндров имеет блок группы цилиндров, который содержит битовые карты для inode и блоков данных, находящихся в этой группе цилиндров. Для большой файловой системы такая организация позволяет копировать в память ядра лишь те участки битовой карты файло- файловой системы, которые активно используются. Каждый из этих активных блоков групп ци- цилиндров хранится в отдельном буфере ввода/вывода и может записываться на диск неза- независимо от других блоков групп цилиндров. При создании файла изменяются три структуры метаданных, расположенные в различных блоках. Первым является новый inode, который инициализируется новым типом файла в поле типа, указанием на действительность его счетчика ссылок (т.е. ссылкой на него какого-нибудь элемента каталога), соответствующим значением в поле прав доступа и значениями по умолчанию во всех остальных полях. Вторым является битовая карта inode, которая модифицируется таким образом, чтобы отобра- отобразить выделение inode. Третьим является новый элемент каталога, который заполняется новым именем и указателем на новый inode. Чтобы гарантировать, что битовые карты всегда отражают все выделенные ресурсы, битовая карта должна быть записана на диск до inode или элемента каталога. Поскольку inode находится в неопределенном состоянии до инициализации на диске, правило №1 определяет, что имеется зависи- зависимость обновления, требующая, чтобы соответствующий inode был записан до соответ- соответствующего элемента каталога. Хотя это не является строго обязательным, большинст- большинство реализаций быстрой файловой системы BSD также сразу же записывают блок ката- каталога до возвращения из системного вызова, создающего файл. Вторая синхронная запись гарантирует, что имя файла находится в стабильном хранилище, если приложе- приложение впоследствии выполнит системный вызов fsync. Если бы этого не было сделано, тогда вызову fsync пришлось бы иметь возможность найти все незаписанные блоки ка- каталогов, содержащие имя для файла, и записать их на диск. Сходная зависимость обновления между inode и элементом каталога существует при добавлении второго имени для одного и того же файла (известное также как прямая ссылка), поскольку добавление второго имени требует от файловой системы увеличить счетчик ссылок в inode и записать inode на диск до того, как элемент может появиться в каталоге. При удалении файла изменяются блок каталога, блок inode и одна или более бито- битовых карт групп цилиндров. В блоке каталога соответствующий элемент каталога «удаляется» путем освобождения занимаемого им места или путем обнуления указате- указателя на inode. В блоке inode обнуляются соответствующее поле типа inode, счетчик
388 Глава 8. Локальные файловые системы ссылок и указатели на блоки. Блоки удаленного файла и inode добавляются затем в со- соответствующие карты свободных блоков/in ode. Правило №2 определяет, что имеются зависимости обновления между элементом каталога и inode и между inode и любыми изменяемыми свободными битовыми картами. Чтобы сохранить счетчик ссылок кон- консервативно высоким (и снизить на практике сложность), зависимость обновления между элементом каталога и inode существует также при удалении одного из несколь- нескольких имен (прямых ссылок) для файла. Создание и удаление каталогов во многом сходно с только что описанным для обычных файлов. Однако элемент .. является ссылкой из содержащегося каталога на родительский, что добавляет дополнительные зависимости обновления. В частно- частности, в ходе создания счетчик ссылок родителя должен быть увеличен на диске до того, как будет записан указатель .. нового каталога. Аналогично в ходе удаления счетчик ссылок родителя должен быть уменьшен после того, как будет обнулен указатель .. удаляемого каталога. (Обратите внимание, что это обнуление неявное при удалении указателя содержащегося каталога на соответствующий блок каталога.) Когда выделяется новый блок, его ячейка битовой карты обновляется, чтобы отра- отразить его использование, а содержание блока инициализируется вновь записанными данными или нулями. Кроме того, указатель на новый блок добавляется в inode или косвенный блок (см. далее). Чтобы убедиться, что битовая карта на диске всегда отра- отражает выделенные ресурсы, битовая карта должна быть записана на диск до указателя. Также, поскольку содержание вновь выделенного блока неизвестно, правило №1 опре- определяет зависимость обновления между новым блоком и указателем на него. Поскольку осуществление этой зависимости обновления с синхронными записями может снизить пропускную способность создания данных на степень двойки [Ganger & Patt, 1994], многие реализации игнорируют ее для обычных блоков данных. Такое решение реали- реализации снижает целостность и безопасность, поскольку вновь выделенные блоки обычно содержат данные ранее удаленных файлов. Мягкие обновления позволяют защитить таким образом все выделения блоков с почти нулевым снижением произво- производительности. Манипулирование косвенными блоками не вводит принципиально новых зависи- зависимостей обновления, но они заслуживают отдельного обсуждения. Распределение, как косвенных блоков, так и блоков, на которые указывают косвенные блоки, происходит, как только что обсуждалось. Удаление файлов, а особенно освобождение, более интересно для косвенных блоков. Поскольку ссылка inode является единственным спо- способом идентифицировать косвенные блоки и блоки, подсоединенные к ним (прямо или косвенно), обнуления указателя inode на косвенный блок достаточно для устранения всех восстанавливаемых указателей на указанные блоки. После обнуления указателя на диске все его блоки могут быть освобождены. Исключением из этого правила явля- является случай, когда файл укорачивается частично. В этом случае указатель из inode на
8.6. Мягкие обновления 389 косвенный блок остается. Некоторые из указателей косвенных блоков будут обнулены, а соответствующие им блоки будут освобождены, тогда как оставшиеся указатели останутся без изменений. При переименовании файла затрагиваются два элемента каталога. Создается новый элемент (с новым именем) и устанавливается указатель на соответствующий inode, а старый элемент удаляется. Правило №3 утверждает, что новый элемент должен быть записан на диск до удаления старого элемента, чтобы избежать файла без ссылки после перезагрузки. Если счетчики ссылок поддерживаются консервативно, переиме- переименование включает по крайней мере четыре обновления диска в последовательности: одно для увеличения счетчика ссылок inode; одно для добавления нового элемента каталога; одно для удаления старого элемента каталога и еще одно для уменьшения счетчика ссылок. Если имя уже существовало, тогда добавление нового элемента ката- каталога действует также в качестве первого шага удаления файла, как обсуждалось выше. Интересно, что переименование является единственной файловой операцией POSIX, которая должна иметь атомарное обновление нескольких видимых пользователю структур метаданных для обеспечения идеальной семантики. POSIX не требует ука- указанной семантики, и большинство реализаций, включая FreeBSD, не могут ее предос- предоставить. В активной файловой системе битовые карты постоянно меняются. Таким обра- образом, копия битовых карт в памяти ядра часто отличается от копии, которая хранится на диске. Если система остановится, не записав внутреннее состояние битовых карт, не- некоторые из недавно выделенных inode и блоков данных могут не быть отражены во внешних копиях битовых карт на диске. Поэтому для всех inode в файловой системе должна быть запущена программа проверки файловой системы, fsck, чтобы выяснить, какие inode и блоки используются, и обновить битовые карты [McKusick & Kowalski, 1994]. Дополнительным преимуществом мягких обновлений является то, что они отслеживают запись битовых карт на диск и используют эту информацию для обес- обеспечения того, что ни один из вновь выделенных inode или указателей на вновь выде- выделенные блоки данных не будет записан на диск до тех пор, пока не будет записана на диск битовая карта, ссылающаяся на них. Это гарантирует, что никогда не будет выде- выделено inode или блока данных, которые не отмечены на битовой карте на диске. Эта гарантия, вместе с другими гарантиями, которые обеспечивает код мягких обновлений, означает, что после системного сбоя больше не нужно запускать fsck. Следующие 12 подразделов описывают структуры данных мягкого обновления и их использование для осуществления только что описанных зависимостей обновле- обновления. Описанные структуры и алгоритмы устраняют из файловой системы все синхрон- синхронные операции записи, за исключением частичного урезания файла и системного вызова fsync, который явным образом требует, чтобы все состояние файла было зафик- зафиксировано на диске до возвращения из системного вызова. Ключевым атрибутом мягких обновлений является отслеживание зависимостей на уровне отдельных изменений внутри кешированных блоков. Таким образом, для блока,
390 Глава 8. Локальные файловые системы содержащего 64 inode, система может поддерживать до 64 структур зависимостей по одной для каждого inode в буфере. Аналогично для буфера, содержащего блок каталога, содержащего 50 имен, система может поддерживать вплоть до 50 структур зависимо- зависимостей по одной для каждого имени в каталоге. С таким уровнем подробности информа- информации о зависимостях круговые зависимости между блоками не представляют проблему. Например, когда система хочет записать буфер, содержащий inode, эти inode, которые могут быть безопасно записаны, можно отправить на диск. Любые inode, которые нельзя пока безопасно записать, временно откатываются в свои безопасные значения, пока не продолжится запись на диск. После завершения записи на диск такие inode воз- возвращаются в свои текущие значения. Поскольку в течении всего времени пока содержимое буфера откатывается, пока идет запись на диск и пока содержимое буфера востанавливается - буфер блокируется то доступ к нему всех процессов, желающие использовать буфер, будет заблокирован до тех пор, пока буфер не вернется в свое пра- правильное состояние. Структуры зависимостей Реализация мягких обновлений использует разнообразные структуры данных, чтобы отслеживать зависимости незаконченных обновлений среди структур файловой систе- системы. В табл. 8.2 перечислены структуры зависимостей, используемые в реализации мягких обновлений в BSD, их главные функции и типы блокировок, с которыми они могут быть связаны. Эти структуры зависимостей выделяются и связываются с блока- блоками по мере завершения различных файловых операций. Они соединяются с внутрен- внутренними блоками, с которыми ассоциируются посредством указателя в соответствующем заголовке буфера. Двумя обычными аспектами всех перечисленных структур зависи- зависимостей являются структура, рабочего списка (worklist) и состояния, используемые для отслеживания развития зависимости. Структура worklist в действительности является просто общим заголовком, включенным в качестве первого элемента в каждую структуру зависимости. Она содержит набор связывающих указателей и поле типа для отображения типа струк- структуры, в которую она внедрена. Структура worklist позволяет связывать несколько раз- различных типов структур зависимостей воедино в один список. Код мягких обновлений может пройти через один из таких гетерогенных списков, используя поле типа для определения того, с какого рода структурой зависимости он столкнулся, и предприни- предпринимая соответствующие действия для каждой. Типичным использованием структуры worklist является объединение набора зависи- зависимостей, связанных с буфером. Каждый буфер в системе имеет указатель на добавленный к нему worklist. Любые зависимости, ассоциированные с этим буфером, связываются в его worklist. После блокирования буфера и непосредственно перед тем, как буфер должен быть записан, система ввода/вывода передает буфер коду мягких обновлений, чтобы дать ему знать, что начинается запись на диск. Затем код мягких обновлений про-
8.6. Мягкие обновления 391 ходит список зависимостей, связанный с буфером, и выполняет все необходимые опера- операции отката. После того как запись на диск завершится, но до разблокирования буфера, система ввода/вывода вызывает код мягких обновлений, чтобы дать ему знать, что запись завершилась. После этого код мягких обновлений проходит список зависимостей, свя- связанный с буфером, выполняет все необходимые операции восстановления и освобождает все зависимости, которые удовлетворены данными в буфере, записанном на диск. Другим важным списком, поддерживаемым кодом мягких обновлений, является список задач (tasklist), который содержит фоновые задачи для рабочего демона. Струк- Структуры зависимости, которые обычно добавляются к tasklist в ходе процедуры заверше- завершения записи на диск, описывают задачи, которые стали безопасными после обновления диска, но которые может понадобиться заблокировать и которые поэтому не могут быть завершены в обработчике прерываний. Раз в секунду пробуждается демон syncer (в своей второй роли в качестве рабочего демона мягких обновлений) и вызывает код мягких обновлений, чтобы обработать элементы в tasklist. Работа, осуществляемая для структуры зависимости в этом списке, зависит от типа. Например, для структуры^гее- blks перечисленные блоки помечаются в битовых картах блоков свободными. Для структуры dirrem соответствующий счетчик ссылок inode уменьшается, что может вызвать удаление файла. Табл. 8.2. Мягкие обновления и отслеживание зависимостей Связанные структуры Блок группы цилиндра Имя Функция bmsafemap Отслеживание зависимостей битовой карты (указывает на списки структур зависимостей для недавно выделенных блоков и inode) inodedep Отслеживание зависимостей inode (информация и указатели Блок inode заголовков списков для всех связанных с inodeMH зависимо- зависимостей, включая изменения числа ссылок, указателей блоков и размера файлов) allocdirect Отслеживание блоков, на которые ссылаются inode (связан- Блок данных или косвен- косвенные в списки, на которые указывает inodedep и bmsafemap ный блок или блок каталога для отслеживания зависимости inode от блока и битовой карты, записываемой на диск) indirdep Отслеживание зависимостей косвенных блоков (указывает Косвенный блок на список структур зависимостей для недавно выделенных блоков с указателями в косвенном блоке) allocindir Отслеживание блока, на который ссылается косвенный блок Блок данных или косвен- связанный в список, на который указывает indirdep и bm- ный блок или блок каталога safemap, для отслеживания зависимости косвенного блока от этого блока и битовой карты, записываемой на диск) pagedep Отслеживание зависимостей блока каталога (указывает на Блок каталога списки структур diradd и dirrem) diradd Отслеживание зависимости между новым элементом катало- inodedep и блок каталога га и inode, на который ссылаются mkdir Отслеживание создания нового каталога (используется в до- inodedep и блок каталога полнение к стандартной структуре diradd при осуществле- осуществлении mkdir)
392 Глава 8. Локальные файловые системы Табл. 8.2. Мягкие обновления и отслеживание зависимостей (Окончание) Имя Функция Связанные структуры dirrem Отслеживание зависимости между удаленным элементом Сначала pagedep, потом каталога и удаленным (unlinked) inode tasklist freefrag Отслеживает один блок или фрагмент, который должен быть Сначала inodedep, потом освобожден, как только соответствующий блок (содержа- tasklist щий inode с только что замененным указателем на него) будет записан на диск freeblks Отслеживает все указатели блоков, которые должны быть Сначала inodedep, потом освобождены, как только соответствующий блок (содержа- tasklist щий inode с только что обнуленными указателями на них) будет записан на диск freeflle Отслеживает inode, который должен быть освобожден, как Сначала inodedep, потом только соответствующий блок (содержащий блок inode с tasklist только что восстановленным inode) будет записан на диск У большинства структур зависимостей есть набор флагов, описывающих состояние завершения соответствующей зависимости. Грязные блоки кеша могут быть записаны на диск в любое время. Когда система ввода/вывода передаёт буфер коду мягких обновле- обновлений (до и после записи на диск), состояния соответствующих структур зависимостей определяют, какие действия предпринимаются. Хотя определенные значения разнятся от структуры к структуре, тремя главными флагами и их значениями являются следующие. ATTACHED Флаг ATTACHED показывает, что буфер, с которым связана струк- структура зависимости, в настоящее время не записывается. Когда начина- начинается запись на диск для буфера с зависимостью, для которой нужно сделать откат, флаг в структуре зависимости ATTACHED сбрасыва- сбрасывается, чтобы показать, что в буфере для нее был сделан откат. Когда запись на диск завершается, обновления, описанные структурами за- зависимостей, у которых флаг ATTACHED был сброшен, восстанавли- восстанавливаются, а флаг ATTACHED устанавливается. Таким образом, струк- структура зависимости никогда не может быть удалена, пока не сброшен ее флаг ATTACHED, поскольку информация, необходимая для операции восстановления, была бы тогда потеряна. DEPCOMPLETE Флаг DEPCOMPLETE показывает, что все связанные зависимости были завершены. Когда начинается запись на диск, для обновления, описываемого структурой зависимости, делается откат, если флаг DEPCOMPLETE сброшен. Например, в структуре зависимости, которая связана с вновь выделенными inode или блоками данных, флаг DEPCOMPLETE устанавливается, когда соответствующая битовая карта была записана на диск.
8.6. Мягкие обновления 393 COMPLETE Флаг COMPLETE показывает, что отслеживаемое обновление было зафиксировано на диске. Для некоторых зависимостей для обновле- обновлений будет сделан откат в ходе записи на диск, когда флаг COM- COMPLETE сброшен. Например, для вновь выделенного блока данных флаг COMPLETE устанавливается, когда содержимое блока было записано на диск. Вообще флаги устанавливаются, когда завершается запись на диск, а структура зави- зависимости может быть освобождена, лишь когда установлены все его флаги ATTACHED, DEPCOMPLETE и COMPLETE. Рассмотрите пример вновь выделенного блока дан- данных, который будет отслеживаться структурой allocdirect. Флаг ATTACHED будет вначале установлен, когда происходит выделение. Флаг DEPCOMPLETE будет уста- установлен после того, как записана битовая карта, выделяющая этот новый блок. Флаг COMPLETE будет установлен после того, как записано содержание нового блока. Если inode, затребовавший вновь выделенный блок, записан до того, как установлены оба флага DEPCOMPLETE и COMPLETE, флаг ATTACHED будет сброшен, пока для ука- указателя блока в inode делается откат на ноль, записывается inode и в указателе блока восстанавливается новый номер блока. Там, где они отличаются, специфические значения этих флагов в различных структурах зависимостей описываются в сле- следующих подразделах. Отслеживание зависимости битовой карты Обновления битовых карт отслеживаются структурой bmsafemap, показанной на рис. 8.18. Каждый буфер, содержащий блок группы цилиндров, будет иметь свою соб- собственную структуру bmsafemap. Как и с каждой структурой зависимости, первым эле- элементом в структуре bmsafemap является структура worklist. Каждый раз, когда из группы цилиндров выделяется inode, прямой или косвенный блок, для этого ресурса создается структура зависимости и связывается с соответствующим списком bmsafemap. Каждый вновь выделенный inode будет представлен структурой inodedep, связанной в список bmsafemap inodedep head. Каждый вновь выделенный блок, на который непосредственно ссылается inode, будет представлен структурой allocdirect, связанной в список bmsafemap allocdirect head. Каждый вновь выделенный блок, на который ссылается косвенный блок, будет представлен структурой allocindir, связан- связанной в список bmsafemap allocindir head. Из-за организации кода между временем, когда блок выделяется впервые, и временем, когда известно об его использовании, имеется небольшое окно. Во время этого периода времени он описывается структурой newblk, связанной в список bmsafemap new blk head. После того как ядро выбирает записать блок группы цилиндров, код мягких обновлений будет уведомлен, когда запись завершится. В это время код обходит списки inode, прямого блока, косвенного блока и нового блока, устанавливая в каждой структуре зависимости флаг DEPCOMPLETE и удаляя указанную структуру зависимости из се списка зависимости. Очистив все
394 Глава 8. Локальные файловые системы списки зависимости, структура bmsafemap может быть освобождена. Имеется множе- множество списков, поскольку списки определенных типов несколько быстрее и безопаснее в отношении типов. bmsafemap worklist cylgrp_bp allocindir head inodedep head new blk head allocdirect head Рис. 8.18. Зависимости обновления битовых карт Отслеживание зависимости inode Обновления inode отслеживаются структурой inodedep, показанной на рис. 8.19. Поля worklist и state предназначены, как было сказано, для структур зависимости вообще. Поля fileystern ptr и inode number идентифицируют указанный inode. Когда inode выде- выделяется вновь, его inodedep присоединяется к списку inodedep head структуры bmsafemap. Здесь deps list прикрепляет в цепочку дополнительные новые структуры inodedep, a dep bp указывает на блок группы цилиндров, который содержит соответствующую битовую карту. Другие поля inodedep объяснены в последующих подразделах. Перед детализацией оставшихся зависимостей, связанных с узлом, нам нужно обсудить шаги, вовлеченные в обновление inode на диске, как показано на рис. 8.20. 1. Ядро вызывает операцию vnode, VOP_UPDATE, которая запрашивает копирование резидентной на диске части inode (которая называется dinode) из структуры vnode в памяти в соответствующий дисковый буфер. Этот дисковый буфер хранит содержимое всего дискового блока, которого обычно хватает на включение 64 dinode. Некоторые зависимости удовлетворяются, лишь когда inode записывается на диск. Этим зависимостям нужно, чтобы структуры зависимостей отслеживали ход записи inode. Поэтому на первом шаге вызывается процедура мягкого обновления softdep_update_inodeblock(), чтобы переместить структуры allocdirect из списка incore update в список buffer update и переместить структуры freefile, freeblks, freefrag, diradd и mlcdir (описанные ниже) из списка inode wait в список buffer wait.
8.6. Мягкие обновления 395 inodedep worklist state deps list depbp hash list lesystem ptr inode number nlink delta saved inode ptr saved size pending ops head buffer wait head inode wait head buffer update head incore update head РИС. 8.19. Зависимости обновления inode vnode inode — dinode dinode dinode dinode ••• dinode буфер dinode Рис. 8.20. Шаги по обновлению inode 2. Ядро вызывает операцию vnode VOP_STRATEGY, которая подготавливает к запи- записи буфер, содержащий dinode, на который на рис. 8.20 указывает Ьр. Процедура мягкого обновления softdep_disk_io_initiation() идентифицирует зависимости inod- inodedep и вызывает initiate_write_inodeblock(), чтобы при необходимости сделать откаты.
396 Глава 8. Локальные файловые системы 3. Вывод в буфере, на который ссылается Ьр, завершается, и система ввода/вывода вызывает процедуру biodoneQ, чтобы уведомить все ждущие процессы, что запись завершилась. Затем процедура biodoneQ вызывает процедуру мягкого обновления softdep_disk_write_complete(), которая идентифицирует зависимости inodedep и вызывает handle_written_inodeblock(), чтобы восстановиться после откатов и очистить все зависимости в списках buffer wait и buffer update. Отслеживание зависимостей прямых блоков На рис. 8.21 показаны структуры зависимостей, вовлеченные в выделение прямых блоков. Вспомните, что ключевыми зависимостями является то, что как блок соответствующей карты, так и сам новый блок должны быть записаны на диск, прежде чем inode на диске укажет на вновь выделенный блок. Порядок, в котором эти две зависимости завершаются, неважен. На рисунке представлена структура allocdirect, которая отслеживает блоки, на ко- которые непосредственно ссылается inode. Каждый из трех недавно выделенных логических блоков A, 2 и 3) показан в разных состояниях. Для логического блока 1 зависимость блока битовой карты разрешена (на что указывает установленный флаг DEPCOMPLETE), но сам блок еще не был записан (на что указывает сброшенный флаг COMPLETE). Для логиче- логического блока 2 разрешены обе зависимости. Для логического блока 3 ни одна зависимость не разрешена, поэтому соответствующая структура allocdirect присоединена к списку Ьт- safemap allocdirect head (вспомните, что этот список просматривается для установки флагов DEPCOMPLETE после записи блоков битовых карт). Флаг COMPLETE для логиче- логических блоков 1 и 3 будет установлен, когда на диск будут записаны блоки их инициализиро- инициализированных данных. На рисунке также показано, что логический блок 1 существовал в то время, когда был вызван VOP_UPDATE, вот почему структура allocdirect находится в списке inod- inodedep buffer update. Логические блоки 2 и 3 были созданы после последнего вызова VOP_UPDATE, поэтому их структуры находятся в списке inodedep incore update. Для файлов, которые увеличиваются небольшими шагами, указатель прямого блока может сначала указывать на фрагмент, который впоследствии переводится в больший фрагмент и в конченом счете в полноразмерный блок. Когда фрагмент замещается более крупным фрагментом или полноразмерным блоком, он должен быть освобожден обратно для файловой системы. Однако он не может быть освобожден до тех пор, пока для нового фрагмента или блока не буцут записаны на диск их элементы битовой карты и содержание. Фрагмент, который должен быть освобожден, описывается структурой freefrag (не показана). Структура freefrag хранится в списке freefrag структуры allocdirect для блока, который за- заместит его, до тех пор, пока для нового блока не буцут записаны его содержание и элемент битовой карты. Затем структура freefrag перемещается в список inode wait структуры inod- inodedep, связанной с его структурой allocdirect, где она мигрирует в список buffer wait, когда вызывается VOP_UPDATE. Структура freefrag в конечном счете добавляется в tasklist после того, как буфер, содержащий блок inode, не будет записан на диск. Когда обслужи- обслуживается tasklist, фрагмент, перечисленный в структуре freefrag, возвращается в битовую карту свободных блоков.
8.6. Мягкие обновления 397 bmsafemap - cylgф_bp->b_dep lbnl_bp->b_dep inodedep worklist state (see below) deps list dep bp hash list filesystem ptr inode number nlink delta saved inode ptr saved size pending ops head buffer wait head inode wait head buffer update head incore update head worklist cylgrp bp allocindir head inodedep head new blk head allocdirect head Ibn3_bp->b_dep-\ allocdirect allocdirect \ —' worklist state (see below) deps list depbp logical blkno 1 new blkno old blkno new size old size freefrag next allocdirect my inodedep ATTACHED ) DEPCOMPLETE worklist state (see below) deps list depbp logical blkno 2 new blkno old blkno new size old size freefrag next allocdirect my inodedep allocdirect ATTACHED DEPCOMPLETE COMPLETE worklist state (see below) deps list depbp logical blkno 3 new blkno old blkno new size old size freefrag next allocdirect my inodedep ATTACHED ATTACHED РИС. 8.21. Зависимости выделения прямого блока Отслеживание зависимостей косвенного блока На рис. 8.22 показаны структуры зависимостей, вовлеченные в выделение косвенных бло- блоков, что включает те же зависимости, что и с прямыми блоками. На этом рисунке представ- представлены две новые структуры зависимостей. Каждый отдельный указатель блока в косвенном блоке отслеживается отдельной структурой allocindir. Структура indirdep управляет всеми зависимостями allocindir, связанными с косвенным блоком. На рисунке показан файл, который недавно выделил логические блоки 14 и 15 (третий и четвертый элементы по сме- смещениям 8 и 12 в первом косвенном блоке). Битовые карты выделения были записаны для логического блока 14 (как показывает его установленный флаг DEPCOMPLETE), но не для блока 15. Таким образом, структура bmsafemap отслеживает структуру allocindir для ло- логического блока 15. На диск было записано содержимое логического блока 15 (как показы- показывает его установленный флаг COMPLETE), но не блока 14. Когда блок будет записан, флаг COMPLETE будет установлен в структуре allocindir блока 14.
398 Глава 8. Локальные файловые системы bmsafemap cylgrp_bp->b_dep - worklist cylgrp_bp allocindir head inodedep head new blk head allocdirect head Ivl 1 indir_bp->b_dep indirdep lbn 14_bp->b_dep worklist state (see below) saved data ptr safe copy bp done head allocindir head ATTACHED ATTACHED DEPCOMPLETE Рис. 8.22. Зависимости выделения косвенного блока allocindir worklist state (see below) deps list dep bp offset 8 in indir blk new blkno old blkno freefrag next allocindir my indirdep allocindir worklist state (see below) deps list dep bp offset 12 in indir blk new blkno old blkno freefrag next allocindir my indirdep ATTACHED COMPLETE Список структур allocindir, отслеживаемых структурой indirdep, может быть длин- длинным (например, до 2048 элементов для 8-килобайтных косвенных блоков). Чтобы избе- избежать обхода длинных списков зависимостей структур в процедурах ввода/вывода, структура indirdep поддерживает вторую версию косвенного блока: saved data ptr всегда указывает на новейшую копию буфера, a safe copy ptr указывает на версию, которая включает лишь подмножество указателей, которые можно безопасно записать на диск (и NULL для других). Первая используется для всех операций файловой систе- системы, а последняя используется для записей на диск. Когда список allocindir head стано- становится пустым, saved data ptr и safe copy ptr указывают на идентичные блоки, и струк- структуру indirdep (и безопасную копию) можно освободить.
8.6. Мягкие обновления 399 dinode indirect block dinode_bp->t>_dep - inodedep indir_bp->b_dep - allocdirect worklist state (see below) deps list depbp hash list filesystem ptr inode number nlink delta saved inode ptr saved size pending ops head buffer wait head inode wait head bufferupdatehead incore update head worklist state (see below) deps list depbp logical blkno new blkno old blkno old size freefrag next allocdirect my inodedep data block ATTACHED cylgrp_bp->b_dep- ATTACHED DEPCOMPLETE data_bp->b_dep - indirdep worklist state (see below) saved data ptr safe copy bp done head allocindir head ATTACHED bmsafemap worklist cylgrp_bp allocindir head inodedep head new blk head allocdirect head allocindir worklist state (see below) deps list depbp offset in indir blk new blkno old blkno freefrag next allocindir my indirdep ATTACHED Рис. 8.23. Зависимости для файла, развертывающегося в косвенный блок Отслеживание зависимостей для новых косвенных блоков На рис. 8.24 показаны структуры, связанные с файлом, который недавно был расширен на один уровень косвенного блока. Конкретнее, это расширение включает структуры inodedep и indirdep для управления структурами зависимостей для inode и косвенного блока, структуру allocdirect для отслеживания зависимостей, связанных с выделением косвенного блока, и структуру allocindir для отслеживания зависимостей, связанных с вновь выделенным блоком, на который указывает косвенный блок. Эти структуры ис- используются, как было описано в предыдущих трех подразделах. Ни косвенный блок, ни блок данных, на который он ссылается, не имели установленных битовых карт, поэтому у них не был установлен флаг DEPCOMPLETE, и они отслеживаются струк- структурой bmsafemap. Был записан элемент битовой карты для inode, поэтому у структуры inodedep установлен флаг DEPCOMPLETE. Использование списка buffer update head структурой inodedep показывает, что внутренний inode был скопирован в свой буфер путем вызова VOP_UPDATE. Ни один из зависимых указателей (из inode на косвенный
400 Глава 8. Локальные файловые системы блок и из косвенного блока на блок данных) пока не может быть безопасно включен в дисковую запись, поскольку не установлены соответствующие флаги COMPLETE и DEPCOMPLETE. Лишь после записи битовых карт и содержания все эти флаги будут установлены и зависимость будет разрешена. dirpage_bp->b_dep inodedep (bar) worklist state (see below) deps list dep bp hash list lesystem ptr inode number nlink delta saved inode ptr saved size pending ops head buffer wait head inode wait head buffer update head incore update head ATTACHED DEPCOMPLETE COMPLETE parent dir pagedep worklist state (see below) hash list mount ptr inode number logical blkno dirrem head diradd head pending ops head флагов нет inodedep (foo) buffer wait head ATTACHED DEPCOMPLETE diradd (foo) worklist state (see below) next diradd dir offset new inode number previous dirrem my pagedep ATTACHED DEPCOMPLETE r diradd (bar) worklist state (see below) next diradd dir offset new inode number previous dirrem my pagedep ATTACHED DEPCOMPLETE COMPLETE Рис. 8.24. Зависимости, связанные с добавлением новых элементов каталога Отслеживание зависимости нового элемента каталога На рис. 8.24 показаны структуры зависимостей для каталога, у которого два новых эле- элемента, foo и bar. На данном рисунке представлены две новые структуры зависимостей. Каждый отдельный элемент каталога в блоке каталога отслеживается отдельной струк- структурой diradd. Структура pagedep управляет всеми зависимостями diradd, связанными с блоком каталога. Для каждого нового файла имеется структура inodedep и структура diradd. Для inode обоих файлов их битовые карты должны быть записаны на диске, на что указывают установленные флаги DEPCOMPLETE в их структурах inodedep. Inode для foo был обновлен VOP_UPDATE, но еще не был записан на диск, на что указывает неустановленный флаг COMPLETE в его структуре inodedep и нахождение его струк-
8.6. Мягкие обновления 401 туры diradd в списке buffer wait. Пока inode не будет записан на диск с увеличением числа его ссылок, элемент каталога не может появиться на диске. Если страница ката- каталога записана, код мягких обновлений сделает откат на создание нового элемента ката- каталога для foo, установив номер его inode в ноль. После завершения записи на диск откат снимается и нужный номер inode для foo восстанавливается. Inode для bar был записан на диск, на что указывает флаг COMPLETE, установ- установленный в структурах inodedep и diradd. Когда запись inode завершена, структура diradd для bar перемещается из списка inodedep buffer wait в список inodedep pending ops. Структура diradd также перемещается из спискаpagedep diradd в списокpagedeppend- списокpagedeppending ops. Поскольку inode был записан, можно свободно позволить записать элемент ка- каталога на диск. Элементы diradd остаются в списке inodedep и pagedep pending ops до тех пор, пока новый элемент каталога не будет записан на диск. Когда элемент записан, структура diradd освобождается. Одной из причин для поддержания списка pending ops является то, что, когда для файла осуществляется системный вызов fsync, ядро может убедиться, что как содержание файла, так и ссылки каталога записаны на диск. Ядро обеспечивает запись ссылок, проверяя, есть ли inodedep для inode, который явля- является целью fsync. Если оно находит inodedep, оно проверяет, есть ли у него какие- нибудь зависимости diradd в списках pending ops или buffer wait. Если оно находит какую-нибудь структуру diradd, оно следует по указателям до их соответствующих структур pagedep и сбрасывает на диск inode каталога, связанный с этим pagedep. Этот поиск рекурсивно применяется к каталогу inodedep. Отслеживание зависимостей нового каталога На рис. 8.25 показаны две дополнительные структуры зависимостей, вовлеченные в создание нового каталога. Для обычного файла элемент каталога может быть зафиксиро- зафиксирован, как только узел, на который вновь ссылаются, будет записан на диск с увеличенным числом ссылок. Когда создается новый каталог, имеются две новые зависимости: запись блока данных каталога, содержащих элементы . и . . (MKDIRJBODY), и запись родитель- родительского inode с увеличенным числом ссылок для . . (MKDIR_PARENT). Эти дополнитель- дополнительные зависимости отслеживаются двумя структурами mkdir, связанными с соответствующей структурой diradd. Модель мягких обновлений требует, чтобы любая данная зависимость соответствовала одному буферу в любой данный момент времени. Поэтому для отслежива- отслеживания двух различных буферов используются две структуры. Когда каждая из них завершает- завершается, связанный с ними флаг в структуре diradd сбрасывается. MKDIRJPARENT связывается со структурой inodedep для родительского каталога. Когда записывается inode этого катало- каталога, число ссылок на диске буцет обновлено. MKDIRJBODY связывается с буфером, ко- который содержит первоначальное содержание нового каталога. Когда этот буфер записыва- записывается, на диске будут элементы для .и ... Последняя структура mkdir для завершения ус- устанавливает в структуре diradd флаг DEPCOMOPLETE таким образом, чтобы структура diradd знала, что эти дополнительные зависимости были разрешены. После разрешения
402 Глава 8. Локальные файловые системы этих дополнительных зависимостей обработка каталога diradd продолжается точно так же, как для обычного файла. dirpage_bp->b_dep new dir inodedep world is t state (see below) deps list depbp hash list lesystem ptr inode number nlink delta saved inode ptr saved size pending ops head buffer wait head inode wait head buffer update head incore update head ATTACHED DEPCOMPLETE COMPLETE newdirpage_bp->b_dep mkdirlisthd- parent dir pagedep worklist state (see below) hash list mount ptr inode number logical blkno dirrem head diradd head pending ops head флагов нет parent dir inodedep buffer wait head ATTACHED DEPCOMPLETE COMPLETE mkdir worklist MKDIR_BODY my diradd next mkdir mkdir worklist MKDIR_PARENT my diradd next mkdir diradd worklist state (see below) next diradd dir offset new inode number previous dirrem my pagedep ATTACHED MKDIR_BODY MKDIR_PARENT Рис. 8.25. Зависимости, связанные с добавлением нового каталога Все структуры mkdir в системе связаны вместе в список. Этот список нужен, чтобы diradd могла найти свои ассоциированные структуры mkdir и освободить их, если она освобождена преждевременно (например, если за системным вызовом mkdir сразу по- последовал вызов rmdir для того же каталога). В этом случае при освобождении струк- структуры diradd нужно пройти список, чтобы найти ассоциированные структуры mkdir, которые ссылаются на нее. Удаление было бы быстрее, если бы структура diradd была просто увеличена на два указателя, которые ссылаются на ассоциированные структуры mkdir. Однако эти дополнительные указатели удвоили бы размер структуры diradd для ускорения нечастой операции.
8.6. Мягкие обновления 403 Отслеживание зависимости удаления элемента каталога На рис. 8.26 показаны структуры зависимостей, вовлеченные в удаление элемента каталога. На этом рисунке представлены одна новая структура зависимости, dirrem, и новое применение для структуры pagedep. Каждый отдельный удаляемый элемент каталога в блоке каталога отслеживается отдельной структурой dirrem. Кроме ранее описанного применения структуры pagedep, связанные с блоком каталога, управляют всеми структурами dirrem, ассоциированными с блоком. После записи блока каталога на диск в список tasklist рабочего демона добавляется запрос dirrem. Для удаления файлов рабочий демон уменьшит счетчик ссылок inode на единицу. Для удаления ката- каталогов рабочий демон уменьшит счетчик ссылок inode на два, уменьшит его размер до нуля и уменьшит счетчик ссылок родительского каталога на единицу. Если счетчик ссылок inode достигнет нуля, будет начата работа по восстановлению ресурсов, опи- описанная в разделе «Повторное использование inode файлов и каталогов». dirpage_bp->b_dep- pagedep worklist state (см. ниже) hash list mount ptr inode number logical blkno dirrem head diradd head pending ops head dirrem worklist state (см. ниже) next dirrem mount ptr old inode number parent inode num my pagedep флагов нет флагов нет РИС. 8.26. Зависимости, связанные с удалением элемента каталога Усечение файлов Когда файл укорачивается до нулевой длины без включенных мягких обновлений, ука- указатели блоков в его inode сохраняются во временном списке, указатели в inode обну- обнуляются, и inode синхронно записывается на диск. Когда запись inode завершается, список его ранее освобожденных блоков добавляется в битовую карту свободных бло- блоков. С мягкими обновлениями указатели блоков в сокращаемом inode копируются в структуру freeblks, указатели в inode обнуляются, и inode помечается как грязный. В список inode wait добавляется структура freeblks, и она перемещается в список buffer wait, когда вызывается VOP_UPDATE. Структура freeblks в конечном счете добавляет- добавляется к tasklist после того, как буфер, хранящий блок inode, был записан на диск. Когда
404 Глава 8. Локальные файловые системы обслуживается tasklist, блоки, перечисленные в структуре freeblks, возвращаются в бито- битовую карту свободных блоков. Повторное использование inode файлов и каталогов Когда счетчик ссылок на файл или каталог падает до нуля, его inode обнуляется, чтобы показать, что он больше не используется. При работе без мягких обновлений обнулен- обнуленный inode синхронно записывается на диск и помечается в битовой карте как свобод- свободный. С мягкими обновлениями информация об освобождаемом inode сохраняется в структуре freefile. Структур a freefile добавляется в список inode wait и перемещается в список buffer wait, когда вызывается VOP_UPDATE. Структура freefile в конечном счете добавляется в tasklist после того, как буфер, содержащий блок inode, был записан на диск. Когда обслуживается tasklist, inode, перечисленный в структуре freefile, возвращается в карту свободных inode. Отслеживание зависимостей переименования элемента каталога На рис. 8.27 показаны структуры, вовлеченные в переименование файла. Зависи- Зависимости следуют в той же последовательности шагов, как при добавлении нового эле- элемента файла, с двумя изменениями. Во-первых, когда для элемента необходим откат из-за того, что inode еще не был записан на диск, в элементе должен быть установлен прежний номер inode, а не ноль. Прежний номер inode хранится в структуре dirrem. В структуре diradd устанавливается флаг DIRCHG таким образом, что код отката знает, что нужно использовать старый номер inode, хранящийся в структуре dirrem. Вторым отличием является то, что после записи измененного элемента на диск струк- структура dirrem добавляется в список tasklist рабочего демона, чтобы счетчик ссылок старого inode был уменьшен, как описано в разделе «Отслеживание зависимостей уда- удаления элемента каталога». Отслеживание зависимостей удаления файла В данном разделе будет разобран короткий пример, описывающий зависимости, которые отслеживают удаление файла. Должны быть сохранены три системати- систематизирующих ограничения. 1. Имя файла в копии каталога на диске должно быть удалено. 2. Inode, описывающий файл, должен быть освобожден путем обнуления его dinode на диске. 3. Блоки, на которые раньше ссылался inode для файла, должны быть возвращены в битовую карту свободного пространства, a inode следует возвратить в битовую карту свободных inode.
8.6. Мягкие обновления 405 dirpage_bp->b_dep new le inodedep worklist state (см. ниже) deps list dep bp hash list lesystem ptr inode number nlink delta saved inode ptr saved size pendin^ ops head buffer wait head inode wait head buffer update head incore update head r 1 { parent dir pagedep dirrem worklist state (см. ниже) hash list mount ptr inode number logical blkno dirrem head diradd head pending ops head ^ флагов нет worklist state (см. ниже) next dirrem mount ptr old inode number parent inode num my pagedep no - ags diradd Г —> ^ ATTACHED DEPCOMPLETE COMPLETE РИС. 8.27. Зависимости, связанные с переименованием эле меш worklist state (см. ниже) next diradd dir offset new inode number previous dirrem my pagedep ATTACHED DEPCOMPLETE COMPLETE DIRCHG га каталога 2. Эти ограничения поддерживаются мягкими обновлениями следующим образом. Блок каталога, содержащий удаляемое имя, читается в буфер ядра. Элемент удаляет- удаляется путем изменения предшествующего элемента, чтобы он указывал на следующий за удаляемым элемент (см. раздел 8.3). Перед освобождением буфера необходимо сконструировать набор зависимостей, как показано на рис. 8.26. Если это удаление является первой зависимостью для блока каталога, ему нужно выделить структуру pagedep, которая связана со списком зависимостей для буфера. Затем выделяется структура dirrem, в которой записывается номер inode удаляемого элемента. Струк- Структура dirrem вставляется в список dirrem структуры pagedep для блока каталога. Затем буфер помечается грязным, разблокируется и освобождается. В течение следующих 30 секунд ядро решает записать грязный буфер каталога. Когда запись завершается, pagedep, связанная с буфером, передается для обработ- обработки мягкому обновлению. Одним из шагов обработки является оперирование с каж- каждым из элементов dirrem. Каждый элемент dirrem вызывает уменьшение на единицу счетчика ссылок inode, на который раньше ссылался каталог. Если счетчик ссылок
406 Глава 8. Локальные файловые системы достигает нуля (что означает, что было удалено последнее имя для файла), inode необходимо освободить. Перед обнулением содержимого inode на диске его спи- список выделенных блоков необходимо сохранить в структуре freeblks, а информа- информацию, необходимую для освобождения inode, необходимо сохранить в структуре freefile. Блок файловой системы, содержащий освобождаемый dinode, читается в буфер ядра, как показано на рис. 8.20. Часть буфера, содержащая dinode, обнуля- обнуляется. Если освобождение является первой зависимостью для inode, ему необходи- необходимо выделить структуру inodedep, которая вставлена в список зависимостей для буфера. Структуры freebIks и freefile вставлены в список buffer wait структуры inodedep. Затем буфер помечается как грязный, разблокируется и освобождается. Структура dirrem освобождается, как и структура pagedep, если она не отслеживает больше никаких зависимостей. 3. В течение следующих 30 секунд ядро решает записать буфер, содержащий обну- обнуленный dinode. Когда запись завершается, inodedep, связанная с буфером, переда- передается для обработки мягким обновлениям. Одним из шагов обработки является оперирование каждым из элементов buffer wait. Обработка эпеиета freeblks вызы- вызывает пометку всех перечисленных блоков в соответствующих битовых картах групп цилиндров свободными. Обработка элемента freefile вызывает пометку уда- удаленного inode в соответствующей битовой карте группы цилиндра свободным. Структуры freeblks к freefile освобождаются, как и структура inodedep, если она больше не отслеживает никаких зависимостей. Теперь файл полностью удален и перестает отслеживаться мягким обновлением. Требования/sy/ic для мягких обновлений Системный вызов fsync требует, чтобы указанный файл был записан в стабильное хра- хранилище и чтобы системный вызов не возвращался до тех пор, пока все необходимые записи не будут завершены. Задача завершения fsync требует большего, чем просто запись всех грязных блоков данных файла на диск. Она нуждается также в том, чтобы все незаписанные элементы каталога, которые ссылаются на файл, а также все незапи- незаписанные каталоги между файлом и корнем файловой системы тоже были записаны. Простое помещение блоков данных на диск может быть сложной задачей. Во-первых, система должна проверить, была ли записана битовая карта для inode, при необходимо- необходимости находя ее и записывая. Затем она должна проверить, найти и записать битовые карты для всех новых блоков в файле. Далее все незаписанные блоки данных должны попасть на диск. За блоками данных записываются все косвенные блоки первого уров- уровня, в которых есть вновь выделенные блоки, затем двойные косвенные блоки, затем косвенные блоки третьего уровня. Наконец, может быть записан inode, который гаран- гарантирует, что содержание файла находится в стабильном хранилище. Для обеспечения того, что все имена для файла также находятся в стабильном хранилище, необходимы структуры данных, которые могут определить, имеются ли какие-нибудь незафиксиро-
8.6. Мягкие обновления 407 ванные имена, и если так, в каких каталогах они находятся. Для каждого каталога, содержащего незафиксированное имя, код мягкого обновления должен пройти через тот же набор операций сбрасывания на диск, который только что был сделан для самого файла. Для добавления расширенных данных атрибутов inode необходимо, чтобы код мягких обновлений был расширен таким образом, чтобы он мог обеспечить целост- целостность этих новых блоков данных. Как и с блоками данных файла, мягкое обновление гарантирует, что расширенные блоки данных и битовые карты, указывающие, что они используются, записываются на диск до того, как они освобождаются inode. Мягкие обновления обеспечивают также, что любые обновленные расширенные атрибуты данных фиксируются на диске, как часть fsync файла. Хотя системный вызов fsync в конечном счете должен осуществляться синхронно, это требование не означает, что каждая из операций сбрасывания на диск должна выполняться синхронно. Вместо этого весь набор битовых карт или блоков данных помещается в очередь диска, а затем код мягких обновлений ждет завершения всех этих записей. Такой подход более эффективен, поскольку он позволяет дисковой под- подсистеме сортировать все запросы записи в наиболее эффективном порядке. Часть кода мягкого обновления для fsync по-прежнему генерирует большую часть остающихся синхронных записей в файловой системе. Другой проблемой, связанной с fsync, является демонтирование (unmounting) фай- файловых систем. Выполнение unmount требует нахождения и сбрасывания на диск всех грязных файлов, которые связаны с файловой системой. Сбрасывание на диск файлов может вести к созданию фоновой активности, такой, как удаление файлов, счетчики ссылок которых достигли нуля в результате записи их обнуленных элементов катало- каталогов. Таким образом, система должна иметь возможность находить запросы фоновой деятельности и обрабатывать их. Даже в находящейся в покое файловой системе может потребоваться несколько итераций дисковых сбросов файла, за которыми следу- следует фоновая деятельность. FreeBSD допускает принудительное демонтирование файло- файловой системы, которое может иметь место при активном использовании файловой сис- системы. Возможность аккуратной приостановки операций в активной файловой системе описана в разделе 8.7. Требования удаления файла для мягких обновлений Для правильной работы элемент каталога . . не должен удаляться до тех пор, пока каталог не будет удален на постоянной основе. Корректировка такого упорядочивания зависимости в коде мягких обновлений ввела задержку до двух минут между време- временем, когда каталог удален (unlinked), и временем, когда он фактически освобожден (когда удаляется элемент . .). До тех пор, пока элемент каталога . . не удален фактиче- фактически, счетчик ссылок его родителя не будет уменьшен. Поэтому, когда пользователь уда- удаляет один или более каталогов, счетчик ссылок их прежнего родителя в течение
408 Глава 8. Локальные файловые системы нескольких минут по-прежнему отражает их наличие. Эта задержка уменьшения счетчика ссылок вызывает не только некоторые вопросы пользователей, но и наруше- нарушения работы некоторых приложений. Например, системный вызов rmdir не удалит ката- каталог, счетчик ссылок которого больше двух. Это ограничение означает, что каталог, из которого недавно удалили каталоги, не может быть удален до тех пор, пока его преж- прежние каталоги не будут удалены полностью. Чтобы разрешить эти проблемы счетчиков ссылок, реализация мягких обновлений наращивает поле inode nlink новым полем, которое называется effnlink. Поле nlink по- прежнему хранится как часть метаданных на диске и отражает истинное число ссылок inode. Поле effnlink поддерживается лишь в памяти ядра и отражает конечное значение, которого поле nlink достигнет, когда все его отсроченные операции будут выполнены. Все взаимодействия с пользовательскими приложениями сообщают о значении поля effnlink, что ведет к иллюзии того, что все случилось сразу. Когда файл удален в файловой системе, работающей с мягкими обновлениями, удаление выглядит быстрым, но процесс удаления файла и возвращения его блоков в список свободных может занять несколько минут. До UFS2 пространство, удерживае- удерживаемое файлом, не отображалось в статистике файловой системы до тех пор, пока удале- удаление файла не было завершено. Поэтому приложения, очищающие дисковое простран- пространство, такие, как программа удаления старых новостей, часто расточительно завышали свои цели. Они работали путем удаления файлов с последующей проверкой, доста- достаточно ли оказалось свободного пространства. Из-за задержки времени при записи сво- свободного пространства они удаляли слишком много файлов. Чтобы разрешить пробле- проблемы подобного рода, код мягких обновлений поддерживает теперь счетчик, который от- отслеживает объем занимаемого файлами пространства, который находится в процессе удаления. Этот счетчик задержанного пространства добавляется к фактическому раз- размеру свободного пространства, о котором сообщает ядро (и соответственно утилиты наподобие df). Результатом этого изменения является то, что свободное пространство появляется сразу после того, как возвращается системный вызов unlink или завершает- завершается утилита rm. Второе связанное с этим изменение в мягких обновлениях должно иметь дело с из- избежанием ложных ошибок недостатка пространства. При работе с мягкими обновле- обновлениями в почти заполненной файловой системе с большой «текучкой» (например, при установке целого набора двоичных файлов в корневом разделе) файловая система может вернуть ошибку заполнения файловой системы, даже когда она сообщает о том, что имеется большое количество свободного пространства. Сообщение о заполнении файловой системы возникает, потому что мягкие обновления не справились с освобож- освобождением пространства от старых двоичных файлов вовремя, чтобы быть доступными для новых двоичных файлов. Первоначальной попыткой исправить эту проблему было просто заставить ожи- ожидать появления свободного пространства тот процесс, который хотел выделить это пространство. Проблема с этим подходом в том, что часто приходится ждать до минуты.
8.6. Мягкие обновления 409 Помимо того что приложение выглядело невыносимо медленным, оно обычно удержи- удерживало заблокированным vnode, что могло вызвать блокировку других приложений в ожидании, пока он станет доступным (что часто называлось состязанием блокировок (lock race) корня файловой системы). Хотя условие снималось через минуту или две, пользователи часто предполагали, что их система зависла, и перегружались. Чтобы исправить эту проблему, решением, разработанным для UFS2, является кооптирование1 процесса, который в противном случае был бы заблокирован, и по- помещение его в работу, помогая мягким обновлениям обрабатывать файлы, которые должны быть освобождены. Чем больше процессов пытаются выделить пространство, тем больше помощь, которая доступна мягким обновлениям, и тем быстрее начнут появляться свободные блоки. Обычно в течение одной секунды появляется достаточно свободного пространства, чтобы процессы могли вернуться к первоначальной задаче и завершиться. Следствием этого изменения является то, что мягкие обновления могут теперь использоваться на небольших, почти заполненных файловых системах с боль- большой «текучкой». Хотя общим случаем для освобождения является удаление всех данных файла, системный вызов truncate может дать приложениям возможность удалять лишь часть файла. Эта семантика создает несколько более сложные зависимости обновления, включая необходимость иметь зависимости освобождения для косвенных блоков и необ- необходимость учитывать частично удаленные блоки данных. Поскольку это так необычно, реализация мягких обновлений не оптимизирует этот случай; вместо этого используется традиционный подход синхронной записи. Одной из проблем с мягкими обновлениями является объем памяти, затрачиваемой структурами зависимостей. В повседневной работе мы обнаружили, что нагрузка допол- дополнительной динамической памяти, наложенная на область выделения памяти ядра, почти равна объему памяти, используемому vnode плюс inode. Для каждых 1000 vnode в систе- системе дополнительная пиковая нагрузка памяти из-за мягких обновлений составляет около 300 Кбайт. Одно исключение из этого правила возникает, когда удаляются большие дере- деревья каталогов. Здесь код файловой системы может зайти произвольно далеко вперед в бу- будущее состояние на диске, заставляя объем памяти, выделенный структурам зависимо- зависимостей, неограниченно расти. Код мягкого обновления был изменен, чтобы контролировать нагрузку памяти в данном случае и не позволить ей расти выше пределов допустимой верхней границы. Когда достигнут предел, новые структуры зависимости могут созда- создаваться лишь с частотой, с которой удаляются старые. Результатом этого ограничения является уменьшение скорости удаления до скорости, с которой могут производиться обновления диска. Хотя это ограничение уменьшает скорость, с которой мягкое обновле- обновление может обычно удалять файлы, она по-прежнему значительно больше, чем у традици- традиционной файловой системы с синхронными записями. В устойчивом состоянии алгоритму 1 Кооптация (от лат. cooptatio - дополнительное собрание) - введение новых членов в выборный коллегиальный орган по его собственному решению без проведения дополнительных выборов. — Примеч. науч. ред.
410 Глава 8. Локальные файловые системы удаления мягкого обновления требуется примерно одна запись на диск для каждых 10 удаляемых файлов, тогда как традиционной файловой системе требуется по крайней мере по две записи для каждого удаляемого файла. Требования мягкого обновления для fsck Как в случае с двойным отслеживанием истинного и эффективного подсчета ссылок, изменения, необходимые для fsck, становятся очевидными в ходе эксплуатационной практики. В реализации файловой системы без мягких обновлений удаление файла про- происходит за несколько миллисекунд. Поэтому между удалением элемента каталога и осво- освобождением inode проходит короткий промежуток времени. Если система терпит аварию в ходе операции удаления объемного дерева, обычно нет inode, на которые нет ссылок из элементов каталога, хотя в редких случаях могут быть один или два. В отличие от этого в системе, работающей с мягкими обновлениями, между удалением элемента каталога и освобождением inode может пройти несколько секунд. Если система потерпит аварию в ходе операции удаления объемного дерева, без ссылок из элементов каталога обычно остаются от десятков до сотен inode. Изначально fsck помещал все inode без ссылок в ка- каталог lost+found. Это действие приемлемо, если файловая система была повреждена из отказа диска, который приводит к потере одного или более каталогов. Однако это приво- приводит к неправильному действию по заполнению каталога lost+found, полного частично удаленных файлов, при работе с мягкими обновлениями. Поэтому программа fsck была изменена для проверки того, что файловая система работает с мягкими обновлениями. Кроме того, она была изменена, с тем чтобы очищать, а не сохранять inode без ссылок (если только не было обнаружено, что в файловой системе возникло неожиданное повре- повреждение, в коем случае файлы сохраняются в lost+found). Второстепенным преимуществом мягких обновлений является то, что fsck может доверять информации о распределениях в битовых картах. Таким образом, ему нужно проверить лишь подмножество узлов в файловой системе, которые, как показывают битовые карты, используются. Хотя некоторые из inode, помеченных как «исполь- «использующиеся», могут быть свободными, ни один из помеченных «свободными» никогда не будет использоваться. Производительность мягких обновлений Мы даем лишь поверхностный взгляд на производительность мягких обновлений. За подробным анализом, включающим сравнения с другими методиками, обратитесь к Ganger, McKusick et al. [2000]. Мы поместили производительность мягких обновлений в контекст сравнения его с системой, работающей без включенных мягких обновлений (которая ниже называется «обычной»), которая использует для упорядочивания обновления синхронные записи, и с файловой системой, смонтированной с опцией OASYNC (которая ниже называется «асинхронной»), игнорирующей все зависимости обновления. В асинхронном режиме все
8.6. Мягкие обновления 411 обновления метаданных преобразуются в отложенные записи (см. раздел 6.6). Таким обра- образом, данные O_ASYNC предоставляют верхнюю границу производительности схемы упорядочивания обновлений. Как ожидалось, мы обнаружили, что мягкие обновления устраняют почти все синхронные записи и обычно позволяют файловой системе достичь производительности в пределах 5 процентов верхней границы. По сравнению с исполь- использованием синхронных записей производительность создания и удаления файла возросла в 2 и 20 раз соответственно. Вообще файловая система, работающая с мягкими обновле- обновлениями, имеет тенденцию запрашивать на 40 процентов меньше дисковых записей и выпол- выполнять задачи на 25 процентов быстрее, чем при работе без мягких обновлений. Табл. 8.3. Сравнение времени прогона тестов файловых систем Конфигурация Дисковые записи . м Г Время прогона файловой системы Синхронные Асинхронные Обычная I 459 147 487 031 27ч. 15мин. Асинхронная 0 1109711 19ч. 43мин. Мягкие обновления 6 1113 686 19ч. 50мин. Чтобы почувствовать, какова производительность системы при обычной работе, мы представляем измерения из трех различных системных задач. Первой задачей был наш «тест пытки (torture) файловой системы». Этот тест состоял из 1000 запусков кон- контрольной задачи Эндрю (Andrew), 1000 копирований и удалений /etc со случайно выбран- выбранными паузами от 0 до 60 секунд между каждым копированием и удалением и 500 исполне- исполнений приложения find из / со случайно выбранными паузами в 100 секунд между каждым запуском. Прогон «теста пытки» приведен в табл. 8.3. Общим результатом является то, что асинхронные и мягкие обновления требуют на 42 процента меньше записей (с почти отсутствующими синхронными записями) и на 28 процентов меньше времени работы. Этот результат особенно впечатляет, если принять во внимание, что поиски и паузы не требуют зависимостей обновления и контрольная задача Эндрю сильно зависит от процессора. Второй тест состоит из построения и инсталляции системы FreeBSD. Эта задача является практическим примером среды разработки программ. Результаты показаны в табл. 8.4. Общим результатом является то, что мягкие обновления требуют на 75 про- процентов меньше записей и имеют на 21 процент меньшее время прогона. Хотя мягкие обновления делают на 30 процентов больше записей по сравнению с асинхронными, они оба имеют одно и то же время прогона. Табл. 8.4. Сравнение времени прогона для построения FreeBSD Конфигурация Дисковые записи Время прогона файловой системы ~~Z " ^ Синхронные Асинхронные
412 Глава 8. Локальные файловые системы Табл. 8.4. Сравнение времени прогона для построения FreeBSD Обычная 162 410 39 924 2ч. 12мин. Асинхронная 0 38 262 1ч. 44мин. Мягкие обновления 1124 48 850 1ч. 44мин. Табл. 8.5. Сравнение времени прогона для почтового сервера Дисковые записи Конфигурация файловой системы - Синхронные Асинхронные Обычная 1877 794 1613 465 Мягкие обновления 118 102 946 519 Третий тест сравнивает производительность основного почтового сервера Berkley Software Design, Inc., запущенного с мягкими обновлениями и без них. Администратор явно не хотел запускать его в асинхронном режиме, поскольку это производственная машина и люди не ожидают потерь своей почты. В отличие от предыдущих тестов, в которые был вовлечен единственный диск, почтовый буфер на этой системе распре- распределен между тремя дисками. Статистика была собрана путем усреднения результатов операций за 30 рабочих дней в каждом режиме. Результаты за 24-часовой период пока- показаны в табл. 8.5. Обычная файловая система в среднем имела около 40 записей в секун- секунду с отношением синхронных записей к асинхронным 1:1. С мягкими обновлениями частота записей снизилась до 12 в секунду, а отношение синхронных записей к асинхронным снизилось до 1:8. Для этого практического приложения мягкие обнов- обновления требуют на 70 процентов меньше записей, что утраивает способность машины к обработке почты. 8.7. Моментальные снимки файловой системы Моментальный снимок (snapshot) файловой системы является зафиксированным обра- образом файловой системы в данный момент времени. Моментальные снимки поддержи- поддерживают несколько важных особенностей: возможность обеспечивать резервные копии файловой системы по нескольку раз в течение дня, возможность делать надежные дампы действующей файловой системы и (наиболее важная для мягких обновлений) возможность запускать программу проверки файловой системы в действующей систе- системе, чтобы вернуть утерянные блоки и inode. Создание моментального снимка файловой системы Проверено, что реализация моментальных снимков является простой. Снятие момен- моментального снимка влечет за собой следующие шаги.
8.7. Моментальные снимки файловой системы 413 1. Создается файл моментального снимка для отслеживания последующих изменений файловой системы; файл моментального снимка показан на рис. 8.28. Этот файл мо- моментального снимка инициализируется размером раздела файловой системы, а его указатели блоков файла помечаются как нулевые, что означает «не скопировано». Выделяются несколько стратегических блоков, таких, как для хранения копий суперблока и карт групп цилиндров. 2. Делается предварительный проход через каждую группу цилиндров, чтобы ско- скопировать его в предварительно выделенный дублирующий блок. Кроме того, осу- осуществляется поиск в битовой карте блока в каждой группе цилиндров, чтобы опре- определить, какие блоки свободны. Для каждого найденного свободного блока соответ- соответствующий участок в файле моментального снимка помечается отличным номером блока A), чтобы показать, что блок «не используется». Нет необходимости копиро- копировать эти неиспользующиеся блоки, если они впоследствии выделяются и записы- записываются. Заголовок inode Не скопировано Не скопировано Не используется Не используется Не используется Одинарный Двойной Тройной Не скопировано Не скопировано Не используется Не используется Не скопировано Не скопировано ... Рис. 8.28. Структура файла моментального снимка 3. Файловая система помечается как «нуждающаяся в приостановке». В этом состоя- состоянии работа процессов, желающих сделать системные вызовы, которые изменят файловую систему, блокируется, тогда как процессам, находящимся уже в процес- процессе таких системных вызовов, разрешается их завершить. Эти действия обеспечи- обеспечиваются путем введения шлюза поверх каждого системного вызова, который может записывать в файловую систему. Набор таких системных вызовов включает write, open (при создании или усечении), /Jwpen (при создании или усечении), mknod, mkfifo, link, symlink, unlink, chflags, fchflags, chmod, Ichmod, fchmod, chown, Ichown, fchown, utimes, lutimes, futimes, truncate, /truncate, rename, mkdir, rmdir, fsync, sync,
414 Глава 8. Локальные файловые системы unmount, undelete, quotactl, revoke и extattrctl Кроме того, шлюзы должны быть добавлены Kpageout, ktrace, созданию сокета локального домена и созданию дампа ядра. Шлюз отслеживает деятельность внутри системного вызова для каждой смонтированной файловой системы. У шлюза два назначения. Первым является приостановка процесса, который хочет войти в шлюзовой системный вызов в периоды, когда файловая система, которую хочет модифицировать процесс, при- приостановлена. Вторым является отслеживание числа процессов, которые работают внутри шлюзового системного вызова для каждой смонтированной файловой сис- системы. Когда процесс входит в шлюзовой системный вызов, счетчик в структуре монтирования для файловой системы, которую он хочет модифицировать, уве- увеличивается. Когда процесс завершает шлюзовой системный вызов, счетчик уменьшается. 4. Состояние файловой системы меняется с «нуждающейся в приостановке» на «пол- «полностью приостановленную». Это изменение состояния осуществляется путем разрешения завершиться всем системным вызовам, записывающим в настоящее время в приостанавливаемую файловую систему. Переход в «полностью приоста- приостановленную» завершается, когда счетчик процессов внутри шлюзовых системных вызовов достигает нуля. 5. Файловая система синхронизируется с диском, как если бы ее собирались демон- демонтировать. 6. Любые группы цилиндров, которые были модифицированы после того, как они были скопированы на шаге два, копируются повторно в свои предварительно выде- выделенные резервные блоки. Кроме того, битовая карта в каждой повторно скопиро- скопированной группе цилиндров сканируется повторно, чтобы определить, какие блоки были изменены. Вновь выделенные блоки помечаются как «нескопированные», а вновь освобожденные блоки помечаются как «неиспользуемые». Подробности того, как идентифицируются эти модифицированные группы цилиндров, следуют далее. Количество пространства, первоначально требующееся моментальному снимку, невелико, обычно менее одной десятой процента. Использование про- пространства файла моментального снимка приведено в подразделе производительно- производительности моментального снимка. 7. Когда файл моментального снимка готов, деятельность файловой системы возоб- возобновляется. Все заблокированные шлюзом процессы пробуждаются, и им разреша- разрешается продолжить свои системные вызовы. 8. Блоки, которые были затребованы любыми моментальными снимками, которые существовали на момент съемки текущего моментального снимка, удаляются из нового моментального снимка по описанным ниже причинам.
8.7. Моментальные снимки файловой системы 415 В ходе шагов с 3-го по 6-й вся деятельность файловой системы по записи приоста- приостанавливается. Шаги 3-го и 4-й завершаются самое большее за несколько миллисекунд. Время для шага 5 является функцией числа грязных страниц в ядре. Она связана количеством памяти, которая выделена для хранения страниц файлов. Она обычно меньше, чем вторая, и не зависит от размера файловой системы. Обычно на шаге 6 необ- необходимо скопировать повторно лишь несколько групп цилиндров, поэтому он также завершается менее чем за секунду. Разделение копий битовых карт между шагами 2-м и 6-м является способом, по- посредством которого мы избегаем того, чтобы время приостановки было функцией раз- размера файловой системы. Давая возможность делать первичную копию в то время, пока файловая система по-прежнему активна, а затем повторно копируя лишь несколько нужных групп цилиндров после ее приостановки, мы сохраняем время приостановки небольшим и обычно не зависящим от размера файловой системы. Подробности двухпроходного алгоритма следующие. До начала копирования и сканирования всех групп цилиндров код моментального снимка выделяет битовую карту «продвижения», размер которой равен числу групп цилиндров в файловой системе. Назначением битовой карты «продвижения» является отслеживание того, какие группы цилиндров были просканированы. Вначале все биты в карте «продвижения» сброшены. Первый проход завершается на шаге 2 до приостановки файловой системы. При первом прходе сканируются все группы цилиндров. Когда группа цилиндров прочитывается, ее соответствующий бит в битовой карте «продвижения» устанавлива- устанавливается. Затем группа цилиндров копируется, и ее карта блока просматривается с целью обновления файла моментального снимка, как описано на шаге 2. Поскольку файловая система по-прежнему активна, блоки файловой системы могут выделяться и освобож- освобождаться, пока сканируются группы цилиндров. Каждый раз, когда группа цилиндров обновляется из-за выделения или освобождения блока, ее соответствующий бит в бито- битовой карте «продвижения» сбрасывается. После завершения этого первого прохода по группам цилиндров файловая система «приостанавливается». Шаг 6 становится теперь вторым проходом алгоритма. При втором проходе нужно лишь идентифицировать и обновить моментальный снимок для любых групп цилиндров, которые были модифицированы после того, как были обработаны при первом проходе. Измененные группы цилиндров идентифицируются путем просмотра битовой карты «продвижения» и повторного сканирования групп цилиндров, биты которых равны нулю. Хотя в худшем случае пришлось бы повторно обработать каждую битовую карту, на практике необходимо повторно копировать и проверить лишь несколько бито- битовых карт. Хранение моментальных снимков файловой системы Каждый раз, когда изменяется существующий блок в файловой системе, файловая сис- система проверяет, использовался ли этот блок во время снятия моментального снимка
416 Глава 8. Локальные файловые системы (т.е. он не помечен как «неиспользующийся»). Если это так и если он не был еще ско- скопирован (т.е. он по-прежнему помечен как «нескопированный»), выделяется новый блок из числа «неиспользующихся» блоков и помещается в файл моментального снимка, чтобы заменить «нескопированный» элемент. Предыдущее содержимое блока копиру- копируется во вновь выделенный блок файла моментального снимка, и тогда разрешается продолжить изменение оригинала. Каждый раз при удалении файла код моментально- моментального снимка проверяет каждый из освобождаемых блоков и затребует те, которые использовались во время моментального снимка. Те блоки, которые помечены как «неиспользующиеся», возвращаются в свободный список. Когда прочитывается файл моментального снимка, чтение блоков, помеченных как «нескопированные», возвращает содержимое соответствующих блоков в файловой системе. Чтение блоков, которые были скопированы, возвращает содержимое скопиро- скопированного блока (например, содержимое, которое было сохранено в этом месте в файло- файловой системе во время моментального снимка). Записи в файлы моментальных снимков не допускаются. Когда файл моментального снимка больше не нужен, его можно уда- удалить тем же способом, как любой другой файл; его блоки просто возвращаются в сво- свободный список, а его inode обнуляется и возвращается в список свободных inode. Моментальные снимки могут сохраняться между перезагрузками. Когда создается файл моментального снимка, номер inode файла моментального снимка записывается в суперблок. Когда файловая система монтируется, проходится список моментальных снимков, и все перечисленные моментальные снимки активируются. Единственным ограничением на моментальные снимки, которые могут существовать в файловой сис- системе, является размер массива в суперблоке, в котором хранится список моментальных снимков. В настоящее время этот массив может содержать вплоть до 20 моментальных снимков. Одновременно может существовать несколько файлов моментальных снимков. Как было только что описано, более ранние моментальные снимки появлялись бы в более поздних. Если предшествующий моментальный снимок удаляется, после- последующий затребует его блоки, не давая возвратить их в свободный список. Эта семан- семантика означает, что было бы невозможно освободить пространство в файловой системе, кроме как удалив новейший моментальный снимок. Чтобы избежать этой проблемы, код моментального снимка проходит и вычеркивает все предыдущие моментальные снимки, изменяя его представление о них так, как будто это файлы с нулевой длиной. С помощью этой методики освобождение предыдущих моментальных снимков осво- освобождает пространство, удерживаемое этим моментальным снимком. Когда блок перезаписывается, всем моментальным снимкам дается возможность скопировать блок. Копия блока делается для каждого моментального снимка, в ко- котором блок находится. Перезаписи обычно происходят лишь для блоков inode и ката- каталогов. Данные файлов обычно не перезаписываются. Таким образом, медленное и свя- связанное с интенсивным вводом/выводом копирование блоков нечасто.
8.7. Моментальные снимки файловой системы 417 Удаленные блоки управляются иначе. Проверяется список моментальных сним- снимков. Когда обнаруживается моментальный снимок, в котором блок активен («не ско- скопирован»), удаленный блок запрашивается этим моментальным снимком. Прохожде- Прохождение списка моментальных снимков затем завершается. Другие моментальные снимки, для которых блок активен, оставляются для этого блока с элементом «не скопирован». В результате когда они получают доступ к этому месту, они по-прежнему будут ссы- ссылаться на удаленный блок. Поскольку моментальные снимки не могут быть изменены, блок не изменится. Поскольку блок запрошен моментальным снимком, он не будет вы- выделен для постороннего использования. Если моментальный блок, запросивший уда- удаленный блок, удаляется, оставшимся моментальным снимкам будет предоставлена возможность запросить этот блок. Лишь когда ни один из оставшихся моментальных снимков не хочет затребовать этот блок (т.е. во всех них он помечен как «неисполь- зующийся»), он будет возвращен в свободный список. Моментальные снимки больших файловых систем Создание и использование моментального снимка требует произвольного доступа к файлу моментального снимка. Создание моментального снимка требует обследова- обследования и копирования всех отображений групп цилиндров. Будучи в работе, каждая операция записи в файловую систему должна проверить, нужно ли копировать записы- записываемый блок. Информация о том, нужно ли копировать блоки, содержится в метадан- метаданных файла моментального снимка (его косвенных блоках). В идеале эти метаданные были бы резидентными в памяти ядра на протяжении времени жизни моментального снимка. В FreeBSD вся физическая память на машине может использоваться для кеширования страниц данных файлов, если память не нужна для других целей. К сожа- сожалению, страницы памяти, связанные с дисками, могут кешироваться лишь в страницах, отображенных в физическую память ядра. На такие нужды выделяется лишь около 10 мегабайт памяти ядра. Если мы позволим использовать вплоть до половины этого пространства для любого отдельного моментального снимка, наибольшим моменталь- моментальным снимком, метаданные которого мы можем хранить в памяти, будет 11 гбайт. Без помощи такой крохотный кеш был бы безнадежен в попытке поддержать многотера- байтный моментальный снимок. В попытке поддержать многотерабайтные моментальные снимки с помощью кро- крошечного доступного кеша метаданных необходимо рассмотреть паттерны доступа в типичных файловых системах. Моментальный снимок просматривается лишь для файлов, которые записываются. Файловая система организована вокруг групп цилиндров, которые отображают небольшие непрерывные области диска (см. раздел 8.9). В пределах каталога файловая система пытается выделить все inode и файлы в той же самой группе цилиндров. При перемещении между каталогами обычно проверяются различные группы цилиндров. Таким образом, возникает крайне случайное поведение от перемещения между группами цилиндров. Когда деятельность по записи файла ус- устанавливается на группе цилиндров, необходимо обращаться лишь к небольшому
418 Глава 8. Локальные файловые системы количеству метаданных моментального снимка. Эти метаданные легко впишутся даже в крошечный кеш метаданных ядра. Поэтому необходимо найти способ избежать про- пробуксовки кеша при перемещении между группами цилиндров. Методикой, используемой для избегания пробуксовки при перемещении между группами цилиндров, является построение таблицы предыстории (look-aside) всех бло- блоков, которые были скопированы, когда делался моментальный снимок. В этой таблице перечисляются блоки, связанные с блоками метаданных моментального снимка, ото- отображениями групп цилиндров, суперблоком и блоками, которые содержат активные inode. Когда для блока возникает отказ копирования при записи, первым шагом являет- является обращение к этой таблице. Если блок в таблице найден, не требуется никакого до- дополнительного поиска в любом из моментальных снимков. Если блок не найден, тогда нужно просмотреть метаданные каждого активного моментального снимка в файловой системе, чтобы проверить, нужно ли копирование. Этот поиск в таблице экономит время, поскольку позволяет избежать не только отказов в метаданных для широко раз- разбросанных блоков, но также и необходимости обращаться потенциально к множеству моментальных снимков. Другой проблемой с моментальными снимками в больших файловых системах яв- является то, что они усугубляют существующие проблемы с тупиками (deadlocks). Когда имеется несколько моментальных снимков, связанных с файловой системой, они хра- хранятся в списке, упорядоченном от старшего к младшему. Когда возникает отказ ко- копирования при записи, список обходится, давая каждому моментальному снимку воз- возможность решить, нужно ли ему копировать блок, который будет перезаписан. Перво- Первоначально у каждого inode моментального снимка есть своя собственная блокировка. Взаимоблокировка может возникнуть между двумя процессами, каждый из которых пытается сделать запись. Рассмотрите пример на рис. 8.29. Он показывает файловую систему с двумя моментальными снимками: snapl и snap2. Процесс А удерживает за- заблокированным моментальный снимок 1, а процесс В удерживает заблокированным моментальный снимок 2. Как snapl, так и snap2 решили, что им нужно выделить новый блок, в котором они будут хранить копию блока, записываемого процессом, который удерживает их заблокированным. Запись нового блока в моментальном снимке 1 заста- заставит ядро, работающее в контексте процесса А, сканировать список моментальных снимков, которые будут заблокированы с моментальным снимком 2, поскольку он удерживается процессом В. Между тем запись нового блока в моментальном снимке 2 заставит ядро, работающее в контексте процесса В, сканировать список моментальных снимков, которые будут заблокированы с моментальным снимком 1, поскольку он заблокирован процессом А. Решением проблемы взаимоблокировки является выделение единственной бло- блокировки, которая используется для всех моментальных снимков в файловой системе. Когда создается новый моментальный снимок, ядро проверяет, есть ли в файловой сис- системе какие-нибудь другие моментальные снимки. Если они есть, блокировка файла, связанная с inode нового моментального снимка, освобождается и заменяется бло-
8.7. Моментальные снимки файловой системы 419 кировкой, используемой для других моментальных снимков. При единственной бло- блокировке доступ к моментальным снимкам в целом сериализуется. Таким образом, на рис. 8.29 процесс В будет удерживать блокировки для всех моментальных снимков и будет иметь возможность сделать необходимые проверки и обновления, пока про- процесс А ожидает. Когда процесс В завершает свой просмотр, процесс А сможет по- получить доступ ко всем моментальным снимкам и сможет успешно отработать до завершения. Из-за добавленной сериализации поисков моментальных снимков для обеспечения приемлемой производительности моментальных снимков важна описан- описанная ранее таблица предыстории (look-aside). При сборе статистики на многих рабо- работающих системах мы нашли, что таблицы предыстории разрешают примерно половину поисков копирования при записи моментальных снимков. Таким образом, мы нашли, что таблицы предыстории удерживают состязание за блокировку моментального снимка на приемлемом уровне. Заблокирован процессом А, ожидающим, пока блокировка snap2 проверит запись Заблокирован процессом В, ожидающим, пока блокировка snapl проверит запись РИС. 8.29. Сценарий взаимоблокировки моментального снимка Табл. 8.6. Аппаратная и программная конфигурация для тестирования моментальных снимков Элемент Компьютер Операционная система Контроллер ввода/вывода Диск Малая файловая система Большая файловая система Загрузка Конфигурация Два процессора Celeron 350 МГц с 256 Мбайт оперативной памяти Текущая FreeBSD 5.0 на 30 декабря 2001 г. Адаптер Adaptec 2940 Ultra2 SCSI. Два устройства фиксированного непосредственного доступа SCSI-2 <IBM DDRS-39130D DC1B>, 80 Мбайт/с, включены помеченные очере- очереди, 8715 Мбайт, 17 850 000 512-байтных секторов: 255 головок, 63 сек- сектора на дорожку, 1111 цилиндров 0,5 Гбайт, 8 Кбайт блок, 1 Кбайт фрагмент, заполненный на 90%, 70 874 файла, начальный размер моментального снимка 0,392 Мбайт @,08% размера файловой системы) 7,7 Гбайт, 16 Кбайт блок, 2 Кбайт фрагмент, заполненный на 90%, 52 0715 файлов, начальный размер моментального снимка 2,672 Мбайт @,03% размера файловой системы) Четыре постоянно работающие теста Эндрю, создающие умеренный объем активности файловой системы, чередующиеся с периодами ин- интенсивной активности процессора [Howard et al., 1988].
420 Глава 8. Локальные файловые системы Производительность моментальных снимков Эксперименты, описанные в данном разделе, использовали аппаратную и программ- программную конфигурацию, приведенную в табл. 8.6. В табл. 8.7 показано время, необходимое для создания моментального снимка в бездействующей файловой системе. Время для создания моментального снимка про- пропорционально размеру снимаемой файловой системы. Однако почти все время на мо- моментальный снимок тратится в шагах 1, 2 и 8. Поскольку файловая система позволяет другим процессам модифицировать файловую систему в ходе шагов 1, 2 и 8, эта часть производства моментального снимка не мешает обычной деятельности системы. Столбец «Время приостановки» показывает количество действительного времени, на которое для процессов блокируется выполнение системных вызовов, модифицирующих фай- файловую систему. Как показано в табл. 8.7, период, в течение которого приостанавлива- приостанавливается запись и который поэтому очевиден для процессов в системе, короткий и не уве- увеличивается пропорционально размеру файловой системы. Табл. 8.7. Время создания моментальных снимков в бездействующей файловой системе Размер файловой системы Прошедшее время Время процессора Время приостановки 0,5 Гб 0,7 с 0,1с 0,025 с 7,7 Гб 3,5 с 0,4 с 0,034 с Табл. 8.8. Время создания моментальных снимков в действующей файловой системе Размер файловой системы Прошедшее время Время процессора Время приостановки 0,5 Гб 3,7 с 0,1с 0,027 с 7,7 Гб 12,1с 0,4 с 0,036 с В табл. 8.8 показано время создания моментального снимка файловой системы, в которой одновременно запущено четыре активных процесса. Прошедшее время уве- увеличивается, поскольку процесс, делающий моментальный снимок, должен соперничать с другими процессами для доступа к файловой системе. Обратите внимание, что время приостановки слегка увеличилось, но по-прежнему незначительно и не увеличивается пропорционально размеру тестируемой файловой системы. Вместо этого оно является функцией уровня активности записей, происходящих в системе. В таблице 8.9 показано время удаления моментального снимка при бездейст- бездействующей файловой системе. Прошедшее время для удаления моментального снимка пропорционально размеру фиксируемой файловой системы. Файловую систему не нужно приостанавливать для удаления моментального снимка. В табл. 8.10 показано время для удаления моментального снимка в файловой сис- системе, в которой одновременно запущено четыре активных процесса. Прошедшее
8.7. Моментальные снимки файловой системы 421 Табл. 8.9. Время удаления моментального снимка на бездействующей файловой системе Размер файловой системы Прошедшее время Время процессора 0,5 Гб 0,5 с 0,02 с 7,7 Гб 2,3 с 0,09 с время увеличивается, поскольку процесс, удаляющий моментальный снимок, должен соперничать с другими процессами для доступа к файловой системе. Табл. 8.10. Время удаления моментального снимка в активной файловой системе Размер файловой системы Прошедшее время Время процессора 0,5 Гб 1,4 с 0,03 с 7,7 Гб 4,9 с 0,10 с Фоновая fsck Традиционно после несоответствующего выключения системы было необходимо запускать программу проверки файловой системы fsck для всех inode в файловой сис- системе, чтобы выяснить, какие inode и блоки используются, и исправить битовые карты. Эта проверка болезненно замедляла процесс, который мог задержать запуск большого сервера на час и более. Текущая реализация мягких обновлений гарантирует согласо- согласованность всех ресурсов файловой системы, включая битовые карты inode и блоков. С мягкими обновлениями единственной несогласованностью, которая может возник- возникнуть в файловой системе (за исключением программных ошибок и сбоев устройств), является то, что некоторые блоки, на которые нет ссылок, не могут появиться в бито- битовых картах, а некоторым inodeM может потребоваться снизить чрезмерно большие счетчики ссылок. Поэтому совершенно безопасно можно начать использовать файло- файловую систему после сбоя без предварительно запуска fsck. Однако после каждого сбоя может теряться некоторое пространство файловой системы. Поэтому имеет смысл в наличии версии fsck, которая может работать в фоновом режиме в действующей фай- файловой системе для обнаружения и восстановления всех потерянных блоков и приведе- приведения в порядок inode с чрезмерно большими счетчиками ссылок. Особым случаем чрез- чрезмерно большого значения счетчика ссылок является значение, которое должно было быть нулевым. Такой inode будет освобожден в ходе уменьшения значения его счетчи- счетчика до нуля. Эта задача по сборке мусора легче, чем могло бы показаться на первый взгляд, поскольку данной версии fsck нужно лишь идентифицировать ресурсы, которые не используются, не могут выделяться и к которым нет доступа из дейст- действующей системы. С добавлением моментальных снимков задача становится проще, требуя лишь небольших изменений стандартной fsck. При работе в фоновом режиме очистки fsck
422 Глава 8. Локальные файловые системы начинает с создания моментального снимка проверяемой файловой системы. Затем fsck работает с образом зафиксированной файловой системы, выполняя свои вычисления точно так же, как при обычной работе. Единственное другое отличие появляется в конце ее работы, когда она хочет записать обновленные версии битовых карт. В этом случае модифицированная fsck берет набор блоков, помеченных в момент создания моменталь- моментального снимка как использующиеся, и удаляет его из набора, помеченного как исполь- зующийося, - отличием является набор потерянных блоков. Она также создает список inode, число ссылок которых необходимо отрегулировать. Затем fsck делает новый сис- системный вызов для уведомления файловой системы об обнаруженных потерянных блоках таким образом, чтобы она могла заменить их в своих битовых картах. Она предоставляет также набор inode, число ссылок которых необходимо отрегулировать; те inode, счетчики которых снижаются до нуля, сокращаются до нулевого размера и освобождаются. Когда fsck завершается, она освобождает свой моментальный снимок. Все подробности того, как реализована fsck, можно найти в McKusick [2002; 2003]. Видимые для пользователя моментальные снимки Моментальные снимки можно сделать в любой момент. Когда они делаются каждые несколько часов в течение дня, они дают пользователям возможность получить файл, который они записали несколькими часами ранее, а позже по ошибке удалили или переписали. Моментальные снимки значительно более удобны для использования, чем ленты с дампами, и их можно создавать значительно чаще. Моментальный снимок, описанный выше, создает зафиксированный образ раздела файловой системы. Чтобы сделать этот моментальный снимок доступным пользовате- пользователям через традиционный интерфейс файловой системы, системный администратор ис- использует драйвер vnode vnd. Драйвер vnd принимает в качестве входных данных файл и создает для доступа к нему интерфейс символьного устройства. Затем в качестве устройства ввода для стандартной команды mount можно использовать символьное устройство vnd, давая моментальному снимку возможность появиться в виде копии за- зафиксированной файловой системы в любой области пространства имен, которую сис- системный администратор выберет для ее монтирования. Динамические дампы Раз снимки файловой системы доступны, становится возможным делать безопасные дампы действующей файловой системы. Когда dump замечает, что у нее запрашивают создание дампа смонтированной файловой системы, она может просто создать момен- моментальный снимок файловой системы и работать с ним, а не с действующей файловой системой. Когда dump завершается, она освобождает моментальный снимок.
8.8. Локальное файловое хранилище 423 8.8. Локальное файловое хранилище Два последних раздела данной главы описывают организацию и управление данными в запоминающих средах. Изначально FreeBSD предоставляла три различных менед- менеджера файловых хранилищ: традиционную быструю файловую систему Беркли (Berke- (Berkeley Fast Filesystem - FSS), структурированную для журналирования файловую систему (Log-Structured Filesystem) и файловую систему, основанную на памяти (Memory-based Filesystem). FreeBSD 5.2 поддерживает лишь FFS, хотя другие файловые системы про- продолжают поддерживаться NetBSD. Файловое хранилаще FFS было спроектировано в предположении, что кеши файлов будут небольшими и что поэтому файлы придется читать часто. Она старается размещать файлы, к которым возможен совместный дос- доступ, в одном и том же месте диска. Обзор файлового хранилища Операции, определенные для выполнения действий хранилища данных файловой сис- системы, представлены в табл. 8.11. Этих операций меньше, и они семантически проще, чем операции, использующиеся для управления пространством имен. Табл. 8.11. Операции хранилища данных файловой системы Выполняемая операция Имена операторов Создание и удаление объекта valloc, vfree Обновление атрибута update Чтение и запись объекта vget, blkatoff, read, write, fsync Изменение распределения памяти truncate Для выделения и освобождения объектов есть два оператора. Оператор valloc соз- создает новый объект. Идентификатором этого объекта является число, возвращаемое оператором. За отображение этого числа на имя отвечает код пространства имен. Объект освобождается оператором vfree. Освобождаемый объект идентифицируется лишь по своему номеру. Атрибуты объекта изменяются оператором update. Этот уровень не делает ин- интерпретацию этих атрибутов; они просто являются дополнительными данными фик- фиксированного размера, хранящимися вне основной области данных объекта. Обычно это атрибуты файлов, такие, как владелец файла, группа, права доступа и т.д. Обратите внимание, что пространство расширенных атрибутов обновляется с использованием интерфейса read и write, поскольку этот интерфейс уже подготовлен к чтению и записи данных произвольной длины в/из процессов уровня пользователя. Для манипулирования существующими объектами есть пять операторов. Опера- Оператор vget получает из файлового хранилища существующий объект. Объект идентифи-
424 Глава 8. Локальные файловые системы цируется по своему номеру и должен быть предварительно создан valloc. Оператор read копирует данные из объекта в область, описываемую структурой uio. Оператор blkatoffсходен с оператором read, за тем исключением, что blkatoff про сто возвращает указатель на буфер памяти ядра с запрошенными данными вместо копирования дан- данных. Этот оператор предназначен для повышения эффективности операций, когда код пространства имен интерпретирует содержимое объекта (т.е. каталогов) вместо про- простого возврата содержимого процессу пользователя. Оператор write копирует данные в/ из области, описываемой структурой uio. Oneparop/syztc запрашивает перемещение всех данных, связанных с объектом, в стабильное хранилище (обычно путем записи их всех на диск). Нет необходимости аналога blkatoffдля записи, поскольку ядро может просто изменить буфер, который оно получило от blkatoff пометить этот буфер как грязный, а затем выполнить операцию fsync, чтобы записать буфер обратно. Заключительной операцией хранилища данных является truncate. Эта операция изменяет объем памяти, связанный с каждым объектом. Изначально она использова- использовалась лишь для уменьшения размера объекта. В FreeBSD она может использоваться как для увеличения, так и для уменьшения размера объекта. Когда размер файла увеличи- увеличивается, создается дыра в файле. Обычно дополнительного дискового пространства не выделяется; единственным изменением является обновление inode, чтобы он отражал увеличенный размер файла. При чтении дыры интерпретируются системой как запол- заполненные нулями байты. У каждого диска есть одно или более подразделений, или разделов (partitions). Каждый такой раздел может содержать лишь одно файловое хранилище, f файловое хранилище никогда не может охватывать несколько разделов. Хотя файловая система может использовать несколько дисковых разделов для разбиения на полосы (striping) или RAID, объединение частей, составляющих файловую систему, управляется драй- драйвером нижележащего уровня в ядре. С точки зрения кода файловой системы он всегда работает с одним непрерывным разделом. Файловое хранилище отвечает за управление пространством в пределах своего дискового раздела. В рамках этого пространства оно отвечает за создание, хранение, получение и удаление файлов. Оно работает с неструктурированным пространством имен. При запросе создания нового файла выделяется inode для этого файла и возвра- возвращается назначенный номер. Именование, управление доступом, блокирование и мани- манипулирование атрибутами для файла - все обрабатывайся уровнем управления иерархической файловой системой над файловым хранилищем. Файловое хранилище управляет также выделением новых блоков файлам, когда последние увеличиваются. Простые реализации файловой системы, такие, как исполь- использовавшиеся ранними микрокомпьютерными системами, выделяют файлы рядом, один за другим, до тех пор, пока файлы не достигнут конца диска. По мере удаления файлов появляются дыры. Чтобы повторно использовать освобожденное пространство, фай- файловая система должна уплотнить диск, чтобы переместить все свободное пространство в конец. Файлы могут создаваться лишь по одному за раз; для того чтобы увеличить
8.8. Локальное файловое хранилище 425 размер любого файла, кроме последнего на диске, его нужно скопировать в конец, а затем расширить. Как мы видели в разделе 8.2, каждый файл в файловом хранилище описывается inode; размещение блоков данных приводится в виде указателей на блоки в его inode. Хотя файловое хранилище может объединять блоки файла для повышения производи- производительности ввода/вывода, inode может ссылать на блоки, разбросанные по разным местам по всему разделу. Таким образом, несколько файлов можно записывать одновременно, а дисковое пространство можно использовать без необходимости уплотнения. Реализация файлового хранилища преобразует абстракцию файла, как массива бай- байтов, в структуру, налагаемую нижележащим физическим носителем. Рассмотрите типичный носитель магнитного диска с секторами фиксированного размера. Хотя поль- пользователь может захотеть записать в файл единственный байт, диск поддерживает чтение и запись лишь кратного числа секторов. В данном случае система должна прочитать сек- сектор, содержащий модифицируемый байт, заменить нужный байт и записать сектор обрат- обратно на диск. Эта операция - преобразование произвольного доступа к массиву байтов в чтения и записи секторов диска - называется блочным вводом/выводом. Сначала система разделяет запрос пользователя на ряд операций, которые необхо- необходимо выполнить с логическими блоками файла. Логические блоки описывают участки файла с размерами, равными размерам блоков. Система вычисляет логические блоки, деля массив байтов на участки с размером, равным размеру блока файлового хранили- хранилища. Таким образом, если размер блока файлового хранилища равен 8192 байтам, ло- логический блок 0 содержал бы байты с 0-го по 8191-й, логический блок 1 содержал бы байты с 8192-го по 16383-й и т.д. Данные в каждом логическом блоке хранятся на диске в физическом блоке. Физиче- Физический блок является местом на диске, на которое система отображает логический блок. Физический дисковый блок составляется из одного или нескольких смежных секторов. Для диска с 512-байтными секторами блок файлового хранилища в 8192 байта был бы составлен из 16 смежных секторов. Хотя содержимое логического блока на диске непрерывно, логические блоки файла не обязательно должны размещаться последова- последовательно. Структура данных, использующаяся системой для преобразования логических блоков в физические блоки, описывается в разделе 8.2. Пользовательский ввод/вывод в файле Хотя пользователь может захотеть записать в файл единственный байт, аппаратное обеспечение диска может читать и записывать лишь кратное число секторов. Следова- Следовательно, система должна организовать чтение сектора, содержащего изменяемый байт, заменить нужный байт и записать сектор обратно на диск. Эта операция преобразова- преобразования произвольного доступа к массиву байтов в чтения и записи секторов диска извест- известна как блочный ввод/вывод.
426 Глава 8. Локальные файловые системы Процессы могут читать данные размерами, меньшими размеров дисковых блоков. В первый раз, когда необходимо небольшое чтение из определенного блока диска, блок будет помещен из диска в буфер ядра. Позже чтения частей того же самого блока потребуют лишь копирования из буфера ядра в память пользовательских процессов. Несколько небольших записей трактуются аналогично. Из кеша выделяется буфер, когда осуществляется первая запись в дисковый блок, а последующие записи в часть того же самого блока скорее всего потребуют лишь копирования в буфер ядра без дис- дискового ввода/вывода. Кроме предоставления абстракции произвольного выравнивания чтений и запи- записей, буферный кеш блоков уменьшает число дисковых перемещений ввода/вывода, не- необходимых для доступов к файловой системе. Поскольку файлы системных пара- параметров, команды и каталоги читаются повторно, их блоки данных обычно находятся в буферном кеше, когда они нужны. Поэтому ядру не нужно каждый раз, когда они тре- требуются, читать их с диска. На рис. 8.30 показан поток информации и работа, необходимая для доступа к файлу на диске. Абстракцией, представленной пользователю, является массив бай- байтов. Эти байты собирательно описываются дескриптором файла, который ссылается на некоторое место в массиве. Пользователь может запросить операцию записи в файл, предоставив системе указатель на буфер с запросом записи некоторого числа байтов. Как показано на рис. 8.30, запрошенные данные не должны быть выровнены по началу или концу логического блока. Более того, размер запроса не ограничен одним логиче- логическим блоком. В приведенном примере пользователь запросил запись данных в части логических блоков 1 и 2. Поскольку диск может перемещать данные лишь в виде крат- кратного числа секторов, файловое хранилище должно сначала организовать чтение данных для любой части блока, который должен остаться без изменения. Система должна организовать промежуточное перемещение данных для передачи. Это проме- промежуточное перемещение осуществляется через один или более системных буферов, описанных в разделе 6.6. В нашем примере пользователь хочет модифицировать данные в логических блоках 1 и 2. Операция повторяет в цикле пять шагов. 1. Выделяет буфер. 2. Определяет местонахождение соответствующего физического блока на диске. 3. Запрашивает у дискового контроллера чтение содержимого физического блока в системный буфер и ожидание завершения перемещения. 4. Выполняет копирования из памяти в память из начала буфера ввода/вывода поль- пользователя в соответствующую часть системного буфера. 5. Записывает блок на диск и продолжает, не ожидая завершения перемещения.
8.8. Локальное файловое хранилище 427 Пользователь: write (fd Буфер: Логический файл: , buffer, cnt) -*— cnt —*¦ Системные буферы: Логические блоки файлов: 0 \ 1 Диск: \ \ РИС. 8.30. Система блочного ввода/вывода i ¦ I #90255 \0: 1 л \ 2: 3 \ \ #51879 —l #32218 ! 3: #11954 Если запрос пользователя не завершен, процесс повторяется со следующим логическим блоком файла. В нашем примере система выбирает логический блок 2 файла и может выполнить запрос пользователя. Если бы записывался весь блок, система могла бы опустить шаг 3 и просто записать данные на диск, не считывая предваритель- предварительно старое содержание. Это постепенное выполнение запроса чтения прозрачно для процесса пользователя, поскольку выполнение этого процесса блокируется во время всей процедуры. Это заполнение прозрачно для других процессов; поскольку inode блокируется в ходе процесса, любая попытка получить доступ со стороны какого-либо другого процесса будет заблокирована до завершения операции записи. Если система испытывает сбой, когда данные для определенного блока находятся в кеше, но еще не были записаны на диск, файловая система на диске будет содержать ошибку, и эти данные будут потеряны. Согласованность критических данных файло- файловой системы поддерживается с использованием методик, описанных в разделе 8.6, но потеря недавно записанных данных приложения по-прежнему возможна. Для того чтобы минимизировать потерянные данные, записи на диск для грязных буферов форсируются через каждые 30 секунд после того, как они были записаны. Имеется
428 Глава 8. Локальные файловые системы также системный вызов fsyitc, который процесс может использовать для форсирования немедленной записи всех грязных блоков для одного файла; эта синхронизация полез- полезна для обеспечения целостности базы данных или перед удалением файла резервной копии редактора. 8.9. Быстрая файловая система Беркли Традиционная файловая система UNIX описывается своим суперблоком, который со- содержит базовые параметры файловой системы. Эти параметры включают число блоков данных в файловой системе, максимальное число файлов и указатель на сво- свободный список, который является списком всех свободных блоков в файловой системе. 150-Мбайтная традиционная файловая система UNIX состоит из 4 мегабайт inode, за которыми следуют 146 Мбайт данных. Такая организация отделяет информацию об inode от данных; таким образом, доступ к файлу обычно влечет длинный поиск от inode файла к его данным. Файлам в одном каталоге обычно не выделяются последова- последовательные слоты в 4 Мбайт inode, что вызывает чтение множества несмежных дисковых блоков, когда осуществляется доступ к множеству inode в одном каталоге. Назначение блоков данных файлам также неоптимальное. Традиционная реализа- реализация файловых систем использует размер блока в 512 байтов. Но следующий последо- последовательный блок данных часто находится не в том же самом цилиндре, поэтому часто необходимы поиски между перемещениями 512-байтных данных. Это сочетание не- небольшого размера блока и разбросанного размещения значительно ограничивает про- производительность файловой системы. Первая работа над файловой системой UNIX в Беркли была попыткой повысить как надежность, так и производительность файловой системы. Разработчики улучши- улучшили надежность, сделав изменения критической информации файловой системы таким образом, чтобы программа после сбоя могла либо завершить, либо чисто восстановить модификации [McKusick & Kowalski, 1994]. Удвоение размера блока файловой систе- системы повысило производительность файловой системы 4.0BSD более чем в 2 раза по сравнению с файловой системой 3BSD. Это удвоение позволило при каждом дисковом перемещении получать доступ к в два раза большему числу блоков и устранило необ- необходимость в косвенных блоках для множества файлов. В оставшейся части данного раздела мы будем ссылаться на файловую систему с этими изменениями как на файло- файловую систему 3BSD. Повышение производительности в файловой системе 3BSD дало твердое указание, что увеличение размера блока было хорошим методом для повышения производитель- производительности. Хотя пропускная способность удвоилась, файловая система 3BSD по-прежнему использовала лишь около 4 процентов от максимальной пропускной способности диска. Главной проблемой было то, что расположение блоков в свободном списке быстро становилось перемешанным по мере создания и удаления файлов. В конечном
8.9. Быстрая файловая система Беркли 429 счете порядок в свободном списке становился полностью случайным, что вызывало назначение блоков файлам случайным образом по всему диску Из-за этой случайности при каждом доступе к блоку возникал поиск. Хотя файловая система 3BSD обеспечи- обеспечивала скорости передачи вплоть до 175 Кбайт в секунду при первом создании, разброс в свободном списке вызывал ухудшение этой скорости в среднем до 30 Кбайт в секун- секунду после нескольких недель умеренного использования. Помимо повторного создания системы, способа восстановить производительность файловой системы 3BSD не было. Организация быстрой файловой системы Беркли Первая версия текущей файловой системы BSD появилась в 4.2BSD [McKusick et al., 1984]. Сегодня эта версия по-прежнему используется как UFS1. В организации файло- файловой системы FreeBSD (как и в организации файловой системы 3BSD) каждый диско- дисковый привод содержит одну или более файловых систем. Файловая система FreeBSD описывается суперблоком, расположенным в начале дискового раздела файловой сис- системы. Поскольку суперблок содержит критические данные, он копируется на случай катастрофической потери. Это копирование осуществляется при создании файловой системы. Поскольку большая часть данных суперблока не изменяется, нет необходи- необходимости обращаться к копиям, если отказ диска не вызовет повреждение суперблока. Данные в суперблоке, которые не изменяются, включают в себя несколько флагов и до- дополнительные сводные данные, которые можно легко создать заново, если потребуется использовать альтернативный суперблок. Чтобы обеспечить поддержку фрагментов файловой системы вплоть до одного 512-байтного сектора диска, минимальный размер блока файловой системы равен 4096 байтам. Размер блока может быть любой степенью 2, большей или равной 4096. Размер блока записывается в суперблок файловой системы, поэтому на одной и той же системе можно получить одновременный доступ к файловым системам с раз- различными размерами блоков. Размер блока можно выбрать во время создания файло- файловой системы; его нельзя изменить впоследствии без перестройки файловой системы. Организация файловой системы BSD делит дисковый раздел на одну или более об- областей, каждая из которых называется группой цилиндров. Изначально группа цилин- цилиндров содержала в себе один или больше последовательных цилиндров на диске. Хотя FreeBSD по-прежнему использует для описания групп цилиндров ту же самую струк- структуру данных, их практическое определение изменилось. При первом проектировании файловой системы она могла получить точное представление о геометрии диска, включая границы цилиндров и дорожек, и могла точно вычислить вращательное поло- положение каждого сектора. Современные диски скрывают эту информацию, предоставляя фиктивные числа блоков в дорожке, дорожек в цилиндре и цилиндров в диске. На самом деле в современных RAID-массивах «диск», представленный файловой сис- системе, мог реально быть составлен из набора дисков в RAID-массиве. Хотя были выпол- выполнены некоторые исследования для выяснения истинной геометрии диска [Griffin et al.,
430 Глава 8. Локальные файловые системы 2002; Lumb et al., 2002; Schindler et al., 2002], сложность использования такой ин- информации эффективным образом велика. Современные диски имеют большее число секторов на дорожку во внешней части диска, чем во внутренней, что усложняет вычисление вращательного положения любого данного сектора. Поэтому, когда составлялся проект для UFS2, мы решили избавиться от всего кода вращательной раз- разметки, имеющегося в UFS1, и просто предположить, что размещение файлов с числен- численно близкими номерами блоков (с рассмотрением последовательного размещения как оптимального) дало бы наибольшую производительность. Таким образом, структура группы цилиндров в UFS2 сохранена, но используется она лишь как удобный способ для управления логически близкими группами блоков. Код вращательной разметки в UFS1 был отключен с конца 1980-х гг., поэтому в ходе очистки базового кода он был удален полностью. Каждая группа цилиндров должна умещаться в одном блоке файловой системы . При создании новой файловой системы утилита newfs вычисляет максимальное число блоков, которые можно упаковать в отображение групп цилиндров, основываясь на размере блока файловой системы. Затем она выделяет минимальное число групп ци- цилиндров, необходимых для описания файловой системы. Файловая система с 16 кило- байтными блоками обычно имеет шесть групп цилиндров на гигабайт. Каждая группа цилиндров содержит учетные сведения, которые включают до- дополнительную копию суперблока, пространство для inode, битовую карту, описы- описывающую доступность блоков в группе цилиндров, и суммарные сведения, описы- описывающие использование блоков данных внутри группы цилиндров. Битовая карта доступных блоков в группе цилиндров заменяет свободный список традиционных файловых систем. Для каждой группы цилиндров в UFS1 во время создания файло- файловой системы выделяется статическое число inode. Политикой по умолчанию являет- является выделение одного inode на четыре фрагмента файловой системы в ожидании, что это количество будет значительно больше, чем может когда-либо понадобиться. Для каждой группы цилиндров в UFS2 по умолчанию резервируется пространство бито- битовой карты для описания одного inode на два фрагмента файловой системы. В каждом типе файловой системы значение по умолчанию можно изменить лишь во время соз- создания файловой системы. Обоснованием для использования групп цилиндров является создание кластеров inode, которые разбросаны по диску вблизи блоков, на которые они ссылаются, вместо размещения их в начале диска. Файловая система пытается выделить блоки файлов рядом с описывающими их inode, чтобы избежать длительных поисков между получе- получением inode и получением связанных с ним данных. Также, когда inode разбросаны, меньше вероятность их потери в результате единственного отказа диска. 1 Имеется в виду, что карта свободных блоков для группы цилиндров должна помещаться в один блок. - Примеч. науч. ред.
8.9. Быстрая файловая система Беркли 431 Хотя мы решили использовать для UFS2 новый формат inode на диске, мы решили не менять формат суперблока, карт отображения групп цилиндров и формат каталогов. Дополнительные сведения, необходимые для суперблока UFS2 и групп цилиндров, хранятся в запасных полях суперблока UFS1 и групп цилиндров. Сохранение того же самого формата для этих структур данных позволяет использовать одну базу кода как для UFS1, так и для UFS2. Поскольку единственным различием между двумя файловы- файловыми системами является формат их inode, код может разыменовывать указатели на суперблоки, группы цилиндров и элементы каталогов без необходимости проверки того, к какому типу файловой системы осуществляется доступ. Чтобы минимизиро- минимизировать условную проверку кода, который ссылается на inode, inode на диске преобразует- преобразуется в обычный формат в памяти, когда он впервые считывается с диска, и преобразуется обратно в формат на диске, когда записывается обратно. Результатом этого решения является то, что есть только девять из нескольких сотен процедур, специфичных для UFS1 в сравнении с UFS2. Преимуществом наличия общей базы кода для обеих файло- файловых систем является то, что она значительно снижает стоимость сопровождения. За исключением девяти специфических для формата файловой системы функций, исправление ошибки в коде исправляет ее для обоих типов файловой системы. Общая база кода означает также, что, когда будет добавлена поддержка симметричной много- многопроцессорной обработки, ее необходимо сделать лишь однажды для семейства файловых систем UFS. Загрузочные блоки Файловая система UFS1 зарезервировала 8-килобайтное пространство в начале файло- файловой системы, в которое поместило загрузочный блок. Хотя это пространство казалось огромным по сравнению с 1-килобайтным блоком, которое оно заместило, со време- временем стало все труднее втискивать загрузочный код в это пространство. Впоследствии мы решили повторно вернуться в UFS2 к размеру загрузочного блока. У загрузочного кода есть список мест, которые проверяются на предмет загру- загрузочных блоков. Начало загрузочного блока можно определить на любой 8-Кбайтной границе. Мы установили начальный список с четырьмя возможными размерами загру- загрузочного блока: отсутствует, 8 килобайт, 64 килобайта и 256 килобайт. Каждое из этих мест было выбрано с определенной целью. Файловым системам, кроме корневых, нет необходимости быть загружаемыми, поэтому мы можем использовать нулевой размер загрузочного блока. Загрузочный блок нулевого размера могут также использовать файловые системы на очень маленьких носителях, таких, как гибкие диски. Для архитектур с простыми загрузочными блоками может использоваться традиционный 8-килобайтный загрузочный блок UFS1. Более типично используется 64-килобайтный загрузочный блок (например, на архитектуре PC с ее потребностью поддержки загруз- загрузки с множества шин и дисковых приводов).
432 Глава 8. Локальные файловые системы Мы добавили 256-Кбайтный загрузочный блок для случая, когда некоторым будущим архитектурам или приложениям потребуется сохранить особенно большую область загруз- загрузки. Это резервирование пространства не является строго необходимым, поскольку в список в любое время можно добавить новые размеры, но распространение обновленно- обновленного списка по всем загрузочным программам и загрузчикам на существующих системах может занять много времени. Добавив опцию для огромной загрузочной области теперь, мы можем гарантировать, что она всегда будет готова, понадобись она в будущем. Неожиданным побочным эффектом использования 64-килобайтного загрузочного блока для UFS2 является то, что, если раздел ранее содержал в себе файловую систему UFS1, суперблок прежней файловой системы UFS1 не может быть переписан. Если запу- запущена старая версия fsck, которая не проверяет вначале наличие файловой системы UFS2 и которая находит суперблок UFS1, она может ошибочно попытаться перестроить файловую систему UFS1, уничтожив по ходу дела файловую систему UFS2. Поэтому при построении файловых систем UFS2 утилита newfs ищет старые суперблоки UFS1 и обнуляет их. Оптимизация использования хранилища Данные располагаются таким образом, что большие блоки могут перемещаться в одной дисковой операции, значительно увеличивая пропускную способность файло- файловой системы. Файл в новой файловой системе мог бы быть составлен из 8192-байтных блоков данных по сравнению с 1024-байтными блоками в файловой системе 3BSD; доступы к диску перемещали бы таким образом до 8 раз больше информации на одну дисковую транзакцию. В больших файлах несколько блоков могут быть выделены по- последовательно, поэтому до того, как потребуется поиск, возможны перемещения даже еще большего количества данных. Основной проблемой с большими блоками является то, что большинство файло- файловых систем BSD содержат главным образом небольшие файлы. Постоянно большой размер блока впустую расходовал бы пространство. В табл. 8.12 показано влияние раз- размера блока файловой системы на количество неиспользующегося пространства файло- файловой системы. Измерения, использованные для вычисления этой таблицы, были собра- собраны из исследования в Интернете, проведенного в 1993 г. [Irlam, 1993]. Исследование охватывало 12 миллионов файлов, находящихся в 1000 файловых системах с общим размером в 250 Гбайт. Исследователи нашли, что медианный размер файла был меньше 2048 байтов; средний размер файла был 22 Кбайта. Теряемое пространство вычислялось в процентах от дискового пространства, не содержащего данных пользо- пользователей. По мере увеличения размера блока объем пространства, зарезервированного для inode, уменьшался, но объем неиспользуемого пространства данных в конце блоков быстро увеличивался до недопустимой потери 29,4 процента с минимальным выделением 8192-байтных блоков файловой системы. Более современные (частные) исследования дали сходные результаты.
8.9. Быстрая файловая система Беркли 433 Для того чтобы большие блоки использовались без значительных потерь, неболь- небольшие файлы должны храниться более эффективно. Чтобы повысить эффективность ис- использования памяти, файловая система допускает разделение одного блока файловой системы на один или более фрагментов. Размер фрагмента определяется во время соз- создания файловой системы; каждый блок файловой системы в необязательном порядке может быть разделен на два, четыре или восемь фрагментов, каждый из которых можно адресовать. Нижний предел размера фрагмента ограничивается размером сек- сектора диска, который обычно равен 512 байтам. Карта блока, связанная с каждой груп- группой цилиндров, записывает пространство, доступное в группе цилиндров в фрагмен- фрагментах; для определения того, доступен ли блок, система проверяет выровненные фраг- фрагменты. На рис. 8.31 показан участок карты блока от файловой системы с 4096-байтны- ми блоками и 1024-байтными фрагментами, именуемый в дальнейшем как файловая система 4096/1024. Табл. 8.12. Объем теряемого пространства как функция размера блока Процент Процент потерь с данными общих потерь 0,0 1,1 7,4 8,8 11,7 15,4 29,4 62,0 0,0 1,1 1,1 2,5 5,4 12,3 27,8 61,2 Процент Организация потерь inode 0,0 Только данные, без разделения между файлами 0,0 Только данные, файлы на 512-байтной границе 6,3 Данные + inode, 512-байтный блок 6,3 Данные + inode, 1024-байтный блок 6,3 Данные + inode, 2048-байтный блок 3,1 Данные + inode, 4096-байтный блок 1,6 Данные + inode, 8192-байтный блок 0,8 Данные + inode, 16384-байтный блок В файловой системе 4096/1024 файл представлен нулем или более 4096-байтных блоков данных с одним возможным фрагментированным блоком. Если система должна фрагментировать блок, чтобы получить данные для новых данных, она делает остав- оставшиеся фрагменты блока доступными для выделения другим файлам. В качестве при- примера рассмотрите 11 000-байтный файл, хранящийся в файловой системе 4096/1024. Этот файл использовал бы два полных блока и одну порцию с тремя фрагментами дру- другого блока. Если блок с тремя выровненными фрагментами во время создания файла был бы недоступен, полный блок был бы разделен, давая нужные фрагменты и один не использующийся фрагмент. Этот оставшийся фрагмент при необходимости мог бы быть выделен другому файлу.
434 Глава 8. Локальные файловые системы Биты в карте Номера фрагментов Номера блоков 0-3 0 --11 4-7 1 11- 8-11 2 1111 12-15 3 РИС. 8.31. Пример размещения блоков и фрагментов в файловой системе 4096/1024. Каждый бит в карте записывает состояние фрагмента; «-» означает, что фрагмент использует- используется, «1» означает, что фрагмент доступен для выделения. В данном примере фрагмен- фрагменты с 0-го по 5-й, 10-й и 11-й используются, тогда как фрагменты с 6-го по 9-й и с 12-го по 15-й свободны. Фрагменты смежных блоков не могут использоваться в качестве полного блока, даже если они достаточно большие. В данном примере фрагменты с 6-го по 9-й не могут выделяться в качестве полного блока; лишь фрагменты с 12-го по 15-й могут быть объединены в полный блок Открыв файл, процесс может читать или записывать в него. Процедурный путь через ядро показан на рис. 8.32. Если требуется чтение, оно проводится через проце- процедуру ^_геа?/(9-.#*_/?Я?/(9 отвечает за преобразование чтения в считывание одного или более логических блоков файла. Запрос логического блока обрабатывается затем ufs_bmap(). ufs_bmap() отвечает за преобразование номера логического блока в номер физического блока путем интерпретации указателей на прямые и косвенные блоки в inode. ffs_read() запрашивает систему блочного ввода/вывода вернуть буфер, запол- заполненный содержанием дискового блока. Если из файла читаются два или более последо- последовательных блоков, подразумевается, что файл читается последовательно. В этом случае ufs_bmap() возвращает два значения: сначала дисковый адрес запрошенного блока, а затем число смежных блоков, следующих на диске за этим блоком. Запрошен- Запрошенный блок и число смежных блоков, следующих за ним, передаются процедуре clusterQ. Если доступ к файлу осуществляется последовательно, процедура clusterQ сделает один большой ввод/вывод для всего диапазона смежных блоков. Если доступ к файлу осуществляется не последовательно (что определяется по поиску другой части файла, предшествующему чтению), будет прочитан лишь запрошенный блок или подмноже- подмножество кластера. Если бы файл делал длинные серии последовательных чтений или если бы число последовательных блоков было небольшим, система выдала бы один или более запросов для упреждающего чтения блоков в ожидании того, что процессу вскоре понадобятся эти блоки. Подробности кластеризации блока описаны в конце данного раздела. Каждый раз, когда процесс выполняет системный вызов write, система проверяет, увеличился ли размер файла. Процесс может переписать данные в середине сущест- существующего файла - в этом случае пространство обычно уже выделено (если только файл не содержит дыру в этом месте). Если файл нужно расширить, запрос округляется до следующего размера фрагмента, и выделяется лишь это пространство (подробности выделения памяти см. в «Механизмах выделения» далее в данном разделе). Систем- Системный вызов write проводится через процедуру ffs_write().ffs_write() отвечает за преобра-
8.9. Быстрая файловая система Беркли 435 re ad () vnode в файловую систему Смещение в логический номер блока Логический номер блока в номер блока файловой системы vn_read{) X ffs_read() ± ufsJbmapQ Идентификация смежных блоков и объединение буферов одного блока Выделение буфера и номер блока файловой системы в номер физического блока Номер физического блока в <цилиндр, дорожка, смещение> диска Дисковые чтение-запись Рис. 8.32. Процедурный интерфейс чтения и записи зование записи в одну или более записей логических блоков файла. Затем запрос логического блока nepe№Z4?Kffs_balloc().ffs_balloc() отвечает за интерпретацию ука- указателей на прямые и косвенные блоки в inode для обнаружения местонахождения ука- указателя на соответствующий физический блок. Если дисковый блок еще не существует, вызывается процедура^_а//ос(9 для запроса нового блока соответствующего размера. После вызова chkdqQ для проверки того, что пользователь не превысил свою квоту, выделяется блок, а адрес нового блока сохраняется в inode или косвенном блоке. Возвра- Возвращается адрес нового или уже существующего блока. ffs_write() выделяет буфер для сохранения содержимого блока. Данные пользователя копируются в возвращенный буфер, который помечается как грязный. Если буфер был заполнен полностью, он передается процедуре cluster(). Когда собран кластер максимального размера, или вы- выделяется несмежный блок, или осуществляется поиск в другой части файла, собранные блоки группируются вместе в одной операции ввода/вывода, которая ставится в очередь записи на диск. Если буфер не был заполнен полностью, он не рассматрива- рассматривается сразу готовым для записи. Вместо этого буфер хранится в ожидании того, что про- процесс вскоре захочет добавить к нему данные. Он не освобождается до тех пор, пока не потребуется для какого-нибудь другого блока, т.е. до тех пор, пока он не достигнет начала свободного списка или пока процесс пользователя не выполнит системный вызов fsync. Когда файл получает свой первый грязный блок, он помещается в очередь
436 Глава 8. Локальные файловые системы 30-секундного таймера. Если по истечении таймера он по-прежнему содержит грязные блоки, все его грязные блоки записываются. Если впоследствии он записывается снова, то будет возвращен в очередь 30-секундного таймера. Повторные запросы небольших записей могут увеличивать файл на один фрагмент за раз. Проблема с увеличением размера файла на один фрагмент за раз в том, что данные могут копироваться множество раз, когда фрагментированный блок увеличива- увеличивается до полного блока. Перераспределение фрагментов можно минимизировать, если процесс пользователя записывает сразу полный блок за раз, за исключением частично- частичного блока в конце файла. Поскольку файловые системы с различными размерами блоков могут находиться на одной и той же системе, интерфейс файловой системы предостав- предоставляет прикладным программам оптимальный размер для чтения или записи. Эта воз- возможность используется стандартной библиотекой ввода/вывода, которую используют многие приложения и определенные системные утилиты, такие, как архиваторы и за- загрузчики, которые осуществляют свое собственное управление вводом/выводом. Если политики размещения (описанные в конце данного раздела) должны быть эф- эффективными, файловая система не может сохраняться полностью заполненной. Пара- Параметр, который называется резервом свободного пространства, дает минимальный процент блоков файловой системы, который должен оставаться свободным. Если число свободных блоков падает ниже этого уровня, выделять блоки разрешается только суперпользователю. Этот параметр может быть изменен в любое время, когда файловая система демонтирована. Когда число свободных блоков достигает нуля, про- производительность файловой системы имеет тенденцию уменьшаться наполовину, по- поскольку файловая система не способна локализовать блоки файла. Если производи- производительность файловой системы падает из-за переполнения, ее можно восстановить, удаляя файлы до тех пор, пока объем свободного пространства снова не достигнет ми- минимально приемлемого уровня. Пользователи могут восстановить локальность, чтобы получить более высокие скорости доступа для файлов, созданных в периоды недостат- недостатка свободного пространства, путем копирования файлов в новое место и удаления первоначального, когда станет доступно достаточно места. Табл. 8.13. Важные параметры, поддерживаемые файловой системой Минимальный процент свободного пространства Ожидаемый средний размер файла Ожидаемое число файлов в каталоге Максимум блоков на файл в группе цилиндра Максимум смежных блоков, которые можно переместить в одном запросе ввода/вывода Имя minfree avgfilesize filesperdir maxbpg maxcontia По yivioj; 8% 16 Кбайт 64 2048 8
8.9. Быстрая файловая система Беркли 437 Политики размещения Каждая файловая система параметризуется таким образом, чтобы ее можно было при- приспособить к особенностям прикладного окружения, в котором она используется. Эти параметры подытожены в табл. 8.13. В большинстве ситуаций хорошо работают пара- параметры по умолчанию, но в окружении лишь с несколькими большими файлами или в окружении лишь с несколькими огромными каталогами производительность можно улучшить, отрегулировав соответствующим образом параметры размещения. Политики размещения файловой системы делятся на две различные части. На самом верхнем уровне находятся глобальные политики, которые используют свод- сводную информацию для принятия решений, относящихся к размещению новых inode и блоков данных. Эти процедуры отвечают за принятие решений о размещении новых каталогов и файлов. Они также размещают блоки в непрерывные группы и решают, когда форсировать далекий переход к новой группе цилиндров, поскольку в текущей группе цилиндров оставлено недостаточно места для приемлемых размещений. Ниже процедур глобальной политики находятся процедуры локального размещения. Эти процедуры для размещения блоков данных используют локально оптимальную схему. Первоначальным намерением было перевести эти решения на уровень пользователя, чтобы они могли игнорироваться или замещаться процессами пользователей. Таким образом, это политики, а не простые механизмы. Двумя методами повышения производительности файловой системы являются увеличение локальности ссылок для минимизации задержек поисков [Trivedi, 1980] и улучшение размещения данных, чтобы сделать возможными более крупные переме- перемещения [Nevalainen & Vesterinen, 1977]. Политики глобального размещения пытаются повысить производительность путем кластеризации связанной информации. Они не могут пытаться локализовать все ссылки данных, но должны вместо этого пытаться распределить несвязанные данные между различными группами цилиндров. Если делается слишком много попыток локализации, локальная группа цилиндров может переполниться, вызвав дальнейшее разбрасывание связанных данных по нелокальным группам цилиндров. Доведенная до предела, тотальная локализация может привести к одному огромному кластеру данных, напоминающему файловую систему 3BSD. Глобальные политики пытаются сбалансировать две противоречивые цели: локализа- локализация данных, доступ к которым осуществляется одновременно, и в то же время распре- распределение не связанных между собой данных. Одним из распределяемых ресурсов являются inode. Доступ к inodeM файлов в одном и том же каталоге часто происходит совместно. Например, команда отображе- отображения каталогов Is может получить доступ к inode каждого файла в каталоге. Политика размещения inode пытается поместить все inode файлов в каталоге в одну группу ци- цилиндров. Чтобы обеспечить распределение файлов по всей файловой системе, система использует другую политику для выделения inode каталогов. Когда каталог создается в корне файловой системы, он помещается в группу цилиндров с числом свободных
438 Глава 8. Локальные файловые системы блоков и inode, больших среднего значения, и с наименьшим числом каталогов. Целью данной политики является обеспечение успешности кластеризации inode в большинст- большинстве случаев. Когда каталог создается ниже по дереву, он помещается в группу цилин- цилиндров с числом свободных блоков и inode, больших среднего значения, рядом со своим родительским каталогом. Целью этой политики является уменьшение расстояния, ко- которое должны пройти обходящие дерево приложения, когда они перемещаются от ка- каталога к каталогу в глубоком первом поиске, в то же время обеспечивая успешность кластеризации inode в большинстве случаев. Файловая система выделяет inode в группе цилиндров, используя стратегию первого свободного. Хотя этот метод выделяет inode в группе цилиндров случайно, он поддержи- поддерживает наименьшее возможное число блоков inode. Даже когда в группе цилиндров вьщелены все возможные inode, доступ к ним может быть осуществлен с помощью от 10 до 20 дис- дисковых пересылок. Данная стратегия выделения накладывает на число дисковых пересы- пересылок, необходимых для доступа к inode всех файлов в каталоге, небольшую и постоян- постоянную верхнюю границу. В отличие от этого файловой системе 3BSD обычно требуется одна дисковая пересылка для получения inode для каждого файла в каталоге. Другим значительным ресурсом являются блоки данных. Доступ к блокам данных для файла обычно осуществляется совместно. Процедуры политики пытаются поме- помещать блоки данных для файла в одну и ту же группу цилиндров, преимущественно рас- располагая их рядом. Проблема с выделением блоков данных в той же группе цилиндров в том, что большие файлы быстро используют доступное пространство, вызывая выход за его пределы в другие области. Более того, использование всего пространства застав- заставляет также разделять на другие области будущие выделения для любого файла в группе цилиндров. В идеале ни одна из групп цилиндров не должна когда-либо заполняться полностью. Выбрана эвристика для перенаправления выделений блоков в другие группы цилиндров после распределения каждых нескольких мегабайтов. Точки перехода за границы предназначены для того, чтобы форсировать перенаправле- перенаправление выделения блоков, когда любой файл использовал примерно 25 процентов блоков данных в группе цилиндров. В повседневном использовании эвристика, похоже, хорошо работает для минимизации числа полностью заполненных групп цилиндров. Хотя эта эвристика кажется предоставляющей преимущества небольшим файлам за счет больших файлов, на самом деле она помогает обоим размерам файлов. Помощь небольшим файлам осуществляется за счет того, что почти всегда в группе цилиндров для них есть доступные для использования блоки. Большие файлы извлекают выгоду, поскольку они могут использовать непрерывное пространство, доступное в группе ци- цилиндров, и продолжить, пропуская блоки, разбросанные вокруг группы цилиндров. Хотя эти разбросанные блоки хороши для небольших файлов, которым нужен лишь блок или два, они замедляют большие файлы, которые лучше хранить в одной большой группе блоков, которую можно прочесть за несколько оборотов диска.
8.9. Быстрая файловая система Беркли 439 Вновь выбранная группа цилиндров для выделения блока является следующей группой цилиндров, в которой число оставшихся свободных блоков больше среднего. Хотя большие файлы имеют тенденцию распространяться по диску, обычно доступны несколько мегабайт до того, как необходим поиск в новой группе цилиндров. Таким образом, время для осуществления одного большого поиска невелико по сравнению со временем, потраченным на осуществление ввода/вывода в новой группе цилиндров. Механизмы выделения Процедуры глобальной политики вызывают процедуры локальной политики с запро- запросами определенных блоков. Процедуры локального выделения всегда выделяют запро- запрошенный блок, если он свободен; в противном случае они выделят свободный блок за- затребованного размера, который ближе всего к запрошенному блоку. Если бы у глобаль- глобальных политик размещения была полная информация, они всегда могли бы запросить не использующиеся блоки, и процедуры выделения были бы уменьшены для упрощения учета. Однако поддержание полной информации дорого; поэтому политики глобально- глобального размещения используют эвристики, основывающиеся на частичной информации, которая доступна. Если запрошенный блок недоступен, локальный распределитель использует стра- стратегию трехуровневого выделения. 1. Используется следующий доступный блок, ближайший к запрошенному блоку в той же самой группе цилиндров. 2. Если группа цилиндров заполнена, квадратично хешируется номер группы цилин- цилиндров, чтобы выбрать другую группу цилиндров для поиска свободного блока. Квадратичный хеш используется из-за своей скорости в поиске неиспользуемых слотов в почти полных хеш-таблицах [Knuth, 1975]. Файловые системы, которые параметризированы для поддержки по крайней мере 8 процентов свободного про- пространства, редко нуждаются в использовании этой стратегии. Файловые системы, использующиеся без свободного пространства, обычно имеют так мало доступных свободных блоков, что почти любое выделение является случайным; наиболее важной особенностью стратегии, использующейся при таких условиях, является то, чтобы она была быстрой. 3. Применяется исчерпывающий поиск во всех группах цилиндров. Этот поиск необ- необходим, поскольку квадратичное рехеширование может не проверить все группы цилиндров. Задача по управлению выделением блоков и фрагментов решается ffs_balloc(). Если файл записывается, а указатель на блок равен нулю или указывает на фрагмент, который слишком маленький, чтобы сохранить дополнительные данные, ffs_balloc() вызывает процедуры выделения, чтобы получить новый блок. Если файл должен быть расширен, имеет место одно из двух условий.
440 Глава 8. Локальные файловые системы 1. Файл не содержит фрагментированных блоков (и последний блок в файле содержит недостаточно места для сохранения новых данных). Если есть пространство в уже выделенном блоке, оно заполняется новыми данными. Если оставшаяся часть новых данных состоит более чем из заполненного блока, выделяется целый блок, и первый полный новыми данными блок записывает туда. Этот процесс повторяется до тех пор, пока размер оставшихся данных не станет меньше размера полного блока. Если оставшиеся новые данные для записи имеют размер меньше блока, находится блок с нужным числом фрагментов; в противном случае находится полный блок. В выде- выделенное пространство записываются оставшиеся новые данные. Однако чтобы избе- избежать излишнего копирования медленно увеличивающихся файлов, файловая система разрешает ссылаться на фрагменты лишь прямым блокам файлов. ffs_balloc{) |—>] ffs_blkpref{) | Политика размещения I | ffs_realloccg{) |—+\ ffsjragextend()| Расширение фрагмента ffs_alloc() Выделение нового блока или фрагмента \ffs_hashalloc{) | Нахождение группы цилиндра ± ffs_alloccg() ± Выделение фрагмента \ffs_alloccgb 1к()\ Выделение блока Рис. 8.33. Процедурный интерфейс выделения блока 2. Файл содержит один или более фрагментов (и фрагменты содержит недостаточно места для сохранения новых данных). Если размер новых данных плюс размер данных, уже находящихся в фрагментах, превышает размер полного блока, выде- выделяется новый блок. Содержимое фрагментов копируется в начало блока, а остав- оставшаяся часть блока заполняется новыми данными. Затем процесс продолжается, как на шаге 1. В противном случае выделяется набор фрагментов, достаточно больших для сохранения данных; если свободно достаточное пространство в оставшейся части текущего блока, файловая система может избежать копирования, использо- использовав этот блок. Содержимое существующих фрагментов с добавленными новыми данными записывается в выделенное пространство. ffs_balloc() отвечает также за выделение блоков для сохранения косвенных указа- указателей. Она должна также иметь дело с особым случаем, когда процесс устанавливает указатель за концом файла и начинает запись. Из-за того ограничения, что фрагментом может быть лишь последний блок файла, ffs_balloc() должна сначала убедиться, что все предыдущие фрагменты были дополнены до полного блока.
8.9. Быстрая файловая система Беркли 441 По завершении успешного выделения процедуры выделения возвращают блок, номер блока или фрагмента, который должен использоваться; затем ffs_balloс() обнов- обновляет в inode указатель на соответствующий блок. Выделив блок, система готова к вы- выделению буфера для сохранения содержимого блока таким образом, чтобы блок можно было записать на диск. Процедурное описание процесса выделения показано на рис. 8.33. ffs_balloc() яв- является процедурой, ответственной за определение того, когда должен быть выделен новый блок. Она сначала выделяет процедуру политики размещения ffs_blkpref(), чтобы выбрать наиболее желательный блок, основываясь на предпочтениях от проце- процедур глобальной политики, которые были описаны ранее в данном разделе. Если фраг- фрагмент уже был выделен и должен быть расширен, ffs_balloc() вызывает ffs_realloccg(). Если пока ничего не было выделено, ffs_balloc() вызывает ffs_alloc(). ffs_realloccg() сначала пытается расширить текущий фрагмент на месте. Рас- Рассмотрите пример блока карты выделения с двумя выделенными из нее фрагментами, показанный на рис. 8.34. Первый фрагмент можно расширить от фрагмента размера 2 до фрагмента размера 3 или 4, поскольку два смежных фрагмента не используются. Второй фрагмент не может быть расширен, поскольку он занимает конец блока, а фраг- фрагментам не разрешается перекрывать блоки. Если ffs_realloccg() может расширить теку- текущий фрагмент на месте, карта соответствующим образом обновляется и функция возвращается. Если фрагмент не может быть расширен, ffs_realloccg() вызывает про- процедуру ffs_alloc(), чтобы получить новый фрагмент. Старый фрагмент копируется в начало нового фрагмента, а старый фрагмент освобождается. Ээлемент в таблице Выделено фрагментов 1 -- 1 1 --- Размер 2 Размер 3 Рис. 8.34. Пример блока с двумя выделенными фрагментами Задачи учета выделений выполняются ffs_alloc(). Она сначала проверяет, что блок доступен в нужной группе цилиндров, проверив общую информацию файловой систе- системы. Если общая информация показывает, что группа цилиндров заполнена, ffs_alloc() просматривает общую информацию о группах цилиндров с помощью квадратичного хеширования в поиске группы цилиндров со свободным пространством. Найдя группу цилиндров с пространством,^_а//ос(9 вызывает либо процедуру выделения фрагмен- фрагментов, либо процедуру выделения блока, чтобы запросить фрагмент или блок. Процедуре выделения блоков указывается предпочтительный блок. Если этот блок доступен, он возвращается. Если блок недоступен, процедура выделения пытается найти другой блок в той же группе цилиндров, который близок к запрошенному блоку Она ищет доступный блок, просматривая карты свободных блоков, начиная с запро- запрошенного места, до тех пор, пока не найдет доступный блок.
442 Глава 8. Локальные файловые системы Процедуре выделения фрагментов указывается предпочтительный фрагмент. Если этот фрагмент доступен, он возвращается. Если запрошенный фрагмент недоступен, а файловая система сконфигурирована для оптимизации использования пространства, файловая система использует для выделения фрагмента стратегию наилучшего согла- согласия. Процедура выделения фрагмента проверяет общую информацию группы цилин- цилиндров, начиная с элемента для нужного размера и сканируя большие размеры до тех пор, пока не будет найден доступный фрагмент. Если фрагментов подходящего или большего размера нет, выделяется полный блок и делится на части. Биты в карте Ддесятичное значение -111-11 115 Рис. 8.35. Элемент карты для файловой системы 8192/1024 Если в сводке фрагмента перечислен фрагмент подходящего размера, процедура выделения ожидает найти его в карте выделения. Чтобы ускорить процесс сканирова- сканирования потенциально больших карт выделения, файловая система использует алгоритм, управляемый таблицей. Каждый байт в карте считается индексом в таблицу дескрип- дескрипторов фрагментов. Каждый элемент в таблице дескрипторов фрагментов описывает фрагменты, которые свободны для соответствующего элемента карты. Таким образом, выполняя логическую операцию И с битом, соответствующим нужному размеру фраг- фрагмента, распределитель может быстро определить, содержится ли нужный фрагмент в данном элементе карты распределения. В качестве примера рассмотрите элемент из карты распределения для файловой системы 8192/1024, показанный на рис. 8.35. Пока- Показанный элемент карты уже был фрагментирован, с единственным фрагментом, выде- выделенным в начале, и фрагментом размера 2, выделенным в середине. Оставшимся без использования является еще один фрагмент размера 2 и фрагмент размера 3. Таким образом, если мы ищем элемент 115 в таблице фрагментов, мы найдем элемент, пока- показанный на рис. 8.36. Если бы мы искали фрагмент размера 3, мы проверили бы третий бит и нашли бы, что поиск был успешным; если бы мы искали фрагмент размера 4, мы проверили бы четвертый бит и обнаружили, что нам нужно продолжить. Код С с реа- реализацией этого алгоритма следующий. for (i =0; i < MAPSIZE; i++) if (fragtbl[allocmapfi]] & A « (size - 1))) break; Элемент в таблице Доступный размер фрагмента 0 0 0 0 110 7 6 5 4 3 2 1 Рис. 8.36. Элемент таблицы фрагментов для элемента 115
8.9. Быстрая файловая система Беркли 443 Использование политики лучшего соответствия имеет преимущество минимиза- минимизации фрагментации диска; однако у нее есть нежелательное свойство, заключающееся в максимизации числа копирований из фрагмента во фрагмент, которые нужно делать, когда процесс записывает в файл множество небольших частей. Чтобы избежать такого поведения, система может конфигурировать файловые системы для оптимизации по времени, а не по пространству. В первый раз, когда процесс осуществляет небольшую запись в файловую систему, оптимизированную по времени, она выделяет наиболее подходящий фрагмент. Однако при второй небольшой записи выделяется блок полного размера, неиспользующаяся часть которого освобождается. Позже небольшие записи могут расширять фрагмент на месте, а не требовать дополнительные операции копиро- копирования. При определенных обстоятельствах эта политика может вызвать сильную фраг- фрагментацию диска. Система отслеживает это условие и автоматически возвращается к оптимизации по пространству, если процент фрагментации достигает половины пре- предела минимального свободного пространства. Кластеризация блоков Большинство машин, на которых работает FreeBSD, не имеют отдельных процессоров ввода/вывода. Центральный процессор должен обрабатывать прерывание после каждой операции дискового ввода/вывода; если должен быть сделан дополнительный ввод/вывод, он должен выбрать следующий буфер для передачи и начать операцию с этим буфером. До появления контроллеров с кешированием дорожек файловые сис- системы достигали наивысшей производительности, оставляя промежуток после каждого блока, чтобы позволить запланировать время для следующей операции ввода/вывода. Если бы блоки были расположены без пробелов, производительность пострадала бы, поскольку диску пришлось сделать почти полный оборот, чтобы попасть на начало следующего блока. Контроллеры с кешированием дорожек имеют большой буфер, который продолжа- продолжает собирать данные, поступающие с диска, даже после получения запрошенных дан- данных. Если следующий запрос для блока, идущего непосредственно после данного, у контроллера в буфере уже будет большинство блоков, поэтому ему не придется ждать оборота, чтобы получить блок. Таким образом, для целей чтения можно при- примерно удвоить производительность файловой системы, располагая файлы непрерыв- непрерывно, а не оставляя промежутки после каждого блока. К сожалению, кеш дорожек менее полезен для записи. Поскольку ядро не пре- предоставляет следующий блок данных до тех пор, пока не завершится запись предыду- предыдущего, по-прежнему есть задержка, во время которой у контроллера нет данных для записи и он ожидает оборота, чтобы попасть в начало следующего блока. В качестве одного из решений для данной проблемы можно заставить контроллер вызвать преры- прерывание после копирования данных в свой кеш, но до завершения его записи. Это раннее
444 Глава 8. Локальные файловые системы прерывание дает процессору время запросить следующий ввод/вывод до того, как за- завершится предыдущий, предоставляя тем самым непрерывный поток данных для записи на диск. У этого подхода есть один серьезный негативный побочный эффект. Когда достав- доставляется прерывание завершения ввода/вывода, ядро ожидает, что данные находятся в надежном хранилище. Целостность файловой системы и приложения пользователя, использующие системный вызов fsync, зависят от этой семантики. Эта семантика будет нарушена, если возникнет сбой питания после прерывания завершения ввода/вывода, но до записи данных на диск. Некоторые производители устраняют эту проблему, используя для кеша контроллера энергонезависимую память и предоставляя после сбоя питания повторный запуск микрокода для определения того, какие операции должны быть завершены. Поскольку эта опция дорогая, эту возможность предостав- предоставляют немногие контроллеры. Более новые диски разрешают эту проблему с помощью методики, которая назы- называется помеченной очередью (tag queueing). С помеченной очередью каждому запросу, передаваемому драйверу диска, назначается уникальный числовой тег. Большинство дисковых контроллеров, поддерживающих помеченные очереди, примут по крайней мере 16 ожидающих запросов ввода/вывода. После завершения каждого запроса тег выполненного запроса возвращается как часть прерывания завершения. Если контрол- контроллеру диска представлено несколько смежных блоков, он может начать работать над следующим, пока возвращается тег для предыдущего. Таким образом, очереди тегов позволяют точно уведомлять приложения о том, когда их данные достигают стабиль- стабильного хранилища, не вызывая проблем с оборотами диска при записи смежных блоков. Чтобы максимизировать производительность на системах без очередей тегов или энергонезависимой памятью контроллера, система FreeBSD реализует кластеризацию ввода/вывода. Кластеризация помогает повысить производительность на системах, снизив число запросов ввода/вывода путем объединения множества небольших запро- запросов в небольшое число более крупных. Кластеризация впервые была осуществлена фирмами Santa Cruz Operations [Peacock, 1988] и Sun Microsystems [McVoy & Kleiman, 1991]; идея была в дальнейшем приспособлена для 4.4BSD и соответственно для FreeBSD [Seltzer et al., 1993]. Когда файл записывается, процедуры выделения пытают- пытаются выделить вплоть до maxcontig (обычно 128 Кбайт) данных в смежных блоках диска. Вместо того чтобы записывать содержащие их буферы по мере их заполнения, вывод откладывается. Кластер заполняется, когда достигается предел maxcontig данных, файл закрывается или кластер не может расти из-за того, что следующий смежный блок на диске уже используется другим файлом. Если размер кластера ограничен пре- предыдущим выделением другому файлу, файловая система уведомляется и ей предостав- предоставляется возможность найти другой набор смежных блоков, в который можно поместить кластер. Если перераспределение успешно, кластер продолжает расти. Когда кластер
8.9. Быстрая файловая система Беркли 445 завершен, буферы, составляющие кластер блоков, объединяются и передаются кон- контроллеру диска в виде одного запроса ввода/вывода. Затем данные могут быть перене- перенесены на диск одним непрерываемым перемещением. Аналогичная схема используется для чтения. Если ffs_read() обнаруживает, что файл читается последовательно, она изучает число смежных блоков, возвращенных ufs_bmap(), чтобы найти кластеры непрерывно выделенных блоков. Затем она выделя- выделяет набор буферов, достаточно больших для хранения непрерывных наборов блоков, и передает их контроллеру диска в одном запросе ввода/вывода. Затем ввод/вывод можно осуществить в одну операцию. Хотя кластеризация чтения не нужна, когда дос- доступны контроллеры с кешами дорожек, она уменьшает загрузку системы прерывания- прерываниями, в которых они есть, и ускоряет недорогие системы, в которых их нет. Чтобы кластеризация была эффективной, файловая система должна иметь возмож- возможность выделять файлам большие кластеры смежных блоков. Если бы система все время пыталась начать выделение для файла в начале большого набора смежных бло- блоков, она вскоре израсходовала бы непрерывное пространство. Вместо этого она ис- использует алгоритм, аналогичный использующемуся для управления фрагментами. Вначале через стандартный алгоритм, описанный в предыдущих двух подразделах, выделяются блоки. Когда стандартный алгоритм не приводит к смежному выделению, осуществляется перераспределение. Код перераспределения просматривает карту кла- кластеров, в которой обобщены доступные кластеры блоков в группе цилиндров. Он вы- выделяет первый свободный кластер, который достаточно велик для сохранения файла, а затем перемещает файл в это непрерывное пространство. Этот процесс продолжает- продолжается до тех пор, пока текущее выделение не достигает размера, равного максимально допустимой операции ввода/вывода (maxcontig). В этот момент ввод/вывод сделан, и процесс выделения пространства начинается сначала. В отличие от перераспределения фрагментов, перераспределение блоков раз- различным кластерам блоков не требует дополнительного ввода/вывода или копирования из памяти в память. Данные для записи хранятся в отложенных буферах записи. Внутри этого буфера есть место в диске, куда должны быть записаны данные. Когда кластер блока перемещается, обход списка буферов в кластере и изменение адресов диска, по которому они должны быть записаны, занимает мало времени. Когда имеет место ввод/вывод, конечное место расположения было выбрано и не будет изменено. Чтобы ускорить работу по обнаружению кластеров блоков, файловая система под- поддерживает карту кластеров с 1 битом на блок (вдобавок к карте с 1 битом на фрагмент). У нее есть также обобщенная информация, показывающая, сколько наборов блоков есть для каждого возможного размера кластера. Обобщенная информация позволяет избежать поиска размеров кластеров, которые не существуют. Карта кластеров исполь- используется, потому что сканировать ее можно быстрее, чем гораздо большую битовую карту фрагментов. Важен размер карты, поскольку карту нужно просматривать бит за битом. В отличие от фрагментов кластеры блоков не ограничены необходимостью
446 Глава 8. Локальные файловые системы выравнивания внутри карты. Поэтому для поиска кластеров не может использоваться оптимизация поиска в таблице, сделанная для фрагментов. Файловая система полагается на выделение смежных блоков для достижения высоких уровней производительности. Фрагментация свободного пространства может увеличиться со временем или с использованием файловой системы. Эта фрагментация может снизить производительность с возрастом файловой системы. Результаты использования и старения были измерены в Гарвардском университете на более чем 50 файловых системах. Возраст проверяемых файловых систем с момента начального создания изменялся от одного до трех лет. Фрагментация свободного пространства на большинстве проверенных файловых систем вызывала уменьшение производительно- производительности не более чем на 10 процентов по сравнению с вновь созданной пустой файловой системой. Самым серьезным измеренным снижением было 30 процентов для высоко- высокоактивной файловой системы, в которой было множество небольших файлов и которая использовалась для буферирования новостей USENET [Seltzer et al., 1995]. Выделение, основанное на экстентах С добавлением динамического перераспределения блоков в начале 1990-х гг. [Seltzer & Smith, 1996] у файловой системы UFS1 появилась возможность размещать большинство файлов на диске рядом. Метаданные, описывающие большой файл, состоят из косвен- косвенных блоков с длинными последовательностями номеров блоков, см. рис. 8.37(а). Для быстрого доступа, пока файл активен, ядро пытается хранить все метаданные файла в памяти. С UFS2 пространство, необходимое для хранения метаданных для файла, удвоилось, поскольку каждый указатель на блок увеличился с 32 до 64 битов. Чтобы предоставить более компактное представление, многие файловые системы используют представление, основанное на экстентах (extents). Типичное, основанное на экстентах представление использует пары номеров блоков и размеров. На рис. 8.37(Ь) представ- представлен тот же самый набор номеров блоков, как на рис. 8.37(а), в формате, основанном на экстентах. Если файл можно разместить почти непрерывно, это представление предос- предоставляет компактное описание. Однако случайно или медленно записанные файлы могут остаться с множеством несмежных выделенных блоков, которые создадут пред- представление, для которого потребуется больше места, чем для использующегося UFS1. Это представление имеет также тот недостаток, что для него может потребоваться множество вычислений для осуществления произвольного доступа к файлу, поскольку номер блока должен быть вычислен путем добавления размеров, начинающихся от начала файла и пока не будет достигнуто нужное смещение. Чтобы получить наибольшую эффективность протяженности без неэффективно- неэффективности произвольного доступа, UFS2 добавила в inode поле, которое позволит этому inode использовать больший размер блока. Небольшие, медленно растущие или разбросан- разбросанные файлы устанавливают в качестве этого значения обычный размер блока файловой системы и представляют свои данные традиционным способом, показанным на
Упражнения 447 ) 24| 25J 2б| 27| 32| 33| 34| 35|3б| 37J 38| 39| '" (а) - традиционное кодирование ••• 1 4 |12| 4 \2Л\ 8 |32[~^« (Ь) - традиционное кодирование протяженности <размер, блок> ••• |12|24|32|3б|««« (с) - смешанное кодирование протяженности Рис. 8.37. Альтернативное представление метаданных файла рис. 8.37(а). Однако когда файловая система обнаруживает большой, плотный файл, она может установить в этом поле размера блока inode значение, от 2 до 16 раз большее размера блока файловой системы. На рис. 8.37(с) представлен тот же самый набор но- номеров блоков, как на рис. 8.37(а), с полем размера блока inode в 4 раза большим раз- размера блока файловой системы. Каждый указатель на блок ссылается на участок диско- дискового хранилища, который в четыре раза больше, что уменьшает требования к хранили- хранилищу метаданных на 75 процентов. Поскольку указатель на каждый блок, кроме, возмож- возможно, последнего, ссылается на блоки одинакового размера, вычисление смещений произвольного доступа так же быстро, как при традиционном представлении метадан- метаданных. В отличие от традиционного основанного на экстентах представления, которое может удвоить необходимое для метаданных пространство для определенных наборов данных, это представление всегда ведет к меньшему пространству, выделяемому для метаданных. Недостатком данного подхода является то, что, если зафиксировано использование файлом блоков большего размера, он может использовать блоки только этого размера. Если файловая система исчерпает большие блоки, тогда файл не может больше расти и приложение либо получит ошибку «недостаточно памяти», либо файловой системе придется повторно создать метаданные со стандартным размером блока файловой сис- системы. В настоящее время планируется написать код для повторного создания метадан- метаданных. Хотя повторное создание метаданных обычно будет вызывать длинную паузу, мы ожидаем, что это условие будет редким и не станет заметной проблемой при практиче- практическом использовании. Упражнения 8.1. Каковы семь системой? 8.2. В чем назначение структуры данных inode? 8.1. Каковы семь классов операций, осуществляемых иерархической файловой системой?
448 Глава 8. Локальные файловые системы 8.3. Как система выбирает замещение inode, когда с диска должен быть считан новый inode? 8.4. Почему элементам каталогов не разрешается перекрывать порции каталога? 8.5. Опишите шаги, вовлеченные в поиск компонента имени пути. 8.6. Почему прямым ссылкам не разрешается перекрывать файловые системы? 8.7. Опишите, как интерпретация символической ссылки, содержащей абсолютное имя пути, отличается от символической ссылки, содержащей относительное имя пути. 8.8. Объясните, почему непривилегированным пользователям не разрешается делать прямые ссылки на каталоги, но разрешается делать символические ссылки на каталоги. 8.9. Как могут использоваться прямые ссылки для получения доступа к файлам, к которым нельзя было бы получить доступ, если бы использовались сим- символические ссылки? 8.10. Как система распознает циклы, вызванные символическими ссылками? Предложите альтернативную схему для обнаружения циклов. 8.11. Чем квоты отличаются от ограничений ресурса размеров файлов, описанных в разделе 3.8? 8.12. Как ядро определяет, есть ли ассоциированная с файлом квота? 8.13. Нарисуйте картинку, изображающую результат обработки запроса процессом 1 исключительной блокировки байтов с 7-го по 10-й для списка блокировок, показанных на рис. 8.15. Какие из случаев перекрывания на рис. 8.14 применяются в данном примере? 8.14. В отсутствие мягких обновлений какие три операции FFS должны осуществ- осуществляться синхронно, чтобы гарантировать, что файловая система всегда будет детерминистически восстановлена после аварии (исключая невосстановимые аппаратные ошибки)? 8.15. Каковы гарантии, предоставляемые системным вызовом fsyncl 8.16. Назовите три ограничения упорядочивания, которые должны поддерживаться при удалении файла. Опишите, как мягкие обновления поддерживают это упорядочивание. 8.17. Приведите три применения для моментальных снимков файловой системы. 8.18. Опишите восемь шагов, необходимых для изготовления моментального снимка файловой системы. 8.19. Каковы три состояния, в которых может находиться блок в моментальном снимке? Опишите действия, предпринимаемые моментальным снимком для каждого из этих состояний, когда осуществляется запись. Опишите действия, предпринимаемые моментальным снимком для каждого из этих состояний, когда блок освобождается. 8.20. Каковы четыре класса операций, выполняемых файловой системой храни- хранилища данных? 8.21. При каких условиях запрос на запись может избежать чтения блока с диска?
Ссылки 449 8.22. В чем различие между логическим блоком и физическим блоком? Почему это разграничение полезно? 8.23. Приведите две причины, почему увеличение базового размера блока в старой файловой системе с 512 байтов до 1024 байтов более чем удвоило производи- производительность системы. 8.24. Сколько блоков и фрагментов выделяются 31200-байтному файлу в FFS с 4096-байтными блоками и 1024-байтными фрагментами? Сколько блоков и фрагментов выделяются этому файлу в FFS с 4096-байтными блоками и 512-байтными фрагментами? Ответьте также на эти два вопроса, предполо- предположив, что inode имеет всего лишь 6 прямых указателей на блоки вместо 12. 8.25. Объясните, почему FFS поддерживает от 5 до 10 процентов резервного сво- свободного пространства. Какие проблемы могли бы возникнуть, если бы резерв свободного пространства был бы установлен в ноль? 8.26. Что такое квадратичный хеш? Опишите, для чего он используется в FFS и почему он используется для этой цели. 8.27. Почему политики выделения для inode отличаются от политик для блоков данных? 8.28. При каких условиях кластеризация блоков предоставляет преимущества, которые нельзя получить с помощью кеша дорожки диска? *8.29. Приведите пример, когда реализация блокировок файлов неспособна обнаружить потенциальную взаимоблокировку (тупик). *8.30. Какие проблемы возникли бы, если бы файлы пришлось выделять в одном непрерывном участке диска? Рассмотрите проблемы, создаваемые несколь- несколькими процессами, произвольным доступом и файлами с дырами. **8.31. Спроектируйте систему, которая позволяет снизить уровень безопасности системы в то время, когда система по-прежнему работает в многопользова- многопользовательском режиме. **8.32. Inode можно выделять динамически как часть элемента каталога. Вместо этого область выделения inode резервируется при создании системы. Почему используется последний подход? Ссылки Apple, 2003. Apple, "Mac OS X Essentials, Chapter 9 Filesystem, Section 12 Resource Forks", http:/ /developer, apple. com/techpubs/macosx/Essentials/System Overview/FileSystem/ chapter_9_section_12.html, 2003. Best & Kleikamp, 2003. S. Best & D. Kleikamp, "How the Journaled File System handles the on-disk layout", http://www-106. ibm. com/developei*works/linux/libraiy/l-jfslayout/, 2003.
450 Глава 8. Локальные файловые системы Chamberlin & Astrahan, 1981. D. Chamberlin & M. Astrahan, "A History and Evaluation of System R", Communications of the ACM, vol. 24, no. 10, pp. 632-646, 1981. Chutametal., 1992. S. Chutani, O. Anderson, M. Kazar, W. Mason, & R. Sidebotham, "The Episode File System", USENIXAssociation Conference Proceedings, pp. 43-59, January 1992. Dowse & Malone, 2002. I. Dowse & D. Malone, "Recent Filesystem Optimizations on FreeBSD", Proceedings of the Freenix Track at the 2002 Usenix Annual Technical Conference, pp. 245-258, June 2002. Elz, 1984. K. R. Elz, "Resource Controls, Privileges, and Other MUSH", USENIX Association Conference Proceedings, pp. 183-191, June 1984. Ganger, McKusick et al., 2000. G. Ganger, M. K. McKusick, С Soules, & Y. Patt, "Soft Updates: A Solution to the Metadata Update Problem in File Systems", ACM Transactions on Computer Systems, vol. 18, no. 2, pp. 127-153, May 2000. Ganger & Patt, 1994. G. Ganger & Y. Patt, "Metadata Update Performance in File Systems", USENIX Sympo- Symposium on Operating Systems Design and Implementation, pp. 49-60, November 1994. Griffin et al., 2002. J. L. Griffin, J. Schindler, S. W. Schlosser, J. S. Bucy, & G. R. Ganger, "Timing-accurate Storage Emulation", Proceedings of the Usenix Conference on File and Storage Tech- Technologies, pp. 75-88, January 2002. Hagmann, 1987. R. Hagmann, "Reimplementing the Cedar File System Using Logging and Group Commit", ACM Symposium on Operating Systems Principles, pp. 155-162, November 1987. Howard etal., 1988. J. Howard, M. Kazar, S. Menees, D. Nichols, M. Satyanarayanan, R. Sidebotham, & M. West, "Scale and Performance in a Distributed File System", ACM Transactions on Computer Systems, vol. 6, no. 1, pp. 51-81, Association for Computing Machinery, February 1988. Irlam, 1993. G. Irlam, Unix File Size Swvey—1993, http://www.base.com/gordoni/ufs93.html, email:<gordoni@home.base.com>, November 1993.
Ссылки 451 Knuth, 1975. D. Knuth, The Art of Computer Programming, Volume 3—Sorting and Searching, pp. 506-549, Addison-Wesley, Reading, MA, 1975.1 Lumb et al., 2002. C. R. Lumb, J. Schindler, & G. R. Ganger, "Freeblock Scheduling Outside of Disk Firm- Firmware", Proceedings of the Usenix Conference on File and Storage Technologies, pp. 275-288, January 2002. McKusick, 2002. M. K. McKusick, "Running Fsck in the Background", Proceedings of the BSDCon 2002 Conference, pp. 55-64, February 2002. McKusick, 2003. M. K. McKusick, "Enhancements to the Fast Filesystem to Support Multi-Terabyte Stor- Storage Systems", Proceedings of the BSDCon 2003 Conference, pp. 79-90, September 2003. McKusick et al., 1984. M. K. McKusick, W. N. Joy, S. J. Leffler, & R. S. Fabry, "A Fast File System for UNIX", A CM Transactions on Computer Systems, vol. 2, no. 3, pp. 181-197, Associa- Association for Computing Machinery, August 1984. McKusick & Kowalski, 1994. M. K. McKusick & T. J. Kow alski, "Fsck: The UNIX File System Check Program", in 4.4BSD System Manager's Manual, pp. 3:1-21, O'Reilly & Associates, Inc., Sebastopol, CA, 1994. McVoy & Kleiman, 1991. L. McVoy & S. Kleiman, "Extent-Like Performance from a Unix File System", USENIX Association Conference Proceedings, pp. 33-44, January 1991. Moranetal., 1990. J. Moran, R. Sandberg, D. Coleman, J. Kepecs, & B. Lyon, "Breaking Through the NFS Performance Barrier", Proceedings of the Spring 1990 European UNIX Users Group Conference, pp. 199-206, April 1990. Nevalainen & Vesterinen, 1977. O. Nevalainen & M. Vesterinen, "Determining Blocking Factors for Sequential Files by Heuristic Methods", The Computer Journal, vol. 20, no. 3, pp. 245-247, August 1977. Ousterhout, 1990. 1 Русский перевод: Дональд Э. Кнут, «Искусство программирования». 2-е изд., т. 3 - Сортировка и поиск. С. 549-597, М.: Вильяме, 2000. - Примеч. науч. ред.
452 Глава 8. Локальные файловые системы J. Ousterhout, "Why Aren't Operating Systems Getting Faster as Fast as Hardware?", Summer USENIX Conference, pp. 247-256, June 1990. Peacock, 1988. J. Peacock, "The Counterpoint Fast File System", USENIX Association Conference Proceedings, pp. 243-249, January 1988. Phillips, 2001. D. Phillips, "A Directory Index for Ext2", Proceedings of the Usenix Fifth Annual Linux Showcase and Conference, November 2001. Reiser, 2001. H. Reiser, "The Reiser File System", http://www.namesys.com/res_whoLshtml, January 2001. Rhodes, 2003. T. Rhodes, "FreeBSD Handbook, Chapter 3, Section 3.3 File System Access Control Lists", http://www.FreeBSD^ig/doc/enJJSJSO8859-I/books/handbook/fs-aclhtml,2003. Rosenblum & Ousterhout, 1992. M. Rosenblum & J. Ousterhout, "The Design and Implementation of a Log-Structured File System", A CM Transactions on Computer Systems, vol. 10, no. 1, pp. 26-52, Asso- Association for Computing Machinery, February 1992. Schindler et al., 2002. J. Schindler, J. L. Griffin, C. R. Lumb, & G. R. Ganger, "Track-aligned Extents: Matching Access Patterns to Disk Drive Characteristics", Proceedings of the Usenix Conference on File and Storage Technologies, pp. 259-274, January 2002. Seltzer etal., 1993. M. Seltzer, K. Bostic, M. K. McKusick, & С Staelin, "An Implementation of a Log- Structured File System for UNIX", USENIX Association Conference Proceedings, pp. 307-326, January 1993. Seltzer & Smith, 1996. M. Seltzer & K. Smith, "A Comparison of FFS Disk Allocation Algorithms", Winter USENIX Conference, pp. 15-25, January 1996. Seltzer etal., 1995. M. Seltzer, K. Smith, H. Balakrishnan, J. Chang, S. McMains, & V. Padmanabhan, "File System Logging Versus Clustering: A Performance Comparison", USENIX Association Conference Proceedings, pp. 249-264, January 1995. Stonebraker, 1987.
Ссылки 453 М. Stonebraker, "The Design of the POSTGRES Storage System", Very Large Data- DataBase Conference, pp. 289-300, 1987. Sweeney et al., 1996. A. Sweeney, D. Doucette, C. Anderson, W. Ни, М. Nishimoto, & G. Peck, "Scalability in the XFS File System", Proceedings of the 1996 Usenix Annual Technical Conference, pp. 1-14, January 1996. Trivedi, 1980. K. Trivedi, "Optimal Selection of CPU Speed, Device Capabilities, and File Assign- Assignments", JournaloftheACM, vol. 27, no. 3, pp. 457-473, July 1980. Watson, 2000. R. Watson, "Introducing Supporting Infrastructure for Trusted Operating System Sup- Support in FreeBSD", Proceedings of the BSDCon 2000 Conference, September 2000. Watson, 2001. R. Watson, "TrustedBSD: Adding Trusted Operating System Features to FreeBSD", Proceedings of the Freenix Track at the 2001 Usenix Annual Technical Conference, pp. 15-28, June 2001. Watson et al., 2003. R. Watson, W. Morrison, C. Vance, & B. Feldman, "The TrustedBSD MAC Framework: Extensible Kernel Access Control for FreeBSD 5.0", Proceedings of the Freenix Track at the 2003 Usenix Annual Technical Conference, pp. 285-296, June 2003. Wu & Zwaenepoel, 1994. M. Wu & W. Zwaenepoel, "eNVy: A Non-Volatile, Main Memory Storage System", International Conference on Architectural Support for Programming Languages and Operating Systems (ASPLOS), pp. 86-97, October 1994.P
Глава 9 Сетевая файловая система Данная глава разделена на три главных раздела. В первом приводится краткая история удаленных файловых систем. Во втором описываются клиентская и серверная части NFS и механизмы их работы. В последнем разделе описываются методики, необходи- необходимые для обеспечения приемлемой производительности для удаленных файловых систем вообще и NFS в частности. 9.1. История и обзор Когда использование сетей впервые стало широко доступным в 4.2BSD, пользовате- пользователям, желавшим разделять файлы, приходилось регистрироваться по сети на основной машине, на которой размещались разделяемые файлы. Эти основные машины быстро стали значительно более загруженными, чем локальные машины пользователей, поэтому быстро возникла необходимость в удобном способе разделять файлы на не- нескольких машинах одновременно. Наиболее простой для понимания моделью совмест- совместного использования является модель, которая позволяет машине сервера экспортиро- экспортировать свои файловые системы на одну или более клиентских машин. Затем клиенты могут импортировать эти файловые системы и представить их пользователю, как если бы они были просто еще одной локальной файловой системой. Были предложены и реализованы многочисленные проекты протоколов и протоко- протоколы удаленных файловых систем. Предпринимались попытки реализаций на всех уров- уровнях ядра. Удаленный доступ в верхней части ядра приводил к семантике, которая почти совпадала с локальной файловой системой, но имела ужасную производительность. Удаленный доступ в нижней части ядра приводил к ужасной семантике, но большой производительности. Современные системы помещают удаленный доступ в середину ядра, на уровень vnode. Этот уровень дает приемлемую производительность и прием- приемлемую семантику.
9.1. История и обзор 455 Ранняя удаленная файловая система UNIX United («Объединенный UNIX») была реализована вблизи вершины ядра на уровне организации системных вызовов. Она проверяла файловые дескрипторы, представлявшие удаленные файлы, и отсылала запросы по этим дескрипторам на сервер. На клиентской машине кеширование не про- производилось. Отсутствие кеширования приводило к низкой производительности, но семантика была почти идентична локальной файловой системе. Поскольку на текущий каталог и существующие файлы внутренне ссылаются vnode, а не дескрипторы, UNIX United не позволяла пользователям изменять каталог в удаленной файловой системе и не могла выполнять файлы из удаленной файловой системы без их предварительного копирования в локальную файловую систему. Противоположной крайностью был сетевой диск Sun Microsystem, реализованный в нижней части ядра на уровне драйверов устройств. В этом случае использовался весь код файловой системы и буферирования клиента. Так же, как в локальной файловой системе, недавно прочитанные с диска блоки сохранялись в кеше страниц. Лишь когда доступ к файлу требовал блок, который еще не находился в кеше, клиент посылал запрос для нужного физического дискового блока на сервер. Производительность была отличной, поскольку кеш обслуживал большинство запросов доступа к файлу точно так же, как для локальной файловой системы. К сожалению, страдала семантика из-за некогерентности между кешами клиента и сервера. Изменения, сделанные на сервере, могли быть не видны клиенту и наоборот. В результате сетевой диск мог быть исполь- использован лишь одним клиентом или в качестве файловой системы только для чтения. Первой файловой системой, поставляемой с System V, была RFS [Rifkin et al., 1986]. Хотя она имела прекрасную семантику UNIX, ее производительность была пло- плохой, поэтому она использовалась редко. Исследования в Carnegie-Mellon привели к файловой системе Эндрю (Andrew) [Howard, 1988]. Файловая система Эндрю была переведена на коммерческую основу Transarc и в конечном счете стала частью среды распределенных вычислений (Distributed Computing Environment), пропагандируемой Фондом открытого программного обеспечения (Open Software Foundation), и была под- поддержана многими производителями. Она спроектирована для управления глобально распределенными серверами и клиентами, а также для хорошей работы с мобильными компьютерами, которые работают в отключенном от сети режиме в течение длитель- длительных периодов времени. Она не получила широкого коммерческого использования, но продолжает служить в качестве исследовательского средства. В семействе операционных систем Microsoft доступ к удаленной файловой систе- системе предоставляется Общей файловой системой Интернета {Common Internet File System - CIFS), которая работает поверх блоков серверных сообщений (Server Message Block - SMB) [SNIA, 2002]. В FreeBSD поддержка клиентов и серверов SMB и CIFS предоставляется Samba, которая находится в /usr/ports/net/samba. Поскольку данная книга имеет дело с ядром, a Samba работает преимущественно вне ядра, мы не будем ее больше обсуждать.
456 Глава 9. Сетевая файловая система Наиболее коммерчески успешным и широко доступным протоколом удаленной файловой системы является сетевая файловая система (network filesystem - NFS), первоначально разработанная и реализованным Sun Microsystems [Sandberg et al., 1985; Walsh et al., 1985]. В успехе NFS есть две важные составляющие. Во-первых, Sun сделала спецификацию протокола для NFS всеобщим достоянием (public domain). Во-вторых, Sun продает ее реализацию любому, кто хочет, по цене меньшей, чем стоимость ее самостоятельной реализации. Поэтому большинство производителей выбрали покуп- покупку реализации Sun. Они захотели купить реализацию Sun, поскольку знали, что всегда могут легально создать свою собственную реализацию, если цена реализации Sun не- необоснованно поднимется. Реализация 4.4BSD была написана по спецификации прото- протокола, а не содержала в себе реализацию Sun, поскольку разработчики хотели ее свобод- свободно распространять в виде исходного кода. Первой широко выпущенной реализацией NFS была версия 2 Sun в 1984 г. Хотя выпуск версии 3 ожидался спустя год или два после версии 2, она прошла через несколько сильно усложненных промежуточных версий, прежде чем эти последова- последовательные улучшения версии 2 были выпущены в конечном счете в 1992 г. Финальный выпуск 4.4BSD включал реализацию NFS, которая поддерживала версии 2 и 3. Реали- Реализация NFS FreeBSD является прямым потомком кода, выпущенного в 4.4BSD. Измене- Изменения включали исправления ошибок и улучшения производительности наряду с сохра- сохранением его работы со все развивающимся интерфейсом vnode. Хотя версии NFS 2 и 3 были сделаны всецело внутри Sun, все возрастающее число компаний, предоставляющих основанные на NFS продукты, оказывают возрастающее давление на Sun, чтобы допустить других в разработку NFS версии 4. После больших политических маневрирований Sun согласилась передать ответственность за опреде- определение спецификации NFS версии 4 Проблемной группе проектирования Интернета (Internet Engineering Task Force - IETF). Версия 4 значительно расширяет функцио- функциональные возможности более ранних версий NFS. Она включает проверку монтирова- монтирования и блокирование, которые должны осуществляться отдельными демонами, рабо- работающими со своими протоколами. Расширена семантика атрибутов и доступа к фай- файлам, чтобы проще взаимодействовать с другими важными протоколами, такими, как CIFS. На время публикации1 B004 г.) версия 4 спецификации NFS по-прежнему нахо- находилась в стадии чернового стандарта [Shepher et al., 2003]. Есть люди, активно рабо- работающие над созданием реализации версии 4 NFS для BSD, но для FreeBSD эта попытка пока не предпринималась. В оставшейся части данной главы исследуется версия 3 NFS, которая развернута в настоящее время в FreeBSD. NFS была спроектирована как клиент-серверное приложение. Ее реализация раз- разделена на клиентскую часть, которая импортирует файловые системы из других машин, и серверную часть, которая экспортирует локальные файловые системы на другие машины. Общая модель представлена на рис. 9.1. В FreeBSD ядро может быть Оригинального издания данной книги. - Примеч. науч. ред.
9.1. История и обзор 457 сконфигурировано для поддержки только клиента, только сервера или и сервера, и кли- клиента. В дизайн NFS было включено множество целей. * Протокол спроектирован как не имеющий состояний. Поскольку нет состояния, которое нужно поддерживать или восстанавливать, NFS может продолжать рабо- работать даже во время сбоя клиента или сервера. Таким образом, она более устойчива, чем система, работающая с состояниями. NFS спроектирована для поддержки семантики файловой системы UNIX. Однако ее дизайн позволяет ей также поддерживать возможно менее богатую семантику других типов файловых систем, таких, как MS-DOS. Дисковое хранилище Сервер Сеть Клиент Клиент Клиент Клиент Клиент Рис. 9.1. Разделение NFS на клиента и сервер * Защита и управление доступом следуют семантике UNIX представления процес- процессом UID и набора групп, которые проверяются по списку владельца файла, групп и других режимов доступа. Проверка безопасности осуществляется зависимым от файловой системы кодом, который может делать больше или меньше проверок в зависимости от возможностей файловой системы, которую он поддерживает. Например, файловая система MS-DOS не может реализовать полную проверку безопасности UNIX, поэтому она принимает решения, основываясь исключитель- исключительно на UID. Дизайн протокола не зависит от транспорта. Хотя первоначально в версии 2 он был построен с использованием протокола дейтаграмм UDP, в версии 3 он был легко перенесён на протокол потоков TCP. Он был также перенесен для работы поверх многочисленных других, не основанных на IP протоколах. Некоторые из проектных решений ограничивают набор приложений, для которых подходит NFS. Дизайн представляет клиентов и серверы как подключенных к быстрой локальной сети. Протокол NFS не работает хорошо через медленные линии связи. При ис- использовании в качестве транспортного протокола UDP он не работает хорошо между клиентами и серверами с промежуточными шлюзами. Он также плохо под-
458 Глава 9. Сетевая файловая система ходит для мобильных вычислений, имеющих длительные периоды работы в отсо- отсоединенном от сети состоянии. * Модель кеширования предполагает, что большинство файлов не будут использо- использоваться совместно. Когда совместно используется множество файлов, страдает про- производительность. Протоколу, не имеющему состояний, требуется некоторая потеря семантики UNIX. Блокировки файловой системы (flock) должны реализоваться отдельным демоном с состояниями. Отсрочка освобождения пространства в удаленном (unlinked) файле до тех пор, пока последний процесс не закроет файл, аппрокси- аппроксимируется эвристикой, которая иногда дает сбой. Несмотря на эти ограничения, NFS распространилась, поскольку она делает разум- разумный компромисс между семантикой и производительностью; низкая стоимость ее принятия сделала ее повсеместной. 9.2. Структура и работа NFS NFS работает в качестве типичного клиент-серверного приложения. Сервер получает от различных клиентов запросы вызова удаленных процедур {remote-procedure-call - RPC). RPC работает во многом подобно локальному вызову процедур: клиент делает вызов процедуры, а затем ожидает результат, пока процедура выполняется. Для удаленного вызова процедур параметры должны быть упакованы {marshalled) в одно сообщение. Упаковка включает замену указателей данными, на которые они указывают, и преобра- преобразование двоичных данных в канонический сетевой порядок байтов. Затем сообщение отсылается на сервер, где оно распаковывается (разъединяется на свои отдельные части) и обрабатывается как локальная операция файловой системы. Результат должен быть аналогичным образом упакован и возвращен обратно клиенту. Клиент разделяет результат и возвращает его вызывающему процессу, как если бы он возвращался после вызова локальной процедуры [Birrell & Nelson, 1984]. Протокол NFS использует про- протокол Sun RPC и внешнее представления данных (XDR) [Reid, 1987]. Хотя реализация ядра сделана вручную для обеспечения максимальной производительности, демоны уровня пользователя, описанные далее в данном разделе, используют общедоступные библиотеки RPC и XDR от Sun. Протокол NFS может работать поверх любого доступного ориентированного на потоки или дейтаграммы протокола. Обычным выбором являются TCP в качестве про- протокола потока и UDP в качестве протокола дейтаграмм. Каждое сообщение NFS RPC может потребоваться разделить на несколько пакетов, чтобы отправить по сети. Большой проблемой производительности для NFS, работающего под UDP или Ethernet, является то, что сообщения могут быть разделены вплоть до шести пакетов. Таким образом, если любой из этих пакетов теряется то теряется все сообщение, которое должно быть
9.2. Структура и работа NFS 459 отправлено повторно. При работе под TCP в Ethernet сообщение также может быть раз- разделено до шести пакетов, однако при этом можно повторно передать отдельные потерянные пакеты, а не все сообщение. В разделе 9.3 проблемы производительности обсуждаются более подробно. Набор запросов RPC, который клиент может отправить серверу, представлен в табл. 9.1. После того как сервер обработает каждый из запросов, он отвечает соответ- соответствующими данными или кодом ошибки, объясняющим, почему запрос не мог быть выполнен. Как отмечено в таблице, многие операции являются идемпотентными. Идемпотентной является операция, которую можно повторять несколько раз без изме- изменения конечного результата и без сообщения об ошибке. Например, запись одних и тех же данных по одному и тому же смещению в файле является идемпотентной, посколь- поскольку ее результатом будет одно и то же, сколько бы раз она ни повторялась. Однако по- попытка удаления одного и того же файла более одного раза является неидемпотентной, поскольку после первой попытки файл больше не существует. Идемпотентность явля- является проблемой, когда сервер медленный или когда потеряно подтверждение RPC и клиент повторно передает запрос RPC. Повторно переданный RPC заставит сервер попытаться сделать ту же самую операцию дважды. Для неидемпотентного запроса, такого, как запрос удаления файла, повторно переданный RPC, если он не будет обнаружен кешем недавних запросов сервера [Juszczak, 1989], вызовет возврат сооб- сообщения об ошибке «нет такого файла», поскольку файл будет уже удален первым RPC. Пользователь может быть сбит с толку сообщением об ошибке, поскольку он успешно нашел и удалил файл. Табл. 9.1. Запросы RPC для NFS версии 3 Запрос RPC Действие Идемпотентность GETATTR Получить атрибуты файла Да SETATTR Установить атрибуты файла Да LOOKUP Поиск имени файла Да ACCESS* Проверка прав доступа Да READLINK Чтение из символической ссылки Да READ Чтение из файла Да WRITE Запись в файл Да COMMIT* Фиксирование кешированных на сервере данных Да в стабильном хранилище CREATE Создание файла Нет REMOVE Удаление файла Нет RENAME Переименование файла Нет LINK Создание ссылки на файл Нет SYMLINK Создание символической ссылки Нет
460 Глава 9. Сетевая файловая система Табл. 9.1. Запросы RPC для NFS версии 3 (Окончание) Запрос RPC Действие Идемпотентность MKNOD* Создание специального устройства Нет MKDIR Создание каталога Нет RMDIR Удаление каталога Нет READDIR Чтение из каталога Да READDIRPLUS* Расширенное чтение из каталога Да FSSTAT Получение динамических атрибутов файловой системы Да FSINFO* Получение статических атрибутов файловой системы Да PATHCONF* Получение информации POSIX Да * Запросы RPC добавлены в версии 3. Каждый файл на сервере можно идентифицировать по уникальному описателю (handle) файла. Описатель файла является обозначением, посредством которого клиен- клиенты ссылаются на файлы на сервере. Описатели глобально уникальны, они передаются при операциях, таких, как чтение и запись, для обозначения файла. Описатель файла создается сервером, когда от клиента на сервер посылается запрос на трансляцию (поиск) имени пути. Сервер должен найти запрошенный файл или каталог и убедиться, что у запрашивающего сервера есть право доступа. Если доступ предоставляется, сервер возвращает клиенту описатель файла для запрошенного файла. Описатель файла идентифицирует файл в будущих запросах клиента. Серверы могут строить описатели файлов из любой информации, которую они сочтут подходящей. В реализа- реализации NFS FreeBSD описатель файла построен из идентификатора файловой системы, номера inode и номера поколения (generation number). Сервер создает уникальный идентификатор файловой системы для каждого из своих локально смонтированных файловых систем. Номер поколения назначается каждому inode каждый раз, когда последний назначается для представления нового файла. Номер поколения выбирается с использованием генератора случайных чисел ядра. Ядро гарантирует, что одно и то же значение никогда не будет использовано для двух последовательных выделений inode Назначением описателя файла является предоставление серверу достаточной ин- информации для того, чтобы найти файл в будущих запросах. Идентификатор файловой системы и inode предоставляют уникальный идентификатор для inode, к которому осу- осуществляется доступ. Номер поколения подтверждает, что inode по-прежнему ссылает- ссылается на тот же самый файл, на который он ссылался, когда к файлу впервые был получен доступ. Номер поколения обнаруживает, когда файл был удален и впоследствии создан новый файл с использованием того же самого inode. Хотя новый файл имеет те же самые идентификатор файловой системы и номер inode, это совершенно другой файл, чем тот, на который ранее ссылался описатель. Поскольку в описатель файла включен
9.2. Структура и работа NFS 461 номер поколения, номер поколения для предыдущего использования inode не будет соответствовать новому номеру поколения в том же самом inode. Когда клиент пред- представляет серверу описатель файла предыдущего поколения, сервер отказывает в доступе и возвращает сообщение об ошибке «устаревший описатель файла». Использование номеров поколений гарантирует, что описатель файла устойчив во времени. Распределенные системы определяют устойчивый во времени (time-stable) идентификатор как такой идентификатор, который уникально ссылается на какой- нибудь элемент как во время его существования, так и спустя длительное время после его удаления. Устойчивый во времени идентификатор позволяет системе запоминать идентичность между кратковременными сбоями и позволяет системе обнаруживать и сообщать об ошибках при попытках получить доступ к удаленным элементам. Протокол NFS Протокол NFS не имеет состояний. Не иметь состояний означает, что серверу не нужно сохранять какую-либо информацию о том, какой клиент обслуживается или об открытых в настоящий момент файлах. Каждый полученный RPC запрос полностью самодостаточен. Серверу для выполнения запроса не нужно никакой дополнительной информации, помимо той, которая содержится в параметрах RPC. Например, запрос на чтение будет включать мандат запрашивающего пользователя, описатель файла, для которого должно быть выполнено чтение, смещение в файле, с которого нужно начать чтение, и число читаемых байтов. Эта информация дает серверу возможность открыть файл, проверив, что у пользователя есть право на его чтение; найти соответствующее место; прочесть нужное содержание и закрыть файл. На практике сервер кеширует данные файла, к которому недавно был осуществлен доступ. Однако, если вследствие повышенной активности файл оказывается вытесненным из кеша, описатель файла предоставляет серверу достаточно информации, чтобы повторно открыть файл. Кроме уменьшения работы, необходимой для обслуживания входящих запросов, кеш сервера обнаруживает также повторные попытки ранее обслуженных запросов. Время от времени UDP-клиент будет отправлять запрос, который обрабатывается сервером, но подтверждение, возвращенное сервером клиенту, теряется. Не получив ответа, у клиента истекает время тайм-аута, и он повторно отправляет запрос. Сервер использует свой кеш, чтобы распознать, что повторно переданный запрос уже был об- обслужен. Поэтому сервер не будет повторять операцию, а просто повторно отправит подтверждение. Чтобы обнаружить такие повторные передачи соответствующим обра- образом, кеш сервера должен быть достаточно большим, чтобы отслеживать запросы NFS по крайней мере в течение последних секунд. Преимуществом протокола, не имеющего состояний, является то, что нет необхо- необходимости в восстановлении состояния после сбоя и перезагрузки клиента или сервера или после разделения и повторного соединения сети. Поскольку каждый RPC является самодостаточным, сервер сразу после запуска может просто начать обслуживать
462 Глава 9. Сетевая файловая система запросы; ему не нужно знать, какие файлы открыли клиенты. На самом деле ему даже не нужно знать, какие клиенты в настоящее время используют его в качестве сервера. У протоколов без состояний есть недостатки. Во-первых, семантика локальной файловой системы предполагает состояние. Когда файлы удаляются (unlinked), они попрежнему доступны до тех пор, пока не будет закрыта последняя ссылка на них. Поскольку NFS ничего не знает ни о том, какие файлы у клиентов открыты, ни о том, когда эти файлы закрываются, она не может должным образом знать, когда освободить свободное пространство файла. В результате она всегда освобождает пространство во время удаления последнего имени файла. Клиенты, которые хотят сохранить семанти- семантику освобождения при последнем закрытии, преобразуют удаления (unlinks) открытых файлов в переименования для скрывания имен на сервере. Эти имена имеют вид .nfsAxxxx4.4, гдехххх замещается шестнадцатеричным значением идентификатора про- процесса, а А последовательно увеличивается до тех пор, пока не будет найдено неисполь- зующееся имя. Когда на клиенте сделано последнее закрытие, клиент отправляет на сервер удаление скрытого имени файла. Эта эвристика работает для доступа к файлам лишь на единственном клиенте; если у одного клиента есть открытый файл, а другой клиент удаляет файл, файл по-прежнему исчезнет от первого клиента во время удале- удаления. Другая семантика состояния включает необязательную блокировку, описанную в разделе 7.5. Семантика блокировки не может управляться протоколом NFS. На большин- большинстве систем она обрабатывается отдельным менеджером блокировок; версия NFS FreeBSD реализует ее, используя демон rpc.Iockd уровня пользователя. Второй недостаток протокола без состояния связан с производительностью. Для версии 2 протокола NFS все операции, которые модифицируют файловую систему, должны быть зафиксированы в стабильном хранилище до того, как RPC может быть подтвержден. Большинство серверов не имеют памяти с аварийным батарейным пита- питанием; требование стабильного хранилища означает, что все записанные данные должны быть на диске до того, как о них можно сообщить RPC. Для увеличивающего- увеличивающегося файла обновление может потребовать до трех синхронных записей на диск: одна для inode - для обновления размера этого файла, одна - для косвенного блока для до- добавления указателя на новые данные и еще одна - для самих новых данных. Каждая синхронная запись требует нескольких миллисекунд; эта задержка значительно огра- ограничивает пропускную способность записи для любого данного файла клиента. Версия 3 протокола NFS устраняет некоторые синхронные записи путем добавле- добавления нового запроса RPC асинхронной записи. Когда сервер получает такой запрос, ему разрешено подтвердить RPC без записи новых данных в стабильное хранилище. Обычно клиент будет запрашивать серию асинхронных записей, за которыми следует запрос RPC-фиксирования, когда достигается конец файла или израсходовано буферное пространство для хранения файла. Запрос RPC-фиксирования заставляет сервер записать все незаписанные части файла в стабильное хранилище до под- подтверждения RPC-фиксирования. Сервер выигрывает, записывая inode и косвенные блоки для файла лишь однажды для группы асинхронных записей вместо записи при
9.2. Структура и работа NFS 463 каждом запросе RPC на запись. Клиент выигрывает от большей пропускной способно- способности при записи файлов. У клиента все же есть дополнительные издержки по необходи- необходимости сохранения копий всех асинхронно записанных буферов до тех пор, пока RPC не будет зафиксирован, поскольку сервер может испытать сбой до записи одного или более асинхронных буферов в стабильное хранилище. Каждый раз, когда клиент делает RPC асинхронной записи, сервер возвращает контрольный маркер. Когда клиент отправляет RPC-фиксирования, подтверждение этого RPC также включает кон- контрольный маркер. Контрольный маркер представляет собой cookie, который клиент может использовать для определения того, перегрузился ли сервер между вызовом для записи данных и последующим вызовом для их фиксирования. Гарантируется, что cookie будет одним и тем же на протяжении одного сеанса загрузки сервера и что он будет другим каждый раз, когда сервер перезагружается, когда незафиксированные данные могут быть потеряны. Если контрольный маркер изменяется, клиент знает, что нужно повторно передать все RPC асинхронных записей, сделанные с момента послед- последнего RPC-фиксирования, который был подтвержден значением старого контрольного маркера. Протокол NFS не определяет степень детализации буферирования, которая должна использоваться при записи файлов. Большинство реализаций NFS буферизирует файлы 8-килобайтными блоками. Таким образом, если приложение записывает 10 байтов в середине блока, клиент читает с сервера весь блок, изменяет запрошенные 10 байтов, а затем записывает весь блок обратно на сервер. Реализация FreeBSD также использует 8-килобайтные буферы, но хранит дополнительную информацию, которая описывает, какие байты в буфере были изменены. Если приложение записывает 10 байтов в сере- середине блока, клиент читает весь блок с сервера, изменяет запрошенные 10 байтов, но потом записывает обратно на сервер лишь 10 измененных байтов. Чтение блока необ- необходимо, чтобы убедиться, что, если приложение впоследствии прочтут другие, не мо- модифицированные части блока, оно получит действительные данные. Обратная запись лишь модифицированных данных имеет два преимущества. 1. Меньше данных пересылается по сети, уменьшается соперничество за скудный ресурс. 2. Неперекрывающиеся изменения файла не теряются. Если два различных клиен- клиента одновременно изменяют различные части одного и того же блока файла, обе модификации будут отражены в файле, поскольку на сервер отправляется лишь измененная часть. Когда клиенты отправляют обратно на сервер блоки целиком, изменения, сделанные первым клиентом, будут переписаны данными, прочитан- прочитанными до того, как была сделана первая модификация и записана обратно вторым клиентом.
464 Глава 9. Сетевая файловая система Реализация NFS FreeBSD Реализация NFS в FreeBSD была создана Риком Маклемом (Rick Macklem) в универси- университете Гельфа (Guelph) с использованием спецификаций версии 2 протокола, опублико- опубликованного Sun Microsystems [Macklem, 1991; Sun Microsystems, 1989]. Позже он расширил ее для поддержки расширений имеющихся в версии 3 протокола [Callaghan et al., 1995; Pawlowski et al., 1994]. В табл. 9.1 выделены новые функциональные возможности про- протокола версии 3. Версия 3 протокола предусматривает следующее: 64-разрядные смещения файлов и размеры; RPC access («доступ»), предоставляющий проверку прав доступа сервера при от- открытии файлов, вместо предположений клиента о том, разрешит ли доступ сервер; * возможность добавления в конец для RPC записи; определенный способ создавать узлы специальных устройств и fifo; оптимизацию массивного доступа к каталогу; * возможность группирования записей в несколько асинхронных RPC, за которыми следует фиксирующий RPC для обеспечения того, что данные находятся в ста- стабильном хранилище; * дополнительную информацию о возможностях нижележащей файловой системы. Кроме поддержки версий 2 и 3, Рик Маклем создал несколько других расширений реализации NFS BSD; расширенная версия стала известна как протокол «не вполне NFS» (Not Quite NFS- NQNFS) [Macklem, 1994a]. Расширения NQNFS добавляют следующее. Расширенные атрибуты файлов для более полной поддержки функциональных возможностей файловой системы FreeBSD. Разновидность кратковременных владений (leases) с клиентским кешированием отложенной записи, которые обеспечивают согласованность распределенного кеша и повышают производительность [Gray & Cheriton, 1989]. Хотя расширения NQNFS никогда не были широко приняты в реализациях версии 3, они были полезными в испытании ценности использования владений в NFS. Эта техно- технология владений была принята для использования в протоколе NFS версии 4 не только ради согласованности кеша и повышения производительности файлов и каталогов, но также как механизм для ограничения времени восстановления для блокировок. Реализация NFS, распространяемая с FreeBSD, поддерживает клиенты и серверы, работающие с протоколами NFS версий 2 или 3 и NQNFS [Macklem, 1994b]. Протокол NQNFS описан в разделе 9.3. Реализации клиента и сервера NFS резидентны для ядра. NFS соединяет с сетью через сокеты, используя интерфейс ядра, доступный через sosendQ и soreceiveQ (обсу- (обсуждение интерфейса сокетов см. в главе 11). Имеются процедуры управления соедине-
9.2. Структура и работа NFS 465 нием для поддержки сокетов с использованием ориентированных на соединения про- протоколов; для сокетов дейтаграмм на клиентской стороне есть поддержка тайм-аутов и повторных передач. Менее критические для времени операции, такие, как монтирование и демонтиро- демонтирование, а также определение того, какие файловые системы можно экспортировать и какому набору клиентов, управляются системными демонами уровня пользователя. Для того чтобы функционировала серверная сторона, должны быть запущены демоны portmap, mountd и nfsd. Для получения полных функциональных возможностей NFS должны быть также запущены демоны rpc.lockd и rpc.statd. Демон portmap действует в качестве службы регистрации для программ, пре- предоставляющих службы, основанные на RPC. Когда запускается демон RPC, он сооб- сообщает демону portmap, какой номер порта он прослушивает и какие службы RPC готов обслуживать. Когда клиент хочет сделать вызов RPC для данной службы, он сначала соединяется с демоном portmap на серверной машине, чтобы определить номер порта, которому должны отправляться сообщения RPC. Взаимодействия между клиентским и серверным демонами (в процессе монтиро- монтирования удаленной файловой системы) показаны на рис. 9.2. Демон mountd выполняет две важные функции. 1. Вначале и после сигнала отбоя (hangup) mountd читает файл /etc/exports и создает список хостов и сетей, в которые можно экспортировать каждую из локальных фай- файловых систем. Он передает этот список в ядро, используя системный вызов mount; ядро связывает список со структурой mount соответствующей файловой системы таким образом, что список быстро доступен для опроса при получении запроса NFS. 2. Запросы монтирования клиента направляются демону mountd. После проверки того, что у клиента есть разрешение на монтирование запрошенной файловой системы, mountd возвращает описатель для запрошенной точки монтирования. Этот описа- описатель файла используется клиентом для последующего обхода файловой системы. Главный демон nfsd порождает процесс, который входит в ядро, используя систем- системный вызов nfssvc. Порожденные процессы обычно остаются резидентными в ядре, пре- предоставляя контекст процесса для демонов RPC NFS. В типичной системе работают от четырех до шести демонов nfsd. Если nfsd предоставляет службу дейтаграмм, он при своем запуске создает сокет дейтаграмм. Если nfsd предоставляет службу потока, со- соединенные сокеты потока будут переданы главным демоном nfsd в ответ на запросы соединения от клиентов. Когда запрос поступает на сокет дейтаграмм или потока, имеет место обратный вызов из уровня сокета, который вызывает процедуру nfsrv_rcv(). Вызов nfsrv_rcv() берет сообщение из приемной очереди сокета и переправ- переправляет это сообщение доступному демону nfsd. Демон nfsd проверяет отправителя, а затем передает запрос для обработки соответствующей локальной файловой системе. Когда результат возвращается от файловой системы, он возвращается запрашивающе-
466 Глава 9. Сетевая файловая система Клиент mount Пользователь Ядро Сервер portmap mountd - - -4 Пользователь Ядро Рис. 9.2. Взаимодействие демонов, когда удаленная файловая система монтируется. Шаг1: процесс mount клиента посылает сообщение в хорошо известный порт демона port- map сервера, запрашивая адрес порта демона mountd сервера. Шаг 2: демон port- map сервера возвращает адрес порта демона mountd своего сервера. Шаг 3: процесс mount клиента посылает запрос демону mountd сервера с именем пути файловой системы, которую он хочет смонтировать. Шаг 4: демон mountd сервера запрашива- запрашивает из его ядра описатель файла для нужной точки монтирования. Если запрос успеш- успешный, описатель файла возвращается процессу mount клиента. В противном случае от запроса обработки файла возвращается ошибка. Если запрос успешен, процесс mount клиента выполняет системный вызов mount, передавая описатель файла, который он получил от демона mountd сервера му клиенту. Затем демон nfsd готов вернуться в начало цикла и обслужить другой за- запрос. Максимальная степень параллелизма на сервере определяется числом запущен- запущенных демонов nfsd. Для ориентированных на соединение транспортных протоколов, таких, как TCP, для каждой точки монтирования клиента с сервером имеется одно соединение. Для ориентированных на дейтаграммы протоколов, таких, как UDP, сервер создает фик- фиксированное число входящих сокетов RPC, когда он запускает свои демоны nfsd; клиен- клиенты создают один сокет для каждой импортированной точки монтирования. Сокет для точки монтирования создается командой mount на клиенте, который затем использует его для взаимодействия с демоном mountd на сервере. После установления соедине- соединения клиента с сервером процессы демона в ориентированных на соединение протоко- протоколах могут выполнить дополнительные проверки, такие, как аутентификацию Kerberos. После создания и проверки соединения сокет передается в ядро. Если соединение разрывается в то время, когда точка монтирования все еще активна, клиент попытается соединиться повторно с помощью нового сокета. Демон rpc.lockd управляет запросами блокировок для удаленных файлов. Клиент- Клиентские запросы блокировок экспортируются из ядра посредством fifo /var/run/lock. Демон rpc.lockd читает запрос блокировки из fifo и посылает его через сеть демону rpc.lockd на сервере, который удерживает файл. Демон, действующий на сервере, открывает файл, который должен быть заблокирован, и использует описанные в разделе 8.5 примитивы блокировок, чтобы получить запрошенную блокировку.
9.2. Структура и работа NFS 467 После получения блокировки демон сервера отправляет сообщение обратно демону клиента. Демон клиента записывает состояние блокировки в fifo, который затем счи- тывается ядром и посылается приложению пользователя. Освобождение блокировки обрабатывается аналогично. Если демон rpc.lockd не запущен, запросы блокировок для файлов NFS будут завершаться с ошибкой «операция не поддерживается». Демон rpc.statd взаимодействует с демонами rpc.statd на других хостах для пре- предоставления службы отслеживания состояния. Демон принимает запросы от программ, запущенных на локальном хосте (обычно rpclockd), для слежения за состоянием указан- указанных хостов. Если отслеживаемый хост испытывает сбой и запускается заново, при повторном запуске демон на испытавшем сбой хосте уведомит об этом другие демоны. После уведомления о сбое или когда демон определяет, что удаленный хост потерпел аварию, по отсутствию ответа он уведомит локальные программы, запросившие службу слежения. Если демон rpc.statd не запущен, блокировки, удерживаемые клиен- клиентами на хосте, потерпевшем аварию, могут удерживаться бесконечно. При использова- использовании службы rpc.statd аварии будут обнаружены, а блокировки, удерживаемые потерпевшим аварию хостом, будут освобождены. Клиентская сторона может работать без каких-либо запущенных демонов, но сис- системный администратор может повысить производительность, запустив несколько демонов nfsiod (эти демоны предоставляют ту же самую службу, что и демоны biod Sun). Как и в случае с сервером, для получения полных функциональных возможностей клиент должен запустить демоны rpc.lockd и rpc.statd. Назначением демона nfsiod является осуществление асинхронного упреждающего чтения и отложенной записи. Они обычно запускаются, когда ядро начинает работать в многопользовательском режиме. Они входят в ядро, используя системный вызов nf- ssvc, и остаются в ядре резидентными, предоставляя контекст процесса для клиентской стороны RPC NFS. В их отсутствие каждое чтение или каждая запись файла NFS, который не может быть обслужен из локального кеша клиента, должны выполняться в контексте запрашивающего процесса. Процесс находится в состоянии сна, пока RPC отправляется на сервер, обрабатывается сервером, и ответ возвращается обратно. Упреждающее чтение не осуществляется, и операции записи продолжаются со скоро- скоростью записи на сервере. Когда они присутствуют, демоны nfsiod предоставляют отдельный контекст, в котором выдают запросы RPC серверу. Когда файл записывается, данные копируются в буферный кеш на клиенте. Затем буфер передается ожидающему nfsiod, который вызывает RPC на сервере и ждет ответа. Когда поступает ответ, nfsiod обновляет локальный буфер, чтобы пометить его как записанный. Между тем процесс, выполнивший запись, продолжает работать. Образец реализации протокола NFS Sun Microsystems сбрасывает все блоки файла на сервер, когда файл закрывается. Если все грязные блоки были записаны на сервер, когда процесс закрывает записываемый им файл, ему не придется ожидать, когда они будут сброшены на диск. Протокол NQNFS не сбрасывает все блоки файла на сервер, когда этот файл закрывается.
468 Глава 9. Сетевая файловая система При чтении файла клиент сначала передает запрос упреждающего чтения nfsiod, который осуществляет RPC на сервере. Затем он ищет буфер, который был запрошен для чтения. Если пользующийся спросом буфер уже находится в кеше из-за предыду- предыдущего запроса упреждающего чтения, он может сразу же продолжить. В противном случае он должен выполнить RPC на сервере и ждать ответа. Взаимодействия между демонами клиента и сервера при выполнении ввода/вывода показаны на рис. 9.3. Клиент write () nfsiod Пользователь Ядро Сервер nfsd © Пользователь Ядро Диск Рис. 9.3. Взаимодействие демонов при выполнении ввода/вывода. Шаг 1: клиентский про- процесс выполняет системный вызов write. Шаг 2: данные, которые должны быть запи- записаны, копируются в буфер ядра на клиенте, и системный вызов write возвращается. ШагЗ: внутри клиентского ядра просыпается демон nfsiod, принимает грязный буфер и отправляет буфер на сервер. Шаг 4: входящий запрос записи доставляется следующему доступному демону nfsd, работающему внутри ядра на сервере. Демон nfsd сервера записывает данные в соответствующий локальный диск и ожидает завершения ввода/вывода диска. Шаг 5: после завершения ввода/вывода демон nfsd сервера отсылает обратно подтверждение ввода/вывода ожидающему демону nfsiod на клиенте. По получении подтверждения демон nfsiod клиента помечает буфер, как чистый Клиент-серверные взаимодействия На локальную файловую систему не влияют нарушения сетевых служб. Она всегда доступна для пользователей на машине, если не случится катастрофическое событие, такое, как сбой диска или питания. Поскольку зависает или терпит аварию вся машина в целом, ядру не нужно беспокоиться о том, как управлять процессами, которые имели доступ к файловой системе. В отличие от этого клиентский конец сетевой файловой системы должен иметь возможность управления процессами, получившими доступ к удаленным файлам, когда клиент по-прежнему работает, но сервер становится недоступным или испытывает сбой. Каждой точке монтирования NFS предоставляются три альтернативы в случае недоступности сервера. 1. По умолчанию используется жесткое монтирование (hard mount), которое будет продолжать пытаться установить контакт с сервером бесконечно, чтобы завершить доступ к файловой системе. Этот вид монтирования подходит, когда процессы на
9.2. Структура и работа NFS 469 клиенте, который получает доступ к файлам в файловой системе, не допускают сис- системных вызовов ввода/вывода, которые возвращают временные ошибки. Жесткое монтирование используется для процессов, для которых доступ к файловой системе критичен для нормальной работы системы. Оно полезно также, если у клиента есть долгоживущая программа, которая хочет просто ждать возобновления работы сервера (например, после того как сервер был выключен для обслуживания). 2. Другой крайностью является мягкое монтирование (soft mount), которое пытается выполнить RPC определенное число раз, а затем соответствующий системный вы- вызов возвращается с временной ошибкой. Для ориентированных на соединение про- протоколов действительный запрос RPC повторно не передается; вместо этого NFS для повторных попыток полагается на повторные передачи протокола. Если ответ не возвращается в пределах указанного времени, соответствующий системный вы- вызов возвращает временную ошибку. Проблема с данным типом монтирования в том, что большинство приложений не ожидают возвращения временной ошибки от системного вызова ввода/вывода (поскольку они никогда не возникают в ло- локальной файловой системе). Часто они ошибочно интерпретируют временную ошибку как постоянную и преждевременно завершаются. Дополнительной про- проблемой является решение о том, каким должен быть период тайм-аута. Если он ус- установлен слишком маленьким, возвраты ошибок начнут появляться каждый раз, когда сервер NFS замедляется из-за большой нагрузки. Наоборот, большой интервал повторных попыток может привести к зависанию процесса на длитель- длительное время из-за потерпевшего аварию сервера или сетевого разделения. 3. Большинство системных администраторов принимают промежуточный вариант, используя прерываемое монтирование (interruptible mount), который будет ждать вечно, подобно жесткому монтированию, но проверять, не ожидает ли сигнал за- завершения для каких-либо процессов, которые ожидают ответа сервера. Если сиг- сигнал (такой, как прерывание) посылается процессу, ожидающему сервер NFS, соот- соответствующий системный вызов ввода/вывода возвращается с временной ошибкой. Обычно процесс завершается сигналом. Если процесс решил перехватить сигнал, он может решить, как обработать временную ошибку. Такая возможность мон- монтирования дает интерактивным программам возможность быть прерванными, когда сервер испытывает сбой, позволяя в то же время долгоживущим процессам ждать возвращения сервера. В первоначальной реализации NFS были лишь две первые возможности. Поскольку ни одна из этих возможностей не была идеальной для интерактивного использования файловой системы, была разработана третья возможность в качестве компромиссного решения.
470 Глава 9. Сетевая файловая система Транспортные проблемы RPC Протокол NFS версии 2 работает поверх транспортного протокола UDP/IP путем от- отправки каждого сообщения запроса/ответа в отдельной UDP-дейтаграмме. Поскольку UDP не гарантирует доставку дейтаграмм, запускается таймер, и, если тайм-аут насту- наступит раньше получения соответствующего ответа RPC, запрос RPC передается повторно. В лучшем случае посторонний запрос RPC повышает нагрузку на сервере и может вызвать повреждение файлов на сервере или возврат клиенту ложных ошибок, когда повторно выполняются не идемпотентные RPC. Обычно для минимизации отри- отрицательного эффекта повторного выполнения дублированных запросов RPC на сервере используется кеш недавних запросов [Juszczak, 1989]. Кеш недавних запросов сохра- сохраняет копии всех неидемпотентных запросов RPC, которые сервер получил за послед- последние несколько минут (обычно вплоть до 15 минут) вместе с ответами, которые он отправил на этот запрос. Каждый входящий запрос проверяется по этому кешу. Если запрос совпадает с одним из тех, на которые сервер уже ответил, вместо повторного выполнения запроса возвращается ранее вычисленный результат. Таким образом, клиент место неверного ответа получает тот же самый результат, который он получил бы до этого. Кеш недавних запросов предотвращает большинство связанных с неидем- неидемпотентностью ошибок. Однако сетевое разъединение, которое превышает значение тайм-аута для элементов з кеше, по-прежнему может заставить проявиться ошибки не идемпотентности. Количество времени, которое клиент ждет, прежде чем повторно отправить запрос RPC, называется тайм-аутом обращения (round-trip timeout — RTT). Выяснение подхо- подходящего значения для RTT трудно. Значение RTT описывает всю операцию RPC, включая передачу сообщения RPC серверу, запрос на сервере nfsd, выполнение всех необходимых операций ввода/вывода и отправку ответного сообщения RPC обратно клиенту. Оно может сильно разниться даже для умеренно загруженного сервера NFS. В результате интервал RTT должен быть консервативной (большой) оценкой, чтобы избежать повторных передач посторонних запросов RPC. Было показано, что дина- динамическое регулирование интервала RTT и применение окна перегрузки к отложенным запросам оказывает некоторую помощь в решении проблемы повторных передач [Nowicki, 1989]. При отправке запроса NFS чтения-записи с 8-килобайтным размером по умолча- умолчанию ответом на запрос чтения-записи будет дейтаграмма 8+-Кбайт. В Ethernet с макси- максимальным блоком передачи (MTU) в 1500 байт дейтаграмма 8+-Кбайт должна быть раз- разделена для передачи на уровне IP по крайней мере на шесть фрагментов. Чтобы на при- приемном конце IP фрагменты были успешно собраны повторно, в месте назначения должны быть получены все фрагменты. Если при передаче хотя бы один фрагмент по- потерян или поврежден, должно быть повторно передано и выполнено заново все сооб- сообщение RPC. Эта проблема может быть осложнена, если сервер отделен от клиента несколькими транзитными участками с маршрутизаторами или медленными каналами.
9.2. Структура и работа NFS 471 Может также оказаться почти фатальным, если сетевой интерфейс на клиенте или сервере не может обрабатывать получение сетевых пакетов, идущих без промежутков [Kent & Mogul, 1987]. Альтернативой всему этому безумству является запуск NFS поверх транспортного протокола TCP вместо UDP. Поскольку TCP обеспечивает надежную доставку с управ- управлением перегрузкой, он избегает проблем, связанных с UDP. Поскольку повторные передачи осуществляются на уровне TCP вместо уровня RPC, единственным случаем, когда на сервер отправляется дублирующий RPC, будет случай,когда сервер терпит аварию или когда имеется длительное разъединение сети, вызывающее обрыв ТСР- соединения после того, как RPC был получен, но еще не подтвержден клиентом. В этом случае клиент повторно отправит RPC после перегрузки сервера, поскольку он не знает, что RPC был получен. Использование TCP допускает также использование чтения и записи размеров данных, превышающих 8-килобайтное ограничение для транспортировки поверх UDP. Использование больших размеров данных дает TCP возможность эффективно исполь- использовать полнодуплексную полосу пропускания сети, прежде чем быть вынужденным останавливаться и ожидать ответа RPC от сервера. NFS поверх TCP обычно обеспечи- обеспечивает производительность от сопоставимой до значительно лучшей, чем NFS поверх UDP, если только не ограничены возможности процессора клиента или сервера. В этом случае становятся значительными дополнительные издержки процессора по использо- использованию транспортного протокола TCP. Главной проблемой при использовании транспортировки по TCP версии 2 NFS является то, что она поддерживается лишь BSD и клиентами и серверами нескольких других производителей. Однако явное преимущество, продемонстрированное ТСР- реализацией NFS версии 2, убедило группу в Sun Microsystems, реализующую версию 3, сделать TCP транспортным протоколом по умолчанию. Таким образом, клиент Sun версии 3 сначала пытается установить соединение, используя TCP; лишь если сервер отказывается, он вернется обратно к использованию UDP. В версрии 4 про- протокола также определено использование TCP. Проблемы безопасности NFS версий 2 и 3 не является безопасной, поскольку протокол не разрабатывался с учетом безопасности. Несмотря на несколько попыток исправления проблем безопас- безопасности в этих версиях, безопасность NFS по-прежнему ограничена. В частности, работа по безопасности учитывает лишь аутентификацию; данные файлов передаются по сети в виде открытого текста. Даже если кто-то не может заставить ваш сервер отправить ему важный файл, он может просто подождать, пока к нему не получит доступ легаль- легальный пользователь, а затем перехватить его, пока он передается по сети. Значительная работа, проделываемая в версии 4, предназначена как для аутентификации, так и для
472 Глава 9. Сетевая файловая система безопасности данных. Когда версия 4 войдет в общее употребление, файловые систе- системы NFS будут иметь возможность работать с приемлемым уровнем безопасности. Управление экспортом NFS имеет уровень модульности локальной файловой сис- системы. С точкой монтирования каждой локальной файловой системы связан список хос- хостов, на которые данная файловая система может экспортироваться. Локальная файло- файловая система может экспортироваться на определенный хост, на все хосты, соответст- соответствующие маске подсети, или на все прочие хосты (внешнему миру). Для каждого хоста или группы хостов файловая система может быть экспортирована только для чтения или для чтения-записи. Кроме того, сервер может указать набор подкаталогов в преде- пределах файловой системы, которые могут быть смонтированы. Однако этот список точек монтирования навязыватся лишь демоном mountd. Если злонамеренный клиент этого хочет, он может получить доступ к любой части файловой системы, которая экс- экспортирована для него. Конечное определение экспортируемости осуществляется списком, поддерживае- поддерживаемым в ядре. Поэтому даже если неконтролируемый клиент организует слежение за сетью и похищает описатель файла для точки монтирования действительного клиента, ядро откажется принять описатель файла, если клиент, представляющий этот описа- описатель, не находится в списке экспорта ядра. Когда NFS работает поверх TCP, проверка осуществляется один раз, когда устанавливается соединение. Когда NFS работает с UDP, проверка должна делаться для каждого запроса RPC. Сервер NFS допускает также ограниченные преобразования мандатов пользовате- пользователя. Обычно мандат суперпользователя не вызывает доверия и преобразуется в пользо- пользователя с низкими привилегиями «nobody». Мандаты всех других пользователей могут приниматься как есть или также отображаться на пользователя по умолчанию (обычно «nobody»). Использование на сервере UID-клиента и списка GID без изменений пред- предполагает, что пространство UID и GID является общим для клиента и сервера (т.е. UID N на клиенте должен ссылаться на того же самого пользователя на сервере). Систем- Системный администратор может поддерживать более сложные отображения UID и GID, ис- используя описанную в разделе 6.7 файловую систему umapfs. Системный администратор может увеличить безопасность, используя мандаты Kerberos вместо приема произвольных мандатов пользователей, отправляемых без шифрования клиентами с неизвестной надежностью [Steiner et al., 1988]. Когда новый пользователь на клиенте хочет получить доступ к файловой системе NFS, которая экс- экспортируется с использованием Kerberos, клиент должен предоставить билет Kerberos для аутентификации пользователя на сервере. В случае успеха система ищет принци- принципал Kerberos в базах данных паролей и групп сервера для получения набора мандатов и передает nfsd сервера локальное преобразование UID-клиента в эти мандаты. Демоны nfsd работают всецело в режиме ядра, за исключением случаев, когда получают билет Kerberos. Чтобы избежать помещения всей аутентификации Kerberos в ядро, nfsd временно возвращается из ядра, чтобы проверить билет с использованием библиотек Kerberos, а затем возвращается с результатом в ядро.
9.3. Методики по повышению производительности 473 Реализация NFS с Kerberos использует зашифрованные временные отметки, чтобы предотвратить попытки воспроизведения. Каждый запрос RPC включает временную отметку, которая шифруется клиентом и расшифровывается сервером с использовани- использованием сеансового ключа, которым обмениваются в ходе первоначальной аутентификации Kerberos. Каждая временная отметка может использоваться лишь один раз, и она должна быть в рамках нескольких минут от текущего времени, записанного сервером. Такая реализация требует, чтобы часы клиента и сервера поддерживали синхронизацию в пределах нескольких минут (это требование уже налагается для запуска Kerberos). Она требует также, чтобы сервер сохранял копии всех временных отметок, которые он получил и которые находятся в пределах диапазона времени, которые он принимает, чтобы можно было проверить, что временная отметка не используется повторно. В качестве альтернативы сервер может потребовать, чтобы временные отметки для ка- каждого от его клиентов возрастали монотонным образом. Однако такой алгоритм вызо- вызовет отказ приема запросов RPC, прибывающих вне очереди. Механизм использования Kerberos для аутентификации запросов NFS не определен строго, и реализация FreeBSD не была протестирована на возможность взаимодействия с другими производителями. Таким образом, Kerberos можно использовать лишь между клиентами или серверами FreeBSD. 93. Методики по повышению производительности Удаленные файловые системы создают требующую напряжения проблему производи- производительности: и предоставление непротиворечивого представления данных по всей сети, и быстрая доставка этих данных часто являются противоречивыми целями. Сервер легко может поддерживать согласованность, владея единственным хранилищем для данных и отправляя их каждому клиенту, когда они нужны им; этот подход имеет тен- тенденцию быть медленным, поскольку каждый доступ к данным требует, чтобы клиент ожидал время обращения RPC. Задержка еще больше усугубляется огромной нагруз- нагрузкой, которая накладывается на сервер, который должен обрабатывать каждый запрос ввода/вывода от своих клиентов. Чтобы повысить производительность и снизить за- загрузку сервера, протоколы удаленных файловых систем пытаются сами кешировать на клиентах часто использующиеся данные. Если кеш спроектирован должным образом, клиент будет способен удовлетворять многие из клиентских запросов ввода/вывода не- непосредственно из кеша. Осуществление таких доступов быстрее, чем взаимодействие с сервером, снижает задержку на клиенте и нагрузку на сервер и на сеть. Трудной частью кеширования на клиенте является поддержание согласованности кешей, т.е. обеспечение того, чтобы каждый клиент быстро замещал любые кешированные дан- данные, которые изменены записями, сделанными на других клиентах. Если первый клиент записывает файл, который позже читается вторым клиентом, второй клиент хочет видеть данные, записанные первым клиентом, а не устаревшие данные, которые
474 Глава 9. Сетевая файловая система находились в файле до этого. Есть два главных способа, когда могут быть случайно прочитаны устаревшие данные. 1. Если второй клиент имеет в кеше устаревшие данные, он может использовать эти данные, поскольку не знает, что доступны более новые данные. 2. У первого клиента в кеше могут быть новые данные, но он мог еще не успеть запи- записать эти данные обратно на сервер. В этом случае, даже если второй клиент запро- запросит у сервера обновленные данные, сервер может вернуть устаревшие данные, по- поскольку он не знает, что у одного из его клиентов есть более новая версия файла в его клиентском кеше. Вторая из этих проблем связана со способом, как осуществляется запись клиентом. Синхронная запись требует, чтобы все записи были переданы на сервер в ходе систем- системного вызова write. Этот подход является самым совместимым, поскольку у сервера всегда будут самые последние записанные данные. Он позволяет также передавать все сообщения об ошибках, такие, как «недостаточно места на файловой системе», обрат- обратно процессу клиента в ходе возврата из системного вызова write. С файловой системой NFS, использующей синхронную запись, возвраты ошибок наиболее близко соответст- соответствуют возвратам ошибок локальной файловой системы. К сожалению, данный подход ограничивает клиента лишь одной записью за время обращения RPC. Альтернативой синхронной записи является отложенная запись, когда системный вызов write возвращается, как только данные кешированы на клиенте; данные записы- записываются на сервер некоторое время спустя. Такой подход позволяет осуществлять записи клиента со скоростью доступа к локальному хранилищу данных размером вплоть до размера локального кеша. Также для случаев, когда вскоре после записи имеет место усечение или удаление файла, записи на сервер можно вообще избежать, поскольку данные были уже удалены. Отмена передачи данных экономит время клиен- клиента и снижает нагрузку на сервер. У отложенной записи есть несколько недостатков. Для обеспечения полной согла- согласованности сервер должен уведомлять клиента, когда другой клиент хочет прочесть или записать файл, чтобы отложенные записи были перенесены на сервер. Есть также проблемы с передачей ошибок назад процессу клиента, который сделал системный вызов write. Например, когда сервер полон, кеширование отложенных записей привно- привносит изменение семантики. В этом случае запросы RPC отложенной записи могут за- завершиться ошибкой «недостаточно места». Если данные отправлены обратно серверу, когда файл закрыт, ошибка может быть обнаружена, лишь, если приложение проверяет возвращаемое системным вызовом close значение. Для отложенных записей записан- записанные данные могут не быть отправлены обратно на сервер до тех пор, пока процесс, сде- сделавший запись, не завершится - спустя большое время после того, как его можно уве- уведомить о каких-либо ошибках. Единственным решением может быть модификация программ, записывающих важные файлы, чтобы они выполняли системный вызов
9.3. Методики по повышению производительности 475 fsync и проверяли возвращаемые им ошибки, вместо того чтобы зависеть от получения ошибок от write или close. Наконец, есть риск потери недавно записанных данных, если клиент терпит аварию до того, как данные записаны обратно на сервер. Компромиссом между синхронной записью и отложенной записью является асин- асинхронная запись. Запись на сервер начинается в ходе системного вызова write, но сис- системный вызов write возвращается до того, как запись завершится. Этот подход мини- минимизирует риск потери данных вследствие аварии клиента, но сводит на нет возмож- возможность снижения загрузки сервера записями за счет отмены записей при усечении или удалении файла. Простейшим механизмом поддержания полной согласованности кеша является ме- механизм, используемый Sprite, который отключает все кеширования клиентом файла каждый раз, когда могла бы возникнуть ситуация одновременой записи [Nelson et al., 1988]. Поскольку NFS не имеет способа узнать, когда может возникнуть разделение записи, она пытается ограничить период несогласованности путем обратной записи данных, когда файл закрывается. Файлы, которые открыты в течение длительных периодов времени, записываются обратно, когда их самые старые данные насчиты- насчитывают 30 секунд. Таким образом, реализация NFS осуществляет смесь асинхронной и отложенной записи, но она всегда передает все записи на сервер при закрытии. Пере- Передача отложенных записей при закрытии сводит на нет значительную часть выигрыша в производительности отложенной записи, поскольку задержки, которые избегаются в системных вызовах write, наблюдаются в системном вызове close. С таким подходом сервер всегда знает обо всех изменениях, сделанных их клиентами, с максимальной за- задержкой в 30 секунд, а обычно меньше, поскольку большинство файлов открываются для записи лишь на короткое время. Сервер поддерживает согласованность чтения, заставляя клиента всегда проверять содержимое своего кеша до его использования. Когда клиент читает данные, он снача- сначала проверяет данные в своем кеше. Каждый элемент кеша отмечается атрибутом, который показывает самое последнее время, когда данные, по данным сервера, были модифицированы. Если данные обнаружены в кеше, клиент посылает своему серверу запрос RPC временной отметки, чтобы выяснить, когда данные были в последний раз изменены. Если время модификации, возвращенное сервером, совпадает с соответст- соответствующим временем в кеше, клиент использует данные в своем кеше; в противном случае он организует замену данных в своем кеше новыми данными. Проблема с проверкой сервером каждого доступа к кешу в том, что клиент по- прежнему испытывает задержку обращения RPC для каждого доступа к файлу, а сервер по-прежнему наводнен запросами RPC, хотя их можно обработать гораздо бы- быстрее, чем полные операции ввода/вывода. Чтобы уменьшить эту задержку клиента и загрузку сервера, большинство реализаций NFS отслеживают, насколько давно у сервера запрашивали о каждом из блоков кеша. Затем клиент использует настраивае- настраиваемый параметр, который обычно устанавливается на несколько секунд задержки опроса сервера о блоке кеша. Если запрос ввода/вывода находит блок кеша, а сервер был
476 Глава 9. Сетевая файловая система запрошен о действительности этого блока в течение срока задержки, клиент не запра- запрашивает сервер снова, а просто использует блок. Поскольку определенные блоки используются много раз подряд, сервер будет запрошен лишь однажды, а не при каждом доступе. Например, доступ к блоку для каталога /usr/include будет сделан лишь однажды для каждого #include в исходном файле, который компилируется. Недостатком этого подхода является то, что изменения, сделанные другими клиентами, не могут быть замечены, пока не пройдет число секунд задержки. Более последовательный подход, используемый некоторыми сетевыми файловыми системами, заключается в использовании схемы обратного вызова, когда сервер отсле- отслеживает все файлы, которые кеширует каждый из его клиентов. Когда изменяется кешированный файл, сервер уведомляет клиенты, сохранившие этот файл, чтобы они могли удалить его из своих кешей. Этот алгоритм значительно снижает число запросов от клиента серверу с эффектом снижения задержки ввода/вывода клиента и загрузки сервера [Howard et al., 1988]. Недостатком является то, что этот подход привносит в сервер состояние, поскольку сервер должен помнить клиентов, которых он обслужи- обслуживает, и набор файлов, которые они кешировали. Если сервер испытает сбой, он должен восстановить состояние до того, как может быть запущен снова. Восстановление состояния сервера является значительной проблемой, когда все работает должным образом; оно становится даже еще более сложным и трудоемким, когда усугубляется разъединениями сети, которые предотвращают взаимодействия сервера с некоторыми из его клиентов [Mogul, 1993]. Реализация NFS FreeBSD использует асинхронные записи, когда файл открыт, но синхронно ожидает, пока все данные будут записаны, когда файл закрывается. Такой подход дает выигрыш в скорости асинхронной записи, гарантируя в то же время, что обо всех отложенных ошибках будет сообщено не позднее, чем в момент закрытия файла. Реализация запрашивает сервер об атрибутах файла самое большее через каждые 3 секунды. Этот 3-секундный период снижает сетевой трафик для файлов с частым доступом, гарантируя в то же время, что любые изменения файла будут обнаружены с задержкой не более 3 секунд. Хотя эти эвристики обеспечивают прием- приемлемую семантику, они заметно несовершенны. Более последовательная семантика за меньшую стоимость доступна с протоколом владения NQNFS, описываемым в сле- следующем разделе. Владения Протокол NQNFS разработан для поддержания полной согласованности кешей между клиентами устойчивым к авариям способом. Это такая адаптация протокола NFS, что сервер поддерживает клиентов и NFS, и NQNFS, поддерживая в то же время полную согласованность между сервером и клиентами NQNFS. Протокол поддерживает согла- согласованность кеша путем использования кратковременных владений (leases) вместо ин- информации об открытых файлах [Gray & Cheriton, 1989]. Владение является билетом,
9.3. Методики по повышению производительности 477 разрешающим деятельность, который действителен до тех пор, пока не пройдет неко- некоторое отведенное время. Пока клиент удерживает действительное владение, он знает, что сервер сделает ему обратный вызов, если состояние файла изменится. Когда время владения истекло, клиент должен соединиться с сервером, если он хочет использовать кешированные данные. Владения выдаются с использованием временных интервалов, а не абсолютного времени, чтобы избежать необходимости синхронизации времени дня. Имеются три важные известные серверу константы времени, maximumJeasejterm устанавливает верхнюю границу длительности владения - обычно от 30 секунд до 1 минуты. clock_skew добавляется ко всем срокам владения на сервере, чтобы скорректировать различные скорости часов между клиентом и сервером. write_slack является числом се- секунд, в течение которых сервер хочет ждать, пока клиент с истекшим владением кеша записи передаст грязные записи. Соединение с сервером после истечения владения сходно с методикой NFS для снижения загрузки сервера путем проверки действительности данных лишь каждые несколько секунд. Главное отличие в том, что сервер отслеживает кешированные файлы своего клиента, так что никогда не бывает периодов времени, когда клиенты ис- используют устаревшие данные. Таким образом, время, используемое для владений, может быть значительно больше, чем несколько секунд, в течение которых клиенты могут допускать возможно устаревшие данные. Результатом этого более продолжи- продолжительного времени является снижение числа вызовов сервера почти до того уровня, ко- который бывает при полной реализации обратных вызовов, такой, как в файловой систе- системе Эндрю [Howard et al., 1988]. В отличие от механизма обратных вызовов восстанов- восстановление состояния с владениями тривиально. Серверу нужно лишь подождать, пока пройдет время срока владения, а затем продолжить работу. Когда время владений ис- истекло, клиенты всегда будут соединяться с сервером до использования своих кеширо- ванных данных. Время истечения владения обычно короче, чем время, которое требу- требуется большинству серверов для перезагрузки, поэтому сервер может эффективно во- возобновить работу, как только будет запущен. Если машина управляется с перезагруз- перезагрузкой быстрее, чем время истечения владения, тогда перед возобновлением работы она должна подождать, пока не истечет время всех владений. Дополнительным преимуществом использования владений, а не информации о со- состоянии является то, что владения используют значительно меньше памяти сервера. Если каждая часть владения требует 64 байтов, большой сервер с тысячами клиентов и пиковой пропускной способностью 10 000 запросов RPC в секунду обычно будет ис- использовать для владений лишь около 1 Мбайт памяти, в худшем случае около 15 Мбайт. Даже если сервер исчерпал хранилище владений, он может просто подождать в тече- течение нескольких секунд времени истечения владения и освободить запись. В отличие от этого сервер с жестким состоянием должен хранить записи для всех открытых в на- настоящее время всеми клиентами файлов. Необходимая память составляет от 30 до 120 Мбайт на 1000 обслуживаемых клиентов.
478 Глава 9. Сетевая файловая система Каждый раз, когда клиент хочет кешировать данные для файла, у него должно быть действительное владение. Имеются три вида владений: некеширующие, кеширующие чтение и кеширующие запись. Некеширующее владение требует, чтобы все файловые операции выполнялись синхронно с сервером. Владение с кешированием чтения позволяет клиенту кешировать данные, но файл изменяться не может. Владение с кешированием записи позволяет клиенту кешировать записи на время действия вла- владения. Если клиент кешировал записываемые данные, которые еще не записаны на сервере, когда владение с кешированием записи почти истекло, он попытается про- продлить владение. Если продление завершается неудачей, клиенту необходимо передать кешированные данные. Если все клиенты файла читают его, им всем будет предоставлено владение с кешированием чтения. Владение с кешированием чтения дает одному или более клиен- клиентам возможность кешировать данные, но они не могут делать какие-либо изменения дан- данных. На рис. 9.4 показан типичный сценарий кеширования чтения. Вертикальные сплош- сплошные черные линии обозначают записи владений. Обратите внимание, что линии времени на шкале не нарисованы, поскольку взаимодействие клиента с сервером обычно занимает менее 100 миллисекунд, тогда как обычная длительность владения составляет 30 секунд. Каждое владение включает время, когда файл в последний раз был изменен на сервере. Клиент может использовать эту временную отметку, чтобы убедиться, что его кеширо- кешированные данные по-прежнему не устарели. Вначале клиент А получает для файла владе- владение с кешированием чтения. Позже клиент А обновляет это владение и использует его для проверки того, что данные в его кеше по-прежнему действительны. Одновременно клиент В может получить владение с кешированием чтения для того же самого файла. Клиент А Сервер Системный [Запрос чтения + Владения [ Клиент В вызов чтения | Системный вызов чтения (из кеша) Тайм-аут владения Системный вызов чтения Время изменения подходит, кеш действительный Системный вызов чтения (из кеша) Тайм-аут владения Ответ Запрос чтения (промах кеша) Ответ Запрос получения владения Ответ с тем же временем изменения Запрос чтения (промах кеша) Ответ Ответ, клиент В добавлен к владению *~ Запрос чтения ВРЕМЯ Владение с кешированием чтения для клиента А Владение истекает Запрос чтения + Владения] Системный вызов чтени (промах кеша) Системный вызов чтени Тайм-аут владения Рис. 9.4. Владения с кешированием чтения. Сплошные вертикальные линии представляют действительные владения
9.3. Методики по повышению производительности 479 Если один клиент хочет записать файл, а читателей этого файла нет, клиенту будет выдано владение с кешированием записи. Владение с кешированием записи допускает кеширование отложенных записей, но требует, чтобы все данные передавались на сервер, когда время владения истекает или завершается уведомлением об очистке (eviction notice). Когда владение с кешированием записи почти истекло, клиент попы- попытается продлить владение, если файл по-прежнему открыт, но ему потребуется пере- передать отложенные записи на сервер, если обновление потерпит неудачу (см. рис. 9.5). Записи могут не поступить на сервер до тех пор, пока на клиенте не истечет владение с кешированием записи. Проблема согласованности не возникает, поскольку сервер хранит владение с кешированием записи действительным на write_slack секунд доль- дольше, чем время, предоставленное во владении, выданном клиенту. Кроме того, записи в файл клиентом, имеющим владение, вызывают продление времени истечения владе- владения по крайней мере на write_slack секунд. Этот период write_slack консервативно оце- оценивается как дополнительное время, которое понадобится клиенту для обратной записи всех записанных данных, которые он кешировал. Если время, выбранное для writejslack, слишком короткое, RPC записи может поступить после истечения владе- владения записью на сервере. Хотя этот RPC записи приведет к тому, что другой клиент увидит несогласованность, эта несогласованность является не большей проблемой, чем семантика, которую обычно предоставляет NFS. Сервер Клиент В Владение с кешированием записи для клиента В Обновление владения Тайм-аут владения Истечение срока отложено из-за записей Прошло write_slack секунд после последней записи Получение владения I -I Системный вызов записи Ответ (владение с кешированием записи) Получение владения записью Ответ (владение с кешированием записи) Запись Ответ —• _. Запись Ответ Системный вызов записи (кешированные отложенные записи) Запрос обновления владения до истечения срока Системный вызов Истечение срока владения ВРЕМЯ РИС. 9.5. Владение с кешированием записи. Сплошные вертикальные линии представляют действительные владения
480 Глава 9. Сетевая файловая система Сервер отвечает за поддержание согласованности среди клиентов NQNFS путем отключения кеширования клиентом каждый раз, когда файловая операция сервера вы- вызвала бы несогласованность. Возможность несогласованности возникает всякий раз, когда у клиента есть владение с кешированием записи, а любой другой клиент или ло- локальная операция на сервере пытается получить доступ к файлу или когда делается по- попытка изменения файла с кодированным чтением клиентами. Если возникает одно из этих условий, всем клиентам будут выданы владения без кеширования. С владением без кеширования все чтения и записи будут осуществляться через сервер, поэтому кли- клиенты всегда будут получать самые последние данные. На рис. 9.6 показано, как владе- владения с кешированиями записи и чтения заменяются владениями без кеширования, когда имеется возможность совместных записей. Вначале файл читается клиентом А. Потом в него записывает клиент В. Пока клиент В все еще записывает, клиент А присылает еще один запрос на чтение. В этом случае сервер посылает клиенту В «уведомление об очистке», а затем ожидает завершения владения. Клиент В записывает обратно свои грязные данные, а затем отправляет сообщение «об освобождении». В конечном счете сервер выдает обоим клиентам владения без кеширования. Вообще, завершение владе- владения возникает, когда сообщение «об освобождении» было получено от всех клиентов, которые получили владение или когда владение истекло. Сервер не ожидает ответа на пару сообщений «уведомление об очистке» и «освобождение», как для всех других RPC сообщений. Они отправляются асинхронно, чтобы избежать бесконечного ожида- ожидания сервером ответа от выключенного клиента. Клиент получает владения либо посредством специфического RPC-запроса владе- владения, либо путем включения запроса владения в другой RPC. Большинство RPC-запро- сов NQNFS допускают добавление к ним запросов владения. Сочетание запросов вла- владения с другими RPC-запросами минимизирует объем дополнительного сетевого тра- трафика. Типичную комбинацию можно сделать при открытии файла. Клиент должен вы- вызвать RPC, чтобы получить описатель для открываемого файла. Он может включить запрос владения, поскольку во время открытия знает, будет ли ему нужно владение с кешированием чтения или записи. Все владения имеют уровень модульности файла, поскольку RPC-запрос NFS оперирует отдельными файлами, а у NFS нет внутреннего представления об иерархии файлов. Можно кешировать чтение каталогов, символиче- символических ссылок и атрибутов файлов, но не запись. Исключением является атрибут размера файла, который обновляется во время кешированной записи на клиенте, чтобы отра- отразить растущий файл. Владения имеют то преимущество, что они обычно требуются лишь тогда, когда имеют место другие операции ввода/вывода. Таким образом, запро- запросы владений почти всегда можно скомбинировать с другими RPC-запросами, избегая некоторых накладных расходов, связанных с явными RPC открытия и закрытия, необ- необходимыми для реализации долговременных обратных вызовов.
9.3. Методики по повышению производительности 481 Клиент А Сервер Системный вызов чтения Системный вызов чтения (из кеша) Тайм-аут владения Системный вызов чтения Запрос чтения + Владени^ Клиент В I [ ВРЕМЯ Ответ Запрос чтения (промах кеша) " Ответ Запрос получения Владение с кешированием чтения для клиента А Владение истекает Получение владения Ответ Чтение данных (без кеширования) (владение без кеширования) Запрос чтения Чтение данных записи Ответ (владение с кешированием записи) Запись. Системный вызов записи Системный вызов записи (отложенные записи кешируются) Отложенные записи сбрасываются на сервер *оо освобождении Получение владения.: Системный вызов Ответ (владение без кеширования) Запись, I записи Ответ Синхронные записи (без кеширования) РИС. 9.6. Владения с кешированием записи. Сплошные вертикальные линии представляют действительные владения Сервер управляет операциями от локальных процессов и от удаленных клиентов, которые не используют протокол NQNFS, выдавая кратковременные владения на время каждой файловой операции или RPC. Например, запрос создания нового файла получит кратковременное владение записью на каталог, в котором создается файл. Перед выдачей этого владения записью сервер освободит владения чтением для всех клиентов NQNFS, которые кешировали данные для этого каталога. Поскольку сервер получает владения для всей не относящейся к NQNFS деятельности, между сервером и клиентами NQNFS поддерживается согласованность, даже когда локальные или NFS-клиенты модифицируют файловую систему. Клиенты NFS будут продолжать оста- оставаться не более несогласованными с сервером, чем они были бы без владений. Восстановление после сбоев Сервер должен поддерживать состояние всех текущих владений, удерживаемых его клиентами. Преимуществом использования кратковременных владений является то, что по истечении maximumJeasejerm секунд после прекращения выдачи владений сервером он знает, что текущих владений не осталось. Восстановление сервера после аварии как таковое не требует восстановления какого-либо состояния. После переза- перезагрузки сервер просто отказывается обслуживать какие-либо запросы RPC, за исключе-
482 Глава 9. Сетевая файловая система нием записей (преимущественно от клиентов, ранее имевших владения записью), пока не пройдет writejslack секунд с момента истечения последнего владения. Для машин, которые не могут вычислить время, в течение которого они были в аварийном состоя- состоянии, время истечения последнего владения можно безопасно оценить как boottime + maximum Jeasejterm + writejslack + clockjskew. Здесь boottime является временем, когда ядро начало работу после загрузки. При maximum Jeasejerm от 30 до 60 секунд и clock_skew и write_slack самое большее в не- несколько секунд эта задержка приближается примерно к 1 минуте, которая для боль- большинства систем проходит в процессе перезагрузки сервера. Когда это время проходит, у сервера не будет оставшихся владений. Клиентам нужно по крайней мере write_slack секунд, чтобы записать данные на сервер, поэтому сервер должен иметь свежие дан- данные. После этого сервер возобновляет обычную работу. Имеется другое условие сбоя, которое возникает, когда сервер перегружен. В случае худшего сценария клиент передает серверу грязные данные, но большая очередь запросов на сервере откладывает эти записи более чем на write_slack секунд. В попытке минимизировать эффект этих штормов восстановления сервер отвечает на RPC-запросы, которые он пока не готов обслужить, «попытайтесь снова позже» [Baker & Ousterhout, 1991]. Сервер предпринимает два шага, чтобы все клиенты имели воз- возможность записать обратно свои записанные данные. Во-первых, владение с кеширо- ванием записи на сервере завершается лишь когда не было записей в файл в течении предыдущих writejslack секунд. Во-вторых, сервер не будет принимать других запро- запросов, кроме записей, пока он не будет перегружен в течении предыдущих write_slack секунд. Сервер считается перегруженным, когда имеются ожидающие запросы RPC, а все процессы nfsd заняты. Другой проблемой, разрешаемой кратковременными владениями, является то, как обрабатывается потерпевший аварию или разъединение клиент, удерживающий владе- владение, которое сервер хочет освободить. Сервер определяет эту проблему, когда ему нужно освободить владение таким образом, чтобы он мог выдать владение второму клиенту, а первый клиент, удерживающий владение, не отвечает на запрос освобожде- освобождения. В данном случае сервер может просто ждать, пока не истечет время владения первого клиента, прежде чем выдать новое второму клиенту. Когда первый клиент перезагружается или подключается к серверу, он просто повторно запрашивает все владения, которые ему теперь нужны. Если сетевое соединение клиента с сервером прервано как раз перед истечением владения с кешированием записи, клиент не может передать грязные записи на сервер. Другие клиенты, которые могут соединиться с сервером, будут продолжать иметь возможность получить доступ к файлу и будут видеть старые данные. Поскольку владение с кешированием записи на клиенте истек- истекло, клиент будет синхронизироваться с сервером, как только будет восстановлено сете- сетевое соединение с сервером. Этой задержки можно избежать при помощи политики сквозной записи.
Упражнения 483 Подробное сравнение влияния владений на производительность приводится в Macklem [1994a]. Вкратце: владения наиболее полезны, когда сервер или сеть сильно загружены. В этом случае владения дают возможность использовать сервер и сеть от 30 до 50 процентов большему количеству клиентов до того, как начнет ощущаться такой же уровень перегрузки, как для сервера и сети, не использующих владения. Кроме того, владения предоставляют клиентам лучшую согласованность и меньшие задержки независимо от загрузки. Хотя протокол NQNFS никогда не принимался за пределами семейства операцион- операционных систем BSD, он все же предоставил подтверждение концепции, которая использо- использовалась для проверки использования владений в протоколе NFS версии 4. В протоколе NFS версии 4 владения используются как для согласования кешей, так и для управле- управления состоянием блокировок. На время публикации ведется активная работа по реали- реализации NFS версии 4 для BSD, которая, как можно надеяться, будет готова для произ- производственного использования в FreeBSD в течение следующего года или двух. Упражнения 9.1. Опишите функции, осуществляемые клиентом NFS. 9.2. Опишите функции, осуществляемые сервером NFS. 9.3. Опишите три преимущества, которые происходят из того, что NFS не имеет состояний. 9.4. Приведите две причины, по которым TCP является лучше, чем UDP, для работы с протоколом RPC NFS. 9.5. Опишите содержимое описателя файла в FreeBSD. Как используется описа- описатель файла? 9.6. Когда файлу присваивается новый номер поколения? Какой цели служит номер поколения? 9.7. Опишите три способа, с помощью которых клиент NFS может обработать попытки доступа к файловой системе, когда его сервер терпит аварию или становится недоступным по другой причине. 9.8. Приведите две причины, почему владения имеют ограниченный срок исполь- использования. 9.9. Что такое обратный вызов? Когда он используется? 9.10. Сервер может выдавать три вида владений: без кеширования, с кеширова- нием чтения и с кешированием записи. Опишите, что клиент может делать с каждым из этих владений. 9.11. Опишите, как сервер NQNFS восстанавливается после аварии.
484 Глава 9. Сетевая файловая система *9.12. Предположим, имеется клиент, который поддерживает и версию 2, и версию 3 протокола NFS, работающего с протоколами как TCP, так и UDP, но сервер поддерживает лишь версию 2 NFS, работающую с UDP. Покажите взаимодействие протокола между клиентом и сервером в предположении, что клиент предпочитает работать с версией 3 NFS с использованием TCP. **9.13. Предположим, что владения имеют неограниченный срок использования. Разработайте систему для восстановления состояния владения после аварии клиента или сервера. Ссылки Baker & Ousterhout, 1991. М. Baker & J. Ousterhout, "A vailability in the Sprite Distributed File System", ACM Operating System Review, vol. 25, no. 2, pp. 95-98, April 1991. Birrell & Nelson, 1984. A. D. Birrell & B. J. Nelson, "Implementing Remote Procedure Calls", ACM Transac- Transactions on Computer Systems, vol. 2, no. 1, pp. 39-59, Association for Computing Machinery, February 1984. Callaghan et al., 1995. B. Callaghan, B. Pawlowski, & P. Staubach, "NFS: Network File System Version 3 Pro- Protocol Specification", RFC 1813, available from http://www.faqs.org/rfcs/rfcl813.html, June 1995. Gray & Cheriton, 1989. C. Gray & D. Cheriton, "Leases: An Efficient Fault-Tolerant Mechanism for Distributed File Cache Consistency", Proceedings of the Twelfth Symposium on Operating Systems Principles, pp. 202-210, December 1989. Howard, 1988. J. Howard, "An Overview of the Andrew File System", USENIX Association Confer- Conference Proceedings, pp. 23-26, January 1988. Howard etal, 1988. J. Howard, M. Kazar, S. Menees, D. Nichols, M. Satyanarayanan, R. Sidebotham, & M. West, "Scale and Performance in a Distributed File System", ACM Transactions on Computer Systems, vol. 6, no. 1, pp. 51-81, Association for Computing Machinery, February 1988. Juszczak, 1989. C. Juszczak, "Improving the Performance and Correctness of an NFS Server", USENIX Association Conference Proceedings, pp. 53-63, January 1989.
Ссылки 485 Kent & Mogul, 1987. С. Kent & J. Mogul, "Fragmentation Considered Harmful", Research Report 87/3, Dig- Digital Equipment Corporation Western Research Laboratory, Palo Alto, CA, December 1987. Macklem, 1991. R. Macklem, "Lessons Learned Tuning the 4.3BSD-Reno Implementation of the NFS Protocol", USENIXAssociation Conference Proceedings, pp. 53-64, January 1991. Macklem, 1994a. R. Macklem, "Not Quite NFS, Soft Cache Consistency for NFS", USENIX Association Conference Proceedings, pp. 261-278, January 1994. Macklem, 1994b. R. Macklem, "The 4.4BSD NFS Implementation", in 4.4BSD System Manager s Manual, pp. 6:1-14, O'Reilly & Associates, Inc., Sebastopol, CA, 1994. Mogul, 1993. J. Mogul, "Recovery in Spritely NFS", Research Report 93/2, Digital Equipment Cor- Corporation Western Research Laboratory, Palo Alto, CA, June 1993. Nelson etal., 1988. M. Nelson, B. Welch, & J. Ousterhout, "Caching in the Sprite Network File System", A CM Transactions on Computer Systems, vol. 6, no. 1, pp. 134-154, Association for Computing Machinery, February 1988. Nowicki, 1989. B. Nowicki, "Transport Issues in the Network File System", Computer Communications Review, vol. 19, no. 2, pp. 16-20, April 1989. Pawlowski etal., 1994. B. Pawlowski, С Juszczak, P. Staubach, С Smith, D. Lebel, & D. Hitz, "NFS Version 3: Design and Implementation", USENIX Association Conference Proceedings, pp. 137-151, June 1994. Reid, 1987. Irving Reid, "RPCC: A Stub Compiler for Sun RPC", USENIX Association Conference Proceedings, pp. 357-366, June 1987. Rifkin etal., 1986. A. Rifkin, M. Forbes, R. Hamilton, M. Sabrio, S. Shah, & K. Yueh, "RFS Architectural Overview", USENIX Association Conference Proceedings, pp. 248-259, June 1986.
486 Глава 9. Сетевая файловая система Sandberg et al., 1985. R. Sandberg, D. Goldberg, S. Kleiman, D. Walsh, & B. Lyon, "Design and Implementa- Implementation of the Sun Network Filesystem", USENIX Association Conference Proceedings, pp. 119-130, June 1985. Shepler et al., 2003. S. Shepler, B. Callaghan, D. Robinson, R. Thurlow, C. Beame, M. Eisler, & D. Noveck, "Network File System (NFS) version 4 Protocol", RFC 3530, available from http:// www.faqs.org/rfcs/rfc3530.html, April 2003. SNIA, 2002. Storage Networking Industry Association SNIA, "Common Internet File System (CIFS) Technical Reference", www.snia.org/techjictivities/CIFS/CIFS-TR-1p00_FINAL.pdf March 2002. Steiner et al., 1988. J. Steiner, С Neuman, & J. Schiller, "Kerberos: An Authentication Service for Open Network Systems", USENIX Association Conference Proceedings, pp. 191-202, Febru- February 1988. Sun Microsystems, 1989. Sun Microsystems, "NFS: Network File System Protocol Specification", RFC 1094, available from http://www.faqs.org/rfcs/rfclO94.html, March 1989. Walsh etal., 1985. D. Walsh, B. Lyon, G. Sager, J. Chang, D. Goldberg, S. Kleiman, T. Lyon, R. Sandberg, & P. Weiss, "Overview of the Sun Network File System", USENIX Association Conference Proceedings, pp. 117-124, January 1985.
Глава 10 Управление терминалами Изначально пользователи взаимодействовали с системой, используя терминалы, под- подключенные к компьютеру через жестко смонтированные последовательные каналы RS-232. Хотя большинство компьютеров по-прежнему имеют один или два смонтиро- смонтированных последовательных канала, их использование передано работе системной кон- консоли, низко скоростных модемов, последовательно соединенных принтеров и тому по- подобных устройств. Хотя жестко смонтированные терминалы в значительной степени являются сноской в истории, обработка символов, осуществляемая для ввода/вывода с клавиатуры, по-прежнему важна. Наиболее обычный вид сеанса пользователя в FreeBSD использует псевдотерми- псевдотерминал, или/7/у. Драйвер псевдотерминала предоставляет поддержку пары устройств, обо- обозначаемых как ведущее (master) и ведомое (slave) устройства. Ведомое устройство пре- предоставляет процессу интерфейс, идентичный интерфейсу, описанному для терминалов в данной главе. Однако вместо связи с аппаратным устройством для чтения и записи символов ведомое устройство имеет другой процесс, манипулирующий им через веду- ведущую часть псевдотерминала. То есть все, что записывается в ведущее устройство, пре- предоставляется в качестве ввода ведомому устройству, и все, записываемое в ведомое устройство, предоставляется в виде ввода ведущему устройству. Драйвер для ведуще- ведущего устройства эмулирует все подробности поддержки аппаратного обеспечения, опи- описанные в оставшейся части данной главы. Псевдотерминалы используются эмулятором терминала xterm, а также обычными программами удаленной регистрации, такими, как ssh и telnet. В типичном случае xterm открывает ведущую сторону псевдотерминала и направляет нажатия клавиш от менеджера окон в его ввод, принимая символы из вывода псевдотерминала и отобра- отображая их в окне. Он порождает процесс, который открывает ведомую сторону псевдо- псевдотерминала, а затем запускает предпочитаемую оболочку пользователя с установлен- установленным в качестве стандартного ввода, стандартного вывода и стандартной ошибки
488 Глава 10. Управление терминалами ведомой частью. По мере нажатия клавиш пользователем они записываются в веду- ведущую часть псевдотерминала, где они обрабатываются дисциплиной линии связи и в конечном счете появляются в качестве ввода в оболочке пользователя. Вывод от оболочки записывается в ведомую часть псевдотерминала, где он обрабатывается дисциплиной линии связи и в конечном счете выходит из главной части и отобража- отображается в окне xterm. Подобно терминальным устройствам, которые предшествовали им, драйвер псев- псевдотерминала обычно обрабатывает по одному символу за раз, используя интерфейс символьного устройства, описанный в разделе 6.2. По мере набора каждого символа на клавиатуре или поступления от пользователя по сети он представляется ведущей стороне псевдотерминала. Ввод символов не зависит от запросов процессов чтения ввода пользователя с ведомой стороны псевдотерминала. Символы обрабатываются по мере их получения, а затем хранятся до тех пор, пока процесс их не запросит, позволяя тем самым упреждающую печать. Когда псевдотерминал поддерживает взаимодейст- взаимодействие пользователя с системой, ввод терминала представляет собой нажатия клавиш пользователем, а вывод терминала отображается на экране пользователя. В данной главе мы в основном имеем дело с таким видом использования псевдотерминала. Когда мы используем термин терминал, мы описываем понятие, которое применяется как к псевдотерминалам, так и к аппаратным терминальным устройствам. Асинхронные последовательные каналы также подключают модемы для взаимо- взаимодействия компьютера с компьютером или принтеров с последовательным интерфей- интерфейсом. Когда последовательные интерфейсы используются для этих целей, они обычно используют подмножество возможностей системы по управлению терминалами. Иногда они используют для большей эффективности особые обрабатывающие моду- модули. Мы обсудим альтернативные модули терминалов в разделе 10.9. 10.1. Режимы обработки терминалов FreeBSD поддерживает несколько режимов обработки терминалов. Значительную часть времени ввод с клавиатуры осуществляется в каноническом режиме (часто назы- называемом также режимом с обработкой (cooked mode) и строковым режимом (line mode), при котором вводимые символы отображаются операционной системой в виде эха по мере их набора пользователем, но внутренне буферируются до тех пор, пока не будет введен символ конца строки. Только после приема символа конца строки вся строка делается доступной оболочке или другому процессу, читающему с клавиатуры. Если процесс пытается прочесть с клавиатуры до того, как готова вся строка, процесс войдет в состояние сна до тех пор, пока не будет получен символ конца строки, незави- независимо от получения части строки. Операционной системой реализуется и делается дос- доступным для конфигурирования пользователем или процессом обычный случай, когда возврат каретки ведет себя аналогично переводу строки и делает строку доступной
10.2. Дисциплины линии связи 489 ожидающему процессу. В каноническом режиме пользователь может исправлять ошибки набора, удаляя недавно набранный символ при помощи символа стирания, удаляя последнее слово при помощи символа удаления слова или удаляя всю текущую строку целиком при помощи символа сброса (kill character). Другие специальные сим- символы генерируют сигналы, направляемые процессам, связанным с клавиатурой; эти сигналы могут прервать обработку или приостановить ее. Дополнительные символы запускают и останавливают вывод, сбрасывают (flush) вывод или предотвращают особую интерпретацию следующего символа. Пользователь может набрать несколько строк ввода, вплоть до определяемого реализацией лимита, не ожидая прочтения ввода и удаления из очереди ввода. Пользователь может определить символы специальной обработки или выборочно отключить их. Экранные редакторы и программы, взаимодействующие с другими компьютерами, обычно работают в неканоническом режиме (обычно называемом также непосредст- непосредственным (raw) режимом или посимвольным (character-at-a-time) режимом). В этом режиме система делает каждый символ доступным для прочтения в качестве ввода, как только этот символ получен. Обработка всех особых символов отключена, не применя- применяется удаление или другая обработка строк, все символы передаются программе, читающей с клавиатуры. Можно сконфигурировать ввод с клавиатуры тысячами различных комбинаций между этими двумя крайними случаями. Например, экранный редактор, желающий получать прерывания от пользователя асинхронно, мог бы включить специальные символы, которые генерируют сигналы, но в прочих отношениях работающий в нека- неканоническом режиме. Помимо обработки вводимых символов интерфейс терминала должен произво- производить определенную обработку вывода. В большинстве случаев эта обработка проста: символы конца строки преобразуются в возврат каретки плюс перевод строки. Кроме обработки символов процедуры вывода на терминал должны управлять контролем за потоком как со стороны пользователя (используя останавливающий и запускающий символы), так и со стороны процессов. Поскольку пользователи принимают вывод медленно по сравнению с периферийными устройствами компьютера, программа, записывающая в терминал, может производить вывод намного быстрее, чем этот вывод может быть обработан пользователем. Когда процесс заполнил выходную очередь терминала, он будет помещен в состояние сна; повторно он будет запущен, когда будет использовано достаточное количество выведенных данных. 10.2. Дисциплины линии связи Большая часть обработки символов, осуществляемая для интерфейсов терминалов, независима от того, связан ли он с псевдотерминалом или реальным аппаратным устройством. Поэтому большая часть этой обработки осуществляется общими проце-
490 Глава 10. Управление терминалами дурами в драйвере tty, или обработчике терминала. Аппаратный интерфейс поддержи- поддерживается определенным драйвером устройства. Аппаратный драйвер является драйвером устройства, подобным описанным в главе 6. Он отвечает за получение и передачу сим- символов и за некоторую синхронизацию при осуществлении процессом вывода. Аппарат- Аппаратный драйвер вызывается драйвером tty для осуществления вывода; в свою очередь, он вызывает драйвер tty с символами ввода, когда они поступают. Интерфейс псевдо- псевдотерминала неотличим от настоящего аппаратного обеспечения для уровней выше и ниже его в ядре. Драйвер tty взаимодействует с оставшейся частью системы в качестве дисциплины линии связи (line discipline). Дисциплина линии связи является обрабатьшающим модулем, используемым для предоставления семантики в асинхронном последовательном интерфейсе. Он описывается процедурным интерфейсом, структурой linesw (line-switch - коммутатор каналов). Структура linesw определяет точки входа дисциплины линии связи во многом аналогично тому, как коммутатор символьного устройства cdevsw перечисляет точки входа драйвера символьного устройства (см. раздел 6.2). Точки входа дисциплины линии связи перечислены в табл. 10.1. Подобно всем драйверам устройств, драйвер терминала делится на верхнюю половину, которая действует син- синхронно, когда вызывается для обработки системного вызова, и нижнюю половину, которая запускается асинхронно, когда ей представляются символы от псевдотерминала или аппаратного устройства. Дисциплина линии связи предоставляет процедуры, которые осуществляют общую обработку терминала как для верхней, так и для нижней половины драйвера терминала. Табл. 10.1. Точки входа линии связи Процедура 1_ореп l_close Ijread l_write IJoctl l_rint l_start Ijnodem Откуда вызывается Сверху Сверх Сверху Сверху Сверху Снизу Снизу Снизу Использование Начальная точка входа дисциплины Выход из дисциплины Чтение из строки Запись в строки Операции управления Получен символ Завершение передачи Переход несущей модема Интерфейс последовательного терминала поддерживает обычный набор точек входа драйвера устройства, определенный коммутатором символьного устройства. Несколько из стандартных точек входа драйвера (read, write и ioctl) при вызове немед- немедленно передают управление дисциплине линии связи. (В качестве элемента poll в ком- коммутаторе символьного устройства обычно используется стандартная опрашивающая tty
10.3. Интерфейс пользователя 491 процедура ttypollQ.) Процедуры open и close аналогичны; элемент open дисциплины линии связи вызывается, когда линия впервые входит в дисциплину, либо при перво- первоначальном открытии, либо когда дисциплина изменяется. Сходным образом процедура закрытия дисциплины вызывается для выхода из дисциплины. Все эти процедуры вы- вызываются сверху в ответ на соответствующий системный вызов. Оставшиеся элементы дисциплины линии связи вызываются нижней половиной драйвера устройства для со- сообщения о вводе или изменениях состояния, обнаруженных во время прерывания. Эле- Элемент l_rint (прерывание приемника) вызывается с каждым полученным из линии симво- символом. Соответствующим элементом для прерываний завершения передачи является про- процедура Ijstart, которая вызывается, когда операция вывода завершена. Этот элемент дает дисциплине линии связи возможность начать дополнительные операции по выво- выводу. Для дисциплины линии связи обычного терминала эта процедура просто вызывает процедуру вывода драйвера, чтобы начать следующий блок вывода. Изменения в управляющих линиях модема (см. раздел 10.7) могут быть обнаружены аппаратным драйвером, в этом случае процедура Ijnodem вызывается с указанием нового состояния. Система включает несколько различных видов дисциплин линий связи. Большин- Большинство линий используют дисциплины, ориентированные на терминалы и описанные в разделе 10.3. Другие дисциплины в системе могут поддерживать различные протоко- протоколы асинхронных последовательных сетевых интерфейсов. 10.3. Интерфейс пользователя Дисциплина линии связи по умолчанию для терминала происходит из дисциплины, которая имелась в System V, модифицированная стандартом POSIX, а затем изменен- измененная далее для предоставления приемлемой совместимости с предыдущими дисципли- дисциплинами линий связи 4.2ВSD. Базовой структурой, использующейся для описания состоя- состояния терминала в System V, была структура termio. Базовой структурой, исполь- использующейся POSIX и FreeBSD, является структура termios. Стандартным программным интерфейсом для управления дисциплиной линии связи терминала является системный вызов ioctl. Этот вызов изменяет дисциплины, устанавливает и получает значения для символов специальной обработки и режимов, устанавливает и получает аппаратные параметры последовательного канала и выпол- выполняет другие служебные операции. Большинству операций ioctl требуется вдобавок к дескриптору файла и команде один аргумент; аргумент является адресом целого числа или структуры, из которой система получает параметры или в которую помеща- помещается информация. Поскольку рабочая группа POSIX думала, что системный вызов ioctl труден и нежелателен для определения - из-за его использования аргументов, которые изменялись в размерах, типах и в том, записывались они или считывались, - члены группы выбрали введение новых интерфейсов для каждого из вызовов ioctl, которые, как они считали, были необходимы для переносимости приложений. Каждый из этих
492 Глава 10. Управление терминалами вызовов начинается с префикса tc. В системе FreeBSD каждый из этих вызовов пре- преобразуется (возможно, после предварительной обработки) в вызов ioctl. Следующий набор команд ioctl применим конкретно к дисциплине линии связи стандартного терминала, хотя все линии связи должны поддерживать по крайней мере первые два. Другие дисциплины обычно поддерживают другие команды ioctl. Этот список неисчерпывающий, хотя он представляет все команды, которые обычно используются. TIOCGETD Получение (установка) дисциплины линии связи для данного канала. TIOCSETD TIOCGETA Получение (установка) параметров termios для данного канала, TIOCSETA включая скорость линии, параметры поведения и специальные сим- символы (например, символ стирания и символ сброса). TIOCSETAW Устанавливает параметры termios для данной линии после ожида- ожидания освобождения выходного буфера (но без уничтожения симво- символов во входном буфере). TIOCSETAF Устанавливает параметры terios для данной линии после ожидания освобождения выходного буфера и уничтожения всех символов во входном буфере. TIOCFLUSH Уничтожение всех символов во входном и выходном буферах. TIOCDRAIN Ожидание освобождения выходного буфера. TIOCEXCL Получение (освобождение) исключительного использования TIOCNXCL данной линии. TIOCCBRK Очистка (установка) для линии аппаратного условия BREAK терми- TIOCSBRK нала. TIOCCDTR Очистка (установка) на линии сигнала готовности данных терминала. TIOCSDTR TIOCGPGRP Получение (установка) группы процесса, связанной с данным TIOCSPGRP терминалом (см. раздел 10.5). TIOCOUTQ Возвращение числа символов в выходной очереди терминала. TIOCSTI Введение символов во входной буфер терминала, как если бы они были набраны пользователем. TIOCNOTTY Отсоединение текущего управляющего терминала от процесса (см. раздел 10.5). TIOCSCTTY Назначение терминала в качестве управляющего для процесса (см. раздел 10.5).
10.4. Структура tty 493 TIOCSTART TIOCSTOP TIOCGWINSZ TIOCSWINSZ Запуск (остановка) вывода в терминал. Получение (установка) размера терминала или окна для линии терминала; размер окна включает ширину и высоту в символах и (необязательно, на графических дисплеях) в пикселах. 10.4. Структура tty Каждый аппаратный или псевдотерминал имеет структуру для хранения своего со- состояния. Эта структура, структура tty (см. табл. 10.2), содержит информацию о со- состоянии, входную и выходную очереди, режимы и опции, установленные операциями ioctl, перечисленными в разделе 10.3, и номер дисциплины линии связи. Структура tty совместно используется драйвером псевдотерминала или аппаратным драйвером и дисциплиной линии связи. При всех вызовах дисциплины линии связи структура tty необходима в качестве параметра; драйвер находит нужную tty в соответствии с ее идентификатором. Табл. 10.2. Структура tty Тип Символьные очереди Аппаратные параметры Опрос Состояние Описание Очередь непосредственного (raw) ввода Очередь канонического ввода Очередь вывода устройства Верхняя/нижняя границы Идентификатор устройства Функции запуска/остановки вывода Функция установки аппаратного состояния Опрос потока для чтения Опрос потока для записи Состояние termios Группа процесса Сеанс Число столбцов терминала Число строк и столбцов Разделы структуры tty включают следующее: Информация о состоянии терминала. Поле tjstate включает основные возможно- возможности файла (например, ввод/вывод, управляемый сигналами) и временное состоя- состояние для управления потоком и синхронизации. Для аппаратных терминалов оно включает состояние линии (открыта, присутствует несущая или ожидание несущей). Очереди ввода и вывода. Драйвер вывода перемещает символы, помещенные в очередь вывода t_outq. Дисциплины линии связи обычно используют для ввода t_rawq и t_canq (неканонические и канонические очереди); в строковом режиме
494 Глава 10. Управление терминалами каноническая очередь содержит полные строки, а неканоническая очередь со- содержит любую текущую незавершенную строку. Кроме того, tjiiwat предусматри- предусматривает границу, по достижении которой очередь, пытающаяся записать в терминал, будет помещена в состояние сна в ожидании очистки очереди вывода; tjowat пре- предоставляет границу, при достижении которой спящие потоки, ожидающие записи в терминал, будут пробуждены. Аппаратные и программные режимы и параметры и специальные символы. Струк- Структура tjermios содержит информацию, установленную TIOCSETA, TIOCSETAF hTIOCSETAW. В частности, скорость линии связи содержится в полях c_ispeed и c_ospeed структуры tjermios, управляющая информация в полях cjflag, cjoflag, cjcflag и cjflag, а специальные символы (конец файла, конец строки, альтернатив- альтернативный конец строки, стирание, стирание слова, сброс, повторная печать, прерывание, завершение, приостановка, запуск, остановка, составной символ, прерывание со- состояния, сбрасывание вывода и сведения о VMIN и VTIME) в поле ссс. * Информацию об аппаратном драйвере. Эта информация включает tjoproc и tjstop, процедуры драйвера, которые запускают и останавливают передачу после того, как данные помещены в очередь вывода; tj?aram, процедуру драйвера, которая устанавливает аппаратное состояние; и tjcdev, устройство, связанное с линией связи терминала. Программное состояние дисциплины линии связи терминала. Это состояние включает число столбцов терминала и счетчики для обработки табуляций и стира- стирания (tjzolumn, trocount и tjrocol), группу процессов терминала (tj?grp), сеанс, связанный с терминалом (tjsessiori), и информацию обо всех процессах, ожи- ожидающих ввод или вывод (tjsel или t_wsel). Размер терминала или окна (t_winsize). Эта информация не используется ядром, но хранится здесь для предоставления приложениям согласованных и правильных сведений. Кроме того, FreeBSD предоставляет сигнал SIGWINCH (унаследован- (унаследованный от SunOS фирмы Sun Microsystems), который может быть отправлен, когда размер окна изменяется. Этот сигнал полезен для оконных пакетов, таких, как X Window System [Scheifler & Gettys, 1986], которые дают пользователям возмож- возможность динамически изменять размеры окон; программы, такие, как текстовые ре- редакторы, работающие в таких окнах, нуждаются в информировании о том, что что-то изменилось и что они должны повторно проверить размер окна. Структура tty инициализируется процедурой открытия драйвера терминала и про- процедурой открытия дисциплины линии связи.
10.5. Группы процессов, сеансы и управление терминалом 495 10.5. Группы процессов, сеансы и управление терминалом Возможности управления процессами (заданиями), описанные в разделе 4.8, зависят от управления доступом к терминалу системы ввода/вывода терминала. Каждое зада- задание (группа процессов, управляемая как единое целое) различают по ID группы про- процесса. Структура каждого терминала содержит указатель на соответствующий сеанс. Когда процесс создает новый сеанс, у этого сеанса нет связанного с ним терминала. Чтобы по- получить связанный с ним терминал, лидер сеанса должен сделать системный вызов ioctl, используя дескриптор файла, связанный с терминалом, и указав флаг TIOCSCTTY. Когда ioctl завершается успешно, лидер сеанса становится управляющим процессом. Кроме того, каждая структура терминала содержит ID группы процесса активной группы про- процессов. Когда лидер сеанса получает связанный с ним терминал, в качестве группы про- процессов устанавливается группа процессов лидера сеанса. Группу процессов терминала можно изменить, сделав системный вызов ioctl с использованием дескриптора файла, связанного с терминалом, и указав флаг TIOCSPGRP. Любой группе процессов в сеансе разрешено стать активной группой процессов для терминала. Сигналы, генерируемые символами, набираемыми на терминале, посылаются всем процессам в активной группе процессов терминала. По умолчанию некоторые из этих сигналов вызывают остановку группы процессов. Оболочка создает задания в виде групп процессов, устанавливая в качестве ID группы процессов PID первого процесса в группе процессов. Каждый раз, когда она активирует новое задание, оболочка уста- устанавливает в качестве группы процессов новую группу процессов. Таким образом, группа процессов терминала является идентификатором для группы процессов, которые в настоящий момент управляют терминалом, т.е. для группы процессов, рабо- работающих в активном режиме. Другие процессы могут работать в фоновом режиме. Если фоновой процесс пытается прочесть с терминала, его группе процессов направ- направляется другой сигнал, который останавливает группу процессов. В необязательном порядке могут также останавливаться фоновые процессы, пытающиеся вывести что- либо в терминал. Эти правила управления операциями ввода и вывода применяются лишь к операциям на управляющем терминале. Когда для терминала потеряна несущая (например, при отключении модема или когда потеряно сетевое соединение), лидеру сеанса, связанного с терминалом, посыла- посылается сигнал SIGHUP. Если лидер сеанса завершается, управляющий терминал освобо- освобождается, и это делает недействительными все открытые в системе дескрипторы для этого терминала. Это освобождение гарантирует, что процессы, удерживающие де- дескрипторы файлов для терминала, не могут получить доступ к терминалу после того, как терминал получает другой пользователь. Это освобождение действует на уровне vnode. Для процесса чтение или запись могут по какой-нибудь причине находиться
496 Глава 10. Управление терминалами в состоянии сна - например, если он был в фоновой группе процессов. Поскольку у такого процесса дескриптор файла был бы уже освобожден посредством уровня vnode, единственное чтение или запись спящего процесса завершилась бы после сис- системного вызова revoke. Чтобы избежать данной проблемы безопасности, система про- проверяет номер поколения tty, когда процесс пробуждается из состояния сна в терминале, и, если номер изменился, повторно запускает системный вызов read или write. 10.6. С-списки Система ввода/вывода терминалов имеет дело с данными в блоках сильно раз- различающихся размеров. Большинство операций ввода и вывода имеют дело с отдельны- отдельными символами (набираемыми символами ввода и их эхом на выводе). Вводимые сим- символы обычно объединяются с предыдущим вводом с образованием строк различных размеров. Некоторые операции вывода включают большие объемы данных, такие, как обновления экрана или выводы других команд. Структуры данных, первоначально спроектированные для драйверов терминалов, символьный блок, С-блок, и символь- символьный список, С-список, по-прежнему используются в FreeBSD. Каждый С-блок являет- является буфером фиксированного размера, содержащим связывающий указатель и про- пространство для буферированных символов и ссылочной информации. Его размер явля- является степенью 2, и он выровнен таким образом, что система может вычислить границы между блоками, сбрасывая с использованием маски младшие биты указателя. FreeBSD использует 128-байтные С-блоки, хранящие 108 символов и массив ссылочных флагов A бит на символ). Очередь вводимых или выводимых символов описывается С-спи- ском, который содержит указатели на первый и последний символы и число символов в очереди (см. рис. 10.1). Оба указателя указывают на символы, хранящиеся в С-бло- ках. Когда символ удаляется из очереди С-списка, счетчик уменьшается, а указатель на первый символ увеличивается. Если указатель перемещается дальше конца первого С-блока в очереди, получается указатель на следующий С-блок из прямого указателя в начале текущего С-блока. После обновления прямого указателя пустой С-блок поме- помещается в свободную очередь. Аналогичный процесс добавляет символ в очередь. Если в текущем буфере нет пространства, из свободного списка выделяется другой буфер, связывающий указатель последнего буфера устанавливается на новый буфер, а ко- конечный указатель устанавливается на размещение первого места сохранения в новом буфере. Символ сохраняется в месте, на который указывает конечный указатель; конечный указатель увеличивается, число символов также увеличивается. С-списками управляет набор служебных процедур: getcQ удаляет из С-списка следующий символ и возвращает этот символ, aputcf) добавляет символ в конец С-списка. Процедура getcQ возвращает целое, а процедураputcQ принимает в качестве аргумента целое. Младшие 8 битов этого значения являются фактическим символом. Старшие биты используются для предоставления ссылок и другой информации. Группы символов могут добавлять- добавляться к С-списку или удаляться из него с помощью b_to_q() и q_to_b() соответственно,
10.7. RS-232 и управление модемом 497 в этом случае нельзя указать или вернуть дополнительную информацию (например, ссылочные сведения). Драйверу терминала требуется также возможность удаления символа из конца очереди с помощью unputcQ, проверки символов в очереди с помо- помощью nextcQ и соединения очередей с помощью catqQ. Счетчик символов Первый символ Последний символ 118 Документация является касторовым маслом программирования Менеджеры знают, что это должно быть хорошим, потому что программисты Так ее ненавидят РИС. 10.1. Структура С-списка Когда UNIX разрабатывалась на компьютерах с небольшими адресными простран- пространствами, проектирование буферов для использования драйверов терминалов представ- представляло собой трудную задачу. С-список и С-блок предоставили элегантное решение про- проблемы хранения очередей данных произвольной длины для входной и выходной очере- очередей терминалов, когда последние были спроектированы для машин с небольшой памя- памятью. На современных машинах с гораздо большими адресными пространствами было бы лучше использовать структуры данных, которые используют меньше процес- процессорного времени на символ за счет снижения эффективности использования памяти. FreeBSD по-прежнему использует первоначальную структуру данных С-списка из-за высокой стоимости работы по преобразованию в новую структуру данных; изменение структуры данных потребовало бы изменений во всех дисциплинах линий связи и во всех драйверах устройств терминалов, что представляло бы существенный объем работ. Разработчики могли бы просто изменить реализации процедур интерфейса, но процедуры по-прежнему вызывались бы для каждого отдельного символа, если не был бы изменен фактический интерфейс, а изменение интерфейса потребовало бы измене- изменения драйверов. 10.7. RS-232 и управление модемом Большинство терминалов и модемов подключаются через асинхронные последова- последовательные порты RS-232. Этот вид соединения поддерживает еще несколько линий помимо тех, которые передают и принимают данные. Система обычно поддерживает лишь немногие из этих линий. Чаще всего используются линии, показывающие, что
498 Глава 10. Управление терминалами оборудование на каждом конце готово к передаче данных. Спецификация электриче- электрических параметров RS-232 асимметрична: каждая линия управляется одним из двух под- подключенных устройств, а выборка осуществляется другим устройством. Таким обра- образом, один конец любого нормального соединения должен быть подключен как терми- терминальное оборудование (data-terminal equipment - DTE), такое, как терминал, а другой как телекоммуникационное оборудование (data-communications equipment - DCE), такое, как модем. Обратите внимание, что терминал в DTE означает конечную точку: терминал, на котором печатают люди, является DTE, а компьютер также является DTE. Линия готовности терминала к передаче данных (data-terminal-ready - DTR) является выходом на конце DTE, которая служит в качестве индикатора готовности. В другом направлении линия детектирования несущей данных (data-carrier-detect - DCD) указы- указывает, что устройство DCE готово для передачи данных. Изначально интерфейсы терми- терминалов VAX все подключались, как DTE (они могли подключаться непосредственно к модемам или локальным терминалам через нуль-модемные кабели). Терминология, используемая в драйверах терминалов и командах FreeBSD, отражает такую ориента- ориентацию, хотя даже многие компьютеры ошибочно используют обратное соглашение. Когда терминальные устройства открыты, вывод DTR устанавливается, так что под- подключенный модем или оборудование могут начать работу. Если на линии поддерживает- поддерживается управление модемом, открытие не завершается, если для линии не была указана опция O_NONBLOCK или не установлен управляющий флаг CLOCAL, а данные не передают- передаются до тех пор, пока не обнаружена входная несущая DCD или не установлен флаг CLO- CLOCAL. Таким образом, открытие линии, подключенной к модему, будет заблокировано до тех пор, пока не будет установлено соединение; соединение обычно имеет место, когда получен вызов от удаленного модема. Затем данные могут передаваться до тех пор, пока остается включенной несущая. Если модем теряет соединение, линия DCD выключается и последующие чтение и запись завершаются ошибкой. Порты, использующиеся с локальными терминалами или другим оборудованием DTE, соединяются нуль-модемным кабелем, который соединяет DTR на каждом конце с DCD на другом конце. Альтернативно вывод DTR в хосте может быть возвращен обратно на вход DCD. Если кабель или устройство не поддерживает управления моде- модемом, система будет игнорировать состояние управляющих сигналов модема, когда для линии установлен управляющий флаг CLOCAL. Наконец, некоторые драйверы можно сконфигурировать так, чтобы игнорировать управляющий ввод для модема. 10.8. Операции терминала Изучив общую структуру системы ввода/вывода терминалов и описав структуры данных этой системы, а также оборудование, которое контролируется системой, мы продолжим описание системных операций ввода/вывода терминала. Мы исследуем работу драйвера устройства псевдотерминала и дисциплину линии связи по умолчанию termios.
10.8. Операции терминала 499 Open Каждый раз, когда открывается ведущая сторона ранее неиспользуемого устройства псевдотерминала, вызывается процедура open драйвера псевдотерминала. Процедура open инициализирует структуру tt, устанавливает режимы по умолчанию, устанавлива- устанавливает в ноль информацию о размере терминала для линии связи, указывая на неизвестный размер и устанавливает состояние в TS_ISOPEN. Затем драйвер передает управление через свой открытый элемент дисциплине линии связи. Настройка терминала заверша- завершается, когда соответствующая ведомая сторона псевдотерминала открывается приложе- приложением, которое будет его использовать. Для аппаратных устройств, которые поддерживают линии управления модемом, процедура open предпринимает дополнительные шаги. Она начинает с включения вы- выходной линии DTR. Если для терминала не установлен управляющий флаг CLOCAL, а в вызове open не указан флаг O_NONBLOCK, процедура open блокируется в ожида- ожидании подтверждения по входной линии DCD. Некоторые драйверы поддерживают флаги устройств для перекрывания управления модемом; эти флаги устанавливаются в файле конфигурирования системы и сохраняются в структурах данных драйверов. Если в флагах устройства установлен бит, соответствующий номеру линии терминала, линии управления модемом на входе игнорируются. Когда в линии обнаруживается сигнал несущей, в состоянии терминала устанавливается бит TS_CARR_ON. Затем линия помечается как открытая (бит состояния TS_OPEN), а драйвер передает управ- управление дисциплине линии связи, как только что было описано для псевдотерминалов. Дисциплина выходной линии связи После того как линия связи была открыта, запись в полученный дескриптор файла вызывает передачу вывода в терминал. Записи в символьные устройства приводят к вызову элемента записи устройства, djwrite, с указателем на устройство, структуру uio, описывающую записываемые данные, и флаг, определяющий, является ли ввод/ вывод неблокирующим. Драйвер псевдотерминала использует указатель на устройст- устройство, чтобы найти нужную структуру tty, а затем вызвать элемент Ijwrite дисциплины линии связи со структурой tty и структурой uio в качестве параметров. Процедура записи дисциплины линии связи выполняет большую часть работы преобразования вывода и управления потоком. Она отвечает за копирование данных в ядро из процесса пользователя и за помещение транслированных данных в выход- выходную очередь псевдотерминала. Процедура записи драйвера терминала ttwriteQ сначала проверяет, разрешено ли текущему процессу в это время записывать в терминал. Чтобы позволить осуществлять вывод только активному процессу (см. раздел 10.5), пользователь может установить опцию tty. Если эта опция установлена и если линия терминала является управляющий терминалом для процесса, процесс должен делать немедленный вывод лишь в том случае, если он находится в активной группе процес- процессов (т.е. если группа процессов процесса и терминала одна и та же). Если процесс не
500 Глава 10. Управление терминалами находится в активной группе процессов, а сигнал SIGTTOU вызвал бы приостановку процесса, группе процессов процесса направляется сигнал SIGTTOU. В этом случае попытка записи будет предпринята снова, когда пользователь сделает группу процес- процессов активной. Если процесс находится в активной группе процессов или сигнал SIGT- SIGTTOU не приостановил процесс, запись продолжается как обычно. Когда ttwriteQ подтвердила, что запись разрешена, она входит в цикл, который ко- копирует записываемые данные в ядро, проверяет на необходимость каких-нибудь пре- преобразований вывода и помещает данные в выходную очередь для терминала. Она пре- предотвращает переполнение очереди, блокируясь, если очередь заполняется до того, как обработаны все символы. Ограничение размера очереди, верхняя граница (high water- watermark), зависит от скорости линии связи; для псевдотерминалов скорость линии уста- устанавливается на максимальную скорость передачи в бодах, поэтому у них верхняя гра- граница составляет несколько тысяч символов. Нижняя граница (low watermark) устанав- устанавливается примерно в половину от верхней границы. Когда ttwriteQ вынуждена ждать освобождения вывода перед тем, как продолжить, она устанавливает в состоянии структуры tty флаг TS_SO_OLOWAT, чтобы запросить пробуждение, когда очередь очистится до нижней границы. Проверка размера очереди и последующего периода сна должны упорядочиваться таким образом, что удаление символов из выходной очереди после состояния сна гарантируется (см. рис. 10.2). Если символы можно уда- удалить после проверки, но до состояние сна, их удаление может вызвать очистку очереди ниже нижней границы, вызывая тем самым пробуждение потока до того, как он пере- перешел в состояние сна. Войдя в состоянии сна, поток был бы заблокирован навсегда, по- поскольку событие, которого бы он ждал, уже произошло. Как показано на рис. 10.2, гарантия осуществляется путем получения мьютекса, которым нужно владеть при добавлении или удалении символов из очереди вывода для терминала. struct tty *tp; ttstart(tp); mtx_lock(&tp->t_outmtx); if (tp->t_outq.c_cc > high-water-mark) { /* Запрос пробуждается, когда вывод <= нижней границы */ tp->t_state |= TS_SO_OLOWAT; msleep(&tp->t_outq, &tp->t_outmtx, TTOPRI, "ttywri", 0); } mtx_unlock(&tp->t_outmtx); Рис. 10.2. Псевдокод для проверки очереди вывода в дисциплине линии связи После проверки ошибок, разрешений и управления потоком, ttwriteQ копирует данные пользователя в локальный буфер в виде порций максимум в 100 символов, используя uiomoveQ. (Значение 100 используется потому, что буфер хранится в стеке, и поэтому он не может быть больше.) Когда драйвер терминала сконфигурирован в нека- неканоническом режиме, преобразования отдельных символов не выполняются, а сразу обра- обрабатывается весь буфер. В каноническом режиме драйвер терминала находит группы сим-
10.8. Операции терминала 501 волов, не требующие трансляции, сканируя выходную строку, просматривая по очереди каждый символ в таблице, которая помечает символы, которым может потребоваться трансляция (например, конец строки), или символы, которые нужно расширить (напри- (например, табуляция). Каждая группа символов, которым не нужна особая обработка, помеща- помещается в выходную очередь с использованием b_to_q(). Оставшиеся специальные символы выводятся с помощью ttyoutputQ. В любом случае ttwriteQ должна проверить, что доступ- доступно достаточно С-блоков; если нет, она ждет в течение небольшого промежутка времени (входя в состояние сна для Ibolt до 1 секунды), затем повторяет попытку. Процедурой, которая выполняет вывод с трансляцией, является ttyoutputQ, которая принимает единственный символ, обрабатывает его нужным образом и помещает результат в выходную очередь. В зависимости от режима терминала могут быть сделаны следующие преобразования. Символы табуляции могут быть заменены пробелами. * Символы конца строки могут быть заменены возвратом каретки плюс переводом строки. Как только данные помещаются в выходную очередь tty, для начала вывода вызы- вызывается ttstartQ. Если только вывод уже не выполняется или не был приостановлен после получения символа остановки, ttstartQ вызывает процедуру запуска, указанную в поле t_oproc tty. Для псевдотерминала процедура запуска проверяет наличие потока, спящего на ведущей стороне, и, если он есть, пробуждает его, чтобы он мог получить данные. Для аппаратного терминала процедура запуска начинает пересылать символы по последовательному каналу. Когда все данные были обработаны и помещены в вы- выходную очередь, ttwriteQ возвращает указание, что write завершилась успешно. Вывод на терминал Символы удаляются из выходной очереди либо потоком, работающим на ведущей стороне псевдотерминала, либо драйвером аппаратного устройства. Каждый раз, когда число сим- символов в выходной очереди становится меньше нижней границы, процедура вывода про- проверяет, установлен ли флаг TSSOOLOWAT, обозначающий, что поток ожидает про- пространство в выходной очереди и должен быть пробужден. Кроме того, вызывается selwake- ирО, и, если поток записан в t_wsel как ожидающий вывода, этот поток уведомляется. Вывод продолжается до тех пор, пока выходная очередь не опустеет. Ввод с терминала В отличие от вывода, ввод с терминала начинается не посредством системного вызова, а возникает асинхронно, когда линия терминала получает символы от сеанса удаленной регистрации или локально от клавиатуры. Таким образом, обработка ввода в системе терминала происходит главным образом во время прерывания. Когда по сети от сеанса удаленной регистрации поступает символ, локально рабо- работающий демон удаленной регистрации записывает его в ведущую часть псевдотерми-
502 Глава 10. Управление терминалами нала. Ведущая сторона псевдотерминала передаст этот символ в качестве ввода дисци- дисциплине линии связи для получающего tty посредством элемента l_rint последнего: (*linesw[tp->t_line].l_rint)(input-character, tp); Для локально подсоединенного оборудования, такого, как клавиатура, вводимый символ будет передан драйвером устройства непосредственно элементу l_rint прини- принимающего tty. В любом случае вводимый символ передается процедуре l_rint в виде целого числа. Младшие 8 битов целого представляют фактический символ. Символы, полученные от локально подсоединенного оборудования, могут иметь аппаратно-об- наруженные ошибки четности, символы прерывания или ошибки кадров. Такие ошибки отображаются посредством установки флагов в старших битах целого числа. Процедурой прерывания приемника (l_rint) для дисциплины линии связи обычного терминала является ttyinputQ. Когда обнаруживается условие break (символ длиннее обычного с лишь нулевыми битами), он игнорируется, либо процессу передается символ прерывания или null, в зависимости от режима терминала. Здесь используется интерпре- интерпретация ввода терминала, описанная в разделе 10.1. Вводимые символы при желании ото- отображаются в виде эха. В неканоническом режиме символы помещаются в очередь непо- непосредственного ввода без интерпретации. В противном случае большей частью работы, делаемой ttyinputQ, является проверка наличия символов с особым значением и выполне- выполнение соответствующих действий. Другие символы помещаются в непосредственную очередь. В каноническом режиме, если полученный символ является возвратом каретки или другим символом, который делает текущую строку доступной программе, читающей с терминала, содержимое непосредственной очереди добавляется в канонизированную очередь и вызывается ttwakeupQ, чтобы уведомить все процессы, ожидающие ввода. В неканоническом режиме ttwakeupQ вызывается при обработке каждого символа. Она пробудит все процессы, находящиеся в состоянии сна в ожидании ввода для read, и уве- уведомит процессы, ожидающие ввод. Если терминал был установлен для управляемого сигналами ввода/вывода с использованием fcntl и флагом FASYNC, группе процессов, управляющих терминалом, посылается сигнал SIGIO. ttyinputQ должна также проверить, что очередь ввода не становится слишком большой, исчерпывая блоки С-списков. Для псевдотерминалов, когда очередь ввода становится пол- полной, ядро просто останавливает чтение символов из главной стороны, что вызывает оста- остановку чтения из сети демона локальной регистрации. В конечном счете управление пото- потоком сети блокирует процесс, генерируюгций символы. Для символов, поступающих от ло- локально подсоединенного оборудования, такого, как клавиатура, вводимые символы отбра- отбрасываются, когда достигается предел (8192 символа). Если установлен флаг termios IXOFF, сквозное управление потоком вызывается, когда очередь становится заполненной наполо- наполовину, путем вывода символа остановки (обычно XOFF или control-S). До этого момента вся обработка асинхронна и имеет место независимо от того, ожидает ли на терминальном устройстве вызов read. Таким образом, опережающий ввод с клавиатуры допускается вплоть до заполнения очередей ввода.
10.8. Операции терминала 503 В конечном счете для дескриптора файла терминального устройства делается вызов read. Подобно всем вызовам чтения из специального символьного устройства, данное при- приводит к вызову элемента d_read драйвера с указателем на устройство, структурой uio, опи- описывающей читаемые данные, и флагом, определяющим, является ли ввод/вывод небло- неблокирующим. Драйверы устройств терминалов используют указатель на устройство для на- нахождения структуры tty для устройства, а затем вызывают элемент Ij-ead дисциплины линии связи, чтобы обработать системный вызов. Элементом l_read для драйвера терминала является ttreadQ. Подобно ttwriteQ, ttreadQ сначала проверяет, является ли процесс частью сеанса и группы процессов, свя- связанных в данный момент с терминалом. Если процесс является членом сеанса, связанно- связанного в настоящий момент с терминалом, если он есть, и является членом текущей группы процессов, чтение продолжается. В противном случае, если SIGTTIN приостановил бы процесс, этой группе процессов посылается SIGTTIN. В данном случае попытка чтения буцет сделана снова, когда пользователь сделает группу процессов активной. В против- противном случае возвращается ошибка. Наконец, ttreadQ проверяет данные в соответствующей очереди (канонической очереди в каноническом режиме, непосредственной очереди в неканоническом режиме). Если данных нет, ttreadQ возвращает ошибку EAGAIN, если терминал использует неблокирующий ввод/вывод; в противном случае она входит в со- состояние сна с адресом непосредственной очереди. Когда ttreadQ пробуждается, она повторно запускает обработку с начала, поскольку состояние терминала или группа про- процесса могли измениться, пока она находилась в состоянии сна. Когда в очереди, которую ожидает ttreadQ^ есть символы, они удаляются из очереди по одному функцией getcQ и копируются в буфер пользователя с помощью ureadcQ. В кано- каноническом режиме определенные символы проходят особую обработку при удалении из очереди: символ отложенной приостановки вызывает остановку текущей группы процес- процессов сигналом SIGTSTP, а символ конца файла завершает чтение без передачи в програм- программу пользователя. Если предыдущих символов не было, символ конца файла приводит к возврату нуля прочитанных символов, и это интерпретируется программой пользовате- пользователя как конец файла. Однако большая часть особой обработки вводимых символов осуще- осуществляется, когда символ вводится в очередь. Например, транслирование возврата каретки в новые строки, основанные на флаге ICRNL, должно осуществляться, когда символ по- получен впервые, поскольку символ новой строки пробуждает ждущие в каноническом режиме процессы. В неканоническом режиме символы в ходе обработки не проверяются. Символы обрабатываются и возвращаются пользователю до тех пор, пока число символов в структуре uio не достигнет нуля, очередь не будет исчерпана или (в кано- каноническом режиме), не будет достигнут конец строки. Когда вызов read возвращается, число возвращенных символов будет числом, на которое было уменьшено запрошен- запрошенное число при обработке символов. После того как чтение завершается, если вывод терминала был заблокирован сиг- сигналом остановки, отправленным из-за заполнения очереди, а теперь очередь заполнена менее чем на 20 процентов, посылается символ запуска (обычно XON, control-Q).
504 Глава 10. Управление терминалами Процедура ioctl В разделе 10.3 был описан интерфейс пользователя к драйверам терминалов и дисципли- дисциплинами лини связи, доступ к большинству из которых осуществляется посредством систем- системного вызова ioctl. Большая часть этих вызовов манипулирует программными опциями в дисциплине линии связи терминала; некоторые из них влияют также на работу асин- асинхронной аппаратуры последовательного порта. В частности, скорость аппаратной линии, размер слова и четность вычисляются из этих установок. Поэтому вызовы ioctl обрабаты- обрабатываются как дисциплиной текущей линии связи, так и аппаратным драйвером. Процедура dioctl драйвера устройства вызывается, помимо других аргументов, с указателем на устройство, командой ioctl и указателем на буфер данных, когда ioctl вы- выполняется для символьного специального файла. Подобно процедурам чтения и записи, процедуры ioctl большинства драйверов терминалов находят структуру tty для устройст- устройства, а затем передают управление дисциплине линии связи. Процедура ioctl дисциплины линии связи выполняет специфические для дисциплины действия, включая изменение дисциплины линии связи. Если процедура дисциплины линии связи завершается не- неудачей, драйвер немедленно возвращает ошибку, как показано на рис. 10.3. В противном случае драйвер вызовет потом процедуру ttioctlQ, которая выполняет наиболее обычную обработку терминала, включая изменение параметров терминала. Если ttioctlQ заверша- завершается неудачей, драйвер немедленно вернет ошибку. В противном случае некоторые драй- драйверы реализуют дополнительные команды ioctl, которые выполняют специфическую для аппаратуры обработку, например манипулирование управляющим выводом модема. Эти команды не распознаются дисциплиной линии связи или обычной обработкой терминала и поэтому должны выполняться драйвером. Процедура ioctl возвращает номер ошибки, если обнаружена ошибка, или ноль, если команда была успешно обработана. Если коман- команда не распознана, в переменной еггпо устанавливается значение ENOTTY. error = (*linesw[tp->t_line].l_ioctl)(tp, cmd, data, flag); if (error >= 0) return (error); error = utioctl(tp, cmd, data, flag); if (error >= 0) return (error); switch (cmd) { case TIOCSBRK: /* специфические для аппаратуры команды */ return @); case TIOCCBRK: return (Ob- default: return (ENOTTY); } Рис. 10.3. Обработка возвращения ошибки от дисциплины линии связи
10.8. Операции терминала 505 Переходы модема Способ использования системой управляющих линий модема на линиях терминалов был представлен в разделе 10.7. Большинство терминального оборудования поддержи- поддерживает по крайней мере набор управляющих линий модема, используемых FreeBSD; те, которые этого не делают, действуют так, как если бы несущая была всегда включена. При открытии устройства включается вывод DTR, затем проверяется состояние ввода несущей. Если состояние ввода несущей впоследствии изменяется, это изменение должно быть обнаружено и обработано драйвером. У некоторых устройств есть от- отдельное прерывание, которое сообщает об изменениях состояния управления моде- модемом; другие сообщают о таких изменениях вместе с другими сведениями о состоянии при получении символов. Некоторые устройства не вызывают прерывания при измене- изменении управляющих линий модема, и драйвер должен периодически проверять их со- состояние. Когда обнаружено изменение, дисциплина линии связи уведомляется посред- посредством вызова ее процедуры Ijnodem с новым состоянием ввода несущей. Обычная процедура драйвера терминала модема ttymodemQ поддерживает в струк- структуре tty состояние флаг TS_CARR_ON и обрабатывает соответствующие изменения со- состояния. Когда обнаружена несущая, осуществляется пробуждение любого процесса, ожидающего завершения открытия. Когда несущая пропадает в открытой линии, лидеру сеанса, связанному с терминалом (если он есть), посылается сигнал разъединения SIGH- UP, и очереди терминала очищаются. Возвращаемое значение ttymodemQ указывает, должен ли драйвер поддерживать свой вывод DTR. Если значение нулевое, DTR должен быть выключен. ttymodemQ реализует также непрозрачную опцию терминала для ис- использования линии несущей для взаимодействия управления потоком, остановки вывода при исчезновении несущей и его восстановления при появлении. Закрытие терминальных устройств Когда закрывается последняя ссылка на терминальное устройство или для устройства делается системный вызов revoke, вызывается процедура close драйвера устройства. Как дисциплине линии связи, так и драйверу устройства может потребоваться посте- постепенное закрытие. Процедура драйвера устройства сначала вызывает процедуру закры- закрытия дисциплины линии связи. Стандартный элемент ttylcloseQ закрытия дисциплины линии связи ожидает окончания всех ожидающих выводов (если терминал не был открыт с установленным флагом O_NONBLOCK и несущая по-прежнему присутству- присутствует), затем очищает очереди ввода и вывода. (Обратите внимание, что закрытие может быть прервано сигналом во время ожидания завершения вывода.) Аппаратный драйвер может очистить любые ожидающие операции, такие, как передача прерывания. Если спомощью вызова ioctl TIOCHPCL был установлен бит состояния TS_HURCLS, DTR отключается, чтобы разорвать линию. В конечном счете процедура драйвера устройст- устройства вызывает ttycloseQ, которая очищает все очереди, увеличивает номер поколения таким образом, чтобы ожидающие чтения и записи могли обнаружить повторное использование терминала, и очищает состояние терминала.
506 Глава 10. Управление терминалами 10.9. Другие дисциплины линии связи Мы исследовали работу системы ввода/вывода терминала с использованием стандарт- стандартных ориентированных на терминалы процедур дисциплин линий связи. Для полноты мы кратко опишем другие классы дисциплин линий связи в системе, использующиеся для подключения последовательных каналов к сети. Протокол двухточечного соединения (РРР), дисциплина двухточечной линии связи используется сетевым программным обеспечением для инкапсуляции и пере- передачи дейтаграмм протокола Интернета (IP) через асинхронные последовательные каналы [Simpson, 1994]. (Информацию об IP см. в главе 13.) Дисциплина линии связи РРР дает возможность устанавливать сетевые соединения с умеренной скоростью с ма- машины, в которой отсутствует высокоскоростное сетевое оборудование. Программа ррр открывает последовательный канал, устанавливает его скорость и вводит дисциплину линии связи РРР. Процедура open дисциплины линии связи РРР связывает линию связи терминала с предварительно сконфигурированным сетевым интерфейсом и подготавливается к передаче и приему сетевых пакетов. Когда сетевой адрес интерфейса с помощью программы ifconfig установлен, сеть будет направлять пакеты через линию связи РРР системе, к которой она подключена. Выходной путь запускается каждый раз, когда пакет выводится через интерфейс РРР. Пакеты собираются в одной из двух очередей: в одной для интерактивного трафика, а в другой для прочего трафика. Интерактивный трафик имеет преимущество перед ос- остальным трафиком. Дисциплина РРР помещает обрамляющие символы и данные сле- следующего пакета в выходную очередь tty, сжимая иногда заголовки пакетов. Затем она начи- начинает передачу, вызвав ttstartQ, которая в свою очередь вызывает процедуру запуска устрой- устройства, на которое ссылается поле t_oproc tty. Она может перед своим возвращением помес- поместить в выходную очередь множество пакетов, пока в системе хватает блоков С-списков. Однако она останавливает перемещение пакетов в выходную очередь tty, когда счетчик символов достиг нижней границы F0 байтов) таким образом, чтобы будущий интерактив- интерактивный трафик не блокировался неинтерактивным трафиком, уже находящимся в выходной очереди. Когда передача завершается, драйвер устройства вызывает стартовую процедуру РРР, которая продолжает помещать данные в выходную очередь до тех пор, пока все пакеты не будут отправлены или очередь снова не достигнет предела. Когда получены символы из линии, которая использует дисциплину РРР, символы данных помещаются в сетевой буфер. Когда пакет завершается символом обрамления, при необходимости распаковывается заголовок пакета, пакет передается сетевому про- протоколу и буфер инициализируется повторно.
Упражнения 507 Упражнения ЮЛ. Какие два общих режима есть для ввода с терминала? Какой режим чаще всего используется, когда пользователи работают с интерактивным экранным редактором? 10.2. Объясните, почему для работы с вводом терминала имеются две очереди сим- символов. Опишите использование каждой из них. 10.3. Что мы имеем в виду, когда говорим, что в линии терминала поддерживается управление модемом? Как обычно используются терминальные линии этого вида? 10.4. Какой сигнал какому процессу, связанному с терминалом, посылается, если пользователь отключает модемную линию в середине сеанса? 10.5. Назовите два устройства, составляющие псевдотерминал. Объясните роль каждого из них. 10.6. Как определяется верхняя граница выходной очереди терминала? *10.7. Рассмотрите возможность, которая помогла бы преподавателю на одном терминале наблюдать и помогать студентам, работающим на других термина- терминалах. Все, что набирают студенты, передавалось бы как в систему в качестве ввода, так и на терминал преподавателя в качестве вывода. Все, что набирает преподаватель, передавалось бы на терминалы студентов в качестве ввода. Опишите, как можно было бы реализовать эту возможность с помощью дис- дисциплины линии связи особого назначения. Опишите дальнейшие полезные обобщения этой возможности. *10.8. Дисциплина линии связи терминала поддерживает логическое стирание вве- введенного текста, когда стираются символы, слова и строки. Помня, что во время ввода пользователем строки продолжается другая деятельность сис- системы, объясните, какие сложности необходимо принять во внимание при реализации этой особенности. Назовите три исключительных случая и опи- опишите их влияние на реализацию. ** 10.9. Предложите другую схему буферирования для замены С-списков.
508 Глава 10. Управление терминалами Ссылки Scheifler & Gettys, 1986. R. W. Scheifler & J. Gettys, "The X Window System", ACM Transactions on Graphics, vol. 5, no. 2, pp. 79-109, April 1986. Simpson, 1994. W. Simpson, "The Point-to-Point Protocol (PPP)", RFC 1661, available from http:// www.faqs.org/rfcs/rfcl661.html, July 1994.
Часть IV Межпроцессное взаимодействие
Глава 11 Межпроцессное взаимодействие FreeBSD предоставляет богатый набор средств межпроцессного взаимодействия (interprocess communication - IPC), предназначенный для поддержки создания рас- распределенных и многопроцессных программ, построенных на основе коммуникацион- коммуникационных примитивов. Поддержка этих средств описывается в данной главе. Ни один механизм не может предоставить все виды межпроцессного взаимодейст- взаимодействия. Подсистемы, предоставляющие IPC в FreeBSD 5.2, можно разделить на две области. Первая обеспечивает IPC в пределах одной системы и включает поддержку семафоров, очередей сообщений и разделяемой памяти. Вторая представляет собой интерфейс соке- тов, который предоставляет единообразный API для сетевой коммуникации. API сокетов глубоко переплетен с сетевой подсистемой. Архитектура сетевой сис- системы описана в главе 12, а сами сетевые протоколы исследуются в главе 13. Проще всего понять материал в этих трех главах, если вы прочтете сначала данную главу до прочтения глав 12 и 13. В конце главы 13 есть раздел, посвященный связыванию всего воедино. 11.1. Модель межпроцессного взаимодействия В проектировании улучшений межпроцессного взаимодействия для UNIX было несколько целей. Самой непосредственной целью было предоставление доступа к ком- коммуникационным сетям, таким, как Интернет [Cerf, 1978]. Предыдущая работа по предо-ставлению сетевого доступа фокусировалась на реализации сетевых протоко- протоколов, экспортировании транспортных средств приложениям через протоколы специаль- специального назначения - часто неуклюжие [Cohen, 1977; Gurwitz, 1981]. В результате каждая новая сетевая реализация приводила к другому интерфейсу приложений, требуя значи- значительного изменения или полной переделки большинства существующих программ. Для 4.2BSD средства межпроцессного взаимодействия были нацелены на предостав-
512 Глава 11. Межпроцессное взаимодействие ление в значительной степени общего интерфейса, чтобы позволить строить сетевые приложения независимо от лежащих в основе коммуникационных средств. Второй целью было дать возможность реализации многопроцессных программ, таких, как распределенные базы данных. Каналы (pipe) UNIX требуют, чтобы все взаи- взаимодействующие процессы происходили от общего родительского процесса. Использо- Использование каналов заставляет проектировать систему с несколько искаженной структурой. Для поддержки не относящихся друг к другу процессов, находящихся локально на одном хосте и удаленно на нескольких хостах, были необходимы новые коммуника- коммуникационные средства. В конце концов стало важно предоставить новые коммуникационные средства, чтобы дать возможность строить службы локальных сетей, такие, как файловые серверы. Целью было предоставление средств, которые можно было бы легко исполь- использовать для поддержки разделения ресурсов в распределенном окружении, а не для строительства распределенной системы UNIX. Средства межпроцессного взаимодействия были разработаны для поддержки: * прозрачности: взаимодействие между процессами не должно зависеть от того, находятся ли процессы на одной и той же машин; * эффективности: применимость любого средства межпроцессного взаимодейст- взаимодействия ограничена производительностью этого средства. Простая реализация меж- межпроцессного взаимодействия часто ведет к сильно модульной, но неэффективной реализации, поскольку большинство средств межпроцессного взаимодействия, особенно относящихся к сетям, разделены на множество уровней. На границе каждого уровня программное обеспечение должно выполнить некоторую работу, либо добавляя информацию в сообщение, либо удаляя ее. FreeBSD вводит лишь те уровни, которые абсолютно необходимы для должного функционирования сис- системы, и не вводит произвольные уровни, когда они не нужны; совместимости: существующие неподготовленные процессы должны быть при- пригодными к использованию в распределенном окружении без изменений. Неподго- Неподготовленный (naive) процесс характеризуется как процесс, который выполняет свою работу, читая из файла стандартного ввода и записывая в файл стандартного выво- вывода. Усовершенствованный (sophisticated) процесс использует для своей работы знание о более богатом наборе интерфейсов, предоставляемых операционной сис- системой. Основной причиной успешности UNIX является то, что операционная сис- система поддерживала модульность с использованием неподготовленных процессов, которые действовали в качестве фильтров потоков байтов. Хотя усовершенство- усовершенствованные приложения, такие, как веб-серверы и экранные редакторы, существуют, коллекция неподготовленных прикладных программ значительно превосходит их численно.
11.1. Модель межпроцессного взаимодействия 513 При проектировании средств межпроцессного взаимодействия разработчики для поддержки этих целей выделяют следующие требования и разрабатывают едино- единообразную концепцию для каждого из них. * Система должна поддерживать коммуникационные сети, которые используют раз- различные наборы протоколов, различные соглашения по именованию, различное ап- аппаратное обеспечение и т. д. Понятие коммуникационного домена было определе- определено по этим причинам. Коммуникационный домен включает стандартную семанти- семантику коммуникации и именования. В различных сетях разные стандарты для имено- именования конечных точек, свойства которых также могут различаться. В одной сети имя может быть фиксированным адресом для коммуникационной точки, тогда как в другой оно может использоваться для обнаружения процесса, который может перемещаться по разным местам. Семантика коммуникации может включать стои- стоимость, связанную с надежной транспортировкой данных, поддержку многоадрес- многоадресных передач, способность передавать права доступа или возможности и т.д. Необходима единообразная абстракция для конечной точки коммуникации, которой можно управлять с помощью дескриптора файла. Сокет является абстрактным объектом, посредством которого отправляются и получаются сооб- сообщения. Сокеты создаются в пределах коммуникационного домена, так же как файлы создаются внутри файловой системы. Однако в отличие от файлов, сокеты существуют, лишь пока на них есть ссылки. * Семантические аспекты коммуникации должны быть сделаны доступными прило- приложениям контролируемым и единообразным способом. Приложения должны иметь возможность запрашивать различные стили коммуникации, такие, как надежный поток байтов или ненадежные дейтаграммы, и эти стили должны быть предоставле- предоставлены согласованно для всех коммуникационных доменов. Все сокеты типизированы в соответствии со своей коммуникационной семантикой. Типы определяются семан- семантическими свойствами, которые поддерживает сокет. Этими свойствами являются: 1. доставка упорядоченных данных; 2. доставка данных без дублирования; 3. надежная доставка данных; 4. ориентированная на соединение коммуникация; 5. сохранение границ сообщений; 6. поддержка внеполосных (out of band, OOB) сообщений. Каналы имеют первые четыре свойства, но не пятое и шестое. Внеполосным явля- является сообщение, которое доставляется получателю вне обычного потока входящих внутриполосных сообщений. Оно обычно связано со срочным или исключительным условием. Соединение является механизмом, который протоколы используют, чтобы
514 Глава 11. Межпроцессное взаимодействие избежать необходимости передавать идентификатор отправляющего сокета с каждым пакетом данных. Вместо этого каждая коммуникационная конечная точка обменивает- обменивается идентификаторами перед передачей любых данных и поддерживает ее таким обра- образом, что ее можно представить в любой момент времени. С другой стороны, при ком- коммуникации без установления соединения требуются исходный и конечный адрес, свя- связанные с каждой передачей. Сокет дейтаграмм предоставляет ненадежную доставку пакетов без установления соединения; поточный (stream) сокет предоставляет надеж- надежный, ориентированный на соединение поток байтов, который может поддерживать передачу внеполосных данных; а сокет упорядоченных пакетов предоставляет упоря- упорядоченную, надежную, недублир о ванную коммуникацию, которая сохраняет границы сообщений. Желательны и могут быть добавлены другие типы сокетов. Процессы должны иметь возможность находить коммуникационные конечные точки таким образом, чтобы их можно было связывать без необходимости родства этих процессов, поэтому сокеты могут быть именованными. Имя сокета осмысленно ин- интерпретируется лишь в контексте коммуникационного домена, внутри которого сокет создан. Имена, используемые большинством приложений, являются удобочитаемыми строками. Однако имя для сокета, который используется внутри коммуникационного домена, обычно является низкоуровневым адресом. Вместо того чтобы помещать функции преобразования имени в адрес в ядро, FreeBSD 5.2 предоставляет функции для использования прикладными программами при преобразованиях имен в адреса. В части данной главы мы называем имя сокета адресом. Использование сокетов В течение последних нескольких лет был написан ряд превосходных книг о програм- программировании сокетов с точки зрения пользователя [Stevens, 1998]. Данный раздел включает краткое описание клиентской и серверной программ, взаимодействующих друг с другом через надежный поток байтов в коммуникационном домене Интернета. Для более подробных сведений по написанию сетевых приложений обращайтесь к приведенным ссылкам. Сначала описывается клиент, а затем сервер. Программа, желающая использовать сокет, создает его при помощи системного вызова socket. int sock, domain = AF_INET, type = SOCK_STREAM, protocol = 0; sock = socket(domain, type, protocol); Тип сокета выбирается в соответствии с характерными особенностями, необходи- необходимыми приложению. В данном примере необходима надежная коммуникация, поэтому выбирается поточный сокет (type = SOCK_STREAM). Параметр domain определяет коммуникационный домен (или семейство протоколов', см. раздел 11.4), в котором должен быть создан сокет, в данном случае Интернет IPv4 (domain = AF_INET). Последний параметр, protocol, может использоваться для указания определенного ком- коммуникационного протокола для использования в поддержке работы сокета. Протоколы
11.1. Модель межпроцессного взаимодействия 515 указываются хорошо известными (стандартными) константами, специфичными для каждого коммуникационного домена. Когда используется ноль, система сама выбирает подходящий протокол. Системный вызов socket возвращает дескриптор файла (неболь- (небольшое целое; см. раздел 6.4), который используется потом во всех последующих опера- операциях с сокетами. После создания сокета следующий шаг зависит от типа используемого сокета. Поскольку данный пример ориентирован на соединение, до использования сокетов необходимо установить соединение. Создание соединения между двумя сокетами обычно требует, чтобы каждый сокет был привязан к адресу, который просто является способом идентификации каждого конца коммуникации. Приложения могут явным образом указать адрес сокета или могут разрешить сис- системе его назначить. Адрес, который должен использоваться с сокетом, должен быть указан в структуре адреса сокета. Формат адресов в различных доменах может быть разным; чтобы допустить широкое разнообразие различных форматов, система рас- рассматривает адреса как массивы байтов переменного размера, имеющие префикс длины и тег, идентифицирующий их формат. У каждого домена есть свой формат адреса, который всегда может быть отображен на более общий формат. Соединение инициируется системным вызовом connect. int error; int sock; /* Ранее создан вызовом socket(). */ struct sockaddr_in rmtaddr; /* Назначен программой. */ int rmtaddrlen = sizeof(struct sockaddr_in); error = connect(sock, (struct sockaddr *)&rmtaddr, rmtaddrlen); Когда вызов connect завершается, у клиента есть полностью работоспособная ком- коммуникационная конечная точка, через которую можно отправлять и получать данные. Сервер после создания сокета идет другим путем. Он должен привязать себя к адресу, а затем принимать входящие соединения от клиентов. Вызов для привязки адреса к сокету следующий. int error; int sock; struct sockaddr_in addr; int addrlen = sizeof(struct sockaddr_in); error = bind(sock, (struct sockaddr*)&localaddr, localaddrlen); где sock является дескриптором, созданным предыдущим вызовом socket. По нескольким причинам привязка имени к сокету была отделена от создания со- сокета. Во-первых, сокеты потенциально полезны без имен. Если бы пришлось имено- именовать все сокеты, пользователям пришлось бы придумывать без причины бессмыслен- бессмысленные имена. Во-вторых, в некоторых коммуникационных доменах может быть необхо- необходимым предоставить системе дополнительную информацию, прежде чем привязать имя к сокету (например, при использовании сокета требуется «тип службы»). Если бы
516 Глава 11. Межпроцессное взаимодействие имя сокета нужно было указывать во время создания сокета, предоставление этой ин- информации было бы невозможно без дальнейшего усложнения интерфейса. В серверном процессе сокет должен быть помечен как принимающий входящие соединения. int error, sock, backlog; error = listen(sock, backlog); Параметр backlog в вызове listen определяет верхнюю границу числа ожидающих соединений, которые нужно поставить в очередь приема. Затем соединения принимаются по одному с помощью int newsock, sock; struct sockaddr_in clientaddr; int clientaddrlen = sizeof(struct sockaddr_in); newsock = accept(sock, (struct sockaddr *)kclientaddr, clientaddrlen); Табл. 11.1. Отправка и получение данных по сокету Процедура read readv write writev recv send recvmsg sendmsg Соединена Да Да Да Да Да Да Да Да Разъединена Нет Нет Нет Нет Да Да Да Да Сведения об адресе Нет Нет Нет Нет Нет Нет Да Да Вызов accept возвращает новый соединенный сокет, а также адрес клиента, опре- определяя параметры clientaddr и clientaddrlen. Новый сокет является сокетом, через ко- который может осуществляться коммуникация. Первоначальный сокет sock использует- используется исключительно для управления очередью запросов соединения на сервере. Для отправки и получения данных доступен ряд вызовов; эти вызовы обобщены в табл. 11.1. Самыми богатыми из них являются вызовы sendto и recvfrom. Кроме воз- возможности операций разбрасывания-собирания может быть указан или получен адрес, доступны необязательные флаги и можно передать интерпретируемые особым обра- образом вспомогательные данные или управляющую информацию (см. рис. 11.1). Вспомо- Вспомогательные данные могут включать специфичные для протокола данные, такие, как адресация или опции, а также интерпретируемые особым образом данные, которые на- называются правами доступа.
11.2. Структура реализации и обзор 517 struct msghdr i 1 msg_name msg_namelen msgjLOV msg_iovlen msgjcontrol q msg_controllen msg_- ags Адрес Служебные данные iovjbase iov_len iovjbase iov_len ... iovjbase iovjen & & struct iov[] РИС. 11.1. Структуры данных для системных вызовов sendmsg и recvmsg Кроме этих системных вызовов предусмотрено несколько других вызовов для дос- доступа к различным службам. Вызов getsockname возвращает адрес локальной привязки сокета, тогда как вызов getpeername возвращает адрес сокета на удаленном конце соединения. Вызов shutdown завершает передачу или прием данных через сокет, а два вызова ioctl - setsockopt и getsockopt - могут использоваться для установки и получе- получения различных параметров, управляющих работой сокета или нижележащими сетевы- сетевыми протоколами. Сокеты уничтожаются обычным системным вызовом close. Интерфейс средств межпроцессного взаимодействия намеренно был разработан как ортогональный существующим стандартным системным интерфейсам, т.е. системным вызовам open, read и write. Это решение было принято, чтобы избежать перегрузки знакомого интерфейса ненужной сложностью. Кроме того, разработчики думали, что использование интерфейса, который полностью независим от файловой системы, повысило бы переносимость программного обеспечения, поскольку, например, не были бы вовлечены имена путей. Обратная совместимость, ради неподготовленных процессов, по-прежнему считалась важной; поэтому знакомый интерфейс read-write был дополнен, чтобы обеспечить доступ к новым коммуникационным средствам, когда это имело смысл (например, когда использовались соединенные сокеты потоков). 11.2. Структура реализации и обзор Средства межпроцессного взаимодействия наложены поверх сетевых средств, как по- показано на рис. 11.2. Данные поступают от приложения через уровень сокетов на сете- сетевой уровень и обратно. Состояние, необходимое уровню сокетов, полностью инкапсу-
518 Глава 11. Межпроцессное взаимодействие лируется внутри него, тогда как все относящиеся к протоколу уровни поддерживаются во вспомогательных структурах данных, специфичных для поддерживаемых протоко- протоколов. Ответственность за хранилище, связанное с переданными данными, передается от уровня сокетов сетевому уровню. Последовательное применение этого правила по- помогает упростить детали управления памятью. Внутри уровня сокетов структура данных сокета является центром активности. Процедуры интерфейса системных вызо- вызовов управляют действиями, относящимися к системному вызову, собирая параметры системного вызова (см. раздел 3.2) и преобразуя данные пользователя в формат, ожи- ожидаемый процедурами второго уровня. Большая часть абстракции сокетов реализуется внутри процедур второго уровня. У всех процедур второго уровня имена начинаются с префикса so, и они непосредственно манипулируют структурами данных сокета и управляют синхронизацией между асинхронными видами деятельности; эти про- процедуры перечислены в табл. 11.2. Уровень сокетов Сетевой уровень Сетевые интерфейсы Сокет потока Протоколы TCP/IP 100 Мбит/с Ethernet РИС. 11.2. Разбиение на уровни реализации межпроцессного взаимодействия. Блоки слева обозначают стандартные уровни; блоки справа обозначают специфические при- примеры уровней, которые могли бы быть использованы отдельным сокетом Табл. 11.2. Процедуры поддержки уровня сокетов Процедура Функция socreate() Создает новый сокет sobind() Привязывает имя к сокету solistenQ Помечает сокет как ожидающий запросы соединения soclose() Закрывает сокет soabort() Прерывает соединение сокета soaccept() Принимает ожидающее соединение сокета soconnect() Инициирует соединение с другим сокетом soconnect2() Создает соединение между двумя сокетами sodisconnect() Инициирует разрыв соединения подключенного сокета sosend() Отправляет данные soreceive() Получает данные soshutdown() Отключает отправку или прием данных sosetopt() Устанавливает значение опции сокета sogetoptQ Получает значение опции сокета
11.3. Управление памятью 519 Оставшаяся часть данной главы фокусируется на реализации уровня сокетов. В разделе 11.3 обсуждается, как осуществляется управление памятью на уровне соке- сокетов и ниже в сетевой подсистеме; раздел 11.4 освещает сокеты и связанные с ними структуры данных; в разделе 11.5 представлены алгоритмы для установления соедине- соединения; в разделе 11.6 обсуждается передача данных, а в разделе 11.7 обсуждается отключение соединения. На протяжении данной главы приводятся с небольшим уточнением ссылки на средства поддержки, предоставляемые протоколами сетевой коммуникации. Полное описание взаимодействия между сетевыми протоколами и уровнем сокетов приведено в главе 12, а внутреннее устройство сетевых протоколов представлено в главе 13. 11.3. Управление памятью Требования, налагаемые на схему управления памятью протоколами межпроцессного взаимодействия и сетевыми протоколами, имеют тенденцию значительно отличаться от требований других частей операционной системы. Хотя все они требуют эффектив- эффективного распределения и повторного использования памяти, коммуникационным прото- протоколам нужна, в частности, память сильно различающихся размеров. Память нужна для структур различных размеров, таких, как пакеты коммуникационных протоколов. Реализации протоколов должны часто присоединять или удалять заголовки к разбитым на пакеты данным. Когда пакеты отправляются и принимаются, может понадобиться разделить буферированные данные на пакеты, а полученные пакеты можно объеди- объединить в отдельную запись. Кроме того, пакеты и другие объекты данных должны быть помещены в очередь при ожидании отправки или получения. Для удовлетворения этих потребностей существуют специальные средства по управлению памятью для исполь- использования системой межпроцессного взаимодействия и сетевой системой. Mbuf Средства управления памятью вращаются вокруг структуры данных, называемой mbuf (см. рис. 11.3). Mbuf, или буферы памяти, различаются в размерах в зависимости от того, что они содержат. Все mbuf содержат фиксированную структуру mjidr, ко- которая отслеживает различные части учета использования mbuf. Mbuf, содержащий лишь данные, имеет размер 234 байта B56 байтов всего для mbuf минус 22 байта заго- заголовка mbuf). Все размеры структур вычислены для 32-разрядных процессоров. Для больших сообщений система может связать с mbuf большие разделы данных, ссылаясь на внешний кластер mbuf из частной области виртуальной памяти. Размер кластера mbuf может различаться в зависимости от архитектуры, он определяется макросом MCLBYTES (который для PC составляет 2 Кбайта). Данные хранятся либо во внутренней области данных, либо во внешнем кластере, но никогда в обоих. Чтобы получить доступ к данным в любом месте, используется
520 Глава 11. Межпроцессное взаимодействие т next m_nextpkt т data m_len т_- ags mjype m dat 234 байта РИС. 11.3. Структура данных буфера памяти (mbuf) указатель данных внутри mbuf. Кроме поля указателя на данные поддерживается также поле размера. Поле размера показывает число байтов действительных данных, которые можно найти на том месте, куда указывает указатель. Поля данных и размера дают процедурам возможность эффективно подрезать данные в начале и конце mbuf. При удалении данных в начале mbuf указатель увеличивается, а размер уменьшается. Для удаления данных с конца mbuf размер уменьшается, но указатель на данные оста- остается без изменения. Когда внутри mbuf доступно пространство, данные можно доба- добавить к любому концу. Эта гибкость добавления и удаления пространства без копирова- копирования особенно полезна в реализации коммуникационного протокола. Протоколы регу- регулярно обрезают информацию протокола спереди или сзади сообщения до того, как со- содержание сообщения передается обрабатывающему модулю вышележащего уровня, или они добавляют информацию протокола, когда сообщение передается нижележа- нижележащим уровням. Можно объединить несколько mbuf для хранения произвольного количества дан- данных. Это объединение осуществляется посредством поля mjiext mbuf. По соглаше- соглашению, цепочка mbuf, связанная таким образом, рассматривается как один объект. Напри- Например, коммуникационные протоколы строят пакеты из цепочек mbuf. Второе поле, mjiextpkt, связывает объекты, построенные из цепочек mbuf в списки объектов. На протяжении нашего обсуждения коллекция mbuf, связанная вместе полем mjiext, будет называться цепочкой; цепочки mbuf, связанные полем mjiestpkt, будут называть- называться очередью.
11.3. Управление памятью 521 Каждый mbuf типизирован в соответствии с его использованием. Этот тип служит двум целям. Единственным практическим применением типа является различение необязательных компонентов сообщения в цепочке mbuf, которая помещена в очередь для получения в очередь данных сокета. В противном случае информация о типе используется для хранения статистических данных об использовании памяти и, если есть проблемы, в качестве вспомогательного средства при отслеживании mbuf. Флаги mbuf логически разделены на два набора: флаги, описывающие использова- использование отдельного mbuf, и флаги, описывающие объект, хранящийся в цепочке mbuf. Флаги, описывающие mbuf, определяют, ссылается ли mbuf на внешнее хранилище (М_ЕХТ), присутствует ли второй набор полей заголовка (M_PKTHDR) и составляет ли mbuf запись (M_EOR). Пакет обычно хранится в цепочке mbuf (из одного или более mbuf) с установленным флагом M_PKTHDR в первом mbuf в цепочке. Флаги mbuf, описывающие пакет, должны быть установлены в первом mbuf и могут включать либо флаг широковещания (M_BCAST), либо флаг многоадресной рассылки (M_MCAST). Последние флаги указывают, что для отправки переданного пакета должны использо- использоваться широковещание или многоадресная рассылка соответственно или что получен- полученный пакет был отправлен таким образом. Если в mbuf установлен флаг M_PKTHDR, mbuf имеет второй набор полей заго- заголовков, следующих непосредственно после стандартного заголовка. Эта добавка вызы- вызывает сокращение области данных mbuf с 234 до 210 байтов. Заголовок пакета, который показан на рис. 11.4, используется лишь в первом mbuf в цепочке. Он включает несколько полей: указатель на интерфейс, через который был получен пакет, общий размер пакета, указатель на заголовок пакета, два поля, относящихся к вычислению контрольной суммы пакета, и указатель на список произвольных тегов. Mbuf, использующий внешнее хранилище, помечается флагом М_ЕХТ. В данном случае различные области заголовка перекрывают внутреннюю область данных mbuf. Поля в этом заголовке, который показан на рис. 11.5, описывают внешнее хранилище, включая начало и размер буфера. Одно поле предназначено для указания на процедуру для освобождения буфера, теоретически позволяя отображение посредством mbuf раз- различных видов буферов. Однако в текущей реализации функция освобождения не используется, и предполагается, что внешнее хранилище является стандартным кла- кластером mbuf. Mbuf может быть и заголовком пакета, и иметь внешнее хранилище, в этом случае за стандартным заголовком mbuf следует заголовок пакета, а затем заго- заголовок внешнего хранилища. Возможность ссылаться на кластеры mbuf из mbuf позволяет ссылаться на данные другим элементам в сетевом коде без необходимости операции копирования из памяти в память. Когда необходимы несколько копий блока данных, на один и тот же кластер mbuf ссылаются несколько mbuf. Для mbuf поддерживается единственный глобальный массив счетчиков ссылок для поддержки такого стиля совместного использования (см. следующий подраздел).
522 Глава 11. Межпроцессное взаимодействие mjnext mjnextpkt mjiata mjen _- ags mjtype pkt.len pkt.rcvif pkt.header pkt.csum_-ags pkt.csum_data pkt.tags m dat 210 байтов Рис. 11.4. Структура данных буфера памяти (mbuf) с M_PKTHDR m_next mjnextpkt т data mjen m_- ags mjtype ext.size ext.buf ext.free ext.args ext.refcnt ext.type mjdat (HE ИСПОЛЬЗУЕТСЯ) Рис. 11.5. Структура данных буфера памяти (mbuf) с внешним хранилищем
11.3. Управление памятью 523 Mbuf содержат области данных фиксированного, а не переменного размера, по ряду причин. Во-первых, фиксированный размер минимизирует фрагментацию памяти. Во-вторых, коммуникационные протоколы часто требуются для присоедине- присоединения заголовков к существующим областям данных, разделения областей данных или подрезки данных от начала или конца области данных. Средства mbuf разработаны для того, чтобы управляться с такими изменениями без повторного распределения или копирования, когда это возможно. Наконец, функция dtomQ, описанная далее в подраз- подразделе о вспомогательных процедурах mbuf, была бы значительно более затратной, если бы mbuf не имели фиксированный размер. Поскольку структура mbuf является центральным объектом всех сетевых подсистем, она претерпевала изменения с каждым большим изменением кода. Теперь она содержит поле флагов и два необязательных набора полей заголовков. Указатель на данные заме- замещает поле, использовавшееся в качестве смещения в первоначальной версии mbuf. Использование смещения не было переносимым, когда ссылаемые данные могли быть в кластере mbuf. Добавление поля флагов позволило использовать флаг, обозначающий внешнее хранилище. Более ранние версии обнаружили значимость смещения для обнаружения того, находились ли данные в области данных внутреннего буфера. Добав- Добавление флага широковещания позволило протоколам сетевого уровня знать, были ли пакеты получены в результате широковещания на канальном уровне, как требовалось для соответствия стандарту. Несколько других флагов были добавлены для использования особыми протоколами, а также для управления обработкой фрагментов. Поля необязательного заголовка претерпели наибольшие изменения с 4.4BSD. Два заголовка первоначально были спроектированы для того, чтобы избежать избыточных вычислений размера объекта, чтобы упростить идентификацию входящего сетевого интерфейса полученного пакета и обобщить использование внешнего хранилища mbuf, поскольку заголовок пакета 4.4BSD был расширен с включением информации о вычислении контрольной суммы (традиционно дорогостоящая операция, которая теперь может выполняться аппаратно) и произвольным набором тегов. Теги являются структурами фиксированного размера, которые могут указывать на произвольные участки памяти и используются для хранения информации, относя- относящейся к различным модулям внутри сетевой подсистемы. У каждого тега есть ссылка на следующий тег в списке, 16-разрядный ID, 16-разрядный размер и 32-разрядный cookie. Cookie используется для идентификации того, какой модуль владеет тегом, а тип является частью данных, которые являются частными для модуля. Теги исполь- используются для передачи информации о пакете, которая не должна помещаться в сам пакет. Примеры этих тегов приведены в разделе 13.10. Дизайн не был полностью успешным. Он, возможно, не стоит сложности наличия заголовка переменного размера в mbuf для заголовка пакета; вместо этого эти поля, на- наверное, стоило включить во все mbuf, даже если бы они не использовались. Некоторые из новых полей заголовка пакета можно было бы объединить, поскольку они дуб- дублируют функциональность; например, поля контрольной суммы могли бы быть реали- реализованы с использованием тегов.
524 Глава 11. Межпроцессное взаимодействие Алгоритмы управления хранилищем Предоставление системы с сетевым стеком, способным к симметричной многопроцес- многопроцессорной обработке, потребовало полной переработки алгоритмов распределения памяти, лежащих в основе кода mbuf. Тогда как предыдущие версии BSD выделяли память с по- помощью системного распределителя, а затем делили ее на mbuf и кластеры, такая про- простая методика не работала при использовании нескольких процессоров. FreeBSD 5.2 выделяет виртуальную память среди ряда списков, предназначенных для использования сетевым кодом распределения памяти. У каждого процессора есть свой собственный индивидуальный контейнер mbuf и кластеров. Имеется также еди- единый, общий пул mbuf и кластеров, из которого делаются попытки выделения, когда список процессора пуст, или куда поступает освобождаемая память, когда список про- процессора полон. Однопроцессорная система действует, как если бы она была SMP-сис- темой с одним процессором, что означает, что у нее есть один список процессора, а также один общий список. Во время загрузки системы распределитель памяти первоначально заполняет каждый список процессора настраиваемым числом mbuf и кластеров (см. раздел 14.6). Общий список инициализируется и оставляется пустым; он используется лишь в случаях большой нагрузки, когда списку процессора не хватает памяти. Счетчики ссылок для кластеров управляются как массив, расположенный отдель- отдельно от самих кластеров. Массив достаточно большой для всех кластеров mbuf, которые могли бы быть выделены системой. Память, выделенная mbuf и кластерам, устанавли- устанавливается на основе параметра ядра maxusers, который сам основан на количестве физиче- физической памяти в системе. Базирование объема памяти, выделенного сетевой подсистеме, на объеме физической памяти дает хорошее значение по умолчанию, которое должно быть переписано, когда система предназначается для сетевых задач, таких, как веб- вебсервер, брандмауэр или маршрутизатор. Запросы выделения mbuf указывают, что они должны быть либо выполнены немед- немедленно, либо могут подождать доступности ресурсов. Если запрос помечен как «способ- «способный ждать» и запрошенные ресурсы недоступны, процесс помещается в состояние сна в ожидании доступности ресурсов. Хотя неблокирующий запрос распределения больше не нужен коду, выполняющемуся на уровне прерывания, сетевой код по-прежнему рабо- работает, как если бы это было так. Если распределение mbuf достигло своего предела или память недоступна, процедуры выделения mbuf запрашивают у модулей сетевых прото- протоколов возврат всех доступных ресурсов, которые они могут держать в резерве. Небло- Неблокирующий запрос завершится неудачей, если ресурсы недо-ступны. Запрос выделения mbuf осуществляется посредством вызова m_get(), m_gethdr() или эквивалентного макроса, mbuf предоставляется из соответствующего списка про- процессора функцией mb_alloc(), а затем инициализируется. Для m_gethdr() mbuf инициа- инициализируется необязательным заголовком пакета. Макрос MCLGET добавляет к mbuf кластер mbuf.
11.3. Управление памятью 525 Освобождение ресурсов mbuf просто: mJreeQ освобождает один mbuf, a mJreemQ освобождает цепочку mbuf. Когда освобождается mbuf, ссылающийся на кластер mbuf, счетчик ссылок для кластера уменьшается. Кластеры mbuf помещаются в соответст- соответствующий список процессора, когда счетчик их ссылок достигает нуля. Вспомогательные процедуры mbuf Для манипулирования mbuf внутри сетевой подсистемы ядра существует множество полезных вспомогательных процедур. Эти процедуры, которые будут использованы в главе 12, кратко описываются здесь. Процедура т_сорут() делает копию цепочки mbuf, начиная с логического смеще- смещения в байтах от начала данных. Эта процедура может использоваться для копирования всей или только части цепочки mbuf. Если mbuf связан с кластером mbuf, копия будет ссылаться на те же самые данные, увеличив счетчик ссылок кластера; в противном случае копируется также часть данных. Функция m_copydata() аналогична, но она ко- копирует данные из цепочки mbuf в предоставленный вызывающим буфер. Этот буфер является не mbuf или цепочкой, а простой областью памяти, на которую указывает тра- традиционный указатель С. Процедура m_adj() выравнивает данные в цепочки mbuf на определенное число байтов, удаляя данные либо спереди, либо сзади. Данные никогда не копируются; m_adj() работает только путем манипулирования полями смещения и размера в струк- структурах mbuf. Макрос mtodQ принимает указатель на заголовок mbuf и тип данных и возвращает указатель на данные в буфере, приводя к данному типу. Функция dtomQ обратная: она принимает указатель на произвольный адрес в данных mbuf и возвращает указатель на заголовок mbuf (а не на заголовок цепочки mbuf). Эта операция осуществляется путем простого усечения адреса данных до выровненного по границе mbuf размера. Эта функция работает, лишь когда данные находятся внутри mbuf. Процедура m_pullup() переставляет цепочку mbuf таким образом, что указанное число байтов находится в непрерывной области данных внутри mbuf (не во внешнем хранилище). Эта операция используется таким образом, чтобы объекты вроде заголов- заголовков протоколов были непрерывны и могли рассматриваться как обычные структуры данных и чтобы dtomQ работала при освобождении объекта. Если есть пространство, m_pullup() увеличит размер непрерывной области до максимального размера заголов- заголовка протокола, пытаясь избежать повторного вызова в будущем. Макрос M_PREPEND() регулирует цепочку mbuf, добавляя впереди указанное число байтов данных. При возможности пространство делается на месте, но может быть выделен дополнительный mbuf в начале цепи. В настоящее время невозможно добавлять данные в пределах кластера mbuf, поскольку различные mbuf могут ссы- ссылаться на данные в различных частях кластера.
526 Глава 11. Межпроцессное взаимодействие 11.4. Структуры данных Сокеты являются основными объектами, использующимися процессами, взаимодейст- взаимодействующими по сети. Тип сокета определяет базовый набор семантики взаимодействия, тогда как коммуникационный домен определяет вспомогательные свойства, важные для использования сокета, и может уточнять набор доступной семантики взаимодейст- взаимодействия. В табл. 11.3 показаны четыре типа сокетов, поддерживаемых в настоящее время системой. Для создания нового сокета приложения должны указать его тип и коммуни- коммуникационный домен. Запрос может также включать особые сетевые протоколы, которые должны использоваться сокетом. Если протокол не указан, система выбирает соответ- соответствующий протокол из набора протоколов, поддерживаемых коммуникационным доменом. Если коммуникационный домен не способен поддерживать запрошенный тип сокета (т.е. подходящий протокол недоступен), запрос завершится неудачей. Табл. 11.3. Типы сокетов, поддерживаемые системой Имя Тип Свойства SOCK_STREAM Потоковый Надежная, упорядоченная передача данных; может под- поддерживать внеполосные данные SOCK_DGRAM Дейтаграммы Ненадежная, неупорядоченная передача данных с сохране- сохранением границ сообщения SOCK_SEQPACKET Упорядоченные Надежная, упорядоченная передача данных с сохранением пакеты границ сообщения SOCK_RAW Прямой Прямой доступ к нижележащим протоколам Сокеты описьшаются структурой данных socket, которая динамически создается во время системного вьвова socket. Коммуникационные домены описьшаются структурой данных domain, которая статически определяется внутри системы на основе конфигурации системы (см. раздел 14.5). Коммуникационные протоколы внутри домена описьшаются структурой protosw, которая также статически определяется внутри системы для каждой сконфигурированной реализации протокола. Статическое определение этих структур оз- означает, что модули протоколов не могут загружаться или выгружаться во время работы. Когда делается запрос создания сокета, система использует значение коммуника- коммуникационного домена, чтобы выполнить линейный поиск в списке сконфигурированных доменов. Если домен найден, делается обращение к таблице поддерживаемых прото- протоколов домена, чтобы найти протокол, подходящий для создаваемого типа сокета, или определенный запрашиваемый протокол. (Для прямого сокета может существовать элемент группового символа (wildcard).) Если запросу удовлетворяют несколько эле- элементов протоколов, выбирается первый. Мы начнем обсуждение структур данных, исследуя структуру domain. Структураргоtosw обсуждается в разделе 12.1.
11.4. Структуры данных 527 Коммуникационные домены Структура domain показана на рис. 11.6. Поле domjiame является ASCII-именем комму- коммуникационного домена. (В первоначальном проекте коммуникационные домены должны были определяться ASCII-строками; теперь они определяются именованными констан- константами.) Поле domjamily идентифицирует семейство адресов, используемое доменом; некоторые возможные значения показаны в табл. 11А. Семейства адресов ссылаются на структуру адресации домена. Семейство адресов обычно имеет связанное с ним семей- семейство протоколов. Семейства протоколов ссылаются на набор коммуникационных прото- протоколов домена, использующихся для поддержки семантики взаимодействия сокета. Поле domjirotosw указывает на таблицу функций, которые реализуют протоколы, поддержи- поддерживаемые коммуникационным доменом, а указатель dom_NPROTOSW помечает конец таб- таблицы. Оставшиеся элементы содержат указатели на специфические для домена про- процедуры, использующиеся при управлении и передаче прав доступа, и поля, относящиеся к инициализации маршрутизации домена. domjamily dom name dom in it dom_extemalize dom_dispose dom_protosw dom_protoswNPROTOSW domjiext domjrtattach domjrtoffset dom_maxrtkey AFJNET "internet" inetsw &inetsw[19] 0 in_inithead 32 sizeof(struct sockaddr_in) Рис. 11.6. Структура данных коммуникационного домена Табл. 11.4. Семейства адресов Имя AF_LOCAL AFJNET AFJNET6 AF_NS AFJSO AF_CCITT AF SNA Описание (AFJJNIX) локальное взаимодействие Интернет (TCP/IP) Интернет версии 6 (TCP/IPv6) Архитектура Xerox Network System (XNS) Сетевые протоколы OSI Протоколы CCITT, например Х.25 System Network Architecture (SNA) IBM
528 Глава 11. Межпроцессное взаимодействие Табл. 11.4. Семейства адресов (Окончание) Имя Описание AF_DLI Интерфейс непосредственного соединения AF_LAT Интерфейс терминала локальной сети AF_APPLETALK Сеть AppleTalk AF_ROUTE Взаимодействие с уровнем маршрутизации ядра AF_LINK Непосредственный доступ к канальному уровню AF_IPX Протокол Novell Internet Сокеты Структура данных socket показана на рис. 11.7. Хранилище для структуры socket выде- выделяется зональным распределителем UMA (описанным в разделе 5.3). Сокеты содержат информацию об их типе, использующемся в поддерживаемом протоколе, и состоянии (таб. 11.5). Передаваемые или принимаемые данные помещаются в сокете в список цепочек mbuf. Для управления очередями сокетов, созданных во время установления соединения, присутствуют различные поля. Каждая структура сокета содержит также идентификатор группы процессов. Идентификатор группы процессов используется при доставке сигналов SIGURG и SIGIO. SIGURG посылается, когда для сокета суще- существует условие для срочной обработки, a SIGIO используется средствами асинхронно- асинхронного ввода/вывода (см. раздел 6.4). Сокет содержит поле ошибки, которое необходимо для сохранения асинхронных ошибок, о которых нужно сообщить владельцу сокета. Местонахождение сокетов определяется посредством дескриптора файла процесса через таблицу файлов. При создании сокета в поле f_data структуры файла устанавливается указатель на структуру сокета, а в поле f_ops устанавливается указатель на набор процедур, определяющих специфические для сокета файловые операции. В этом случае структура сокета является прямым аналогом структуры vnode, используемой файловыми системами. Структура сокета действует в качестве точки постановки в очередь для передавае- передаваемых и получаемых данных. Когда данные поступают в систему в результате системных вызовов, таких, как write или send, уровень сокетов передает данные в сетевую подсис- подсистему в виде цепочки mbuf для немедленной передачи. Если поддерживающий модуль протокола решает отложить передачу данных или если копия данных должна сохра- сохраняться до тех пор, пока не будет получено подтверждение, данные помещаются в очередь отправки сокета. Когда сеть использует данные, она уничтожает их из исхо- исходящей очереди. По получении сеть передает данные на уровень сокетов, также в це- цепочках mbuf, где они затем остаются в очереди до тех пор, пока приложение не сделает вызов с их запросом. Уровень сокетов может также сделать обратный вызов внутрен- внутреннего клиента ядра при поступлении данных, давая возможность обработать данные без переключения контекста. Обратные вызовы используются сервером NFS (см. главу 9).
11.4. Структуры данных 529 Буфер приема сокета sb ее sb ctl sb hiwat sb mbent sb mbmax sb lowat sb mb sb sel sb_fags sb_timeo \ \ \ / so_count sojype sojiinger so_options sojstate so_pcb so_proto so_head so_incomp so_comp soJList so_incqlen so_qlimit so_qlen sojtimeo so_error sojsigio so_aiojobq sojrev... so_snd... so_oobmark sojupcall sojupcallarg Буфер отправки сокета sb_cc sb_ctl sbjkiwat sbjnbcnt sb mbmax sb_lowat sb_mb sb sel sb_fags sbjtimeo Сокет Рис. 11.7. Структура данных сокета Чтобы избежать истощения ресурсов, сокеты налагают верхний предел на число байтов данных, которые можно поставить в очередь в буфер данных сокета, а также на объем памяти хранилища, который может использоваться для данных. Эта верхняя гра- граница первоначально устанавливается протоколом, хотя приложение может изменить значение вплоть до системного максимума, обычно 256 Кбайт. Сетевые протоколы могут проверить верхнюю границу и использовать это значение в политиках управле- управления потоком. Нижняя граница также присутствует в каждом буфере данных сокета. Нижняя граница позволяет приложениям контролировать поток данных, указывая ми- минимальное число байтов, необходимых для удовлетворения запроса приема, со значе- значением по умолчанию в 1 байт и максимальным значением верхней границы. Для вывода нижняя граница устанавливает минимальный объем пространства, доступный до по- попытки передачи; значением по умолчанию является размер кластера mbuf. Эти значе- значения также управляют работой системного вызова select, когда он используется для про- проверки возможности чтения из сокета или записи в него.
530 Глава 11. Межпроцессное взаимодействие Табл. 11.5. Состояния сокета Состояние SS_NOFDREF SSJSCONNECTED SSJSCONNECTING SSJSDISCONNECTING SS_CANTSENDMORE SS_CANTRCVMORE SS_RCVATMARK SS_ISCONFIRMING SS_NBIO SS_ASYNC SS_INCOMP SS_COMP SS INDISCONNECTED Описание Нет ссылки таблицы файлов Подключен к удаленному узлу В процессе подключения к удаленному узлу В процессе отключения от удаленного узла Не может больше отправлять данные узлу Не может больше получать данные от узла Метка внеполосных данных на входе Ожидание узлом подтверждения соединения Неблокирующий ввод/вывод Уведомление асинхронного ввода/вывода Соединение не завершено и пока не принято Соединение завершено, но еще не принято Сокет отключается от удаленного узла Когда на уровне коммуникационного протокола получены признаки соединения, для завершения соединения может потребоваться дополнительная обработка. В зави- зависимости от протокола эта обработка может быть сделана до того, как соединение воз- возвращается прослушивающему процессу, или прослушивающему процессу может быть позволено подтвердить или отвергнуть запрос соединения. Сокеты, использующиеся для принятия входящих запросов соединений, поддерживают две очереди сокетов, свя- связанные с запросами соединений. Список сокетов, возглавляемый полем so_incomp, представляет очередь соединений, которые до возвращения должны быть завершены на уровне протокола. Поле so_comp возглавляет список сокетов, которые готовы для возвращения в прослушивающий процесс. Подобно очередям данных, очереди соединений также имеют контролируемый приложением предел. Ограничение накла- накладывается на обе очереди. Поскольку предел может включать сокеты, которые пока не могут быть приняты, система применяет ограничение, на 50 процентов превы- превышающее номинальный предел. Хотя соединение может быть установлено сетевым протоколом, приложение может выбрать не принимать установленное соединение или может закрыть соединение сразу же после определения идентификатора клиента. Сетевой протокол может также отло- отложить завершение соединения до тех пор, пока приложение не получит управление с по- помощью системного вызова accept. Приложение могло бы затем принять или отвергнуть соединение явным образом с помощью специфичного для протокола механизма. В против- противном случае, если приложение выполняет перемещение данных, соединение подтвержда- подтверждается; если приложение немедленно закрывает сокет, соединение отвергается.
11.4. Структуры данных 531 Адреса сокетов Сокеты могут быть помечены, так что к ним могут подключаться другие узлы. Уровень сокетов рассматривает адрес как непрозрачный объект. Приложения передают и по- получают адреса как помеченные байтовые строки переменной длины. Адреса поме- помещаются в mbuf внутри уровня сокетов. В качестве шаблона для ссылки на идентифика- идентификационную метку и размер каждого адреса используется структура sockaddr, показанная на рис. 11.8. Большинство уровней протоколов поддерживают единственный тип адреса, идентифицируемый меткой и известный как семейство адресов. sajien sa_Jamily sa_data \+— 1 байт *•]•+ 1 байт ^|^ Переменное >) РИС. 11.8. Структура шаблона адресов сокетов Адреса, переданные приложением, обычно находятся в mbuf, лишь пока уровень сокета передает их поддерживаемому протоколу для передачи в структуру адреса фик- фиксированного размера (например, когда протокол записывает адрес в блок управления протоколом). Структура sockaddr является обычным средством, посредством которого уровень сокетов и средства поддержки сети обмениваются адресами. Размер общего мас- массива данных был выбран достаточно большим, чтобы содержать множество типов адре- адресов непосредственно, хотя общий код не может зависеть от наличия достаточного про- пространства в структуре sockaddr для произвольного адреса. Локальный коммуникацион- коммуникационный домен (известный ранее как домен UNIX), например, хранит в mbuf имена путей файловой системы и допускает имена сокетов до 104 байтов, как показано на рис. 11.9. Как IPv4, так и IPv6 используют структуру фиксированного размера, которая объединяет интернет-адрес и номер порта. Разница в размере адреса D байта для IPv4 и 16 байтов для IPv6) и в том факте, что структуры адресов IPv6 содержат другую информацию (сведения об области действия и потоке). Оба протокола Интернета резервируют место для адресов в специфических для протокола структурах данных управляющих блоков и освобождают mbuf, которые содержат адреса, после их копирования. Блокировки В разделе 4.3 обсуждалась необходимость для структур блокировки в ядре SMP. Сете- Сетевая подсистема использует эти блокировки внутренне, чтобы защитить свои структуры данных. Когда впервые были представлены средства SMP, вся сетевая подсистема была по- помещена вместе с остальным ядром под всеобщую (giant) блокировку. В ходе разработ- разработки FreeBSD 5.2 несколько частей сетевого кода были изменены для работы без всеоб- всеобщей блокировки, но на данный момент отдельные части системы по-прежнему ее ис- используют. Каждый раз, когда процесс пользователя вызывает сетевую подсистему
532 Глава 11. Межпроцессное взаимодействие Адрес сокета локального домена sunjen AF_LOCAL sun_path 104 байта Адрес сокета домена IPv4 Адрес сокета домена IPv6 sin_len AFJNET sin_port sin addr 14 байтов sin6_len AFJNET6 sin6_port sin6_- owinfo sin6_addr sin6_scope_id i It \ >6 РИС. 11.9. Структуры адресов локального домена, IPv4 и IPv6 (т.е. создает сокет, устанавливает соединение, отправляет или получает данные), он получает всеобщую блокировку. Другие блокировки получаются и освобождаются при взаимодействии с нижними уровнями протоколов и сетевых устройств. Особые экземпляры блокировок сетевой подсистемы обсуждаются в разделах, к которым они имеют максимальное отношение. 11.5. Установление соединения Чтобы два процесса могли передавать между собой информацию, между ними должна быть установлена связь. Шаги, вовлеченные в создание связи {socket, connect, listen, accept и т.д.), обсуждались в разделе 11.1. В данном разделе мы изучим работу уровня сокета при установлении связи. Поскольку состояние, связанное с передачей данных без установления соединения, полностью инкапсулируется в каждом отправляемом со- сообщении, мы сфокусируемся на ориентированных на соединение связях, устанавли- устанавливаемых с помощью системных вызовов connect, listen и accept. Установление соединения в клиент-серверной модели асимметрично. Клиент активно инициирует соединение для получения службы, тогда как сервер пассивно принимает соединения для предоставления службы. На рис. 11.10 показана диаграмма переходов состояний для сокета, использующегося для инициирования или принятия соединений. Переходы состояний инициируются либо действиями пользователя (т.е. системными вызовами), либо действиями протокола в результате получения сете- сетевых сообщений или обслуживания сработавших таймеров. Для получения и отправки данных обычно используются сокеты. Когда они ис- используются при установлении соединения, они трактуются несколько по-другому. Если сокет должен использоваться для приема соединения, должен использоваться
11.5. Установление соединения 533 socreate{) listen () solisten () i sodisconnectQ \ Пассивный sonewconnl () с f sonewconnl ( so_incqlen ) connect() soconnectQ Активный r soisconnectedQ so_qlen != 0 accept() soaccept () soisconnectedQ soisco SSJSCONNECTING r ^ soisconnectedQ SSJSCONNECTED Рис. 11.10. Переходы состояний сокета в ходе процесса взаимодействия системный вызов listen. Вызов listen запускает solistenQ, которая уведомляет под- поддерживаемый протокол о том, что сокет будет получать соединения, устанавливает в сокете пустой список ожидающих соединений (через поле so_comp), а затем помеча- помечает сокет, как принимающий соединения, SO_ACCEPTCON. Ко времени завершения listen приложением указывается параметр незавершенных заданий. Этот параметр устанавливает предел на число входящих соединений, которые система будет помещать в очередь ожидания принятия приложением. (Максимальное значение этого предела обеспечивается системой.) Когда сокет установлен для приема соединений, оставшая- оставшаяся часть работы управляется уровнями протоколов. Для каждого установленного на серверной стороне соединения с помощью процедуры sonewconnQ создается новый сокет. Пока соединения устанавливаются, эти новые сокеты могут быть помещены в очередь сокета частично установленных соединений (см. рис. 11.11) или непосредст- непосредственно в очередь соединений, готовых для передачи приложению через вызов accept. Новые сокеты могли бы быть готовыми для передачи приложению либо потому, что для установления соединения больше не нужно никаких действий протокола, либо по- потому, что протокол позволяет прослушивающему процессу подтвердить или отверг- отвергнуть запрос соединения. В последнем случае сокет помечается как подтверждающий (бит состояния SSCONFIRMING) таким образом, что ожидающий запрос соединения будет при необходимости подтвержден или отвергнут. Когда сокеты в очереди час- частично установленных соединений готовы, они перемещаются в очередь завершенных и ожидающих принятия приложением соединений. Когда для получения соединения делается системный вызов accept, система проверяет, что соединение присутствует
534 Глава 11. Межпроцессное взаимодействие в очереди сокета готовых соединений. Если готового соединения для возврата нет, сис- система помещает процесс в состояние сна до тех пор, пока не поступит соединение (если только с сокетом не использовался неблокирующий ввод/вывод, в этом случае возвра- возвращается ошибка). Когда соединение доступно, соответствующий сокет удаляется из очереди, для ссылки на сокет выделяется новый дескриптор файла, и результат воз- возвращается вызывающему. Если вызов accept указывает, что должен быть возвращен идентификатор узла, от уровня протокола получается адрес узла и копируется в пре- предоставленный буфер. SOCK_STREAM SO_ACCEPTCON so head sojmcomp SOCK_STREAM sojiead — so_incomp SOCK_STREAM sojiead so_incomp Рис. 11.11. Соединения, помещенные в очередь сокета в ожидании вызова accept На клиентской стороне приложение запрашивает соединение с помощью систем- системного вызова connect, предоставляя адрес сокета узла, с которым нужно соединиться. Система проверяет, что попытка соединения для сокета уже не исполняется в текущий момент, а затем вызывает soconnectQ, чтобы инициировать соединение. Процедура soconnectQ сначала проверяет, не подключен ли уже сокет. Если сокет уже подключен, существующее соединение сначала завершается (это разъединение совершается лишь с сокетами дейтаграмм). С сокетом в несоединенном состоянии soconnectQ делает запрос уровню протокола для инициирования нового соединения. Когда запрос соеди- соединения был передан уровню протокола, если запрос соединения незавершен, система помещает процесс в состояние сна в ожидании уведомления от уровня протокола о том, что существует установленное соединение. Неблокирующее соединение может в этот момент вернуться, но процесс, ожидающий полного подключения, буцет пробужден, лишь когда запрос соединения будет выполнен полностью - либо успешно, либо с ошибкой. Состояние сокета в ходе установления соединения управляется совместно уровнем сокета и уровнем поддерживающего протокола. Значение состояния протокола прото- протоколом прямо никогда не изменяется; для содействия модульности все изменения осу- осуществляются процедурами-заместителями уровня сокетов, такими, как soisconnectedQ. Эти процедуры изменяют указанным образом состояние сокета и уведомляют все ожи-
11.6. Обмен данными 535 дающие процессы. Уровни поддерживающих протоколов никогда не используют сред- средства синхронизации или сигналы непосредственно. Ошибки, которые обнаруживаются асинхронно, сообщаются сокету через его поле sojerror. Например, если запрос соеди- соединения завершается неудачно из-за того, что уровень протокола обнаруживает, что запрошенная служба недоступна, в поле so error перед пробуждением запрашивающего процесса устанавливается ECONNREFUSED. Уровень сокетов всегда проверяет значение so_error по возвращении из вызова sleepQ; это поле используется для сообщения об асинхронно обнаруженных уровнями протоколов ошибках. 11.6. Обмен данными Большая часть работы, осуществляемой уровнем сокета, заключается в отправке и по- получении данных. Обратите внимание, что сам уровень сокетов явным образом воз- воздерживается от наложения на передаваемые или получаемые через сокеты данные структуры, отличной от необязательных границ записей. В рамках всеобщей модели межпроцессного взаимодействия любые интерпретация или структурирование данных логически изолированы от реализации коммуникационно- коммуникационного домена. Примером этой логической изоляции является возможность передавать между процессами дескрипторы файлов с использованием сокетов локальных доменов. Отправка и получение данных может быть осуществлена с использованием любого из нескольких системных вызовов. Системные вызовы различаются в соответствии с объемом передаваемой и получаемой информации и в соответствии с состоянием сокета, выпол- выполняющего операцию. Например, системный вызов write может использоваться с сокетом в соединенном состоянии, поскольку место назначения данных неявно обозначается соеди- соединением. Однако системные вызовы sendto и sendmsg дают процессу возможность указать место назначения для сообщения явным образом. Аналогично при получении данных сис- системный вызов read дает процессу возможность получить данные через соединенный сокет, не получая адреса отправителя; системные вызовы recvfrom и recvinsg позволяют процессу получить входящие сообщения и адрес отправителя. Различия между этими вызовами обобщены в разделе 11.1. Системные вызовы recvinsg и sendmsg дают средство разбросан- разбросанного ввода/вывода с множеством предоставленных пользователем буферов. Кроме того, recvmsg предоставляет дополнительную информацию о полученном сообщении, в частности, является ли оно срочным (внеполосным), завершает ли запись, или не усече- усечено ли оно из-за того, что буфер был слишком маленьким. Решение предоставить множество различных системных вызовов вместо одного общего интерфейса спорно. Было возможно реализовать единый интерфейс системного вызова и предоставить приложениям упрощен- упрощенные интерфейсы через библиотечные процедуры уровня пользователя. Однако единствен- единственный системный вызов должен был быть самым общим вызовом, что имеет несколько боль- большие издержки. Внутренне все запросы передачи и приема преобразуются в единообразный формат и передаются процедурам уровня сокетов senditQ и recvitQ соответственно.
536 Глава 11. Межпроцессное взаимодействие Передача данных Процедура senditQ отвечает за сбор всех параметров системного вызова, которые ука- указало приложение, в адресное пространство ядра (за исключением фактических данных), а затем вызов процедуры sosendQ для выполнения передачи. Параметры могут включать следующие компоненты, показанные на рис. 11.1. Адрес, по которому должны быть отправлены данные, если сокет не был соединен. Необязательные служебные данные (управляющие данные), связанные с сообще- сообщением; служебные данные могут включать специфические для протокола данные, связанные с сообщением, информацию об опциях протокола или права доступа. Обычные данные, указанные в виде массива буферов (см. раздел 6.4). Необязательные флаги, включающие флаги внеполосных данных и конца записи. Процедура sosendQ обрабатывает большую часть опций передачи данных на уровне сокетов, включая запросы для передачи внеполосных данных и для пере- передачи без сетевой маршрутизации. Эта процедура отвечает также за проверку состояния сокета - например, сделано ли нужное соединение, возможна ли до сих пор передача через сокет и нужно ли сообщать об ожидающей ошибке вместо попытки передачи. Кроме того, sosendQ отвечает за помещение процессов в состояние сна, когда переда- передаваемые ими данные превышают пространство, доступное в буфере отправки сокета. Фактическая передача данных осуществляется поддерживающим коммуникационным протоколом; sosendQ копирует данные из адресного пространства пользователя в mbuf в адресном пространстве ядра, а затем делает вызовы протокола для передачи данных. Основная часть работы, выполняемой sosendQ, заключается в проверке состояния сокета, обеспечении управления потоком, проверке условий завершения и разделении запроса передачи приложения на один или более запросов передачи протокола. Запрос должен быть разделен лишь тогда, когда размер запроса пользователя плюс ряд дан- данных, помещенных в буфер отправки сокета, превышает верхнюю границу сокета. Не допускается разделять запрос, если протокол является неделимым, поскольку каждый запрос, делаемый уровнем сокетов модулям протоколов, неявно означает гра- границу потока данных. К данному типу относится большинство протоколов дейтаграмм. Учет верхней границы каждого сокета гарантирует, что протокол всегда будет иметь место в буфере отправки сокета для помещения в очередь неподтвержденных данных. Он также гарантирует, что ни один процесс или группа процессов не сможет монопо- монополизировать системные ресурсы. Для сокетов, которые гарантируют надежную доставку данных, протокол обычно будет сохранять копию всех переданных данных в очереди отправки сокета до тех пор, пока их прием не будет подтвержден получателем. Протоколы, не предоставляющие заверений доставки, обычно принимают данные от sosendQ и прямо передают данные на место назначения без сохранения копии. Но sosendQ сама не различает надежную и ненадежную доставку.
11.6. Обмен данными 537 sosendQ всегда гарантирует, что буфер отправки сокета имеет достаточно доступ- доступного места для сохранения следующей секции данных для отправки. Если в буфере отправки сокета недостаточно места для сохранения всех данных, которые должны быть переданы, sosendQ проверяет, что сообщение не превышает размер буфера; если сообщение больше, она возвращает ошибку EMSGSIZE. Если доступное пространство в очереди отправки меньше, чем нижняя граница отправки, передача откладывается. Если процесс не использует неблокирующий ввод/вывод, процесс помещается в со- состояние сна до тех пор, пока в буфере отправки не станет доступно больше памяти. В противном случае возвращается ошибка. Когда пространство доступно, формирует- формируется запрос передачи протокола в соответствии с доступным в буфере отправки про- пространством. sosendQ копирует данные из адресного пространства пользователя в кла- кластеры mbuf каждый раз, когда данные превышают минимальный размер кластера (определяемый MINCLSIZE). Если запрос передачи для не являющегося неделимым протокола большой, каждый запрос передачи протокола обычно будет содержать полный кластер mbuf. Хотя до доставки протоколу к цепочке mbuf могут быть добав- добавлены дополнительные данные, предпочтительнее сразу же передать данные на нижеле- нижележащие уровни. Эта стратегия позволяет улучшить конвейеризацию, поскольку данные достигают дна стека протоколов раньше и соответственно быстрее могут начать физическую передачу. Эта процедура повторяется до тех пор, пока не останется недос- недостаточно места; она возобновляется каждый раз, когда становится доступно дополни- дополнительное пространство. Эта стратегия имеет тенденцию сохранять определенный приложением размер со- сообщения и помогает избежать фрагментации на сетевом уровне. Последнее преимуще- преимущество важно, поскольку производительность системы значительно повышается, когда блоки передаваемых данных большие (например, размером с кластер mbuf). Когда получатель или сеть медленнее, чем передающий, нижележащий основан- основанный на соединении протокол передачи обычно применяет какую-нибудь форму управ- управления потоком, чтобы задержать передачу отправителя. В этом случае объем данных, который получатель разрешит передающему отправить, может уменьшиться до такого значения, что естественный размер передачи отправителя упадет ниже своего опти- оптимального значения. Чтобы сдержать такой эффект, sosendQ откладывает передачу, вместо разделения передаваемых данных, в надежде, что получатель возобновит свое окно управления потоком и позволит отправителю вести себя оптимально. Результат подобной схемы довольно тонкий и связан также с оптимизированной обработкой сетевой подсистемой входящих пакетов данных, которые представляют собой кратное размера страницы машины (описанной в разделе 12.8). Процедура sosendQ при манипулировании буфером отправляемых данных сокета заботится об обеспечении того, чтобы доступ к буферу был синхронизирован между множеством отправляющих процессов. Она делает это путем объединения доступа к структуре данных с вызовами sblockQ и sbunlockQ.
538 Глава 11. Межпроцессное взаимодействие Получение данных Процедура soreceiveQ получает данные из очереди сокета. В качестве дополнения sosendQ soreceiveQ появляется на том же самом уровне во внутренней программной структуре и выполняет сходные задачи. Для получения сокетом в очереди могут быть три типа данных: внутриполосные (in-band) данные, внеполосные (out-of-band) данные и служебные данные, такие, как права доступа. Внутриполосные данные могут также быть помечены адресом отправителя. Обработка внеполосных данных различается в за- зависимости от протокола. Они могут быть помещены в начало приемного буфера или в конец буфера для упорядоченного появления с другими данными или могут управ- управляться на уровне протокола отдельно от приемного буфера сокета. В первых двух случаях они возвращаются обычными операциями приема. В последнем случае они из- извлекаются посредством специального интерфейса, будучи запрошенными пользовате- пользователем. Эти средства допускают различные стили передачи срочных данных. Процедура soreceiveQ проверяет состояние сокета, включая буфер полученных данных, входящие данные, ошибки или переходы состояний, и обрабатывает данные в очереди в соответствии с их типом и действиями, определенными вызывающим. Запрос системного вызова может указывать, что должны быть получены лишь внепо- внеполосные данные (MSG_OOB) или что данные должны быть возвращены, но не удалены из буфера данных (посредством указания флага MSG_PEEK). Вызовы получения обычно возвращаются, как только достигнута нижняя граница. Поскольку по умолча- умолчанию это один октет, вызов возвращается, когда имеются в наличии любые данные. Флаг MSG_WAITALL указывает, что вызов должен блокироваться до тех пор, пока он не сможет вернуть все запрошенные данные, если это возможно. В качестве аль- альтернативы флаг MSG_DONTWAIT заставляет вызов действовать таким образом, как будто сокет находится в не блокирующем режиме, возвращая EAGAIN вместо бло- блокирования. Данные, присутствующие в приемном буфере данных, организованы одним из не- нескольких способов в зависимости от того, сохраняются ли границы сообщений. Имеются три типичных случая для сокетов потоков, дейтаграмм и упорядоченных па- пакетов. В общем случае приемный буфер данных организован в виде списка сообщений (см. рис. 11.12). Каждое сообщение может включать адрес отправителя (для протоко- протоколов дейтаграмм), служебные данные и обычные данные. В зависимости от протокола срочные или внеполосные данные могут также быть помещены в обычный приемный буфер. Каждая цепочка mbuf в списке представляет одно сообщение или (для послед- последней цепочки) возможно неполную запись. Протоколы, которые предоставляют с каждым сообщением адрес отправителя, помещают один mbuf, содержащий адрес, в начало сообщения. За каждым адресом сразу следует необязательный mbuf, содержа- содержащий все служебные данные, mbuf с обычными данными следует за служебными дан- данными. Имена и служебные данные различаются по полю типа mbuf; адреса помечают- помечаются как MTSONAME, тогда как служебные данные помечаются как MT_CONTROL.
11.6. Обмен данными 539 • • • sbjnb — • • • J Сокет mjiext — mjiextpkt — MT_SONAME mjiata, m_len mjiat »* "Л L Сообщение г Г mjiext — mjiextpkt — MT_SONAME mjiata, m_len mjiat mjiext — mjiextpkt — MT_DATA mjiata, m_len mjdat > mjiext — mjiextpkt — MT_DATA mjiata, m_len mjiat ^Сообщение Рис. 11.12. Очередь данных для сокета дейтаграмм n Л _^7 , J П I Л <7 i i i i i i i i i j Каждое сообщение, помимо последнего, рассматривается как завершенное. Последнее сообщение завершается явным образом, когда используется неделимый протокол, такой, как большинство протоколов дейтаграмм. Протоколы упорядоченных пакетов могли бы рассматривать каждое сообщение как неделимую запись, или они могли бы поддерживать записи, которые могли бы быть произвольной длины (как сделано в OSI). В последнем случае финальная запись в буфере могла или не могла бы быть за- завершенной, а флаг последнего mbuf M_EOR помечает завершение записи. Границы записи (если они есть) обычно протоколами потоков игнорируются. Однако переходы от внеполосных данных к обычным данным в буфере (или наличие служебных дан- данных) создают логические границы. Одна операция приема никогда не возвращает дан- данные, которые пересекают логическую границу. Обратите внимание, что схема хране- хранения, используемая сокетами, позволяет им уплотнять данные одного и того же типа в минимальное число mbuf, необходимое для хранения этих данных. При входе в soreceiveQ делается проверка, не запрашиваются ли внеполосные дан- данные. Если это так, у уровня протокола запрашивается, доступны ли такие данные и если данные доступны, то они возвращаются вызывающему. Поскольку обычные данные нельзя получать одновременно с внеполосными данными, sorecieveQ после этого возвращается. В противном случае были запрошены данные из обычной очереди.
540 Глава 11. Межпроцессное взаимодействие Функция soreceiveQ сначала проверяет, находится ли сокет в состоянии confirming, когда противоположный узел ожидает подтверждения запроса соединения. Если это так, данные до подтверждения соединения поступить не могут, и уровень протокола уведомляется, что соединение должно быть установлено полностью. Затем soreceiveQ проверяет число символов буфера приема данных, чтобы узнать, доступны ли данные. Если да, вызов возвращается с доступными в данный момент данными. Если данных нет, soreceiveQ проверяет состояние сокета, чтобы выяснить, могут ли появиться дан- данные. Данные могут больше не поступать, поскольку сокет отсоединен (и для получе- получения данных необходимо соединение) или поскольку прием данных был завершен по- посредством shutdown со стороны противоположного узла. Кроме того, если асинхронно была обнаружена ошибка от предыдущей операции, ее нужно вернуть пользователю; soreceiveQ проверяет поле so_error после проверки данных. Если ни данных, ни ошибки нет, данные по-прежнему могут поступить, и если сокет не помечен для неблокирующего ввода/вывода, soreceiveQ помещает процесс в состояние сна в ожида- ожидании поступления новых данных. Когда поступают данные для сокета, поддерживающий протокол уведомляет уро- уровень сокета посредством вызова sorwakeupQ. Затем soreceiveQ может обработать содержимое буфера приема, соблюдая описанные ранее правила структурирования данных. soreceiveQ сначала удаляет все адреса, которые могут присутствовать, затем необязательные служебные данные, и наконец, обычные данные. Если приложение предоставило буфер для приема служебных данных, они передаются приложению в этот буфер; в противном случае они уничтожаются. Удаление данных слегка услож- усложнено взаимодействием между внутриполосными и внеполосными данными, управляе- управляемыми протоколом. Положение следующей порции внеполосных данных может быть помечено во внутриполосном потоке данных и используется в качестве границы записи при обработке внутриполосных данных. То есть когда получены внеполосные данные протоколом, который хранит внеполосные данные отдельно от обычного буфера, соответствующее место в потоке внутриполосных данных помечается. Затем, когда делается запрос для получения внутриполосных данных, данные будут возвра- возвращены лишь до отметки. Эта отметка позволяет приложениям синхронизировать внутри- и внеполосные данные таким образом, чтобы, например, полученные данные можно было сбросить до того места, в котором получены внеполосные данные. У каж- каждого сокета есть поле so_oobmark, которое содержит смещение символов от начала приемного буфера данных до места в потоке данных, в котором было получено послед- последнее внеполосное сообщение. Когда из приемного буфера внутриполосные данные уда- удалены, смещение обновляется таким образом, чтобы данные за отметкой не были сме- смешаны с данными, предшествующими отметке. Когда so_oobmark достигает нуля, уста- устанавливается бит SSRCVATMARK в поле состояния сокета, чтобы показать, что по- пометка внеполосных данных находится в начале приемного буфера сокета. Приложение может проверить состояние этого бита с помощью вызова ioctl S IOC ATM ARK, чтобы выяснить, были ли прочитаны все внутриполосные данные вплоть до отметки.
11.7. Отключение сокета 541 Когда данные были удалены из приемного буфера сокета, soreceiveQ обновляет состояние сокета и уведомляет уровень протокола о том, что данные были получены пользователем. Уровень протокола может использовать эту информацию для освобож- освобождения внутренних ресурсов, для запуска сквозного подтверждения получения данных, для обновления сведений управления потоком или для запуска новой передачи. В конечном счете, если в качестве служебных данных были получены какие-либо права доступа, soreceiveQ передает их специфической процедуре коммуникационного домена для пре- преобразования от их внутреннего представления во внешнее. Функция soreceiveQ возвращает набор флагов, которые предоставляются вызы- вызывающему системного вызова recvmsg через поле msgjlags структуры msghdr (см. рис. 11.1). Возможные флаги включают MSG_EOR для указания, что полученные данные завершают запись для неделимого протокола упорядоченных пакетов; MSG_OOB для указания, что из обычного приемного буфера сокета были получены срочные (внеполосные) данные; MSG_TRUNC для указания того, что неделимая запись была усечена, поскольку предоставленный буфер слишком маленький; MSG_CTRUNC для указания того, что служебные данные были усечены, поскольку буфер управления слишком маленький. 11.7. Отключение сокета Хотя закрытие сокета и освобождение его ресурсов на первый взгляд выглядит как простая операция, она может быть сложной. Сложность возникает из-за неявной се- семантики системного вызова close. В определенных ситуациях (например, когда про- процесс завершается) от вызова close никогда не ожидают неудачного завершения. Однако, когда со кет, обещающий надежную доставку данных, закрывается с данными, по-преж- по-прежнему находящимися в очереди отправки, или ожидает подтверждения получения, он должен пытаться передать данные, возможно, в течение неопределенного периода вре- времени, чтобы вызов close сохранил обещанную им семантику. Если сокет уничтожает поставленные в очередь данные, чтобы позволить close завершиться успешно, он нарушает свое обещание надежной доставки данных. Уничтожение данных может заставить неподготовленные процессы, которые зависят от неявной семантики close, работать в сетевом окружении ненадежно. Однако, если сокеты блокируются до тех пор, пока все данные не будут надежно переданы, тогда в некоторых коммуникацион- коммуникационных доменах close может никогда не завершиться! Уровень сокетов идет на компромисс в попытке разрешить эту проблему, сохраняя тем не менее семантику системного вызова close. На рис. 11.13 показаны возможные переходы состояний для сокета от соединенного в закрытое состояние. При обычной работе закрытие сокета вызывает уничтожение всех находящихся в очереди, но непод- неподтвержденных данных. Если сокет находится в соединенном состоянии, инициируется разъединение. Сокет помечается для указания того, что дескриптор файла больше
542 Глава 11. Межпроцессное взаимодействие не ссылается на него, и операция закрытия возвращается успешно. Когда завершается вызов разъединения, сетевая поддержка уведомляет уровень сокетов, и ресурсы сокета освобождаются. Сетевой уровень может попытаться передать данные, поставленные в очередь в буфер отправки сокета, хотя гарантии, что он это сделает, нет. Однако используемые в большинстве случаев ориентированные на соединение протоколы обычно пытаются передать все находящиеся в очереди данные асинхронно после того, как вызов close возвращается, сохраняя обычную семантику close для файла. sowakeup () |Данные 'отправлены I* i 1 sosendQ и sbwaitQ sowakeupQ SSJSCONNECTED close () soclose () sodisconnect{) Новые данные ~[ т ! получены I soreceive{) ' ' и sbwait() SSJSDISCONNECTING и не разъединено sofree() Рис. 11.13. Переходы состояний сокета в ходе выключения В качестве альтернативы сокет может быть помечен явным образом, чтобы заста- заставить прикладной процесс задержаться при закрытии до тех пор, пока не будут очище- очищены остающиеся данные и соединение не будет разорвано. Такая возможность помеча- помечается в структуре данных socket путем использования системного вызова setsockopt с опцией SO_LINGER. Когда приложение указывает, что сокет должен задержаться, оно указывает также длительность периода задержки. Затем приложение может бло- блокироваться до тех пор, пока не пройдет указанный период времени в ожидании очист- очистки оставшихся данных. Если период задержки истечет до того, как разъединение завершится, уровень сокетов уведомляет сеть, что он закрывается, с возможным уничтожением любых оставшихся данных. Некоторые протоколы обрабатывают опцию задержки по-другому. В частности, если опция задержки установлена с нулевой длительностью, протокол может уничтожить ожидающие данные вместо того, чтобы пытаться доставить их асинхронно. 11.8. Локальное межпроцессное взаимодействие Интерфейсы сокетов являются не единственным API, который обеспечивает межпро- межпроцессное взаимодействие. Приложения, которые хотят разделять работу на одном хосте, используют для взаимодействия своих процессов семафоры, очереди сообщений и раз- разделяемую память. Каждый вид локального IPC имеет свои показатели производитель-
11.8. Локальное межпроцессное взаимодействие 543 ности и предоставляет другую форму коммуникации. Механизмы локального IPC, поддерживаемые в настоящее время в FreeBSD 5.2, унаследованы от System V, как опи- описано в Bach [1986]. По этой причине их часто называют семафорами, мьютексами и разделяемой памятью System V. Каждый вид IPC должен давать независимо выполняющимся процессам возмож- возможность встретиться и найти ресурсы, которые они разделяют. Эта часть информации должна быть известна им всем и должна быть достаточно уникальной, чтобы ни один другой процесс не мог случайно натолкнуться на эту же информацию. В области локаль- локальных IPC эта часть информации называется ключом. Табл. 11.6. Локальный Подсистема Семафоры Очереди сообщений Разделяемая память IPC, API уровня пользователя Создает semget msgget shmget Управление semctl mesgctl shmctl, shmdt Коммуникация semop msgrcv, msgsnd Нет Ключ является длинным целым, который рассматривается взаимодействующими процессами в качестве непрозрачной части данных, что означает, что они не делают попытки расшифровать или приписать ему какое-нибудь значение. Для создания ключа из имени пути используется библиотечная процедура ftokQ. Пока каждый про- процесс использует одно и то же имя пути, они гарантированно получают один и тот же ключ. Все подсистемы локального IPC были спроектированы и реализованы для исполь- использования сходным образом. Когда у процесса есть ключ, он использует его для создания или получения соответствующего объекта, используя специфический для системы вызов get, который аналогичен open или creat файла. Чтобы создать объект, флаг IPC_CREAT передается в качестве аргумента вызову get. Все вызовы get возвращают целое, которое должно использоваться во всех последующих системных вызовах IPC. Подобно дескриптору файла, это целое используется для идентификации объекта, которым манипулирует процесс. У каждой подсистемы свой собственный способ работы с лежащим в основе объ- объектом, и эти функции описываются в следующих разделах. Все управляющие опера- операции, такие, как получение статистики или удаление ранее созданного объекта, выпол- выполняются специфической для подсистемы процедурой ей. Сводка всех API уровня поль- пользователя приведена в табл. 11.6, а прекрасное введение в их использование можно найти в Stevens [1999].
544 Глава 11. Межпроцессное взаимодействие Семафоры Семафор является наименьшим атомом IPC, доступным набору взаимодействующих процессов. Каждый семафор содержит короткое целое, которое можно увеличить или уменьшить. Процесс, пытающийся уменьшить значение семафора ниже 0, будет либо заблокирован, либо, при вызове в неблокирующем режиме, сразу будет возвращен со значением errno EAGAIN. Концепция семафоров и то, как они используются в мно- многопроцессных программах, первоначально были предложены в Dijkstra & Genuys [1968]. В отличие от семафоров, описанных в большинстве учебников по вычислительной технике, семафоры в FreeBSD 5.2 сгруппированы в массивы таким образом, чтобы код в ядре мог защитить процесс, использующий их, от возникновения взаимоблокировки. Взаимоблокировки (тупики) были обсуждены в контексте блокировок внутри ядра в разделе 4.3, но также обсуждаются и здесь. С семафорами System V взаимоблокировка происходит между двумя процессами уровня пользователя, а не между потоками ядра. Взаимоблокировка происходит между двумя процессами, А и В, когда оба они пытаются получить два семафора, S\ и S2- Если процесс А получает Sb а процесс В получает S2, возникает взаимоблокировка, когда процесс А пытается получить S2, а процесс В пытается получить Sh поскольку для ка- каждого из процессов нет способа оставить семафор, который нужен другому для про- продолжения работы. При использовании семафоров всегда важно, чтобы все взаимодей- взаимодействующие процессы получали и освобождали их в том же самом порядке, чтобы избе- избежать этой ситуации. Реализация семафоров в System V предупреждала взаимоблокировки, заставляя пользователя API группировать свои семафоры в массивы и выполняя операции сема- семафоров в виде последовательности событий массива. Если последовательность, пред- представленная в вызове, могла вызвать взаимоблокировку, возвращалась ошибка. Раздел по семафорам в Bach [1986] указывает, что эта сложная семантика никогда не должна помещаться в ядро, но для того, чтобы придерживаться ранее принятого API, та же се- семантика существует и в FreeBSD. В некоторый момент в будущем ядро должно пре- предоставить более простую форму семафоров, чтобы заменить текущую реализацию. Создание и присоединение семафора осуществляется с помощью системного вызова semget. Хотя семафоры разрабатывались так, чтобы выглядеть как дескрипторы файлов, они не хранятся в таблице дескрипторов файлов. Все семафоры в системе со- содержатся в ядре в одной таблице, размер и форма которой описываются несколькими настраиваемыми параметрами. Эта таблица защищена всеобщей блокировкой (giant lock) (см. раздел 4.3), создающей внутри нее элементы. Эта блокировка берется лишь при создании или присоединении к семафору и не является узким местом при реаль- реальном использовании существующих семафоров. Когда процесс создал семафор или присоединил существовавший до этого, он вызывает semop, чтобы выполнить операции с ним. Операции с семафором передаются
11.8. Локальное межпроцессное взаимодействие 545 системному вызову в виде массива. Каждый элемент массива включает номер сема- семафора для работы (индекс в массиве, возвращенном предыдущим вызовом semget), операцию для выполнения и набор флагов. Операция является неправильным названи- названием, поскольку это не команда, а просто число. Если число положительно, значение соответствующего семафора увеличивается на это значение. Если операция равна О, а значение семафора не равно 0, процесс либо помещается в состояние сна, пока значе- значение не достигнет 0, либо, если был передан флаг IPC_NOWAIT, вызывающему возвра- возвращается ошибка EAGAIN. Когда операция отрицательная, есть несколько возможных последствий. Если значение семафора было больше, чем абсолютное значение опера- операции, значение операции вычитается из значения семафора и вызов возвращается. Если вычитание абсолютного значения операции из значения семафора сделало бы^ это значение меньшим нуля, процесс помещается в состояние сна, если не был передан флаг IPC_NOWAIT. В этом случае вызывающему возвращается EAGAIN. Вся эта логика реализуется в системном вызове semop. Вызов сначала выполняет некоторые элементарные проверки, чтобы убедиться, что у него есть возможность успешного выполнения, включая проверку того, что достаточно памяти для выполне- выполнения всех операций в один проход и что вызывающий процесс имеет соответствующие разрешения для доступа к семафору. Каждый ID семафора, возвращенный процессу ядром, имеет свой собственный мьютекс для защиты от изменения несколькими про- процессами одного и того же семафора в одно и то же время. Процедура блокирует этот мьютекс, а затем пытается выполнить все операции, переданные ей в массиве. Она об- обходит массив и пытается по порядку выполнить каждую операцию. Есть вероятность, что этот вызов войдет в состояние сна до того, как завершит всю свою работу. Если воз- возникнет такая ситуация, код откатывает обратно всю свою работу до перехода в состоя- состояние сна. Когда она пробуждается снова, процедура запускается с начала массива и пы- пытается снова выполнить операции. Процедура либо выполнит всю свою работу, вернется с соответствующей ошибкой, либо вернется в состояние сна. Откат всей работы необ- необходим для обеспечения идемпотентности процедуры. Либо делается вся работа, либо ничего не делается. Очереди сообщений Очередь сообщений способствует отправке и получению типизированных сообщений произвольного размера. Отправляющий процесс добавляет сообщения в один конец очереди, а получающий процесс удаляет сообщения из другого конца. Размер очереди и другие характеристики контролируются набором настраиваемых параметров ядра. Очереди сообщений по своей сути являются полудуплексными, что означает, что один процесс всегда является отправителем, а другой получателем, но нет способа исполь- использовать их в полнодуплексной коммуникации, как мы увидим позже.
546 Глава 11. Межпроцессное взаимодействие Сообщения, передаваемые между конечными точками, содержат тип и область данных, как показано на рис. 11.14. Эту структуру данных не следует путать с mbuf, которые используются сетевым кодом (см. раздел 11.3). MSGMNB является настраи- настраиваемым параметром ядра, который определяет размер очереди сообщений и соответст- соответственно самое большое сообщение, которое можно передать между двумя процессами, и по умолчанию установлено в 2048. mtype 4 байта mtext 234 байта- Рис. 11.14. Структура данных сообщения Очереди сообщений могут быть использованы для реализации либо чистой очере- очереди «первым вошел, первым вышел», когда все сообщения доставляются в том порядке, в каком они отправляются, либо приоритетной очереди, когда сообщения определенно- определенного типа могут быть получены раньше других. Эта возможность предоставляется полем type структуры сообщения. Когда процесс отправляет сообщение, он запускает системный вызов msgsnd, который проверяет правильность всех аргументов вызова, а затем пытается получить достаточно ресурсов, чтобы поместить сообщение в очередь. Если ресурсов недоста- недостаточно и пользователь не передал флаг IPC_NOWAIT, вызывающий помещается в со- состояние сна до тех пор, пока ресурсы не станут доступными. Ресурсы поступают из пула памяти, который выделяется ядром во время загрузки. Пул устроен фиксиро- фиксированными сегментами, размер которых определяется MSGSSZ. Пул памяти управляется как большой массив, поэтому можно эффективно находить сегменты. Структуры данных ядра, управляющие очередями сообщений в системе, защище- защищены единственной блокировкой (msgjntx), которая получается и удерживается как msg- msgsnd, так и msgrcv в течение своего выполнения. Использование одной блокировки для обеих процедур предупреждает одновременное чтение очереди и запись в нее, что могло бы вызвать повреждение данных. Это является также узким местом производи- производительности, поскольку означает, что все другие очереди сообщений блокируются, когда используется любая одна из них. Когда у ядра достаточно ресурсов, оно копирует сообщение в сегменты в массиве и обновляет остальные структуры данных, относящиеся к данной очереди. Чтобы получить сообщение из очереди, процесс вызывает msgrcv. Если процессы используют очередь в качестве простого fifo, тогда получатель передает этому вызову 0 в аргументе msgtype, чтобы получить первое доступное в очереди сообщение. Чтобы получить первое в очереди сообщение определенного типа, передается положительное целое. Процессы реализуют приоритетную очередь, используя тип в качестве приори-
11.8. Локальное межпроцессное взаимодействие 547 тета сообщения. Для реализации полнодуплексного канала каждый процесс выбирает другой тип - скажем, 1 и 2. Сообщения типа 1 от процесса А, а сообщения типа 2 от В. Процесс А посылает сообщения типа 1, а получает сообщения типа 2, тогда как про- процесс В делает обратное. После получения мьютекса очереди сообщений принимающая процедура находит нужную очередь для получения данных и, если там есть соответствующее сообщение, возвращает данные из сегментов вызывающему. Если данные недоступны и вызы- вызывающий указал флаг IPC_NOWAIT, вызов возвращается немедленно; в противном случае вызывающий процесс помещается в состояние сна до тех пор, пока не появятся данные для возвращения. Когда из очереди сообщений получено сообщение, его данные освобождаются после того, как они доставлены получающему процессу. Разделяемая память Разделяемая память используется тогда, когда двум или более процессам нужно пере- передавать между собой большие объемы данных. Каждый процесс хранит данные в разде- разделяемой памяти точно так же, как внутри своей индивидуальной памяти процесса. Нужно позаботиться о том, чтобы упорядочить доступ к разделяемой памяти таким образом, чтобы процессы не записывали один поверх другого. Поэтому разделяемая память часто используется с семафорами, чтобы синхронизировать доступ для чтения и записи. Процессы, использующие разделяемую память, в действительности разделяют виртуальную память (см. главу 5). Когда процесс создает сегмент разделяемой памяти посредством системного вызова shmget, ядро выделяет набор страниц виртуальной памяти и помещает указатель на них в описатель разделяемой памяти, который затем возвращается вызывающему процессу. Чтобы фактически использовать внутри про- процесса разделяемую память, он должен сделать системный вызов shmat, который при- присоединяет страницы виртуальной памяти к вызывающему процессу. Процедура при- присоединения использует описатель разделяемой памяти, переданный ей в качестве аргумента, чтобы найти соответствующие страницы, и возвращает соответствующий виртуальный адрес вызывающему. Когда этот вызов завершается, процесс может затем получить доступ к памяти, на которую указывает возвращенный адрес, как если бы он работал с любым другим видом памяти. Когда процесс заканчивает использование разделяемой памяти, он отсоединяется от нее, используя системный вызов shmdt. Эта процедура не освобождает ассоцииро- ассоциированную память, поскольку другие процессы могут использовать ее, но удаляет отобра- отображение виртуальной памяти из вызывающего процесса. Подсистема разделяемой памяти использует систему виртуальной памяти для выполнения большей части реальной работы (отображение страниц, обработка грязных страниц и т.д.), поэтому ее реализация сравнительно проста.
548 Глава 11. Межпроцессное взаимодействие Упражнения 11.1. Какие ограничения в использовании каналов вдохновили разработчиков на проектирование альтернативных средств межпроцессного взаимодейст- взаимодействия? 11.2. Почему средства межпроцессного взаимодействия FreeBSD для именования сокетов спроектированы как независимые от файловой системы? 11.3. Почему межпроцессное взаимодействие построено в FreeBSD поверх сетевой организации, а не сделано другим способом? 11.4. Как в соответствии с приведенными в данной главе определениями можно было бы рассмотреть экранный редактор - как неподготовленную или усовершенствованную программу? Объясните свой ответ. 11.5. Что такое внеполосные (out-of-band) данные? Какие типы сокетов поддерживают передачу внеполосных данных? Опишите одно из возможных применений внеполосных данных. 11.6. Приведите два требования, которые налагает межпроцессное взаимодействие на средства управления памятью. 11.7. Сколько mbuf и кластеров mbuf потребовалось бы для хранения сообщения в 3024 байта? Нарисуйте картинку необходимой цепочки mbuf и всех соот- соответствующих кластеров mbuf. 11.8. Почему у mbuf есть два связывающих указателя? Для чего используется каж- каждый из них? 11.9. У буферов отправки и приема данных каждого сокета есть верхняя и нижняя границы. Для чего используются эти границы? 11.10. Рассмотрите сокет с сетевым соединением, которое поставлено в очередь сокета в ожидании системного вызова accept. Находится ли этот сокет в очереди, озаглавленной в структуре сокета полем so_comp или so_incompl Как используется очередь, которая не содержит сокет? 11.11. Опишите два вида протоколов, которые немедленно поместили бы запросы входящих соединений в очередь, озаглавленную в структуре сокета полем so_comp. 11.12. Как уровень протокола сообщает об асинхронной ошибке уровню сокета? 11.13. Со кеты явным образом воздерживаются от интерпретации данных, которые они отправляют и получают. Считаете ли вы, что этот подход правильный? Объясните свой ответ. 11.14. Почему процедура sosendQ удостоверяется, что в буфере отправки сокета достаточно места, до того как сделать вызов уровню протокола для передачи данных? 11.15. Как используется информация о типе каждого mbuf при постановке данных в очередь в сокете дейтаграмм? Как используется эта информация в поста- постановке данных в очередь в сокете потока?
Ссылки 549 11.16. Почему процедура soreceiveQ опционально уведомляет уровень протокола, когда данные удаляются из приемного буфера сокета? 11.17. Чтоб могло бы вызвать бесконечную задержку соединения при закрытии? 11.18. Опишите взаимоблокировку между двумя процессами А и В, которые совме- совместно используют два семафора S\ и Sj. 11.19. Как может использоваться очередь сообщений для реализации приоритетной очереди? Как она может использоваться для обеспечения полнодуплексной коммуникации? 11.20. Почему системный вызов shmdt не освобождает нижележащую разделяемую память? * 11.21. Какое влияние могло бы оказать уплотнение хранилища на производитель- производительность протоколов сетевой коммуникации? **11.22. Почему освобождение хранилища кластеров mbuf для системы затруднено? Объясните, почему это могло бы быть желательным. **11.23. В первоначальном проекте средств межпроцессного взаимодействия ссылка на коммуникационный домен получалась с помощью системного вызова domain int d; d = domain("inet"); (где d является дескриптором, подобным дескриптору файла), а сокеты созда- создавались потом с помощью s = socket(type, d, protocol); int s, type, protocol; Какие преимущества и недостатки имеет эта схема по сравнению со схемой, использующейся в FreeBSD? Как влияет введение дескриптора типа домена на управление и использование дескрипторами внутри ядра? **11.24. Спроектируйте и реализуйте простую замену для семафоров локального IPC, которая работает с одним семафором вместо массива. Новая система должна придерживаться оригинального API в той степени, чтобы она реализовала процедуры semget, semctl и semop. Ссылки Bach, 1986. М. J. Bach, The Design of the UNIX Operating System, Prentice-Hall, Englewood Cliffs, NJ, 1986. Cerf, 1978. V. Cerf, "The Catenet Model for Internetworking", Technical Report IEN 48, SRI Network Information Center, Menlo Park, CA, July 1978.
550 Глава 11. Межпроцессное взаимодействие Cohen, 1977. D. Cohen, "Network Control Protocol (NCP) Software", University of Illinois Software Distribution, University of Illinois, Champaign-Urbana, IL, 1977. Dijkstra & Genuys, 1968. E. Dijkstra & F. Genuys, editor, "Cooperating Sequential Processes", in Programming Languages, pp. 43-112, Academic Press, New York, NY, 1968. Gurwitz, 1981. R. F. Gurwitz, "VAX-UNIX Networking Support Project—Implementation Descrip- Description", Technical Report IEN 168, SRI Network Information Center, Menlo Park, CA, January 1981. Stevens, 1998. R. Stevens, Unix Network Programming Volume /, Second Edition, Prentice-Hall, Englewood Cliffs, NJ, 19981. Stevens, 1999. R. Stevens, Unix Network Programming Volume 2, Second Edition, Prentice-Hall, Englewood Cliffs, NJ, 19992. 1 Русский перевод: Стивене У Р. UNIX: разработка сетевых приложений. 1-е изд. С.-Петербург, Питер, 2003. - Примеч. науч. ред. 2 Русский перевод: Стивене У. P. UNIX: взаимодействие процессов. 1-е изд. С.-Петербург, Питер, 2003. - Примеч. науч. ред.
Глава 12 Сетевая коммуникация В данной главе мы изучим внутреннюю структуру сетевой подсистемы, предоставляе- предоставляемой FreeBSD. Сетевые средства предоставляют инфраструктуру, внутри которой могут сосуществовать многие сетевые архитектуры. Сетевая архитектура включает в себя набор сетевых коммуникационных протоколов - семейство протоколов', согла- соглашения по именованию конечных точек коммуникации - семейство адресов', и любые дополнительные средства, которые могут выпасть из области управления соединения- соединениями и передачи данных. Доступ к сетевым средствам осуществляется через абстракцию сокетов, описанную в главе 11. Сетевая подсистема предоставляет инфраструктуру общего назначения, внутри которой реализованы сетевые службы. Эти средства включают следующее. Структурированный интерфейс к уровню сокетов, который позволяет разрабаты- разрабатывать независимое от сети прикладное программное обеспечение. Согласованный интерфейс с аппаратными устройствами, используемый для пере- передачи и получения данных. Не зависящую от сети поддержку маршрутизации сообщений. # Управление памятью. Мы опишем внутреннюю структуру сетевой подсистемы в разделе 12.1. Затем мы обсудим интерфейс между уровнем сокетов и сетевыми средствами и изучим интерфейсы между программными уровнями, которые составляют сетевую подсистему. В разделе 12.5 мы обсудим службы маршрутизации, используемые сетевыми протоко- протоколами; в разделе 12.6 мы опишем механизмы, предусмотренные для управления буферизацией и управления перегрузкой. Мы представим интерфейс непосредствен- непосредственных (raw) сокетов, предоставляющий непосредственный доступ к сетевым протоко- протоколам нижележащего уровня, в разделе 12.7. Наконец, в разделе 12.8 мы обсудим ряд проблем и возможностей, включая внеполосные данные, адресацию подсетей и прото- протокол разрешения адресов.
552 Глава 12. Сетевая коммуникация После того как мы обсудим инфраструктуру, в которую вписываются сетевые про- протоколы, мы в главе 13 исследуем реализации нескольких существующих сетевых про- протоколов. Подробное описание внутренних структур данных и функций сетевого уровня и протоколов можно найти в Wright & Stevens [1995]. 12.1. Внутренняя структура Сетевая подсистема логически делится на три уровня. Эти три уровня управляют сле- следующими задачами. 1. Транспортировкой данных между процессами. 2. Межсетевой адресацией и маршрутизацией сообщений. 3. Поддержкой средств передачи. Первые два уровня составлены из модулей, реализующих коммуникационные прото- протоколы. Программное обеспечение на третьем уровне большей частью включает подуро- подуровень протокола, а также один или более драйверов сетевых устройств. Самый верхний уровень в сетевой подсистеме называется транспортным уров- уровнем. Транспортный уровень должен предоставить структуру адресации, которая сдела- сделает возможным взаимодействие между сокетами и любыми механизмами протоколов, необходимыми для семантики сокетов, такой, как надежная доставка данных. Второй уровень, сетевой уровень, отвечает за доставку данных, предназначенных для удален- удаленного транспорта или для протоколов сетевого уровня. Предоставляя межсетевую дос- доставку, сетевой уровень должен управлять индивидуальной базой данных маршрутиза- маршрутизации или использовать общесистемные средства маршрутизации сообщений до их хоста назначения. Нижний уровень, уровень сетевого интерфейса, или канальный уро- уровень, отвечает за транспортировку сообщений между хостами, подключенными к передающей среде. Уровень сетевого интерфейса занимается главным образом управлением использующимися средствами передачи и выполнением необходимых сборки к разборки протокола канального уровня. Транспортный, сетевой уровни и уровень сетевого интерфейса сетевой подсисте- подсистемы соответствуют транспортному, сетевому и канальному уровням соответственно в эталонной модели взаимодействия открытых систем ISO [ISO, 1984]. Внутренняя структура сетевого программного обеспечения не видна пользователям непосредст- непосредственно. Вместо этого доступ ко всем сетевым средствам осуществляется через уровень сокетов, описанный в главе 11. Каждый коммуникационный протокол, разрешающий доступ к своим средствам, экспортирует ряд процедур пользовательских запросов на уровень сокетов. Эти процедуры используются уровнем сокетов при предоставлении доступа к сетевым службам.
12.1. Внутренняя структура 553 Описанное здесь разбиение на уровни является логическим. Программное обес- обеспечение, реализующее сетевые службы, может использовать больше или меньше ком- коммуникационных протоколов в зависимости от дизайна поддерживаемой сетевой архи- архитектуры. Например, непосредственные сокеты часто используют пустую реализацию на одном или более уровнях. Другая крайность, туннелирование одного протокола посредством другого, использует один сетевой протокол для инкапсуляции и доставки пакетов для другого протокола и вовлекает несколько экземпляров некоторых уровней. Поток данных Ранние версии BSD использовались в сети как оконечные системы. Они были либо источником, либо местом назначения информации. Хотя многие инсталляции исполь- использовали рабочую станцию в качестве шлюза масштаба отдела предприятия, для выпол- выполнения более сложных задач установки мостов и маршрутизации использовалось спе- специальное аппаратное обеспечение. Во время первоначальной разработки и реализации сетевых подсистем возможность обеспечения безопасности данных путем шифрова- шифрования пакетов по-прежнему была в далеком будущем. С момента того первоначального проекта было сделано множество изменений кода. Благодаря росту общей производи- производительности процессора теперь довольно просто построить мост или маршрутизатор и стандартных комплектующих, а внедрение специализированных криптографических сопроцессоров сделало шифрование пакетов практичным в домашних условиях, в кафе и офисах. Эти факты сделали обсуждение потока данных внутри сетевой под- подсистемы более сложной, чем она когда-то была. Хотя идеи сложны, код еще более сложен. Несколько различных реализаций доба- добавили свои индивидуальные особенности изменений, чтобы их конкретные приложения работали таким способом, как они того хотели, и эти изменения привели к чрезмерно сложной реализации. Основная идея, которую следует иметь в виду, заключается в том, что через сетевой узел есть лишь четыре действительных пути. Входящий Предназначен для приложения уровня пользователя. Исходящий От приложения уровня пользователя в сеть. Пересылка В мосте или маршрутизаторе пакеты предназначены не для данного узла, а для пересылки в другую сеть или на другой хост. Ошибка Прибыл пакет, который требует, чтобы сетевая подсистема сама отпра- отправила ответ без вовлечения приложения уровня пользователя. Входящие данные, полученные через сетевой интерфейс, движутся наверх через коммуникационные протоколы до тех пор, пока они не будут помещены в приемную очередь сокета назначения. Исходящие данные движутся вниз в сетевую подсистему от уровня сокетов посредством вызовов модулей транспортного уровня, которые под- поддерживают абстракцию сокетов. Поток данных вниз обычно начинается системным
554 Глава 12. Сетевая коммуникация вызовом. Данные, движущиеся наверх, поступают асинхронно и передаются от уровня сетевого интерфейса соответствующему коммуникационному протоколу через входные очереди сообщений протоколов, как показано на рис. 12.1. Система управляет входящим сетевым трафиком, разделяя обработку пакетов между верхней и нижней половинами сетевого драйвера. Нижняя часть драйвера работает в контексте прерывания устройства, обрабатывает физические прерывания от устройства и управляет памятью устройства. Верхняя часть драйвера работает в качестве потока прерывания и может либо поме- помещать пакеты в очередь для сетевого потока swi_net, либо обрабатывать их до заверше- завершения. По умолчанию FreeBSD 5.2 помещает все пакеты в очередь. Точная (fine-grained) блокировка сетевого кода, разработанная для поддержки в FreeBSD симметричной многопроцессорной обработки, сделала возможным вытеснение потоков прерываний, которые обрабатывают пакеты, без отрицательных побочных эффектов, что означает, что производительность интерактивной работы не страдает, когда система испытывает большую сетевую нагрузку. Третьей альтернативой для обработки пакетов является опрос системой наличия пакетов, и этот опрос был добавлен в качестве эксперимен- экспериментальной возможности, но в настоящее время он используется лишь в немногих устрой- устройствах и не будет здесь обсуждаться. Когда пакет помещен в очередь потоком прерывания устройства, за обработку пакета отвечает поток swijtet. Этот обработчик представляет собой поток в ядре, задачей которого является считывание пакетов из очереди. Если сообщение, получен- полученное коммуникационным протоколом, предназначено для протокола вышележащего уровня, этот протокол вызывается непосредственно. Если сообщение предназначено для другого хоста (т.е. следует пути пересылки) и система сконфигурирована в качестве маршрутизатора, сообщение может быть возвращено уровню сетевого интерфейса для повторной передачи. Коммуникационные протоколы Сетевой протокол определяется набором соглашений, включая форматы пакетов, состояния и переходы состояний. Модуль коммуникационного протокола реализует протокол и составлен из набора процедур и частных структур данных. Модули прото- протокола описываются структурой переключения протокола, которая содержит ряд види- видимых извне точек входа и определенные атрибуты, показанные на рис. 12.2. Уровень со- кетов взаимодействует с коммуникационным протоколом исключительно через струк- структуру переключения протоколов последнего, записывая адрес структуры в поле so_proto сокета. Это изолирование уровня сокетов от сетевой подсистемы важно для обеспечения того, что уровень сокетов предоставляет пользователям согласованный интерфейс для всех протоколов, поддерживаемых системой. Когда создается сокет, уровень сокетов просматривает домен для семейства протоколов в поиске массива структур переключения протоколов для этого семейства (см. раздел 11.4). Протокол выбирается из массива на основе поддерживаемого типа сокета (поле prjtype) или по выбору на основе определенного номера протокола (поле pr_protocol). У переключателя
12.1. Внутренняя структура 555 Уровень сокетов ДАННЫЕ soreceiveQ Транспортный уровень Сетевой уровень IP TCP ДАННЫЕ TCP ДАННЫЕ Программное прерывание Network-Interface Layer ETHER IP TCP ДАННЫЕ T Прерывание от устройства ETHERNET РИС. 12.1. Пример движения вверх пакета данных в сетевой подсистеме. Обозначения: ETHER - заголовок Ethernet; IP - заголовок протокола Интернета; TCP - заголовок протокола управления передачей протоколов есть обратный указатель на домен (prjdomain). Внутри семейства протоко- протоколов каждый протокол, способный к непосредственной поддержке сокета (например, большинство транспортных протоколов), должен предоставить структуру переключе- переключения протоколов, описывающую протокол. Протоколы нижележащего уровня, такие, как протоколы сетевого уровня, также могут иметь элементы переключения протоко- протокола, хотя это может зависеть от соглашений внутри семейства протоколов. Перед первым использованием протокола вызывается процедура инициализации протокола. После этого протокол будет вызываться для основанных на времени дейст- действиях каждые 200 миллисекунд, если присутствует элемент pr_fasttimo(), и каждые 500 миллисекунд, если присутствует элемент prjslowtimo(). Вообще для большинства обработок таймера протоколы используют более медленный таймер; основным использованием быстрого таймера является обработка отложенных подтверждений в надежных транспортных протоколах. Предусмотрен элемент pr_drain(), чтобы система могла уведомить протокол, если система испытывает недостаток памяти и хотела бы
556 Глава 12. Сетевая коммуникация Идентификаторы протокола Интерфейс со кет-протокол Интерфейс протокол-протокол Вспомогательные процедуры Тип Домен Протокол Флаги Таблица запросов пользователя Вход данных Выход данных Управляющий вход Управляющий выход Инициализация Быстрый тайм-аут Медленный тайм-аут Очистка Фильтр протокола Рис. 12.2. Структура переключения протоколов сбросить все некритические данные. Наконец, элемент pr_pfil() предоставляет ловуш- ловушку для вызовов фильтрации пакетов, что дает системному администратору возмож- возможность уничтожать или модифицировать пакеты, когда они обрабатываются сетевой подсистемой. Протоколы могут передавить данные между своими уровнями в mbuf (см. раздел 11.3), используя процедуры pr_input() и pr_output(). Процедура pr_input() передает данные наверх к пользователю, тогда как процедура pr_output() передает данные вниз по направлению к сети. Аналогичным образом управляющая информация передается вверх и вниз через процедуры pr_ctlinput() wpr_ctloutput(). Таблица процедур запросов пользователя, prjusrreqs(), является интерфейсом между протоколом и уровнем сокетов; она подробно описана в разделе 12.2. Вообще протокол отвечает за пространство, занимаемое любым из аргументов, передаваемых вниз через эти процедуры, и должен либо передать пространство даль- дальше, либо освободить его. При выводе самый низкий достигнутый уровень должен освободить пространство, переданное в виде аргументов; при вводе за освобождение переданного ему пространства отвечает самый высший уровень. Вспомогательное пространство, необходимое протоколам, выделяется из пула mbuf. Это пространство используется временно для составления сообщений или для хранения адресов сокетов с переменным размером. (Некоторые протоколы используют mbuf также для структур данных, таких, как блоки управления состояниями, хотя многие подобные применения были переделаны на непосредственное использование mallocQ.) Mbuf, выделенные протоколом для индивидуального использования, должны быть освобождены этим протоколом, когда они больше не нужны. Поле prjlags в структуре переключения протокола описывает возможности прото- протокола и определенные аспекты его работы, которые относятся к работе на уровне сокетов;
12.1. Внутренняя структура 557 флаги перечислены в табл. 12.1. Протоколы, которые основываются на соединении, указывают флаг PR_CONNREQUIRED, чтобы процедуры сокетов никогда не пыта- пытались отправить данные до установления соединения. Если установлен флаг PR_WANTRCVD, процедуры сокетов будут уведомлять протокол, когда пользова- пользователь удалил данные из приемной очереди сокета. Это уведомление дает протоколу воз- возможность реализовать уведомление о приеме пользователем, а также обновить ин- информацию об управлении потоком, основываясь на объеме памяти, доступной в при- приемной очереди. Поле PR_ADDR указывает, что все данные, помещенные протоколом в приемную очередь сокета, будут предварены адресом отправителя. Флаг PR_ATOMIC указывает, что каждый пользовательский запрос отправки данных должен выполнять- выполняться посредством единственного запроса отправки протокола', за сохранение границ записи отправляемых данных отвечает протокол. Этот флаг предполагает также, что сообщения должны получаться и доставляться процессам неделимым образом. Флаг PR_RIGHTS указывает, что протокол поддерживает передачу прав доступа; этот флаг в настоящее время используется лишь протоколами в домене локальной коммуника- коммуникации. Ориентированные на соединение протоколы, позволяющие пользователю уста- устанавливать, отправлять данные и разрывать соединение в единственном вызове sendto, устанавливают флаг PR_IMPLOPCL. Флаг PR_LASTHDR используется протоколами безопасности, такими, как IPSec, когда для получения фактических данных должно быть обработано несколько заголовков. Табл. 12.1. Флаги протоколов Флаг Описание PR_ATOMIC Сообщения отправляются отдельно, каждое в отдельном пакете PR_ADDR С каждым сообщением протокол представляет адрес PR_CONNREQUIRED Для передачи данных требуется соединение PR_WANTRCVD Протокол уведомляется о получении данных пользователем PR_RIGHTS Протокол поддерживает передачу прав доступа PR_IMPLOPCL Подразумеваемые открывание/закрывание PR_LASTHDR Используется протоколами безопасности для проверки последнего заголовка Сетевые интерфейсы Каждый сконфигурированный в системе сетевой интерфейс определяет путь каналь- канального уровня, посредством которого можно отправлять и получать сообщения. Путь ка- канального уровня является путем, который позволяет отправить сообщение посредством одной передачи на место его назначения без пересылки сетевого уровня. Обычно с этим интерфейсом ассоциируется аппаратное устройство, хотя нет требования, чтобы оно было (например, во всех системах есть программный интерфейс возврат- возвратной петли). Кроме манипулирования аппаратным устройством модуль сетевого
558 Глава 12. Сетевая коммуникация интерфейса отвечает за упаковку и распаковку любых заголовков протокола канального уровня, необходимых для доставки сообщения по месту его назначения. Для обычных типов интерфейсов протокол канального уровня реализуется в отдельном подуровне, который используется совместно различными аппаратными драйверами. Выбор интерфейса для использования при отправке пакета является решением маршрутиза- маршрутизации, принимаемым на уровне сетевого протокола. Интерфейс может иметь адреса в одном или более семействах адресов. Каждый адрес устанавливается во время загрузки с использованием системного вызова ioctl для сокета в соответствующем домене; эта операция реализуется семейством протоколов после того, как сетевой интерфейс про- проверит операцию с помощью точки входа ioctl, предоставленной сетевым интерфейсом. Абстракция сетевого интерфейса предоставляет протоколам согласованный интерфейс ко всем аппаратным устройствам, которые могут присутствовать на машине. Интерфейс и его адреса определяются структурами, показанными на рис. 12.3. По мере обнаружения интерфейсов во время запуска инициализируются и помещают- помещаются в связанный список структуры ifnet. Модуль сетевого интерфейса обычно под- поддерживает структуру данных интерфейса ifnet как часть большей структуры, которая содержит также информацию, использующуюся в управлении нижележащим аппарат- аппаратным устройством. Аналогично структура адреса интерфейса ifaddr часто является частью большей структуры, содержащей дополнительную информацию протокола об интерфейсе и его адресе. Поскольку адреса сетевых сокетов имеют различный размер, каждый протокол отвечает за выделение памяти, на которую ссылаются указатели адреса, маски и широковещательного адреса или указатель на адрес назначения в структуре ifaddr. Каждый сетевой интерфейс идентифицируется двумя способами: строкой симво- символов, идентифицирующей драйвер, плюс номером устройства для драйвера (например, fxpO) и двоичным общесистемным номером индекса. Индекс используется в качестве стенографического идентификатора - например, когда устанавливается маршрут, ссы- ссылающийся на интерфейс. Как только все интерфейсы обнаружены при запуске систе- системы, система создает массив указателей на структуры ifnet для интерфейсов. Таким образом она может быстро локализовать интерфейс по данному номеру индекса, тогда как поиск с использованием строкового имени менее эффективен. Некоторые опера- операции, такие, как назначение адреса интерфейса, используют для интерфейса строковое имя для удобства пользователя, поскольку производительность не является ре- решающей. Другие операции, такие, как установление маршрута, передают идентифика- идентификатор более нового стиля, который может использовать либо строку, либо индекс. Новый идентификатор использует структуру sockaddr в новом семействе адресов, AFJLINK, указывающую адрес канального уровня. Специфической для семейства версией струк- структуры является структура sockaddr_dl, показанная на рис. 12.4, которая может со- содержать до трех идентификаторов. Она включает имя интерфейса в строковом виде плюс размер, причем нулевой размер означает отсутствие имени. Она включает также индекс интерфейса в виде целого, причем нулевое значение указывает, что индекс не
12.1. Внутренняя структура 559 РПК I ©«¦ j ИНТЕ1 L ! тер /////// Следующий — \- Предыдущий Прототип порта III >HET'CW'earf > ^ 1 I Управляющий блок протокола — socket - Сокет Следующий — - Предыдущий Локальный порт Локальный адрес Внешний порт Внешний адрес Опции Опции mcast Мьютекс protocol Ctrl blk - inpcb — inpcb tcpcb > ¦^ Управляющий блок протокола — socket — Co кет Следующий н - Предыдущий Локальный порт Локальный адрес Внешний порт Внешний адрес Опции Опции mcast Мьютекс protocol Ctrl blk — inpcb — inpcb tcpcb i 1 f&i i i Рис. 12.3. Структуры данных сетевого интерфейса установлен. Наконец, она может включать двоичный адрес канального уровня, такой, как адрес Ethernet, и размер адреса. Адрес такого вида создается для каждого сетевого интерфейса, когда интерфейс конфигурируется системой, и возвращается в списке ло- локальных адресов для системы вместе с адресами сетевых протоколов (см. далее в подразделе). На рис. 12.4 показана структура, описывающая интерфейс Ethernet, ко- который является первым интерфейсом в системе; структура содержит имя интерфейса, индекс и адрес канального (Ethernet) уровня. Структура данных интерфейса включает структуру if_data, которая содержит дос- доступное извне описание интерфейса. Она включает тип канального уровня интерфейса, максимальный поддерживаемый размер пакета сетевого протокола и размеры заголов- заголовка канального уровня и адреса. Она содержит также многочисленные статистические данные, такие, как отправленные и полученные пакеты и байты, ошибки ввода и вывода и другие данные, требуемые протоколами управления сетью. Состояние интерфейса и определенные видимые извне характеристики хранятся в поле ifjlags, описанном в табл. 12.2. Первый набор флагов характеризует интерфейс. Если ин- интерфейс соединен с сетью, которая поддерживает передачу широковещательных сообще- сообщений, будет установлен флаг IFF_BROADCAST, а список адресов интерфейса будет содержать широковещательный адрес, который должен использоваться при отправке
560 Глава 12. Сетевая коммуникация sdljien sdljamily sdl_index sdljype sdljilen sdl alen sdl slen sdl_data 20 AF LINK 1 IFT_ETHER 0 struct sockaddr_dl T V 'p' '0' 00:00:c0:c2:59:0b Пример struct sockaddr_dl Рис. 12.4. Структура адреса канального уровня. Прямоугольник слева обозначает элементы структуры sockaddrjdl. Прямоугольник справа показывает примеры значений для этих элементов для интерфейса Ethernet. Массив sdl_data может содержать имя (если sdljilen не равен нулю), адрес канального уровня (если sdl_alen не равен ну- нулю) и селектор адреса (если sdljslen не равен нулю). Для Ethernet sdljiata со- содержит трехбуквенное имя, за которым следует номер устройства, fxpO, за которым следует 6-байтовый Ethernet адрес и получении таких сообщений. Если интерфейс связан с двухточечным аппаратным соединением (например, каналом выделенной линии), будет установлен флаг IFFPOINTOPOINT1, а список адресов интерфейса будет содержать адрес хоста на другой стороне соединения. (Обратите внимание, что широковещательный и двух- двухточечный атрибуты взаимно исключают друг друга.) Эти адреса и локальные адреса интерфейса используются протоколами сетевого уровня при фильтровании входящих пакетов. Флаг IFF_MULTICAST устанавливается интерфейсами, которые поддерживают вдобавок к IFF_BROADCAST многоадресную рассылку. Многоадресные пакеты отправ- отправляются по одному или нескольким групповым адресам и предназначены для всех членов группы. Табл. 12.2. Флаги сетевого интерфейса Флаг IFFJJP IFFBROADCAST IFF_DEBUG IFF_LOOPBACK IFF_POINTOPOINT IFF SMART Описание Интерфейс доступен для использования Широковещание поддерживается Включение отладки в ПО интерфейса Программный интерфейс возвратной петли Интерфейс для двухточечного соединения Интерфейс управляет своими собственными маршрутами 1 Это не опечатка. Флаг действительно называется IFF_POINTOPOINT, а не IFF_POINTTOPOINT. - Примеч. науч. ред.
12.1. Внутренняя структура 561 Табл. 12.2. Флаги сетевого интерфейса (Окончание) IFF_RUNNING Ресурсы интерфейса были выделены IFF_NOARP Интерфейс не поддерживает ARP IFF_PROMISC Интерфейс получает все пакеты IFF_ALLMULTI Интерфейс получает все многоадресные пакеты IFF_OACTIVE Интерфейс занят выводом IFF_SIMPLEX Интерфейс не может получать свои собственные передачи IFF_LINK0 Специфический для канального уровня IFFJLINK1 Специфический для канального уровня IFF_LINK2 Специфический для канального уровня IFF_MULTICAST Поддерживается многоадресная рассылка IFFPOLLING Интерфейс в режиме опроса IFF_PPROMISC Неразборчивый режим, запрошенный пользователем IFF_MONITOR Режим контроля, запрошенный пользователем IFF_STAICARP Интерфейс использует только статический ARP Дополнительные флаги интерфейса описывают рабочее состояние интерфейса. Интерфейс устанавливает флаг IFF_RUNNING после того, как он выделил системные ресурсы и запросил начальное чтение с устройства, которым управляет. Этот бит состояния позволяет избежать множественных запросов выделения, когда адрес интерфейса изменяется. Флаг IFF_UP устанавливается, когда интерфейс сконфи- сконфигурирован и готов передавать сообщения. Флаг IFF_OACTIVE используется для ко- координации между процедурами ifjoutput и if_start, описанными далее в подразделе; он устанавливается, когда дополнительная попытка вывода недопустима. Флаг IFF_PROMISC устанавливается программами наблюдения за сетью для обеспечения неразборчивого (promiscuous) приема: когда они хотят получать пакеты для всех мест назначения, а не только для локальной системы. Пакеты, адресованные другим систе- системам, передаются отслеживающему фильтру пакетов, но не доставляются сетевым про- протоколам. Флаг IFFALLMULTI аналогичен, но применяется лишь к многоадресным пакетам и используется агентами многоадресной пересылки. Флаг IFF_SIMPLEX уста- устанавливается драйверами Ethernet, аппаратура которых не может принимать пакеты, которые они отправляют. В этом случае функция отправки эмулирует прием широко- широковещательных и (в зависимости от протокола) многоадресных пакетов, которые были отправлены. Наконец, флаг IFFDEBUG может быть установлен для включения любых необязательных диагностических тестов и сообщений уровня драйвера. Три флага определены для использования отдельными драйверами канального уровня (IFF_LINK0, IFF_LINK1 и IFF_LINK2). Они могут использоваться для выбора опций канального уровня, таких, как тип носителя Ethernet.
562 Глава 12. Сетевая коммуникация ifnet J, pfl_addr pf2_addr 1 enO if_addrlist PFladdr ifa_next PF2addr ш next \ enl if_addrlist PFladdr ia_next 1 ifajiext PF2addr ш next < loO if_addrlist PFladdr ifajiext tajiext PF2addr Рис. 12.5. Структуры данных сетевого интерфейса и протокола. Связанный список структур ifnet показан в левой части рисунка. Структуры ifaddr, хранящие адреса для каждо- каждого интерфейса, находятся в связанном списке, возглавляемом структурой ifnet и по- показанном в виде горизонтального списка. Структуры ifaddr для большинства про- протоколов также связаны вместе; они показаны в вертикальных списках, возглавляе- возглавляемых pfl_addr и pf2_addr Адреса и флаги интерфейсов устанавливаются с помощью запросов ioctl. Запросы, специфические для сетевого интерфейса, передают имя интерфейса в виде строки в структуре входящих данных, причем строка состоит из имени для типа интерфейса плюс номер устройства. Вначале используется либо запрос SIOCSIFADDR, либо запрос SIOCAIFADDR, чтобы определить адрес каждого интерфейса. Первый устанав- устанавливает единственный адрес для протокола на данном интерфейсе. Последний добавля- добавляет адрес с соответствующей маской адреса и широковещательным адресом. Он позво- позволяет интерфейсу поддерживать несколько адресов для одного и того же протокола. В любом случае протокол выделяет структуру ifaddr и достаточное пространство для адресов и любых индивидуальных данных и вносит структуру в список адресов для сетевого интерфейса. Кроме того, большинство протоколов хранят список адресов для протокола. Результат выглядит наподобие двумерного связанного списка, как показано на рис. 12.5. Адрес можно удалить с помощью запроса SIOCDIFADDR. Запрос SIOCSIFFLAGS может использоваться для изменения состояния интерфей- интерфейса и выполнения специфической для системы конфигурации. Адрес назначения двух- двухточечного соединения устанавливается запросом SIOCSIFDSTADDR. Соответст- Соответствующие операции существуют для чтения каждого значения. Семейства протоколов также могут поддерживать операции по установке и чтению широковещательного адреса. Наконец, запрос SIOCGIFCONF может быть использован для получения списка имен интерфейсов и адресов протоколов для всех интерфейсов и протоколов, сконфигурированных на работающей системе. Аналогичная информация возвращает- возвращается более новым механизмом, основанным на системном вызове sysctl с запросом в се- семействе протоколов маршрутизации (см. разделы 12.5 и 14.6). Эти запросы позволяют разработчикам конструировать сетевые процессы, такие, как демон маршрутизации, без детального знания внутренних структур данных системы.
12.2. Интерфейс между сокетами и протоколами 563 У каждого интерфейса есть очередь пакетов для передачи и процедуры, исполь- зующиехся для инициализации и вывода. Процедура if_output() принимает пакет для передачи и обычно обрабатывает упаковку и постановку в очередь канального уровня, которые независимы от использования специфического аппаратного драйвера. Если флаг IFFOACTIVE не установлен, процедура вывода может вызвать затем функцию драйвера if_start(), чтобы начать пересылку. Потом функция запуска устанавливает флаг IFF_OACTIVE, если она не способна принять для пересылки дополнительные па- пакеты; флаг будет сброшен, когда передача завершится. Точка входа if_done() преду- предусмотрена в качестве функции обратного вызова для использования в том случае, когда выходная очередь освобождена. Эта возможность пока еще не используется, но она предназначена для поддержки чередования (striping) данных для одного логического интерфейса из множества физических интерфейсов. Интерфейс может также указать процедуру сторожевого таймера и значение таймера (если оно не равно нулю), которое система будет уменьшать один раз в секунду, вызы- вызывая процедуру таймера, когда значение достигнет нуля. Механизм тайм-аута обычно используется интерфейсами для реализации сторожевых схем для ненадежного аппарат- аппаратного обеспечения и для сбора статистики, которая находится в аппаратном устройстве. 12.2. Интерфейс между сокетами и протоколами Интерфейс между процедурами сокетов и коммуникационными протоколами осущест- осуществляется посредством процедур prusrreqsQ и prctloutputQ таблицы запросов пользо- пользователя, которые определены в таблице переключения протоколов для каждого прото- протокола. Когда уровень сокетов запрашивает услуги поддерживающего протокола, он делает вызов одной из функций из табл. 12.3. Процедура управления выводом реализу- реализует системные вызовы getsockopt и setsockopt; процедуры запроса пользователя исполь- используются для всех других операций. Вызовы pr_ctloutput() указывают SOPT_GET для получения текущего значения опции или SOPT_SET для установки значения опции. Табл. 12.3. Таблица процедурpr_usrreqs() Точка входа Описание pru_abort() Прервать соединение и отсоединиться pru_accept() Принять соединение от противоположного узла pru_attach() Присоединить протокол к сокету pru_bind() Привязать имя к сокету pru_connect() Установить соединение с узлом pru_connect2() Соединить два сокета pru_contro!() Управляющая операция протокола (ioctl) pru_detach() Отсоединить протокол от сокета
564 Глава 12. Сетевая коммуникация Табл. 12.3. Таблица процедур prjusrreqsQ (Окончание) Точка входа Описание pru_disconnect() Отключиться от узла pru_listen() Слушать в ожидании соединения pru_peeraddr() Получить адрес узла pru_rcvd() Получены данные; теперь больше места pru_rcvoob() Получить внеполосные данные pru_send() Отправить эти данные pru_sense() Узнать состояние сокета (fstat) pru_shutdown() He будет больше посылать данных pru_sockaddr() Получить адрес сокета pru_sosend() Быстрый путь для sosend pru_soreceiveO Быстрый путь для sereceive pru_sopoll() Быстрый путь для sopoll Процедуры пользовательских запросов протокола Вызовы процедур пользовательских запросов имеют специфическую для процедуры сигнатуру, но первый аргумент всегда является указателем на структуру socket, которая указывает сокет, для которого предназначена операция. Для операций вывода и для не- некоторых других операций, когда должен быть возвращен результат, предоставляется цепочка данных mbuf. Для ориентированных на адрес запросов, таких, как pru_bind(), pru_connect() wpru_send() (когда указан адрес - например, вызов sendto), предоставля- предоставляется структура sockaddr. Там, где он используется, параметр control является указате- указателем на необязательную цепочку mbuf, содержащую специфическую для протокола управляющую информацию, передаваемую посредством вызова sendmsg. Каждый протокол отвечает за управление цепочками данных mbuf в операциях вывода. Нену- Ненулевое возвращаемое значение из процедуры пользовательского запроса указывает на номер ошибки, который должен быть передан программному обеспечению вышележа- вышележащего уровня. Далее следует описание каждого из возможных запросов. pru_attach()\ присоединение протокола к сокету. Когда протокол впервые привязы- привязывается к сокету (с помощью системного вызова socket), вызывается процедура pru_attach() модуля протокола. Модуль протокола отвечает за выделение всех необходимых ресурсов. Процедура attach всегда будет предшествовать всем другим операциям и будет вызываться для сокета лишь один раз. 9 pru_detach(): отсоединить протокол от сокета. Эта операция обратна процедуре присоединения и используется во время удаления сокета. Модуль протокола
12.2. Интерфейс между сокетами и протоколами 565 может освободить все ресурсы, которые он выделил для сокета в предыдущем вызове pru_attach(). 9 pru_bind(): привязка адреса к сокету. Когда сокет создается впервые, у него нет привязанного адреса. Эта процедура привязывает адрес к существующему сокету. Модуль протокола должен проверить, что запрошенный адрес действителен и дос- доступен для использования. 9 pru_listen()\ прослушивать входящие соединения. Процедура запроса прослушива- прослушивания указывает, что пользователь хочет прослушивать запросы входящих соедине- соединений для соответствующего сокета. Модуль протокола должен произвести любые изменения состояния, необходимые для удовлетворения этого запроса (если это возможно). Вызов процедуре прослушивания всегда предшествует всем запросам принятия соединения. 9 pru_connect(): подключает сокет к узлу. Процедура запроса соединения указывает, что пользователь хочет установить связь. Параметр addr описывает узел, соедине- соединение с которым желательно. Результат запроса соединения может быть различным в зависимости от протокола. Поточные протоколы используют этот запрос для инициирования установления сетевого соединения. Протоколы дейтаграмм просто записывают адрес узла в индивидуальной структуре данных, где они используют его в качестве адреса назначения всех исходящих пакетов и в качестве фильтра источника для входящих пакетов. Ограничений на то, сколько раз после присоединения может быть использована процедура подключения, нет, хотя боль- большинство поточных протоколов допускают лишь один вызов соединения. pru_accept()\ принятие ожидающего соединения. Следуя после успешного запроса прослушивания и поступления одного или более соединений, эта процедура вызы- вызывается для указания того, что пользователь собирается принять сокет из очереди сокетов, готовых к возвращению. Сокет, предоставленный в качестве параметра, является сокетом, который принимается', ожидается, что модуль протокола запол- заполнит предоставленный буфер адресом узла, соединенного с сокетом. 9 pru_disconnect()\ отсоединение соединенного сокета. Эта процедура уничтожает связь, созданную процедурой подключения. Она используется с сокетами дейта- дейтаграмм до создания новой связи; с поточными сокетами она используется, лишь когда сокет закрывается. 9 pru_shutdown(): прекращение передачи данных сокетом. Этот вызов указывает, что данные больше посылаться не будут. Протокол может по своему усмотрению освободить любые структуры данных, относящиеся к прекращению работы, или оставить всю работу для своей процедуры pru_detach(). Модуль в это время может также уведомить подключенный узел о прекращении работы.
566 Глава 12. Сетевая коммуникация 9 pru_rcvd()\ пользователем были получены данные. Эта процедура вызывается, лишь если элемент протокола в таблице переключений протоколов содержит флаг PR_WANTRCVD. Когда уровень сокетов удаляет данные из приемной очереди и передает их пользователю, в модуле протокола будет вызвана данная процедура. Она может использоваться протоколом для запуска подтверждений, обновления сведений об окнах, инициирования передачи данных и т.д. Эта процедура вызыва- вызывается также, когда приложение пытается получить данные от сокета, который нахо- находится в состоянии confirming, указывающем, что протокол должен принять запрос соединения до того, как могут быть получены данные (см. раздел 11.5). prujsendQ: отправить данные пользователя. Каждый запрос пользователя по от- отправке данных преобразуется в один или более вызовов процедуры pru_send() модуля протокола. Протокол может указать, что один запрос отправки пользовате- пользователя должен быть преобразован в единственный вызов процедуры pru_send(), указав в описании протокола флаг PR_ATOMIC. Данные для отправки представляются протоколу в виде цепочки mbuf, а в параметре addr опционально предоставляется адрес. Протокол отвечает за сохранение данных в очереди отправки сокета, если он не может их немедленно отправить или если они могут ему понадобиться впо- впоследствии (например, для повторной передачи). Протокол в конечном счете должен передать данные на более низкий уровень или освободить mbuf. 9 pru_abort(): служба ненормального завершения. Эта процедура выполняет ненормальное завершение службы. Протокол должен удалить любые сущест- существующие связи. pru_control(): осуществляет управляющую операцию. Процедура запроса управле- управления вызывается, когда пользователь осуществляет системный вызов ioctl для сокета и ioctl не перехватывается процедурами сокета. Эта процедура дает воз- возможность предоставлять специфические для протокола операции вне области видимости обычного интерфейса сокета. Параметр cmd содержит действительный код запроса ioctl. Параметр data содержит любые данные, относящиеся к выпол- выполняющейся команде, а параметр ifp содержит указатель на структуру сетевого интерфейса, если операция ioctl относится к определенному сетевому интерфейсу. pru_sense()\ распознает состояние сокета. Процедура запроса распознавания вызывается, когда пользователь делает для сокета системный вызов fstat; она запрашивает состояние соответствующего сокета. Этот вызов возвращает стан- стандартную структуру stat, которая обычно содержит лишь оптимальный размер передачи для соединения (основываясь на размере буфера, информации об окнах и максимальном размере пакета). 9 pru_rcvoob()\ получение внеполосных данных. Эта процедура запрашивает воз- возврат любых доступных в настоящий момент внеполосных данных. Модулю прото- протокола передается mbuf, и протокол должен либо поместить данные в mbuf, либо
12.2. Интерфейс между сокетами и протоколами 567 присоединить новые mbuf к предоставленному, если в одном mbuf недостаточно места. Может быть возвращена ошибка, если внеполосные данные (пока) еще не- недоступны или были уже использованы. Параметр flags содержит любые опции, такие, как MSG_PEEK, которые нужно учесть при выполнении этого запроса. 9 pru_sockaddr(): получение локального адреса сокета. Эта процедура возвращает локальный адрес сокета, если он был привязан к сокету. Адрес возвращается в параметре пат, который является указателем на структуру sockaddr. 9 pru_peeraddr()\ получение адреса удаленного узла. Эта процедура возвращает адрес узла, с которым соединен сокет. Сокет должен быть в соединенном состоя- состоянии, чтобы этот вызов завершился успешно. Адрес возвращается в параметре пат, который является указателем на структуру sockaddr. 9 pru_connect2(): соединяет два сокета без привязки адресов. В этой процедуре модулю протокола предоставляются два сокета и просят установить соединение между ними, не привязывая никаких адресов, если это возможно. Система исполь- использует этот вызов в реализации системного вызова socketpair. 9 pru_fasttimo(): обслуживает быстрый тайм-аут. Эта процедура вызывается, когда истекает быстрый тайм-аут B00 мс). Процедура быстрого тайм-аута не может быть вызвана из уровня сокетов. 9 pru_slowtimo()\ обслуживает медленный тайм-аут. Эта процедура вызывается, когда истекает медленный тайм-аут E00 мс). Процедура медленного тайм-аута не может вызываться из уровня сокетов. Процедура управления выводом протокола Вызов процедуры управления выводом осуществляется в виде int (*pr->pr_ctloutput)( struct socket *so, struct sockopt *sopt); где so является модифицируемым сокетом, a sopt является структурой опций сокета. enum sopt_dir { SOPT_GET\ SOPT_SET }; struct sockopt { enum sopt_dir sopt_dir; int sopt_level; int sopt_name; void *sopt_val; size_t sopt_valsize; struct thread *sopt_td; }
568 Глава 12. Сетевая коммуникация Направлением является либо SOPT_SET для установки опции, либо SOPT_GET для ее получения. Член soptjevel указывает уровень программного обеспечения, который должен интерпретировать запрос опции. Значение SOL_SOCKET soptjevel указывает- указывается для управления опцией на уровне сокетов. Когда опция должна быть обработана модулем протокола ниже уровня сокетов, в level устанавливается номер соответст- соответствующего протокола (тот же самый номер, использованный в системном вызове socket). У каждого уровня есть свой собственный набор имен опций; это имя интерпретируется лишь уровнем программного обеспечения, к которому направлен запрос. Оставшаяся часть структуры содержит указатель на значение, передаваемое в модуль или из модуля, размер указываемых данных и указатель на структуру потока. Если операция имеет место полностью внутри ядра, указатель на структуру потока равен null. В поддержке системных вызовов getsockopt и setsockopt уровень сокетов всегда вы- вызывает процедуру управления выводом протокола, присоединенного к сокету. Чтобы получить доступ к протоколам нижележещего уровня, каждая процедура управления выводом должна передать запросы управления выводом, которые не предназначены для нее самой, вниз следующему протоколу в иерархии протоколов. В главе 13 описы- описываются некоторые из опций, предоставляемых протоколами в коммуникационном домене Интернета. 12.3. Интерфейс протокол-протокол Интерфейс между модулями протоколов использует процедуры pr_usrreqs(), а также процедуру pr_ctloutput(). Процедуры pr_usrreqs() и pr_ctloutput() используются уров- уровнем сокетов для взаимодействия с протоколами. Хотя наложение стандартных соглашений по вызову для всех точек входа протоко- протокола теоретически могло бы разрешить произвольные взаимодействия модулей протоко- протоколов, на практике это было бы трудноосуществимым. Пересечение границы семейства протоколов - например, между IPv4 и IPX - потребовало бы преобразования сетевого адреса из формата домена вызывающего в формат домена получателя. Поэтому соеди- соединение протоколов в различных доменах в общем не поддерживается и соглашения по вызовам для процедур, перечисленные в предыдущем абзаце, обычно стандарти- стандартизируются для каждого отдельного домена. (Однако система все же поддерживает инкапсуляцию пакетов от одного протокола в пакеты протокола в другом семействе для туннелирования одного протокола через другой.) В данном разделе мы кратко исследуем общую инфраструктуру и соглашения по вызовам протоколов. В главе 13 мы рассмотрим конкретные протоколы, чтобы уви- увидеть, как они вписываются в эту схему.
12.3. Интерфейс протокол-протокол 569 pr output У каждого протокола свое отличающееся соглашение по вызову для процедуры выво- вывода. Это отсутствие стандартизации является одной из вещей, которые препятствуют свободному обмену модулей протоколов друг с другом в произвольных стеках, как сделано в системе STREAMS [Ritchie, 1984]. До сих пор такая разновидность стан- стандартизации не считалась необходимой, поскольку каждый стек протоколов имеет тен- тенденцию оставаться сам по себе, ничего не занимая от других. Произвольное наложение модулей протоколов усложнило бы также интерпретацию в каждом модуле сетевых адресов, поскольку каждому пришлось бы проверять, что адрес имеет для них какой- нибудь смысл в их домене. Простейший пример выходной процедуры протокола часто использует соглашение по вызову, разработанное для отправки через соединение одного сообщения; например, int (*pr_output)( register struct inpcb *inp, struct mbuf *msg, struct sockaddr *addr struct mbuf *control, struct thread *td) ; отправила бы сообщение, содержащееся в msg, в сокет, описанный управляющим блоком протокола inp. Специальный адрес и управляющая информация передаются со- соответственно в addr и control. prinput Процедуры ввода протокола верхнего уровня обычно вызываются задачей сетевого программного прерывания после того, как протокол сетевого уровня обнаружил иден- идентификатор протокола. Они имеют более строгие соглашения, чем процедуры вывода, поскольку они вызываются через переключение протоколов. В зависимости от семей- семейства протоколов они могут получить указатель на управляющий блок, идентифи- идентифицирующий соединение, или им может потребоваться найти управляющий блок из ин- информации в полученном пакете. Типичным соглашением по вызову является void (*pr_input)( struct mbuf *msg, int hien); В данном примере входящий пакет передается транспортному протоколу в mbuf msg с заголовком сетевого протокола, по-прежнему находящемуся на своем месте, для использования транспортным протоколом, а также с длиной заголовка Men, чтобы заголовок можно было удалить. Протокол выполняет разделение на уровне конечной точки, основываясь на информации в сетевом и транспортном заголовках.
570 Глава 12. Сетевая коммуникация Табл. 12.4. Запросы процедур управления вводом Запрос PRCJFDOWN PRC_ROUTEDEAD PRCJFUP PRC_QUENCH PRC_QUENCH2 PRC_MSGSIZE PRC_HOSTDEAD PRC_HOSTUNREACH PRC_UNREACH_NET PRC_UNREACH_HOST PRC_UNREACH_PROTOCOL PRC_UNREACH_PORT PRC_UNREACH_SRCFAIL PRC_REDIRECT_NET PRC_REDIRECT_HOST PRC_REDIRECT_TOSNET PRC_REDIRECT_TOSHOST PRC_TIMXCEED_INTRANS PRC_TIMXCEED_REASS PRC_PARAMPROB PRC UNREACH ADMIN PROHIB Описание Отключение сетевого интерфейса Выбрать новый маршрут, если возможно Сетевой интерфейс обратно включился Один из получателей просил снизить скорость Бит перегрузки DEC сигнализирует снизить скорость Размер сообщения вызвал удаление пакета Удаленный хост выключен Удаленный хост недоступен Сеть недоступна Хост недоступен Место назначения не поддерживает протокол Место назначения не использует этот номер порта Маршрутизация источника завершилась неудачей Перенаправление маршрута для сети Перенаправление маршрута для хоста Перенаправление маршрута для типа службы и сети Перенаправление маршрута для типа службы и хоста Время жизни пакета истекло в пути Время жизни истекло в очереди повторной сборки Обнаружена проблема параметра заголовка Пакет запрещен административно prctlinput Эта процедура передает контрольную информацию (т.е. информацию, которая может быть передана пользователю, но не состоящую из данных) наверх от одного модуля протокола другому. Обычным соглашением по вызову этой процедуры является void (*pr_ctlinput)( int cmd, struct sockaddr *addr, void* opaque); Параметр and является одним из значений, показанных в табл. 12.4. Параметр addr является удаленным адресом, к которому применяется условие. Многие из запро- запросов были получены из протокола управляющих сообщений Интернета (ICMP) [Postel, 1981] и из сообщений об ошибках, определенных в соглашении хоста (процессора
12.4. Интерфейс между протоколом и сетевым интерфейсом 571 сообщений Интернета (Internet Message Processor) 1822 [BBN, 1978]. Некоторые про- протоколы внутренне могут передавать дополнительные параметры, такие, как локальные адреса, или более специфическую информацию. 12.4. Интерфейс между протоколом и сетевым интерфейсом Самый нижний уровень в наборе протоколов, составляющем семейство протоколов, должен взаимодействовать с одним или более сетевыми интерфейсами, чтобы переда- передавать и получать пакеты. Предполагается, что все решения по маршрутизации были сде- сделаны до отправки пакета сетевому интерфейсу; решение о маршрутизации необходимо для обнаружения вообще какого-нибудь интерфейса. Хотя через любой сетевой стек есть четыре пути, во взаимодействии между протоколами и сетевыми интерфейсами нам следует беспокоиться лишь о двух случаях: передаче пакета и принятии пакета. Мы рассмотрим каждый из них отдельно. Передача пакета Если протокол выбрал интерфейс, определенный ifp, указателем на структуру сетевого интерфейса, протокол передает полностью сформированный пакет сетевого уровня с помощью следующего вызова: int (*if_output)( struct ifnet *ifp, struct mbuf *msg, struct sockaddr *dst, struct rtentry *rt); Процедура вывода для сетевого интерфейса передает пакет msg по адресу протоко- протокола, указанного в dst, или возвращает номер ошибки. На самом деле передача может не быть немедленной или успешной; обычно процедура вывода проверяет адрес назначения, помещает пакет в свою очередь отправки и устанавливает управляемую прерыванием процедуру для передачи пакета, если интерфейс не занят. Для ненадеж- ненадежных сред, таких, как Ethernet, успешная передача означает просто то, что пакет был передан по кабелю без коллизий. В отличие от этого надежные двухточечные сети, такие, как Х.25, могут гарантировать правильную доставку пакета или указать ошибку для каждого пакета, который не был успешно передан. Модель, использующаяся в се- сетевой системе, не предоставляет никаких обещаний доставки пакетам, представлен- представленным сетевому интерфейсу, и тем самым ближе всего соответствует Ethernet. Процедура вывода возвращает лишь такие ошибки, которые можно обнаружить немедленно и ко- которые обычно тривиальны по своей природе (отключение сети, нет буферной памяти, не распознан формат адреса и т.д.) Если ошибки обнаруживаются после возвращения из вызова, протокол не уведомляется.
572 Глава 12. Сетевая коммуникация Когда сообщения передаются в широковещательную сеть, такую, как Ethernet, каждый сетевой интерфейс должен определить адрес канального уровня для каждого исходящего пакета. Уровень интерфейса должен понимать формат адреса каждого про- протокола, который он поддерживает, чтобы определять соответствующие адреса каналь- канального уровня. Сетевой уровень для каждого семейства протоколов выбирает адрес на- назначения для каждого сообщения, а затем использует этот адрес для выбора использо- использования соответствующего сетевого интерфейса. Этот адрес назначения передается про- процедуре вывода интерфейса в виде структуры sockaddr. Предположив, что формат адреса интерфейсом поддерживается, интерфейс должен отобразить адрес протокола назначения на адрес для протокола канального уровня, связанного со средой передачи, которую поддерживает интерфейс. Это отображение может представлять собой про- простой алгоритм, оно может требовать поиска в таблице или оно может потребовать более сложных методик, таких, как использование протокола разрешения адресов, описанного в разделе 12.8. Прием пакетов Сетевые интерфейсы получают пакеты и переправляют их соответствующим протоко- протоколам сетевого уровня в соответствии с информацией, содержащейся в заголовке прото- протокола канального уровня. У каждого семейства протоколов должен быть один или более протоколов, составляющих сетевой уровень, описанный в разделе 12.1. В этой системе у каждого протокола сетевого уровня есть назначенная ему очередь входящих пакетов. Входящие пакеты, полученные сетевым интерфейсом, помещаются в очередь входя- входящих пакетов протокола, и сетевой поток делает обработку сетевого уровня; см. рис. 12.6. Аналогичные очереди используются для хранения пакетов, ожидающих передачи драйверами сетевых устройств. Рис. 12.6. l Протокол А Входная очередь Интерфейс 1 входящие пакеты, Протокол В Входная очередь Интерфейс 2 Интерфейс 3 направляемые во входные очереди протоколов
12.4. Интерфейс между протоколом и сетевым интерфейсом 573 Для манипулирования очередями пакетов доступно несколько макросов: * IF_ENQUEUE (ifq, m) помещает пакет т в хвост очереди ifg. * IF_DEQUEUE (ifq, m) помещает указатель на пакет в начале очереди ifg в т и удаляет пакет из очереди; т будет равен нулю, если очередь пуста. * IF_PREPEND (ifq, m) помещает пакет т в начало очереди ifg. Очереди пакетов имеют связанный с ними максимальный размер в качестве про- простого вида управления перегрузкой. Для определения того, полна ли очередь, может использоваться макрос IF_QFULL(); если да, можно использовать другой макрос, IF_DROP(), для записи события в статистику, собираемую для очереди. Каждая очередь защищается мьютексом, так что потоки различных процессов не могут случайно помешать друг другу. Любой макрос, манипулирующий очередью, сначала блокирует мьютекс, затем делает изменения и, наконец, освобождает мьютекс. В качестве примера в процедуре вывода сетевого интерфейса мог бы использо- использоваться следующий фрагмент кода: if (IF_QFULL(ifp->if_snd)) { IF_DROP(ifp->if_snd); m_freem(m); /* уничтожить пакет */ error = ENOBUFS; } else IF_ENQUEUE(ifp->if_snd, m); По получении пакета сетевой интерфейс расшифровывает тип пакета, вырезает заголовок протокола канального уровня, записывает идентификатор получающего интерфейса, а затем переправляет пакет соответствующему модулю канального уров- уровня. Выше канального уровня у каждого протокола есть процедура обработки ввода и входная очередь, которые регистрируются в сетевом потоке посредством процедуры netisr_register(). Например, драйвер устройства Ethernet помещает свои пакеты в очередь в каналь- канальном уровне Ethernet, вызывая ether_demux(), которая затем вызывает netisr_dispatch(), чтобы поместить пакеты в очередь для определенного протокола. netisrjdispatchQ является общей процедурой, которая вручает пакеты протоколам. Она, в свою очередь, использует процедуру if_handoff() для фактического помещения пакета в очередь с помо- помощью следующего кода: IF_LOCK(ifq); if (_IF_QFULL(ifq)) { _IF_DROP(ifq); IF_UNLOCK(ifq) ; m_freem(m); return @); } _IF_ENQUEUE(ifq, m); IF_UNLOCK(ifq);
574 Глава 12. Сетевая коммуникация Когда пакет помещен в очередь, макрос schednetisrQ планирует выполнение сете- сетевого потока таким образом, чтобы он выполнил обработку пакета. Элементы во входной очереди протокола представляют собой цепочки mbuf с дей- действительными заголовками пакетов, содержащими размер пакета и указатель на сете- сетевой интерфейс, через который пакет был получен. Указатель на интерфейс может использоваться множеством возможных способов, например при решении, когда соз- создавать сообщения перенаправления маршрутизации. Входная процедура протокола не исключает пакет из очереди, но получает mbuf, указывающий на пакет, от сетевого потока. Именно сетевой поток выводит пакеты из очереди, а затем вызывает входную процедуру протокола. Пока элемент исключается из входной очереди, сетевой поток блокирует вхожде- вхождение всех других пакетов в протокол, завладевая мьютексом сетевого потока. Блокиров- Блокировка удерживается, чтобы гарантировать, что указатели в структуре данных очереди не изменяются. После того как сообщение удалено из очереди, оно обрабатывается; если в пакете есть информация для протокола более высокого уровня, сообщение передает- передается наверх. 12.5. Маршрутизация Сетевая система была разработана для гетерогенного сетевого окружения, в котором совокупность локальных сетей объединена в одной или более точках посредством маршрутизаторов, как показано на примере на рис. 12.7. Маршрутизаторы являются узлами с несколькими сетевыми интерфейсами, по одному в каждой локальной или удаленной (long-haul) сети. В таком окружении важны проблемы, связанные с маршру- маршрутизацией пакетов. Некоторые из этих проблем, такие, как управление перегрузкой, в FreeBSD обрабатываются упрощенно (см. раздел 12.6). Для других сетевая система предоставляет простые механизмы, с помощью которых могут быть реализованы более сложные политики. Эти механизмы гарантируют, что, когда эти проблемы будут поняты лучше, в систему можно будет внедрить их решения. Обратите внимание, что во время разработки первоначального дизайна этой части системы сетевой узел, который пересылал пакеты сетевого уровня, обычно был известен как шлюз (gateway). Современным термином является маршрутизатор (router). Мы попеременно исполь- используем оба термина отчасти из-за того, что структуры данных системы продолжают использовать название шлюз. В данном разделе описываются средства, предусмотренные для маршрутизации пакетов. Средства маршрутизации были разработаны для использования одиночно- и множественноподключенными хостами, а также маршрутизаторами. В маршрутиза- маршрутизацию вовлечено несколько компонентов, показанных на рис. 12.8. Дизайн системы маршрутизации некоторые компоненты помещает внутри ядра, а другие на уровне пользователя. Маршрутизация в действительности является слишком широким терми-
12.5. Маршрутизация 575 Локальная магистральная сеть Локальная магистральная сеть Обозначения: I J —маршрутизатор Глобальная /Глобальна; сеть Рис. 12.7. Пример топологии, для которой были разработаны средства маршрутизации ном. В сложной современной сети есть два основных компонента системы маршрути- маршрутизации. Сбор и поддержка маршрутизирующей информации (т.е. какие интерфейсы ра- работают, каковы стоимости использования определенных связей и т.д.), а также реали- реализация политик маршрутизации (какие интерфейсы могут быть использованы для пере- пересылки трафика в административном смысле) выполняется на уровне пользователя демонами маршрутизации. Действительная пересылка пакетов, представляющая собой выбор интерфейса, по которому будет отправлен пакет, осуществляется ядром. Информация о маршрутизации Информация о маршрутизации Пользователь Ядро f Сокет маршрутизации Таблица маршрутизации Рис. 12.8. Дизайн маршрутизации Механизм маршрутизации представляет собой простой поиск, предоставляющий маршрут первого транзитного участка (определенный сетевой интерфейс и непосред- непосредственное место назначения) для каждого исходящего пакета. Современный дизайн по- помещает в ядро достаточно информации для отправки пакетов своим путем без внешней помощи; все другие компоненты находятся вне ядра. Демоны маршрутизации на уровне пользователя взаимодействуют с ядром через сокет маршрутизации, чтобы
576 Глава 12. Сетевая коммуникация манипулировать таблицей маршрутизации ядра и прослушивать внутренние изменения, такие, как включение или отключение интерфейсов. В данном разделе описан каждый из этих компонентов. Таблицы маршрутизации ядра Механизм маршрутизации ядра реализует таблицу маршрутизации для поиска маршрутов первого транзитного участка (frist-hop) (или следующего транзитного уча- участка при пересылке пакетов). Она включает две различные части: структуру данных, описывающую каждый определенный маршрут (элемент маршрутизации), и алгоритм поиска для нахождения правильного маршрута для каждого возможного места назначения. В данном подразделе описываются элементы в таблице маршрутизации, а в следующем подразделе объясняется алгоритм поиска. Место назначения описыва- описывается структурой sockaddr с семейством адресов, размером и значением. Маршруты классифицируются следующими способами: как маршруты либо хоста, либо сети и как либо прямые, либо непрямые. Различие хост - сеть определяет, применяется ли маршрут к определенному хосту или к группе хостов с общей частью адреса - обычно префиксом адреса. Для маршрутов хостов адрес места назначения маршрута должен точно соответствовать нужному месту; семейство адресов, размер и все биты адреса места назначения должны совпадать с соответствующими значениями для маршрута. Для сетевых маршрутов адрес назначения в маршруте образует пару с маской. Маршрут подходит любому адресу, который содержит те же самые биты, что и адрес назначения, в позициях, обозначенных установленными в маске битами. Маршрут хоста является особым случаем сетевого маршрута, в котором установлены все биты маски, и поэтому при сравнении ни один бит не игнорируется. Другим особым случаем является универсальный маршрут (wildcard route) - сетевой маршрут с пустой маской. Такой маршрут подходит любому месту назначения и служит в качестве маршрута по умолчанию для тех мест назначения, о которых больше ничего не известно. Этот запас- запасной сетевой маршрут обычно указывает на маршрутизатор, который может принять более информированные решения о маршрутизации. Другим важным различием между видами маршрутов является то, прямые они или непрямые. Прямой маршрут ведет непосредственно к месту назначения: первый тран- транзитный участок представляет собой весь путь, а место назначения находится в общей с источником сети. Большинство маршрутов являются непрямыми: маршрут обознача- обозначает маршрутизатор в локальной сети, который является местом назначения первого транзитного участка для пакета. Значительная часть литературы (особенно для прото- протоколов Интернета) ссылается на решение о локальности-удаленности, когда реализа- реализация сначала проверяет, является ли место назначения локальным для подключенной сети или удаленным. В первом случае пакет отправляется до места назначения (через канальный уровень) локально; в последнем случае он посылается маршрутизатору, который может переправить его на место назначения. В реализации FreeBSD решение о локальности-удаленности принимается как часть поиска маршрута. Если лучший
12.5. Маршрутизация 577 маршрут является прямым, место назначения является локальным. В противном случае маршрут является непрямым, место назначения является удаленным, а элемент маршрутизации обозначает маршрутизатор для места назначения. В любом случае маршрут указывает лишь шлюз первого транзитного участка - интерфейс канального уровня, который должен использоваться при отправке пакетов - и место назначения для пакетов в этом транзитном участке, если оно отличается от конечного места назначения. Эта информация дает пакету возможность быть отправленным через локальный интерфейс на место назначения, непосредственно доступное через этот интерфейс, - либо конечный пункт, либо маршрутизатор на пути к месту назначения. Это различие необходимо, когда осуществляется инкапсуляция канального уровня. Если пакет предназначен для узла, который не подключен непосредственно к источнику, заголовок пакета будет содержать адрес конечного места назначения, тогда как заголо- заголовок протокола канального уровня будет адресовать промежуточный маршрутизатор. Сетевая система поддерживает ряд таблиц маршрутизации, которые используются протоколами при выборе сетевого интерфейса для использования при доставке пакета до места его назначения. Эти таблицы составлены из элементов формы, показанной в табл. 12.5. Табл. 12.5. Элементы структуры элементов (rtentry) таблицы маршрутизации Элемент Описание rt_nodes[2] Внутренний и концевой базисные узлы (со ссылками на место назначения и маску) rt_gateway Адрес маршрутизатора/следующего транзитного участка rt_refcnt Счетчик ссылок rt_flags Флаги; см. таблицу 12.6 rt_ifp Ссылка на интерфейс, ifnet rt_ifa Ссылка на адрес интерфейса, ifaddr rt_genmask Маска для клонирования rt_llinfo Указатель на частные данные канального уровня rtjmx Метрика корня (например, MTU) rt_gwroute Если непрямой, маршрут до шлюза rt_parent Клонирующий родитель этого маршрута rt_output Процедура вывода для данного маршрута/интерфейса, не используется rt_mtx Мьютекс для блокирования элемента (только ядро) Элементы маршрутизации хранятся в структуре rtentry, которая содержит ссылку на адрес назначения и маску (если это не маршрут хоста, в этом случае маска является неявной). Адрес назначения, маска адреса и адрес шлюза имеют переменный размер и поэтому помещаются в отдельно выделяемую память. Элементы маршрутизации
578 Глава 12. Сетевая коммуникация содержат также ссылку на сетевой интерфейс, набор флагов, характеризующих маршрут, и (по выбору), адрес шлюза. Флаги обозначают тип маршрута (хост или сеть, прямой или непрямой) и другие атрибуты, показанные в табл. 12.6. Элемент маршрута содержит также поле для использования драйвером канального уровня, набор метрик и мьютекс для блокирования элемента. Флаг RTFHOST в элементе таблицы маршру- маршрутизации указывает, что маршрут применяется к отдельному хосту, используя неявную маску, содержащую все биты адреса. Флаг RTF_GATEWAY в элементе таблицы маршрутизации указывает, что маршрут до маршрутизатора и заголовок канального уровня должны быть заполнены значением из поля rt_gateway, а не из конечного адреса места назначения. Элемент маршрута содержит поле, которое может использо- использоваться канальным уровнем для квитирования ссылки на прямой маршрут для маршру- маршрутизатора. Флаг RTF_UP установлен, когда установлен маршрут. Когда маршрут удален, флаг RTFJJP сбрасывается, но элемент маршрута не освобождается до тех пор, пока все пользователи маршрута не будут уведомлены об ошибке и не освободят свои ссыл- ссылки. Элемент маршрута содержит счетчик ссылок, поскольку он выделяется динамиче- динамически и не может быть освобожден до тех пор, пока не освобождены все ссылки. Флаг RTFCLONING указывает, что маршрут является общим маршрутом, который перед использованием должен быть клонирован и сделан более конкретным. Этот флаг обычно используется для маршрутов канального уровня, которые применяются к непо- непосредственно подключенной сети, а клонированные маршруты обычно являются маршрутами хостов, которые содержат некоторую информацию канального уровня об одном хосте в этой сети. Когда маршрут клонируется, для заполнения сведений каналь- канального уровня, необходимых для места назначения, может быть вызван внешний агент. Другие флаги (RTFREJECT и RTFJBLACKHOLE) обозначают место назначения маршрута как недоступное, вызывая либо сообщение об ошибке, либо просто не- неудачное завершение, когда делается попытка отправки по данному месту назначения. Отвергаемые маршруты полезны, когда маршрутизатор получает пакеты для кластера адресов извне, но может не иметь маршрутов для всех хостов или сетей в кластере все время. Нежелательно отправлять пакеты с недоступными местами назначения за пре- пределы кластера через маршрут по умолчанию, поскольку маршрутизатор по умолчанию вернет такие пакеты обратно для доставки внутри кластера. Маршруты черных дыр используются во время переходных процессов маршрутизации, когда вскоре может стать доступным новый маршрут. Табл. 12.6. Флаги элемента маршрутизации Описание Маршрут действительный место назначения является шлюзом Элемент хоста (в противном случае сеть) Хост или сеть недоступны Флаг RTF RTF RTF RTF _UP GATEWAY HOST REJECT
12.5. Маршрутизация 579 Табл. 12.6. Флаги элемента маршрутизации (Окончание) RTF_DYNAMIC Создается динамически (по перенаправлению) RTF_MODIFIED Изменен динамически (по перенаправлению) RTF_DONE сообщение подтверждено RTF_MASK Присутствует маска подсети RTF_CLONING При использовании создавать новые маршруты RTF_XRESOLVE Имя разрешает внешний демон RTF_LLINFO Создан канальным уровнем RTF_STATIС Добавлен вручную администратором RTF_BLACKHOLE Просто уничтожать пакеты (в ходе обновлений) RTF_PROTO1 Специфический для протокола флаг маршрутизации RTF_PROTO2 Специфический для протокола флаг маршрутизации RTF_PROTO3 Специфический для протокола флаг маршрутизации RTF_WASCLONED Маршрут создан посредством клонирования RTF_LOCAL Маршрут представляет локальный адрес RTF_BROADCAST Маршрут представляет широковещательный адрес RTF_MULTICAST Маршрут представляет адрес групповой рассылки Многие ориентированные на соединение протоколы хотят накапливать информа- информацию о характеристиках определенного сетевого пути. Некоторую часть этих сведений для каждого соединения можно оценить динамически, например время обращения или MTU пути. Полезно кешировать такую информацию таким образом, чтобы не прихо- приходилось начинать такую оценку заново для каждого соединения [Mogul & Deering, 1990]. Элемент маршрутизации содержит набор метрик маршрута, хранящихся в структуре rtjnetricsjite, которая может быть установлена извне или может опреде- определяться протоколами динамически. Эти метрики включают максимальный размер пакета для пути, который называется максимальным блоком передачи (maximum trans- transmission unit-MTU), время жизни для маршрута и число пакетов, которые были отправ- отправлены через данный маршрут. В версиях FreeBSD до 5.2 определение MTU пути было реализовано с использованием гораздо большей структуры, называемой rtjnetrics. Большинство полей, содержащихся в структуре rtjnetrics, относились только к TCP, и поэтому они были перемещены из структуры элемента маршрутизации в код TCP. Когда маршрут добавляется или создается путем клонирования и когда маршрут удаляется, канальный уровень вызывается через точку входа ifaj'trequest, хранящуюся в структуре ifaddr для адреса данного интерфейса. Канальный уровень может выде- выделить индивидуальную память, связанную с элементом маршрутизации. Эта особен- особенность используется с прямыми маршрутами к сетям, которые помечены как кло- клонирующие маршруты; канальный уровень может использовать этот механизм для
580 Глава 12. Сетевая коммуникация управления информацией трансляции адресов канального уровня для каждого хоста. Трансляция адресов может организовываться внутри системы - она может выполнять- выполняться вне ядра, когда установлен флаг RTF_XRESOLVE. Поиск маршрутизации Когда имеется набор элементов маршрутизации, описывающий различные места назначения, от конкретных хостов до универсального маршрута, необходим алгоритм поиска маршрутизации. Алгоритм поиска в FreeBSD использует модификацию базис- базисного дерева поиска (radix search trie) [Sedgewick, 1990]. (Первоначальный дизайн пред- предполагал использовать поиск PATRICIA, который описан в Sedgewick [1990] и который отличается лишь в деталях управления хранением). Алгоритм базисного поиска пре- предоставляет способ нахождения битовой строки, такой, как сетевой адрес, в наборе из- известных строк. Хотя для поисков маршрутизации был реализован модифицированный поиск, базисный код реализован более общим способом, так что он может использо- использоваться для других целей. Например, код файловой системы использует базисное дерево для управления информацией о клиентах, для которых файловая система может быть экспортирована. Каждый элемент маршрутизации ядра начинается со структуры данных для базисного дерева, включая внутренний базисный узел и концевую верши- вершину, которые ссылаются на адрес назначения и маску. Алгоритм базисного поиска использует двоичное дерево узлов, начинающееся с корневого узла для каждого семейства адресов. На рис. 12.9 показан пример базисно- базисного дерева. Поиск начинается в корневом узле и спускается через некоторое число внутренних узлов до тех пор, пока не будет найдена концевая вершина. Каждому внутреннему узлу необходима проверка определенного бита в стоке, и поиск спускает- спускается по одному из двух направлений в зависимости от значения этого бита. Внутренние узлы содержат индекс бита, который должен быть проверен, а также заранее рассчи- рассчитанный индекс байта и маску для использования в тесте. Концевая вершина помечает- помечается индексом бита-1, что завершает поиск. Например, поиск для адреса 127.0.0.1 (адрес возвратной петли) с деревом из рис. 12.9 начался бы с начала и ответвился налево при тестировании бита 0, направо в узле для бита 1 и направо при тестировании бита 31. Этот поиск приводит к концевой вершине, содержащей маршрут, специфичный для этого хоста; такой маршрут не содержит маску, но использует неявную маску со всеми установленными битами. Методика поиска тестирует минимальное число битов, необходимых для различения в наборе битовых строк. Когда найдена концевая вершина, она либо определяет данную определенную битовую строку, либо то, что битовая строка не присутствует в дереве. Этот алгоритм позволяет протестировать минимальное число битов в строке, чтобы найти неизвестное, такое, как маршрут узла; однако он не обеспечивает частичное сопос- сопоставление, что необходимо для поиска сетевого маршрута. Поэтому поиск маршрутизации использует модифицированный базисный поиск, при котором каждый сетевой маршрут
12.5. Маршрутизация 581 Начало Маска Ключ= 0.0.0.0 Маска = 0x00000000 Маска v Ключ = 127.0.0.0 Маска = OxfOOOOOO Рис. 12.9. Пример базисного дерева. Упрощенный пример базисного дерева содержит маршруты для семейства протоколов IPv4, которое использует 32-разрядные адреса. Кружки представляют собой внутренние узлы, начинаясь с корня дерева наверху. Внутри кружка показано положение тестируемого бита. Концевые вершины пока- показаны в виде прямоугольников, содержащих ключ (адрес назначения, изображен- изображенный в виде четырех десятичных байтов, разделенных точками) и соответствующую маску (шестнадцатеричную). Некоторые внутренние узлы связаны с масками, находящимися в дереве ниже, как показано пунктирными стрелками включает маску, а узлы вставляются в дерево таким образом, что самые длинные маски находятся при поиске раньше [Sklower, 1991]. Внутренние узлы для поддеревьев с общим префиксом помечаются маской для этого префикса. (Маски обычно выбирают префикс из адреса, хотя маска и не должна определять непрерывную часть адреса.) В процессе поиска маршрутизации внутренние узлы связываются с масками, которые увеличивают их специфичность. Если найденный после поиска в узле маршрут является маршрутом сети, место назначения до сравнения с ключом маскируется, что приводит в соответствие каждое место назначения в этой сети. Если концевая вершина не подходит для места назначения, один из внутренних узлов, который был посещен в ходе поиска маршрутиза- маршрутизации, должен ссылаться на лучшее сопо-ставление. После того как в ходе поиска не най- найдено пары с концевым узлом, процедура поиска обходит дерево в обратном порядке, ис- используя в каждом узле указатель на родителя. В каждом внутреннем узле, содержащем маску, осуществляется поиск в той части места назначения, которая находится под этой маской. Например, поиск адреса 128.32.33.7 в дереве на рис. 12.9 до попадания на маршрут хоста справа A28.32.33.5) протестировал бы биты 0, 18 и 29. Поскольку этот адрес не подходит, поиск перемещается вверх на один уровень, где находится маска. Маска представляет собой 24-битный префикс и связана с маршрутом к 128.32.33.0, который является лучшим совпадением. Если бы маска не была префиксом (в коде маршрут с маской, определяющей префикс, называется нормальным маршрутом), потре- потребовался бы поиск для значения 128.32.33.7, начиная с этого места.
582 Глава 12. Сетевая коммуникация Первое найденное совпадение является лучшим совпадением для места назначе- назначения; т.е. у него самая длинная маска из всех подходящих маршрутов. Таким образом, совпадения находятся путем объединения базисного поиска с тестированием по 1 биту на узел на пути вниз по дереву плюс полное сравнение под маской в концевой вершине. Если концевая вершина (либо хост, либо сеть) не подходит, поиск возвращается вверх по дереву, проверяя (с учетом маски) каждого родителя до тех пор, пока не будет най- найдено совпадение. Этот алгоритм позволяет избежать полного сравнения на каждом шагу при поиске вниз по дереву, что свело бы на нет эффективность алгоритма базис- базисного поиска. Он оптимизирован для сопоставлений с маршрутами с более длинными масками и наименьшую эффективность демонстрирует, когда лучшей парой является маршрут по умолчанию (маршрут с самой короткой маской). Другим затруднением при использовании базисного поиска является то, что базисное дерево не допускает дублирования ключей. Есть две возможные причины дублирова- дублирования ключа в дереве: либо существует несколько маршрутов к одному и тому же месту назначения, либо один и тот же ключ присутствует с различными масками. Последний случай не является полностью дублированным, но два маршрута заняли бы одно и то же место на дереве. Код маршрутизации не поддерживает полностью дублированные маршруты, но поддерживает несколько маршрутов, отличающихся лишь маской. Когда добавление маршрута вызывает дублирование ключа, соответствующие маршруты объединяются в цепочку из одной концевой вершины. Маршруты объединяются в це- цепочку в порядке важности маски, с более специфической маской вначале. Если маски являются непрерывными, более длинные маски считаются более специфическими (считается, что маршрут хоста имеет наиболее длинную маску). Если поиск маршрути- маршрутизации проходит через узел с дублированным ключом при сравнении с маской (либо на концевой вершине, либо при перемещении обратно вверх по дереву), сравнение повторяется для каждого дублированного узла в цепочке, причем первое успешное сравнение дает лучшее совпадение. Как мы отметили, FreeBSD не поддерживает несколько маршрутов до одного и того же места назначения (идентичные ключ и маску). Главной причиной для под- поддержки нескольких путей было бы разделение нагрузки между несколькими путями, но чередование трафика по нескольким возможным путям часто было бы субоптималь- субоптимальным. Лучшим решением было бы добавление в каждом маршруте указателя на функ- функцию вывода. Большинство маршрутов копировали бы выходной указатель для ин- интерфейса, используемого маршрутом. Маршруты, для которых были бы доступны не- несколько путей, были бы представлены виртуальным маршрутом, содержащим ссылки на отдельные маршруты, которые не помещались бы в базисное дерево. Виртуальный маршрут вставлял бы промежуточную функцию вывода, которая распределяла бы пакеты по функциям вывода отдельных маршрутов. Эта схема позволила бы обес- обеспечить хорошее чередование пакетов, даже когда путь использовался бы единствен- единственным соединением. Хотя поле процедуры вывода было добавлено к структуре rtentry, оно не используется в коде FreeBSD 5.2.
12.5. Маршрутизация 583 Перенаправления маршрутизации Перенаправление маршрутизации является запросом от протокола системе маршрути- маршрутизации для изменения существующего элемента таблицы маршрутизации или создания нового элемента таблицы маршрутизации. Протоколы обычно создают такие запросы в ответ на сообщения перенаправления, которые они получают от маршрутизаторов. Маршрутизаторы создают запросы перенаправления, когда они распознают существо- существование лучшего маршрута для пакета, который им передали для пересылки. Например, если два хоста А и В находятся в одной и той же сети и хост А посылает пакет хосту В через маршрутизатор С, тогда С отправит сообщение перенаправления А, указывая, что А должен посылать пакеты непосредственно В. На хостах, где исчерпывающую информацию о маршрутизации поддерживать слишком дорого (например, маршрутизаторы SOHO, модемы DSL, PDA и другие встроенные системы), может использоваться комбинация групповых (wildcard) эле- элементов маршрутизации и сообщений перенаправления для предоставления простой схемы управления маршрутизацией без использования процесса политики более высо- высокого уровня. Текущие соединения можно перенаправить после уведомления протоко- протоколов через элементы pr_ctlinput() протоколов. Процедурами таблицы маршрутизации поддерживается статистика использования сообщений перенаправлений маршрутиза- маршрутизации и влияния последних на таблицы маршрутизации. Перенаправление вызывает из- изменение шлюза для маршрута, если перенаправление применяется ко всем направле- направлениям, к которым применяется маршрут; в противном случае из соответствующего маршрута хоста клонируется новый маршрут хоста. Клонированные маршруты хостов легко могут загрузить таблицы маршрутизации из-за отсутствия в системе автомати- автоматизированного метода для удаления устаревших маршрутов хостов, созданных перена- перенаправлениями. Обычно устаревшие маршруты хостов будут очищаться демоном маршрутизации уровня пользователя, но в большинстве хостов демоны маршрутиза- маршрутизации не запускаются. Интерфейс таблицы маршрутизации Протокол получает доступ к таблицам маршрутизации через три процедуры: одну для создания маршрута, одну для освобождения маршрута и еще одну для обработки управляющего сообщения перенаправления маршрутизации. Процедура rtallocQ соз- создает маршрут; она вызывается с указателем на структуру route, которая содержит нужное место назначения, как показано на рис. 12.10, и указатель, который будет уста- установлен для ссылки на элемент маршрутизации, который лучше всего подходит для места назначения. Место назначения записывается таким образом, чтобы после- последующие операции вывода могли проверить, является ли новое место назначения тем же, что и прежнее, позволяя использовать тот же самый маршрут. Подразумевается, что возвращенный маршрут сохраняется вызывающим до тех пор, пока не будет осво- освобожден через вызов макроса RTFREE. Все доступы к таблице маршрутизации должны
584 Глава 12. Сетевая коммуникация соответствующим образом блокироваться в FreeBSD, а макрос RTFREE обрабатывает блокировки так же, как уменьшение счетчика ссылок маршрута, освобождая элемент маршрута, когда значение счетчика ссылок достигает нуля. TCP использует кеш хоста для хранения маршрутов в течение времени жизни соединения. Протоколы без уста- установления соединения, такие, как UDP, выделяют и освобождают маршруты каждый раз, когда изменяется адрес места назначения. Процедура rtallocQ просто проверяет, содержит ли уже маршрут ссылку на действительный маршрут. Если ссылки на дейст- действительный маршрут нет или если маршрут больше недействителен, rtallocQ вызывает процедуру rtalloclQ для поиска элемента маршрутизации для места назначения, пере- передавая флаг, указывающий, будет ли маршрут использоваться или просто проверяется. Если будут отправляться пакеты, при необходимости путем клонирования создается маршрут. Указатель на маршрут Место назначения I > struct route Указатель на маршрут Место назначения struct route Базисные узлы (с местом назначения и маской) Шлюз Флаги Счетчик ссылок Процедура вывода Указатель интерфейса Указатель на адрес интерфейса Индивидуальные для канального уровня Метрики Маршрут до шлюза Родитель Мьютекс /- ** struct ifnet Г struct ifaddr Индивидуальные данные struct rtentry Рис. 12.10. Структуры данных, используемые при назначении маршрута Для обработки управляющего сообщения перенаправления вызывается процедура rtredirectQ. Она вызывается с адресом назначения и маской, новым шлюзом до этого места назначения и источником перенаправления. Перенаправления принимаются лишь от текущего маршрутизатора для данного места назначения. Если для места на- назначения существует не универсальный маршрут, элемент шлюза в маршруте модифи- модифицируется таким образом, чтобы указывать новый предоставленный шлюз. В против- противном случае клонируется новый маршрут хоста из существующего в таблице маршру- маршрутизации маршрута сети. Маршруты к интерфейсам и маршруты к шлюзам, которые с хоста непосредственно недоступны, игнорируются.
12.5. Маршрутизация 585 Политики маршрутизации уровня пользователя Средства маршрутизации ядра умышленно воздерживаются от принятия решений о политике. Вместо этого политики маршрутизации определяются процессами поль- пользователя, которые добавляют, удаляют или изменяют элементы в таблице маршрутиза- маршрутизации ядра. Решение о помещении принятия решений о политике в процесс пользователя подразумевает, что обновления таблицы маршрутизации могут отставать от идентифи- идентификации новых маршрутов или от отказов существующих маршрутов. Этот период неста- нестабильности обычно короткий, если процесс маршрутизации реализован соответст- соответствующим образом. Специфическую для Интернета вспомогательную информацию, такую, как сообщения ICMP об ошибках, можно также прочесть из непосредственных сокетов (описанных в разделе 12.7). Было реализовано несколько процессов политики маршрутизации. Стандартный системный демон маршрутизации routed использует протокол маршрутной информа- информации (Routing Information Protocol - RIP) [Hedrick, 1988]. Многие сайты, которым требу- требуется использование других протоколов маршрутизации или больше опций конфи- конфигурирования, чем предоставляется routed, используют либо коммерческий пакет, либо Quagga Routing Suite с открытым исходным кодом [Ishiguro, 2003]. Интерфейс маршрутизации уровня пользователя: сокет маршрутизации Процессам уровня пользователя, которые реализуют политику и протоколы маршрути- маршрутизации, требуется интерфейс к таблице маршрутизации ядра, чтобы они могли добав- добавлять, удалять и измерять маршруты ядра. Интерфейс с уровнем маршрутизации ядра в FreeBSD использует сокет для взаи- взаимодействия с уровнем маршрутизации ядра. Привилегированный процесс создает в се- семействе протоколов маршрутизации AF_ROUTE непосредственный сокет, а затем передает сообщения в уровень и из уровня маршрутизации ядра. Этот сокет действует аналогично обычному сокету дейтаграмм, включая помещение полученных через сокет сообщений в очередь, за тем исключением, что взаимодействие имеет место между процессом пользователя и ядром. Сообщения включают заголовок с типом сообщения, идентифицирующим одно из действий, перечисленных в табл. 12.7. Сооб- Сообщения ядру являются запросами добавления, изменения или удаления маршрута или запросами сведений о маршруте до определенного места назначения. Ядро посылает сообщение в ответ на первоначальный запрос, указание на то, что сообщение является ответом, и номер ошибки в случае сбоя. Поскольку сокеты маршрутизации являются непосредственными сокетами, каждый открытый сокет маршрутизации получает копию ответа и должен фильтровать сообщения, которые ему нужны. Заголовок сооб- сообщения включает ID процесса и номер последовательности, чтобы каждый процесс мог определить, является ли данное сообщение ответом на его собственный запрос, и со- сопоставить ответы с запросами. Ядро также посылает сообщения в качестве указаний
586 Глава 12. Сетевая коммуникация об асинхронных событиях, таких, как перенаправления и изменения в состоянии локального интерфейса. Эти сообщения дают демону возможность отслеживать изме- изменения в таблице маршрутизации, делаемые другими процессами, события, обнаружи- обнаруживаемые ядром, и изменения в адресах и состояниях локальных интерфейсов. Сокет маршрутизации используется также для доставки запросов для внешнего разрешения маршрута канального уровня, когда для элемента маршрута установлен флаг RTF_XRESOLVE. Табл. 12.7. Типы сообщений маршрутизации Тип сообщения Описание RTM_ADD Добавить маршрут RTM_DELETE Удалить маршрут RTM_CHANGE Изменить метрики или флаги RTM_GET Сообщить маршрут и метрики RTM_LOSING Ядро предполагает разбиение сети RTM_REDIRECT Команда использовать другой маршрут RTMJMISS Ошибка поиска данного адреса RTM_LOCK Заблокировать указанные метрики RTMJDLDADD Вызван SIOCADDRT RTM_OLDDEL Вызван SIOCDELRT RTM_RESOLVE Запрос разрешения канального адреса RTM_NEWADDR Адрес добавлен к интерфейсу RTM_DELADDR Адрес удален от интерфейса RTM_IFINFO Будет включен или отключен интерфейс RTM_IFANNOUNCE Добавление или удаление интерфейса RTM_NEWMADDR К интерфейсу добавляется членство в рассылочной группе RTM_DELMADDR Из интерфейса удаляется членство в рассылочной группе Запросы добавления или удаления маршрута включают всю информацию, необходи- необходимую для маршрута. В заголовке есть поле для флагов маршрута, перечисленных в табл. 12.6, а также структура rtjnetrics метрик, которые можно установить или забло- заблокировать. Единственной метрикой, которую можно установить, является MTU. Все другие метрики игнорируются, но были сохранены для совместимости с программным обеспечением, которое использовало интерфейс сокета маршрутизации до FreeBSD 5.2. Заголовок содержит также битовый вектор, описывающий набор адресов, переносимых в сообщении; адреса следуют за заголовком в виде массива структур sockaddr с переменным размером. Адрес места назначения необходим также как маска для сете- сетевых маршрутов. Кроме того, обычно требуется адрес шлюза. Система чаще всего опреде-
12.6. Буферирование и управление перегрузкой 587 ляет используемый интерфейс по маршруту от адреса шлюза с помощью интерфейса используемого совместно с этим шлюзом. По соглашению, прямые маршруты содержат локальный адрес интерфейса, который должен быть использован. В некоторых случаях адреса шлюза недостаточно для определения интерфейса и может быть также передан адрес интерфейса, обычно с использованием структуры sockaddr_dl, содержащей имя интерфейса или индекс (см. раздел 12.1). 12.6. Буферирование и управление перегрузкой Основным фактором, влияющим на производительность протокола, является политика буферирования. Отсутствие подходящей политики буферирования может вызвать уничтожение пакетов, выдачу протоколами ложной информации об окнах, фрагмен- тирование памяти и снижение общей производительности хоста. Из-за этих проблем большинство систем выделяют сетевой системе фиксированный пул памяти и устанав- устанавливают политику, оптимизированную для обычной работы сети. Сетевая система FreeBSD в этом отношении кардинально не отличается от осталь- остальных. В ходе загрузки сетевой системой выделяется фиксированный объем памяти для mbuf и кластеров mbuf. Впоследствии для кластеров mbuf может быть запрошено большее количество системной памяти по мере увеличения потребности, вплоть до предварительно сконфигурированного лимита; однако эта память никогда не возвра- возвращается системе. Можно было бы получать память от сетевых буферов обратно, но в окружениях, где использовалась эта система, данное хранилище для сетевых пакетов не представляло собой большой проблемы, поэтому освобождение хранилища было оставлено нереализованным. Политики буферирования протокола Когда создается сокет, протокол резервирует некоторый объем буферной памяти для очередей отправки и приема. Эти объемы определяют верхние границы, используемые процедурами сокетов при принятии решений о том, когда блокировать и разблокиро- разблокировать процесс. Резервирование памяти в настоящее время не приводит ни к каким дей- действиям со стороны процедур управления памятью. Протоколы, предоставляющие управление потоком на уровне соединения, осно- основывают свои решения на объеме пространства в соответствующих очередях сокетов. То есть окна, отправляемые удалённым узлам, вычисляются на основе объема свобод- свободного пространства в приемной очереди сокета, тогда как использование окна отправки, полученного от узла, зависит от верхней границы очереди отправки.
588 Глава 12. Сетевая коммуникация Ограничение очереди Входящие пакеты из сети принимаются всегда, если только выделение памяти не за- завершается неудачей. Однако у каждой входной очереди протокола сетевого уровня есть верхняя граница размера очереди, и все пакеты, выходящие за пределы этой гра- границы, уничтожаются. Хост может оказаться переполненным непомерным сетевым трафиком (например, если хост действует в качестве маршрутизатора, который соеди- соединяет сеть с высокой пропускной способностью с сетью с низкой пропускной способно- способностью). В качестве защитного механизма можно регулировать ограничения очереди для управления загрузки системы сетевым трафиком. Уничтожение пакетов не всегда явля- является удовлетворительным решением этой проблемы (простое уничтожение пакетов склонно увеличивать нагрузку на сеть); размеры очереди были приняты главным обра- образом в качестве меры предосторожности. С другой стороны, ограничение размеров выходной очереди может иметь значение на хостах, которые направляют трафик из сети с высокой пропускной способностью в сеть с низкой пропускной способностью. Лимит очереди должен быть достаточно большим, чтобы можно было справиться с временной перегрузкой, но слишком большое увеличение очереди увеличивает сете- сетевые задержки до неприемлемого уровня. 12.7. Непосредственные сокеты Непосредственный (raw) сокет позволяет привилегированным пользователям получать непосредственный доступ к протоколу, который обычно не используется для транспортировки данных пользователя (например, к протоколам сетевого уровня). Непосредственные сокеты предназначены для хорошо осведомленных процессов, которые хотят использовать преимущества некоторых особенностей протокола, недо- недоступных непосредственно через обычный интерфейс, или для разработки протоколов, построенных поверх существующих протоколов. Например, программа ping реализо- реализована с использованием непосредственного сокета ICMP (см. раздел 13.8). Интерфейс непосредственных сокетов IP пытается предоставить идентичный интерфейс, который был бы у протокола, если бы он был размещен в ядре. Поддержка непосредственных сокетов построена вокруг общего интерфейса непо- непосредственных сокетов, возможно дополненных специфическими для протокола про- процедурами обработки. В данном разделе описывается лишь основная часть интерфейса непосредственных сокетов; подробности, специфичные для отдельных протоколов, не обсуждаются. Некоторые семейства протоколов (включая IPv4) используют частные версии описанных здесь процедур и структур данных .
12.7. Непосредственные сокеты 589 Управляющие блоки У каждого непосредственного сокета есть управляющий блок протокола в форме, представленной на рис. 12.11. Непосредственные управляющие блоки хранятся в оди- одинарном связанном списке для осуществления поисков в ходе распределения пакетов. В полях, на которые ссылаются управляющие блоки, могут быть записаны связи, которые могут использоваться процедурой вывода при подготовке пакета для пере- передачи. Поле rcb_proto содержит семейство протоколов и номер протокола, с которым связан непосредственный сокет. Протокол, семейство и адреса используются для фильтрования пакетов на входе, как описано в следующем подразделе. Сокет Список Управляющий блок протокола socket Сокет Список Внешний адрес Локальный адрес Идентификатор протокола 1 Непосредственные rawcb rawcb Управляющий блок протокола — socket Сокет Список Внешний адрес Локальный адрес Идентификатор протокола rawcb Рис. 12.11. Управляющий блок непосредственного сокета Непосредственный сокет является ориентированным на дейтаграммы: для каждой отправки или получения через сокет требуется адрес места назначения. Адреса назначения могут предоставляться пользователем или указываться через указатели на структуры sockaddr в управляющем блоке и автоматически вставляться в исходящие пакеты процедурой вывода. Если необходима маршрутизация, она должна выполнять- выполняться нижележащим протоколом. Обработка ввода Входящие пакеты направляются непосредственным сокетам на основе простой схемы соответствия паттерну. Каждый протокол (и потенциально некоторые сетевые интерфейсы) предоставляет неназначенные пакеты процедуре непосредственного ввода с помощью вызова void raw_input( struct mbuf *msg, struct sockproto *proto, struct sockaddr *src, struct sockaddr *dst);
590 Глава 12. Сетевая коммуникация Во входные очереди всех непосредственных сокетов помещаются входящие пакеты, заголовок которых подходит в соответствии со следующими правилами. 1. Семейство протоколов сокета и заголовка согласуются. 2. Если номер протокола в сокете не равен нулю, то он согласуется с номером прото- протокола в заголовке пакета. 3. Если для сокета определен локальный адрес, формат локального адреса сокета такой же, как у адреса назначения пакета, и два адреса точно соответствуют друг другу. 4. Правило 3 применяется к внешнему адресу сокета и исходному адресу пакета. Основным предположением в схеме сопоставления паттерна является то, что адреса, присутствующие в блоке управления и заголовке пакета (сконструированные сетевым интерфейсом и любым модулем входного протокола), находятся в каноническом виде, который можно сравнивать побитово. Если входящему пакету соответствуют несколько сокетов, пакет при необходимости копируется. Обработка вывода При выводе каждый запрос отправки приводит к вызову процедуры rawjusend непо- непосредственного сокета, которая вызывает процедуру вызова, специфическую для прото- протокола или семейства протоколов. Любая необходимая обработка делается до того, как пакет передается соответствующему сетевому интерфейсу. 12.8. Дополнительные темы сетевой подсистемы В данном разделе мы обсуждаем несколько аспектов сетевой подсистемы, которые сложно отнести к какой-либо другой категории. Внеполосные данные Способность обрабатывать внеполосные данные является средством, специфическим для абстракций сокетов потоков и сокетов упорядоченных пакетов. По-видимому, нет единого мнения относительно того, какой должна быть семантика внеполосных дан- данных. TCP определяет понятие, обозначаемое как срочные (urgent) данные, когда поточные данные помечаются для срочной доставки. Протокол предоставляет отметку в потоке данных, отграничивающую срочные данные от последующих обычных дан- данных. Протоколы ISO/OSI [Burruss, 1980] и многочисленные другие протоколы предо- предоставляют полностью независимый канал логической передачи, по которому можно пересылать внеполосные данные. Кроме того, объем данных, который можно пересы- пересылать во внеполосных сообщениях, изменяется от протокола к протоколу и составляет от 1 бита до 512 байтов и более.
12.8. Дополнительные темы сетевой подсистемы 591 Понятие внеполосных данных для сокетов потоков было определено в качестве наименьшего приемлемого общего знаменателя. Предполагается, что внеполосные данные передаются вне обычных ограничений упорядочивания и управления потоком данных. От протокола, поддерживающего внеполосные сообщения, ожидается предо- предоставление минимум 1 байта внеполосных данных и одного ожидающего внеполосного сообщения. Поддержка сообщений большего размера или более чем одного ожи- ожидающего внеполосного сообщения одновременно является прерогативой протокола. Внеполосные данные могут храниться протоколом отдельно от приемной очереди сокета. Они могут также предварять обычную приемную очередь, помеченные как внеполосные данные. Для принудительного помещения всех внеполосных данных в обычную приемную очередь при получении срочных данных предусматривается опция уровня сокетов SO_OOBINLINE. Эта опция предусмотрена, потому что реали- реализация TCP 4.2BSD удаляла 1 байт данных из потока данных в месте, указанном меткой срочности, для отдельного представления. Однако это удаление вызывало проблемы, когда дополнительные срочные данные отправлялись до того, как приложением был получен первый такой байт. Помещение внеполосных данных в обычный поток данных может позволить про- протоколу хранить несколько внеполосных сообщений одновременно. Этот механизм может позволить избежать потерь внеполосных данных, вызванных медленно от- отвечающим процессом. Протокол разрешения адресов Протокол разрешения адресов (address resolution protocol - ARP) является протоколом канального уровня, предоставляющим механизм динамического преобразования адресов для сетей, поддерживающих широковещательную или многоадресную коммуникацию [Plummer, 1982]. ARP отображает 32-разрядные адреса IPv4 в 48-разрядные адреса Ether- Ethernet. Хотя ARP не является специфическим ни для адресов протокола IPv4, ни для Ether- Ethernet, сетевая подсистема FreeBSD поддерживает лишь эту комбинацию, хотя и обеспечи- обеспечивает возможность добавления дополнительных комбинаций. ARP внедрен в уровень се- сетевого интерфейса, хотя логически он находится между сетевым уровнем и уровнем се- сетевого интерфейса. Общая идея ARP проста. Поддерживается набор преобразований сетевых адресов в адреса канального уровня. Когда сетевым интерфейсом службе ARP делается запрос преобразования адреса и запрошенный адрес не находится в известном ARP наборе преобразований, создается сообщение ARP, в котором указывается запрошенный сете- сетевой адрес и неизвестный адрес канального уровня. Затем это сообщение распространя- распространяется интерфейсом с помощью метода широковещания в ожидании, что хосту, под- подключенному к сети, будет известно преобразование - обычно из-за того, что хост явля- является местом назначения первоначального сообщения. Если своевременно получен ответ, служба ARP использует ответ для обновления своих таблиц преобразований
592 Глава 12. Сетевая коммуникация и для разрешения ожидающего запроса, а затем вызывается запрашивающий сетевой интерфейс для передачи первоначального сообщения. На практике простота этого алгоритма усложняется из-за необходимости избежать устаревших данных преобразования, минимизировать широковещательные запросы, когда целевой хост выключен, и обработки завершившихся неудачей запросов пре- преобразования. Кроме того, необходимо иметь дело с пакетами, для которых была пред- предпринята попытка передачи до завершения преобразования адреса. Таблицы преобразо- преобразований ARP реализованы как часть таблицы маршрутизации. Маршрут к локальному Ethernet устанавливается в виде клонируемого маршрута таким образом, что для каж- каждого локального хоста при ссылке будет создан индивидуальный маршрут хоста. Когда маршрут клонируется, канальный уровень создает пустой элемент ARP, связанный с маршрутом. Процедуре сетевого вывода обычно требуется поиск маршрутизации или кешированный маршрут, и теперь она передает ссылку на маршрут функции вывода интерфейса. Для разрешения адреса IPv4 в адрес Ethernet для исходящего сообщения делается вызов int arpresolve( struct ifnet *ifp, struct rtentry *rt, struct mbuf *msg, struct sockaddr *dst, u_char *desten/ struct rtentry *rtO); ARP сначала проверяет в своих таблицах, не является ли адрес назначения широ- широковещательным или групповым, в этих случаях Ethernet-адрес можно вычислить непо- непосредственно. В противном случае он проверяет, не содержит ли уже переданный эле- элемент маршрута выполненное преобразование, срок действия которого еще не истек. Если это так, значение шлюза в элементе маршрута является адресом назначения ка- канального уровня, и это значение возвращается в desten для использования в качестве адреса назначения исходящего пакета. Если адрес канального уровня неизвестен или просрочен, ARP должен поместить исходящее сообщение в очередь для последующей отправки и разослать широковещательное сообщение с запросом преобразования адреса Интернета. В элементе ARP записывается время рассылки сообщения, и в тече- течении этой секунды не предпринимаются дополнительные широковещательные рассыл- рассылки, если делаются попытки дополнительных передач. Если другой запрос преобразова- преобразования делается до того, как получен ответ, помещенное в очередь сообщение уничтожа- уничтожается, а сохраняется лишь более новое. После нескольких широковещательных запро- запросов без ответа (обычно 5 в течение не менее 5 секунд) маршрут изменяется на непригодный с временем истечения в 20 секунд, что вызывает возвращение ошибок недоступности хоста в ответ на попытки получить доступ к хосту в течение указанного времени.
Упражнения 593 Впоследствии - желательно до истечения срока таймера для сообщения в очереди - ARP получит ответ на свой запрос преобразования. Полученное сообщение обрабатыва- обрабатывается процедурой etherjdemuxQ, вызванной из сетевого потока. Поскольку пакет имеет тип ARP, он помещается в очередь процедуры arpintrQ, аналогичной другим входным процедурам протоколов сетевого уровня. Пакет ARP обрабатывается, чтобы найти эле- элемент преобразования в таблице маршрутизации. Если сообщение завершает ожидающее преобразование, элемент обновляется, и первоначальное сообщение передается обратно сетевому интерфейсу для отправки. На этот раз результирующий вызов arpresolveQ завершится успешно без задержки. Обработка ввода ARP должна справляться с запросами собственного адреса хоста, а также с ответами на запросы преобразования, генерируемые хостом. Входной модуль наблюдает также за ответами от других хостов, которые сообщают о преобразованиях для своих собственных адресов IPv4. Это наблюдение делается для того, чтобы гаран- гарантировать, что никакие два хоста в одной и той же сети не считают, что у них один и тот же адрес (хотя такая ошибка может быть обнаружена, единственное, что может сделать ARP - запись диагностического сообщения в журнал). Обычно ARP устанавливает тайм-аут для элементов завершенных преобразований в своем кеше в 20 минут, а для элементов незавершенных преобразований примерно в 5 секунд. Однако элементы могут быть помечены как постоянные; в этом случае они никогда не удаляются. Элементы могут также быть отмечены как опубликованные, что дает одному хосту возможность действовать в качестве заместителя для других хостов, которые не поддерживают ARP, или действовать в качестве представителя (proxy) для хоста, доступ к которому осуществляется не через Ethernet, а через маршрутизатор. Упражнения 12.1. Назовите две ключевые структуры данных, использующиеся в сетевой подсис- подсистеме, которые важны для обеспечения независимости программного обеспечения уровня сокетов от сетевой реализации. 12.2. Какая работа выполняется нижней половиной драйвера сетевого устройства? Какая работа выполняется верхней половиной драйвера сетевого устройства? 12.3. Какие процедуры в переключателе протоколов вызываются уровнем сокетов? Объясните, зачем вызывается каждая из этих процедур. 12.4. Предположим, что сокет наделено доставляемых сообщений (SOCK_RDM) является сокетом без установления соединения, который гарантирует надежную доставку данных и который сохраняет границы сообщений. Какие флаги были бы установлены в поле prjlags протокола, который поддерживает этот тип сокета, в его элементе переключения протокола? 12.5. Приведите пример сетевого интерфейса, который полезен без лежащего в его основе аппаратного устройства.
594 Глава 12. Сетевая коммуникация 12.6. Приведите две причины, почему адреса сетевого интерфейса не находятся в структуре данных сетевого интерфейса. 12.7. Почему имя или адрес сокета хранятся на сетевом уровне, а не на уровне сокетов? 12.8. Почему FreeBSD не пытается осуществить жесткую структуру интерфейса протокол-протокол? 12.9. Опишите две задачи, выполняемые процедурой вывода сетевого интерфейса. 12.10. Почему идентификатор сетевого интерфейса, через который получено каж- каждое сообщение, передается наверх с этим сообщением? 12.11. Какие политики маршрутизации реализованы в ядре? 12.12. Опишите три вида маршрутов, которые можно найти в таблице маршрутиза- маршрутизации и которые отличаются по типу места назначения, к которому они применяются. 12.13. Какое средство маршрутизации разработано главным образом для поддержки рабочих станций? 12.14. Что такое перенаправление маршрутизации? Для чего оно используется? 12.15. Почему у выходных очередей пакетов для каждого сетевого интерфейса есть ограничения на число пакетов, которые могут находиться в очереди? 12.16. Что делает опция сокета SO_OOBINLINE? Почему она существует? *12.17. Объясните, почему невозможно использовать интерфейс непосредственных сокетов для поддержки параллельных реализаций протоколов - некоторых в режиме ядра, а некоторых в режиме пользователя. Какие изменения в сис- системе были бы необходимы для поддержки этой возможности? *12.18. Предыдущие версии системы использовали хешированный поиск маршрути- маршрутизации для места назначения в виде хоста или сети. Назовите два аспекта, в которых алгоритм базисного поиска в FreeBSD более предпочтителен. Ссылки BBN, 1978. BBN, "Specification for the Interconnection of Host and IMP", Technical Report 1822, Bolt, Beranek, and Newman, Cambridge, MA, May 1978. Burruss, 1980. J. Burruss, "Features of the Transport and Session Protocols", Report No. ICST/HLNP-80-1, National Bureau of Standards, Washington, DC, March 1980. Hedrick, 1988. C. Hedrick, "Routing Information Protocol", RFC 1058, available from http:// www.faqs.org/rfcs/rfcl058.html, June 1988.
Ссылки 595 Ishiguro, 2003. К. Ishiguro, Quagga, available from www.quagga.net, August 2003. ISO, 1984. ISO, "Open Systems Interconnection: Basic Reference Model", ISO 7498, International Organization for Standardization, available from the American National Standards Insti- Institute, 1430 Broadway, New York, NY 10018, 1984. Mogul & Deering, 1990. J. Mogul & S. Deering, "Path MTU Discovery", RFC 1191, available from http:// www.faqs.org/rfcs/rfcll91.html, November 1990. Plummer, 1982. D. Plummer, "An Ethernet Address Resolution Protocol", RFC 826, available from http://www.faqs.org/rfcs/rfc826.html, November 1982. Postel, 1981. J. Postel, "Internet Control Message Protocol", RFC 792, available from http:// www.faqs.org/rfcs/rfc792.html, September 1981. Ritchie, 1984. D. Ritchie, "A Stream Input-Output System", AT&TВell Laboratories TechnicalJournal, vol. 63, no. 8-2, pp. 1897-1910, October 1984. Sedgewick, 1990. R. Sedgewick, Algorithms in C, Addison-Wesley, Reading, MA, 1990. Sklower, 1991. K. Sklower, "A Tree-Based Packet Routing Table for Berkeley UNIX", USENIXAssociation Conference Proceedings, pp. 93-99, January 1991. Wright & Stevens, 1995. G. R. Wright & W. R. Stevens, TCP/IP Illustrated, Volume 2, The Implementation, Addison- Wesley, Reading, MA, 1995.
Глава 13 Сетевые протоколы В главе 12 была представлена архитектура сетевых коммуникаций FreeBSD. В данной главе мы исследуем сетевые протоколы, реализованные в рамках этой инфраструк- инфраструктуры. Система FreeBSD поддерживает несколько основных коммуникационных доме- доменов, включая IPv4, IPv6, Xerox Network Systems (NS), ISO/OSI и локальный домен (известный ранее как домен UNIX). Локальный домен не включает сетевые протоко- протоколы, поскольку он работает всецело внутри одной системы. Набор протоколов IPv4 был первым набором протоколов, реализованным в рамках сетевой архитектуры 4.2BSD. Вслед за выпуском 4.2BSD в рамках сетевой архитектуры производителями было реа- реализовано несколько собственных семейств протоколов. Однако лишь с добавлением протоколов Xerox NS в 4.3BSD была явно продемонстрирована способность системы поддерживать множество семейств сетевых протоколов. Хотя некоторые части интерфейса протоколов раньше не использовались и поэтому не были реализованы, изменения, потребовавшиеся для добавления второго семейства сетевых протоколов, не изменили существенным образом сетевую архитектуру. Реализация сетевых прото- протоколов ISO/OSI, а также другие меняющиеся требования привели к дальнейшему усо- усовершенствованию сетевой архитектуры в 4.4BSD. Два новых протокола, которые были добавлены к системе, IPv6 и IPSec, потребовали некоторых изменений из-за необходи- необходимости их одновременного сосуществования с IPv4. Эти изменения, а также IPv6 и IPSe представлены в конце данной главы. В этой главе мы фокусируемся на организации и реализации протоколов IPv4. Эта реализация протоколов является стандартом, на котором построен современный Интернет, поскольку она была общедоступной, когда множество производителей искали налаженные и надежные коммуникационные протоколы. На протяжении данной главы мы будем использовать термины «Интернет» и «IPv4» взаимозаменяе- взаимозаменяемым образом. При обсуждении новых протоколов IPv6, которые предназначены для замены в конечном счете IPv4, будет сделано особое упоминание. После описания
13.1. Сетевые протоколы IPv4 597 всеобщей структуры протоколов IPv4 мы исследуем их работу в соответствии со структурой, определенной в главе 12. Мы также опишем важные алгоритмы, исполь- используемые протоколами внутри IPv4. Затем мы обсудим изменения, которые разработчи- разработчики сделали в системе, мотивируясь аспектами протоколов IPv6 и их реализации. 13.1. Сетевые протоколы IPv4 IPv4 был разработан при поддержке DARPA для использования в ARPANET [DARPA, 1983; McQuillan & Walden, 1977]. Эти протоколы общеизвестны как TCP/IP, хотя TCP и IP являются лишь двумя из множества протоколов в семействе. Эти протоколы не предполагают наличие надежной подсети, которая гарантирует доставку данных. Вместо этого IPv4 был придуман для модели, в которой хосты были подключены к сетям с различными характеристиками, а сети были объединены маршрутизаторами. Протоколы Интернета были разработаны для сетей с коммутацией пакетов, исполь- использующих дейтаграммы, переправляемые через соединения типа Ethernet, которые не предоставляют уведомлений о доставке. Эта модель ведет к использованию по меньшей мере двух уровней протоколов. Один уровень работает в сквозном режиме между двумя хостами, вовлеченными в диа- диалог. Он основан на протоколе более низкого уровня, который работает на последова- последовательной основе, переправляя каждое сообщение на хост назначения через промежу- промежуточные маршрутизаторы. Вообще существует по крайней мере один уровень протоко- протокола над другими двумя - это уровень приложения. Эти три уровня примерно соответст- соответствуют уровням 3 (сетевому), 4 (транспортному) и 7 (прикладному) в эталонной модели взаимодействия открытых систем ISO [ISO, 1984]. Прикладной Транспортный уровень Сетевой уровень Канальный уровень J Прикладной TCP UDP IP ICMP Сетевой интерфейс Обозначения: TCP-протокол управления передачей, UDP-протокол пользовательских дейтаграмм, IP - протокол Интернета, ICMP -протоколуиравляютихсообщенийИнтернета. РИС. 13.1. Разбиение на уровни протоков IPv4
598 Глава 13. Сетевые протоколы Разбиение на уровни протоколов, поддерживающих эту модель, показано на рис. 13.1. Протокол Интернета (IP) является в данной модели протоколом самого низ- низкого уровня; этот уровень соответствует сетевому уровню ISO. IP работает шаг за шагом, когда дейтаграмма отправляется от исходящего хоста на место назначения через любые промежуточные маршрутизаторы. Он предоставляет службы сетевого уровня по адресации хостов, маршрутизации и при необходимости фрагментации и повторной сборки, если промежуточные сети не могут переслать весь пакет как одно целое. Все другие протоколы используют службы IP. Протокол управления передачей (TCP) и протокол пользовательских дейтаграмм (UDP) являются протоколами транс- транспортного уровня, предоставляющими дополнительные возможности приложениям, использующим IP. Каждый протокол добавляет к адресу хоста IP номер порта таким образом, что можно идентифицировать локальные и удаленные сокеты. TCP предос- предоставляет ориентированную на соединение, надежную, недублированную передачу данных с управлением потоком; он поддерживает в домене Интернета тип сокетов по- потока. UDP кроме идентификатора порта предоставляет контрольную сумму данных для проверки целостности, но в других отношениях мало что добавляет к службам, предоставляемым IP. UDP является протоколом, используемым в домене Интернета сокетами дейтаграмм. Протокол управляющих сообщений Интернета (ICMP) исполь- используется для сообщений об ошибках и других простых задач по управлению сетью; он логически является частью IP, но подобно транспортным протоколам расположен над IP. Пользователи обычно не получают к нему доступ. Непосредственный доступ к про- протоколам IP и ICMP возможен через прямые сокеты (сведения об этой возможности см в разделе 12.7). Протоколы Интернета были разработаны для поддержки гетерогенных систем хостов и архитектур, которые используют широкое разнообразие внутренних пред- представлений данных. Даже основная единица данных (байт) не была одинаковой на всех хостах; один типичный тип хоста поддерживал байты переменного размера. Однако сетевым протоколам требуется стандартное представление. Это представление выра- выражается через использование октета - 8-битного байта. Мы будем использовать этот термин, поскольку он используется в спецификациях протоколов для описания сете- сетевых данных, хотя мы будем продолжать использовать термин байт для ссылки на данные или память внутри системы. Все поля в протоколах Интернета, которые превы- превышают размер октета, выражаются в сетевом порядке байтов, когда старший октет идет первым. Сетевая реализация FreeBSD использует набор процедур или макросов для преобразования 16-разрядных и 32-разрядных целых полей между порядками байтов хоста и сети на хостах (таких, как системы PC), которые имеют отличный собственный порядок.
13.1. Сетевые протоколы IPv4 599 Адреса IPv4 Адрес IPv4 представляет собой 32-разрядное число, идентифицирующее сеть, в которой находится хост, а также уникально идентифицирующее сетевой интерфейс на этом хосте. Отсюда следует, что хост с сетевыми интерфейсами, подключенными к несколь- нескольким сетям, имеет несколько адресов. Сетевые адреса назначаются блоками Региональ- Региональными регистратурами Интернета (Regional Internet Registries - RIR) провайдерам служб Интернета (Internet Service Provider - ISP), которые затем раздают адреса небольшими порциями компаниям и отдельным пользователям. Если бы назначение адресов не осуществлялось таким централизованным способом, в сети возникли бы конфликтующие адреса и было бы невозможно правильно маршрутизировать пакеты. Исторически адреса IPv4 были жестко поделены на три класса (А, В и С) для обес- обеспечения потребностей больших, средних и малых сетей [Postel, 1981a]. Три класса ока- оказались слишком ограничивающими, а также слишком расточали адресное пространст- пространство. Современная схема адресации IPv4 называется бесклассовой междоменной маршрутизацией (Classless Inter-Domain Routing- CIDR) [Fuller et al., 1993]. В схеме CIDR каждой организации предоставляется непрерывная группа адресов, описывае- описываемых одним значением и маской сети. Например, у ISP могла бы быть группа адресов, определенная 18-разрядной маской сети. Это означает, что сеть определяется первыми 18 битами, а оставшиеся 14 битов потенциально могут использоваться для идентифи- идентификации хостов в сети. На практике число хостов меньше, поскольку ISP разделяет это пространство дальше на более мелкие сети, которые уменьшают число битов, которые можно эффективно использовать. Именно благодаря этой схеме в элементах маршру- маршрутизации вместе с маршрутами хранятся произвольные маски сетей. Каждый адрес Интернета, назначенный сетевому интерфейсу, хранится в струк- структуре* in_ifaddr, которая содержит независимую от протокола структуру адреса интерфейса и дополнительную информацию об использовании в домене Интернета (см. рис. 13.2). Когда указана маска сети интерфейса, она записывается в поле ia_subnetmask структуры адреса. Маска сети, ia_netmask, по-прежнему вычисляется по основе типа номера сети (класс А, В или С), когда назначается адрес интерфейса, но она больше не используется для определения того, находится ли место назначения внутри или вне локальной подсети. Система интерпретирует локальные адреса Интернета, используя значение ia_subnetmask. Адрес считается локальным для подсети, если поле под маской подсети соответствует полю подсети адреса интерфейса. Широковещательные адреса В сетях, способных поддерживать широковещательные дейтаграммы, 4.2BSD исполь- использовала для широковещания адрес с нулевой частью для хоста. После того как была вы- выпущена 4.2BSD, был определен широковещательный адрес Интернета как адрес, в части хоста которого установлены все единицы [Mogul, 1984]. Это изменение и вве- введение подсетей усложнили определение широковещательных адресов. Хосты могут
600 Глава 13. Сетевые протоколы injfaddr ifa_addr ifsjbroadaddr ifsjnetmask ifsjfp ifa_next ifajrtrequest ifs_- ags ifsjrefcnt ifsjnetric ia_net ia netmask ia_subnet iajsubnetmask iajoroadcast ia next ia multiaddrs L ifa_addr ifsjbroadaddr ifs_netmask ifsJfp ifa_next ifajrtrequest ifs_- ags ifsjrefcnt ifsjnetric iajiet iajietmask ia_subnet ia_subnetmask ia_broadcast iajiext iajnultiaddrs Рис. 13.2. Структура адреса интерфейса Интернета (injfaddr) использовать для обозначения широковещания часть хоста со всеми 0 или 1 и неко- некоторые могут распознавать наличие подсетей, тогда как другие нет. По этим причинам в 4.3BSD и в последующих системах BSD широковещательный адрес для каждого интерфейса имеет в значении хоста все установленные единицы, но для обратной совместимости допускает и установку альтернативного адреса. Если сеть содержит подсети, поле подсети широковещательного адреса содержит обычный номер подсети. Логический широковещательный адрес для сети также вычисляется при установке адреса; этим адресом был бы стандартный широковещательный адрес, если бы подсе- подсети не использовались. Этот адрес необходим для входной процедуры IP для фильтро- фильтрования входящих пакетов. На входе FreeBSD распознает и принимает широковещатель- широковещательные адреса сети и подсети с частью хоста, содержащей все 0 или 1, а также адрес со всеми 32 битами, установленными в 1 («широковещание для данного физического соединения»). Многоадресная рассылка Интернета Многие сети канального уровня, такие, как Ethernet, предоставляют возможность мно- многоадресной рассылки, которая позволяет адресовать группу хостов, но является более избирательной по сравнению с широковещанием, поскольку предусматривает несколь- несколько различных групповых адресов. IP предоставляет аналогичную возможность на уровне сетевого протокола, используя там, где это возможно, многоадресную рассылку канального уровня [Deering, 1989]. Многоадресные рассылки IP используют адреса
13.1. Сетевые протоколы IPv4 601 назначения, старшие биты которых начинаются с 1110. В отличие от адресов хостов, групповые адреса не делятся на части сети и хоста; вместо этого весь адрес относится к группе, такой, как группа хостов, использующая определенную службу. Эти группы могут создаваться динамически, и члены группы могут со временем меняться. Группо- Групповые IP-адреса отображаются непосредственно на физические групповые адреса в сетях, подобных Ethernet, используя младшие 24 бита IP-адреса вместе с постоянным 24-разрядным префиксом для образования 48-разрядного адреса канального уровня. Чтобы сокет использовал многоадресную рассылку, он должен присоединиться к рассылочной группе, использовав системный вызов setsockopt. Этот вызов информирует канальный уровень о том, что он должен принимать групповую рас- рассылку для соответствующих адресов канального уровня, а также отправляет сообще- сообщение о членстве в рассылочной группе, используя протокол управления группами Интернета (Cain et al., 2002). После этого агенты многоадресной рассылки в сети могут отслеживать членов каждой группы. Агенты многоадресной рассылки получают все групповые пакеты из непосредственно подсоединенных сетей и пере- пересылают при необходимости многоадресные дейтаграммы членам групп в других сетях. Эта функция сходна с ролью маршрутизаторов, пересылающих обычные (одноадресные) пакеты, но критерии для пересылки пакета другие, и пакет может пересылаться в несколько соседних сетей. Порты и связи Интернета На уровне IP пакеты адресуются хостам, а не процессам или коммуникационным портам. Однако каждый пакет содержит 8-разрядный номер протокола, который иден- идентифицирует следующий протокол, который должен получить пакет. Транспортные протоколы Интернета используют дополнительный идентификатор для обозначения в хосте соединения или коммуникационного порта. Большинство протоколов (включая TCP и UDP) используют для этой цели 16-разрядный номер порта. Каждый транспорт- транспортный протокол поддерживает свое собственное отображение номеров портов на процес- процессы или дескрипторы. Таким образом, связь, такая, как соединение, полностью опреде- определяется кортежем <исходный адрес, адрес назначения, номер протокола, исходный порт, порт назначениям Протоколы, ориентированные на соединение, такие, как TCP, должны обеспечить уникальность связей; другие протоколы также это делают. Когда локальная часть адреса установлена прежде удаленной части, необходимо выбрать уникальный номер порта, чтобы предотвратить коллизии при определении удаленной части. Управляющие блоки протоколов Для каждого основанного на TCP или UDP сокета создается управляющий блок прото- протокола Интернета (структура inpcb) для хранения сетевых адресов Интернета, номеров портов, информации о маршрутизации и указателей на любые вспомогательные струк-
602 Глава 13. Сетевые протоколы туры данных. TCP, кроме того, создает управляющий блок TCP (структуру tcpcb) для хранения большого количества информации о состоянии протокола, необходимой для его реализации. Управляющие блоки Интернета для использования с TCP хранятся в собственном двойном связанном списке для модуля протокола TCP. Управляющие блоки Интернета для использования с UDP хранятся в сходном собственном списке для модуля протокола UDP. Два отдельных списка нужны, потому что каждый прото- протокол в домене Интернета имеет отдельное пространство для идентификаторов портов. Отдельные протоколы используют общие процедуры для добавления в список новых управляющих блоков, установки локальной и удаленной частей связи, поиска управ- управляющего блока по связи и удаления управляющих блоков. IP демультиплексирует трафик сообщений, основываясь на идентификаторе протокола, указанном в заголовке протокола, и каждый протокол более высокого уровня отвечает после этого за про- проверку своего списка управляющих блоков Интернета, чтобы направить сообщение соответствующему сокету. На рис. 13.3 показана связь между структурой данных сокета и этими специфическими для протокола структурами данных. Управляющий блок протокола Управляющий блок протокола Рис. 13.3. Структуры данных протокола Интернета
13.2. Протокол пользовательских дейтаграмм (UDP) 603 Реализации протоколов Интернета тесно связаны, как подобает тесному перепле- переплетению протоколов. Например, транспортные протоколы отправляют и получают пакеты, включающие не только свой собственный заголовок, но также и псевдозаголовок IP, содержащий исходный и конечный адреса, идентификатор протокола и размер пакета. Этот псевдозаголовок включен в контрольную сумму пакета транспортного уровня. Теперь мы готовы изучить работу протоколов Интернета. Мы начнем с UDP, поскольку он намного проще, чем TCP. 13.2. Протокол пользовательских дейтаграмм (UDP) Протокол пользовательских дейтаграмм (User Datagram Protocol- UDP) [Postel, 1980] является простым, ненадежным протоколом дейтаграмм, предусматривающим лишь сквозную адресацию и необязательную контрольную сумму данных. В FreeBSD кон- контрольные суммы включаются или отключаются для всей системы в целом и не могут включаться или отключаться для отдельных сокетов. Заголовки протокола UDP чрез- чрезвычайно просты и содержат лишь номера исходного и конечного портов, размер дейта- дейтаграммы и контрольную сумму данных. Адреса хостов для дейтаграммы предоставляются псевдозаголовком IP. Инициализация Когда в домене Интернета создается новый сокет дейтаграмм, уровень сокетов нахо- находит элемент переключения протокола для UDP и вызывает процедуру udp_attach() с сокетом в качестве параметра. UDP использует in_pcballoc() для создания нового управляющего блока протокола в своем списке текущих сокетов. Он также устанавли- устанавливает ограничения по умолчанию для исходящего и входящего буферов. Хотя дейта- дейтаграммы никогда не помещаются в исходящий буфер, в качестве предела устанавлива- устанавливается верхний предел размера дейтаграммы; элемент переключения протокола UDP со- содержит флаг PRATOMIC, который требует, чтобы все данные при операции отправки представлялись протоколу одновременно. Если прикладная программа хочет привязать номер порта - например, хорошо известный номер порта для какой-нибудь службы дейтаграмм, - она делает системный вызов bind. Этот запрос достигает UDP в виде вызова процедуры udp_bind(). Привязка может также указать определенный адрес хоста, который должен быть адресом интерфейса на данном хосте. В противном случае адрес будет оставлен неопределен- неопределенным, отвечающим любому локальному адресу на входе и выбираемым в качестве под- подходящего для каждой операции вывода. Привязка осуществляется посредством in_pcbbind(), которая проверяет, что выбранный номер порта (или адрес и порт) не используется, а затем записывает локальный порт связи. Для отправки дейтаграмм система должна знать удаленную часть связи. Программа может указывать этот адрес и порт при каждой операции отправки, используя sendto
604 Глава 13. Сетевые протоколы или sendmsg, или она может заранее определить их с помощью системного вызова connect. В любом случае UDP использует функцию in_pcbconnect() для записи адреса и порта назначения. Если локальный адрес был не привязан и если найден маршрут до места назначения, в качестве локального адреса используется адрес исходящего интерфейса. Если локальный номер порта был не привязан, он выбирается в это время. Вывод Системный вызов, отправляющий данные, достигает UDP в виде вызова процедуры udp_send(), которая принимает цепочку mbuf, содержащих данные для дейтаграммы. Если вызов предоставил адрес назначения, он также передается; в противном случае используется адрес из предшествующего вызова connect. Фактическая операция по отправке осуществляется udp_output(): static int udp_output( struct inpcb *inp, struct mbuf *msg, struct mbuf *addr, struct mbuf *control, struct thread *td) ; где inp является управляющим блоком протокола IPv4, msg является цепочкой mbuf, содержащих данные для отправки, addr является необязательным mbuf, содержащим адрес назначения, a td является указателем на структуру потока. Структуры потока об- обсуждались в разделе 4.2, они используются внутри сетевого стека для идентификации отправителя пакета, поэтому они используются лишь с процедурами вывода. Любые вспомогательные данные в control отбрасываются. Адрес назначения, возможно, опре- определен предварительно с помощью вызова connect^ в противном случае он должен быть предоставлен в вызове отправки. UDP просто добавляет свой собственный заголовок, заполняет поля заголовка UDP и поля прототипа IP-заголовка и вычисляет контрольную сумму, прежде чем передать пакет модулю IP для вывода: int ip_output( struct mbuf *msg, struct mbuf *opt, struct route *ro, int flags, struct ip_moptions *imo, struct inpcb *inp); Вызов выходной процедуры IP более сложный, чем UDP, поскольку процедуре IP требуется указать больше информации о конечной точке, с которой она взаимодействует. Параметр msg указывает сообщение, которое должно быть отправлено, а параметр opt может определять список опций IP, которые должны быть помещены в заголовок IP пакета. Для многоадресных рассылок параметр into может ссылаться на опции многоадресной рассылки, такие, как выбор интерфейса и числа транзитных участков для групповых
13.2. Протокол пользовательских дейтаграмм (UDP) 605 пакетов. Опции LP можно установить для сокета с помощью системного вызова setsockopt, указав уровень протокола IP и опцию IP_OPTIONS. Эти опции хранятся в отдельном mbuf, а указатель на этот mbuf хранится в управляющем блоке протокола для сокета. Указа- Указатель на опции передается ip_output() с каждым отправляемым пакетом. Параметр ю явля- является необязательным, процедура udp_output() передает в качестве его значения NULL, так что маршрут для пакета будет определять IP. Параметр flags определяет, разрешено ли пользователю передавать широковещательное сообщение и должна ли быть пропуще- пропущена маршрутизация для отправляемого пакета (см. раздел 13.3). Флаг широковещания может быть неуместным, если нижележащее оборудование не поддерживает широкове- широковещательные передачи. Флаги также указывают, включает ли пакет псевдозаголовок IP или полностью инициализированный IP-заголовок, как в случае пересылки IP пакетов. Ввод Все транспортные протоколы Интернета, расположенные непосредственно поверх IP, используют следующее соглашение по вызову при получении пакетов от IP: (void) (*pr_input)( struct mbuf *m, int off); Каждая переданная цепочка mbuf является отдельным пакетом для обработки моду- модулем протокола. Пакет включает IP-заголовок вместо псевдозаголовка, а размер IP-заго- IP-заголовка передается в качестве смещения во втором параметре. Входная процедура UDP udpjnput() является типичной входной процедурой протокола. Она сначала проверяет, что длина пакета по крайней мере равна размеру заголовков IP плюс UDP, и использует m_pullup() для того, чтобы сделать заголовки непрерывными. Затем процедура udp_input() проверяет, что пакет имеет правильный размер, и вычисляет контрольную сумму для данных в пакете. Если любой из этих тестов завершается ошибкой, пакет уничтожается, а число ошибок увеличивается. Потом обрабатываются все вопросы мно- многоадресной рассылки. В конечном счете in_pcblookup() находит управляющий блок про- протокола для сокета, который должен получить данные, используя адреса и номера портов в пакете. Может быть несколько управляющих блоков с одним и тем же номером порта, но различными локальными или удаленными адресами; в таком случае выбирается управляющий блок, больше всего подходящий. Лучше всего подходит точное соответст- соответствие, но, если такого нет, подойдет сокет с правильным номером локального порта, но с неуказанными локальным адресом, номером удаленного порта или удаленного адреса. Таким образом, управляющий блок с неуказанным локальным или удаленным адресами действует в качестве группового (wildcard), получающего пакеты для своего порта, если не найдено точного совпадения. Если управляющий блок найден, данные и адрес, откуда был получен пакет, помещаются в приемный буфер указанного сокета с помощью udp_append(). Если адрес назначения является адресом групповой рассылки, копии пакета доставляются каждому сокету с подходящими адресами. В противном случае,
606 Глава 13. Сетевые протоколы если получатель не найден и если пакет не имеет широковещательного или группового адреса, отправителю дейтаграммы посылается сообщение ICMP об ошибке недоступно- недоступности порта. Это сообщение об ошибке обычно не имеет никакого эффекта, поскольку отправитель чаще всего соединяется с этим местом назначения лишь временно и уничто- уничтожает связь до того, как новый ввод обрабатывается. Однако, если отправитель по-преж- по-прежнему имеет полностью определенную связь, он может получить уведомление об ошибке. Управляющие операции Протокол UDP не поддерживает управляющих операций и перенаправляет вызовы своему элементу pr_ctloutput() непосредственно в протокол IP. У него есть простая процедура pr_ctlinput(), которая получает уведомление о любых асинхронных ошиб- ошибках. Ошибки передаются любому сокету дейтаграмм с указанным местом назначения; лишь сокеты с местом назначения, зафиксированным вызовом connect, могут получить асинхронные уведомления об ошибках. Такие ошибки просто записываются в соответ- соответствующем сокете, и, если процесс выполняет системный вызов select или находится в состоянии сна в ожидании ввода, он пробуждается. Когда сокет дейтаграмм UDP закрывается, вызывается процедура udp_detach(). Управляющий блок протокола и его содержимое просто удаляются функцией in_pcbdetach()\ другой обработки не требуется. 13.3. Протокол Интернета (IP) Рассмотрев работу простого транспортного протокола, мы продолжим обсуждение протокола сетевого уровня [Postel, 1981a; Postel et al., 1981]. Протокол Интернета (Internet Protocol - IP) является уровнем, отвечающим за адресацию и маршрутизацию между хостами, пересылки пакетов и фрагментацию и повторную сборку пакетов. В отличие от транспортных протоколов он не всегда работает для сокетов на локаль- локальном хосте; он может пересылать пакеты, получать пакеты, для которых нет локального сокета, или генерировать пакеты ошибок в ответ на эти ситуации. Функции, выполняемые IP, иллюстрируются содержимым заголовка его пакета, показанном на рис. 13.4. Заголовок идентифицирует исходный и конечный хосты и протокол места назначения, он содержит размеры заголовка и пакета. Поля иденти- идентификации и фрагмента используются, когда пакет или фрагмент должны быть разделе- разделены на меньшие части для передачи на свой следующий транзитный участок, и для повторной сборки фрагментов, когда они прибывают на место назначения. Флагами фрагментации являются Не фрагментировать и Дополнительные фрагменты, последний флаг плюс смещение дают достаточно информации для сборки фрагментов первоначального пакета на месте его назначения.
13.3. Протокол Интернета (IP) 607 0 3 4 Версия IHL Время жизни 7 8 ID 15 Тип службы Протокол 16 31 Общий размер Флаги и смещение фрагмента Контрольная сумма заголовка Адрес источника Адрес места назначения Опции Рис. 13.4. Заголовок IPv4. IHL является размером заголовка Интернета (Internet header length), указанным в единицах по четыре октета. Размер опций определяется по IHL. Все размеры полей приводятся в битах Опции IP представлены в IP-пакете, если поле размера заголовка имеет значение, превышающее минимальное, которое равно 20 байтам. Опция нет-операции и опция конец-списка-операций имеют размер каждая по одному октету. Все другие опции являются самокодирующимися, когда тип и размер расположены перед всеми допол- дополнительными данными. Хосты и маршруты способны таким образом пропустить опции, которые они не реализуют. Примерами существующих опций являются опции отметки- времени и записи-маршрута, которые обновляются каждым маршрутизатором, пере- пересылающим пакет, и опция маршрутизации-источника, которая предоставляет полный или частичный маршрут до места назначения. На практике они используются редко, и большинство сетевых операторов молча удаляют пакеты с опцией маршрутизации-источника, поскольку она затрудняет управление трафиком сети. Вывод Мы уже видели соглашение по вызову для выходной процедуры IP, которая представ- представляет собой int ip_output( struct mbuf *msg, struct mbuf *opt, struct route *ro, int flags, struct ip_moptions *imo, struct inpeb *inp); Как описано в подразделе по выводу в разделе 13.2, параметр msg является цепочкой mbuf, содержащей пакет для отправки, включая скелетный заголовок IP; opt является необязательным mbuf, содержащим опции IP для вставки после заголовка. Если дан маршрут го, он может содержать ссылку на элемент маршрутизации (струк- (структуру rtentiy), который определяет маршрут до места назначения, оставшийся от преды-
608 Глава 13. Сетевые протоколы дущего вызова, и в котором для будущего использования будет оставлен любой новый маршрут. Поскольку в FreeBSD 5.2 из структуры inpcb были удалены кешированные маршруты, этот кешированный маршрут используется редко. Поле flags может разре- разрешить использовать широковещание или может указывать, что таблицы маршрутизации нужно игнорировать. Если выставлено поле imo, то оно включает опции для много- многоадресных передач. Управляющий блок протокола inp используется подсистемой IPSec (см. раздел 13.10) для хранения данных об ассоциациях безопасности для пакета. Схема работы, выполняемой ip_output(), следующая. * Вставка всех IP-опций. Заполнение оставшихся полей заголовка (версия IP, нулевое смещение, размер за- заголовка и идентификация нового пакета), если пакет содержит псевдозаголовок IP. * Определение маршрута (т.е. исходящего интерфейса и места назначения следующего транзитного участка). Проверка того, не является ли место назначения групповым адресом. Если да, определение исходящего интерфейса и числа транзитных участков. * Проверка того, не является ли место назначения широковещательным адресом; если да, проверка того, разрешено ли широковещание. * Выполнение всех необходимых пакету манипуляций IPSec, таких, как шифрование. * Проверка наличия каких-нибудь правил фильтрования, которые изменили бы пакет или предотвратили его отправку. Если размер пакета не больше максимального размера пакета для исходящего интерфейса, вычисление контрольной суммы и вызов выходной процедуры интерфейса. Если размер пакета больше максимального размера пакета для исходящего интерфейса, разделение пакета на фрагменты и поочередная их отправка. Шаг маршрутизации мы рассмотрим более подробно. Во-первых, если в качестве параметра не передано ссылки на маршрут, временно используется внутренняя струк- структура для ссылки маршрутизации. Структура маршрута, которая передается от вызы- вызывающего, проверяется на предмет того, что это маршрут до того самого места назначе- назначения и что он по-прежнему действителен. Если любая из этих проверок неудачна, старый маршрут освобождается. После этих проверок, если маршрута нет, вызывается rtalloc_ign() для назначения маршрута. Возвращенный маршрут включает указатель на исходящий интерфейс. Информация интерфейса включает максимальный размер пакета, флаги, включая возможности широковещания и многоадресной рассылки, и процедуру вывода. Если маршрут помечен флагом RTF_GATEWAY, адрес маршрути- маршрутизатора следующего транзитного участка дан в маршруте; в противном случае место назначения пакета является местом назначения следующего транзитного участка.
13.3. Протокол Интернета (IP) 609 Если маршрутизацию следует проигнорировать из-за опции MSG_DONTROUTE (см. раздел 11.1) или опции SO_DONTROUTE, в этом случае находится непосредст- непосредственно подсоединенная сеть, в которой находится место назначения; если непосред- непосредственно подсоединенной сети нет, возвращается ошибка. После обнаружения исхо- исходящего интерфейса и места назначения следующего транзитного участка для отправки пакета информации достаточно. Как описано в главе 12, выходная процедура интерфейса обычно проверяет адрес назначения и помещает пакет в выходную очередь, возвращая ошибку лишь если интерфейс выключен, выходная очередь заполнена или непонятен адрес назначения. Ввод В главе 12 мы описали прием пакета сетевым интерфейсом и помещение пакета во входную очередь для соответствующего протокола. Потом обработчик сетевого интерфейса планирует для запуска протокол, установив соответствующий бит в слове состояния сети и назначив выполнение сетевого потока. Процедура ввода IPv4 вызы- вызывается через это программное прерывание, когда сетевые интерфейсы получают сооб- сообщения для протокола IPv4. Входная процедура, ip_input(), вызывается с mbuf, который содержит пакет для обработки. Удаление пакета из очереди и вызовы входной процедуры осуществляются вызовом сетевым потоком netisr_dispatch(). Пакет обрабатывается одним из четырех способов: он передается в качестве ввода протоколу вышележащего уровня, он сталкивается с ошибкой, которая сообщается обратно источнику, он удаля- удаляется из-за ошибки или пересылается на следующий транзитный участок на своем пути к месту назначения. В виде наброска шаги по обработке пакета на входе следующие. 1. Проверка того, что размер пакета по крайней мере равен размеру заголовка IPv4, и проверка непрерывности заголовка. 2. Проверка контрольной суммы заголовка пакета и уничтожение пакета, если там есть ошибка. 3. Проверка того, что пакет имеет по крайней мере такой же размер, какой указан в заго- заголовке, и уничтожение пакета, если это не так. Отрезание заполнения от конца пакета. 4. Выполнение всех функций фильтрования или защиты, необходимых ipfw или IPSec. 5. Обработка опций заголовка. 6. Проверка того, предназначен ли пакет для данного хоста. Если да, продолжить обработку пакета. Если нет и если осуществляется маршрутизация, попытаться переправить пакет. В противном случае уничтожить пакет. 7. Если пакет был фрагментирован, сохранение его до тех пор, пока не будут получе- получены и повторно собраны все фрагменты, или до тех пор, пока он не устареет. 8. Передача пакета входной процедуре следующего вышележащего протокола.
610 Глава 13. Сетевые протоколы Когда входящий пакет передается входной процедуре, он сопровождается указате- указателем на интерфейс, через который пакет был получен. Эта информация передается сле- следующему протоколу, функции пересылки или функции сообщения об ошибке. Если обнаружена какая-нибудь ошибка и сообщена отправителю пакета, исходный адрес сообщения об ошибке будет установлен в соответствии с местом назначения пакета и входящим интерфейсом. Решение о том, принимать ли полученный пакет для локальной обработки прото- протоколом вышележащего уровня, не такое простое, как можно было бы подумать. Если у хоста несколько адресов, пакет принимается, если место его назначения совпадает с любым из этих адресов. Если любая из подключенных сетей поддерживает широко- широковещание, а место назначения является широковещательным адресом, пакет также при- принимается. Входная процедура IPv4 использует простую и эффективную схему для обнаруже- обнаружения входной процедуры для принимающего протокола входящего пакета. Поле прото- протокола в пакете имеет размер 8 битов; поэтому есть 256 возможных протоколов. Опреде- Определено или реализовано менее 256 протоколов, а переключение протокола Интернета имеет гораздо меньше 256 элементов. Поэтому ip_input() использует 256-элементный отображающий массив для отображения номера протокола на элемент переключения протокола принимающего протокола. В качестве каждого из элементов в массиве первоначально установлен индекс непосредственного элемента IP в переключении протокола. Затем для каждого протокола с отдельной реализацией в системе в качестве соответствующего элемента отображения устанавливается индекс протокола в пере- переключателе протокола IP. Когда получен пакет, IP просто использует поле протокола в качестве индекса в отображающем массиве и вызывает входную процедуру соответ- соответствующего протокола. Пересылка Реализации IPv4 традиционно спроектированы для использования либо хостами, либо маршрутизаторами, а не обоими сразу. То есть система была либо конечной точкой для пакетов (в качестве источника или места назначения), либо маршрутизатором (который пересылает пакеты между хостами в различных сетях, но протоколы вышеле- вышележащих уровней использует лишь для функций сопровождения). Традиционные систе- системы хостов не содержат в себе функции пересылки пакетов; вместо этого, если они по- получают пакеты, адресованные не им, они просто уничтожают пакеты. 4.2BSD была первой распространенной реализацией, которая попыталась предоставить службы как хоста, так и маршрутизатора в обычной работе. У этого подхода есть преимущества и недостатки. Это означало, что хосты 4.2BSD, соединенные с несколькими сетями, могли служить в качестве маршрутизаторов также, как и в качестве хостов, снижая требования к аппаратному обеспечению выделенных маршрутизаторов. Первые маршрутизаторы были дорогими и не особенно мощными. Альтернативно существо-
13.3. Протокол Интернета (IP) 611 вание поддержки функции маршрутизатора в обычных хостах увеличивало вероят- вероятность того, что ошибки неправильного конфигурирования приводили к проблемам в подключенных сетях. Наиболее серьезная проблема должна была иметь дело с пере- пересылкой широковещательных пакетов из-за неправильного понимания места назначе- назначения пакета либо отправителем, либо получателем. В FreeBSD функции маршрутиза- маршрутизатора по пересылке пакетов по умолчанию отключены. Их можно включить во время работы с помощью системного вызова sysctl. Хосты, не сконфигурированные в качест- качестве маршрутизаторов, никогда не пытаются пересылать пакеты или возвращать сообще- сообщения об ошибках в ответ на неправильно направленные пакеты. В результате значитель- значительно меньшее число проблем неправильного конфигурирования способны вызывать син- синхронные и повторные широковещательные рассылки в локальной сети, называемые широковещательными штормами. Процедура пересылки IP-пакетов, полученных маршрутизатором, но предна- предназначенных для другого хоста, следующая. 1. Проверка того, что пересылка разрешена. Если нет, пакет уничтожается. 2. Проверка того, что адрес назначения разрешает пересылку. Пакеты, предназначен- предназначенные для сети 0, сети 127 (официальная сеть возвратной петли) или с недопустимы- недопустимыми сетевыми адресами, не могут пересылаться. 3. Сохранение максимум 64 октетов полученного сообщения на случай, если придется создавать в ответ сообщение об ошибке. 4. Определение маршрута, который должен использоваться для пересылки пакета. 5. Если исходящий маршрут использует тот же самый интерфейс, через который па- пакет был получен, и если хост-отправитель находится в той же самой сети, произво- производится отправка хосту-отправителю сообщения перенаправления ICMP. (ICMP опи- описан в разделе 13.8.) 6. Обработка любых обновлений IPSec, которые должны быть сделаны с заголовком пакета. 7. Вызов ip_output() для отправки пакета на место его назначения или на шлюз сле- следующего транзитного участка. 8. Если обнаружена ошибка, отправка сообщения ICMP об ошибке на исходный хост. Многоадресные передачи обрабатываются отдельно от остальных пакетов. Системы могут быть сконфигурированы в качестве групповых маршрутизаторов независимо от других функций маршрутизации. Групповые маршрутизаторы получают все входящие групповые пакеты и пересылают эти пакеты локальным получателям и членам групп в других сетях в соответствии с групповым членством и оставшимся числом транзит- транзитных участков во входящих пакетах.
612 Глава 13. Сетевые протоколы 13.4. Протокол управления передачей (TCP) Наиболее используемым протоколом набора протоколов Интернета является протокол управления передачей (Transmission Control Protocol - TCP) [Cerf & Kahn, 1974; Postel, 1981b]. TCP является надежным, ориентированным на соединение потоковым транс- транспортным протоколом, поверх которого располагается большинство прикладных прото- протоколов. Он включает несколько особенностей, которых нет в других транспортных и се- сетевых протоколах, описанных до сих пор. Явные и подтверждаемые инициализация и завершение соединения. * Надежная, упорядоченная доставка данных без дублирования. * Управление потоком. Внеполосное указание срочных данных. Избежание перегрузки. Из-за этих особенностей реализация TCP намного более сложна, чем реализации UDP и IP. Эти осложнения, вместе с преобладанием использования TCP, делают подробности реализации TCP как более критичными, так и более интересными, чем реализации более простых протоколов. На рис. 13.5 показан поток данных через со- соединение TCP. Мы начнем с исследования самого TCP, а затем продолжим с описания его реализации в FreeBSD. TCP-соединение можно представить как двунаправленный упорядоченный поток данных, передаваемых между двумя узлами. Данные могут передаваться в пакетах раз- различных размеров через разные промежутки времени - например, когда они используются для поддержки сеансов регистрации через сеть. Инициация и завершение потока являют- являются явными событиями в начале и конце потока и занимают положения в пространстве последовательностей потока таким образом, что их можно подтверждать тем же самым способом, как и данные. Номера последовательностей являются 32-разрядными числами из циклического пространства; т.е. сравнения делаются по модулю 232, поэтому ноль является следующим последовательным номером после 2-1. Номера последовательно- последовательностей для каждого направления начинаются с произвольного значения, которое называет- называется начальным номером последовательности, отправляемым в первоначальном пакете для соединения. Вслед за Bellovin [1996] реализация TCP выбирает начальный номер последовательности путем вычисления функции 4-элементного кортежа локального порта, внешнего порта, локального адреса, внешнего адреса, уникально идентифи- идентифицирующего соединение, а затем добавления небольшого смещения, основанного на теку- текущем времени. Этот алгоритм предотвращает имитацию TCP-соединений атакующим на основе угадывания следующего начального номера последовательности для соединения. Это должно также гарантировать то, что старый дублированный пакет не подойдет к про- пространству последовательностей текущего соединения.
13.4. Протокол управления передачей (TCP) 613 Вывод send (DATA) Пользователь Ввод recv (DATA) sos end() \copyin() Данные copy out () \s о receive () Ядро tcp_usr_send() tcp_output() PIP TCP ip_outpiit() у IP TCP Сетевой интерфейс \ ETHER IP TCP Данные Данные Данные Данные tcp_input() PIP IP TCP Данные ip_input() TCP Данные Сетевой интерфейс ETHER IP TCP Данные Прерывание от устройства Ethernet Рис. 13.5. Поток данных через TCP/IP-соединение поверх Ethernet. Обозначения: ETHER - заголовок Ethernet; PIP - псевдозаголовок IP; TCP - заголовок TCP; IF - интерфейс Каждый пакет TCP-соединения несет номер последовательности первого элемента данных и (за исключением момента установления соединения) подтверждение всех полученных непрерывных данных. Пакет TCP называется сегментом, поскольку он начинается в определенном месте пространства последовательностей и имеет опреде- определенную длину. Подтверждения указываются в виде следующего еще не полученного номера последовательности. Подтверждения являются кумулятивными и поэтому могут подтверждать данные, полученные более чем в одном (или в части одного) паке- пакете. Пакет может содержать или не содержать данные, но он всегда содержит номер по- последовательности следующего элемента данных для отправки. Управление потоком в TCP осуществляется посредством схемы скользящего окна. Каждый пакет с подтверждением содержит окно, которое представляет собой число октетов данных, которые получатель готов принять, начиная с номера последователь- последовательности в подтверждении. Окно является 16-разрядным полем, ограничивая по умолча- умолчанию окно 65 535 октетами; однако можно договориться на использование большего окна. Срочные данные обрабатываются аналогичным образом; если установлен флаг, указывающий на срочные данные, указатель на них используется в качестве положи-
614 Глава 13. Сетевые протоколы тельного смещения от номера последовательности пакета для указания размера срочных данных. Таким образом, TCP может отправить уведомление о срочных дан- данных, не отправляя всех промежуточных данных, даже если окно управления потоком не позволило бы отправить промежуточные данные. 0 15 Исходный порт 16 31 Порт назначения Номер последовательности Номер подтверждения Смещение данных Зарезерви- Зарезервировано и R G А С К Р S Н R S Т S Y N F I N Контрольная сумма Окно Указатель срочных данных Опции Заполнение Данные РИС. 13.6. Заголовок ТСР-пакета Полный заголовок TCP-пакета показан на рис. 13.6. Флаги включают SYN и FIN, обозначая инициацию (синхронизацию) и завершение соединения. Каждый из этих флагов занимает в пространстве последовательностей один номер. Таким образом, полное соединение состоит из SYN, нуля или более октетов данных и FIN, отправлен- отправленных каждым узлом и подтвержденных другим узлом. Дополнительные флаги указы- указывают, действительны ли поля подтверждения (АСК) и срочных данных (URG), и включают сигнал прерывания соединения (RST). Опции кодируются тем же спосо- способом, что и опции IP: опции нет-операции и конец-опций являются простыми октетами, а все другие опции включают тип и размер. Единственная опция в первоначальной спецификации TCP обозначает максимальный размер сегмента (пакета), который корреспондент хочет принять; эта опция используется лишь в ходе первоначального установления соединения. Было определено несколько других опций. Чтобы избежать путаницы, стандарт протокола разрешает использовать эти опции в пакетах данных, лишь если обе конечные точки включили их в ходе установления соединения. Состояния ТСР-соединения Механизмы установления соединения и завершения соединения TCP спроектированы для устойчивости. Они служат для обрамления данных, передаваемых в ходе соедине- соединения, таким образом, чтобы не только данные, но и их размеры сообщались надежно. Кроме того, процедура спроектирована для обнаружения старых соединений, которые не завершились правильно из-за аварии одного из узлов или потери возможности сете- сетевого соединения. Если обнаруживается такое полуоткрытое соединение, оно прерыва-
13.4. Протокол управления передачей (TCP) 615 ется. Хосты выбирают новые начальные номера последовательности для каждого соединения, чтобы уменьшить шансы того, что старый пакет может быть перепутан с текущим соединением. Обычная процедура установления соединения известна как трехстороннее руко- рукопожатие (three-way handshake). Каждый узел посылает другому SYN, и каждый в свою очередь подтверждает SYN другого своим АСК. На практике соединение обычно инициируется клиентом, пытающимся соединиться с сервером, прослуши- прослушивающим хорошо известный порт. Клиент выбирает номер порта и начальный номер последовательности и использует эти значения в первоначальном пакете с SYN. Сервер создает управляющий блок протокола для ожидающего соединения и отправ- отправляет пакет с начальным номером последовательности, SYN и АСК клиентского SYN. Клиент отвечает АСК для SYN-сервера, завершая установление соединения. Поскольку АСК первого SYN скомбинирован со вторым SYN, эта процедура требует трех пакетов, откуда и происходит название трехстороннее рукопожатие. Протокол по-прежнему работает правильно, если оба узла пытаются установить соединение одновременно, хотя установление соединения требует четырех пакетов. FreeBSD при инициализации соединения включает вместе с SYN три опции. Одна содержит максимальный размер сегмента, который система хочет принять [Jacobson et al., 1992]. Вторая из этих опций указывает значение масштабирования окна, выраженное в виде значения двоичного сдвига, что позволяет окну превысить 65 535 октетов. Если в ходе трехстороннего рукопожатия оба узла включат эту опцию, действуют оба значе- значения масштабирования; в противном случае значение окна остается в октетах. Третья опция является опцией отметки времени. Если эта опция отправляется в ходе установле- установления соединения в обоих направлениях, она будет также отправляться с каждым пакетом в ходе передачи данных. Поле данных опции отметки времени включает отметку време- времени, связанную с текущим номером последовательности, а также отражает в виде эха от- отметку времени, связанную с текущим подтверждением. Аналогично пространству после- последовательностей отметка времени использует 32-разрядное поле и модульную арифметику. Единица поля отметки времени не определена, хотя она должна находиться между 1 мил- миллисекундой и 1 секундой. Значение, отправляемое каждой системой, должно в ходе соединения монотонно не снижаться. FreeBSD использует значение тиков, которое уве- увеличивается со скоростью системного тактового генератора в герцах. Эти отметки времени могут использоваться для реализации определения времени обращения. Они служат также в качестве расширения пространства последовательностей для предотвращения приема старых дублированных пакетов; это расширение имеет значение, когда исполь- используются большое окно или путь с помехами, такой, как Ethernet. После установления соединения каждый узел включает в каждый пакет под- подтверждение и сведения об окне. Каждый может посылать данные в соответствии с окном, которое он получает от другого узла. Когда один конец отправляет данные, окно заполняется. Когда данные получаются другим узлом, могут быть отправлены подтверждения, так что отправитель может сбросить данные из своей очереди отправ-
616 Глава 13. Сетевые протоколы ки. Если получатель готов принять дополнительные данные, возможно из-за того, что принимающий процесс использовал предыдущие данные, он передвигает также окно управления потоком. Данные, подтверждения и обновления окна все могут объеди- объединяться в одно сообщение. Если отправитель не получает подтверждения в течение некоторого разумного време- времени, он повторно передает данные, которые, как он полагает, были потеряны. Дублирован- Дублированные данные уничтожаются получателем, но подтверждаются снова на случай, если повторная передача была вызвана потерей подтверждения. Если данные получены вне очереди, получатель обычно сохраняет внеочередные данные для использования, когда будет получен отсутствующий сегмент. Внеочередные данные не могут быть под- подтверждены, поскольку подтверждения являются кумулятивными. В Jacobson et al. [1992] был введен механизм избирательного подтверждения, но он в FreeBSD не реализован. Каждый узел может в любое время завершить передачу данных, отправив пакет с битом FIN. FIN представляет окончание данных (аналогично указателю конца файла). FIN подтверждается путем увеличения номера последовательности на 1. Соединение может продолжать передавать данные в другом направлении до тех пор, пока в этом направлении не будет отправлен FIN. Подтверждение FIN завершает соединение. Чтобы гарантировать синхронизацию при завершении соединения, узел, отправ- отправляющий последний АСК для FIN, должен сохранять свое состояние достаточно долго, чтобы любые повторно переданные пакеты FIN дошли до него или были уничтожены; в противном случае, если АСК был бы потерян, а повторно переданный FIN получен, получатель не смог бы повторить подтверждение. В качестве этого интервала произ- произвольно устанавливается промежуток времени, равный двум максимальным ожидае- ожидаемым временам жизни сегмента (известный как 2MSL). Табл. 13.1. Состояния ТСР-соединения Состояние Описание Состояния, использующиеся до установления соединения CLOSED Закрыто LISTEN Прослушивание в ожидании соединения SYN SENT Активно, отправлен SYN SYN RECEIVED Отправлен и получен SYN Состояние во время установленного соединения ESTABLISHED Установлено Состояния, использующиеся при инициировании отключения соединения удаленным узлом CLOSE WAIT Получен FIN, ожидание закрытия LAST АСК Получены FIN и закрытие; ожидание FIN АСК CLOSED Закрыто
13.4. Протокол управления передачей (TCP) 617 Табл. 13.1. Состояния TCP-соединения (Окончание) Состояния, использующиеся при инициировании отключения соединения локальным узлом FIN WAIT 1 Закрыто, отправлен FIN CLOSING Закрыто, сделан обмен FIN; ожидание FIN ACK FIN WAIT 2 Закрыто, FIN подтвержден; ожидание FIIN TIME WAIT Спокойное ожидание 2MSL* после закрытия CLOSED Закрыто *2MSL - два максимальных времени жизни сегмента. Модуль обработки ввода TCP и модули таймеров должны поддерживать состояние соединения в течение времени жизни этого соединения. Таким образом, кроме обра- обработки полученных через соединение данных модуль ввода должен обрабатывать флаги SYN и FIN и другие переходы состояний. Список состояний для одного конца ТСР- соединения приведен в табл. 13.1. На рис. 13.7 показан конечный автомат, составленный из этих состояний, события, вызывающие переходы, и действия во время переходов. Если соединение потеряно из-за аварии или тайм-аута на узле, но другим узлом оно по-прежнему рассматривается как установленное, тогда любые отправленные через соединение и полученные на другом конце данные вызовут обнаружение полуот- полуоткрытого соединения. При обнаружении полуоткрытого соединения принимающий узел отправляет пакет с флагом RST и номером последовательности, полученном от входящего пакета, чтобы оповестить о том, что соединения больше не существует. Переменные, описывающие последовательность Каждое TCP-соединение поддерживает в управляющем блоке TCP большой набор переменных состояния. Эта информация включает состояние соединения, таймеры, опции и флаги состояния, очередь, в которой хранятся внеочередные данные, и не- несколько переменных номеров последовательностей. Переменные последовательности используются для определения пространства последовательности отправки и получе- получения, включая текущее окно для каждого. Окно находится в диапазоне номеров после- последовательности данных, которые разрешено передавать в настоящий момент, от первого октета еще не подтвержденных данных вплоть до конца диапазона, который был пре- предоставлен в поле окна заголовка. Переменные, использованные в FreeBSD для опреде- определения окон, представляют собой надмножество переменных, использованных в специ- спецификации протокола [Postel, 1981b]. Окна отправки и приема показаны на рис. 13.8. Значения переменных последовательности перечислены в табл. 13.2. Область между sndjrna и sndjuna + snd_wnd известна как окно отправки. Данные для диапазона от sndjuna до sndjnax были отправлены, но еще не подтверждены и хранятся в буфере отправки сокета вместе с еще не переданными данными. Поле sndjixt обозначает следующий номер последовательности для отправки и увеличива-
618 Глава 13. Сетевые протоколы CLOSED ПассивноеОРЕЫ (созданиеТСВ) } г CLOSE (улалснисТСВ) LISTEN Получение SYN (отправка SYN. АСК) SYN RECEIVED SEND (pTnpaBKaSYN) Получение SYN (отправка АСК) Активное OPEN (созданиеТСВ, отправка SYN) SYN SENT Получение АСК на SYN (нет действи1 CLOSE (отправка FIN) ESTABLISHED FIN WAIT I Получение АСК на FIN (нет действий) FIN WAIT 2 CLOSE (отправка FIN) Получение SYN, АСК (отправка АСК) Получение FIN (отправка АСК) CLOSE WAIT Получение FIN (отправка АСК) CLOSING Получение FIN (send АСК) CLOSE (отправка FIN) LAST АСК TIME WAIT Получение АСК на FIN (нет действий) Тайм-аутв 2MSL (удалениеТСВ) Получение АСК на FIN (нет действий) CLOSED РИС. 13.7. Диаграмма состояний TCP. Обозначения: ТСВ - управляющий блок TCP; 2MSL - двойное максимальное время жизни сегмента Окно отправки- _Не подтвержденные данные Используемое _ "окно отправки snd una sndjixU snd max sndjiina + snd_wnd Пространство последовательности отправки receive window rev nxt rcvjbxt + rcv_\vnd Пространство последовательности приема Рис. 13.8. Пространство последовательности TCP
13.4. Протокол управления передачей (TCP) 619 Табл. 13.2. Переменные последовательности TCP Переменная Описание snd una Наименьший еще не подтвержденный номер последовательности snd nxt Следующий номер последовательности для отправки snd wnd Число октетов данных, которые получит узел, начиная с sndjuna snd max Наибольший отправленный номер последовательности rev nxt Следующий ожидающийся номер последовательности для приема rev wnd Число октетов после revjixt, которые могут быть приняты rev adv Последний октет приемного окна, сообщенный другому узлу ts recent Последняя отметка времени, полученная от другого узла ts recent age Время, когда было получено ts_recent ется по мере отправки данных. Область от sndjixt до sndjuna + sndjwnd является ос- оставшейся частью окна, годной для использования, и ее размер определяет, можно ли отправлять дополнительные данные. Значения sndjixt и sndjnax обычно сохраняются вместе, за исключением повторной передачи TCP. Область между revjixt и revjixt + rcvjwnd известна как приемное окно. Эти переменные используются в выходном модуле для принятия решения о том, можно ли отправлять данные, а во входном модуле для принятия решения о том, можно ли принять полученные данные. Когда получатель обнаруживает, что пакет принять нельзя из-за того, что данные выходят за пределы окна, он уничтожает пакет, но отправляет копию своего последнего подтверждения. Если пакет содержал старые данные, первое подтверждение, возможно, было потеряно, и поэтому его нужно по- повторить. Подтверждение включает также обновление окна, синхронизацию состояния отправителя с состоянием получателя. Если для соединения применяется опция отметок времени, тесты приемлемости входящего пакета подкрепляются проверками отметок времени. Каждый раз, когда входящий пакет принимается в качестве следующего ожидавшегося пакета, его отмет- отметка времени записывается в поле tsj-ecent управляющего блока протокола TCP. Если входящий пакет включает отметку времени, она сравнивается с последней полученной отметкой времени. Если отметка времени меньше, чем предыдущее значение, пакет уничтожается как старый дубликат, а в ответ отправляется текущее подтверждение. Таким образом, отметка времени служит в качестве расширения номера последова- последовательности, позволяя избежать случайного принятия старого дубликата, когда окно большое или номера последовательности могут быстро использоваться повторно. Однако из-за степени детализации значения отметки времени временная отметка, по- полученная более 24 дней назад, не может сравниваться с новым значением, и этот тест
620 Глава 13. Сетевые протоколы пропускается. Текущее время записывается, когда ts_recent обновляется из входящей отметки времени для выполнения этого теста. Конечно, соединения редко бывают без- бездействующими в течение более 24 дней. 13.5. Алгоритмы TCP Теперь, представив TCP, его конечный автомат и его пространство последовательности, мы можем начать исследовать реализацию протокола в FreeBSD. Несколько аспектов реализации протокола зависят от общего состояния соединения. Состояние соедине- соединения TCP, состояние вывода и изменения состояния зависят от внешних событий и тай- таймеров. Обработка TCP происходит в ответ на одно из трех событий. 1. Запроса от пользователя, такого, как отправка данных, удаление данных из приемного буфера сокета или открытия или закрытия соединения. 2. Приема пакета для соединения. 3. Истечения времени таймера. Эти события обрабатываются в процедурах tcp_usr_send(), tcp_input() и наборе процедур таймера. Каждая процедура обрабатывает текущее событие и делает все необходимые изменения в состоянии соединения. Затем для любого перехода, которому может потребоваться отправка пакета, вызывается процедура tcp_output(), чтобы сделать любой необходимый вывод. Критерии для отправки пакета с данными или управляющей информацией сложны, и поэтому политика отправки TCP является самой интересной и важной частью реали- реализации протокола. Например, в зависимости от параметров управления состоянием и потоком для соединения любое из следующего может позволить отправить данные, которые не могли быть отправлены ранее. * Пользовательский вызов отправки помещает в очередь отправки новые данные. Прием обновления окна от другого узла. * Истечение срока таймера повторной передачи. Истечение срока таймера обновления окна. Кроме того, процедура tcpoutputQ может решить отправить пакет с управляющей информацией, даже если данные не могут быть отправлены, по любой из следующих причин. * Изменение в состоянии соединения (например, запрос открытия, запрос закрытия). Получение данных, которые должны быть подтверждены. * Изменение приемного окна из-за удаления данных из приемной очереди.
13.5. Алгоритмы TCP 621 Запрос отправки со срочными данными. Разрыв соединения. Большинство этих решений мы более подробно рассмотрим после описания использующихся состояний и таймеров. Мы начнем с алгоритмов, использующихся для согласования во времени, установки соединения и отключения; они распределены по разным местам кода. Мы продолжим описание обработки нового ввода и обзора обработки и алгоритмов вывода. Таймеры В отличие от сокета UDP, TCP-соединение поддерживает значительный объем информации о состоянии, и благодаря этому состоянию некоторые операции могут выполняться асинхронно. Например, из-за управления потоком данные могут не быть отправленными немедленно при предоставлении их процессом. Требование надежной доставки предполагает, что данные должны сохраняться после первой их передачи, чтобы при необходимости их можно было отправить повторно. Чтобы предотвратить зависание протокола в случае потери пакетов, каждое соединение поддерживает набор таймеров, использующихся для восстановления после потерь или сбоев другого узла. Эти таймеры хранятся в управляющем блоке протокола для соединения. Ядро предо- предоставляет службу таймеров через набор процедур calloutQ. Модуль TCP может зареги- зарегистрировать с помощью службы callout вплоть до пяти процедур тайм-аутов, как пока- показано в табл. 13.3. У каждой процедуры имеется свое соответствующее время, когда она будет вызвана. В предыдущих версиях BSD тайм-ауты обрабатывались процедурой tcp_slowtimo(), которая вызывалась каждые 500 миллисекунд, а затем при необходимо- необходимости выполняла обработку таймера. Использование службы таймера ядра имеет двой- двойную выгоду. Во-первых, оно является более точным, поскольку каждый таймер можно обрабатывать независимо, во-вторых, имеет меньшие издержки, поскольку процедуры не вызываются, если это не является абсолютно необходимым. Табл. 13.3. Процедуры таймеров TCP Процедура Тайм-аут Описание tcp_timer_2msl 60 с Ожидание при закрытии tcp_timer_keep 75 с Отправляет дежурное сообщение или удаляет бездействующее соединение tcp_timer_persist 5-60 с Форсирует сохранение соединения tcp_timer_rexmt 3 тика - 64 с Вызывается, когда нужна повторная передача tcp_timer_delack 100 мс Отправляет другому узлу отложенное подтверждение
622 Глава 13. Сетевые протоколы Для обработки вывода используются два таймера. Каждый раз при отправке данных по соединению запускается таймер повторной передачи (tcp_rexmt) путем вызова callout_reset(), если он уже не действует. Когда все новые данные подтвержде- подтверждены, таймер останавливается. Если таймер истекает, самые старые неподтвержденные данные отправляются повторно (по крайней мере, один полный пакет) и таймер запус- запускается повторно с большим значением. Скорость, с которой увеличивается значение таймера (откат таймера), определяется по таблице коэффициентов, предусматри- предусматривающей экспоненциальное увеличение значений тайм-аутов вплоть до максимума. Другим таймером, использующимся для поддержания выходного потока, является таймер сохранения (persist timer) (tcp_timer_persist()). Этот таймер защищает от друго- другого вида потерь пакетов, которые могут вызвать затор соединения: потерю обновления окна, которое разрешило бы отправку дополнительных данных. Каждый раз, когда данные готовы к отправке, но окно отправки для этого слишком маленькое (нулевое или меньше приемлемого размера) и неподтвержденных данных нет (таймер повторной отправки не установлен), запускается таймер сохранения. Если до истече- истечения времени таймера обновления окна не получено, процедура отправляет такой сег- сегмент, который допускает окно отправки. Если этот размер равен нулю, она отправляет зондирование окна (единственный октет данных) и повторно запускает таймер сохра- сохранения. Если обновление окна было потеряно в сети или если получатель проигнориро- проигнорировал отправку окна обновления, подтверждение будет содержать текущие сведения об окне. С другой стороны, если получатель по-прежнему не способен принимать допол- дополнительные данные, он должен отправить подтверждение для предыдущих данных с по-прежнему закрытым окном. Закрытое окно могло бы существовать бесконечно; например, получатель мог бы быть клиентом сетевой регистрации, а пользователь мог остановить вывод терминала и уйти на обед (или в отпуск). Третьим таймером, используемым TCP, является дежурный таймер (tcp_timer_keepQ). У дежурного таймера две различные цели на разных фазах соединения. В ходе установле- установления соединения этот таймер ограничивает время для завершения трехстороннего рукопо- рукопожатия. Если таймер истекает в ходе установления соединения, оно закрывается. После установления соединения дежурный таймер отслеживает бездействующие соединения, которые могли бы больше не существовать на другом узле вследствие разъединения сети или аварии. Если установлена опция уровня сокета и соединение было бездействующим с момента последнего дежурного тайм-аута, процедура таймера отправит дежурный пакет (keepalive packet), имеющий целью получить от другого конца TCP либо подтверждение, либо сброс (RST). Если получен сброс, соединение буцет закрыто; если после нескольких попыток не буцет получено никакого ответа, соединение удаляется. Это средство спроек- спроектировано для того, чтобы сетевые серверы могли избегать «подвисших» срединений, когда клиент исчезает, не закрыв соединение. Дежурные пакеты не являются явной особенно- особенностью протокола TCP. Пакеты, использующиеся для этой цели FreeBSD, устанавливают номер последовательности на единицу меньше, чем sndjuna, который должен получить подтверждение от другого узла, если соединение по-прежнему существует.
13.5. Алгоритмы TCP 623 Четвертый TCP-таймер известен как таймер 2MSL («двойного времени жизни сегмен- сегмента»). TCP запускает этот таймер, когда соединение завершено путем отправки подтвержде- подтверждения для FIN (из FIN_WAIT_2) или получено АСК для FIN (из состояния CLOSING, когда отправляющая сторона уже закрыта). При этих условиях отправитель не знает, было ли по- получено подтверждение. Если FIN отправляется повторно, желательно, чтобы осталось дос- достаточное состояние, чтобы повторить подтверждение. Поэтому, когда ТСР-соединение входит в состояние TIME_WAIT, запускается таймер 2MSL; когда таймер истекает, управ- управляющий блок удаляется. Если переданный повторно FIN получен, посылается другой АСК, и таймер запускается повторно. Чтобы предотвратить блокирование из-за этой задержки закрытия процессом соединения, любой запрос закрытия возвращается успешно без ожи- ожидания процессом таймера. Таким образом, управляющий блок протокола может продол- продолжать свое существование даже после того, как дескриптор сокета был закрыт. Кроме того, FreeBSD запускает таймер 2MSL, когда осуществляется вход в состояние FIN_WAIT_2 после закрытия пользователя; если соединение бездействует до истечения времени тай- таймера, оно буцет закрыто. Поскольку пользователь уже закрылся, через такое соединение в любом случае данные не могут быть приняты. Этот таймер устанавливается, поскольку некоторые другие реализации TCP (ошибочно) не отправляют FIN для только прини- принимающего соединения. Соединения с такими хостами бесконечно оставались бы в состоя- состоянии FIN_WAIT_2, если бы у системы не было тайм-аута. Последним таймером является tcp_timer_delack(), который обрабатывает отложен- отложенные подтверждения. Это будет описано в разделе 13.6. Оценка времени обращения Когда соединение должно пересечь медленные сети, теряющие пакеты, важным реше- решением, определяющим пропускную способность соединения, является значение, которое должно быть использовано, для установки таймера повторной передачи. Если это значение слишком большое, поток данных в соединении остановится на излишне длительное время перед повторной отправкой потерянного пакета. Второй интервал времени обращения потребуется отправителю для получения подтверждения повторно переданного сегмента и обновления окна, дающего ему возможность отправлять новые данные. (В удачном случае будет потерян лишь один сегмент, а подтверждение будет включать другие сегменты, которые были отправлены.) Однако, если время тайм-аута слишком маленькое, пакеты будут передаваться повторно без нужды. Если причиной медлительности сети или потери пакетов является перегрузка, ненужная повторная передача лишь осложнит проблему. Традиционным решением этой проблемы в TCP является оценка отправителем времени обращения (round-trip time - rtt) для соеди- соединения путем измерения времени, необходимого для получения подтверждений для отдельных сегментов. Система поддерживает оценку времени обращения как сглажен- сглаженного скользящего среднего, srtt [Postel, 1981b], с использованием srtt = (а Ч srtt) + (A - а) Ч rtt).
624 Глава 13. Сетевые протоколы Кроме сглаженной оценки времени обращения TCP хранит сглаженную дис- дисперсию (вычисляемую как табличная поправка, чтобы избежать вычисления квадрат- квадратного корня в ядре). Он использует для времени обращения значение а 0,875 и соответ- соответствующий сглаживающий фактор 0,75 для дисперсии. Эти значения были выбраны частично таким образом, чтобы система могла вычислить сглаженные средние, ис- используя операции сдвига для значений с фиксированной точкой, а не с плавающей точкой, поскольку на многих аппаратных архитектурах использование арифметики с плавающей точкой обходится дорого. Затем в качестве значения начального тайм- аута повторной передачи устанавливается текущее сглаженное время обращения плюс учетверенная сглаженная дисперсия. Этот алгоритм значительно более эффективен на путях с большими задержками и с небольшой дисперсией задержки, таких, как спутни- спутниковые линии, поскольку он динамически вычисляет фактор BETA [Jacobson, 1988]. Для простоты переменные в управляющем блоке протокола TCP допускают измерение времени обращения лишь для одного значения последовательности за раз. Это ограничение мешает точной оценке времени, когда окно большое; можно рассчи- рассчитать время лишь для одного пакета на окно. Однако, если опция отметок времени TCP поддерживается обоими узлами, отметки времени отправляются с каждым пакетом и возвращаются с каждым подтверждением. В этом случае оценки времени обращения могут быть получены с каждым новым подтверждением; качество сглаженного сред- среднего и дисперсии таким образом улучшается, и система может более быстро отвечать на изменения сетевых условий. Установление соединения Имеются два способа, какими может быть установлено новое TCP-соединение. Актив- Активное соединение инициируется вызовом connect, тогда как пассивное соединение созда- создается, когда слушающий сокет получает запрос соединения. Мы рассмотрим каждый из них по очереди. Начальные шаги попытки активного соединения сходны с действиями, предпринимае- предпринимаемыми при создании сокета UDP. Процесс создает новый сокет, приводя к вызову проце- процедуры tcp_attachQ. TCP создает управляющий блок протокола inpcb, а затем создает допол- дополнительный управляющий блок (структуру tcpcb), как описано в разделе 13.1. Некоторые из параметров управления потоком в tcpcb инициализируются в это время. Если процесс явным образом привязывает к соединению адрес или номер порта, действия идентичны действиям для сокета UDP Затем вызов tcp_connect() инициирует фактическое соединение. Первым шагом является установка ассоциации с injpcbconnectQ, опять-таки идентично этому шагу в UDP. Для использования в конструировании каждого выходного пакета созда- создается шаблон заголовка пакета. Из прототипа номера последовательности выбирается начальный номер последовательности, который затем увеличивается на значительную величину. Потом сокет помечается с помощью soisconnectingQ, состояние TCP устанавли- устанавливается в TCPS_SYN_SENT, дежурный таймер устанавливается (в 75 секунд) для ограниче- ограничения продолжительности попытки соединения, и в первый раз вызывается tcpoutputQ.
13.5. Алгоритмы TCP 625 Модуль обработки вывода tcp_output() использует набор управляющих флагов пакета, индексируемых по состоянию соединения, чтобы определить, какие управ- управляющие флаги должны быть установлены в каждом состоянии. В состоянии TCPS_SYN_SENT отправляется флаг SYN. Поскольку ей нужно отправить управ- управляющий флаг, система отправляет пакет немедленно, используя только что скон- сконструированный прототип и включив текущие параметры управления потоком. Пакет обычно содержит три поля опций: опцию максимального размера сегмента, опцию масштабирования окна и опцию отметки времени (см. раздел 13.4). Опция максималь- максимального размера сегмента сообщает наибольший размер сегмента, который хочет прини- принимать TCP. Чтобы вычислить это значение, система находит маршрут до места назначе- назначения. Если маршрут указывает максимальный блок передачи (MTU), система использу- использует это значение после разрешения заголовков пакетов. Если соединение до места на- назначения в локальной сети, используется максимальный блок передачи исходящего сетевого интерфейса, возможно округленный в сторону уменьшения до кратного раз- размеру кластера mbuf для эффективности буферирования. Если место назначения не является локальным, а про промежуточный путь ничего не известно, используется размер сегмента по умолчанию E12 октетов). В более ранних версиях FreeBSD многие из важных переменных, относящихся к соединениям TCP, такие, как MTU пути между двумя конечными точками, и данные, использующиеся для управления соединением, содержались в корневом элементе, ко- который описывал соединение, и в элементе маршрутизации в виде набора метрик маршрута. Для централизации всей этой информации в одном удобном для нахожде- нахождения места был разработан кеш хоста TCP таким образом, чтобы информация, которая была собрана для одного соединения, могла быть повторно использована при откры- открытии нового соединения с той же самой конечной точкой. Данные, записываемые для со- соединения, показаны в табл. 13.4. Все переменные, сохраненные в элементе кеша хоста, описаны в различных частях последующих разделов данной главы тогда, когда они имеют отношение к нашему обсуждению того, как TCP управляет соединением. Табл. 13.4. Метрики кеша хоста TCP Переменная Описание rmxjntu MTU для данного пути nnxjssthresh Ограничение буфера исходящего шлюза nnx_rtt Оценка времени обращения rmx_rttvar Оценка дисперсии rtt nnxjbandwidth Оценка полосы пропускания rmx_c\vnd Окно перегрузки rmx_sendpipe Произведение исходящей пропускной способности на задержку rmxj'ecvpipe Произведение входящей пропускной способности на задержку
626 Глава 13. Сетевые протоколы Каждый раз при открытии нового соединения делается вызов tcp_hc_get() для на- нахождения всех сведений о прошлых соединениях. Если существует элемент в кеше для целевой конечной точки, TCP использует кешированную информацию для того, чтобы принимать более обоснованные решения об управлении соединением. Когда соедине- соединение закрывается, кеш хоста обновляется соответствующей информацией, которая была обнаружена в ходе соединения между двумя хостами. У каждого элемента хоста есть время жизни по умолчанию в один час. Каждый раз, когда осуществляется доступ к или его обновление, восстанавливается время его жизни в один час. Каждые пять минут вызывается процедура tcp_hc_purge(), чтобы очистить все элементы, у которых истек срок. Очистка от старых элементов гарантирует, что кеш хоста не становится слишком большим и что он всегда будет содержать относительно свежие данные. TCP может использовать определение MTU пути, как описано в Mogul & Deering [1990]. Определение MTU пути является процессом, посредством которого система зондирует сеть, чтобы обнаружить, каким является максимальный блок передачи в определенном маршруте между двумя узлами. Он делает это путем отправки пакетов с установленным в каждом пакете IP флагом не фрагментировать. Если пакет на своем пути до места назначения сталкивается с участком, на котором его пришлось бы фрагментировать, он уничтожается промежуточным маршрутизатором, а отправителю возвращается ошибка. Сообщение об ошибке содержит пакет максимального размера, который примет участок. Эта информация записывается в кеш хоста TCP для соответ- соответствующей конечной точки, и предпринимается попытка передачи с меньшим MTU. После установления соединения, когда достаточное количество пакетов прошли через сеть для установления TCP-соединения, исправленный MTU, записанный в кеш хоста, подтверждается. Пакеты будут продолжать передаваться с установленным флагом не фрагментировать таким образом, что, если путь к узлу изменится и у этого пути будет даже еще меньший MTU, будет записан этот меньший MTU. FreeBSD в настоящее время не имеет способа увеличения размера MTU при изменении маршрута. Когда соединение открывается впервые, таймер повторной передачи устанавлива- устанавливается в значение по умолчанию F секунд), поскольку информация о времени обраще- обращения пока еще недоступна. Если повезет, ответный пакет будет получен от места на- назначения соединения до того, как истечет время таймера повторной передачи. Если нет, пакет передается повторно, и таймер повторной передачи запускается снова с большим значением. Если ответа не получено до истечения дежурного таймера, попытка соединения прерывается с ошибкой «Тайм-аут попытки соединения». Однако, если получен ответ, проверяется его соответствие исходящему запросу. Он должен подтверждать SYN, который был отправлен, и должен включать SYN. Если оба усло- условия выполнены, инициализируются переменные последовательностей, и состояние соединения переходит в TCPS_ESTABLISHED. Если в ответе присутствует опция макси- максимального размера сегмента, в качестве максимального размера сегмента для соединения устанавливается минимальное из значений предложенного размера и максимального блока передачи для исходящего интерфейса; если эта опция отсутствует, записывается
13.5. Алгоритмы TCP 627 значение по умолчанию E12 байтов). В управляющем блоке TCP устанавливается флаг TF_ACKNOW, прежде чем вызвать процедуру вывода, чтобы немедленно под- подтвердить SYN. Теперь соединение готово к передаче данных. События, которые происходят, когда соединение создается при помощи пассивно- пассивного открытия, отличаются от описанных. Создается сокет, и к нему как прежде привязы- привязывается адрес. Затем сокет помечается вызовом listen как желающий принимать соеди- соединения. Когда для сокета TCP в состоянии TCPS_LISTEN прибывает пакет, создается новый сокет с помощью sonewconnQ, который вызывает процедуру tcp_usr_attach() для создания управляющих блоков протокола для нового сокета. Новый сокет помеща- помещается в очередь частичных соединений, возглавляемых слушающим сокетом. Если пакет содержит SYN и является приемлемым в других отношениях, связь нового сокета привязывается, инициализируется как последовательный номер отправки, так и приемный последовательный номер, а состояние соединения переходит в TCPS_SYN_RECEIVED. Дежурный таймер устанавливается, как раньше, а после ус- установки TF_ACKNOW для форсирования подтверждения SYN вызывается процедура вывода; также отправляется исходящий SYN. Если этот SYN подтверждается долж- должным образом, новый сокет перемещается из очереди частичных соединений в очередь полностью установленных соединений. Если владелец прослушивающего сокета нахо- находится в состоянии сна в вызове accept или выполняет select, сокет будет указывать, что доступно новое соединение. В конечном счете сокет снова готов к отправке данных. Ко времени завершения вызова accept может быть получено и подтверждено вплоть до одного окна данных. KemSYN Одной из проблем в предыдущих реализациях TCP было то, что для программ зло- злоумышленников имелась возможность затопить систему пакетами SYN, препятствуя выполнению полезной работы по обслуживанию любых настоящих соединений. Эта разновидность атак отказа в обслуживании стала обычной в ходе коммерциализации Интернета в конце 1990-х гг. Чтобы бороться с этой атакой, был введен кеш синхрони- синхронизации (syncache), чтобы эффективно сохранять и, возможно, удалять пакеты SYN, которые не приводят к установлению действительных соединений. Кеш синхрониза- синхронизации управляет трехсторонним рукопожатием между локальным сервером и соеди- соединяющимся узлом. Когда для сокета, находящегося в состоянии LISTEN, получен пакет SYN, модуль TCP пытается добавить для пакета новый элемент кеша синхронизации, используя процедуру syncache_add(). Если в полученном пакете есть какие-нибудь данные, они в это время не подтверждаются. Подтверждение данных использовало бы системные ресурсы, и атакующий мог бы израсходовать эти ресурсы, затопив систему пакетами SYN, содержащими данные. Если этот SYN раньше не встречался, в хеш-таблице соз- создается новый элемент, основанный на внешнем адресе пакета, внешнем порте, локаль-
628 Глава 13. Сетевые протоколы ном порте сокета и маске. Модуль кеша синхронизации отвечает на SYN посредством SYN/ACK и устанавливает таймер на новый элемент. Если кеш синхронизации со- содержит элемент, подходящий полученному пакету, подразумевается, что первоначаль- первоначальный SYN/ACK не был получен инициировавшим соединение узлом, и отправляется другой SYN/ACK, а таймер кеша синхронизации устанавливается снова. Ограничение на число пакетов SYN, которые могут быть отправлены соединяющимся узлом, не ус- установлено. Любое ограничение не соответствовало бы TCP RFC и могло бы мешать со- соединениям через сети с потерями. Отключение соединения TCP-соединение является симметричным и полнодуплексным, поэтому любая сторона может инициировать разъединение независимо. Пока одно направление соединения может передавать данные, соединение остается открытым. Сокет может указать, что он завершил отправку данных, с помощью системного вызова shutdown, который при- приводит к вызову процедуры tcp_usr_shutdown(). Ответом на этот запрос является изме- изменение состояния соединения; оно меняется с ESTABLISHED в FIN_WAIT_1. Следующий вызов вывода отправит FIN, обозначая конец файла. Принимающий сокет перейдет в состояние CLOSE_WAIT, но может продолжить отправлять данные. Процедура может отличаться, если процесс просто закрывает сокет. В этом случае FIN отправля- отправляется немедленно, но если получены новые данные, они не могут быть доставлены. Обычно протоколы более высокого уровня завершают свои собственные транзакции, так что обе стороны знают, когда закрываться. Однако, если они этого не знают, TCP должен отвергать новые данные. Он делает это путем отправки пакета с установлен- установленным флагом RST, если получены новые данные после того, как пользователь закрылся. Если в буфере отправки сокета остаются данные, когда осуществляется close, TCP обычно пытается их доставить. Если была установлена опция сокета SO_LINGER с нулевым временем задержки, буфер отправки просто сбрасывается; в противном случае процессу пользователя разрешено продолжить и протокол ожидает завершения доставки. При этих условиях сокет помечается битом состояния SS_NOFDREF (no file- descriptor reference - нет ссылок на дескрипторы файлов). Завершение передачи данных и завершающее закрытие могут иметь место позже в произвольный момент времени. Когда TCP в конечном счете завершает соединение (или разрывает из-за тайм-аута или другой неудачи), он вызывает tcp_close(). Управляющие блоки протокола и другие динамически выделяемые структуры освобождаются в это время. Сокет также освобождается, если был установлен флаг SS_NOFDREF. Таким образом, сокет продолжает существовать до тех пор, пока на него ссылается либо дескриптор файла, либо управляющий блок протокола.
13.6. Обработка ввода TCP 629 13.6. Обработка ввода TCP Хотя обработка ввода TCP более сложна, чем обработка ввода UDP, предшествующие разделы предоставили нам основу, которая нужна для исследования реальной работы. Как всегда, входная процедура вызывается с параметрами void tcp_input( struct mbuf *msg, int offO); Первые несколько шагов, возможно, начинают казаться знакомыми. 1. Найти TCP-заголовок в полученной IP дейтаграмме. Убедиться, что пакет вмещает по крайней мере TCP-заголовок, и использовать при необходимости m_pullupQ, чтобы сделать его непрерывным. 2. Вычислить размер пакета, установить псевдозаголовок IP и контрольную сум- сумму заголовка и данных TCP. Уничтожить пакет, если контрольная сумма непра- неправильная. 3. Проверить размер TCP-заголовка; если он больше минимального заголовка, убе- убедиться, что весь заголовок является непрерывным. 4. Найти управляющий блок протокола для соединения с указанным номером порта. Если его нет, отправить пакет, содержащий флаг сброса RST, и уничтожить пакет. 5. Проверить, прослушивает ли сокет соединения; если да, следовать процедуре, опи- описанной для установления пассивного соединения. 6. Обработать все опции TCP из заголовка пакета. 7. Очистить время бездействия для соединения и установить обычное значение сторожевого таймера. В этот момент были сделаны обычные проверки, и мы готовы иметь дело с данны- данными и управляющими флагами в полученном пакете. Имеется по-прежнему много про- проверок целостности, которые нужно сделать в ходе обычной обработки; например, флаг SYN должен присутствовать, если мы все еще устанавливаем соединение, и не должен присутствовать, если соединение было установлено. Мы опустим большинство этих проверок из нашего обсуждения, но тесты важны для предотвращения создания пута- путаницы и возможной порчи данных неверными пакетами. Следующим шагом является проверка того, является ли TCP-пакет приемлемым в соответствии с окном приема. Важно выполнить этот шаг до проверки управляющих флагов (в частности, RST) поскольку старые или посторонние пакеты не должны влиять на текущее соединение, если они несомненно не являются относящимися к те- текущему контексту. Сегмент приемлем, если приемное окно имеет ненулевой размер
630 Глава 13. Сетевые протоколы и если по крайней мере некоторая часть пространства последовательностей, занимае- занимаемых пакетом, попадает в пределы приемного окна. Если пакет содержит данные, неко- некоторая часть данных должна попасть в пределы окна. Часть данных, предшествующих окну, обрезается, поскольку она была уже получена, а часть, выходящая за пределы окна, также уничтожается, поскольку она была отправлена преждевременно. Если при- приемное окно закрыто (rcv_wnd равно нулю), приемлемыми являются лишь сегменты без данных с номером последовательности, равным rcv_nxt. Если входящий пакет является неприемлемым, он сбрасывается после отправки подтверждения. Обработка входящих TCP-пакетов должна быть полностью общей, принимающей во внимание все возможные входящие пакеты и все возможные состояния принимающих конечных точек. Однако большая часть обрабатываемых пакетов попадает в две основ- основные категории. Типичные пакеты содержат либо следующий ожидающийся сегмент данных для существующего соединения, либо подтверждение плюс обновление окна для одного или более сегментов данных без дополнительных флагов или указаний состоя- состояния. Вместо рассмотрения каждого входящего сегмента, основываясь на первых принци- принципах, tcp_input() сначала проверяет эти общие случаи. Этот алгоритм известен как пред- предсказание заголовка. Если входящий сегмент подходит к соединению с состоянии ESTAB- ESTABLISHED, если он содержит флаг АСК, но не содержит других флагов, если номер после- последовательности является следующим ожидавшимся значением (и отметка времени, если она есть, не уменьшается), если поле окна является тем же самым, как в предыдущем сег- сегменте, и если соединение не находится в состоянии повторной отправки, тогда входящий сегмент относится к одному из двух общих типов. Система обрабатывает все опции от- отметки времени, которые содержит сегмент, записывая значение, полученное для включе- включения в следующее подтверждение. Если сегмент не содержит данных, это чистое под- подтверждение с обновлением окна. В обычном случае определяется информация о време- времени обращения, если она доступна, подтвержденные данные удаляются из очереди от- отправки буфера, а значения последовательности обновляются. После проверки значений заголовка пакет уничтожается. Таймер повторной передачи отменяется, если все ожи- ожидающие данные были подтверждены; в противном случае он запускается повторно. Уро- Уровень сокетов уведомляется, если какой-нибудь процесс мог ожидать вывода. Наконец, вызывается tcp_output(), поскольку окно передвинулось вперед, и эта операция заверша- завершает обработку чистого подтверждения. Если пакет, удовлетворяющий тестам для предсказания заголовка, содержит сле- следующие ожидавшиеся данные, если для соединения в очередь не помещены внеполос- ные данные и если в приемном буфере сокета есть место для входящих данных, этот пакет является чистым порядковым сегментом данных. Переменные последовательно- последовательности обновляются, из пакета удаляются заголовки пакета, а оставшиеся данные добав- добавляются в приемный буфер сокета. Уровень сокетов уведомляется так, что он может уведомить любой заинтересованный поток, а управляющий блок помечается флагом, указывающим, что необходимо подтверждение. Для чистого пакета данных дополни- дополнительной обработки не требуется.
13.6. Обработка ввода TCP 631 Для пакетов, которые не обрабатываются алгоритмом предсказания заголовка, шаги обработки следующие. 1. Обработать опции отметки времени, если она присутствует, с отвержением всех пакетов, для которых отметка времени уменьшилась, отправив сначала текущее под- подтверждение. 2. Проверить, начинается ли пакет до rcvjixt. Если да, игнорировать в пакете любые SYN и обрезать любые данные, оказавшиеся перед rcvjtxt. Если больше данных не остается, отправить текущее подтверждение и уничтожить пакет. (Предполагается, что пакет является переданным дубликатом.) 3. Если пакет после обрезания по-прежнему содержит данные, а процесс, создавший сокет, уже закрыл его, отправить сброс (RST) и удалить соединение. Этот сброс не- необходим для разрыва соединений, которые не могут завершиться; обычно он от- отправляется, когда во время приема данных отключается клиент удаленной реги- регистрации. 4. Если конец сегмента выходит за пределы окна, обрезать любые выходящие за пре- пределы окна данные. Если окно было закрыто, а номером последовательности пакета является rcv_nxt, пакет интерпретируется как обновление окна и обрабатывается оставшаяся часть пакета. Если установлен SYN, а соединение было в состоянии TIME_WAIT, этот пакет является в действительности запросом нового соединения и старое соединение удаляется; эта процедура называется быстрым повторным использованием соединения. В противном случае, если не остается данных, необходимо отправить подтверждение и удалить пакет. Оставшиеся шаги обработки ввода TCP проверяют следующие флаги и поля и предпринимают соответствующие действия: RST, ACK, окно, URG, данные и FIN. Поскольку приемлемость пакета была уже подтверждена, эти действия просты. 5. Если присутствует опция отметки времени и пакет включает ожидавшийся сле- следующий номер последовательности, записать полученное значение для включения в следующее подтверждение. 6. Если установлен RST, закрыть соединение и удалить пакет. 7. Если АСК не установлен, удалить пакет. 8. Если значение поля подтверждения больше, чем значение предыдущих под- подтверждений, то были подтверждены новые данные. Если соединение было в со- состоянии SYN_RECEIVED и пакет подтверждает SYN, отправленное для этого соединения, войти в состояние ESTABLISHED. Если пакет включает опцию отмет- отметки времени, использовать ее для вычисления пробы времени обращения; в против- противном случае, если диапазон вновь подтвержденных последовательностей включает номер последовательности, для которого измеряется время обращения, этот пакет
632 Глава 13. Сетевые протоколы предоставляет пробу. Усреднить время в сглаженную оценку времени обращения для соединения. Если были подтверждены все ожидающие данные, остановить таймер повторной передачи; в противном случае установить для него значение те- текущего тайм-аута. Наконец, удалить из очереди отправки в сокете подтвержденные данные. Если был отправлен и подтвержден FIN, изменить состояние конечного автомата. 9. Проверить по полю окна, перемещается ли известное окно отправки. Сначала про- проверить, содержит ли данный пакет новое обновление окна. Если номер последователь- последовательности пакета больше, чем у предыдущего обновления окна, или если номер последова- последовательности тот же самый, но поле подтверждения больше, или если как последователь- последовательность, так и подтверждение те же самые, но окно больше, записать новое окно. 10. Если установлен флаг срочных данных URG, сравнить указатель срочных данных в па- пакете с последним полученным указателем на срочные данные. Если он отличается, бы- были отправлены новые срочные данные. Использовать указатель на срочные данные для вычисления so_oobmark, смещения от начала приемного буфера сокета до отметки срочных данных (раздел 11.6), и уведомить сокет с помощью sohasoutofbandQ. Если указатель на срочные данные меньше, чем размер пакета, все срочные данные были получены. TCP обычно удаляет последний октет данных, посланный в срочном режи- режиме (последний октет перед указателем на срочные данные), и помещает этот октет в управляющий блок протокола до тех пор, пока он не будет запрошен с использовани- использованием PRU_RCVOOB. (Окончание срочных данных является предметом разногласий; ин- интерпретация BSD следует оригинальной спецификации TCP.) Опция сокета SO_OOBINLINE может запросить, чтобы срочные данные были оставлены в очереди с обычными данными, хотя отметка в потоке данных по-прежнему сохраняется. 11. Наконец, исследовать поле данных в полученном пакете. Если данные начинаются с rcvjvct, они могут быть помещены непосредственно в приемный буфер сокета с по- помощью sbappendstreamQ. В управляющем блоке протокола устанавливается флаг TF_DELACK, чтобы указать на необходимость подтверждения, но последнее не от- отправляется немедленно в надежде, что его можно будет объединить с отправляемыми вскоре пакетами (преимущественно в ответ на входящие данные) или объединить с подтверждением других полученных вскоре данных; см. раздел об отложенных под- подтверждениях и обновлениях окон в разделе 13.7. Если никакая деятельность не вызы- вызывает возвращение пакета до следующего запуска процедуры tcp_delack(), она изменит флаг на TFACKNOW и вызовет процедуру tcp_output() для отправки подтверждения. Подтверждения могут таким образом задерживаться не более чем на 200 миллисекунд. Если данные не начинаются с rcvjvct, пакет сохраняется в очереди соединения до по- поступления промежуточных данных и подт-верждение отправляется немедленно. 12. В качестве последнего шага в обработке полученного пакета проверить флаг FIN. Если он присутствует, возможно, придется изменить конечный автомат соединения,
13.7. Обработка вывода TCP 633 и сокет помечается с помощью socantrcvmoreQ для передачи указания конца файла. Если сторона отправки уже закрылась (был отправлен и подтвержден FIN), сокет считается теперь закрытым, и он помечен таким образом soisdisconnectedQ. Для форсирования немедленного подтверждения устанавливается флаг TF_ACKNOW. Шаг 10 завершает действия, предпринимаемые при получении tcp_input() нового пакета. Однако, как отмечено ранее в данном разделе, прием ввода может потребовать нового вывода. В частности, подтверждение всех ожидающих данных или нового об- обновления окна требует либо нового вывода, либо изменения состояния модулем вывода. Несколько особых случаев устанавливают также флаг TF_ACKNOW. В этих случаях в заключение обработки ввода вызывается tcp_output(). 13.7. Обработка вывода TCP Наконец мы готовы исследовать самую интересную часть реализации TCP: политику отправки. Как мы видели ранее, TCP-пакет содержит подтверждение и поле окна, а также данные, и один пакет может быть отправлен, если любое из этих трех полей из- изменится. Простая политика отправки TCP могла бы отправить значительно больше па- пакетов, чем нужно. Например, рассмотрите, что случится, когда пользователь набирает один символ в соединении удаленного терминала, который использует удаленное эхо. TCP на стороне сервера получает пакет с единственным символом. Он мог бы отпра- отправить немедленное подтверждение одного символа. Затем, спустя миллисекунды, сервер регистрации прочел бы символ, удалив его из приемного буфера; TCP мог бы немедленно отправить обновление окна, уведомляя, что доступен один дополнитель- дополнительный октет окна отправки. Спустя еще одну миллисекунду или около того, сервер реги- регистрации отправил бы эхо символа обратно клиенту, делая необходимым отправку третьего пакета в ответ на единственный введенный символ. Очевидно, что все три ответа (подтверждение, обновление окна и возвращение данных) можно было бы от- отправить в одном пакете. Однако, если сервер не отображал бы эхо вводимых данных, подтверждение не могло бы удерживаться в течение слишком длительного времени или TCP клиентской стороны пришлось бы начать повторную передачу. Алгоритмы, использованные в политике отправки для минимизации сетевого трафика и в то же время максимизации пропускной способности, являются наиболее тонкой частью реа- реализации TCP. Политика отправки, использующаяся в FreeBSD, включает несколько стандартных алгоритмов, а также несколько подходов, рекомендуемых сообществом исследования сетей. Мы изучим каждую часть политики отправки. Как мы видели в предыдущем разделе, имеются несколько различных событий, которые могут запустить отправку данных через соединение; кроме того, пакеты должны отправляться для доставки подтверждений и обновлений окон (рассмотрите одно- односторонние соединения!).
634 Глава 13. Сетевые протоколы Отправка данных Наиболее очевидной причиной вызова tcp_output() выходного модуля является то, что пользователь записал в сокет новые данные. Операции записи делаются посредством вызова процедуры tcp_usr_send(). (Вспомните, что sosendQ при необходимости ожидает появления достаточного места в буфере отправки сокета, а затем копирует данные поль- пользователя в цепочку mbuf, которая передается протоколу процедурой tcp_usr_send().) Действием tcp_usr_send() является просто помещение новых выходных данных в буфер отправки сокета с помощью sbappendstreamQ и вызов tcp_output(). Если процедура tcp_output() допускает управление потоком, то она отправит данные немедленно. Фактическая операция отправки не отличается существенно от операции для сокета UDP-дейтаграмм. Различие в том, что заголовок более сложный и должны быть инициализированы дополнительные поля, а отправленные данные являются просто копией данных пользователя. Однако для операций отправки, достаточно больших для того, чтобы sosendQ поместил данные во внешние кластеры mbuf, копирование осуще- осуществляется путем создания новой ссылки на кластер данных. Копия должна быть сохра- сохранена в буфере отправки сокета на случай необходимости повторной отправки. Также, если число октетов данных больше, чем размер одного сегмента максимального раз- размера, в единственном вызове будет создано и отправлено несколько пакетов. Процедура tcp_outputQ выделяет mbuf для сохранения заголовка выходного пакета и копирует в этот mbuf содержание шаблона заголовка. Если данные для отправки помещаются в тот же самый mbuf, что и заголовок, tcp_outputQ копирует их на место из буфера отправки сокета, используя процедуру m_copydataQ. В противном случае tcp_outputQ добавляет данные для отправки в качестве отдельной цепочки mbuf, полученной с помощью операции т_сору() из соответствующей части буфера отправки. Номер последовательности для пакета устанавливается из sndjixt, а подтверждение устанавливается из rcvjixt. Флаги получаются из массива, содержащего флаги, которые должны отправляться в каждом состоянии соединения. Окно для объявления вычисляется из объема оставшегося в приемном буфере сокета пространства; однако, если этого пространства мало (менее одной четверти буфера или менее одного сегмента), оно устанавливается в ноль. Окно никогда не может заканчиваться на номере последо- последовательности меньшем, чем номер, на котором закончился предыдущий пакет. Если были установлены срочные данные, соответствующим образом устанавливаются ука- указатель на срочные данные и флаг. Должен быть установлен еще один флаг: флаг PUSH- пакета указывает, что данные должны быть переданы пользователю; он подобен запро- запросу очистки буфера. Этот флаг обычно рассматривается как устаревший, но он устанав- устанавливается каждый раз, когда были отправлены все данные в буфере отправки; FreeBSD игнорирует этот флаг на вводе. Когда заголовок заполнен, вычисляется контрольная сумма пакета. Оставшиеся части IP-заголовка инициализируются, включая поля типа- службы и времени-жизни, и пакет отправляется с помощью ip_outputQ. Таймер по-
13.7. Обработка вывода TCP 635 вторной передачи запускается, если он еще не работает, а значения sndjixt и sndjnax для соединения обновляются. Избегание синдрома незначительного окна Синдром незначительного окна (silly-window syndrome) является названием потенци- потенциальной проблемы в основанной на окне схеме управления потоком, в котором система отправляет несколько небольших пакетов вместо ожидания доступности окна прием- приемлемого размера [Clark, 1982]. Например, если клиентская программа сетевой регистра- регистрации имеет общий размер приемного буфера в 4096 октетов, а пользователь останавли- останавливает вывод терминала в ходе большого вывода, буфер станет почти полным, когда будут получены новые сегменты большого размера. Если оставшееся пространство буфера снизится на 10 байтов, для принимающего не было бы полезным предлагать получить дополнительные 10 октетов. Если затем пользователю было бы разрешено напечатать несколько символов и снова остановиться, для принимающего TCP попрежнему не было бы полезным посылать обновление окна с разрешением на дополнительные 14 октетов. Вместо этого желательно подождать до тех пор, пока не будет возможно отправить приемлемо большой пакет, поскольку приемный буфер уже содержит достаточно данных для нескольких следующих страниц вывода. Избегание синдрома незначительного окна желательно как для отправителя, так и для получателя соединения с управляемым потоком, поскольку на каждом конце можно предотвратить использование незначительных окон. Мы описали избегание получателем синдрома незначительного окна в предыдущем подразделе; когда пакет отправляется, приемное окно объявляется нулевым, если оно менее одного пакета и менее одной четверти при- приемного буфера. Для избегания отправителем синдрома незначительного окна операция вывода откладывается, если для отправки готов по крайней мере полный пакет дан- данных, но может быть отправлено менее одного пакета из-за размера окна отправки. Вместо отправки tcp_output() устанавливает состояние вывода в дежурное состояние, запустив дежурный таймер. Если ко времени истечения таймера не было получено обновления окна, отправляются доступные данные в надежде, что подтверждение будет включать окно большего размера. Если этого не происходит, соединение остается в дежурном состоянии, периодически осуществляя зондирование окна до тех пор, пока окно не будет открыто. Начальная реализация избежания отправителем синдрома незначительного окна создавала большие задержки и низкую пропускную способность через соединения с хостами, использующими реализации TCP с маленькими буферами. К сожалению, эти реализации всегда объявляют окна приема с размером меньше максимального раз- размера сегмента - поведение, которое считается незначительным данной реализацией. Английское слово «silly» кроме своего привычного значения «глупый» также имеет и значение «незначительный». Так как silly-window syndrome описывает проблему, возникающую при использовании небольших окон, которые не стоит учитывать, то перевод «незначительный» более удачен, чем общепринятый перевод «глупый». - Примеч. науч. ред.
636 Глава 13. Сетевые протоколы Как решение этой проблемы, TCP сохраняет запись самого большого окна приема, предложенного противоположным узлом, в переменной управляющего блока протоко- протокола maxjsndwnd. Когда может быть отправлена по крайней мере половина max_sndwnd, отправляется новый сегмент. Эта методика повышает производительность, когда сис- система BSD взаимодействует с этими ограниченными хостами. Избежание небольших пакетов Сетевой трафик демонстрирует бимодальное распределение размеров1. Переносы больших объемов данных имеют тенденцию использовать для максимальной пропуск- пропускной способности пакеты максимально возможного размера. Интерактивные службы (такие, как сетевая регистрация) имеют тенденцию, однако, использовать небольшие пакеты, часто содержащие лишь один символ данных. В быстрой локальной сети, такой, как Ethernet, использование пакетов с одним символом часто не представляет проблемы, поскольку полоса пропускания сети обычно не насыщается. В протяжен- протяженных сетях, соединенных медленными или перегруженными сетями, или в беспровод- беспроводных LAN, которые являются и медленными, и теряющими данные, желательно собирать данные в течение определенного периода времени, а затем отправлять их в одном сетевом пакете. Для сбора ввода в течение фиксированного времени - обычно от 50 до 100 миллисекунд - и затем отправки их в одном пакете были предложены раз- различные схемы. Однако эти схемы заметно снижают скорость эхо-отображения символов в быстрых сетях и часто сохраняют несколько пакетов в медленных сетях. В отличие от этого простая и элегантная схема для снижения трафика небольших пакетов была предложена Nagle [1984]. Эта схема разрешает отправить первый октет вывода в пакете без задержки. Однако до подтверждения этого пакета новые небольшие пакеты не могут быть отправлены. Если поступает достаточно новых данных для за- заполнения пакета максимального размера, отправляется другой пакет. Как только ожи- ожидающие данные подтверждаются, может быть отправлен ввод, помещенный в очередь в ожидании первого пакета. Лишь один небольшой пакет может находиться в ожида- ожидании для соединения в одно и то же время. Конечным результатом является то, что данные из небольших операций вывода помещаются в очередь в течение времени обращения. Если время обращения меньше, чем время поступления новых символов, что имеет место в сеансе удаленного терминала в локальной сети, передачи никогда не задерживаются и время ответа остается небольшим. Когда вклиниваются медленные сети, ввод после первого символа помещается в очередь, и следующий пакет содержит ввод, полученный в ходе предыдущего времени обращения. Такой алгоритм привлека- привлекателен как из-за своей простоты, так и из-за своей самонастраивающейся природы. В конечном счете люди обнаружили, что этот алгоритм не работал хорошо для определенных классов сетевых клиентов, которые отправляли потоки небольших запросов, которые не могли быть собраны в пакеты. Одним из таких клиентов была То есть это распределение имеет два «пика». - Примеч. науч. ред.
13.7. Обработка вывода TCP 637 сетевая система X Window [Scheifler & Gettys, 1986], которая требовала немедленной доставки небольших сообщений для получения обратной связи реального времени для интерфейсов пользователя, таких, как установка размеров нового окна перетаскивани- перетаскиванием. Поэтому разработчики добавили опцию TCP_NODELAY, чтобы аннулировать этот алгоритм в соединении. Эта опция может быть установлена с помощью системного вызова setsockopt, который достигает TCP через процедуру tcp_ctloutput(). К сожале- сожалению, библиотека системы X Window всегда устанавливает флаг TCPNODELAY, вместо того чтобы устанавливать его лишь тогда, когда клиент использует позицио- позиционирование с использованием мыши. Отложенные подтверждения и обновления окон TCP-пакеты должны передаваться и по другим причинам, кроме передачи данных. В одностороннем соединении принимающий TCP по-прежнему должен посылать пакеты для подтверждения полученных данных и перемещения окна отправки отпра- отправителя. Механизм отсрочки подтверждений в надежде комбинирования или объедине- объединения их с данными или обновлениями окон был описан в разделе 13.6. При передаче большого количества данных время, с которым отправляются обновления окон, явля- является определяющим фактором для пропускной способности сети. Например, если по- получатель просто устанавливает флаг TFDELACK каждый раз, когда были получены данные в соединении с большим количеством данных, подтверждения отправлялись бы каждые 200 миллисекунд. Если используются 8192-октетные окна в 10 Мбит/с Ethernet, этот алгоритм приведет к максимальной пропускной способности 320 Кбит/с, или 3,2 процента от физической пропускной способности сети. Очевидно, раз отправи- отправитель заполнил данное окно отправки, он должен остановиться до тех пор, пока получа- получатель не подтвердит старые данные (позволив удалить их из буфера отправки и заме- заменить их новыми данными) и не предоставит обновление окна (позволив отправлять новые данные). Поскольку основанное на окнах управление потоком TCP ограничено местом в приемном буфере сокета, TCP устанавливает в своем элементе переключения прото- протокола флаг PR_RCVD таким образом, что протокол будет вызываться (через процедуру tcp_usr_rcvd()), когда пользователь сделал принимающий вызов, который удалил данные из приемного буфера. Процедура tcp_usr_rcvd() просто вызывает tcp_output(). Каждый раз, когда tcp_output() определяет, что обновление окна, отправленное в теку- текущих условиях, предоставило бы отправителю новое окно отправки, достаточно боль- большое, чтобы представлять ценность, она отправляет подтверждение и обновление окна. Если получатель будет ждать до тех пор, пока окно заполнится, отправитель на самом деле в течение некоторого времени будет бездействовать, пока не получит в конце концов обновление окна. Более того, если бы буфер отправки в отправляющей системе был меньше, чем буфер принимающего, - и поэтому меньше, чем окно получателя, - отправитель не смог бы заполнить окно получателя, не получив подтверждения. Поэтому стратегия обновления окна в FreeBSD основана лишь на максимальном размере
638 Глава 13. Сетевые протоколы сегмента. Каждый раз, когда новое обновление окна переместило бы окно вперед на по крайней мере два полных размера сегмента, отправляется обновление окна. Эта стра- стратегия обновления окна создает двойное уменьшение трафика обновлений и двойное уменьшение обработки ввода для отправителя. Однако обновления отправляются дос- достаточно часто, чтобы предоставить отправителю обратную связь о ходе соединения и чтобы позволить отправителю посылать дополнительные сегменты. Обратите внимание, что TCP вызывается в двух различных стадиях обработки на принимающей стороне передачи большого количества данных: он вызывается при приеме пакета для обработки ввода, и он вызывается после каждой операции приема, удаляя данные из входного буфера. При первом вызове могло бы быть отправлено под- подтверждение, но не обновление окна. После операции приема возможно также обновле- обновление окна. Таким образом, важно, чтобы алгоритм для обновлений запускался во второй половине этого цикла. Состояние повторной передачи Когда истекает таймер повторной передачи, в то время пока отправитель ожидает под- подтверждения переданных данных, для повторной передачи вызывается tcp_output(). Сначала в качестве значения таймера повторной передачи устанавливается следующее кратное времени обращения значение в серии отсрочек. Переменная sndjvct изменяется обратно от текущего номера последовательности в sndjuna. Затем отправляется един- единственный сегмент, содержащий самые старые данные в очереди передачи. В отличие от некоторых других систем FreeBSD не сохраняет копии пакетов, которые были отправлены через соединение; она сохраняет лишь данные. Таким образом, хотя повторно отправляется лишь один пакет, этот пакет может содержать больше данных, чем самый старый ожидающий отправки пакет. В медленных соединениях с небольши- небольшими операциями отправки, таких, как удаленная регистрация, этот алгоритм может вызвать повторную передачу пакета с единственным октетом данных вместе со всеми помещенными в очередь с момента первой передачи этого октета данными. Если единственный пакет был потерян в сети, повторно переданный пакет вызовет подтверждение всех переданных до этого времени данных. Если было потеряно более одного пакета, следующее подтверждение включит повторно переданный пакет и, воз- возможно, некоторые промежуточные данные. Он может также включать обновление окна. Таким образом, когда после тайм-аута повторной передачи получено под- подтверждение, любые старые данные, которые не были подтверждены, будут отправлены повторно, как если бы они еще не были отправлены, а также могут быть отправлены некоторые новые данные. Медленный старт Многие TCP-соединения между исходной и конечной точками пересекают несколько сетей. Когда некоторые из сетей являются более медленными, чем другие, входному
13.7. Обработка вывода TCP 639 маршрутизатору для самой медленной сети часто передается больше данных, чем он может обработать. Он может буферировать некоторые из входных пакетов, чтобы избе- избежать их удаления из-за внезапных изменений потока, но в конечном счете его буферы заполнятся, и он должен будет начать удалять пакеты. Когда TCP-соединение начинает сначала отправлять данные через быструю сеть маршрутизатору, пересылающему через более медленные сети, оно может обнаружить, что очереди маршрутизатора уже почти полностью заполнены. В первоначальной политике отправки, использованной в BSD, передачи большого количества данных начинались бы после установки соеди- соединения с отправки полного окна пакетов. Эти пакеты могли бы быть отправлены на полной скорости сети на маршрутизатор узкого места, но этот маршрутизатор мог бы передавать их лишь со значительно меньшей скоростью. В результате начальный взрыв пакетов, весьма вероятно, переполнил бы очередь маршрутизатора, и некоторые из пакетов были бы потеряны. Если бы такое соединение использовало расширенный размер окна в попытке обеспечить производительность - например, при пересечении спутниковой линии связи с длительным временем обращения, - эта проблема была бы даже еще более серьезной. Однако, если соединение смогло однажды достичь стабиль- стабильного состояния, сеть часто могла бы принять полное окно данных, если пакеты были бы равномерно распределены по всему пути. В устойчивом состоянии новые пакеты вводились бы в сеть лишь тогда, когда предыдущие пакеты были подтверждены и число пакетов в сети было бы постоянным. На рис. 13.9 представлено желательное стабильное состояние. Кроме того, даже если бы пакеты поступали на исходящий маршрутизатор в кластере, они были бы распределены, по крайней мере когда они пересекали бы самую медленную сеть. Если бы получатель отправлял подтверждения при получении каждого пакета, подтверждения возвращались бы отправителю с при- примерно правильными интервалами. Затем у отправителя были бы средства самосинхро- самосинхронизации для передачи в сеть с нужной скоростью без отправки пачек пакетов, которые в узком месте не могли бы быть буферированы. Отправитель Получатель РИС. 13.9. Синхронизация подтверждений. Есть два маршрутизатора, объединенных медлен- медленным каналом между отправителем и получателем. Толщина связей представляет их скорость. Ширина пакетов представляет время их прохождения по каналу. Быстрые связи широкие, а пакеты узкие. Медленные связи узкие, а пакеты широкие. В пока- показанном устойчивом состоянии отправитель отправляет новый пакет каждый раз, когда от получателя получено подтверждение
640 Глава 13. Сетевые протоколы В это стабильное состояние TCP-соединение приводится алгоритмом медленного старта [Jacobson, 1988]. Он называется медленным, поскольку при пересечении мед- медленной сети передачу данных нужно начинать медленно. На рис. 13.10 показан ход работы алгоритма медленного старта. Схема проста: соединение А начинает с огра- ограничения в один ожидающий пакет. Каждый раз при получении подтверждения предел увеличивается на один пакет. Если подтверждение содержит также обновление окна, в ответ могут быть отправлены два пакета. Этот процесс продолжается до тех пор, пока окно не будет полностью открыто. В ходе фазы медленного старта соединения, если каждый пакет подтверждался бы отдельно, ограничение удваивалось бы в ходе каждого обмена, приводя к экспоненциальному открытию окна. Отложенные под- подтверждения могли бы вызвать объединение подтверждений, если к получателю могло бы поступать более одного пакета в течение 200 миллисекунд, слегка замедляя откры- открытие окна. Однако отправитель никогда не посылает в ходе фазы открытия наборы из более чем двух или трех пакетов, а когда окно открыто, отправляет лишь один или два пакета. Время 0 r^biji ->-5tt Время 2 Время 1 Время 3 ы .- lO .n ы * Ki4^i% Время 4 1 4 15'л; Ы /9i Время 5 10 18 r |,-U9.'^ till (i2 Рис. 13.10. Ход алгоритма медленного старта Реализация алгоритма медленного старта использует второе окно, аналогичное окну отправки, но управляемое отдельно, которое называется окном перегрузки (congestion window, sndjzwnd). Окно перегрузки поддерживается в соответствии с оценкой данных, которые сеть в состоянии буферировать для данного соединения. Политика отправки изменяется таким образом, чтобы новые данные отправлялись, лишь если они допускаются как обычным окном отправки, так и окном отправки пере- перегрузки. Окно перегрузки инициализируется размером пакета, заставляя соединение начать с медленного старта. Его размер устанавливается в один пакет каждый раз, когда передача останавливается из-за тайм-аута. В противном случае, когда подтвержден повторно переданный пакет, получающееся окно обновления могло бы позволить отпра-
13.7. Обработка вывода TCP 641 вить полное окно, что снова переполнило бы промежуточные маршрутизаторы. Этот медленный старт после тайм-аута повторной передачи устраняет необходимость теста в процедуре вывода для ограничения вывода размером одного пакета в течение перво- первоначального тайм-аута. Кроме того, тайм-аут может указывать, что сеть стала медлен- медленнее из-за перегрузки, и временное уменьшение окна может помочь сети восстановить- восстановиться из этого состояния. Соединение вынуждается повторно установить свой таймер син- синхронизации после того, как оно было остановлено, и медленный старт также приводит к этому эффекту. Медленный старт также используется, если соединение начинает передачу после периода бездействия, по крайней мере, с текущего значения повторной передачи (функции сглаженного времени обращения и оценок дисперсии). Обработка сдерживания источника Если маршрутизатор вдоль маршрута, использованного соединением, получает больше пакетов, чем он может отправить по этому пути, он в конечном счете будет вы- вынужден удалять пакеты. Когда пакеты удаляются, маршрутизатор может отправить хостам, чьи пакеты были уничтожены, сообщение ICMP об ошибке сдерживания ис- источника (source-quench), указывающее, что отправители должны замедлить свою передачу. Хотя это сообщение указывает, что должно быть сделано некоторое измене- изменение, оно не предоставляет информации о том, какие изменения должны быть сделаны или сколько времени они должны длиться. Кроме того, не все маршрутизаторы отправ- отправляют сообщения сдерживания источника для каждого уничтоженного пакета. Использо- Использование алгоритма медленного старта после тайм-аутов повторной передачи дает соедине- соединению возможность отвечать правильно на уничтоженный пакет независимо от того, получено или нет сообщение сдерживания источника для указания потери. Действие по получении сдерживания источника для TCP-соединения заключается просто в пре- предупреждении тайм-аута из-за уничтоженного пакета, установив окно перегрузки раз- размером в один пакет. Это действие предотвращает отправку новых пакетов до тех пор, пока уничтоженный пакет не будет отправлен повторно со следующим тайм-аутом. В это время снова начинается медленный старт. Задание размеров буфера и окна Производительность TCP-соединения очевидна ограничено полосой пропускания пути, который должно использовать соединение. На производительность также влияет время обращения для пути. Например, пути, включающие спутниковые соединения, имеют присущую им длительную задержку, хотя пропускная способность может быть высокой, но производительность ограничена одним окном данных на время обраще- обращения. После заполнения окна получателя отправитель должен ждать прибытия под- подтверждения и обновления окна в течение по крайней мере одного времени обращения. Чтобы использовать всю полосу пропускания пути, как отправитель, так и получатель должны использовать буферы, равные по крайней мере произведению полосы пропус-
642 Глава 13. Сетевые протоколы кания на время задержки, чтобы дать отправителю возможность передавать в течение всего времени обращения. В устойчивом состоянии это буферирование дает отправи- отправителю, получателю и промежуточным частям сети возможность поддерживать конвейер заполненным на каждом этапе. Для некоторых путей использование медленного старта и большого окна может вести к значительно большей производительности, чем могло быть достигнуто ранее. Время обращения для сетевого пути включает два компонента: транзитное время и время очереди. Транзитное время включает в себя время распространения, коммута- коммутации и пересылки на физических уровнях сети, включая время для побитовой передачи данных после каждого транзитного участка с сохранением и пересылкой. В идеале время очереди было бы пренебрежимым, причем пакеты, прибывающие на каждый узел сети точно вовремя, должны отправляться после предыдущего пакета. Этот иде- идеальный поток достижим, когда единственное соединение, использующее подходящий размер окна, синхронизировано с сетью. Однако по мере введения в сеть дополнитель- дополнительного трафика от других источников в маршрутизаторах создаются очереди, особенно на входах в медленные участки пути. Хотя задержка очереди является частью времени обращения, наблюдаемого в каждом использующем путь сетевом соединении, уве- увеличение размера рабочего окна для соединения до значения большего произведения ограничивающей полосы пропускания для пути на задержку транзитного участка не является полезным. Отправка дополнительных данных, превышающих этот лимит, вызывает помещение в очередь дополнительных данных, увеличивая задержку очереди без увеличения пропускной способности. Избежание перегрузки с помощью медленного старта Добавление к TCP алгоритма медленного старта позволяет соединению отправлять пакеты со скоростью, которую способна допустить сеть, достигая устойчивого состоя- состояния, в котором пакеты посылаются, лишь когда сеть покидают другие пакеты. Отдель- Отдельное соединение может разумно использовать большое окно, не затопляя вначале вход- входной маршрутизатор медленной сети. Когда соединение открывает в ходе медленного старта окно, оно вводит пакеты в сеть до тех пор, пока сетевые связи остаются заняты- занятыми. Во время этой фазы оно может посылать пакеты со скоростью, в два раза превы- превышающей скорость, с которой сеть может доставлять данные, из-за экспоненциального открытия окна. Если окно для пути выбрано соответствующим образом, соединение достигнет устойчивого состояния без затопления сети. Однако, когда путь разделяют несколько соединений, полоса пропускания, доступная каждому соединению, снижа- снижается. Если каждое соединение использует окно, равное произведению полосы пропус- пропускания на задержку, дополнительные пакеты в транзитном участке должны помещаться в очередь, увеличивая задержку. Если общая предложенная нагрузка слишком высока, маршрутизаторы могут уничтожать пакеты вместо увеличения размеров очередей и за- задержки. Таким образом, размер подходящего окна для TCP-соединения зависит не только от пути, но также от конкурирующего трафика. Размер окна, достаточно боль-
13.7. Обработка вывода TCP 643 шой для того, чтобы обеспечить хорошую производительность, когда на пути находит- находится соединение с большой задержкой, переполнит сеть, когда большая часть времени обращения относится к задержкам очередей. Для TCP-соединения крайне желательно, чтобы оно было самонастраивающимся, поскольку характеристики пути редко извест- известны на конечных точках и с течением времени могут изменяться. Если соединение рас- расширяет свое окно до значения, слишком большого для пути, или если дополнительная нагрузка сети совместно превышает пропускную способность, будут строиться очере- очереди маршрутизаторов до тех пор, пока пакеты не должны будут уничтожаться. В этот момент соединение закроет окно перегрузки в один пакет и инициирует медленный старт. Однако, если окно просто слишком большое для пути, этот процесс будет по- повторяться каждый раз, когда окно открыто слишком сильно. Соединение может извлечь урок из этой проблемы и научиться регулировать свое поведение, используя другой алгоритм, связанный с алгоритмом медленного старта. Этот алгоритм сохраняет для каждого соединения переменную состояния, sndjssthresh (slow-start threshold - порог медленного старта), которая является оценкой приемлемо- приемлемого окна для пути. Когда пакет уничтожается, о чем свидетельствует тайм-аут повторной передачи, эта оценка окна устанавливается в половину числа ожидающих октетов данных. Текущее окно, очевидно, слишком большое в данный момент, и уменьшение использования окна должно быть достаточно большим, чтобы перегрузка уменьшилась, а не стабилизировалась. В это же время окно медленного старта (snd_cwnd) устанавливается в один сегмент для повторного старта. Соединение старту- стартует снова, как раньше, экспоненциально открывая окно до тех пор, пока оно не достига- достигает предела sndjssthresh. В этот момент соединение близко к предполагаемому прием- приемлемому окну для пути. Оно входит в устойчивое состояние, посылая пакеты данных, допускаемые обновлениями окна. Чтобы испытать улучшение в сети, оно продолжает медленно расширять окно; пока это расширение успешно, соединение может продол- продолжать использовать преимущества снижения сетевой нагрузки. Расширение окна на этой фазе является линейным, причем для каждого полного окна переданных данных к текущему окну добавляется один дополнительный полноразмерный сегмент. Это медленное возрастание дает соединению возможность обнаружить, когда безопасно возобновить использование большего окна, в то же время снижая потерю производи- производительности из-за ожидания после потери пакета возобновления передачи. Обратите внимание, что увеличение размера окна во время этой фазы соединения является линей- линейным, пока пакеты не теряются, но уменьшение размера окна при появлении признаков перегрузки является экспоненциальным (он делится на 2 при каждом тайм-ауте). С использованием этого алгоритма динамического размера окна можно использовать большие размеры окна по умолчанию для соединений со всеми местами назначения без переполнения сетей, которые не могут их поддерживать.
644 Глава 13. Сетевые протоколы Быстрая повторная передача Пакеты могут теряться в сети по двум причинам: перегрузка и повреждение. В любом случае TCP обнаруживает потерянные пакеты по тайм-ауту, вызывающему повторную передачу. Когда пакет потерян, поток пакетов в соединении останавливается в ожида- ожидании тайм-аута. В зависимости от времени обращения и дисперсии этот тайм-аут может вылиться в значительный период, в течение которого соединение не развивается. С по- появлением тайм-аута в качестве первой фазы медленного старта повторно передается единственный пакет, а порог медленного старта устанавливается в половину предыду- предыдущего рабочего окна. Если последующие пакеты не теряются, соединение проходит через медленный старт до нового порога, а затем постепенно открывает окно для зон- зондирования того, не исчезла ли перегрузка. Каждая из этих фаз снижает эффективную пропускную способность для соединения. Результатом является снижение производи- производительности, даже если перегрузка, возможно, была кратковременной. Когда соединение достигает устойчивого соединения, оно посылает непрерывный поток пакетов данных в ответ на поток подтверждений с обновлениями окна. Если по- потерян единственный пакет, получатель видит прибывающие вне очереди пакеты. Боль- Большинство получателей TCP, включая FreeBSD, отвечают на внеочередной сегмент по- повторным подтверждением на очередные данные. Если потерян один пакет, хотя отправ- отправлено достаточно пакетов для заполнения окна, каждый пакет после потерянного будет провоцировать дублированные подтверждения без данных, обновления окна или другой новой информации. Получатель может сделать из этих дублированных под- подтверждений предположение о поступлении внеочередных пакетов. При наличии дос- достаточных свидетельств о нарушении очереди получатель может предположить, что был потерян пакет. TCP FreeBSD, основываясь на этом сигнале, реализует быструю повторную передачу (fast retransmission). На рис. 13.11 показана последовательность передач и подтверждений пакетов при использовании алгоритма быстрой повторной передачи во время потери одного пакета. После обнаружения трех идентичных под- подтверждений функция tcp_input() сохраняет текущие параметры соединения, имитирует тайм-аут повторной передачи для повторной отправки одного сегмента самых старых данных в очереди отправки, а затем восстанавливает текущее состояние передачи. По- Поскольку этот признак потерянного пакета является сигналом о перегрузке, оценка пре- предела буферирования сети, sndjssthresh, устанавливается в половину текущего окна. Однако, поскольку поток подтверждений не остановился, медленный старт не нужен. Если был потерян единственный пакет, выполнение быстрой повторной передачи за- заполняет дыру быстрее, чем ожидание тайм-аута повторной передачи. Потом будут по- получены подтверждение для отсутствующего сегмента плюс все помещенные в очередь до повторной передачи внеочередные сегменты, и соединение может продолжать работать нормально.
13.7. Обработка вывода TCP 645 Номер последовательное! i О 1 3 4 Время обращения Рис. 13.11. Быстрая повторная передача. Толстые, более длинные прямоугольники представляют пакеты отправляемых данных. Тонкие, более короткие линии представляют возвращенные подтверждения Даже с быстрой повторной передачей возможно, что TCP-соединение с потерян- потерянным сегментом достигнет конца окна отправки и будет вынуждено прекратить пере- передачу в ожидании подтверждения потерянного сегмента. Однако после быстрой по- повторной передачи дублированные подтверждения получаются для каждого дополни- дополнительного пакета, полученного узлом после потерянного пакета. Эти дублированные подтверждения подразумевают, что пакет покинул сеть и теперь помещен получателем в очередь. В этом случае пакет не нужно рассматривать как находящийся внутри окна перегрузки сети, возможно допуская отправку дополнительных данных, если окно получателя достаточно большое. Каждое дублированное подтверждение после быстрой повторной передачи вызывает таким образом искусственное перемещение окна пере- перегрузки вперед на размер сегмента. Если окно получателя достаточно большое, оно позволяет соединению продвинуться вперед в ходе большей части времени, когда отправитель ожидает подтверждения повторно переданного сегмента. Чтобы этот алгоритм подействовал, у отправителя и получателя должно быть дополнительное буферирование, помимо обычного произведения пропускной способности на задержку. Чтобы этот алгоритм возымел полный эффект, нужно в два раза большее пространство.
646 Глава 13. Сетевые протоколы 13.8. Протокол управляющих сообщений Интернета (ICMP) Протокол управляющих сообщений Интернета (Internet Control Message Protocol - ICMP) [Postel, 1981c] является управляющим протоколом и протоколом сообщений об ошибках для IPv4. Хотя он для операций ввода и вывода расположен поверх IPv4, во многом подобно UDP, большинство сообщений ICMP получаются и реализуются ядром. Сообщения ICMP могут также отправляться и получаться через непосредствен- непосредственный (raw) сокет IPv4 (см. раздел 12.7). Сообщения ICMP относятся к одному из трех общих классов. Один класс включает различные ошибки, которые могут произойти где-то в сети и которые могут быть сообще- сообщены отправителю пакета, вызвавшему ошибку. Такие ошибки включают ошибки маршрути- маршрутизации {сеть или хост недоступны), окончание времени жизни пакета или сообщение хостом места назначения о том, что нужный протокол или порт недоступны. Пакеты ошибок включают заголовок IPv4 плюс по крайней мере восемь дополнительных октетов заголовка, столкнувшегося с ошибкой. Второй класс сообщений можно рассматривать как управляющие сообщения маршрутизатора хосту. Примерами таких сообщений являются сообщение сдерэюивания источника, которое сообщает о потере пакетов, вызванной чрез- чрезмерным выводом; сообщение перенаправления маршрутизации, информирующее хост о том, что для хоста или сети доступен лучший маршрут через другой маршрутизатор; объ- объявления маршрутизатора, предоставляющие простой способ определения хостом своего маршрутизатора. Последний класс сообщений включает пакеты управления сетью, тес- тестирования и измерений. Эти пакеты включают запрос сетевого адреса и ответ на него, запрос маски сети и ответ на него, эхо-запрос и эхо-ответ, запрос и ответ отметки времени и запрос и ответ с общей информацией. Все действия и ответы, требуемые входящим сообщением ICMP, выполняются моду- модулем ICMP. Пакеты ICMP получаются от IPv4 через обычную точку входа протокола, поскольку ICMP имеет свой собственный номер протокола IPv4. Входная процедура ICMP обрабатывает три главных случая. Если пакет представляет ошибку, такую, как порт недоступен, сообщение обрабатывается и доставляется любому протоколу вышележащего уровня, которому может понадобиться знать об этом (например, инициировавшему соеди- соединение). Сообщения, требующие ответа (например, эхо) обрабатываются и отправляются обратно своему источнику с помощью процедуры icmp_reflect(). Наконец, если есть какие- нибудь сокеты, прослушивающие сообщения ICMP, им предоставляется копия сообщения путем вызова ripjnputQ в конце процедуры icmpJnputQ. Когда получены указания об ошибках или сдерживания источника, в структуре sockaddr создается родовой адрес. Адрес и код ошибки сообщаются процедурой icmpjnputQ элементу управляющего ввода каждого сетевого протокола pr_ctlinput(). Например, сообщение ICMP порт недоступен вызывает ошибку лишь для соединений с указанными удаленным портом и протоколом.
13.8. Протокол управляющих сообщений Интернета (ICMP) 647 Изменения в маршрутизации, на которые указывают сообщения перенаправления, обрабатываются процедурой rtredirectQ. Она удостоверяется, что маршрутизатор, от ко- которого было получено сообщение, является используемым для места назначения шлюзом следующего транзитного участка, и проверяет, что новый шлюз находится в непосредст- непосредственно подсоединенной сети. Если эти проверки успешны, соответствующим образом из- изменяются таблицы маршрутизации ядра. Если область действия нового маршрута экви- эквивалентна старому маршруту (например, оба предназначены для сети назначения), шлюз маршрута меняется на новый шлюз. Если область действия нового маршрута меньше, чем у оригинального маршрута (либо было получено перенаправление хоста при исполь- использовании маршрута сети, либо старый маршрут использовал групповой (wildcard) маршрут), в таблице ядра создается новый маршрут. Маршруты, создаваемые или изме- изменяемые перенаправлениями, помечаются флагами RTF_DYNAMIC и RTF_MODIFIED соответственно. Когда таблицы маршрутизации обновлены, протоколы уведомляются посредством pfctlinputQ с использованием кода перенаправления, а не кода ошибки. Как TCP, так и UDP игнорируют сообщения перенаправления, поскольку они не хранят ука- указатель на маршрут. Следующий пакет, отправленный через сокет, вновь назначит маршрут, выбрав новый маршрут, если он теперь является лучшим. Когда входящее ICMP-сообщение было обработано ядром, оно передается rip_input() для приема любым прямым сокетом ICMP. Прямые сокеты могут использо- использоваться также для отправки ICMP-сообщений. Низкоуровневая сетевая тестирующая программа ping работает путем отправки эхо-запросов ICMP через непосредственный сокет и прослушивания соответствующих ответов. ICMP также используется другими протоколами Интернета для создания сообще- сообщений об ошибках. UDP отправляет лишь сообщения об ошибках ICMP порт недосту- недоступен, a TCP использует для сообщения о таких ошибках другие средства. Однако IP может обнаружить множество различных ошибок, особенно на системах, используе- используемых в качестве IP-маршрутизаторов. Функция icmp_error() создает в ответ на IP-пакет сообщение об ошибке указанного типа. Большинство сообщений об ошибке включают часть оригинального пакета, вызвавшего ошибку, а также тип и код для этой ошибки. Исходный адрес для пакета ошибки выбирается в соответствии с контекстом. Если оригинальный пакет был отправлен по адресу локальной системы, в качестве источни- источника используется этот адрес. В противном случае используется адрес, который связан с интерфейсом, через который был получен пакет, как при выполнении перенаправле- перенаправления; затем можно установить в качестве исходного адреса сообщения об ошибке адрес маршрутизатора в сети, ближайшей к хосту происхождения (или общей с ним). Также, когда IP пересылает пакет через тот же самый сетевой интерфейс, через который был получен пакет, он может отправить сообщение о перенаправлении хосту происхожде- происхождения, если этот хост находится в той же самой сети. Процедура icmp_error() принимает для сообщений перенаправлений дополнительный параметр: адрес нового маршрути- маршрутизатора для использования хостом.
648 Глава 13. Сетевые протоколы 13.9. IPv6 После многих лет успешного развертывания и использования IPv4 возникло несколько проблем, которые заставили Интернет-сообщество начать работать над новыми версиями протоколов Интернета. Движущей силой этой работы было то, что у перво- первоначального Интернета начали кончаться адреса [Gross & Almquist, 1992]. Для решения этой проблемы в рамках протоколов IPv4 было предложено и реализовано несколько решений, включая подсети и бесклассовую междоменную маршрутизацию (Classless Inter-Domain Routing - CIDR) [Fuller et al.,1993; Mogul & Postel, 1985], но ни одно из них не оказалось достаточным. Было сделано несколько различных предложений по полной замене протоколов IPv4, и принятие окончательного решения потребовало нескольких лет. Работа над новым поколением протоколов Интернета продолжилась сначала 1990-х гг., но вплоть до 2003 г. протокол не был развернут какими-нибудь крупными производителями. На сегодня принятие новых протоколов было ограничено из-за огромной установленной базы IPv4, которая должна быть преобразована. FreeBSD включает сетевой домен IPv6, который содержит реализацию протоколов IPv6. Домен поддерживает полный набор протоколов от сетевого до транспортного уровней. Протоколы описаны в большом наборе RFC, начиная с Deering & Hinden [1998а]. В ходе разработки IPv6 было написано несколько реализаций с открытым ис- исходным кодом. Каждая реализация поддерживала свое подмножество полных возмож- возможностей IPv6 в соответствии с потребностями авторов. Реализация, которая имела в конце концов наиболее полный набор свойств, была разработана проектом КАМЕ [КАМЕ, 2003] и была принята FreeBSD. Полное обсуждение IPv6 выходит за рамки данной книги. В этом разделе обсуж- обсуждаются области IPv6, которые отличают его от IPv4, и те изменения, которые пришлось сделать в FreeBSD, чтобы принять их. Между IPv4 и IPv6 есть несколько значительных различий, включая: * 128-разрядные адреса на сетевом уровне; упор на автоматическое конфигурирование; встроенную поддержку протоколов безопасности. Поскольку движущим фактором перехода на новый протокол была потребность в дополнительных адресах, первым изменением, которое нужно было сделать от IPv4 к IPv6, было увеличение размера адресов. В IPv4 адрес, идентифицирующий уникаль- уникальный интерфейс в сети, является 32-разрядным. Этот размер теоретически достаточно большой для адресации свыше четырех миллиардов интерфейсов. Есть несколько причин того, почему этот теоретический максимум никогда не достигается. Первой явля- является необходимость в управлении размером таблиц маршрутизации в основных маршру- маршрутизаторах Интернета. Маршрутизация Интернета наиболее эффективна, когда взаимо- взаимодействовать с множеством адресов можно по одному адресу, адресу маршрутизатора этой
13.9. IPv6 649 сети. Если для каждого маршрута требуется свой собственный маршрут, в каждой табли- таблице маршрутизации в Интернете было бы свыше четырех миллиардов адресов, что было бы невозможно при современном состоянии сетевого аппаратного и программного обес- обеспечения. Поэтому адреса объединяются в блоки, а эти блоки назначаются провайдерам Интернета (ISP), которые затем разрезают их на меньшие блоки для своих заказчиков. Потом заказчики берут эти блоки и разделяют их дальше посредством создания подсетей и, наконец, назначают отдельные адреса конкретным компьютерам. На каждом уровне этой иерархии некоторые адреса оставляются для будущего использования, что ведет ко второму источнику расточительства IP-адресов, избыточным назначениям. Поскольку дорого и трудно повторно перенумеровывать большие инсталляции машин, заказчики запрашивают больше адресов, чем им когда-либо понадобится, в попытке предотвратить возможную необходимость в повторной нумерации своих сетей. Чрезмерное выделение привело к нескольким призывам компаниям и ISP для возврата неиспользуемых адресов [Nesser, 1996]. По этим причинам размер IP-адресов был расширен до 128 битов. Число адресов, доступных в IPv6, может быть сопоставлено с числом всех атомов во вселенной или с предоставлением каждому человеку на земле свыше миллиарда IP-адресов. Поскольку Интернет был охвачен людьми, которые не являются учеными в вычис- вычислительной технике и инженерами, главным камнем преткновения была трудность установки и сопровождения даже одного хоста в Интернете. Компании содержат команды профессионалов, выполняющих эту работу, но для небольшой компании - например, кабинета зубного врача или одиночного товарищества — задача может быть обескураживающей. Эти трудности привели разработчиков IPv6 к включению в прото- протокол нескольких разновидностей автоконфигурирования. В идеале любой, исполь- использующий IPv6 может включить компьютер, подсоединить к нему сетевой кабель и ока- оказаться в сети в течение нескольких минут. Эта цель не была достигнута, но она все же объясняет многие из проектных решений в протоколах IPv6. Даже до коммерческого успеха Интернета исследователи сетей и операторы поня- поняли, что первоначальные протоколы не предоставляли пользователям сети каких-либо форм безопасности. Причиной отсутствия безопасности было то, что средой перво- первоначального Интернета была кооперация, при которой упор был на совместном исполь- использовании информации. IPv6 включает набор протоколов безопасности (IPSec), которые также присутствуют в IPv4. Эти протоколы являются стандартной частью IPv6 и рас- рассмотрены в разделе 13.10. Адреса IPv6 128-разрядные адреса в IPv6 вынудили создать новые структуры для своего хранения и новые интерфейсы для своей обработки. Хотя работать с традиционной четверной разделенной точками нотации IPv4 (например, 128.32.1.1) достаточно просто, тексту- текстуальная запись адреса IPv6 требует немного больше работы, вот почему архитектура адресации IPv6 получила свой RFC [Deering & Hinden, 1998b].
650 Глава 13. Сетевые протоколы IPv6 определяет несколько типов адресов. Однонаправленные Подобно однонаправленным адресам в IPv4, однонаправлен- (unicast) ный адрес IPv6 является 128-разрядным числом, которое уни- уникально идентифицирует интерфейс на хосте. Групповые Адрес, идентифицирующий набор интерфейсов, обычно при- (multicast) нимающих участие в какой-либо форме группового взаимо- взаимодействия. Пакет, отправленный по этому адресу, доставляется всем интерфейсам в сети, которые привязаны к этому адресу. Альтернативные Альтернативные адреса используются для идентификации (anycast) общих служб. Сеть будет направлять пакет, отправленный по этому адресу, ближайшему интерфейсу, привязанному к этому адресу. Расстояние измеряется по числу транзитных участков, которые пакету пришлось бы сделать между источником и местом назначения. Обратите внимание, что, в отличие от IPv4, IPv6 не имеет понятия о широковеща- широковещательном (broadcast) адресе, который принимается всеми интерфейсами определенного соединения. Задачей широковещательных адресов в IPv4 является предоставление хостам способа обнаружения служб, даже когда у них еще нет своего IP-адреса. Широко- Широковещательные пакеты являются расточительными, потому что они пробуждают каждый хост на линии связи, даже если этот хост не предоставляет соответствующей службы. Вместо использования широковещательного адреса в качестве способа нахождения хостом службы, IPv6 использует широко известные групповые адреса для каждой предоставляемой службы. Хосты, готовые предоставить службу, регистрируются для прослушивания хорошо известного группового адреса этой службы. Когда адрес IPv6 записывается, он представляется в виде набора разделяемых двоеточиями шестнадцатеричных байтов. Значение между каждым набором двое- двоеточий представляет собой 16-разрядное значение. Например, строка: 1080:0:0:8:0:0:200С:417А представляет однонаправленный адрес в сети IPv6. При записи в виде текста часть адреса, содержащая нули, может быть сокращена с помощью двойного двоеточия: 1080::8:0:0:200С:417А Первый набор двух нулей в данном конкретном адресе опущен. Когда адрес сокра- сокращается, может быть опущен лишь один набор нулей. Для опускания последовательности нулей нужно либо удалить все нули, либо не удалять ни одного. Следующее является примером неправильных сокращений предыдущего адреса: 1080::0:8:0:0:200С:417А 1080::8::200С:417А
13.9. IPv6 651 В первой строке в группу не включен весь первый набор нулей. Вторая строка является двусмысленной, поскольку вы не можете сказать, как распределены четыре нуля между двумя областями, помеченными двойными двоеточиями. Однонаправленные и групповые адреса различаются по установленным в начале адреса битам. Все однонаправленные адреса начинаются битами 100, а групповые адреса начинаются 1111 1111. Примерами наиболее типичных адресов являются пока- показанные в табл. 13.5. Неопределенный адрес используется хостом, которому еще не был назначен адрес, когда он находится в процессе установления своего сетевого ин- интерфейса. Адрес запрашиваемого узла используется в ходе определения соседа, что рассматривается далее в данном разделе. Частью багажа, который не был перенесен из IPv4 в IPv6, является понятие о клас- классах сетей в адресах. IPv6 всегда использует стиль CIDR для отметки границ между сетевым префиксом (который далее называется просто префиксом) и идентифика- идентификатором интерфейса, который идентифицирует интерфейс на определенном хосте. Следующие примеры определяют одну и ту же сеть, имеющую 60-битный префикс. 1234:0000:0000:1230:0000:0000:0000:0000/60 1234: :1230:0:0:0:0/60 1234:0:0:1230::/60 Табл. 13.5. Хорошо известные адреса IPv6 Адрес Описание FF02:: 1 Групповой адрес всех узлов (локального соединения) FF02::2 Групповой адрес всех маршрутизаторов (локального соединения) FF05::2 групповой адрес всех маршрутизаторов (локального сайта) FF02:0:0:0:0:l:FF00::/104 Адрес требуемого узла ::1 Адрес возвратной петли :: Неопределенный адрес Форматы пакета IPv6 Когда проектировался IPv6, одной из целей было снижение объема работы, необходи- необходимой маршрутизатору для пересылки пакета. Это снижение было выполнено сле- следующим образом. Упрощение заголовка пакета. Сравнивая заголовок пакета IPv6 на рис. 13.12 с заго- заголовком IPv4, показанным на рис. 13.4, мы видим, что в заголовке IPv6 на 4 поля меньше, и лишь одно из них нужно менять в ходе пересылки пакета: число пере- пересылок (hop limit). Число пересылок уменьшается каждый раз, когда пакет пересы- пересылается маршрутизатором, до тех пор, пока число пересылок не достигнет 0, когда пакет уничтожается.
652 Глава 13. Сетевые протоколы 34 11 12 15 16 23 24 31 Версия Класс трафика Размер полезной нагрузки - Метка потока Следующий заголовок Исходный адрес Адрес места назначения Число пересылок РИС. 13.12. Заголовок пакета IPv6 * Заголовок пакета имеет фиксированный размер. Заголовок IPv6 никогда не со- содержит внутри себя каких-либо опций или заполнения. Обработка опций в IPv4 является дорогостоящей операцией, которая должна быть выполнена каждый раз при отправке, пересылке или получении пакета IPv4. * IPv6 решительно не рекомендует использовать фрагментацию на сетевом уровне. Избежание фрагментации пакетов упрощает пересылку пакетов, а также обработку хостами (хосты являются местом, где происходит повторная сборка фрагментиро- ванных пакетов). Все эти упрощения уменьшают интенсивность вычислений при обработке пакетов IPv6 по сравнению с обработкой пакетов IPv4. Полное удаление возможностей, ко- которые были неудобными, таких, как опции или фрагментация, уменьшило бы призна- признание IPv6. Вместо этого разработчики предложили добавить эти и несколько других возможностей, не загружая заголовок пакета. Дополнительные возможности и прото- протоколы более высокого уровня в IPv6 обрабатываются заголовками расширения (exten- (extension headers). Пример пакета показан на рис. 13.13. Все заголовки расширения начи- начинаются с поля следующего заголовка, а также 8-разрядного поля длины, показы- показывающей размер расширения в единицах по 8 байтов. Все пакеты выровнены по 8-байт- ной границе. Заголовок IPv6 и заголовки расширения образуют цепочку, связанную вместе полем следующего заголовка, присутствующим в каждом из них. Поле сле- следующего заголовка идентифицирует тип данных,, следующих непосредственно за обрабатываемым в настоящий момент заголовком, и является прямым потомком поля протокол в пакетах IPv4. Пакеты TCP обозначаются в обоих полях одним и тем же номером F). Маршрутизаторы при пересылке пакетов не просматривают ни один из заголовков расширения, кроме заголовка транзитных (hop-by-hop) опций, который
13.9. IPv6 653 предназначен для использования маршрутизаторами. Каждый из заголовков расшире- расширения также кодирует каким-нибудь способом свою длину. Пакеты TCP не знают о пере- пересылке через IPv6 и используют свой оригинальный формат заголовка пакета, что означает, что они не содержат ни поля следующего заголовка, ни размера. Размер вычисляется, как в IPv4. Следующий: О Следующий: 51 Длина: 1 Следующий: 50 Длина: 4 Следующий: 6 Длина: 6 IP Транзитный АН ESP TCP Рис. 13.13. Заголовки расширения. Обозначения: АН - заголовок аутентификации (тип 51); ESP - полезная нагрузка инкапсуляции безопасности (тип 50) Хостам требуется кодировать и декодировать заголовки расширения в определен- определенном порядке, чтобы не было нужно когда-либо возвращаться в просмотре пакета назад. Порядок, в котором должны появляться заголовки, показан на рис. 13.13. Транзитный заголовок (тип 0) должен следовать непосредственно за IP-заголовком таким образом, чтобы маршрутизаторы могли легко его находить. Заголовки аутентификации (АН) и полезной нагрузки инкапсуляции безопасности (ESP) используются протоколами безопасности, которые обсуждаются в разделе 13.10, и должны идти до заголовка TCP и данных, поскольку информация в заголовках безопасности должна быть получена до того, как они могут будут использованы для аутентификации и расшифровки заголовка TCP и данных. Изменения API сокетов Политикой Проблемной группы проектирования Интернета (IETF) всегда было опре- определение протоколов, а не реализаций. Для IPv6 это правило было повернуто таким образом, чтобы разработчики приложений могли бы иметь API, с которым они могли использовать для программирования и ускорения перемещения приложений на IPv6. Разработчики взяли первоначальный интерфейс сокетов, как он был реализован в BSD, и определили расширения [Gilligan et al., 1999], которые включены в FreeBSD. В рас- расширении API сокетов было несколько целей. Изменения не должны разрушать существующих приложений. Ядро должно обес- обеспечивать обратную совместимость как для исходного кода, так и для двоичного файла. Минимизировать число изменений, необходимых для подготовки и запуска при- приложений IPv6. Обеспечить способность к взаимодействию между хостами IPv6 и IPv4.
654 Глава 13. Сетевые протоколы * Адреса, передаваемые в структурах данных, должны быть выровнены по границе 64 битов, чтобы обеспечить оптимальную производительность на 64-разрядных архитектурах. Добавление нового типа адреса было просто, поскольку все процедуры, обрабаты- обрабатывающие адреса, такие, как bind, accept, connect, sendto и recyfrom, уже работают с адре- адресами как непрозрачными объектами. Для хранения информации о конечных точках IPv6 была определена новая структура данных sockaddr_in6, как показано на рис. 13.14. Структура sockaddr_in6 подобна структуре sockaddrjn, представленной в разделе 11.4. Она содержит размер структуры, семейство (которое всегда равно AF_INET6), 16-разрядный порт, который идентифицирует конечную точку транспорт- транспортного уровня, идентификатор потока, адрес сетевого уровня и идентификатор области видимости. Много предложений было сделано для использования информации о потоке и идентификатора области видимости, но эти поля в настоящее время не используются. Информация о потоке предназначена для запроса особой обработки для пакетов внутри сети. Например, аудиопоток реального времени мог бы иметь опреде- определенную метку потока, чтобы ему можно было дать больший приоритет по сравнению с менее критичным трафиком. Хотя идею просто объяснить, ее реализация в сети, где ни один объект не контролирует все оборудование, проблематично. В настоящее время нет способа согласования того, что означает метка потока, когда она покидает одну сеть и входит в другую. Пока эта головоломка не будет решена, метка потока будет ис- использоваться лишь при развертывании частных сетей и в исследовательских лабора- лабораториях. IPv6 определяет несколько областей действия (scopes), в которых можно использо- использовать адреса. В IPv4 все адреса имели глобальную область действия, что означает, что они были действительны вне зависимости от того, где они находились в Интернете. Определенные области действия в IPv6 являются локальными для соединения, локаль- локальными для сайта, локальными для организации и глобальными. Адрес с меньшей обла- областью действия не может быть передан в более широкую область действия. Например, локальный для соединения адрес не будет пересылаться маршрутизатором в другое со- соединение. Работа со 128-разрядными адресами вручную неуклюжа и чревата ошибками. Ожидается, что приложения будут иметь дело почти исключительно с именованными объектами для IPv6 путем использования системы доменных имен (DNS) [Thomson & Huitema, 1995]. Первоначальный API для поиска адреса по имени хоста, gethost- bynameQ, был специфичным для домена AF_INET, поэтому для поиска адреса IPv6 сданным именем был добавлен новый API. Когда клиент хочет найти сервер, он использует процедуру gethostbyname2(), которая принимает в качестве одного из своих аргументов семейство адресов. struct hostent * gethostbyname2( char *name,
13.9. IPv6 655 sin6_len AFJNET6 sin6_port sin6_- owinfo sin6_addr sin6_scope_id I 2 6 РИС. 13.14. Структура адреса сокета домена IPv6 int address_family); Этот новый API может работать с адресами либо IPv4, либо IPv6, поскольку второй аргумент определяет семейство адресов, а возвращаемое значение является указате- указателем на структуру, содержащую тип возвращаемого адреса. Автоконфигурирование Одна из целей внедрения IPv6 - сделать добавление компьютера к сети более простым процессом. Механизмы и протоколы, которые используются для достижения этой цели, называются автоконфигурированием. Чтобы хост был автоматически отконфи- гурирован, он должен уметь находить информацию в сети без каких-либо предвари- предварительных данных. Хост должен быть способен автоматически выяснить свой собствен- собственный адрес, адрес своего первого транзитного маршрутизатора и сетевой префикс соединения, к которому он подключен. Для взаимодействия с другими хостами на своей линии связи и с маршрутизатором следующего транзитного участка хосту нужны адреса канального уровня для этих других систем. На эти вопросы отвечает протокол определения соседа (neighbor-discovery), который является частью IPv6 и определен в Narten et al. [1998]. Определение соседа либо улучшает, либо замещает несопоставимые протоколы, которые были частью IPv4, и объединяет их в набор сооб- сообщений ICMPv6 [Conta & Deering, 1998]. Поскольку определение соседа использует ICMPv6 и поэтому работает поверх уровня IPv6, пока IPv6 работает в определенном типе соединения, также будут работать службы определения соседа и автоконфи- автоконфигурирования. Мы рассмотрим два аспекта протокола определения соседа. Первым будет определение маршрутизатора - как узел находит свой следующий маршрутиза- маршрутизатор, а вторым будет определение самого соседа. Хост находит свой следующий маршрутизатор двумя различными способами. Маршрутизаторы IPv6 периодически отправляют сообщения объявления маршрутиза- маршрутизатора по групповому адресу всех узлов. Формат сообщения объявления маршрутиза-
656 Глава 13. Сетевые протоколы тора показан на рис. 13.15. Все хосты, отконфигурированные для получения этих груп- групповых пакетов, увидят объявления маршрутизатора и обработают их. Хотя объявления маршрутизатора посылаются довольно часто для обеспечения того, чтобы все хосты в соединении знали о расположении своего маршрутизатора и знали, когда он терпит неудачу, этого механизма недостаточно для включения в соединение нового хоста. Когда хост впервые подключается к сети, он посылает сообщение запроса маршрути- маршрутизаторов (router-solicitation) по групповому адресу всех маршрутизаторов. Маршрути- Маршрутизатор, получивший действительный запрос, должен немедленно отправить в ответ объ- объявление маршрутизатора. Объявление будет отправлено по групповому адресу всех узлов, если только маршрутизатор не знает, что он может успешно отправить однона- однонаправленный запрос хосту, отправившему запрос маршрутизатора. Маршрутизатор может послать опцию с объявлением, которое включает адрес маршрутизатора каналь- канального уровня. Если включена опция адреса канального уровня, принимающим хостам определение соседа перед отправкой пакетов маршрутизатору будет не нужно. Каждый хост поддерживает связанный список элементов своих маршрутизаторов. Отдельный элемент маршрутизатора показан на рис. 13.16. Каждый раз, когда получе- получено объявление маршрутизатора, оно передается процедуре defrtrlistjupdateQ, которая проверяет сообщение на предмет того, представляет ли оно новый маршрутизатор, и если да, помещает новый элемент в начало списка маршрутизатора по умолчанию. Каждое сообщение объявления маршрутизатора содержит поле времени жизни. Это время жизни контролирует, как долго элемент может оставаться в списке маршрутиза- маршрутизатора по умолчанию. Каждый раз, когда defrtrlist_update() получает объявление маршрутизатора для маршрутизатора, который уже присутствует в списке маршрути- маршрутизатора по умолчанию, время годности этого маршрутизатора увеличивается. О 78 15 16 31 Тип Число пересылок Код М О Зарезервировано Контрольная сумма Время жизни маршрутизатора Время достижения Повторная передача РИС. 13.15. Объявление маршрутизатора. Обозначения: М-контролируемый флаг; О-другой флаг Для того чтобы хост определил, находится ли следующий транзитный участок, в который должен быть отправлен пакет, в том же самом соединении, что и сам хост, он должен знать префикс соединения. Исторически префикс вручную конфигурировался на каждом интерфейсе в системе, теперь же он обрабатывается как часть обнаружения маршрутизатора.
13.9. IPv6 657 Информация о префиксе отправляется в виде опции внутри объявления маршрути- маршрутизатора. Формат опции префикса показан на рис. 13.17. Каждая опция префикса несет 128-разрядный адрес. Число действительных битов в этом адресе дается в поле длины префикса опции. Например, префикс, приведенный в предыдущем примере, был бы отправлен с опцией префикса 1234:0000:0000:12 30:0 00 0:0000:0000:0000 закодированной в поле префикса, и записанным в поле длины префикса значением 60. Каждый префикс действителен лишь для периода, обозначенного действительным временем жизни. Последующие объявления маршрутизатора, содержащие опции пре- префикса, будут иметь действительные времена жизни, перемещенные в будущее. Когда хост обнаруживает, что у него префикс с истекшим временем жизни, префикс удаляет- удаляется из интерфейса, с которым он связан, и истекший префикс больше не определяет, находится ли адрес назначения в локальном соединении. rtaddr -ags гtlife time expire struct nd_defrouter Рис. 13.16. Элемент маршрутизатора 15 16 23 24 31 Тип Размер Размер префикса О Зарезер виро ваш Действительное время жизни Предпочтительное время жизни Зарезервировано Префикс Рис. 13.17. Опция префикса. Обозначения: О - флаг соединения; А - флаг автоматизации
658 Глава 13. Сетевые протоколы Когда у хоста есть пакет для другого хоста в его соединении, включая его маршру- маршрутизатор следующего транзитного участка, он должен найти адрес канального уровня хоста, которому он хочет отправить пакет. В IPv4 этот процесс обрабатывался протоко- протоколом разрешения адреса (ARP); см. раздел 12.8. Проблема с ARP в том, что он является специфическим для Ethernet и содержит в себе предположения об адресах канального уровня, которые затрудняют его приспособление к другим видам соединений. Хост изучает адреса канального уровня своих соседей, используя пару сообщений: запрос соседа (neighbor solicitation) и объявление соседа (neighbor advertisement). Когда ядро хочет отправить пакет IPv6 на другой хост, пакет в конечном счете проходит через процедуру ip6_output(), которая выполняет различные проверки пакета, чтобы убе- убедиться, что он подходит для передачи. Все сформированные должным образом пакеты передаются затем вниз модулю определения соседа через процедуру nd6_output(), которая обрабатывает отображение адреса IPv6 на адрес канального уровня. Когда у пакета есть правильный адрес назначения канального уровня, он передается драй- драйверу сетевого интерфейса через процедуру драйвера if_output(). Отношения между различными модулями протокола показаны на рис. 13.18. У модуля определения соседа нет процедуры nd_input(), поскольку он получает сообщения через модуль ICMPv6. Эта инверсия разбиения протокола на уровни дает протоколу определения соседа возможность быть независимым от канального уровня. В IPv4 модуль ARP вставляется через ловушки в модуль сетевого интерфейса таким образом, что он может отправлять и получать сообщения. Соединение между ARP и нижележащим интерфей- интерфейсом канального уровня означает, что код ARP должен понимать каждый тип соедине- соединения, который система поддерживает. UDPv6 udp6_input() ip6_input() к TCPv6 ip6_output() г \ i tcp6_input() jrnipfi ir\put() IPv6 i \ «* ICMPv6 ip6_output() nd6_output() nd6 ra input0 NDv6 «4 ¦ vifp->if_output0 пао_па_трш(, Сетевой интерфейс РИС. 13.18. Отношения модуля IPv6
13.9. IPv6 659 Адреса канального уровня сохраняются в таблице маршрутизации, и именно здесь nd6_output() пытается искать адрес канального уровня для пакетов, которые ему пере- передаются. Когда хост еще не знает адреса канального уровня для места назначения, исхо- исходящий пакет должен быть сохранен до тех пор, пока не завершится определение сосе- соседа. Каждая структура rtentiy содержит указатель на информацию канального уровня. Когда элемент маршрутизации сохраняет информацию для IPv6, он указывает на структуру Ilinfo_nd6, как показано на рис. 13.19. До отправки запроса соседа пакет, который пытается передать ядро, сохраняется путем записи в поле \n_hold структуры llinfojide указателя на него. Одновременно можно сохранить лишь один пакет, поэтому, если система пытается передать другой пакет на то же самое место назначения до того, как получено объявление соседа, первый пакет будет потерян, и вышележащим уров- уровням придется передать его повторно. Процедура nf6_output() не ожидает объявления соседа, а возвращается. Когда получен ответ в виде объявления соседа, он обрабатыва- обрабатывается модулями IPv6 и ICMPv6 и в конечном счете передается в модуль определения соседа посредством вызова процедуры nd6_na_input(), как показано на рис. 13.18. Проце- Процедура nd6_na_input() записывает адрес канального уровня и проверяет, хранился ли пакет для передачи в это место назначения. Если имеется пакет, ожидающий передачи, вызывается процедура nd6_output() с сохраненным пакетом. Адрес канального уровня для места назначения сохраненного пакета теперь в системе, поэтому nd6_output() копирует адрес канального уровня в цепочку mbuf и вызывает процедуру if_output() сетевого интерфейса для передачи пакета. Injrt Injiold ln_asked ln_expire ln_state Injrouter ln_byhint struct Uinfo_nd6 РИС. 13.19. Информация канального уровня об определении соседа Раз в секунду процедура nd6_timer() обходит список определения соседа канального уровня, а также списки маршрутизатора по умолчанию и интерфейсов и удаляет элементы, у которых истекло время хранения. Удаление устаревших элементов предотвращает попытку системы отправить данные на хост, который исчез или стал недоступным.
660 Глава 13. Сетевые протоколы 13.10. Безопасность В разделе 13.9 мы упомянули, что набор протоколов безопасности был разработан в виде части IPv6. Эти протоколы были написаны как независимые от определенной версии IP, поэтому они были интегрированы в IPv4 и IPv6. На сетевом уровне были до- добавлены механизмы безопасности, чтобы предоставить аутентификацию таким обра- образом, что хост может знать, с кем он взаимодействует. Было добавлено шифрование таким образом, чтобы данные можно было скрыть от не пользующихся доверием объ- объектов при их пересечении Интернета. Протоколы, совместно предоставляющие безо- безопасность в рамках сетевого уровня, называются IPSec. Помещение протоколов безопасности в сетевой уровень внутри стека протоколов не было произвольным решением. Безопасность можно поместить почти на любой уровень в пределах взаимодействующей системы. Например, протокол защищенных сокетов (SSL) поддерживает безопасность взаимодействия на уровне приложения и дает клиенту и серверу возможность безопасного взаимодействия через произволь- произвольную сеть. На другом конце спектра находятся различные протоколы, поддерживающие безопасность в беспроводных сетях, которые работают на канальном уровне. Решение поместить безопасность на сетевой уровень было сделано по нескольким причинам. * IP-протоколы действуют в качестве унифицированной платформы, в которую помещаются протоколы безопасности. Различия в лежащей в основе аппара- аппаратуре, такие, как различные типы сетевых носителей, не должны приниматься во внимание при разработке и реализации IPSec, поскольку, если часть оборудова- оборудования может отправлять и принимать IP-дейтаграммы, она может также под- поддерживать IPSec. Пользователям не нужно предпринимать какие-либо действия для использования протоколов безопасности. Поскольку IPSec реализована на сетевом, а не приклад- прикладном уровне, пользователи, работающие с сетевыми программами, автоматически работают безопасно, если их системные администраторы соответствующим обра- образом настроили систему. Управление ключами может осуществляться автоматическим способом системны- системными демонами. Самой трудной проблемой при развертывании протоколов сетевой безопасности является выдача и отмена ключей, использующихся для шифрова- шифрования данных. Поскольку IPSec обрабатывается в ядре и обычно не имеет дела с пользователями, можно создать демоны для выполнения работы по управлению ключами. Безопасность в контексте IPSec означает несколько вещей. Возможность доверять тому, что хост является именно тем, за который он себя выдает (аутентификация).
13.10. Безопасность 661 # Защиту против повторного использования старых данных. Конфиденциальность данных (шифрование). Предоставление архитектуры безопасности для протоколов Интернета является сложной проблемой. Соответствующие протоколы освещаются в нескольких RFC, а обзор приведен в Kent & Atkinson [1998a]. FreeBSD содержит две реализации IPSec. Одна происходит из базы кода КАМЕ, а другая, известная как Быстрая IPSec, является переработкой базы кода КАМЕ таким образом, что он может работать с криптографической подсистемой OpenBSD [Leffler, 2003a]. Наибольшим отличием между этими двумя базами кода является то, что код Быстрой IPSec не имеет каких-либо встроенных в себя криптографических алгоритмов и полно- полностью зависит от криптографической подсистемы для выполнения работы по шифрова- шифрованию, расшифровке и другому манипулированию данными. Хотя код КАМЕ был первой доступной реализацией протоколов IPSec и по-прежнему широко используется, мы обсу- обсудим код Быстрой IPSec, поскольку он дает нам возможность объяснить аппаратную криптографическую подсистему, которая была добавлена в FreeBSD. Обзор IPSec Протоколы, составляющие IPSec, предоставляют инфраструктуру безопасности для использования хостами и маршрутизаторами в Интернете. Службы безопасности, такие, как аутентификация и шифрование, доступны между двумя хостами, между хостом и маршрутизатором или между двумя маршрутизаторами. Когда любые два элемента сети (хосты или маршрутизаторы) используют для безопасного взаимодействия IPSec, говорят, что между ними ассоциации безопасности (security association - SA). Каждая SA является однонаправленной, что означает, что трафик между двумя точками защищен лишь в направлении, в котором была установлена SA. Для полностью защищенного со- соединения необходимы две SA, по одной в каждом направлении. SA уникально идентифицируются своими адресами места назначения, используе- используемым протоколом безопасности и индексом параметра безопасности (security-parameter index -SPI), который представляет собой 32-разрядное значение, которое различно среди нескольких SA, завершающихся на том же самом хосте или маршрутизаторе. SPI являет- является ключом, используемым для поиска соответствующей информации в базе данных ассо- ассоциаций безопасности, которая поддерживается каждой системой, запускающей IPSec. SA может использоваться в двух режимах. В транспортном рео/симе защищается часть заголовка IP, а также заголовок IPSec и данные. IP-заголовок защищен лишь час- частично, поскольку он должен проверяться промежуточными маршрутизаторами вдоль пути между двумя хостами, и невозможно или нежелательно требовать от каждого воз- возможного маршрутизатора, чтобы на нем работали протоколы IPSec. Одной из причин работы протоколов безопасности «из конца в конец» является то, что не нужно доверять промежуточным маршрутизаторам данные, которые они обрабатывают. Другой причиной является то, что протоколы безопасности часто являются затратными в вычислительном
662 Глава 13. Сетевые протоколы плане, а у промежуточных маршрутизаторов часто нет вычислительной мощности для дешифровки и повторного шифрования каждого пакета перед его пересылкой. Поскольку в транспортном режиме защищена лишь часть IP-заголовка, этот вид SA предоставляет защиту лишь протоколов вышележащего уровня, тех, которые полностью инкапсулированы в секции данных пакета, такого, как UDP и TCP. На рис. 13.20 показана SA транспортного режима от хоста А до хоста D, а также результирующий пакет. Хост А подготавливает обычный IP-пакет с хостом D в качестве места назначения. Затем он добав- добавляет заголовок IPSec и данные. В конечном счете он применяет любой протокол безопасно- безопасности, который был выбран пользователем, и отправляет пакет, который идет через маршру- маршрутизатор В к маршрутизатору С и, наконец, к хосту D. Хост D расшифровывает пакет, оты- отыскивая протокол безопасности и ключи в своей базе данных ассоциации безопасности. Host A Router В Router С HostD Безопасные данные Место назначения: Источник: Протокол: хост D ESP/AH хост А SPI IP-заголовок Заголовок АН или ESP Данные Рис. 13.20. Защищенное соединение в транспортном режиме. Обозначения: АН - заголовок аутентификации; ESP - полезная нагрузка инкапсулирования безопасности; SPI - индекс параметра безопасности Другим режимом является режим туннеля, показанный на рис. 13.21, где весь пакет помещается внутри туннеля 1Р-через-1Р [Simpson, 1995]. При туннелировании весь пакет, включая все заголовки и данные, помещается в качестве данных внутри другого пакета и отправляется между двумя точками. Хост А хочет отправить пакет хосту D. Когда пакет достигает маршрутизатора В, он помещается в безопасный тун- туннель между маршрутизатором В и маршрутизатором С. Весь первоначальный пакет помещается внутрь нового пакета и защищается. Внешний IP-заголовок идентифи- идентифицирует лишь конечные точки туннеля (маршрутизатор В и маршрутизатор С) и не выдает информацию заголовка оригинального пакета. Когда пакет достигает конца туннеля в маршрутизаторе С, он расшифровывается, а затем отправляется на свое первоначальное место назначения на хосте D. В данном примере ни хост А, ни хост D не знают ни о том, что данные были зашифрованы, ни о том, что они работали с прото- протоколами IPSec для участия в этом защищенном соединении.
13.10. Безопасность 663 Host A Router В Router С HostD Безопасные данные Назначения: маршрутизатор С Источник: маршрутизатор В Протокол: AH/ESP SPI Назначения: хост D Источник:хост А Протокол: TCP/UDP Внешний IP Заголовок АН или ESP Внутренний IP Данные Рис. 13.21. Защищенное соединение в режиме туннеля. Обозначения: АН - заголовок аутентификации; ESP - полезная нагрузка инкапсуляции безопасности; SPI - индекс параметра безопасности Режим туннеля используется лишь для взаимодействий хост-маршрутизатор или маршрутизатор-маршрутизатор и чаще всего встречается в реализации виртуальных частных сетей, которые объединяют две частные сети или соединяют пользователей к корпоративным LAN через общедоступный Интернет. Протоколы безопасности Есть два протокола безопасности, определенных для использования с IPSec: заголовок безопасности (authentication header- АН) и полезная нагрузка инкапсулирования безо- безопасности (encapsulating-security payload- ESP), каждый из которых предоставляет свои службы безопасности [Kent & Atkinson, 1998b; Kent & Atkinson, 1998c]. Оба протокола используются с IPv4 и IPv6 без изменения их заголовков. Это двойное использование возможно благодаря тому, что заголовки пакетов являются в действительности заго- заголовками расширения IPv6, которые соответствующим образом кодируют информацию о других протоколах, следующих за ними в пакете. Протокол АН предоставляет основанную на пакетах службу аутентификации, а также защиту от атакующего, пытающегося повторно использовать старые данные. Чтобы понять, как АН обеспечивает безопасность, легче всего взглянуть на заголовок его пакета, показанный на рис. 13.22. Поле следующего заголовка идентифицирует тип паке- пакета, который следует за текущим заголовком. Поле следующего заголовка использует то же самое значение, как значение в поле протокола пакета IPv4: 6 для TCP, 17 для UDP и 1 для ICMP. Размер полезной нагрузки определяет число 32-разрядных слов, которые содержатся в заголовке аутентификации, минус 2. Искусственное удаление 2 из этого числа происходит из спецификации для заголовков расширения IPv6. SPI был только что объяснен и представляет собой 32-разрядное число, которое используется каждой конечной точкой для поиска соответствующей информации об ассоциации безопасности.
664 Глава 13. Сетевые протоколы О 7 8 15 16 31 Следующий заголовок Размер полезной нагрузки Зарезервировано Индекс параметров безопасности (SPI) Поле номера последовательности Данные аутентификации (переменный) Рис. 13.22. Заголовок аутентификации Аутентификация предоставляется путем вычисления для пакета значения проверки целостности (integrity-check value - ICV). Если АН используется в транспортном режиме, защищается лишь часть IP-заголовка, поскольку некоторые из полей изменяются промежуточными маршрутизаторами в ходе пересылки, и эти изменения непредска- непредсказуемы для отправителя. В режиме туннеля защищается весь заголовок, поскольку он инкапсулирован в другой пакет, a ICV вычисляется для оригинального пакета. ICV вычисляется с использованием алгоритма, определенного SPI, с сохранением получен- полученного результата в поле данные-аутентификации заголовка аутентификации. Получа- Получатель использует тот же самый алгоритм, запрошенный SPI для вычисления ICV-пакета, который он получил, и сравнивает это значение со значением в поле данных аутенти- аутентификации пакета. Если значения те же самые, пакет принимается; в противном случае он уничтожается. Одной из возможных атак на коммуникационный канал является отправка новых или фальшивых данных, как если бы они происходили из подлинного источника, что называется атакой повторного использования данных (replay attack). Чтобы защититься от атаки повторного использовании данных, протокол АН использует для уникальной идентификации каждого пакета, передаваемого через SA, поле номера последователь- последовательности. Этот номер последовательности отличается от поля с тем же названием в TCP. Когда установлен SA, как отправитель, так и получатель устанавливают в качестве номера последовательности ноль. Отправитель увеличивает номер последовательно- последовательности перед отправкой пакета. Получатель реализует скользящее окно фиксированного размера, причем его левая граница является наименьшим номером последовательно- последовательности, который он видел и подтвердил, а правая граница является наибольшим. Когда получен новый пакет, его номер последовательности проверяется с окном с тремя возмож- возможными исходами. Номер последовательности пакета меньше, чем номер левой кромки окна, и пакет уничтожается.
13.10. Безопасность 665 Номер последовательности пакета находится в пределах окна. Проверяется, не является ли пакет дубликатом, и если да, он уничтожается. Если пакет не является дубликатом, он вставляется в окно. Номер последовательности пакета находится справа от текущего окна. Проверяется ICV, и если он правильный, окно перемещается вправо для включения нового значе- значения номера последовательности. Когда номер последовательности переполняется после примерно 4 миллиардов пакетов, ассоциация безопасности должна быть отброшена и запущена снова. Этот повторный запуск является лишь небольшим неудобством, поскольку на скоростях гигабитного Ethernet 83 000 пакетов в секунду для переполнения номера последова- последовательности безопасности требуется свыше 14 часов. Все отправители предполагают, что получатель использует защиту от повторного использования данных, и всегда увеличивают номер последовательности, но для получателя не обязательно реализовать защиту от повторного использования данных, и она может быть выключена по усмотрению оператора принимающей системы. 0 7: 15 16 23 24 31 Индекс параметров безопасности (SPI) Поле номера последовательности Полезные данные (переменный) Заполнение @ - 255 байтов) Размер заполнения Следующий заголовок Данные аутентификации (переменный) Рис. 13.23. Инкапсулирование заголовка протокола безопасности Помимо служб, предоставляемых АН, ESP обеспечивает также конфиденциаль- конфиденциальность с использованием шифрования. Как и в случае АН, легче всего понять ESP, если мы исследуем заголовок пакета, показанный на рис. 13.23. Заголовок ESP содержит все те же самые поля, которые были в заголовке АН, но он добавляет три новых. Зашифрованные данные, отправленные с использованием ESP, хранятся в поле полезных данных пакета. Поле заполнения, которое следует за данными полезной нагрузки, может использоваться для трех целей.
666 Глава 13. Сетевые протоколы # Алгоритм шифрования мог бы потребовать, чтобы данные для шифрования пред- представляли кратное число байтов. К шифруемым данным добавляются данные запол- заполнения, чтобы блок данных имел нужный размер. Заполнение могло бы понадобиться для выравнивания соответствующим образом некоторой части пакета. Например, поля размер заполнения и следующий заголовок должно быть выровнено в пакете справа, а поле данные аутентификации должны быть выровнены по границе 4 байтов. Заполнение может также использоваться для скрывания первоначального размера полезной нагрузки в попытке предотвратить получение атакующим информации путем наблюдения за потоком трафика. Управление ключами Приложения уровня пользователя не могут использовать IPSec тем же способом, каким они используют транспортные протоколы наподобие UDP и TCP. Например, приложение не может открыть безопасный сокет с другой конечной точкой, используя IPSec. Вместо этого все SA хранятся в ядре и управляются с использованием нового домена и семейства протоколов, называемых PF_KEY_V2 [McDonald et al., 1998]. Автоматизированное распределение ключей для использования в IPSec управляется протоколом Интернета для обмена ключами (Internet key exchange - IKE) [Harkins & Carrel, 1998]. Демоны уровня пользователя, реализующие протокол IKE, такие, как Racoon, взаимодействуют с ядром, используя сокеты PF_KEY_V2 [Sakane, 2001]. Поскольку эти демоны реализованы не в ядре, они выходят за рамки данной книги. Табл. 13.6. Сообщения PFJCEY Тип сообщения Описание SADB_GETSPI Получить от ядра уникальный индекс безопасности SADB_UPDATE Обновить существующую ассоциацию безопасности SADB_ADD Добавить новую ассоциацию безопасности с известным индексом безопасности SADB_DELETE Удалить существующую ассоциацию безопасности SADB_GET Получить информацию об ассоциации безопасности SADB_ACQUIRE Посылается демонам уровня пользователя, когда ядру нужно больше информации SADB_REGISTER Сообщить ядру, что данное приложение может предоставить ин- информацию о безопасности SADB_EXPIRE Отправляется ядром приложению, когда срок SA истекает SADB_FLUSH Поручить ядру очистить все SA определенного типа SADB_DUMP Поручить ядру выгрузить все сведения о SA в вызывающий сокет SADB_X_PROMISC Данное приложение хочет видеть все сообщения PF_KEY
13.10. Безопасность 667 Табл. 13.6. Сообщения PFKEY (Окончание) Тип сообщения Описание SADB_X_PCHANGE Сообщение отправлено пассивным наблюдателям SADB_X_SPDUPDATE Обновить базу данных политики безопасности (SPDB) SADB_X_SPDADD Добавить элемент в SPDB SADB_X_SPDDELETE Удалить элемент из SPDB по индексу политики SADB_X_SPDGET Получить элемент из SPDB SADB_X_SPDACQUIRE Сообщение, отправляемое ядром для получения SA и политики SADB_X_SPDDUMP Поручить ядру выгрузить свою базу данных политики в вызы- вызывающий сокет SADB_X_SPDFLUSH Сбросить на диск базу данных политики SADB_X_SPDSETIDX Добавить элемент в SPDB по его индексу политики SADB_X_SPDEXPIRE Сообщить прослушивающему сокету, что срок действия элемента SPDB истек SADB_X_SPDDELETE2 Удалить элемент SPDB по идентификатору политики Обозначения: SA - ассоциация безопасности; SADB - база данных ассоциаций безопасности; SPDB - база данных политики безопасности. Приложения уровня пользователя взаимодействуют с базой данных безопасности путем открытия сокета типа PF_KEY. Соответствующего семейства адресов AF_KEY нет. Сокеты ключей основаны на реализации сокета маршрутизации и действуют во многом подобно сокету маршрутизации. В то время как API сокетов маршрутизации манипулирует таблицей маршрутизации ядра, API сокетов ключей управляет ассоциа- ассоциациями безопасности и политиками безопасности. Сокеты ключей поддерживают между приложениями пользователя и ядром возможность дейтаграмм без установле- установления соединения. Приложения уровня пользователя посылают в пакетах команды базе данных безопасности ядра. Приложения также могут получать сообщения о переменах в базе данных безопасности, такие, как истечение срока действия ассоциаций безопас- безопасности, путем чтения сокета ключа. Сообщения, которые можно отправить с использованием сокета ключа, показаны в табл. 13.6. Для сокетов ключей определены две группы сообщений: базовый набор сооб- сообщений, которые все начинаются с SADB, и набор расширенных сообщений, которые начинаются с SADB_X. Тип сообщения является второй частью имени. В FreeBSD рас- расширенные сообщения манипулируют базой данных политики безопасности (SPDB), которая является отдельной от базы данных ассоциаций безопасности (SADB). Сообщения сокета ключа составлены из базового заголовка, показанного на рис. 13.24, и набора заголовков расширения. Базовый заголовок содержит информа- информацию, которая является обычной для всех сообщений. Версия гарантирует, что прило- приложение будет работать с версией модуля сокета ключа в ядре. Посылаемая команда
668 Глава 13. Сетевые протоколы кодируется в поле типа сообщения. Ошибки посылаются вызывающему сокету с ис- использованием того же самого набора заголовков, которые используются для отправки команд. Приложения не могут зависеть от того, что все ошибки будут возвращаться системными вызовами send или write, сделанными для сокета; для должной обработки ошибок они должны проверять номера ошибок любых возвращаемых сокетом сообще- сообщений. Соответствующий номер ошибки устанавливается в поле егпго до того, как сооб- сообщение отправляется прослушивающему сокету. Тип ассоциации безопасности, которым хочет манипулировать приложение, помещается в поле SA-type пакета. В поле размера сохраняется размер всего сообщения, включая базовый заголовок, все заголовки расширений и любое добавленное заполнение. Каждое сообщение уникаль- уникально идентифицируется своим полям последовательности и PID, которые соответствуют ответам на запросы. Когда ядро посылает сообщение прослушивающему процессу, в PID записывается 0. 0 Версия 78 Размер 15 Тип сообщения 16 23 24 errno (номер ошибки) Тип SA Зарезервировано Последовательность PID 31 Рис. 13.24. Базовый заголовок PF KEY База данных ассоциаций безопасности и база данных политики безопасности не могут быть изменены с использованием лишь базового заголовка. Чтобы сделать изме- изменения, приложение добавляет к своему сообщению один или более расширенных заго- заголовков. Каждый расширенный заголовок начинается с размера и типа таким образом, чтобы ядро или приложение могли легко пройти через все сообщение. Расширение соединения показано на рис. 13.25. Расширение соединения делает изменения в одной ассоциации безопасности, такие, как указание для использования алгоритма аутенти- аутентификации или шифрования. 0 78 Размер адреса Повторное использование Состояние 15 16 SPI Флаги 23 24 31 Тип расширения Авторизация Шифрование Рис. 13.25. Расширение соединения PF_KEY
13.10. Безопасность 669 Каждый раз, когда используется расширение соединения, также должно присутст- присутствовать расширение адреса, поскольку каждая ассоциация безопасности идентифициру- идентифицируется по сетевом адресам взаимодействующих конечных точек. Расширение адреса, показанное на рис. 13.26, хранит сведения об адресах IPv4 и IPv6, используя структуры sockaddr. 0 78 Размер адреса Протокол Длина 15 префикса 16 Исходный адрес Адрес назначения 23 24 Тип расширения Зарезервировано 31 РИС. 13.26. Расширение адреса PFJCEY Одной из проблем с текущей реализацией PF_KEY является то, что это протокол дейтаграмм, и размер сообщений ограничен 64 килобайтами. 64-килобайтный предел не является важным для пользователей с небольшими базами данных, но когда систе- система, использующая IPSec, развертывается в крупном предприятии с сотнями и тысяча- тысячами параллельных ассоциаций безопасности, SADB станет большой, и это ограничение затрудняет написание демонов уровня пользователя для управления базами данных безопасности ядра. Назначением сокетов ключей является управление базой данных ассоциаций безо- безопасности, хранящейся в ядре. Подобно многим другим базам данных в FreeBSD, структуры ассоциаций безопасности в действительности являются объектами, реали- реализованными на С. Каждая связанная с ассоциацией безопасности структура содержит все данные, относящиеся к определенной ассоциации безопасности, а также набор функций, необходимых для работы со связанными с ним пакетами. База данных ассоциаций безопасности хранится в двойном связанном списке структур ассоциаций безопасности. Структура ассоциации безопасности показана на рис. 13.27. Каждая ассоциация безопасности может разделяться более чем одним объ- объектом в системе, вот почему она содержит счетчик ссылок. Ассоциации безопасности могут находиться в одном из четырех состояний: LARVAL (латентное), MATURE (зрелое), DYING (отключающееся) и DEAD (отключенное). Когда SA создается в первый раз, оно помещается в состояние LARVAL, которое указывает, что оно в настоя- настоящее время не может использоваться, но по-прежнему устанавливается. Когда SA годно к использованию, оно помещается в состояние MATURE. SA остается в состоянии MATURE до тех пор, пока некоторое событие, такое, как превышение SA своего срока времени жизни, переводит его в состояние DYING. SA в состоянии DYING можно про- просмотреть, если приложение делает запрос для использования SA с теми же самыми параметрами, прежде чем пометить его, как DEAD.
670 Глава 13. Сетевые протоколы Ассоциация безопасности struct sadbjcey bits 1 reserved 1 •j \ struct sadbjtifetime allocations bytes addtime usetime -* -* щ lock refcnt state alg auth alg_enc alg_comp spi -ags key_auth keyjenc lifetime jcurrent lifetime_soft lifetime Jiard replay secashead tdb_xform tdb_encalgxform tdb_authalgxform tdbjoompalgxform struct xformsw type -ags name initQ zeroize () input () output() struct enc_xform struct auth hash Рис. 13.27. Структура безопасности соединения Структура ассоциации безопасности содержит всю информацию об определенном SA, включая использующиеся алгоритмы, SPI и данные ключей. Вся эта информация используется в обработке пакетов для определенного соединения. Поля времени жизни ограничивают использование определенного SA. Хотя от SA не требуется время жизни и поэтому оно могло бы не проходить, рекомендуемой практикой является установка времени жизни. Ограничения по времени для времени жизни можно установить, исполь- используя поля addtime и usetime. Ограничение на объем обработки данных можно установить, используя поле bytes. Эти три структуры времени жизни, на которое указывает ассоциа- ассоциация безопасности, кодируют текущее использование для соединения, а также его жест- жесткий и мягкий лимиты. При достижении значения мягкого времени жизни (soft-lifetime) SA помещается в состояние DYING, чтобы показать, что его полезное время близко к окончанию. Достижение значения жесткого времени жизни (hard-lifetime) означает, что SA вовсе не может больше использоваться. Когда SA проходит ограничение жесткого времени жизни, оно устанавливается в состояние DEAD и может быть восстановлено. Структура текущего времени жизни содержит текущие значения использования для SA (например, сколько байтов было обработано с момента создания SA).
13.10. Безопасность 671 Каждая структура ассоциации безопасности имеет несколько таблиц функций, ко- которые указывают на процедуры, которые выполняют работу с пакетами, обрабатывае- обрабатываемыми этим соединением. Таблица tdbjcform содержит указатели на функции, которые реализуют инициализацию и функции ввода и вывода для определенного протокола безопасности, такого, как ESP или АН. Другие три таблицы специфичны для протоко- протокола и содержат указатели на соответствующие криптографические функции для обра- обработки протокола, используемого SA. Причина изобилия таблиц в том, что криптогра- криптографическая подсистема, перенесенная из OpenBSD, использовала эти таблицы для ин- инкапсуляции функций, которые выполняют фактическую работу по криптографии. Для упрощения сопровождения кода этот набор интерфейсов и таблиц при переносе был сохранен. Полезным побочным эффектом наличия этих таблиц является то, что это упрощает добавление новых протоколов или криптографических процедур. Далее в данном разделе мы описываем, как эти таблицы используются. Сокеты ключей реализованы тем же самым способом, что и другие типы сокетов. Имеется структура домена, keydomain; структура переключения протокола, keysw; набор процедур запросов пользователей, key_usrreqs\ и процедура вывода, key_output(). В структуре keyjusrreqs реализованы лишь те процедуры, которые необ- необходимы для протоколов дейтаграммного типа без установления соединения. Любая попытка использования сокета ключа способом, ориентированным на соединение - например, путем вызова connect для сокета ключа, - приведет к возвращению ядром пользователю EINVAL. Когда приложение записывает в сокет ключа, сообщение в конечном счете переда- передается в ядро и обрабатывается процедурой key_output(). После некоторой элементарной проверки ошибок сообщение передается key_parse(), которая выполняет дополнитель- дополнительную проверку ошибок, а затем в конечном счете перемещается через переключатель указателя на функцию, называемый keyjtypes. Функции, на которые указывают keyjypes, являются теми, которые выполняют манипуляции с базами данных ассоциа- ассоциаций безопасности и политики безопасности. Если ядру нужно отправить сообщение прослушивающим соединениям из-за изменений в базах данных безопасности, оно использует процедуру key_sendup_mbuf() для копирования сообщения в один или более прослушивающих сокетов. Каждый сокет получает свою собственную копию сообщения. Реализация IPSec Протоколы IPSec воздействуют на все области обработки пакетов в стеке протоколов IPv4 и IPv6. В некоторых местах IPSec использует инфраструктуру существующих сетей, а в других делаются прямые вызовы для осуществления некоторой части обра- обработки безопасности. Мы рассмотрим три возможных пути через стек IPv4: входящий, исходящий и пересылку.
672 Глава 13. Сетевые протоколы Одним из ухищрений, которое IPSec добавляет к обычной обработке пакетов, явля- является потребность в обработке некоторых пакетов более одного раза. Примером являет- является прибытие зашифрованного пакета, предназначенного для текущей системы. Пакет будет обработан один раз в своем зашифрованном виде, а затем во второй раз теми же самыми процедурами после расшифровки. Эта многопроходная обработка отличается от обычной обработки TCP или UDP, когда из пакета удаляется IP-заголовок, а резуль- результат передаеётся для обработки модулям TCP или UDP и доставки в конечном счете со- кету. Стиль обработки пакетов с продолжением является одной из причин того, что программное обеспечение IPSec интенсивно использует теги пакетов. Другой причи- причиной использования тегов пакетов является то, что части IPSec, а именно криптогра- криптографические алгоритмы, могут поддерживаться аппаратными ускорителями специального назначения. Аппаратный ускоритель может делать всю или частичную обработку безо- безопасности, такую, как проверка информации аутентификации пакета или расшифровка полезной нагрузки пакета, а затем передавать результирующий пакет в стек протоко- протоколов для конечной доставки ожидающему сокету. Аппаратному обеспечению нужен некоторый способ сообщить стеку протоколов о том, что он завершил необходимую работу. Не является ни возможным, ни желательным хранить эту информацию в заголовках или данных пакета. Добавление такой информации к заголовку пакета яв- является очевидной дырой в безопасности, поскольку злоумышленный отправитель мог бы просто установить соответствующее поле и обойти обработку безопасности. Было бы можно расширить структуру mbuf для управления этой функциональной возможно- возможностью, но теги пакетов являются более гибким способом добавления к пакетам метадан- метаданных без изменения ключевых структур данных в сетевом стеке. Теги, использованные IPSec, описаны в табл. 13.7. Табл. 13.7. Теги пакета IPSec Тег Описание IPSEC_IN_DONE Обработка входящего IPSec завершена IPSEC_OUT_DONE Обработка исходящего IPSec завершена IPSEC_IN_CRYPTO_DONE Обработка входящего IPSec выполняется аппаратно IPSEC_OUT_CRYPTO_DONE Обработка исходящего IPSec выполняется аппаратно Как мы видели в разделе 13.3, когда ядром получен пакет IPv4, он вначале обраба- обрабатывается ip_input(). Процедура ip_input() делает две проверки пакетов, которые имеют отношение к IPSec. Первая проверка смотрит на то, что пакет действительно является частью туннеля. Если пакет туннелируется, тогда, если он уже был обработан про- программным обеспечением IPSec, он может обойти фильтрование ловушками фильтров или кодом брандмауэра ядра. Вторая проверка делается, когда пакет должен быть пере- переправлен. Маршрутизаторы могут реализовать политики безопасности переправляемых пакетов. До передачи пакета ip_forward() он проверяется путем вызова функции
13.10. Безопасность 673 ipsec_getpolicy() на предмет того, имеется ли политика, связанная с самим пакетом. Функция ipsec_getpolicybyaddr() вызывается для проверки того, есть ли политика, свя- связанная с адресом пакета. Если любая из функций возвращает указатель на процедуру политики, пакет передается для проверки этой процедуре политики. Если пакет от- отвергается, он молча уничтожается без возвращения отправителю сообщения об ошибке. Когда ip_input() определила, что пакет действительный и предназначен для локаль- локальной машины, начинается действие инфраструктуры стека протоколов. Пакет передает- передается соответствующей входной процедуре с использованием поля prjnput структуры in- etsw. Хотя пакеты, использующие различные протоколы, имеют различные точки входа, они в конечном счете заканчивают передачей для обработки единственной про- процедуре, ipsec_common_input(). Процедура ipsec_common_input() пытается найти для пакета соответствующую структуру ассоциации безопасности, основываясь на адресе его назначения, используемом им протоколе безопасности и SPI. Если соответст- соответствующее соединение найдено, управление передается входной процедуре, содержащей- содержащейся в структуре xfonn-switch. Входная процедура протокола безопасности извлекает из пакета все относящиеся к делу данные (например, используемый ключ) и создает де- дескриптор криптографического задания. Затем этот дескриптор передается в криптогра- криптографические процедуры. Когда криптографические процедуры закончили свою работу, они вызывают специфическую для протокола процедуру обратного вызова, которая из- изменяет mbuf, связанные с пакетом, таким образом, что он может быть теперь передан в незашифрованном виде обратно в стек протоколов посредством процедуры ip_input(). Приложения не знают, что они используют IPSec для взаимодействия с другими хостами в Интернете. Для исходящих пакетов использование IPSec в действительно- действительности управляется из процедуры ip_output(). Когда исходящий пакет достигает проце- процедуры ip_output(), делается проверка на предмет наличия политики безопасности, которая применяется к пакету либо из-за его адреса назначения, либо из-за сокета, который его отправил. Если политика безопасности найдена, то пакет передается в код IPSec через процедуру ipsec4j)rocess_packet(). Если для данного конкретного места назначения не была установлена ассоциация безопасности, она создается в базе данных ассоциаций безопасности. ipsec4jprocess_packet() использует процедуру outputQ из переключате- переключателя xfonn в ассоциации безопасности для передачи пакета выходной процедуре прото- протокола безопасности. Выходная процедура протокола безопасности использует соответ- соответствующую криптографическую процедуру для изменения пакета для передачи. Когда пакет был соответствующим образом изменен, он снова передается в ip_output(), но с подсоединенным к нему тегом PACKET_TAG_IPSEC_OUT_DONE. Этот тег помечает пакет как завершивший обработку IPSec, показывая, что он теперь может быть передан, как любой другой пакет
674 Глава 13. Сетевые протоколы Криптографическая подсистема В основе всех протоколов безопасности, предоставляемых IPSec, лежит набор API и библиотек, поддерживающих криптографию. Криптографическая подсистема в FreeBSD поддерживает как симметричную, так и асимметричную криптографию. Симметричная криптография, используемая IPSec, использует для шифрования данных тот же самый ключ, который используется и для расшифровки. Асимметричная криптография, которая реализует шифрование с открытым ключом, использует один ключ для шифрования данных и другой ключ для ее расшифровки. В данном разделе описыва- описывается, как реализована симметричная криптография по отношению к специфическому клиенту, IPSec. Криптографическая подсистема была перенесена из OpenBSD и оптимизирована для полностью вытесняющего ядра с симметричной многопроцессорной обработкой (SMP) [Leffler, 2003b]. В FreeBSD криптографические алгоритмы существуют либо в программном обеспечении, либо в аппаратном обеспечении специального назначе- назначения. Программный модуль, предоставляющий поддержку криптографии реализован точно таким же способом, как устройства для криптографического аппаратного обес- обеспечения. Это сходство означает, что с точки зрения подсистемы криптографии, про- программные и аппаратные драйверы одинаковы. Пользователям верхнего уровня крипто- криптографической подсистемы, таким, как IPSec, представлен тот же самый API независимо от того, выполняются ли криптографические операции, которые они запрашивают, на аппаратном или программном уровне. Подсистема криптографии реализована двумя наборами API и двумя потоками ядра. Один набор API используется программным обеспечением, которое хочет использовать криптографию; другой набор используется создателями драйверов устройств для предоставления интерфейса для своих аппаратных устройств. Модель вычислений, поддерживаемая подсистемой криптографии, является моделью предо- предоставления заданий и обратных вызовов, когда пользователи помещают в очередь зада- задание, которое должно быть сделано, и предоставляют указатель на функцию, которая будет вызвана, когда задание будет выполнено. Прежде чем пользователь криптографии сможет поручить работу подсистеме криптографии, сначала нужно создать сеанс. Сеанс является способом инкапсуляции информации о типе работы, которую запрашивает пользователь. Это также способ кон- контролирования объема ресурсов, потребляемых устройством, поскольку некоторые устройства имеют ограничения на объем параллельной работы, которую они могут поддерживать. Пользователь создает сеанс, используя процедуру crypto_newsession(), которая возвращает либо действительный идентификатор сеанса, либо ошибку. Когда у пользователя есть соответствующий идентификатор сеанса, запрашивает- запрашивается криптографический дескриптор, показанный на рис. 13.28. Пользователь заполняет поля в криптографическом дескрипторе, включая предоставление соответствующего обратного вызова в элементе crp_callback. Когда дескриптор готов, он передается
13.10. Безопасность 675 crp_sid crp_ilen crp_olen crp_etype crp_- ags crp_buf crp_opaque crp_desc crp_callback struct cryptop Рис. 13.28. Криптографический дескриптор криптографической подсистеме через процедуру aypto_dispatch(), которая помещает его в очередь для обработки. Когда работа завершена, вызывается функция обратного вызова. Все функции обратного вызова имеют вид: int (*crp_callback)( struct cryptop *arg); Если возникла ошибка, код ошибки содержится в поле crp_etype криптографиче- криптографического дескриптора, который передан в функцию обратного вызова. Набор драйверов устройств предоставляет низкоуровневый интерфейс к специа- специализированному криптографическому оборудованию. Каждый драйвер при своей реги- регистрации предоставляет три указателя на функции криптографической подсистемы. Регистрация драйвера осуществляется через вызов процедуры crypto registerQ. crypto_register( u_int32_t driverid, int alg, u_intl6_t maxoplen, u_int32_t flags, int (*newsession)(void*, u_int32_t*, struct cryptoini*), int (*freesession)(void*, u_int64_t), int (*process)(void*, struct cryptop *, int), void *arg); Процедура newsessionQ вызывается криптографической подсистемой каждый раз, когда пользователем вызывается процедура cryptojiewsessionQ. Процедура freeses- sionQ вызывается каждый раз, когда пользователем вызывается процедура ayptoji'eesession(), а процедура processQ вызывается потоком ядра ciypto_proc() для передачи операций в устройство.
676 Глава 13. Сетевые протоколы Нижняя половина криптографической подсистемы использует два потока про- программных прерываний и две очереди для управления лежащим в основе оборудовани- оборудованием. Каждый раз, когда в очереди crp_q есть запросы, поток crypto_ргос() удаляет их из очереди и посылает на соответствующее устройство, используя процедуру aypto_invoke(). После вызова за обработку запроса отвечает лежащее в основе обору- оборудование. Единственным требованием является то, что, когда оборудование завершило свою работу, драйвер устройства, связанный с оборудованием, должен вызвать crypto_done(), который либо помещает в очередь crp_ret_q обратный вызов, либо, что происходит реже, непосредственно вызывает пользовательскую процедуру обратного вызова. Очередь crp_ret_q предоставлена потому, что процедура crypto_done() часто будет вызываться из контекста прерывания, и запуск обратного вызова пользователя с разрешенными прерываниями снизит производительность интерактивной работы системы. При работе в контексте прерывания обратный вызов будет помещен в очередь, а затем обработан позже потоком программного прерывания aypto_ret_proc. Это использование очередей и потоков программных прерываний эффективно разъе- разъединяет ядро от любых проблем производительности, привносимых разнообразным криптографическим оборудованием. К сожалению, с только что описанной системой есть несколько проблем. # Использование нескольких потоков требует двух переключений контекстов на криптографическую операцию. Переключения контекста являются не тривиаль- тривиальными и значительно снижают производительность. Некоторые процедуры обратного вызова выполняют незначительную работу, и поэтому перемещение всех обратных вызовов из процедуры обработки прерыва- прерывания драйвера устройства добавляет еще одно переключение контекста, которое является дорогостоящим и ненужным. Очередь диспетчеризации объединяет операции, но многие пользователи криптогра- криптографической подсистемы, включая IPSec, не объединяют операции, поэтому это пере- перемещение работы в очередь диспетчеризации является ненужными издержками. Для решения этих проблем производительности в криптографическую подсистему было внесено несколько изменений. Криптографическим драйверам теперь вместе с заданием предоставляется подсказка о том, имеется ли дополнительная работа, которая последует за предоставленной работой. Драйверы могут решить, нужно ли объединять работу, основываясь на этой подсказке, и, когда запросы не объединяются, полностью обойти очередь crp_q. Криптографические запросы, процедуры обратного вызова которых короткие, помечают свои запросы таким образом, чтобы лежащее в основе оборудование выполняло их непосредственно вместо помещения их в очередь crypto_ret_q. Оптимизация обхода очереди aypto_req_q особенно полезна пользователям устройства /dev/crypto, процедуры обратного вызова которых более полно описаны в Leffler [2003b].
Упражнения 677 Упражнения 13.1. Является ли TCP протоколом транспортного, сетевого или канального уровня? 13.2. Как IPv4 идентифицирует протокол следующего вышележащего уровня, который должен обработать входящее сообщение? Чем могла бы эта переправка отличаться в других сетевых архитектурах? 13.3. Сколько хостов может существовать в подсети IPv4 с маской 255.255.255.0? 13.4. Что такое широковещательное сообщение? Как широковещательные сообще- сообщения идентифицируются в IPv4? Как идентифицируются широковещательные сообщения IPv6? 13.5. Почему управляющие блоки TCP и UDP хранятся в различных списках? 13.6. Почему выходная процедура, а не процедура отправки уровня сокетов (sos- endQ) проверяет адрес назначения исходящего пакета на предмет того, явля- является ли адрес назначения широковещательным? 13.7. Почему FreeBSD не пересылает широковещательные сообщения? 13.8. Почему заголовок TCP включает поле размера заголовка, даже если он всегда инкапсулирован в IP-пакете, который содержит размер ТСР-сообщения? 13.9 В чем заключается механизм управления потоком, используемый TCP для ограничения скорости, с которой передаются данные? 13.10. Как TCP распознает сообщения от хоста, которые направлены по соеди- соединению, которое существовало до этого, но с тех пор было отключено (например, после перезагрузки машины)? 13.11. Когда размер приемного окна TCP для соединения не равен размеру доступного пространства в приемном буфере соответствующего сокета? Почему в это время эти значения не равны? 13.12. Что такое дежурные (keepalive) сообщения? Для чего TCP их использует? Почему дежурные сообщения реализованы в ядре, а не, скажем, в каждом приложении, которое хочет иметь эту возможность? 13.13. Почему важно вычисление сглаженного (smoothed) времени обращения, а не, например, просто усредненного времени обращения? 13.14. Почему TCP откладывает подтверждения для полученных данных? Каково максимальное время, на которое TCP будет откладывать подтверждение? 13.15. Объясните, что такое синдром незначительного окна. Приведите пример, в котором для хорошей производительности протокола важно его избегать. Объясните, как TCP FreeBSD избегает этой проблемы. 13.16. Что подразумевается под избеганием небольших пакетов! Почему избегание небольших пакетов плохо для клиентов (например, системы X Window), которые демонстрируют однонаправленный поток данных и которым для хорошей интерактивной производительности требуются низкие задержки? 13.17. Опишите три аспекта, в которых IPv6 отличается от IPv4.
678 Глава 13. Сетевые протоколы 13.18. Какой протокол в IPv6 замещает ARP для преобразования IP-адресов в аппаратные адреса? 13.19. Для определения чего сетевой код использует маску сети или префикс соеди- соединения? 13.20. Почему в IPSec есть отдельные протоколы для аутентификации и шифрова- шифрования? 13.21. Почему криптографическая подсистема реализована с использованием двух очередей и двух потоков ядра? 13.22. Какие ограничения ARP преодолевает определение соседа? Как оно преодо- преодолевает эти ограничения? 13.23. Как защита, предоставляемая IPSec пакетам, отличается в транспортном и туннельном режимах? * 13.24. Почему начальный номер последовательности для ТСР-соединения выбирается случайно, а не устанавливается, скажем, равным нулю? * 13.25. Почему в протоколе TCP флаги SYN и FIN занимают пространство в пространст- пространстве номеров последовательностей? * 13.26. Опишите типичный обмен пакетами TCP в ходе установления соединения. Предположите, что активный клиент инициировал соединение с пассивным сервером. Как бы изменился этот сценарий, если сервер одновременно попы- попытался инициировать соединение с клиентом? * 13.27. Набросайте схему переходов состояний TCP, которые имели бы место, если процесс сервера принимал соединение, а затем немедленно закрывал это соединение до получения каких-нибудь данных. Как изменился бы этот сценарий, если TCP FreeBSD поддерживал механизм, при котором сервер отвергал бы запрос соединения до завершения системой соединения? * 13.28. Почему UDP сопоставляет полностью определенный адрес места назначения входящих сообщений с сокетами с неполными локальными и удаленными адресами назначения? * 13.29. Зачем отправитель мог бы установить флаг не фрагментироватъ в заголовке IP-пакета? *13.30.Максимальное время жизни пакета (MSL) является максимальным временем, в течение которого сообщение может существовать в сети, т.е. максимальным временем, в течение которого сообщение может быть в пути в каком-нибудь физическом носителе или помещенным в очередь шлюза. Что делает TCP для обеспечения того, чтобы TCP-сообщения имели ограниченный MSL? Что делает IP для обеспечения ограниченного MSL? Другой подход к этой проблеме см. в Fletcher & Watson [1978]. * 13.31. Почему TCP использует опцию отметки времени вдобавок к номерам после- последовательностей при определении старых дублированных пакетов? При каких условиях эта опция наиболее желательна?
Ссылки 679 ** 13.32. Опишите протокол для вычисления границ максимальной продолжительно- продолжительности жизни сегмента сообщений в окружении Интернета. Как TCP мог бы использовать границу MSL для сообщений для минимизации издержек, свя- связанных с отключением ТСР-соединения? ** 13.33. Опишите определение MTU пути. Может FreeBSD использовать преимуще- преимущества того факта, что MTU пути внезапно возросло? Почему да или почему нет? ** 13.34. В чем заключается компромисс между частой и редкой передачей объявлений маршрутизатора в IPv6? ** 13.35. Поскольку IPSec может рекурсивно вызывать процедуры в сетевом стеке, какие требования к коду предъявляются в данном случае? ** 13.36. Опишите три пути, которыми может идти пакет через сетевой код. Как и где выбирается каждый из путей? Ссылки Bellovin, 1996. S. Bellovin, "Defending Against Sequence Number Attacks", RFC 1948,available from http://www.faqs.org/rfcs/rfcl948.html, May 1996. Cain et al., 2002. B. Cain, S. Deering, I. Kouvelas, B. Fenner, & A. Thyagarajan, "Internet Group Man- Management Protocol, Version 3", RFC 3376, available from http://www.faqs.org/rfcs/ rfc3376.html, October 2002. Cerf&Kahn, 1974. V. Cerf & R. Kahn, "A Protocol for Packet Network Intercommunication", IEEE Trans- Transactions on Communications, vol. 22, no. 5, pp. 637-648, May 1974. Clark, 1982. D. D. Clark, "Window and Acknowledgment Strategy in TCP", RFC 813, available from http://www.faqs.org/rfcs/rfc813.html, July 1982. Conta & Deering, 1998. A. Conta & S. Deering, "Internet Control Message Protocol (ICMPv6) for the Internet Protocol Version 6 (IPv6) Specification", RFC 2463, available from http:// www.faqs.org/rfcs/rfc2463.html, December 1998. DARPA, 1983. DARPA, "A History of the ARPANET: The First Decade", Technical Report, Bolt, Beranek, and Newman, Cambridge, MA, April 1983.
680 Глава 13. Сетевые протоколы Deering, 1989. S. Deering, "Host Extensions for IP Multicasting", RFC 1112, available from http:// www.faqs.org/rfcs/rfcll 12.html, August 1989. Deering & Hinden, 1998a. S. Deering & R. Hinden, "Internet Protocol, Version 6 (IPv6)", RFC 2460, available from http://www.faqs.org/rfcs/rfc2460.html, December 1998. Deering & Hinden, 1998b. S. Deering & R. Hinden, "IP Version 6 Addressing Architecture", RFC 2373, available from http://www.faqs.org/rfcs/rfc2373.html, July 1998. Fletcher & Watson, 1978. J. Fletcher & R. Watson, "Mechanisms for a Reliable Timer-Based Protocol", in Com- Computer Networks 2, pp. 271-290, North-Holland, Amsterdam, The Netherlands, 1978. Fuller etal., 1993. V Fuller, T. Li, J. Yu, & K. Varadhan, "Classless Inter-Domain Routing (CIDR): An Address Assignment and Aggregation Strategy", RFC 1519, available from http://www.faqs.org/ rfcs/rfcl 519.html, September 1993. Gilligan et al., 1999. G. Gilligan, S. Thomson, J. Bound, & W. Stevens, "Basic Socket Interface Extensions for IPv6", RFC 2553, available from http://www.faqs.org/rfcs/rfc2553.html, March 1999. Gross & Almquist, 1992. P. Gross & P. Almquist, "IESG Deliberations on Routing and Addressing", RFC 1380, available from http://www.faqs.org/rfcs/rfcl380.html, November 1992. Harkins & Carrel, 1998. D. Harkins & D. Can-el, "The Internet Key Exchange (IKE)", RFC 2409, available from http://www.faqs.org/rfcs/rfc2409.html, November 1998. ISO, 1984. ISO, "Open Systems Interconnection: Basic Reference Model", ISO 7498, International Organization for Standardization, available from the American National Standards Institute, 1430 Broadway, New York, NY 10018, 1984. Jacobson, 1988. V. Jacobson, "Congestion Avoidance and Control", Proceedings of the ACM SIGCOMM Conference, pp. 314-329, August 1988.
Ссылки 681 Jacobson et al., 1992. V. Jacobson, R. Braden, & D. Borman, "TCP Extensions for High Performance", RFC 1323, available from http://www.faqs.org/rfcs/rfcl323.html, May 1992. КАМЕ, 2003. КАМЕ, "Overview of КАМЕ Project", Web site, http://www.kame.net/project-over- view.html#overview, December 2003. Kent & Atkinson, 1998a. S. Kent & R. Atkinson, "Security Architecture for the Internet Protocol", RFC 2401, available from http://www.faqs.org/rfcs/rfc2401.html, November 1998. Kent & Atkinson, 1998b. S. Kent & R. Atkinson, "IP Authentication Header", RFC 2402, available from http:// www.faqs.org/rfcs/rfc2402.html, November 1998. Kent & Atkinson, 1998c. S. Kent & R. Atkinson, "IP Encapsulating Security Payload (ESP)", RFC 2406, available from http://www.faqs.org/rfcs/rfc2406.html, November 1998. Leffler, 2003a. S. Leffler, "Fast IPSec: A High-Performance IPSec Implementation", Proceedings of BSDCon 2003, September 2003. Leffler, 2003b. S. Leffler, "Cryptographic Device Support for FreeBSD", Proceedings of BSDCon 2003, September 2003. McDonald etal., 1998. D. McDonald, С Metz, & B. Phan, "PFJCEY Key Management API, Version 2", RFC 2367, available from http://www.faqs.org/rfcs/rfc2367.html, July 1998. McQuillan & Walden, 1977. J. M. McQuillan & D. C. Walden, "The ARPA Network Design Decisions", Computer Networks, vol. 1, no. 5, pp. 243-289, 1977. Mogul, 1984. J. Mogul, "Broadcasting Internet Datagrams", RFC 919, available from http:// www.faqs.org/rfcs/rfc919.html, October 1984. Mogul & Deering, 1990. J. Mogul & S. Deering, "Path MTU Discovery", RFC 1191, available from http:// www.faqs.org/rfcs/rfcll91.html, November 1990.
682 Глава 13. Сетевые протоколы Mogul & Postel, 1985. J. Mogul & J. Postel, "Internet Standard Subnetting Procedure", RFC 950, available from http://www.faqs.org/rfcs/rfc950.html, August 1985. Nagle, 1984. J. Nagle, "Congestion Control in IP/TCP Internetworks", RFC 896, available from http:/ /www.faqs.org/rfcs/rfc896.html, January 1984. Nartenetal., 1998. T. Narten, E. Nordmark, & W. Simpson, "Neighbor Discovery for IP Version 6 (IPv6)", RFC 2461, available from http://www.faqs.org/rfcs/rfc2461.html, December 1998. Nesser, 1996. P. Nesser, "An Appeal to the Internet Community to Return Unused IP Networks (Pre- (Prefixes) to the IANA", RFC 1917, available from http://www.faqs.org/rfcs/rfcl917.html, February 1996. Postel, 1980. J. Postel, "User Datagram Protocol", RFC 768, available from http://www.faqs.org/rfcs/ rfc768.html, August 1980. Postel, 1981a. J. Postel, "Internet Protocol", RFC 791, available from http://www.faqs.org/rfcs/ rfc791.html, September 1981. Postel, 1981b. J. Postel, "Transmission Control Protocol", RFC 793, available from http:// www.faqs.org/rfcs/rfc793.html, September 1981. Postel, 1981c. J. Postel, "Internet Control Message Protocol", RFC 792, available from http:// www.faqs.org/rfcs/rfc792.html, September 1981. Postel etal., 1981. J. Postel, С Sunshine, & D. Cohen, "The ARPA Internet Protocol", Computer Net- Networks, vol. 5, no. 4, pp. 261-271, July 1981. Sakane, 2001. S. Sakane, Simple Configuration Sample of IPs ec/Racoon, available at http://www.ka- me.net/newsletter/20001119, September 2001. Scheifler & Gettys, 1986. R. W. Scheifler & J. Gettys, "The X Window System", ACM Transactions on Graphics, vol. 5, no. 2, pp. 79-109, April 1986. Simpson, 1995. W. Simpson, "IP in IP Tunneling", RFC 1853, available from http://www.faqs.org/rfcs/ rfcl853.html, October 1995. Thomson & Huitema, 1995. S. Thomson & С Huitema, "DNS Extensions to Support IP Version 6", RFC 1886, available from http://www.faqs.org/rfcs/rfcl886.html, December 1995.
Часть V Работа системы
Глава 14 Запуск и выключение При включении компьютера нет выполняющихся на процессоре задач. Чтобы запус- запустить программу, с устройства хранения в память должен быть загружен двоичный образ программы. Многие системы автоматически запускают программы, находящие- находящиеся в энергозависимых устройствах, таких, как постоянные запоминающие устройства (read-only memories - ROM). После загрузки образа программы процессор должен по- получить указание начать выполнение с начального адреса загруженной программы. Этот процесс начальной загрузки (bootstrapping) программы на исполнение начинает работу процессора с программой. В данной главе мы исследуем, как осуществляется начальная загрузка ядра FreeBSD или любой другой аналогичной программы. Затем мы изучим работу системы на фазе инициализации, которая переводит систему с холодного старта в точку, когда можно запускать пользовательские программы. Последний раздел исследует темы, которые связаны с процедурой запуска. Эти темы включают конфигурирование ядра для загрузки изображения, выключение работающей системы и отладку системных сбоев. 14.1. Обзор Ядро FreeBSD является всего лишь программой, хотя и сложной. Подобно любой про- программе, его двоичный образ находится на файле в файловой системе до тех пор, пока он не будет загружен и запущен. FreeBSD предполагает, что исполняемый образ ядра находится в файле с именем /boot/kernel/kernel в файловой системе, которая опреде- определена в качестве корневой файловой системы. Процесс начальной загрузки является ма- машинно-зависимым. Часто небольшая загрузочная программа помещается в заре- зарезервированную область в начале первичного диска. Эта программа обычно ограничена небольшой областью - всего лишь одним 512-байтным сектором диска - и просто за- загружает более крупную программу из следующей области диска. Эта программа или
686 Глава 14. Запуск и выключение некоторый другой механизм обычно используется всего лишь для загрузки и запуска специальной программы с названием boot. Задачей программы boot является загрузка и инициализация исполняемого образа программы и ее запуск, boot может находиться на том же устройстве хранения, что и файл, осуществляющий начальную загрузку, или она может быть загружена с сетевого файлового сервера. Табл. 14.1. Команды программы boot Команда bcachestat boot autoboot help 9 show set unset echo read more Isdev include Is load unload lsmod pnpscan pnpload Описание Получить статистику кеша дисковых блоков Загрузить файл или ядро Загрузить автоматически после задержки Подробная справка Список доступных команд Показать переменную(-ые) Установить переменную Сбросить переменную Отобразить вывод, аналогично эхо-отображению оболочки Имитирование команды чтения оболочки Показать содержание файла с постраничным выводом Перечислить все устройства Прочесть команды из файла Перечислить файлы, которые может видеть программа загрузки Загрузить ядро или модуль Выгрузить все модули Перечислить все загруженные модули Сканировать самонастраивающиеся (plug-and-play- PnP) устройства Загрузить модули для самонастраивающихся (РпР) устройств Когда запускается программа boot, она загружает /boot/kernel/kernel, а затем начинает обратный отсчет, который может быть прерван пользователем. Если обрат- обратный отсчет программы boot прерывается, она предоставляет пользователю в консо- консоли интерпретатор командной строки и ожидает ввода. Список команд показан в табл. 14.1. Интерпретатор командной строки помогает отлаживать проблемы, воз- возникающие во время загрузки. Чаще всего использующимися командами является команда unload для удаления ядра по умолчанию, за которой следует команда load для загрузки альтернативного ядра. Обычно загружаемым альтернативным ядром является /boot/kernel/kernel.old, сохраненное системой построения ядра при инсталляции нового ядра на случай, если новое ядро окажется неисправным.
14.2. Начальная загрузка 687 Ядро запускается по команде boot либо пользователем, либо при достижении обратным отсчетом 0. Команда boot начинается с инициализации процессора, обес- обеспечивая отключение трансляции виртуальных адресов и запрещая прерывания. Загру- Загруженная программа отвечает за включение этих возможностей и любого дополнитель- дополнительного аппаратного обеспечения, такого, как устройства ввода/вывода, которые она на- намеревается использовать. Когда ядро FreeBSD загружено программой boot, ядро проходит через несколько этапов аппаратной и программной инициализации, подготавливая нормальную работу системы. Первый этап отвечает за установку начального состояния процессора, включая стек времени исполнения и отображение виртуальной памяти. Отображение памяти, включая трансляцию виртуальных адресов, включается в самом начале проце- процедуры запуска, чтобы минимизировать объем кода специального назначения на языке ассемблера, который должны создавать те, кто портирует ядра. После включения ото- отображения виртуальной памяти система выполняет машинно-зависимую инициализа- инициализацию, а затем машинно-независимую инициализацию. Машинно-зависимые операции включают установку мьютексов и таблиц страниц виртуальной памяти и конфи- конфигурирование устройств ввода/вывода; машинно-независимые действия включают мон- монтирование корневой файловой системы и инициализацию множества системных структур данных. Такой порядок необходим, поскольку значительная часть машинно- независимой инициализации зависит от должной инициализации устройств ввода/вы- ввода/вывода. После настройки машинно-независимых частей ядра система оказывается в ра- рабочем состоянии. Создаются и запускаются системные процессы, и программы уровня пользователя загружаются из файловой системы для выполнения. В этот момент сис- система готова к запуску обычных приложений. 14.2. Начальная загрузка Начальная загрузка программы является машинно-зависимой операцией. На большин- большинстве машин эта операция поддерживается базовой системой ввода/вывода (Basic Input/ Output System- BIOS). BIOS является резидентным в энергонезависимой памяти и вы- вызывается автоматически при запуске процессора. Ожидается, что средства, предостав- предоставляемые BIOS, должны поддерживать начальную загрузку автономных программ. Боль- Большинство систем BIOS при перезагрузке выполняет также диагностические операции, чтобы убедиться, что аппаратное обеспечение функционирует правильно. Программа boot BIOS не понимает формат файловой системы FreeBSD. Вместо этого процедура запус- запуска считывает из зарезервированной области загрузочного диска программу. Эта про- программа сразу же выполняет программу FreeBSD boot. Последняя является автономной
688 Глава 14. Запуск и выключение программой общего назначения, которую система может использовать для загрузки и исполнения других автономных программ. Автономной (standalone) является такая программа, которая может работать без помощи ядра FreeBSD. Автономные программы обычно компонуются с автономной библиотекой ввода/вывода, библиотекой, под- поддерживающей FreeBSD-подобный интерфейс ввода/вывода для различных аппарат- аппаратных устройств. Автономная библиотека ввода/вывода предоставляет эти возможности через набор автономных драйверов устройств и библиотечных процедур, поддержи- поддерживающих чтение файлов из файловых систем FreeBSD, которые находятся на устройствах. Программа boot хранится в месте, доступном BIOS, которое представляет собой несколько первых секторов системного диска. После загрузки и запуска программы boot она должна загрузить файл, содержа- содержащий исполняемый образ загружаемой программы, а затем должна запустить загружен- загруженную программу. Чтобы загрузить соответствующий файл, boot должна знать имя пути загружаемого файла и аппаратное устройство, на котором файл находится. Программа boot обычно имеет устройство и имя программы по умолчанию, из которых она пыта- пытается загрузиться. Часто это значение по умолчанию хранится в загрузочных секторах загрузочного диска вместе с программой boot. BIOS сообщает сведения о начальной загрузке программе boot, инициализируя стек времени выполнения, а затем помещая параметры в стек тем же самым способом, как ядро FreeBSD передает аргументы про- программам. В качестве альтернативы пользователь может набрать устройство и имя про- программы для использования. boot всегда загружает программы по хорошо известным адресам памяти. Поскольку boot первоначально загружается в память по тем же самым хорошо известным адре- адресам, она должна скопировать свой образ по другому адресу памяти, чтобы избежать перезаписывания себя образом загружаемой программы (см. рис. 14.1). Это перемеще- перемещение предполагает, что программа boot должна создаваться с начальным адресом, уста- установленным на адрес памяти, в которую она будет скопирована; в противном случае ссылки на структуры данных в программе boot после ее копирования будут получать доступ к неверным адресам памяти (помните, что boot работает с отключенной транс- трансляцией виртуальных адресов). 14.3. Инициализация ядра Когда ядро FreeBSD запускается программой boot, она выполняет инициализацию, готовясь к выполнению прикладных программ. Процесс инициализации делится при- примерно на три этапа. На первом этапе необходим созданный вручную на языке ассемб- ассемблера код для настройки оборудования для того, чтобы мог действовать более общий код не на языке ассемблера. На втором этапе загружаются и инициализируются модули ядра, которые реализуют внутренние службы ядра, включая конфигурирование и ини- инициализацию устройств ввода/вывода на машине. На третьем этапе запускается рези-
14.3. Инициализация ядра 689 Старшие адреса памяти Младшие адреса Перемещенный boot Начальная загрузка boo Рис. 14.1. Размещение программы boot в памяти дентный процесс системы, составляющий основу обычной среды FreeBSD времени исполнения, а затем выполняются сценарии запуска уровня пользователя. Запуск на языке ассемблера Первые шаги, предпринимаемые системой в ходе инициализации, выполняются кодом на языке ассемблера. Эта работа является сильно машинно-зависимой и включает сле- следующее. * Установку стека времени выполнения. * Идентификацию типа процессора, на которой выполняется система. Вычисление объема физической памяти на машине. * Включение аппаратной трансляции виртуальных адресов. Инициализацию аппаратного управления памятью. * Установку при необходимости таблиц для SMP. Создание аппаратного контекста для процесса 0. * Вызов начальной, написанной на С точки входа в систему. Хотя детали этих шагов могут отличаться от архитектуры к архитектуре, описан- описанная здесь общая схема применима к любой машине, на которой работает FreeBSD. Когда программа boot запускает ядро FreeBSD, она устанавливает лишь два компо- компонента состояния машины. 1. Все прерывания блокируются. 2. Аппаратная трансляция адресов отключается таким образом, что все ссылки памяти относятся к адресам физической памяти.
690 Глава 14. Запуск и выключение Программа boot передает также ядру идентификатор устройства загрузки и набор флагов загрузки. Ядро FreeBSD ничего не предполагает о состоянии машины, на которой оно работает. Ядро загружается в физическую память по известному адресу - часто по наимень- наименьшему физическому адресу. В ходе обычной работы аппаратура трансляции адресов включена и образ ядра отображается в виртуальную память, начиная с адреса вблизи вершины адресного пространства. До включения трансляции адресов код запуска на языке ассемблера должен преобразовать все абсолютные адреса из виртуальных в фи- физические. Ядро обычно загружается в непрерывную область физической памяти, поэтому трансляция представляет собой просто постоянное смещение, которое должно быть сохранено в индексном регистре. Следующей задачей кода запуска является идентификация типа процессора, на котором выполняется система. Часто более старые версии процессоров поддерживают лишь подмножество полного набора инструкций. Для этих машин ядро должно эмулировать отсутствующие аппаратные инструкции программным способом. Для большинства архитектур FreeBSD можно сконфигурировать таким образом, что один загружаемый образ ядра может поддерживать все модели в семействе архитектуры. Код запуска может также вызывать машинно-зависимый код для инициализации процессора или подсистемы виртуальной памяти. 14.4. Инициализация модуля ядра В предыдущих версиях BSD инициализация нижележащего оборудования выполнялась ис- исключительно индивидуально написанным кодом. Для каждого, кто хотел добавить новую службу или подсистему, было необходимо глубокое понимание всей операционной систе- системы, что замедляло развитие ядра. В FreeBSD был разработан метод для разделения служб ядра на модули и логического упорядочивания этих модулей во время загрузки, упрощая экспериментальное добавление новых возможностей к операционной системе. Все подсис- подсистемы перечислены в заголовочном файле /sys/sys/kernel.h. В FreeBSD используются два вида модулей ядра. Модули, которые могут загру- загружаться в ядро и выгружаться из него в ходе выполнения, называются загружаемыми модулями ядра и обсуждаются далее в данном разделе. Модули ядра, которые должны быть загружены во время начальной загрузки и не могут выгружаться в ходе выполне- выполнения, считаются постоянными модулями ядра. Постоянный модуль экспортируется сис- системе с использованием макроса SYSINIT для идентификации модуля, его процедуры инициализации и порядка, в котором она должна вызываться. SYSINIT(имя, подсистема, порядок, функция, идентификатор) Все модули организованы в двухуровневую иерархию для предоставления упоря- упорядоченного запуска системы. Аргумент подсистема является первым уровнем иерархии. Каждой подсистеме назначается определенная числовая константа, которая создает упоря-
14.4. Инициализация модуля ядра 691 допивание первого уровня модулей, которые должны быть загружены. Второй уровень иерархии управляется аргументом порядок. Если два модуля находятся в одной и той же подсистеме, порядок показывает, который должен идти сначала. Аргумент функция являет- является функцией инициализации, которую ядро вызывает при запуске системы. После завершения кодом на языке ассемблера своей работы он вызывает первую написанную на С процедуру ядра: процедуру mijstartupQ. Процедура mijstartupQ сначала сортирует список модулей, которые должны быть запущены, а затем вызывает процедуру функция каждой. Некоторая часть инициализации является машинно-зави- машинно-зависимой. Другая часть инициализации является машинно-независимой. Она включает службы, которым не нужно знать о нижележащем оборудовании. Каждый модуль ядра реализован либо как аппаратно-зависимый, либо аппаратно-независимый, в соответст- соответствии с его нуждами. После завершения последовательности запуска на языке ассемб- ассемблера через точки входа С вызывается любой другой код на языке ассемблера, необхо- необходимый для запуска системы. Это разделение зависимой и независимой инициализации упрощает задачу программирования последовательности запуска на новой платформе по сравнению с предыдущими выпусками BSD. Базовые службы Прежде чем FreeBSD сможет делать какую-нибудь полезную работу, она должна устано- установить некоторые базовые службы, которые используются всеми подсистемами внутри ядра. Эти службы включают поддержку мьютексов, менеджер блокировок и менеджер памяти ядра. Они показаны в табл. 14.2. Все эти службы инициализируются в начале последовательности загрузки, чтобы их могла использовать оставшаяся часть ядра. Табл. 14.2. Базовые службы Модуль Первая процедура SI_SUB_MTX_POOL_STATIC mtx_pool_setup_static() SI_SUB_LOCKMGR lockmgr_init() SI_SUB_VM vm_mem_init() SI_SUB_KMEM kmeminitO SI_SUB_KVM_RSRC vmmapentry_rsrc_init() SI_SUB_WITNESS witness_initialize() SI_SUB_MTX_POOL_DYNAMIC mtx_pool_setup_dynamic() SI_SUB_LOCK selecting) SI_SUB_EVENTHANDLER eventhandler_init() SI_SUBJCLD linker_init() SI_SUB_CPU cpu_startup() Мьютексы, которые были рассмотрены в разделе 4.3, инициализируются двумя различными пулами: один статический, а другой динамический. Инициализация раз-
692 Глава 14. Запуск и выключение делена, потому что динамические пулы используют распределитель памяти ядра, который должен быть установлен после статических мьютексов. Из-за тесной зависи- зависимости служб ядра друг от друга необходимо тщательно продумывать изменения порядка служб ядра. Непосредственно после инициализации менеджера блокировок система включает систему виртуальной памяти посредством вызова vm_mem_init(). После завершения про- процедуры vm_mem_init() все выделения памяти ядром или процессами осуществляются для виртуальных адресов, которые преобразуются аппаратурой управления памятью в фи- физические адреса. Когда работает система виртуальной памяти, ядро запускает свой собст- собственный внутренний распределитель. Последней частью инициализации подсистемы памяти является наложение ограничений на ресурсы, используемые системой виртуаль- виртуальной памяти ядра, что выполняется процедурой vmmapentry_rsrc_init(). Модули ядра теперь могут запрашивать память, используя процедуру ядра mallocQ. Переход от однопроцессорного ядра к ядру с поддержкой SMP потребовал блокировки структур данных ядра для нескольких независимых потоков. Отладка многопроцессорного кода достаточно трудна на уровне процессов, когда операционная система может предоста- предоставить программисту некоторую помощь. В ядре операционной системы эта проблема стано- становится еще сложнее из-за того, что «рухнувшее» ядро тяжело отлаживать. Чтобы помочь в отладке ядра, FreeBSD добавила библиотеку ядра, которая может отслеживать все используемые и затем освобождаемые блокировки. Процедура witnessJnitializeQ инстан- циирует модуль SIJSUBWITNESS в качестве базовой службы для предоставления биб- библиотеки модуля доказательств модулям, обрабатывающим блокировки. Некоторые службы требуют, чтобы их блокировки выделялись в виде части базо- базовых служб. Системный вызов select и подсистема, обслуживающая объекты ядра, являются двумя такими службами. Модуль SI_SUB_LOCK предоставляет для служб место для регистрации того, что нужно каждый раз в начале последовательности загрузки вызывать их процедуры инициализации блокировок. Модуль обработки событий позволяет различным службам регистрировать функ- функции, вызываемые ядром при возникновении события. Служба обработки события ин- интенсивно используется в последовательности выключения, описанной в разделе 14.6, но обрабатывает также такие разнообразные задачи, как клонирование устройства работа системы виртуальной памяти при нехватке памяти и разветвление, исполнение и завершение процессов. Модуль обработки событий SI_SUB_EVENTHANDLER запускается как часть базовых служб таким образом, чтобы другие модули могли его использовать при своем запуске. Последней запускаемой службой является загрузчик модулей ядра, который загру- загружает в систему динамические модули ядра при загрузке системы или в ходе ее работы. Процедура module_init() создает базовые структуры данных для обработки загрузки и выгрузки модуля, а также регистрации обработчика события для выгрузки всех модулей при выключении системы. Использование загрузчиком модулей ядра является
14.4. Инициализация модуля ядра 693 причиной того, что этот модуль должен инициализироваться после только что обсуж- обсужденного модуля обработчика событий. Когда базовые службы запущены, ядро может теперь завершить установку процес- процессора. Модуль SI_SUB_CPU в действительности разделен на несколько подмодулей, которые отвечают за различные компоненты запуска системы. Первым подмодулем, который должен быть инициализирован, является расширенный программируемый кон- контроллер прерываний (Advanced Programmable Interrupt Controller - APIC), который пре- предоставляет поддержку аппаратных прерываний, а также координирует работу различных процессоров в системе SMP. Процедура apic_init() запускает устройство APIC, а затем зондирует наличие в системе других процессоров. Без устройства APIC у нескольких процессоров в системе SMP не было бы способа координировать свою работу, поэтому этот шаг предпринимается в начале последовательности загрузки системы. Хотя система пока еще не готова запустить другие процессоры, она выделяет и инициализирует под- поддерживающие их структуры данных. Следующей подсистемой для запуска является сам центральный процессор. Хотя он уже работает, запущенный загрузочным кодом на языке ассемблера, в этот момент происходит несколько других частей инициализации. Процедурой startclockQ запускаются часы реального времени, и информация о процес- процессоре выводится на консоли вместе со статистикой памяти. Модулем процессора запус- запускается также система буферов, чтобы ядро могло читать данные с диска. FreeBSD сильно зависит от стабильного хранилища для памяти подкачки и для загрузки модулей ядра и программ. Поэтому система буферов устанавливается в начале последовательности загрузки, чтобы с самого начала дать ядру доступ к этим ресурсам. В системах SMP после модулей APIC и процессора форсируется запуск модуля cpujnp, поскольку его аргумент порядка установлен в SI_ORDER_SECOND, тогда как порядок и APIC, и процессора равен SI_ORDER_FIRST. Поскольку в системе SMP контроллер APIC абсолютно необходим, то инициализация SMP должна быть произве- произведена после инициализации APIC. Функция инициализации cpujnp, mp_start(), вызывает машинно-зависимые процедуры, которые выполняют фактическую работу по запуску других процессоров. Когда эта процедура завершается, все процессоры в SMP-системе активны, но не выполняют никакой работы до тех пор, пока не завершится вся после- последовательность запуска. Инициализация потоков ядра В ядре есть несколько процессов, которые устанавливаются во время загрузки и которые нужны всегда при работе ядра. Модули, представляющие процессы ядра, показаны в табл. 14.3. Процессы swapper, init и idle устанавливаются после инициализации процес- процессора, но они не планируются на исполнение. Ни один процесс или поток ядра не выпол- выполняется до тех пор, пока не будут инициализированы все модули ядра. Когда ядро работает, все новые процессы создаются путем разветвления сущест- существующего процесса. Однако, поскольку при первом запуске системы нет ни одного про-
694 Глава 14. Запуск и выключение Табл. 14.3. Модули процесса ядра Модуль Первая процедура SI_SUB_INTRINSIC procO_init() SI_SUB_VM_CONF vm_initjimits() SI_SUB_RUN_QUEUE runq_init() SI_SUB_KTRACE ktrace_initO SI_SUB_CREATE_INIT create_init() SI_SUB_SCHED_IDLE idle_setup() цесса, код запуска должен создать первый процесс. Планировщик подкачки (swapper) всегда является первым процессом и получает PID 0. Создание процесса подкачки началось в момент запуска на языке ассемблера, но теперь в системе работает доста- достаточно служб для того, чтобы почти завершить создание процесса подкачки. Процедура procO_init() не только устанавливает процесс 0, но также инициализирует все глобаль- глобальные структуры данных для управления процессами и потоками, таблицу дескрипторов файлов и структуры ограничений. Процедура procOJnitQ создает также прототип ото- отображения виртуальной памяти, которая будет прообразом для всех других процессов, которые в конечном счете будут созданы. После того как были инициализированы структуры данных для процесса swapper, система создает процесс ink, вызвав процедуру create_init(). Процесс ink устанавлива- устанавливается непосредственно после процесса swapper таким образом, что у него всегда будет PID 1. Поскольку привилегированные пользователи взаимодействуют с процессом ink, посылая ему сигналы, обеспечение наличия у него хорошо известного PID означа- означает, что пользователям не придется сначала искать его, чтобы иметь возможность взаи- взаимодействия с ним. У каждого процессора в системе есть процесс idle. Именно процесс idle отвечает за то, чтобы остановить процессор, когда для него нет работы. После установки процес- процессов swapper и ink ядро инициализирует для каждого процессора в системе процесс idle. Если в системе есть лишь один процессор, тогда создается лишь один процесс idle. Подобно всем другим процессам ядра, в этот момент процесс idle лишь создается, но не запускается. Инициализация модулей устройств Когда все базовые службы ядра на месте и созданы все базовые процессы, можно ини- инициализировать остальные устройства в системе, куда входят диски, сетевые интерфейсы и таймеры. В табл. 14.4 показаны основные компоненты, используемые для инициали- инициализации модулей устройств.
14.4. Инициализация модуля ядра 695 Табл. 14.4. Модули устройств Модуль Первая процедура SI_SUB_MBUF mbuf_init() SI_SUB_INTR intr_init() SI_SUB_SOFTINTR start_softintr(), start_netisr() SI_SUB_DEVFS devfs_initO, devs_set_ready() SI_SUB_INIT_IF ifjnitO SI_SUB_DRIVERS Множество различных процедур SI_SUB_CONFIGURE configure_firstO SI_SUB_VFS vfsinitO SI_SUB_CLOCKS initclocks() SI_SUB_CLIST clist_init() Прежде чем могут быть инициализированы какие-либо устройства - в частности, сетевые интерфейсы, - должна быть установлена подсистема mbuf, чтобы у сетевых интерфейсов был набор буферов, который они могут использовать при своей собствен- собственной инициализации. Инициализация подсистемы mbuf осуществляется модулем SI_SUB_MBUF и процедурой mbuf_init(). Как мы обсуждали в разделе 11.3, подсистеме mbuf нужно управлять двумя видами памяти: небольшими mbuf и кластерами mbuf. Каждый вид памяти выделяется из своей собственной области памяти, чтобы сохра- сохранить распределитель простым и чтобы остающаяся память не фрагментировалась выделениями различных размеров. В этот момент последовательности запуска в системе не разрешены прерывания. Теперь ядро устанавливает все потоки прерываний, которые будут обрабатывать прерывания, когда система начнет работу. Прерывания устанавливаются двумя моду- модулями: SI_SUB_INTR, который устанавливает потоки прерываний, обрабатывающие прерывания от устройств, и SI_SUB_SOFTINTR, который создает потоки програм- программных прерываний. Потоки программных прерываний используются службами, которые обрабатывают асинхронные события, которые не генерируются аппаратно. Потоки программных прерываний предоставляют программные часы, которые под- поддерживают подсистему выносок (callout). Они также предусматривают сетевой поток, который забирает входящие пакеты из очередей сетевых интерфейсов и передает их через сетевой стек. Как часть запуска реальных аппаратных устройств, ядро сначала инициализирует файловую систему устройств, а затем готовит сетевой стек для работы с устройствами посредством вызова процедуры if_init(). Процедура if_init() не инициализирует какие- либо сетевые интерфейсы; она лишь устанавливает структуры данных, которые будут их поддерживать. В конечном счете устройства сами инициализируются модулями
696 Глава 14. Запуск и выключение SISUBDRIVERS и SISUBCONFIGURE. Все устройства в системе инициали- инициализируются посредством автоконфигурирования, как описано в разделе 7.5. После конфигурирования устройств инициализируется виртуальная файловая сис- система. Запуск VFS является многоэтапным процессом, который влечет за собой инициа- инициализацию самой VFS, подсистемы vnode и подсистемы кеша имен и имен путей, которая отображает имена путей на массив inode. Поддержка именованных каналов также ини- инициализируется как часть VFS. Следующими системами для запуска являются системы, относящиеся к часам реального времени, предоставляемым аппаратным обеспечением. Процедура initclocksQ, являющаяся частью модуля SI_SUB_CLOCKS, вызывает специфическую для архитек- архитектуры процедуру cpu_initclocks() для инициализации в системе аппаратных часов и их запуска. После начала работы аппаратных часов запускаются другие службы, такие, как протокол сетевого времени (Network Time Protocol - NTP), опрос устройств и счетчик времени. Последние структуры данных для инициализации находятся в под- подсистеме терминалов модуля SI_SUB_CLIST. Все, что нужно сделать, - это выделить начальный набор cblock и добавить их в clist. Загружаемые модули ядра Некоторые модули ядра можно загружать, выключать и выгружать во время работы системы. Наличие системы, в которой службы ядра могут загружаться и выгружаться во время работы, имеет ряд преимуществ перед системой, в которой все службы ядра должны компоноваться во время построения. Для системных программистов возмож- возможность загружать и выгружать модули в ходе работы означает, что они могут быстрее разрабатывать свой код. Непосредственно компоновать с ядром нужно лишь те моду- модули, которые абсолютно необходимы для системы, такие, как управление памятью или планировщик. Модуль ядра можно откомпилировать, загрузить, отладить, выгрузить, изменить, откомпилировать и снова загрузить без необходимости компоновать его не- непосредственно с ядром или необходимости перезагружать систему. При работе на месте использование модулей ядра делает возможным обновление лишь избранных частей системы по мере необходимости. Обновление на месте абсолютно необходимо во встроенных (embedded) приложениях, когда система может быть физически недо- недоступной, но это удобно также в более традиционных средах, когда нужно изменить множество систем в одно и то же время (например, в большой группе серверов). Одной из проблем при разрешении загрузки и выгрузки модулей во время работы является безопасность. В версиях BSD до FreeBSD ядро было цельным и полностью защищенным от изменения пользователем во время работы. Единственным способом взаимодействия с работающим ядром был интерфейс syscall (системных вызовов). Системные вызовы определялись во время построения ядра и предоставляли лишь узкий канал коммуникации. Этот плотно контролируемый интерфейс предоставлял уровень безопасности. Хотя пользователи могли вызывать сбои своих собственных
14.4. Инициализация модуля ядра 697 программ, при отсутствии серьезной ошибки в ядре они не могли бы вызвать сбой операционной системы или процессов других пользователей. С введением загружае- загружаемых модулей ядра любой пользователь, который может получить привилегии суперпользователя (root), может изменить нижележащее ядро. Определенные службы нельзя выгрузить, что является разновидностью защиты, но все службы, созданные должным образом, могут быть загружены, включая злонамеренные. После загрузки злоумышленного модуля в ядро нет защиты против злонамеренных разрушений. Серь- Серьезным упущением в системе модулей ядра является то, что она не поддерживает цифровые подписи модулей. Если разработчик хочет предоставить всем желающим службу через загружаемый модуль ядра - например, в виде части большого приложе- приложения, - для клиентов приложения было бы полезно, если бы ядро могло проверить, что модуль на самом деле поступил от заявленного разработчика. На данный момент под- подписывание модулей и проверка службы не является частью системы загружаемых мо- модулей ядра. По этим соображениям безопасности большинство людей, работающих с FreeBSD в коммерческом окружении, продолжают использовать полностью целост- целостные ядра и не загружают случайные службы во время работы. Загружаемые модули ядра объявляются следующим образом: DECLARE_MODULE(имя, данные, подсистема, порядок) У каждого модуля есть имя, подсистема и порядок, служащие тем же целям, что и в макросе SYSINIT. Ключевым отличием является использование аргумента данные, представляющего собой структуру данных, определенную следующим образом: int (*modeventhand_t)( struct module *module, int command, void ^argument); typedef moduledata = { const char *name; modeventhand_t event_handler; void *data; } moduledata_t; У всех модулей есть связанная с ними версия, которая объявляется с помощью макроса MODULEVERSION. Без версии было бы невозможно отличить различные переработанные версии того же самого модуля, что затрудняет обновления на месте. Одним из последних макросов, используемых модулями ядра, является SYSCALL_MODULE_HELPER, который разработчики используют для добавления к ядру новых системных вызовов. Для того чтобы были модули ядра, которые можно загружать как при запуске сис- системы, так и в ходе ее работы, необходимо учесть два различных случая. Когда модуль загружается в ходе запуска системы, он уже обработан процессом построения ядра, что означает, что его точки входа системных вызовов и другие данные уже известны
698 Глава 14. Запуск и выключение ядру. Это знание облегчает процесс загрузки. Все, что нужно сделать, - это вызвать обработчик событий модуля с помощью команды MODJLOAD. Во время работы модуль должен быть загружен в память, зарегистрирован ядром, и его системные вызовы должны быть динамически добавлены в таблицу системных вызовов. После того как вся эта работа проделана, его можно инициализировать, добавив свой обра- обработчик событий. Вся загрузка во время выполнения обрабатывается системным вызо- вызовом kldload и процедурой module_register(). Чтобы сохранить интерфейс, используе- используемый программистами простым, все эти функциональные возможности скрыты за макросом DECLARE_MODULE и использованием единственной процедуры обработки событий. При создании модуля ядра программисту нужно беспокоиться лишь о созда- создании обработчика событий модуля и экспортировании системных вызовов обработчика модуля через макросы. Запуск межпроцессного взаимодействия Модули, вовлеченные в запуск интерфейсов IPC, показаны в табл. 14.5. Первыми тремя модулями, которые должны быть загружены и запущены как часть поддержки IPC, являются семафоры, разделяемая память и очереди сообщений System V. Эти средства локального IPC были рассмотрены в разделе 11.8, а их инициализация проста. Их делает интересными то, что это первые модули, которые могут загружаться и выгру- выгружаться во время работы. Табл. 14.5. Модули межпроцессного взаимодействия Модуль Первая процедура SI_SUB_SYSV_SEM sysvsem_modload() SI_SUB_SYSV_SHM sysvshm_modload() SI_SUB_SYSV_MSG sysvmsg_modload() SI_SUB_PROTO_IF if_check() SI_SUB_PROTO_DOMAIN domaininit() SI_SUB_PROTO_IFATTACHDOMAIN if_attachdomain() В качестве примера мы используем модуль SI_SUB_SYSV_SEM. Он реализует семафоры System V. Этот модуль объявлен системе следующим образом: static moduledata_t sysvsem_mod - { "sysvsem", &sysvsem_modload, NULL SYSCALL_MODULE_HELPER(semsys); SYSCALL_MODULE_HELPER( semctl); SYSCALL_MODULE_HELPER(semget);
14.4. Инициализация модуля ядра 699 SYSCALL_MODULE_HELPER(semop); DECLARE_MODULE(sysvsem, sysvsem_mod/ SI_SUB_SYSV_SEM/ SI_ORDER_FIRST); MODULE_VERSION(sysvsem, 1); Все системные вызовы, предоставляемые модулем, объявлены в макросе SYSCALL_MODULE_HELPER и должны быть знакомы из раздела 11.8. Макрос DECLARE_MODULE гарантирует, что добавляется элемент в последовательность запуска и что его порядок первый. В этой подсистеме нет других модулей, поэтому аргумент порядок не важен. Процедурой, которую последовательность запуска вызы- вызывает первой, является не sysvsemjnodloadQ, a module_register_init(), которая затем вызьшает процедуру sysvsem jnodloadQ модуля. Поскольку этот модуль был скомпонован в ядро во время построения, все его системные вызовы и другие данные уже скомпоно- скомпонованы с ядром. Когда процедура module_register_init() завершается, модуль загружен и готов к использованию. За подсистемой локального IPC запускается несколько псевдоустройств. Псевдо- Псевдоустройства являются различными службами внутри ядра, которые представляют себя остальной системе как устройства. Примеры псевдоустройств включают буфер кадров, поддержку виртуальных LAN, сетевые брандмауэры и криптографические подсистемы. Все псевдоустройства объявлены в виде загружаемых модулей ядра. Последние три модуля - SI_SUB_PROTO_IF, SI_SUB_PROTO_DOMAIN и SI_SUB_PROTO_IFATTACHDOMAIN - инициализируют код, который поддерживает обсуждавшиеся в части IV сетевые возможности. В модуле SI_SUB_PROTO_IF есть одна процедура, if_check(), которая проверяет каждый сетевой интерфейс в системе на предмет того, был ли он правильно инициализирован. SI_SUB_PROTO_DOMAIN является набором модулей, которые инициализируют различные коммуникационные домены, поддерживаемые ядром. Когда базовые структуры данных для доменов ини- инициализированы процедурой domaininitQ, инициализируются сами коммуникационные домены. Каждый домен отвечает за объявление себя путем использования макроса DOMAIN_SET, который предоставляет процедуру инициализации для вызова ядром, подобно макросу SYSINIT. В отличие от многих предыдущих служб, которые были объявлены в виде модулей, сетевые домены объявляются таким образом, что их нельзя удалить. Домены закрепляются в ядре после своей загрузки, поскольку уровень сокетов может использовать структуры данных домена для активной коммуникации, и резуль- результат выгрузки домена, когда он используется, непредсказуем. Возможность выгрузки коммуникационного домена во время работы потребует множества изменений в моду- модулях уровня сокетов и доменов.
700 Глава 14. Запуск и выключение Запуск потоков ядра Набор модулей, приведенных в табл. 14.6, завершает установку всех потоков, необ- необходимых для получения рабочего ядра. Здесь запускаются потоки ядра swapper, init, pagezero, pagedaemon, bufdaemon, vnlru и syncer, а также потоки ядра для сетевой файловой системы (NFS). До этого места система работала в однопроцессорном режиме. На многопроцессорной системе модуль SI_SUB_SMP вызывает последнюю часть машинно-зависимого кода, процедуру release_aps(), чтобы запустить в систе- системе другие процессоры. Теперь настало время запустить систему. Модуль SI_SUB_RUN_SCHEDULER является потоком, особым в последовательности запус- запуска, поскольку он всегда должен выполняться последним. В списке модулей ему дается наибольший возможный номер подсистемы со всеми установленными - 1-й, чтобы он не был случайно помещен в списке раньше. Вызывается процедура scheduler (), которая никогда не возвращается. Она немедленно начинает планировать выполнение на процессорах системы потоков ядра и процессов уровня пользователя. Табл. 14.6. Модули потоков ядра Модуль Первая процедура SI_SUB_INTRINSIC_POST procO_post() SI_SUB_KTHREAD_INIT kick_init() SI_SUB_KTHREAD_PAGE vm_pageout() SI_SUB_KTHREAD_VM vm_daemon() SI_SUB_KTHREAD_BUF buf_daemon() SI_SUB_KTHREAD_UPDATE vnlru_proc() SI_SUB_KTHREAD_IDLE ald_daemon() SI_SUB_SMP release_aps() SI_SUB_RUN_SCHEDULER scheduIerQ 14J5, Инициализация уровня пользователя С началом процесса init большая часть системы является работающей и функциональ- функциональной. Есть несколько дополнительных шагов, выполняющих между данным моментом и временем, когда пользователь видит приглашение зарегистрироваться в системе. Все эти действия выполняются программами уровня пользователя, которые используют стандартный интерфейс системных вызовов FreeBSD, который был описан в предыду- предыдущих главах. Мы кратко рассмотрим шаги, которые имеют место в типичной системе.
14.5. Инициализация уровня пользователя 701 /sbin/init Программа /sbin/init вызывается в качестве последнего шага в процедуре начальной загрузки. Параметры, указанные во время загрузки FreeBSD, передаются init машин- машинно-зависимым образом, init использует значения этих флагов для определения того, должна ли система работать в однопользовательском или многопользовательском режиме и должна ли она проверять согласованность дисков с помощью программы fsck. При работе в однопользовательском режиме init ответвляет процесс, который вызывает стандартную оболочку /bin/sh. Дескрипторы стандартного ввода, вывода и ошибки процесса направляются в системный терминал консоли /dev/console. Затем эта оболочка работает обычным образом, но с привилегиями суперпользователя до тех пор, пока не завершится. При работе в многопользовательском окружении init сначала порождает оболочку для интерпретации команд в файле /etc/rc, который является корнем набора сценариев запуска системы, которые все выполняют инициализацию системы на уровне пользо- пользователя. Если сценарий /etc/rc завершается успешно, init ответвляет свою копию для каж- каждого устройства терминала, который помечен как включенный в файле /etc/ttys. Эти копии init вызывают другие системные программы, такие, как /usr/Iibexec/getty, для управления стандартной процедурой регистрации. Процесс 1 всегда действует в каче- качестве основного координирующего процесса для работы системы. Он отвечает за порож- порождение новых процессов, когда завершаются сеансы терминала, и за управление выключением работающей системы. Сценарии запуска системы Файл /etc/rc обыкновенно пуст и служит лишь для упорядочивания и исполнения различных сценариев запуска системы, содержащихся в каталоге /etc/red. Два файла, /etc/rc.conf и /etc/default/rc.conf, контролируют, какие службы уровня пользователя за- запускаются во время загрузки. Каждый из этих файлов загружается сценариями загруз- загрузки системы при их исполнении, /etc/defaults/rc.conf содержит значения по умолчанию для различных переменных оболочки, которые управляют тем, должна ли служба запус- запускаться. Администраторы изменяют значения по умолчанию, помещая в /etc/rc.conf другие значения для тех же самых переменных оболочки. Например, чтобы включить использование в ходе загрузки безопасную оболочку (Secure Shell - ssh), в /etc/rc.conf была бы помещена следующая строка: sshd_enable = "yes" Основой системы сценариев гс является программа с названием reorder, которая принимает в качестве ввода сценарии оболочки, вычисляет их взаимозависимости, затем выводит упорядоченный список имен. Каждый сценарий запуска объявляет мо- модули, которые ему нужны, а также те, которые он предоставляет. Программа reorder использует операторы REQUIRE и PROVIDE для определения порядка, в каком нужно запускать сценарии.
702 Глава 14. Запуск и выключение Одной из вспомогательных задач является проверка локальных файловых систем после системных сбоев. Если система не загружена с опцией fastboot, тогда сценарий /etc/rc.d/fsck выполняет проверку файловой системы. В версиях BSD до FreeBSD про- проверки файловой системы были абсолютно необходимыми и должны были проводиться перед любой другой работой, но с введением мягких обновлений (раздел 8.6) и других изменений файловой системы это больше не нужно. Программа /sbin/fsck проверяет целостность файловой системы и исправляет поврежденные файловые системы. Обычно fsck вызывается из сценария /etc/rc.d/fsck для проверки и восстановления каждой файловой системы, до того как последняя монтируется. При начальной загруз- загрузке системы корневая файловая система монтируется с доступом только для чтения. Если корневая файловая система требует исправлений, FreeBSD выполняет вариант системного вызова mount, который запрашивает у ядра повторную загрузку всех струк- структур данных его корневой файловой системы. Повторная загрузка гарантирует согласо- согласованность между данными в памяти ядра и любыми данными в файловой системе, которые были изменены fsck. Монтирование корневой файловой системы с доступом только для чтения гарантирует, что у ядра не будет в памяти каких-либо измененных данных, которые не могут быть загружены повторно. Вслед за проверками файловых систем они монтируются, корневая файловая сис- система изменяется с предоставлением доступа на запись, и подключаются все устройст- устройства, которые должны использоваться для подкачки. Затем проверяются и включаются дисковые квоты, и система запускает фоновые процессы, которые реализуют раз- различные системные службы. Эти процессы включают /usr/sbin/cron, программу, ко- которая выполняет команды через периодические интервалы; /usr/sbin/accton, програм- программу, которая включает учет использования ресурсов системы; и /usr/sbin/syslog, про- процесс регистрации ошибок в журнале. Каждый из этих процессов запускается из своего собственного сценария в /etc/red. /usr/libexec/getty Программа /usr/libexec/getty порождается init для каждой аппаратной терминальной линии в системе. Эта программа отвечает за открытие и инициализацию линии терми- терминала. В качестве меры предосторожности против открывания линии другим процессом и подсматривания ввода пользователя getty использует системный вызов revoke, чтобы аннулировать доступ к любым открытым для линии дескрипторам (см. раздел 6.6). Затем он создает для линии новый сеанс и запрашивает назначение этого терминала в качестве управляющего терминала сеанса. Программа getty устанавливает началь- начальные параметры для линии терминала и устанавливает тип терминала, подключенного к линии. Для линий связи, подключенных к модему, getty может быть настроен для приема соединений с различными скоростями, getty выбирает эту скорость, изменяя ее в ответ на символ прерывания или ошибку кадра, генерируемую обычно в результате нажатия пользователем клавиши прерывания. Пользователь может несколько раз нажать на клавишу прерывания, чтобы циклически переключить несколько скоростей
14.6. Работа системы 703 лини связи, пока не будет найдена нужная. Действия getty управляются базой данных конфигурирования терминала, которая расположена в файле /etc/gettytab. В конечном счете getty считывает регистрационное имя и вызывает программу /usr/bin/Iogin для завершения последовательности регистрации. /usr/bin/login Программа login отвечает за регистрацию пользователя в системе; она обычно вызыва- вызывается /usr/libexec/getty с именем пользователя, который хочет войти в систему, login за- запрашивает у пользователя пароль (после отключения режима эхо терминала, если это возможно). Если пароль, предоставленный пользователем, дает после шифрования то же самое значение, которое хранится в главном файле паролей /etc/master.passwd, login делает запись о регистрации в различных файлах отчетов, инициализирует иден- идентификаторы пользователя и группы значениями, указанными в файлах пароля и /etc/ group, и переходит в регистрационный каталог пользователя. Регистрационное имя пользователя сохраняется в структуре сеанса с использованием системного вызова set- login таким образом, что оно может быть надежно получено посредством системного вызова getlogin программами, которые хотят знать регистрационное имя, связанное с данным процессом. Наконец, login использует exec, чтобы загрузить поверх себя обо- оболочку пользователя. Программа login вызывается также, когда пользователь входит в систему через сете- сетевое соединение. Для таких соединений getty и in it не используются; их функциональ- функциональность реализуется демоном, порождаемому при установлении сетевого соединения. 14.6. Работа системы В данном разделе мы рассмотрим темы, которые связаны с процедурой запуска системы. Конфигурация ядра Программное обеспечение, составляющее ядро FreeBSD, определяется файлом конфи- конфигурации, который интерпретируется программой /usr/sbin/config, которая, в свою очередь, вызывается как часть процесса построения ядра. В FreeBSD процесс построе- построения ядра стал значительно более сложным и теперь контролируется набором целей Makefile. Чтобы построить ядро, пользователь вызывает make следующим образом: make buildkernel КЕКЫСОЫР=<файл_конфигурации_ядра> Аргумент buildkernel является целью Makefile, который поручает make постро- построить ядро, но не устанавливать его. KERNCONF является переменной Makefile, в качестве значения которой устанавливается конфигурация ядра. Когда ядро построено соответствующим образом, оно устанавливается посредством запуска make следующим образом:
704 Глава 14. Запуск и выключение make installkernel КЕКЫСОЫР=<файл_конфигурации_ядра> Одной из причин для такого нового процесса построения является необходимость по- построения и установки необходимых модулей ядра, которые мы обсуждали в разделе 14.4. Файл конфигурации указывает аппаратные и программные компоненты, которые должны поддерживаться ядром. Процесс построения генерирует несколько выходных файлов, некоторые из которых компилируются и компонуются в загружаемый образ ядра. Он создает также каталог, в котором будут строиться все загружаемые модули ядра. Когда ядро устанавливается, его модули также устанавливаются. Полное описание про- процесса построения ядра приводится в Hamby & Mock [2004]. Выключение системы и автоматическая перезагрузка FreeBSD предоставляет несколько вспомогательных программ для остановки или перезагрузки системы или для смены режима работы системы с многопользователь- многопользовательского в однопользовательский. Безопасная остановка и перезагрузка системы требуют поддержки ядра. Эта поддержка предоставляется системным вызовом reboot. Системный вызов reboot является привилегированным вызовом. Параметр этого системного вызова определяет, как должна выключаться и перезагружаться система. Этот параметр является надмножеством флагов, передаваемых программой boot сис- системе во время начальной загрузки последней. Система может быть остановлена (обычно посредством входа в бесконечный цикл) или может быть перезагружена в режиме однопользовательской или многопользовательской работы. Имеются допол- дополнительные элементы управления, которые можно использовать для форсирования соз- создания аварийного дампа перед перезагрузкой (информацию об аварийных дампах см. в следующем подразделе) и для отключения записи данных, находящихся в буферном кеше диска, если информация в буферном кеше является неверной. При распознавании катастрофического сбоя обычно применяется также автома- автоматическая перезагрузка. Система перезагрузит себя автоматически, если она распознает невосстановимую ошибку в ходе обычной работы. Все сбои такого типа, называемые паникой, обрабатываются подпрограммой panicQ. Когда система выключается, она проходит через три отдельные фазы. Выключение всех служб, которые зависят от файловой системы для хранения данных. Выключение самой файловой системы. Выключение служб, которые не зависят от файловой системы. Эти три фазы необходимы, поскольку некоторые службы захотят записать в файло- файловую систему некоторые последние данные, прежде чем она будет выключена, и чистый перезапуск может быть невозможен, если они не смогут это сделать.
14.6. Работа системы 705 Службы регистрируют в ядре обработчики событий для обеспечения упорядочен- упорядоченного выключения системы. Каждый обработчик события объявляется следующим макросом: EVENTHANDLER_REGISTER(имя, функция, аргумент, приоритет) Имя показывает, в какой части последовательности выключения будет вызвана функция обработчика события, как показано в табл. 14.7. Аргумент дает модулю воз- возможность передать себе любые частные данные, необходимые для выключения себя. Приоритет упорядочивает процедуры выключения внутри фазы. Приоритет служит здесь тем же целям, каким служит аргумент порядок в макросе SYSINIT при создании упорядоченной последовательности запуска. Приоритет необходим для того, чтобы службы не отключались, когда от них зависят другие службы. Табл. 14.7. Фазы выключения Имя Фаза выключения shutdown_pre_sync Перед синхронизацией дисков shutdown_post_sync После синхронизации дисков shutdown_final Непосредственно перед остановкой процессора Процедура выключения ядра сначала обходит список функций shutdown_pre_sync и вызывает каждую из них по очереди, а затем выключает файловые системы на локальных дисках. С находящимися в покое файловыми системами она вызывает функции shutdown_post_sync. При необходимости делается дамп ядра (например, если она была вызвана вследствие паники ядра). Дампы ядра записываются непосредствен- непосредственно в раздел подкачки, а не в обычную файловую систему, вот почему этот шаг можно делать после отключения файловых систем. Наконец, процедура выключения ядра вызывает все функции, зарегистрированные в группе shutdown Jinal. Затем она входит в бесконечный цикл в ожидании сброса пользователем. Отладка системы FreeBSD предоставляет несколько возможностей для отладки системных сбоев. Чаще всего используемым средством является аварийный дамп (crash dump): копия памяти, которая сохраняется ядром во вторичном хранилище, когда возникает катастрофиче- катастрофический сбой. Аварийные дампы создаются процедурой doadumpQ. Они возникают, если делается системный вызов перезагрузки, в котором указан флаг RB_DUMP, или если система сталкивается с невосстановимой - и неожиданной - ошибкой. Процедура doadumpQ сохраняет текущий контекст посредством вызова процедуры savectxQ, а затем вызывает процедуру dumpsysQ, чтобы записать содержимое физиче- физической памяти во вторичное хранилище. Точное расположение аварийного дампа можно конфигурировать; большинство систем помещают эту информацию в конец первичного
706 Глава 14. Запуск и выключение раздела подкачки. Эта операция выполняется точкой входа дампа отконфигуриро- ванного драйвера диска. Аварийный дамп получается из места его размещения на диске посредством про- программы /sbin/savecore после того, как система перезагружена, а файловые системы проверены. Она создает файл, в которую копируется образ аварийного дампа, savecore делает также для использования в отладке копию начального загружаемого образа ядра. Системный администратор может проверить аварийный дамп с помощью стан- стандартной отладочной программы FreeBSD, gdb. Ядро установлено также таким обра- образом, что отладчик gdb, работающий на одной машине, может соединить себя через по- последовательную линию связи с ядром, работающим на другой машине. После присое- присоединения он может устанавливать точки останова, проверять и изменять структуры данных ядра и вызывать процедуры ядра на отлаживаемой машине. Такой вид отладки на уровне исходных кодов особенно полезен при разработке драйверов устройств ядра, пока разрабатываемый драйвер сам не является драйвером линии последовательной передачи. Передача информации ядру и от ядра В 4.3BSD и более ранних системах утилиты, которым была нужна информация от ядра, открывали специальное устройство /dev/kmem, которое предоставляло доступ к памяти ядра. Используя список имен из двоичного файла ядра, утилиты искали адрес нужного символа и читали значение в этом месте. Утилиты с привилегиями су- суперпользователя могли также использовать эту методику для изменения переменных ядра. Хотя этот подход работал, у него было четыре проблемы. 1. У приложений не было способа надежным образом найти двоичный файл для теку- текущего работающего ядра. Использование неверного двоичного файла привело бы к просмотру /dev/kmem в неправильном месте, что дало бы совершенно неправиль- неправильный вывод. Для программ, которые изменяли ядро, использование неверного дво- двоичного файла обычно приводило к сбою системы с засорением не имеющих отноше- отношения к делу структур данных. 2. Чтение и интерпретация списка имен ядра требует большого времени. Поэтому приложения, которым приходилось читать структуры данных ядра, работали медленно. 3. Приложения, которые получали доступ к памяти ядра, могли читать всю память ядра. Злонамеренные программы могли подсматривать входные очереди термина- терминалов или сетей в поиске пользователей, набирающих важную информацию, такую, как пароли.
14.6. Работа системы 707 4. По мере того как все больше структур данных ядра становились динамически выделяемыми, становилось труднее надежно извлекать нужную информацию. Например, в 4.3BSD все структуры процесса содержались в одной статически выделяемой таблице, которую можно было прочесть за одну операцию. В FreeBSD структуры процессов выделяются динамически, а ссылки на них осуществляются через связанные списки. Поэтому можно считывать лишь по одному процессу за раз. Поскольку элемент процесса подразделен на множество отдельных частей, каждая из которых находится в разных местах памяти ядра, для извлечения из /dev/kmem каж- каждого элемента процесса требуется по несколько операций поисков и чтений. Для решения этих проблем в 4.4BSD был введен системный вызов sysctl. Этот рас- расширяемый интерфейс ядра дает возможность управляемого доступа к структурам данных ядра. Перечисленные ранее проблемы разрешаются следующим образом. 1. Приложениям не нужно знать, с каким двоичным файлом ядра они работают. Рабо- Работающее ядро отвечает на их запрос и знает, где находятся их структуры данных. Таким образом, всегда возвращаются или изменяются нужные структуры данных. 2. Не тратится время на чтение или интерпретацию списков имен. Доступ к струк- структурам данных ядра требует лишь нескольких системных вызовов. 3. Нельзя получить доступ к важным структурам данных. Ядро контролирует набор структур данных, которые оно возвращает. Все остальное в ядре является недос- недоступным. Ядро может наложить свои собственные ограничения на доступ к струк- структурам данных на основе структур данных. 4. Ядро может использовать свои стандартные механизмы для обеспечения согласо- согласованного доступа к распределенным структурам данных. При запросе элементов процесса ядро может получить соответствующие блокировки для обеспечения то- того, что может быть возвращен согласованный набор данных. Дополнительные преимущества этого интерфейса включают в себя следующее. * Интерфейс sysctl полностью интегрирован с системным вызовом jail, так что про- процессы, работающие в тюрьмах, могут получить доступ лишь к тем переменным ядра, которые согласуются с их представлением о системе. * До обновления структуры данных можно проверить данные, которые должны быть изменены. Если изменение структуры данных требует исключительного дос- доступа, до совершения обновления может быть получена соответствующая бло- блокировка. Таким образом, в связанный список может быть добавлен элемент без риска того, что другой процесс обходит этот список в ходе его обновления. Информация может вычисляться лишь по требованию. Нечасто запрашиваемая информация может вычисляться лишь тогда, когда она затребована, а не все
708 Глава 14. Запуск и выключение время. Например, значительная часть статистики виртуальной памяти вычисляет- вычисляется лишь, когда ее запрашивает программа отслеживания системы. Этот интерфейс дает суперпользователю возможность изменять параметры ядра, даже когда система работает в безопасном режиме (безопасный режим описан в разделе 8.2). Чтобы предотвратить должностные преступления, ядро не допуска- допускает открывания /dev/kmem, когда система работает в безопасном режиме. Даже когда система работает в безопасном режиме, интерфейс sysctl по-прежнему будет позволять суперпользователю изменять структуры данных ядра, которые не влияют на безопасность. Системный вызов sysctl описывает пространство имен ядра, используя базу управ- управляющей информации (management information base - MIB). MIB является иерархиче- иерархическим пространством имен, во многом подобным пространству имен файловой систе- системы, за тем исключением, что каждый компонент описывается целым значением, а не строковым именем. У иерархического пространства имен есть несколько преиму- преимуществ. Можно добавлять новые поддеревья без влияния на существующие приложения. * Если ядро не включает поддержку для подсистемы, информация sysctl для этой части системы может быть опущена. * Каждая подсистема ядра может определять свои собственные соглашения по име- именованию. Таким образом, сеть можно разделить на семейства протоколов. Каждое семейство протоколов может быть разделено на специфическую для протокола информацию и т.д. Пространство имен можно разделить на те части, которые являются машинно- независимыми и доступны на каждой архитектуре, и части, которые являются машинно-зависимыми и определяются на основе отдельной архитектуры. С момента добавления в 4.4BSD системного вызова sysctl число переменных, которые он контролирует, было расширено до примерно 1000 значений, управляющих систе- системой виртуальной памяти, файловыми системами, сетевыми стеками и нижележащим оборудованием, а также самим ядром. Упражнения 14.1. В чем назначение программы boot? 14.2. В чем заключается задача программы запуска на языке ассемблера? Почему эта программа написана на языке ассемблера? 14.3. Какие процессы запускаются при загрузке системы? 14.4. Как загружаются в систему модули ядра во время начальной загрузки? Приведите пример модуля ядра.
Ссылки 709 14.5. Системный вызов reboot заставляет систему остановиться или перезагрузиться. Приведите две причины, почему этот системный вызов полезен. 14.6. Приведите две причины полезности загружаемых модулей ядра при разработке служб ядра. Приведите один довод против их использования. 14.7. Почему необходим определенный порядок, в котором должны загружаться и инициализироваться службы ядра? Почему также необходимо иметь определенный порядок, в котором службы ядра должны выключаться? *14.8. Предположим, что машина не имеет часов для времени суток на основе батарейки. Предложите метод для определения того, что часы для времени суток неверны. Опишите способ инициализации времени дня в часах. Каковы ограничения вашего метода? **14.9. Какие макросы являются необходимыми для создания загружаемого модуля ядра? Как вы протестировали бы свой модуль, не компонуя его непосредст- непосредственно с ядром? Ссылки Hamby & Mock, 2004. J. Hamby & J. Mock, "FreeBSD Handbook, Chapter 9, Configuring the FreeBSD Kernel", http://www.freebsd.org/doc/eп_USJSO8859-l/booЫhaпdbookAemelcoпflgЯtml, 2004.
Глоссарий ACL. См. Список контроля доступа. ARP. См. Протокол определения адреса (address resolution protocol). AST. См. Асинхронное системное прерывание (asynchronous system trap). CPU. См. Центральный процессор. DARPA (Defense Advanced Research Projects Agency - Управление перспективных исследовательских программ). Управление Министерства обороны США, отвечающее за управление спонсируемых оборонным ведомством исследований в Соединенных Штатах. demon. Си. Демон. DMA. См. Непосредственный доступ к памяти. errno. Глобальная переменная в программе С, содержащая код ошибки, который ука- указывает причину неудачи системы. Значение, которое должно быть помещено в errno, возвращается ядром в стандартном регистре возврата; оттуда в errno оно помещается библиотекой С времени выполнения. GID. См. Идентификатор группы. I/O. См. Ввод/вывод. ICMP. См. Протокол управляющих сообщений Интернета. init. Первая программа пользователя (/sbin/init), которая запускается при загрузке системы. inode. Структура данных, используемая файловой системой для описания файла. Содержание индекса включает тип и размер файла, UID владельца файла, GID катало- каталога, в котором он был создан, и список дисковых блоков и фрагментов, составляющих файл. Обратите внимание, что у индексов нет имен; для связи имени с индексом используются элементы каталогов. iovec. Структура данных, используемая для определения запросов ввода/вывода поль- пользователя, сделанных ядру. Каждая структура содержит адрес буфера данных и число байтов данных, которые нужно прочесть или записать. Массивы таких структур пере- передаются в ядро в системных вызовах readv и writev. См. также Ввод/вывод разбрасывания- собирания (scatter-gather I/O).
Глоссарий 711 I PC. См. Межпроцессное взаимодействие. IPSec. Набор протоколов, реализующих безопасность на сетевом уровне в протоколах Интернета версий 4 и 6. kqueue. Структура данных ядра, связанная с дескриптором файла, описывающим набор событий, при появлении которых должен быть уведомлен процесс, ссылающий- ссылающийся на этот дескриптор. События включают как динамические переходы, такие, как по- поступление данных для чтения, так и статические переходы, такие, как переименование файла, связанного с дескриптором. LRU. См. Наиболее давний. MAC. См. Обязательный контроль доступа. mbuf. Структура данных, описывающая блок данных. Mbuf используются в средствах межпроцессной коммуникации для хранения сетевых пакетов, а также данных, являющихся внутренними для модулей сетевых протоколов. «Mbuf» является сокра- сокращением от «memory buffer» (буфер памяти). MMU. См. Блок управления памятью. MSL. См. Максимальное время жизни сегмента. newbus. Инфрасруктура драйверов устройств, используемая в FreeBSD для управле- управления устройствами системы. Newbus включает независимые от оборудования проце- процедуры и структуры данных для использования зависящими от оборудования уровнями и предоставляет каркас для динамического выделения структур данных для каждого устройства. См. также Автоконфигурирование. pagedaemon. В FreeBSD имя процесса ядра, отвечающего за сохранение частей адрес- адресного пространства процесса во вторичном хранилище для поддержки возможностей подкачки системы управления виртуальной памятью. См. также Планировщик под- подкачки (swapper). PID. См. Идентификатор процесса. ртар. См. Физическое отображение. POSIX. Группа стандартов для Р1003, переносимых стандартов операционных систем, установленных IEEE. Первым установленным стандартом был интерфейс ядра, 1003.1, который был утвержден в 1988 г. Последний стандарт POSIX был утвержден в 1999 г. С 1999 г. единственным изменением существующего стандарта POSIX было поддержание его текущего состояния. РРР. См. Протокол точка-точка. РТЕ. См. Элемент таблицы страниц. RPC. См. Удаленный вызов процедур. SA. См. Соединение безопасности. SMP. См. Симметричная многопроцессорная обработка. SMT. См. Симметричная многопоточность.
712 Глоссарий SPI. См. Индекс параметра безопасности (security-parameter index). swapper. В FreeBSD имя процесса ядра, реализующего функцию подкачки средств управления памятью. Исторически swapper является процессом 0. См. также pagedae- топ. ТСВ. См. Блок управления потоком. TCP. См. Протокол управления передачей. TLB. См. Буфер быстрого преобразования адреса. UDP. См. Протокол дейтаграмм пользователя. UID. См. Идентификатор пользователя. uio. Структура данных, используемая системой для описания операции ввода/вывода. Эта структура содержит массив структур iovec; смещение в файле, с которого должна начаться операция; сумму длин векторов ввода/вывода; флаг, обозначающий, какая это операция - чтения или записи; а также флаг, указывающий, находятся ли источник и место назначения в адресном пространстве ядра или источник и место назначения разделены между адресными пространствами ядра и пользователя. См. также iovec. vnode. Открытый объектно-ориентированный интерфейс, содержащий общую информацию о файле. Каждый активный файл в системе представлен vnode, плюс спе- специфичная для файловой системы информация связана с vnode посредством файловой системы, содержащей файл. Ядро поддерживает одну общесистемную таблицу vnode, которая постоянно находится в основной памяти. Неактивные элементы таблицы ис- используются повторно на основе дольше всего не использовавшегося элемента. wait. Системный вызов, ожидающий завершения порожденного процесса. Абсолютный путь. См.Иуть. Аварийный дамп (crash dump). Запись состояния машины на время возникновения аварийного отказа. Эта запись обычно сохраняется во вторичном хранилище в месте, которое считается безопасным для хранения информации до тех пор, пока она не по- потребуется. Аварийный отказ (crash). Среди компьютерных специалистов означает неожидан- неожиданный отказ системы. Автоконфигурирование. Зондирование и идентификация оборудования, подключен- подключенного к системе. Успешно идентифицированное оборудование присоединяется к под- подсистеме ввода/вывода. Автоконфигурирование осуществляется при подключении к системе нового оборудования. В сетевом протоколе это процесс, посредством которого система выявляет важные сведения о себе и сети (такие, как сетевой адрес и маршрутизатор по умолчанию) без вмешательства пользователя. Автономная программа. Программа, которая может работать без поддержки опера- операционной системы. Автономный драйвер устройства. Драйвер устройства, который используется в авто- автономной программе. Автономный драйвер устройства обычно отличается от драйвера
Глоссарий 713 устройства, используемого операционной системой, тем, что у него нет служб преры- прерываний, управления памятью или полной поддержки отображения виртуальной памяти. Например, в библиотеке автономного ввода/вывода FreeBSD автономные драйверы устройств опрашивают устройство для определения завершения операции, они от- отвечают за установку своего собственного отображения виртуальной памяти при осу- осуществлении перемещений между устройством и основной памятью. Адрес памяти. Число, определяющее расположение в памяти. Адреса памяти часто подразделяются на физические и виртуальные в соответствии с тем, ссылаются ли они на физическую или виртуальную память. Алгоритм глобального замещения страниц. Алгоритм, который осуществляет заме- замещение страниц на общесистемной основе. Стратегия глобального замещения страниц обладает тенденцией наиболее эффективно использовать системную память. Однако один процесс может одолеть всю систему, пытаясь использовать всю доступную па- память. Алгоритм замещения локальных страниц. Алгоритм замещения страниц, который сначала выбирает процесс, в котором нужно заместить страницу, а затем выбирает страницу в этом процессе на основе критериев этого процесса. Обычно процессу вы- выделяется фиксированное число страниц, и он затем должен выбирать среди своих соб- собственных страниц, когда ему нужна новая страница. Алгоритм сортировки элеватора. Алгоритм, используемый драйверами устройств для запросов ввода/вывода для перемещения главных дисков. Алгоритм сортирует за- запросы в циклическом восходящем порядке, основанном на номере блока запроса. Имя выводится из того факта, что алгоритм размещает запросы диска способом, сходным со способом запроса путей для элеватора для наиболее эффективной работы. Анонимный объект. Анонимный объект представляет область для временного хране- хранения данных. Страницы анонимного объекта при первом обращении заполнены нуля- нулями, а измененные страницы будут сохранены в области подкачки при недостатке памя- памяти. Объект уничтожается, когда на него не остается больше ссылок. Асимметричная криптография. Криптографическая система, которая не использует для расшифровки данных тот же самый ключ, который был использован для шифрова- шифрования данных; иногда называется криптографией с открытым ключом. См. также Сим- Симметричная криптография. Асинхронное системное прерывание (asynchronous system trap - AST). Иницииро- Инициированный программным обеспечением вызов процедуры обслуживания прерывания. AST дает возможность асинхронно уведомлять процесс о появлении определенного события. В FreeBSD AST используются для инициирования перепланирования испол- исполнения потоков. Ассоциация безопасности (security association - SA). Основной канал безопасной коммуникации в IPSec. Данные защищаются ассоциацией безопасности лишь в одном направлении. Это означает, что для создания между двумя хостами полностью защи-
714 Глоссарий щенного канала требуются две ассоциации безопасности. См. также Индекс параметра безопасности (security-parameter index). Ассоциация. В средствах межпроцессной коммуникации логическая привязка (binding) между двумя конечными точками, которая должна быть установлена до того, как будет иметь место взаимодействие. Ассоциации могут быть долговременными, как в случае взаимодействия на основе потоков, или кратковременными, как в парадигме взаимо- взаимодействия на основе дейтаграмм. Атака отказа в обслуживании. Любая попытка перегрузить систему таким образом, чтобы она не смогла обслуживать настоящих пользователей системы. Например, отправка системе такого большого количества пакетов, что ее буферы переполняются и она становится неспособной обрабатывать сетевой трафик. Байт Единица измерения объема данных. Байт почти всегда равен 8 битам. См. также Октет. Библиотека автономного ввода/вывода. Библиотека программного обеспечения, которая используется при написании автономных программ. Эта библиотека включает автономные драйверы, которые используются для осуществления ввода/вывода. Блок В файловой системе единица распределения. Файловая система выделяет память в единицах размеров блоков или в фрагментах единиц размеров блоков. Блок управления памятью (memory-management unit- MMU). Аппаратное устрой- устройство, реализующее задачи по управлению памятью, такие, как преобразование адресов и защита памяти. Большинство современных блоков управления памятью предостав- предоставляют также поддержку для управления виртуальной памятью с подкачкой по требова- требованию. См. также Преобразование адресов. Блок управления потоком (thread control block- TCB). Структура данных, исполь- используемая для хранения контекста потока. Определяемый аппаратным обеспечением ТСВ содержит аппаратную часть этого контекста. Программный ТСВ содержит програм- программную часть и располагается в памяти непосредственно за аппаратным ТСВ. Буфер быстрого преобразования адреса (translation lookaside buffer - TLB). Кеш процессора, содержащий преобразования для недавно использованных виртуальных адресов. Буферированный. Как в «буферированном вводе/выводе»; методика, посредством которой данные сохраняются, или буферируются, для уменьшения числа выполняемых операций ввода/вывода. Например, стандартная библиотека ввода/вывода буферирует вывод в файлы, аккумулируя выводимые на запись данные до тех пор, пока для записи не будет накоплен полный блок файловой системы, или до тех пор, пока приложение на затребует очистки буфера. Буферный кеш. Кеш недавно использованных дисковых блоков. В FreeBSD буферный кеш совмещен с кешем виртуальной памяти.
Глоссарий 715 Быстрое повторное использование соединения. Новое соединение, которое полно- полностью дублирует адреса и порты недавно закрытого сокета потока, который находится в состоянии TIME_WAIT. Ввод/вывод (input/output - I/O). Передача данных между компьютером и его пери- периферийными устройствами. Ввод/вывод разбрасывания-собирания (scatter-gather I/O). Разбросанный ввод позволяет размещать результаты одной операции чтения в нескольких различных буферах. Разбросанный вывод позволяет записывать в несколько буферов в одной неделимой операции записи. Ввод/вывод разбрасывания-собирания использует структуру iovec, массив буферов и длин, для идентификации используемого для ввода/вывода буфера. См. также iovec. Ввод/вывод, управляемый сигналами. Режим, в который может быть помещен дескриптор, при котором система доставляет процессу сигнал SIGIO каждый раз, когда для дескриптора доступен ввод/вывод. См. также Неблокирующий ввод/вывод; Опрос ввода/вывода. Ведомое устройство (slave device). Аппаратное устройство, которое управляется веду- ведущим устройством. Например, дисковый привод является ведомым устройством для контроллера SCSI. Различие между ведущим и ведомым устройствами используется системой автоконфигурирования. Предполагается, что ведомое устройство должно быть доступно, лишь когда присутствует соответствующее ведущее устройство. Ведущее устройство (master device). См. Ведомое устройство. Вектор ввода/вывода. См. iovec. Верхняя граница (high watermark). Верхняя граница количества данных, которые могут быть буферированы. В средствах межпроцессного взаимодействия буфер данных каждого сокета имеет высшую отметку, указывающую максимальный объем данных, которые могут быть поставлены в очередь в буфере данных до того, как запрос отправки данных заблокирует процесс (или возвратит ошибку, если используется не- неблокирующий ввод/вывод). См. также Низшая отметка (low watermark). Верхняя половина (top half). В отношении работы системы процедуры ядра, вызы- вызываемые синхронно в результате системного вызова или прерывания. Эти процедуры зависят от состояния процесса и могут блокироваться посредством вызова sleepQ. См. также Нижняя половина (bottom half). Виртуальная машина. Машина, архитектура которой эмулируется программными средствами. Виртуальная память. Возможность, посредством которой эффективный диапазон адресуемой памяти, предоставляемой процессу, не зависит от размера основной памяти; т.е. виртуальное адресное пространство процесса не зависит от физического адресного пространства процессора. Виртуальное адресное пространство. Непрерывный диапазон виртуальной памяти.
716 Глоссарий Виртуальный адрес. Адрес, ссылающийся на местоположение в виртуальном адрес- адресном пространстве. Владение (lease). Разрешение деятельности, которое действительно до истечения установленного времени. В протоколе NQNFS клиент получает владение от своего сервера на чтение, запись или на чтение и запись файла. Пока у клиента есть действи- действительное владение, сервер будет уведомлять его об изменениях состояния файла. После истечения срока владения клиент должен перед использованием любых кешированных данных файла связаться с сервером, чтобы запросить новое владение. См. также Обратный вызов; Уведомление об очистке (eviction notice). Владение кешем записи (write-caching lease). Владение кешем записи выдается сервером NFS клиенту, чтобы разрешить клиенту кешировать чтение и запись на время действия владения. См. также Некеширующее владение; Владение кешем чтения. Владение кешем чтения (read-caching lease). Владение кешем чтения выдается клиенту сервером NFS, чтобы дать клиенту возможность кешировать данные, но запрещает измене- изменение этих данных. См. также Некеширующее владение; Владение кешем записи. Внеполосные данные (out-of-band data). Данные, передаваемые и получаемые вне нормального потока данных. Сокеты потоков поддерживают логически отдельный канал внеполосных данных, через который может быть передано по крайней мере одно сообщение с по крайней мере одним октетом данных. Система немедленно уведомляет получающий процесс о наличии внеполосных данных, и они могут быть получены из потока помимо обычного порядка, при помощи которого получаются обычные данные. Восстановление (reclaim). См. Восстановление страницы. Восстановление из запаса. Восстановление страницы из запасного списка. Страница может быть восстановлена из запасного списка, если эта страница освобождена алгоритмом замещения страниц, но эта страница не назначается заново до тех пор, пока процессом не будет вызван отказ для нее. Восстановление страницы (page reclaim). Отказ страницы, при котором страница, вызвавшая отказ, находится в памяти, обычно в списке неактивных или кеша. Вторичное хранилище. Хранилище, в котором содержатся данные, которые не поме- помещаются в основной памяти. Вторичное хранилище обычно размещается на вра- вращающемся магнитном носителе, таком, как диск. См. также Резервное хранилище. Входящий (inbound). Направление движения сетевого пакета, когда он достигает сис- системы, для которой предназначен. Входящий сетевой пакет доставляется приложению в системе или вызывает ошибку, если не найдено соответствующего приложения. См. также Перенаправление (forward). Гибкая ссылка (soft link). См.Символическая ссылка. Группа оставленных процессов (orphaned process group). Группа процессов, в которой родитель каждого из членов группы либо сам является членом группы, либо не является
Глоссарий 717 участником сеанса группы. Таким родителем обычно является управляющая заданиями оболочка, способная возобновлять остановленные порожденные процессы. Группа процессовю Совокупность процессов на одной машине, у которых один и тот же идентификатор группы процессов. Ядро использует такую группировку, чтобы выносить решения по поводу множества заданий, претендующих на один и тот же терминал. Группа процессора. Набор сердцевин процессоров в поддерживающем симметричную многопоточность процессоре или набор процессоров в системе SMP, который рассматрива- рассматривается планировщиком как один блок. Группа цилиндра. В Fast Filesystem коллекция блоков на диске, которые группируются вместе для использования информации локализации, т.е. файловая система выделяет индексы и блоки данных на основе группы цилиндра. Группа цилиндра является историче- историческим названием с тех пор, когда была известна геометрия дисков. Грязный. В компьютерных системах означает измененный. Система обычно отслежи- отслеживает, был ли объект модифицирован - «грязный», поскольку ей нужно сохранить содержимое объекта перед повторным использованием памяти, занимаемой объектом. Например, в системе с виртуальной памятью страница в кеше виртуальной памяти является грязной, если ее содержимое было изменено. Грязные страницы должны быть записаны в область подкачки или файловую систему до того, как они могут быть повторно использованы. Двойной косвенный блок (double indirect block). См. Косвенный блок (indirect block). Дежурный пакет (keepalive packet). Тип пакета, используемого TCP для хранения ин- информации о том, работает ли хост назначения. Дежурные пакеты посылаются удален- удаленному хосту, который, если он действует, должен ответить. Если в течение приемлемого времени ответ на любой из дежурных пакетов не получен, соединение завершается. Дежурные пакеты используются лишь в тех TCP-соединениях, которые были созданы для сокетов с установленной опцией SO_KEEPALIVE. Дежурный таймер (keepalive timer). Таймер, используемый протоколом TCP при использовании дежурных пакетов. Таймер запускается, когда передается дежурный пакет. Если ответ на пакет не получен до того, как таймер сработает несколько раз, соеди- соединение разрывается. Действительный GID. См. Действительный идентификатор группы. Действительный UID. См. Действительный идентификатор пользователя. Действительный идентификатор группы (действительный GID) (real group identifier). GID, который записывается в учетную запись при завершении процесса. Действительный GID процесса вначале устанавливается во время регистрации пользо- пользователя в системе, а затем наследуется порожденными процессами в результате после- последующих системных вызовов fork и exec (независимо от того, является ли программа ус- устанавливающей идентификатор группы). См. также Мандат; Эффективный иденти- идентификатор группы; Сохраненный идентификатор группы; Программа установки иден- идентификатора группы.
718 Глоссарий Действительный идентификатор пользователя (действительный UID) (real user identifier). По отношению к процессу настоящая личность пользователя, который запустил процесс. Действительный UID для процесса вначале устанавливается при регистрации пользователя в системе, а затем наследуется порожденными процессами в результате системных вызовов foгк и exec (независимо от того, является ли программа устанавливающей идентификатор пользователя). Действительный UID записывается в учетной записи при завершении процесса. См. также Мандат; Эффективный иден- идентификатор пользователя; Сохраненный идентификатор пользователя; Программа установки идентификатора пользователя. Демон (daemon). Долгоживущий процесс, предоставляющий системные службы. Существуют процессы демонов, выполняющиеся в режиме ядра (например, pagedaemon), и процессы демонов, выполняющиеся в режиме пользователя (например, демон маршру- маршрутизации). Старый английский термин daemon означает «обожествляемое существо», в отличие от термина demon, означающего «злой дух». Демон маршрутизации. Процесс в FreeBSD, предоставляющий системе службу управления маршрутизацией. Эта служба использует протокол, который реализует распределенную базу данных информации маршрутизации, которая динамически обновляется для отражения изменений в топологии соединений. Дескриптор. Целое число, назначаемое системой при ссылке на файл посредством системного вызова open или при создании сокета посредством системных вызовов socket, pipe или socketpair. Целое число уникально идентифицирует путь доступа к файлу или сокету из данного процесса или любого из потомков данного процесса. Дескрипторы можно также дублировать с помощью системных вызовов dup wfcntl. Деятельность системы (system activity). Элемент в ядре. Деятельности системы можно подразделить в соответствии с событием или действием, которые их вызывают: систем- системные вызовы, аппаратные прерывания и программные исключения и прерывания. Дисциплина линий связи (line discipline). Обрабатывающий модуль в ядре, предо- предоставляющий семантику для асинхронного последовательного интерфейса или для про- программной эмуляции такого интерфейса. Дисциплины линий связи описываются про- процедурным интерфейсом, точки входа которых хранятся в структуре данных linesw. Добавочный заголовок (extension header). Заголовок сообщения, который легко может добавляться в пакет и удаляться из пакета, поскольку он содержит свою длину и некоторое указание на то, где и как начать обработку следующего заголовка, если таковой существует. Долговременный алгоритм распределения. См. Кратковременный алгоритм рас- распределения (short-term-scheduling algorithm). Домашний каталог (home directory). Текущий рабочий каталог, который устанавли- устанавливается для оболочки пользователя, когда последний регистрируется в системе. Этот каталог обычно является индивидуальным для пользователя. Домашний каталог для
Глоссарий 719 пользователя указывается в соответствующем поле элемента пользователя в файле паролей. Домен IPv4. Версия 4 протоколов Интернета. IPv4 обычно назывался протоколом Интернета, пока не была разработана версия 6. См. также Домен IPv6. Домен IPv6. Версия 6 протоколов Интернета. Новейшая версия протоколов Интернета с поддержкой больших адресов, безопасности и автоконфигурацией. См. также Домен IPv4. Дорожка. В компьютерных системах сектора диска, к которым можно получить доступ в одном из положений головки. Драйвер tty. Программный модуль, реализующий семантику, связанную с устройст- устройством терминала или псевдотерминала. См. также Дисциплина линии связи. Драйвер устройства. Модуль программного обеспечения, который является частью ядра и который поддерживает доступ к периферийному устройству. Дыра. В файле область, являющаяся частью файла, но не имеющая связанных с ней блоков данных. Файловая система возвращает нулевые данные, когда процесс произ- производит чтение из дыры в файле. Дыра в файле создается, когда процесс позиционирует файловый указатель за текущим концом файла, записывает некоторые данные, а затем закрывает файл. Дыра возникает между предыдущим концом файла и началом вновь записанных данных. Жесткий лимит (hard limit). Предел, который нельзя превышать. См. также Мягкий лимит (soft limit). Зависимость обновления. Необходимая последовательность связанных обновлений для отделения структур метаданных, чтобы обеспечить восстанавливаемость в усло- условиях непредсказуемых сбоев. Обновление, которое должно быть сделано позже, имеет зависимость обновления от более раннего обновления, поскольку оно не может быть сделано до тех пор, пока более раннее обновление не будет зафиксировано в стабиль- стабильном хранилище. См. также Метаданные. Загружаемые модули ядра. Совокупность программного обеспечения, реализующая службу ядра, но не скомпонованная статически с образом ядра. Загружаемые модули ядра приводятся в систему динамически, возможно, во время выполнения, через дей- действия, инициированные либо системой, либо пользователем. См. также Постоянные модули ядра. Загрузка страницы (pagein). Операция, осуществляемая системой виртуальной памя- памяти, при которой содержимое страницы считывается из вторичного хранилища. Задание (job). В UNIX набор процессов, имеющих один и тот же идентификатор группы процесса. Задания, содержащие несколько процессов, обычно создаются с помо- помощью конвейера. Задание является фундаментальным объектом, которым манипулируют посредством управления заданиями.
720 Глоссарий Запрос маршрутизатора (router solicitation). Сообщение, отправленное хостом в попытке выявить, какая машина является маршрутизатором, без вмешательства человека. См. также Автоконфигурирование; Определение соседа. Запрос опознавания (sense request). Запрос, переданный модулю коммуникационного протокола в результате осуществления процессом системного вызова stat для сокета. Запрос прослушивания. Запрос, отправленный модулю коммуникационного прото- протокола в результате совершения процессом системного вызова listen для сокета. Этот запрос означает, что система должна ожидать запросов на установление соединения с сокетом. В противном случае система будет отвергать любые запросы соединения, которые будут получены для сокета. Запрос соединения. Запрос, переданный пользовательской процедуре запроса модуля коммуникационного протокола в результате осуществления процессом системного вызова connect для сокета. Запрос заставляет систему попытаться установить ассоциа- ассоциацию между локальным и удаленным сокетами. Запрос управления. Запрос, переданный модулю коммуникационного протокола в резуль- результате осуществления процессом для сокета системного вызова ioctl или setsockopt. Зарезервированная страница (wired page). Память, которая не подлежит замещению демоном подкачки страниц. Диапазону виртуальных адресов без страничной подкачки физические адреса назначаются при выделении адресов. Жесткие страницы никогда не должны вызывать отказ страницы, который мог бы вызвать блокирование операции. Жесткие страницы обычно используются в адресном пространстве ядра. Значение проверки целостности. Значение, вычисляемое отправителем для диапазона данных, которое используется получателем для проверки того, что переданные по сети данные не были повреждены. См. также Контрольная сумма. Зондирование (probe). Операция по проверке наличия в системе аппаратного устрой- устройства. Более новые конструкции шин имеют стандартизированный способ определения присоединенных к ним устройств. Зондирование окна. В TCP сообщение, передаваемое в случае, когда данные помещены в очередь передачи, окно отправки слишком маленькое, чтобы начать передачу, и в течение длительного времени не было получено сообщения, содержащего обновление для окна отправки. Сообщение зондирования окна содержит один октет данных. Идемпотентный. Операция, которая может быть повторена несколько раз без измене- изменения конечного результата или появления ошибки. Например, запись одних и тех же данных по одному и тому же смещению в файле идемпотентна, поскольку это даст тот же самый результат независимо от того, один или много раз будет проделано. Однако попытка удаления одного и того же файла несколько раз неидемпотентна, поскольку после первой попытки файл больше не будет существовать. Идентификатор группы (group identifier -GID). Целое значение, уникально иденти- идентифицирующее коллекцию пользователей. GID используются в возможности управления доступом, предоставляемой файловой системой. См. также Мандат; Эффективный
Глоссарий 721 идентификатор группы; Действительный идентификатор группы; Сохраненный идентификатор группы; Программа установки идентификатора группы. Идентификатор группы процессов. Положительное целое число, используемое для уникальной идентификации каждой активной группы процессов в системе. Идентифи- Идентификаторы группы процессов обычно определяются как PID лидера группы процессов. Идентификаторы группы процессов используются командными интерпретаторами в реализации управления заданиями, когда командный интерпретатор рассылает широковещательные сигналы с помощью системного вызова killpg и когда он изменяет приоритеты всех процессов в группе с помощью системного вызова setpriority. Идентификатор пользователя (user identifier - UID). Неотрицательное целое число, уникально идентифицирующее пользователя. UID используются в средствах контроля доступа, предоставляемых файловой системой. См. также Мандат; Эффективный идентификатор пользователя; Действительный идентификатор пользователя; Сохраненный идентификатор пользователя; Программа с устанавливаемым иденти- идентификатором пользователя. Идентификатор процесса (process identifier - PID). Неотрицательное целое, исполь- используемое для идентификации каждого активного процесса в системе. Имя пути. Завершающаяся нулем строка символов с необязательным слешем (/), за которым следуют ноль или более имен каталогов, разделяемых слешами, за которыми следует необязательное имя файла. Если имя пути начинается со слеша, говорят, что это абсолютное имя пути, а поиск пути начинается с корневого каталога. В противном случае говорят, что это относительное имя пути, а поиск пути начинается с текущего рабочего каталога процесса. Слеш сам по себе обозначает корневой каталог. Нулевое имя пути ссылается на текущий рабочий каталог. Имя файла. Строка ASCII-символов, называющая обычный файл, специальный файл или каталог. Символы в имени файла не могут включать null @) или ASCII код для слеша (/). Инвертированная таблица страниц (таблица страниц обратного отображения) (inverted page table, reverse-mapped page table). Аппаратно поддерживаемая постоян- постоянно находящаяся в памяти таблица, содержащая по одному элементу для каждой физической страницы и индексируемая по физическому адресу вместо виртуального адреса. Элемент содержит виртуальный адрес, на который в настоящий момент ото- отображена физическая страница; элемент включает также атрибуты состояния и защиты. Аппаратное обеспечение осуществляет преобразование виртуальных адресов в фи- физические путем вычисления хеш-функции виртуального адреса, чтобы выбрать эле- элемент в таблице. Аппаратное обеспечение разрешает коллизии, связывая вместе эле- элементы таблицы и осуществляя линейный поиск в получившейся цепи до тех пор, пока не будет найден подходящий виртуальный адрес. См. также Таблица страниц прямого отображения (forward-mappedpage table).
722 Глоссарий Индекс параметра безопасности (security-parameter index- SPI). 32-разрядный эле- элемент данных, используемый для идентификации конца ассоциации безопасности хоста с использованием IPSec. Индекс параметра безопасности используется в качестве ключа при работе с ассоциациями безопасности в базах данных безопасности системы. См. также Ассоциация безопасности. Индивидуальное отображение. При индивидуальном отображении файла на вирту- виртуальную память изменения, сделанные в отображенном в память файле, не записывают- записываются в файл и не видны другим процессам, отображающим этот файл. См. также Общее отображение (shared mapping). Инкапсуляция. В сетевом обмене процедура, посредством которой создается сообще- сообщение, которое содержит в себе заключенное в виде данных существующее сообщение. Протокол обычно инкапсулирует сообщение, добавляя ведущий заголовок протокола, указывающий, что оригинальное сообщение должно рассматриваться как данные. См. также Разборка (decapsulation). Интерактивная программа. Программа, которая должна для осуществления своей работы периодически получать ввод от пользователя. Экранный текстовый редактор является примером интерактивной программы. Интерпретатор. Программа, анализирующая и выполняющая описательный язык по- пошагово, вместо использования более обычного двухэтапного процесса компилирова- компилирования с языка и выполнения полученного двоичного файла. Оболочка является при- примером интерпретатора; она анализирует и выполняет сценарий оболочки без предвари- предварительного его компилирования. Интерфейс непосредственного доступа (raw-device interface). Интерфейс символь- символьного устройства для блочных устройств, таких, как диски. Этот интерфейс предостав- предоставляет прямой доступ к нижележащему устройству, устраивая непосредственный ввод/ вывод между процессом и устройством. Интерфейс символьного устройства. Соглашения, установленные для доступа к устройствам в ядре. Эти соглашения включают набор процедур, которые можно вы- вызвать для осуществления операций ввода/вывода, и параметры, которые должны быть переданы в каждом вызове. Исключение точки останова (breakpoint fault). Аппаратное прерывание, которое генерируется при выполнении процессом инструкции точки останова. Исключение трассировки (trace trap). Исключение, используемое системой для реа- реализации пошагового прохождения в отладчиках программ. В архитектурах, поддержи- поддерживающих бит трассировки, ядро устанавливает определяемый аппаратно бит трассиров- трассировки в контексте отлаживаемого потока и помещает поток в очередь запуска. Когда поток запускается в следующий раз, бит трассировки вызывает генерацию исключения после выполнения потоком одной инструкции. Это исключение перехватывается ядром, которое останавливает поток и возвращает управление в отлаживающий процесс.
Глоссарий 723 Канал (pipe). Средство межпроцессного взаимодействия, которое поддерживает одно- однонаправленный поток данных между связанными процессами. Передача данных ориен- ориентирована на потоки, надежна, и поток контролируется. Канал обозначается для обо- оболочки символом «|». Например, чтобы соединить стандартный вывод программы а со стандартным вводом программы Ь, пользователю нужно набрать команду «а | Ь». Канал ожидания. Значение, используемое для идентификации события, ожидаемого потоком. В большинстве ситуаций канал ожидания определяется как адрес структуры данных, связанной с событием, которого ожидает поток. Например, если поток ожида- ожидает завершения чтения с диска, канал ожидания определяется как адрес буферной структуры данных, передаваемой системе дискового ввода/вывода. Канальный уровень (link layer). Уровень 2 в Эталонной модели взаимодействия от- открытых систем ISO. В данной модели канальный уровень, или уровень передачи дан- данных, отвечает за (возможно, ненадежную) доставку сообщений в пределах единой физической сети. Канальный уровень наиболее близко соответствует уровню сетевого интерфейса (network-interface layer) сетевой подсистемы FreeBSD. См. также Уровень сетевого интерфейса. Канонический режим. Режим терминала. Символьный ввод от терминала или псевдо- псевдотерминала, действующего в каноническом режиме, обрабатывается таким образом, чтобы предоставить стандартные функции строчного редактирования, а ввод пред- представляется процессу на основе отдельных строк. Когда терминал работает в некано- неканоническом режиме, ввод передается читающему процессу немедленно и без интерпре- интерпретации. Канонический режим известен также как режим с обработкой, а неканониче- неканонический — как непосредственный реэ/сим. Карусель (round robin). В организации очередей алгоритм, посредством которого каждый инициатор запроса обслуживается в течение фиксированного интервала вре- времени на основе порядка «первым вошел, первым обслужен»; запросы помещаются в конец очереди, если после обслуживания они не завершены. Каталог (directory). В UNIX специальный тип файла, который содержит элементы, являющиеся ссылками на другие файлы. По соглашению каталог содержит по крайней мере два элемента: точка (.) и точка-точка (..). Точка ссылается на сам каталог, точка- точка ссылается на родительский каталог. Каталог страниц (directory table). Верхняя из двухуровневой иерархии структур данных, используемых алгоритмом страничного преобразования для описания виртуального адрес- адресного пространства процесса. Каждый элемент в каталоге страниц указывает на страницу, содержащую таблицу страниц. Двухуровневая иерархия преобразования используется в архитектурах PC. См. также Таблица страниц (foi*ward-mapped page table); Элемент таблицы страниц (page-table entry); Страницы таблицы страниц (page-table pages).
724 Глоссарий Квант времени (time quantum, time slice). В окружении с разделением времени пери- период, который планировщик процессов дает процессу для выполнения до вытеснения этого процесса, чтобы дать возможность исполнения другого процесса. Класс планирования. Ядро FreeBSD имеет пять классов планирования: прерывания ядра, системные вызовы, реального времени, разделения времени и холостой. Каждый класс помещается в класс планирования. В пределах каждого класса потоки процесса организуется в соответствии со своим приоритетом планирования. См. также Приори- Приоритет планирования; Приоритет процесса. Кластер. Логическая группировка смежных страниц виртуальной памяти или файла. В FreeBSD эта группировка используется ядром для объединения страниц при записи на диск или чтении с диска для уменьшения числа операций ввода/вывода, необходи- необходимых для перемещения данных. Клиентский процесс. В клиент-серверной модели взаимодействия процесс, который соединяется с процессом сервера для запроса службы. Клиентский процесс обычно не имеет отношения к процессу сервера; единственная связь клиента с сервером осущест- осуществляется через коммуникационный канал. См. также Серверный процесс. Ключ. В ядре элемент данных, уникально идентифицирующий некоторый системный ресурс. При использовании системой межпроцессного взаимодействия идентифициру- идентифицирует конечную точку соединения, такую, как очередь сообщений или общее средство, на- наподобие разделяемой области памяти. Код запуска сигнала (signal-trampoline code). Фрагмент кода, вызывающий обра- обработчик сигнала. Код запуска сигнала содержит инструкции, устанавливающие пара- параметры для вызова обработчика сигнала, осуществления самого вызова обработчика сигнала и по возвращении из него осуществления системного вызова sigreturn для вос- восстановления состояния ядра и возобновления выполнения процесса после обработки сигнала. Коммуникационный домен. Абстракция, используемая средствами межпроцессного взаимодействия для организации свойств коммуникационной сети или аналогичного устройства. Коммуникационный домен включает набор протоколов, обозначаемых термином семейство протоколов (protocol family)', правила для интерпретирования имен и манипулирования ими; семейство адресов (address family), а также другие воз- возможные свойства. Возможности, предоставленные системой для межпроцессного взаимодействия, определены таким образом, что они не зависят от коммуникационных доменов, поддерживаемых системой. Такой дизайн дает приложениям возможность быть написанными независимым от коммуникационных доменов способом. Коммуникационный протокол. Набор соглашений и правил, используемых двумя взаимодействующими процессами. Коммуникационные протоколы чаще всего ассо- ассоциируются с работой в сети. Конвейер (pipeline). Совокупность процессов, в которых стандартный вывод одного процесса подключен через канал к стандартному вводу следующего.
Глоссарий 725 Контекст процесса. Контекст процесса FreeBSD состоит из состояния уровня пользо- пользователя, включая содержимое его адресного пространства и окружения времени выпол- выполнения, и состояния уровня ядра, которое включает параметры распределения процес- процессорного времени, управление ресурсами и идентификационную информацию. Контекст процесса включает все, что используется ядром для обслуживания процесса. Контрольная сумма. Значение математической функции, вычисленной для блока дан- данных; используется для определения целостности блока данных. Конфигурационный файл. Файл, который содержит параметры для программы кон- конфигурирования системы /u sr/s bin/con fig. Этот файл описывает драйверы устройств, которые должны быть сконфигурированы в ядре, и другие основные возможности ядра, такие, как поддержка симметричной многопроцессорной работы. Копирование при записи (copy-on-write). Методика, посредством которой на общий объект поддерживаются несколько ссылок до тех пор, пока объект не будет модифи- модифицирован (записан). Прежде чем объект будет записан, делается копия; изменяется копия, а не оригинал. В управлении виртуальной памятью копирование при записи является обычной схемой, которую ядро использует для управления общими для нескольких процессов страницами. Все элементы таблицы страниц, отображающие общую страницу, устанавливаются таким образом, что первая попытка записи на стра- страницу вызывает отказ страницы. Когда отказ страницы обслуживается, соответст- соответствующая страница заменяется частной копией, в которую можно осуществить запись. Корневая файловая система (root filesystem). Файловая система, содержащая корне- корневой каталог, который интерпретируется как корневой для всех файловых систем на ма- машине. Идентичность корневой файловой системы по умолчанию встроена в ядро, хотя во время загрузки системы в качестве действительной корневой файловой системы, ис- используемой системой, может быть установлена какая-нибудь другая файловая система. Корневой каталог (root directory). Каталог, который используется ядром для разре- разрешения абсолютных имен путей. У каждого процесса есть корневой каталог, который может быть установлен с помощью системного вызова chroot, а у системы есть уни- уникальный корневой каталог, идентичность которого устанавливается во время загрузки системы. Косвенный блок. В файловой системе вспомогательный блок данных, в котором хра- хранится число блоков данных. Первые 12 блоков файла указываются непосредственно в индексе. Дополнительные блоки данных описываются с помощью указателя индекса на косвенный блок данных; система должна сначала добыть косвенный блок, в котором хранится число блоков данных. В FreeBSD ядру для обнаружения нужного блока данных может потребоваться получить до трех косвенных блоков. Косвенный блок, в котором содержатся номера блоков данных, называется одноуровневым косвенным блоком (single-level indirect block); косвенный блок, в котором содержатся номера блоков одноуровневых косвенных блоков, называется двухуровневым косвенным блоком (double-level indirect block); косвенный блок, в котором содержатся номера
726 Глоссарий блоков двухуровневых косвенных блоков, называется трехуровневым косвенным блоком (triple-level indirect block). Красная область. Область памяти только для чтения непосредственно за последней страницей стека режима ядра времени выполнения для потока. Красная область уста- устанавливается системой таким образом, чтобы возникло исключение, если поток выйдет за пределы памяти, отведенной для стека ядра. Кратковременный алгоритм распределения. Алгоритм, используемый системой для выбора следующего процесса для запуска среди набора процессов, которые счи- считаются работоспособными. Долговременный алгоритм распределения, с другой сторо- стороны, может повлиять на набор работоспособных процессов путем их подкачки в/из главной памяти (и, таким образом, в/из набора работоспособных процессов). Куча (heap). Область процесса, которая может динамически расширяться с помощью системного вызова sbrk (или вызова библиотеки С mallocQ). Название происходит от беспорядочной манеры размещения данных в этой области. Кеш дорожки. Когда ядро считывает с диска, память, связанная с диском, в которой хранятся данные, проходящие под головкой диска независимо от того, были ли они за- запрошены явным образом. Когда ядро записывает на диск, память, связанная с диском, в которой данные сохраняются до тех пор, пока головка диска не достигнет нужного положения для их записи. Лидер группы процессов. Процесс в группе процессов, PID которого используется в качестве идентификатора группы процессов. Это обычно первый процесс в конвейере. Лидер сеанса. Процесс, создавший сеанс. Лидер сеанса - это управляющий процесс для сеанса, ему разрешается выделять и назначать для сеанса управляющий терминал. Обычно сеанс создается для каждой оболочки регистрации. Все процессы, запущен- запущенные этой оболочкой регистрации, являются частью сеанса. Липкий бит (sticky bit). Бит в индексе, представляющий каталог, который указывает, что пользователь без привилегий не может удалять или переименовывать файлы других пользователей в этом каталоге. Липкий бит может быть установлен любым пользователем на каталог, которым пользователь владеет или для которого у него есть соответствующие права доступа. Исторически это бит в индексе, означающий, что сег- сегмент text программы должен быть общим и постоянно храниться в памяти или про- пространстве подкачки из-за ожидаемого использования в будущем. Этот бит больше не нужен для подобного использования, поскольку система виртуальной памяти отслежи- отслеживает недавно использованные исполняемые файлы. Логический блок. Блок, определенный путем деления линейного размера файла на размер блока соответствующей файловой системы. Каждый логический блок файла сопоставляется с физическим блоком. Этот дополнительный уровень отображения дает возможность помещать физические блоки на диск, не заботясь о линейном разме- размещении блоков в файле.
Глоссарий 727 Локальность ссылки. Феномен, посредством которого за короткое время обнаружи- обнаруживаются ссылки памяти работающей программы в виртуальном адресном пространстве. Большинство программ имеют тенденцию проявлять некоторую степень локальности ссылок. Такая локальность ссылок дает системе возможность осуществлять предвари- предварительную выборку страниц, смежных со страницей, для которой был получен отказ, что снижает частоту отказов в работающей программе. Локальный домен. Коммуникационный домен в средствах межпроцессного взаимо- взаимодействия, поддерживающий ориентированные на потоки и на дейтаграммы стили ком- коммуникации между процессами на одной и той же машине. Магическое число. Число, размещаемое в первых нескольких байтах исполняемого файла, указывающее на тип исполняемого файла. У многих хранящихся на диске структур данных есть магические числа, помогающие проверить их содержимое. Максимальное время жизни сегмента (maximum segment lifetime - MSL). Макси- Максимальное время, в течение которого сегмент может находиться в сети. См. также Таймер 2MSL. Мандат (credential). Структура, идентифицирующая пользователя. Она содержит дей- действительный, эффективный и сохраненный идентификаторы пользователя и группы. См. также Действительный идентификатор пользователя; Действительный иденти- идентификатор группы; Эффективный идентификатор пользователя; Эффективный иден- идентификатор группы; Сохраненный UID; Сохраненный GID. Маршалинг. Подготовка ряда параметров для переправки по сети. Маршалинг включает замену указателей данными, на которые они указывают, и преобразование двоичных данных в канонический сетевой порядок расположения байтов. См. также Удаленный вызов процедур. Маршрут. В сети с коммутацией пакетов маршрут до места назначения определяет хост или хосты, через которые будут передаваться данные для достижения места на- назначения. Маршрут подстановки (wildcard route). Маршрут, который используется в тех случаях, когда нет явного маршрута к месту назначения. Маршрутизатор (router). Машина, известная также как шлюз (gateway), имеющая два или более сетевых интерфейсов и переправляющая пакеты между сетями, к которым она подключена. Обычно на маршрутизаторе запускается процесс маршрутизации, который собирает сведения о топологии сети; он использует эту информацию для разработки набора маршрутов для следующего переключения, которые записывает в таблицу маршрутизации ядра. См. также Механизм маршрутизации; Политика маршрутизации. Маска сети (network mask). Значение, которое используется в схеме адресации маршрутизации между доменами без классов (Classless Inter-Domain Routing — CIDR) в Интернете. Маска сети определяет, какие биты должны считаться значимыми при извлечении сетевой составляющей адреса.
728 Глоссарий Маскированный сигнал. Сигнал, заблокированный в системном вызове sigprocmask. Когда сигнал маскируется, его доставка откладывается до тех пор, пока он не будет демаскирован. Вдобавок в FreeBSD система автоматически маскирует перехваченный сигнал, когда он обрабатывается. Машинная проверка. Исключительное состояние машины, указывающее, что CPU обнаружил ошибку при выполнении операции. Например, машинная проверка ге- генерируется, если в памяти кеша обнаружена ошибка четности. Межпроцессное взаимодействие (interprocess communication - IPC). Передача данных между процессами. Большинство средств для межпроцессного взаимодейст- взаимодействия спроектировано таким образом, что данные передаются между объектами, не являющимися процессами. Модель межпроцессного взаимодействия, не ориентиро- ориентированная непосредственно на процессы, имеет преимущества, поскольку появляется воз- возможность смоделировать сценарии, при которых конечные точки взаимодействия независимы от расположения и, возможно, динамически перемещаются. Например, в FreeBSD коммуникация происходит между сокетами, а не между процессами. Метаданные (metadata). В файловых системах метаданные (вспомогательные дан- данные) предоставляют указатели и описания для связывания нескольких секторов диска в файлы и идентификации этих файлов. Метаданными являются каталоги, индексы и карты свободных блоков, которые структурируют общий объем памяти. Механизм маршрутизации. Средства маршрутизации, включенные в ядро, которые реализуют внешне определенные политики. Механизм маршрутизации использует ме- механизм поиска, который предусматривает начальный маршрут (определенный сетевой интерфейс и непосредственное место назначения) для каждого направления. См. также Маршрутизатор; Политики маршрутизации. Младший номер устройства (minor device number). Целое число, уникально иденти- идентифицирующее элемент блока устройства. Младший номер устройства интерпретируется на основе отдельного устройства. Например, младший номер устройства для дискового привода указывает на элемент, обозначаемый как раздел, тогда как младший номер устройства для псевдотерминала определяет конкретную пару ведущий-ведомый. Многоуровневая очередь обратной связи. Схема создания очереди, при которой запросы группируются в несколько подочередей с различными приоритетами, причем запросы перемещаются между ними в зависимости от динамически изменяемого кри- критерия. Ядро FreeBSD использует схему многоуровневой очереди для распределения выполнения потоков. Моментальный снимок (snapshot). Моментальный снимок файловой системы - это замороженное состояние файловой системы в данный момент времени. Мягкое ограничение (soft limit). Ограничение, которое можно временно нарушить или нарушить определенное число раз. Мягкое ограничение обычно используется с жестким ограничением. См. также Жесткое ограничение.
Глоссарий 729 Наиболее давний (least-recently used - LRU). Политика повторного использования, посредством которой в первую очередь используются элементы, не использовавшиеся дольше всего. Например, в файловой системе есть фиксированное число vnode, дос- доступных для доступа к файлам. Vnode, содержащие действительные значения, выде- выделяются повторно в порядке дольше всего не использовавшихся (LRU), в надежде, что файл, на который ссылается vnode, может быть повторно использован последующим запросом на открывание. Нахождение MTU пути. Алгоритм и набор сообщений, используемые для обнаруже- обнаружения максимального размера пакета, который может быть передан по сети между двумя конечными точками. Начальная загрузка (first-level bootstrap). Начальный код, исполняемый при опера- операции многоуровневой загрузки. Обычно начальная загрузка ограничена в размере и не делает ничего, кроме загрузки и запуска большей, более интеллектуальной програм- программы. Начальный загрузчик обычно загружает программу /boot, чтобы она, в свою очередь, загрузила ядро. Начальная загрузка. Задача приведения системы в рабочее состояние. Когда на машину впервые подается питание, она обычно не выполняет какую-либо программу. Начальная загрузка инициализирует машину, загружает программу из вторичного хра- хранилища в главную память и запускает эту программу на исполнение. Некеширующее владение (noncaching lease). Некеширующее владение выдается сервером NFS клиенту для указания того, что клиент должен проделывать все файло- файловые операции синхронно с сервером. См. также Владение с кешем чтения; Владение с кешем записи. Неблокирующий ввод/вывод. Режим, в который может быть переведен дескриптор, при котором система возвратит ошибку, если осуществление какой-либо операции ввода/вывода для этого дескриптора вызвало бы блокирование процесса. Например, если осуществлен системный вызов read для дескриптора, находящегося в режиме не- неблокирующего ввода/вывода, до поступления данных система возвратит код ошибки ЕAGAIN, вместо того, чтобы заблокировать процесс. См. также Опрос ввода/вывода (polling I/O); Управляемый сигналами ввод/вывод (signal-driven I/O). Неизменный идентификатор. Идентификатор, уникально ссылающийся на какой- нибудь объект как во время его существования, так и спустя длительное время после его удаления. Неизменный идентификатор дает системе возможность запоминать идентичность, выходя за рамки кратковременных сбоев, и обнаруживать ошибки и со- сообщать о них при попытках получения доступа к удаленным объектам. Неканонический режим. См. Канонический режим. Нелокальный переход (nonlocal goto). Передача управления, которая обходит нормальный поток выполнения программы в рамках процедуры. Например, если про- процедура А вызывает процедуру В, а В вызывает С, тогда прямая передача управления из С обратно в А (минуя В) была бы нелокальным переходом.
730 Глоссарий Необязательная блокировка (advisory lock). Блокировка, которая применяется лишь тогда, когда процесс явным образом запрашивает ее применение. Необязательная бло- блокировка противопоставляется обязательной блокировке, которая применяется все время. См. также Обязательная блокировка (mandatory lock). Непосредственный доступ к памяти (direct memory access - DMA). Возможность, посредством которой периферийное устройство может получить доступ к главной памяти без участия CPU. DMA обычно используется для передачи смежных блоков данных между основной памятью и периферийным устройством. Непосредственный режим (raw mode). См. Канонический режим. Непосредственный сокет (raw socket). Сокет, предоставляющий прямой доступ к коммуникационному протоколу, лежащему в основе транспортного уровня. Например, сокет в домене IPv4 дает пользователю возможность читать и записывать IP-пакеты непосредственно, не используя транспортный протокол, такой, как UDP или TCP. Нерезидентный объект. Объект, не присутствующий в основной памяти. Например, страница в виртуальном адресном пространстве процесса может быть нерезидентной, если на нее никогда не производилась ссылка. Нижняя граница (low watermark). Нижняя граница, указывающая минимальный объем данных, который должен присутствовать, прежде чем будет предпринято дейст- действие. В средствах межпроцессного взаимодействия буфер данных каждого сокета имеет нижнюю границу, которая обозначает минимальное количество данных, которые должны находиться в буфере до удовлетворения запроса приема. См. также Верхняя граница (high watermark). Нижняя половина (bottom half). По отношению к работе системы - коллекция проце- процедур ядра, которые вызываются в результате прерываний. Эти процедуры не могут зависеть от состояния какого-либо процесса. См. также Верхняя половина (top half). Номер начальной последовательности. См. Пространство последовательностей (sequence space). Номер образования (generation number). Номер, каждый раз назначаемый индексу при его выделении для представления нового файла. Каждый номер образования используется лишь однажды. Чтобы затруднить предсказание описателей файлов, большинство реализаций NFS, включая FreeBSD, используют для выбора нового номера образования генератор случайных чисел. Номер устройства. Номер, уникально идентифицирующий устройство внутри класса символьных устройств. Исторически номер устройства включает в себя две части: старший номер устройства и младший номер устройства. В FreeBSD 5.2 номера устройств назначаются динамически и используются лишь для обратной совместимо- совместимости со старыми приложениями.
Глоссарий 731 Область (region). Диапазон памяти, интерпретируемый одним и тем же способом. Например, текст программы является областью с доступом только для чтения и под- подкачкой по требованию из файла на диске, который содержит программу. Область подкачки (swap area). Область вторичного хранилища, которая использует- используется для подкачки. Обмен ключами Интернет. Сетевой протокол для обмена ключами, используемыми в протоколах безопасности IPSec. Оболочка (shell). Программа, интерпретирующая и выполняющая команды пользова- пользователя. Когда пользователь регистрируется в системе UNIX, обычно создается процесс оболочки, дескрипторы стандартного ввода, стандартного вывода и стандартной ошибки которого направлены в терминал или псевдотерминал, на котором пользова- пользователь зарегистрировался. Обработчик (handler). Процедура, вызываемая в ответ на событие, такое, как сигнал. Обработчик сигнала. Процедура, вызываемая в ответ на сигнал. Обработчик события. Зарегистрированная программной системой функция, которая должна быть вызвана в дальнейшем при возникновении определенного события. См. также Очередь меток; kqueue. Обратный вызов (callback). В ядре механизм для уведомления подсистемы о за- завершении асинхронной операции. В NFS схема, при которой сервер отслеживает все объекты, которые кешировал каждый из его клиентов. Когда кешированный объект фиксируется двумя или более клиентами и один из них его изменяет, сервер посылает уведомление об очистке кеша всем остальным клиентам, удерживающим этот объект, чтобы они могли очистить свои кеши. См. тгхжоУведомление об очистке (eviction попсе); Владение (lease). Общее отображение (shared mapping). При осуществлении общего отображения файла на виртуальную память изменения, сделанные в отображении файла в память, записываются обратно в отображенный файл и видны другим процессам, отобразив- отобразившим этот файл. См. также Индивидуальное отображение. Объект виртуальной памяти. Структура данных ядра, представляющая хранилище данных (например, файл). Объект содержит пейджер для получения и помещения данных во вторичное хранилище, а также список физических страниц, которые кешируют фрагменты хранилища в памяти. Объект. См. Объект виртуальной памяти. Обязательная блокировка (mandatory lock). Блокировка, которую невозможно игнорировать или избежать. Обязательная блокировка противопоставляется необя- необязательной (advisory) блокировке, которая применяется лишь тогда, когда процесс явным образом запрашивает ее использование. См. также Необязательная блокировка (advisory lock).
732 Глоссарий Обязательный контроль доступа (mandatory access control - MAC). Обязательный контроль доступа представляет собой метки файлов, которые позволяют накладывать дополнительные ограничения на их использование. Например, файл, помеченный как «секретный», может быть показан лишь пользователям с уровнем секретный или выше. См. также Список контроля доступа. Оверлей. В компьютерных системах область кода или данных, которые могут быть замещены по требованию другой такой областью. Оверлеи обычно загружаются в адресное пространство процесса по требованию, возможно, поверх другого оверлея. Оверлеи являются часто используемой схемой для программ, которые слишком велики для того, чтобы поместиться целиком в адресное пространство машины, не поддержи- поддерживающей виртуальную память. Окно отправки. В TCP диапазон порядковых номеров, определяющий данные, которые система может передать через соединение и гарантировать, что у получающей стороны достаточно памяти для сохранения этих данных при получении. Любые данные с порядковыми номерами меньше начального для окна отправки уже были посланы и подтверждены. Любые данные с порядковыми номерами больше конечного для окна отправки не будут отправляться до тех пор, пока окно отправки не изменится таким образом, что будет включать их. См. также Схема скользящего окна. Октет. Базовая единица представления данных; 8-разрядный байт. Термин октет используется вместо байта в определениях многих сетевых протоколов, поскольку на некоторых машинах используются другие размеры байтов. Опережающая подкачка (prepaging). Упреждающая выборка страниц памяти. Опережающая подкачка является методикой, используемой системами виртуальной памяти для снижения числа отказов страниц. См. также Кластер. Опережающий ввод с клавиатуры (type-ahead). Передача данных системе, обычно при вводе пользователя с клавиатуры, до того, как данные будут запрошены процессом. Описатель файла (file handle). Глобально уникальная лексема, создаваемая сервером NFS и передаваемая обратно клиенту NFS. Клиент может затем использовать описа- описатель файла для ссылки на связанный с ним файл на сервере. Описатель создается, когда файл впервые открывается; он передается серверу клиентом в последующих операциях, таких, как чтение и запись, относящихся к открытому файлу. Определение соседа. Методика, посредством которой сетевая система определяет ап- аппаратные адреса своих соседей, включая свой маршрутизатор, так что может посылать им сетевые пакеты. Протокол определения соседей используется в IPv6. См. также Протокол определения адреса. Опрос ввода/вывода (polling I/O). Обычный режим для дескриптора, при помощи ко- которого система будет блокироваться, если для запроса чтения нет доступных данных или для запроса записи нет доступного буфера. Процесс может определить, будет ли заблокирована операция ввода/вывода, опрашивая ядро при помощи системных вызо- вызовов select или poll. Для системных вызовов select или poll может быть затребовано
Глоссарий 733 немедленное возвращение со сведениями или блокирование до тех пор, пока не будет завершена по крайней мере одна операция ввода/вывода. См. также Неблокирующий ввод/вывод; Ввод/вывод, управляемый сигналами. Основная память (main memory). Главная системная память на машине. Отказ страницы (page fault). Исключение, генерируемое обращением процесса к странице виртуального адресного пространства этого процесса, которая не помечена как присутствующая в памяти. Отказ страницы заполнения по требованию (fill-on-demand page fault). Первый отказ страницы для отдельной страницы; он обрабатывается путем получения данных от файловой системы или путем выделения заполненной нулями страницы. Относительное имя пути. См. Имя пути. Относительный приоритет (nice).1 Параметр распределения времени для процессов, контролируемый пользователем. Значение переменной относительного приоритета процесса используется при вычислении распределения приоритетов потоков процесса. Положительные значения относительного приоритета означают, что процесс желает получать меньше своей доли процессорного времени. Отрицательные значения отно- относительного приоритета означают, что процесс запрашивает больше своей доли процес- процессорного времени. Отображенный объект (mapped object). Объект, страницы которого отображены в адресное пространство процесса. Процессы отображают объекты в свое виртуальное адресное пространство, используя системный вызов ттар. Отправка сигнала. Уведомление процессу, что для этого процесса имеется ожи- ожидающий сигнал. Поскольку большинство действий, связанных с сигналом, совершает- совершается получающим процессом, процесс, посылающий сигнал, обычно осуществляет всего лишь запись ожидающего сигнала в структуру получающего процесса и организует запуск получающего процесса. Отсрочка таймера (timer backoff). Величина, на которую увеличивается значение таймера (например, в TCP значение таймера повторной передачи определяется по таб- таблице множителей, обеспечивающей примерно экспоненциальный рост значений тайм- аутов). Очередь запуска (run queue). Очередь потоков, готовых для запуска. См. также Очередь простоя (idle queue); Очередь ожидания (sleep queue). Очередь меток (callout queue). Структура данных ядра, которая описывает ожи- ожидающие события. Каждое событие в очереди описывается структурой, которая содержит функцию для вызова, указатель, который должен быть передан в качестве аргумента этой функции, и число тиков таймера до возникновения события. 1 Англоязычный термин «nice» говорит о том, насколько данный процесс пытается быть приятным (nice) по отношению к другим процессам. Именно поэтому положительные значения означают меньший приоритет, чем отрицательные. - Примеч. науч. ред.
734 Глоссарий Очередь ожидания (sleep queue). Очередь тех потоков, которые заблокированы в ожи- ожидании события. Название sleep происходит от процедуры sleepQ, которая помещает потоки в эту очередь. См. также Очередь простоя (idle queue); Очередь запуска (run queue). Очередь простоя (idle queue). В очереди простоя сохраняются все потоки режима простоя. Поток режима простоя (idle thread) запускается лишь тогда, когда процессору больше нечего делать. См. также Очередь запуска; Очередь ожидания (sleep queue). Очередь сообщений. Локальный механизм межпроцессного взаимодействия, под- поддерживающий доставку сообщений по порядку. Сообщения добавляются в один конец очереди и удаляются из другого, а ядро гарантирует сохранение их порядка. Паника. В UNIX непоправимая системная ошибка, обнаруженная ядром. FreeBSD автоматически восстанавливается после паники посредством перезагрузки компьютера, исправления повреждений файловой системы и возобновлением после этого нормаль- нормальной работы. См. также Аварийный дамп (crash dump). Пейджер. Модуль ядра, отвечающий за предоставление данных для заполнения стра- страницы и за предоставление места для сохранения этой страницы, когда она была изме- изменена, а память требуется использовать для других целей. Переключение контекста (context switching). Действие по прерыванию работы теку- текущего потока и переключения на другой поток. Переключение контекста происходит, когда после отработки одного потока выделяется процессорное время для другого по- потока. Контекст прерванного потока сохраняется в управляющем блоке соответст- соответствующего потока, а контекст другого потока загружается. Перемещение (relocation). Копирование содержания программы из одного места адресного пространства в другое. Такое копирование может сопровождаться измене- изменениями в образе программы таким образом, чтобы ссылки на память, содержащиеся в программе, остались правильными после копирования программы. Код, который не привязан к определенному начальному адресу памяти, называется переместимым или независимым от расположения. Перенаправление ввода/вывода. Перенаправление потока ввода/вывода от распре- распределения по умолчанию. Например, все стандартные оболочки дают пользователю воз- возможность перенаправлять поток стандартного вывода в файл или процесс. Перена- Перенаправление ввода/вывода реализуется в оболочке путем закрывания дескриптора, свя- связанного с потоком ввода/вывода, а затем открывания или дублирования на его месте другого дескриптора. Пересылка (forward). Направление прохождения пакета через систему, когда он по- получается хостом, не являющимся местом его окончательного назначения. См. также Входящий (inbound); Маршрутизатор. Перехваченный сигнал. Сигнал, доставка которого процессу вызывает извлечение процедуры обработчика сигнала. Обработчик сигнала устанавливается процессом посредством системного вызова sigaction.
Глоссарий 735 Планирование (scheduling). В операционных системах планирование, используемое для совместного использования ресурса. Например, планирование процессов совмест- совместно использует процессорное время и основную память. Подкачка по требованию (demand paging). Методика управления памятью, при которой память подразделяется на страницы, а страницы предоставляются процессам по мере необходимости, т.е. по требованию. См. также Чистая подкачка по требованию. Подкачка процессов (swapping). Алгоритм управления памятью, при котором в и из вторичного хранилища перемещаются процессы целиком в случае, когда наблюдается нехватка основной памяти. Подкачка страниц (paging). Действия по загрузке страниц выполняющегося процесса в основную память, когда к ним производится обращение, или удаление их из памяти, когда они замещаются. При выполнении процесса говорят, что все его страницы нахо- находятся в виртуальной памяти. Однако в основной памяти должны находиться лишь активно используемые страницы. Оставшиеся страницы могут находиться на диске до тех пор, пока не понадобятся. Политика выборки (fetch policy). Политика, используемая системой управления виртуальной памятью с подкачкой по требованию при обработке отказов страниц. Политики выборки различаются главным образом по способу, которым они органи- организуют опережающую подкачку страниц. Политика замещения. Политика, которую использует система управления виртуаль- виртуальной памятью с подкачкой по требованию для повторного использования памяти, когда последняя недоступна другим путем. Политика оптимального замещения. Политика замещения, которая оптимизирует производительность системы управления виртуальной памятью с подкачкой по требо- требованию. В данной книге политика, посредством которой полная строка ссылки програм- программы известна заранее, а страницы выбираются таким образом, что количество отказов страниц минимизируется. Политика размещения. Политика, используемая системой виртуальной памяти для размещения страниц в главной памяти при обслуживании отказа страницы. FreeBSD использует для оптимизации размещения страниц расцветку страниц (page coloring). Политики маршрутизации. Средства маршрутизации, предусмотренные в процессе уровня пользователя, которые определяют внешние политики. Политики маршрутиза- маршрутизации включают все компоненты, которые демон маршрутизации использует при выборе начальных маршрутов (first-hop routes), такие, как определение топологии локальной сети, реализация различных протоколов маршрутизации и конфигурационная ин- информация, определяющая локальные политики. См. также Маршрутизатор; Механизм маршрутизаг^ии. Полуоткрытое соединение. Соединение, которое открыто для коммуникации в одном направлении между двумя конечными точками. Например, клиент может закрыть свою передающую сторону соединения потока, поскольку у него больше нет данных для
736 Глоссарий передачи, но сохранить приемную половину соединения открытой, чтобы иметь воз- возможность получать данные от сервера. Порожденный процесс (child process). Процесс, являющийся прямым потомком дру- другого процесса как результат создания посредством системного вызова fork. Посимвольный режим. Режим работы для устройства псевдотерминала, посредством которого процессы, читающие из псевдотерминала, получают ввод немедленно по мере печатания. Этот режим отличается от непосредственного режима тем, что систе- система по-прежнему осуществляет определенную обработку ввода, такую, как интерпрета- интерпретация символа прерывания. См. также Канонический режим. Постоянный модуль ядра. Совокупность программного обеспечения, реализующего службу ядра, которая должна присутствовать при загрузке и не может быть удалена при работе системы. См. также Загружаемые модули ядра. Поток (thread). Единица выполнения процесса. Потоку требуется адресное простран- пространство и другие ресурсы, но он может разделять многие из этих ресурсов с другими по- потоками. Потокам, разделяющим адресное пространство и другие ресурсы, процес- процессорное время распределяется независимо, и они все могут осуществлять системные вызовы одновременно. Поток ввода/вывода. Поток данных, направляемых в процесс или генерируемых про- процессом. Большинство потоков ввода/вывода в UNIX имеют один общий формат дан- данных, который позволяет пользователям писать программы в стиле создания инстру- инструментов и комбинировать эти программы в конвейеры, направляя поток стандартного вывода одной программы в поток стандартного ввода другой. Поток программного прерывания. Поток, который запускается в ответ на програм- программное прерывание. В FreeBSD обработка ввода для каждого коммуникационного прото- протокола транспортного уровня внедрена в поток программного прерывания. Предел пересылок (hop limit). Число маршрутизаторов, через которые может быть переправлен пакет до того, как он будет уничтожен. См. также Маршрутизатор. Предотвращение малых пакетов. В организации сетей предотвращение передачи таких маленьких пакетов, что их передача была бы неэффективной. Предсказание заголовка. Эвристика, используемая TCP для входящих пакетов для обнаружения двух обычных случаев: следующего ожидаемого сегмента данных для существующего соединения или подтверждения плюс обновления окна для одного или более сегментов данных. Когда возникает один из этих двух случаев, а у пакета нет до- дополнительных флагов или указателей состояния, полная общая обработка ввода TCP пропускается. Преобразование адреса. Механизм, обычно реализованный аппаратно, преобра- преобразующий адреса памяти, поставляемые программой, в физические адреса памяти. Эта возможность важна для поддержки многозадачности, поскольку она позволяет опера- операционной системе загружать программы в различные области памяти, в то же время по-
Глоссарий 737 зволяя исполнять программы так, как если бы они были загружены в одну фиксирован- фиксированную область памяти. См. также Блок управления памятью (memory-management unit). Прерывание. В компьютерных системах событие, внешнее по отношению к текущему выполняющемуся процессу, которое вызывает изменение нормального потока испол- исполнения инструкций. Прерывания обычно генерируются аппаратными устройствами, внешними по отношению к CPU. Приемное окно. В TCP диапазон порядковых номеров, которые определяют данные, которые система примет для соединения. Любые полученные данные с порядковыми номерами, выходящими за пределы этого диапазона, уничтожаются. См. также Схема скользящего окна. Принудительное перемещение. Когда планировщик активно перемещает поток от одного процессора на другой, чтобы сбалансировать рабочую нагрузку системы. Приоритет планирования. Поддерживаемый ядром параметр каждого процесса, определяющий приоритет, с которым будет планироваться процессорное время для выполнения потоков процесса. Когда поток выполняется в режиме пользователя в классе планирования времени, система периодически вычисляет приоритет распре- распределения, используя приоритет потока и параметр относительного приоритета (nice). См. также Приоритет процесса; Класс планирования. Приоритет процесса. Параметр, используемый ядром для назначения на выполнение потоков процесса. Приоритет для потоков, работающих в классе разделения времени, динамически изменяется в соответствии с работой потока. К тому же для процесса может быть установлен параметр относительного приоритета, чтобы присвоить весо- весовые коэффициенты для общих приоритетов распределения времени для потоков про- процесса. См. также Класс распределения; Приоритет распределения. приоритетный процесс (foreground process) В системах управления процессами, ориентированных на управление заданиями, процесс, группа которого та же самая, как у управляющего терминала; соответственно процесс может читать и записывать в терминал. В противном случае приоритетным является тот процесс, завершения которого ожидает командный интерпретатор. Противоположным приоритетному является фоновый (background) процесс. Пробуксовка (thrashing). Состояние, при котором потребности в использовании памяти намного превышают ее доступность. При пробуксовке машина большую часть времени тратит на осуществление системных задач, а не кода приложений в режиме пользователя. Программа с устанавливаемым идентификатором группы (set-group-identifier program). Программа, которая запускается с дополнительными привилегиями для группы. Программа с установленным идентификатором группы обозначается соответ- соответствующим битом в индексе файла. Когда процесс указывает такой файл в системном вызове exec, в качестве GID файла используется эффективный GID процесса.
738 Глоссарий Программа с устанавливаемым идентификатором пользователя (set-user-identifi- (set-user-identifier program). Программа, которая запускается с UID, отличающимся от UID запус- запускающего процесса. Программы с устанавливаемым идентификатором пользователя обозначаются соответствующим битом в индексе файла. Когда процесс указывает такой файл в системном вызове exec, в качестве UID файла используется эффективный UID процесса. Программное прерывание (software interrupt). Прерывание, вызываемое програм- программным обеспечением. Оно запрашивается асинхронным системным прерыванием. Просроченное преобразование (stale translation). Преобразование или отображение, которое раньше было истинным, но больше недействительно. Например, на машинах, в которых имеется буфер быстрого преобразования адреса (translation lookaside buffer), если элемент таблицы страниц изменился, любое преобразование адреса для этой страницы в буфере быстрого преобразования должно быть сброшено на диск (flushed), чтобы не допустить просроченного преобразования. Простой косвенный блок. См. Косвенный блок. Пространство подкачки (swap space). См. Область подкачки. Пространство последовательностей. Диапазон порядковых номеров, назначенных передаваемым через TCP соединение данным. В TCP порядковые номера берутся из циклического 32-разрядного пространства, которое начинается с произвольного значе- значения, называемого начальным номером последовательности. Протокол дейтаграмм пользователя (user datagram protocol - UDP). Простой нена- ненадежный протокол дейтаграмм, используемый с протоколами Интернета. UDP предо- предоставляет точечную адресацию, адресацию нескольким получателям и широковеща- широковещательную адресацию, а также возможность контрольных сумм для данных. Одна и та же версия UDP работает одинаковым образом поверх как IPv4, так и IPv6. Протокол Интернета (Internet protocol). См. IPv4. Протокол определения адреса (address resolution protocol - ARP). Коммуникацион- Коммуникационный протокол, использующийся для динамического преобразования одного сетевого адреса в другой. Например, ARP используется в FreeBSD для динамического сопостав- сопоставления адресов Интернета с адресами Ethernet. См. также Определение соседа (neighbor discovery). Протокол точка-точка (point-to-point protocol - РРР). Протокол точка-точка являет- является дисциплиной линии связи, используемой сетевым программным обеспечением для инкапсуляции и пересылки дейтаграмм протокола Интернета через асинхронные линии последовательной передачи. См. также Дисциплина линии связи. Протокол управления передачей (transmission control protocol - TCP). Ориентиро- Ориентированный на соединение транспортный протокол, используемый в Интернете. TCP обес- обеспечивает надежную передачу данных, а также внеполосное (out-of-band) указание на наличие срочных данных.
Глоссарий 739 Протокол управляющих сообщений Интернета (Internet control message protocol - ICMP). Коммуникационный протокол, используемый для сообщений об ошибках и управления работой протоколов Интернета. Как IPv4, так и IPv6 включают свою версию ICMP, которые называются соответственно ICMPv4 и ICMPv6. Процедура запроса пользователя. Набор процедур, предоставляемых каждым из коммуникационных протоколов, который непосредственно поддерживает сокет (про- (протокол, поддерживающий сокет опосредованно, расположен ниже протокола, под- поддерживающего сокет непосредственно). Эти процедуры служат в качестве главного интерфейса между уровнем программного обеспечения, реализующего сокеты, и ком- коммуникационным протоколом. Средства межпроцессного взаимодействия для боль- большинства связанных с сокетами системных вызовов осуществляют вызовы процедур за- запросов пользователя. См. также Запрос соединения (connect request); Запрос управле- управления (control request); Запрос прослушивания (listen request); Запрос опознавания (sense request). Процедура запуска (start routine). Процедура драйвера устройства, отвечающая за запуск операции устройства после получения системой всех ресурсов, необходимых для операции. Процесс. В операционных системах задача, содержащая один или более потоков ис- исполнения. В UNIX процессы пользователя могут создаваться с помощью системного вызова/о гк. Процесс зомби (zombie process). Процесс, который завершился, но статус завершения которого не был еще получен родительским процессом (или процессом init). Процесс ядра. Процесс, выполняющийся процессором в режиме ядра. Примерами процессов ядра являются pagedaemon и swapper. Процессорное притяжение (processor affinity). В системах SMP желание запускать поток на выполнение на одном и том же процессоре. По соображениям производитель- производительности поток не должен без нужды перемещаться между процессорами из-за потери кешированной рабочей памяти. Прямая ссылка (hard link). Элемент каталога, непосредственно ссылающийся на ин- индекс. Если на один индекс имеются несколько прямых ссылок и если одна из ссылок удаляется, оставшиеся ссылки по-прежнему указывают на индекс. В противополож- противоположность этому символическая ссылка является файлом, в котором содержится путь, ука- указывающий на файл. См. также Символическая ссылка. Псевдотерминал. Псевдотерминал обеспечивает программную эмуляцию аппаратно- аппаратного терминала. Псевдотерминал создается из пары символьных устройств, ведущего и ведомого. Ведомое устройство предоставляет процессу интерфейс, идентичный интерфейсу аппаратного терминала. Однако, вместо того чтобы им управляло аппаратное устройство, у ведомого терминала есть другой процесс, который управляет им посред- посредством ведущей части псевдотерминала. Все, что записывается в ведущее устройство,
740 Глоссарий подается ведомому устройству в качестве ввода, и все, что записывается в ведомое устройство, представлено как ввод для ведущего устройства. Путь клонирования. Элемент маршрутизации, который не используется непосредст- непосредственно, но который вызывает создание нового экземпляра пути. Например, путь к ло- локальному Ethernet настраивается в виде пути клонирования таким образом, что при ссылках для каждого локального хоста будут создаваться пути отдельных хостов. Рабочий каталог (working directory). См. Текущий рабочий каталог Рабочий набор (working set). Набор страниц виртуального адресного пространства процесса, на которые были сделаны ссылки в течение последних нескольких секунд. В большинстве процессов наблюдается локальность ссылок, и размер их рабочего набора обычно менее половины общего размера их виртуальной памяти. Разборка (decapsulation). В сетевом взаимодействии удаление из сообщения сведений внешнего заголовка. См. также Инкапсуляция. Раздел диска. Программная схема, разделяющая диск на один или более непрерывных областей или разделов. Каждый раздел представляет собой непрерывную область дис- дискового накопителя, которая используется в качестве области подкачки или для со- содержания файловой системы. Раздел. См. Раздел диска. Разделяемая память (shared memory). Область памяти, которую могут читать и в которую могут записывать два различных процесса. Самый быстрый способ обмена информа- информацией между процессами на одной системе. См. также Семафор. Размер блока. Естественная единица памяти, выделяемой файлу (размер блока файло- файловой системы), или наименьшая единица ввода/вывода, с которой может работать сим- символьное устройство (для дисковых устройств обычно размер сектора). В FreeBSD размер блока файловой системы является параметром файловой системы, который фиксируется при создании файловой системы. Размер резидентного набора. Количество страниц физической памяти, занимаемой процессом. В хорошо настроенной системе размер резидентного набора процесса будет равен рабочему набору (working set) процесса. Обычно точный рабочий набор вычислить невозможно, поэтому у процесса будут дополнительные страницы помимо тех, которые нужны для его рабочего набора. Распределенная программа. Программа, которая может быть разделена между мно- множеством процессов, которые могут находиться на различных машинах. Режим пользователя (user mode). Наименее привилегированный режим доступа к процессору. Процессы пользователя работают в режиме пользователя. См. также Режим ядра. Режим с обработкой (cooked mode). См. Канонический режим. Режим транспортировки. Один из двух режимов, используемых для безопасной комму- коммуникации в IPSec. В режиме транспортировки защищается лишь полезная нагрузка пакета,
Глоссарий 741 тогда как заголовки протокола сетевого уровня оставлены открытыми. См. также Туннельный режим. Режим ядра. Наиболее привилегированный режим доступа к процессору. Ядро FreeBSD работает в режиме ядра. См. также Режим пользователя. Резерв свободной памяти. Процент пространства в файловой системе, который резервируется для обеспечения надежной работы определенных алгоритмов выделения памяти, используемых файловой системой. По умолчанию в Fast Filesystem резервируется 8 процентов доступной памяти. Резервное хранилище. Хранилище, в котором содержатся объекты, удаленные из основной памяти во время операций страничной подкачки. См. также Вторичное хранилище. Резидентный объект. Объект, который присутствует в основной памяти. Например, страница в виртуальном адресном пространстве процесса резидентна, если ее содержи- содержимое присутствует в основной памяти. Родительский процесс. Процесс, являющийся непосредственным предшественником другого процесса в результате системного вызова fork. С-блок (C-block). Буфер, содержащий действительные данные в структуре данных С-списка (C-list). Сбрасывание страницы (pageout). Операция, осуществляемая системой виртуальной памяти, при которой содержимое страницы записывается во вторичное хранилище. Свободный список. В системах управления памятью список доступных страниц физической памяти (называемый также списком свободной памяти). Сходный свобод- свободный список имеется в системе и для динамически выделяемой памяти ядра. Многие структуры данных ядра выделяются динамически, включая vnode, элементы таблиц файлов и структуры квот дисков. Сеанс (session). Совокупность групп процессов, установленных для целей управления заданиями. Обычно сеанс создается для каждой оболочки регистрации. Все процессы, запущенные такой оболочкой регистрации, являются частью ее сеанса. Сегмент BSS. Часть программы, которая должна быть инициализирована нулями при загрузке программы в память. Название bss происходит от сокращения «block started by symbol» (блок, начатый символом). См. также Сегмент данных; Сегмент стека; Сегмент text. Сегмент text. Сегмент программы, в котором хранятся машинные инструкции. Система обычно делает сегмент text программ при загрузке программы в память доступным только для чтения и общим для нескольких процессов. См. также Сегмент BSS; Сегмент стека. Сегмент данных. Сегмент адресного пространства программы, содержащий инициа- инициализированные и неинициализированные данные программы. См. также Сегмент BSS; Сегмент стека; Сегмент text.
742 Глоссарий Сегмент. Непрерывная область данных, определяемая базой и размером. В управле- управлении памятью сегмент описывает область адресного пространства процесса. В протоко- протоколе TCP сегмент является диапазоном байтов в рамках одного соединения, определяе- определяемых начальным и конечным порядковыми номерами. Сегмент стека. Сегмент, в котором содержится стек. См. также Сегмент BSS; Сегмент данных; сегмент text. Сектор. Наименьшая непрерывная область диска, к которой можно получить доступ за одну операцию ввода/вывода. Семафоры. Структуры данных и набор функций, используемых для синхронизации доступа к разделяемому ресурсу, такому, как область памяти. Семафоры реализуют две функции: захвата и освобождения, такие, что, когда один поток захватил семафор, все остальные, следующие за первым, блокируются до тех пор, пока первый поток не освободит семафор. Семейство адресов. Коллекция связанных форматов адресов, использующихся в одном домене взаимодействия. Например, домен IPv4 использует семейство адресов Интернета. Семейство протоколов. Совокупность коммуникационных протоколов, члены которых связаны тем, что являются частью одной сетевой архитектуры. Например, протоколы TCP, UDP, IPv4 и ICMPv4 являются частью семейства протоколов для домена IPv4. Сервер без состояния. Сервер, которому не требуется поддерживать какую-либо ин- информацию о том, какой клиент обслуживается или какие данные были ему переданы. Каждый получаемый таким сервером запрос должен быть полностью самодоста- самодостаточным, предоставляя всю необходимую для его обслуживания информацию. Серверный процесс. Процесс, предоставляющий службы клиентским процессам через средства межпроцессного взаимодействия. См. также Клиентский процесс. Сетевая архитектура. Коллекция протоколов, средств и соглашений (таких, как формат сетевого адреса), которые определяют сеть. Как и машинные архитектуры, сетевые архитектуры могут быть реализованы различными способами. Например, некоторые сетевые архитектуры могут быть специально спроектированы для того, чтобы была возможность их реализации аппаратным способом. Сетевой порядок байтов. Порядок, определенный сетью для передачи полей протоко- протоколов, превышающих по размеру один октет. В IPv4 и IPv6 этим порядком является «наи- «наиболее значимый октет первым». Сетевой уровень. Уровень программного обеспечения в сетевой подсистеме FreeBSD, отвечающий за реализацию возможностей уровня 2 модели ISO. В домене IPv4 эти возможности реализованы в модуле протокола IP. Сигнал. В UNIX программное событие. В FreeBSD это событие моделируется после аппаратного прерывания.
Глоссарий 743 Сигнал возобновления. Сигнал 19 (SIGCONT). Сигнал, при получении которого остановленный или спящий процесс возобновляет свое выполнение. Символ сброса (kill character). Символ, который распознается обработчиком терми- терминала в каноническом режиме как «удалить все, набранное на этом терминале с момента последнего символа конца строки». У каждого сеанса терминала может быть свой символ сброса, и пользователь может изменить его в любой момент с помощью сис- системного вызова tcsetattr. Обработчик терминала не распознает символ сброса на терминалах, которые находятся в неканоническом режиме. См. также Символ стира- стирания; Символ удаления слова. Символ стирания (erase character). Символ, который интерпретируется обработчи- обработчиком терминала, находящегося в каноническом режиме, как «удалить последний символ в строке ввода». У каждого сеанса терминала может быть другой символ стирания, и этот символ стирания в любое время может быть изменен с помощью системного вызова scsetattr. Обработчик терминала не распознает символ стирания терминалов, находящихся в неканоническом режиме. См. также Символ сброса (kill character); Символ удаления слова. Символ удаления слова (word-erase character). Символ, который интерпретируется обработчиком терминала в каноническом режиме как означающий «удалить последнее введенной слово на данном терминале». По умолчанию удаляются предыдущий разде- разделитель слов и максимальная последовательность символов, не являющихся разделите- разделителями. В качестве альтернативы может быть указан другой алгоритм, настроенный на удаление компонентов имени пути. У каждого сеанса терминала свой символ удаления слова, и пользователь может изменить его в любой момент времени с помощью сис- системного вызова scsetattr. Обработчик терминала не распознает символ удаления слова на терминалах, которые не находятся в каноническом режиме. См. также Символ стирания; Символ сброса (kill character). Символ. Элемент данных, представляющий один печатный или управляющий знак. Символы обычно имеют длину 8 или 16 битов. См. также Байт; Октет. Символическая ссылка (symbolic link). Файл, содержимое которого интерпретирует- интерпретируется как имя пути, когда он оказывается компонентом пути. Называется также гибкой ссылкой (soft link). См. также Прямая ссылка (hard link). Символьное устройство. Устройство, предоставляющее либо интерфейс ввода/выво- ввода/вывода, ориентированный на поток символов, либо (в качестве альтернативы), неструк- неструктурированный («сырой») интерфейс. Все устройства в FreeBSD используют символь- символьный интерфейс. Симметричная криптография. Криптографическая система, использующая для шифро- шифрования данных тот же самый ключ, который используется для расшифровки данных; иногда называется криптографией с секретным ключом. См. также Асимметричная криптография.
744 Глоссарий Симметричная многопоточность (symmetric multithreading - SMT). Процессор, содержащий несколько ядер CPU, в который могут быть запущены отдельные потоки, поддерживает симметричную многопоточность. Каждое ядро CPU само действует в качестве процессора, но все ядра CPU разделяют одну и ту же кеш-память. Симметричная многопроцессорная обработка (symmetric multiprocessing - SMP). Многопроцессорная система содержит один или более центральных процессоров, со- соединенных с общей основной памятью. Симметричная многопроцессорная обработка описывает ядро, которое может одновременно работать на всех CPU в одно и то же время. См. также Симметричная многопоточность. Синдром незначительного окна (silly-window syndrome). Условие, наблюдаемое в системах управления потоком на основе окна, когда получатель посылает несколько небольших (т.е. тупых) выделений окна, вместо того чтобы подождать доступности окна приемлемого размера. Синхронный Синхронно с текущим выполняемым процессом. Например, в UNIX все операции ввода/вывода выглядят синхронными: системные вызовы read и write не воз- возвращаются до тех пор, пока операция не будет завершена. (Однако в случае write данные могут быть не записаны в конечное место назначения до определенного време- времени в будущем - например, при записи в файл на диске.) Система управления памятью. Часть операционной системы, которая отвечает за управление ресурсами памяти, доступными в машине. Системные часы. Устройство, поддерживающее в системе понятие времени дня. На большинстве систем этим устройством является интервальный таймер, периодиче- периодически прерывающий работу центрального процессора. Система использует эти прерыва- прерывания для поддержания текущего времени дня и выполнения периодических функций, таких, как планировка потоков. Системный вызов (system call). В операционных системах запрос системе для обслу- обслуживания; называется также запросом системной службы (system service request). Системный режим. См. Режим ядра. Служебные данные. Интерпретируемые особым образом данные, передаваемые по сетевому соединению. Служебные данные могут включать специфичные для протоко- протокола данные, такие, как адресация или опции. Совмещение виртуальных адресов (virtual-address aliasing). Два или более процес- процессов, отображающих одну и ту же физическую страницу на различные виртуальные адреса. При использовании таблицы страниц обратного отображения в любой момент времени с данной физической страницей может быть сопоставлен лишь один вирту- виртуальный адрес. В данном случае ядро должно делать недействительным элемент таблицы страниц для совмещенной страницы каждый раз при переключении между процессами с конфликтующими виртуальными адресами для этой страницы. См. также Таблица страниц обратного от обрaoiceния.
Глоссарий 745 Сокет. В модели межпроцессного взаимодействия FreeBSD конечная точка (endpoint) коммуникации. Это также структура данных, реализующая абстракцию сокета, и сис- системный вызов, создающий сокет. Сокет дейтаграмм. Тип сокета, поддерживающий ненадежную передачу данных с сохра- сохранением границ сообщения. Сокет последовательной передачи пакетов. Тип сокета, моделирующий последова- последовательную, надежную, недублированную, основанную на соединении коммуникацию с сохранением рамок сообщений. Сокет потока (stream socket). Тип сокета, моделирующий надежный, основанный на соединении поток, который может поддерживать внеполосную (out-of-band) передачу данных. Сокет с надежной доставкой сообщений. Тип сокета, который гарантирует надеж- надежную доставку данных и сохранение рамок сообщения, который не основан на соедине- соединении. Сообщение о недоступности хоста. Сообщение об ошибке сетевого уровня, указы- указывающее, что хост, которому было направлено предыдущее сообщение, недоступен, поскольку отсутствует известный путь к нему. Сообщение перенаправления маршрута. Сообщение, генерируемое маршрутиза- маршрутизатором, когда он обнаруживает, что полученное им сообщение может быть доставлено более прямым путем. Состояние гонки (race condition). Условие, при котором два или более действий для операции возникают в непредсказуемом порядке. Проблемы появляются тогда, когда существует возможный порядок, при котором получается неверный результат. Состояние ядра. Состояние ядра времени выполнения. Это состояние, в которое входят счетчик программ, регистры общего назначения и стек времени выполнения, должно сохраняться и восстанавливаться при любом переключении контекста. Сохраненный GID (saved GID). Механизм, записывающий идентичность программы setgid путем копирования значения эффективного GID во время вызова exec для про- программы. Во время своего выполнения программа может временно отменить свои при- привилегии setgid, установив в качестве эффективного GID свой действительный GID. В дальнейшем она может восстановить свои привилегии setgid, установив обратно в качестве эффективного GID сохраненный GID. См. также Мандат; Эффективный идентификатор группы. Сохраненный UID (saved UID). Механизм, записывающий идентичность программы setuid путем копирования значения эффективного UID во время вызова exec для про- программы. Во время своего выполнения программа может временно отменить свои при- привилегии setuid, установив в качестве эффективного UID свой действительный UID. В дальнейшем она может восстановить свои привилегии setuid, установив обратно в качестве эффективного UID сохраненный UID. См. также Мандат; Эффективный идентификатор пользователя.
746 Глоссарий Специальный файл. См. Специальный файл устройства. Специальный файл устройства. Файл, посредством которого процессы могут по- получить доступ к аппаратным устройствам компьютера. Например, через подобный файл осуществляется доступ к звуковой карте. Список контроля доступа (access control list - ACL). Список контроля доступа заме- замещает права доступа группы к файлу более специфическим списком пользователей, которым разрешен доступ к файлам. ACL включает также список разрешений, которые даются каждому пользователю. Эти разрешения включают традиционные разрешения на чтение, запись и исполнение вместе с другими свойствами, такими, как право на переименование или удаление файла. Список свободной памяти См. Свободный список. Средняя загрузка. Мера загрузки центрального процессора в системе. Средняя загрузка в FreeBSD является средним от числа процессов, готовых к запуску или ожи- ожидающих краткосрочных событий, таких, как завершение дисковой операции ввода/ вывода, измеряемой один раз в секунду в течение предыдущего интервала работы сис- системы в одну минуту. Срочные данные (urgent data). В TCP данные, помеченные для срочной доставки. С-список (C-Hst). Структура данных связанного списка, используемая системой для поддержки ввода-вывода по линии последовательной передачи. Стандартная ошибка. Поток ввода/вывода, в который условно помещаются сообще- сообщения об ошибках. Этот поток обычно связан с дескриптором 2 процесса. Стандартный ввод. Поток ввода/вывода, из которого условно получается ввод. Этот поток обычно связан с дескриптором 0 процесса. Стандартный вывод. Поток ввода/вывода, в который условно направляется вывод. Этот поток обычно связан с дескриптором 1 процесса. Старший номер устройства (major device number). Номер, уникально идентифи- идентифицирующий устройство в классе символьных устройств. Исторически старший номер устройства использовался в качестве индекса в таблице устройств ядра. В FreeBSD 5.2 номера устройств присваиваются динамически и используются лишь для обратной со- совместимости со старыми приложениями. Стек. Область памяти, зарезервированная для временного хранения или для связыва- связывания процедур и служб прерываний. Стек использует концепцию «последним вошел, первым вышел» (last-in, first-out - LIFO). В большинстве архитектур стек растет от больших адресов памяти к меньшим. По мере добавления (проталкивания) элементов в стек указатель стека уменьшается; по мере получения (выталкивания) элементов из стека указатель стека увеличивается. Стек прерываний. Стек времени выполнения, который используется процедурами, вызываемыми в ответ на прерывания и исключения. В FreeBSD 5.2 у каждого устрой- устройства есть свой собственный поток, контекст которого включает стек прерываний.
Глоссарий 747 Стиль с продолжением. Стиль программирования, при котором две или более функ- функций действуют совместно, вызывая друг друга, вместо того чтобы непосредственно возвращаться в конец исполнения. Когда текущая функция заканчивает свою работу, она вызывает другую функцию, которая была передана в качестве аргумента первой функции, как часть вызова returnQ. Программирование с продолжением ведет к созда- созданию цепи функций и часто используется, когда системе нужно передать работу аппаратному сопроцессору, но в то же время нужно вызвать процедуру уборки после завершения работы сопроцессором. Страница. В управлении памятью единица фиксированного размера, использующаяся для деления физического или виртуального адресного пространства. См. также Под- Подкачка по требованию. Страница доступа (referenced page). В системе виртуальной памяти страница, с которой производится чтение или на которую производится запись. Страницы таблицы страниц (page-table pages). Верхний уровень двухуровневой иерархии структуры данных, используемой алгоритмом прямого отображения страниц для описания виртуального адресного пространства процесса. На PC страницы таблицы страниц хранятся в массиве, который называется каталогом страниц (directory table); каждый элемент в странице таблицы страниц указывает на страницу элементов таблицы нижнего уровня. См. также Каталог страниц (directory table); Прямое отобраясение страниц (forward-mappedpage table); Элемент таблицы страниц (page-table entry). Строка доступа (reference string). Набор данных, описывающих страницы, к которым обращается процесс в процессе выполнения. Это описание представляет поведение процесса по отношению к памяти в различные моменты времени жизни процесса. Строковый режим. См. Канонический режим. Структура termios. Структура, используемая для описания состояния терминала. Состояние терминала включает специальные символы, такие, как символы стирания, сброса и удаления слова; режимы работы, такие, как канонический или неканониче- неканонический; и аппаратные параметры линии последовательной передачи, такие, как четность и скорость передачи. Структура адреса сокета. Общая структура для хранения адресов для сокета. Многим процедурам межпроцессного взаимодействия, таким, как connectQ и bindQ, нужно знать сетевые адреса взаимодействующих конечных точек, и для них нужно переда- передавать структуру адресов сокета в качестве параметра. Структура отображения (mapping structure). Зависящее от машины состояние, необ- необходимое для описания преобразования и прав доступа к одной странице. См. также Элемент таблицы страниц. Структура переключения протоколов. Структура данных, хранящая точки входа для коммуникационных протоколов, поддерживаемых ядром. Структура пользователя. Структура данных, поддерживаемая ядром для каждого активного процесса в системе. Структура пользователя содержит статистику процесса
748 Глоссарий и действия сигналов. Для совместимости с более старыми отладчиками значительная часть состояния процесса копируется в структуру пользователя до генерации дампа ядра. В отличие от структуры процесса, структура пользователя для процесса переме- перемещается во вторичное хранилище в процессе подкачки. Также называется область u-dot и область пользователя (user area). Структура процесса. Структура данных, поддерживаемая ядром для каждого актив- активного процесса в системе. Структура процесса всегда постоянно находится в основной памяти, в отличие от пользовательской структуры, которая перемещается во вторичное хранилище при сбрасывании страниц при подкачке. Суперблок. Структура данных в файловой системе на диске, определяющая основные параметры файловой системы. Суперпользователь (superuser). Пользователь с UID, равным 0. Процессам, которы- которыми владеет суперпользователь, UNIX предоставляет особые привилегии. Регистраци- Регистрационным именем суперпользователя обычно является root. Схема скользящего окна. Схема управления потоком, при которой получатель огра- ограничивает количество данных, которые он хочет получить. Это ограничение выражает- выражается в виде непрерывного диапазона последовательных номеров, которые обозначаются как окно приема (receive window). Оно периодически сообщается отправителю, от ко- которого ожидается, что он будет отправлять лишь данные, попадающие в это окно. По мере получения и подтверждения данных окно скользит вперед в пространстве последовательностей. См. также Окно приема (receive window); Окно отправки (send window); Пространство последовательности (sequence space). Таблица дескрипторов. Индивидуальная для каждого процесса таблица, содержащая ссылки на объекты, с помощью которых можно осуществлять ввод/вывод. Дескрип- Дескрипторы ввода/вывода являются индексами этой таблицы. Таблица дескрипторов фрагментов. Структура данных в Fast Filesystem, описы- описывающая свободные фрагменты в карте распределения. Файловая система использует таблицу дескрипторов фрагментов, помещая байт в карту распределения и используя его в качестве индекса для таблицы дескрипторов фрагментов. Значение в таблице де- дескрипторов фрагментов указывает, сколько фрагментов определенного размера дос- доступны в элементе карты распределения. Выполнив операцию логического И с битом, соответствующим нужному размеру фрагмента, система может быстро определить, содержится ли нужный фрагмент в элементе карты распределения. Таблица открытых файлов процесса. См. Таблица дескрипторов. Таблица страниц обратного отображения (reverse-mapped page table). См. Инвертиро- Инвертированная таблица страниц. Таблица страниц прямого отображения (forward-mapped page table). Большой не- непрерывный массив, индексируемый виртуальным адресом, содержащий по одному элементу для каждой виртуальной страницы в адресном пространстве. Этот элемент содержит физическую страницу, на которую отображается виртуальная страница,
Глоссарий 749 а также биты прав доступа и состояния, указывающие на то, имеется ли ссылка на страницу и была ли она изменена, а также бит, определяющий действительность со- содержащихся в странице данных. Большинство современных дизайнов устройств управления страницами для архитектур с 32-разрядными адресными пространствами содержат некоторую разновидность таблицы страниц. См. также Инвертированная таблица страниц (inverted page table); Устройство управления памятью (memory- management unit). Таймер 2MSL. Таймер, используемый протоколом TCP при разрыве соединения. Название указывает на тот факт, что в качестве значения таймера устанавливается время, в два раза превышающее максимальное время, в течение которого пакет может находиться в сети. Это время выбрано для гарантирования того, что будущие соедине- соединения не будут ошибочно принимать опоздавшие сообщения старых соединений. См. также Максимальное время жизни сегмента (maximum segment lifetime). Таймер повторной передачи. Таймер, используемый TCP для запуска повторной передачи данных. Этот таймер устанавливается каждый раз, когда на удаленный хост передаются данные. Значение таймера выбирается таким, чтобы оно было больше вре- времени, которое требуется для получения данных удаленным хостом и возврата под- подтверждения получения данных. Таймер соединения (persist timer). Таймер, используемый TCP для поддержания вы- выходного потока соединения. Этот таймер запускается всякий раз, когда данные готовы к отправке, но окно отправки слишком маленькое, чтобы начать отправку, а других ожидающих отправки данных нет. Если до истечения срока таймера не будет получено обновление размера окна, отправляется образец окна (window probe). Текущий рабочий каталог. Каталог, от которого для процесса отсчитываются относи- относительные пути. Текущий рабочий каталог для процесса устанавливается с помощью системных вызовов chdir или fchdir. Теневой объект. Анонимный объект, расположенный между процессом и нижележа- нижележащим объектом для того, чтобы предотвратить отражение сделанных процессом изме- изменений обратно на нижележащий объект. Теневой объект используется, когда процесс осуществляет индивидуальное отображение файла таким образом, что изменения, сде- сделанные процессом, не отображаются в файле. Терминал. В компьютерных системах устройство, использующееся для интерактив- интерактивного ввода и получения данных из компьютера. Большинство терминалов включают монитор, отображающий получаемые от компьютера данные. В стандарте Ассоциации электронной промышленности (EIA) RS-232-C для соединения компьютеров и терми- терминального оборудования (data-terminal equipment - DTE) терминал является устройст- устройством, подключенным к другому концу провода, соединенного с аппаратурой передачи данных (data-communications equipment - DCE). В соответствии с этим стандартом терминалом может быть любой тип устройства, а не только устройство, на котором печатают люди.
750 Глоссарий Тик. Прерывание системного таймера. Транспортный уровень. Уровень программного обеспечения в сетевой подсистеме, отвечающий за перемещение данных между двумя сокетами. В зависимости от типа используемого транспортного протокола данные могут доставляться в виде последова- последовательного упорядоченного потока или в виде неупорядоченного набора отдельных сообщений. См. также Протокол управления передачей; Протокол дейтаграмм поль- пользователя. Тройной косвенный блок (triple indirect block). См. Косвенный блок. Туннельный режим. Один из двух режимов, используемых для безопасной коммуни- коммуникации в IPSec. В туннельном режиме защищаемый пакет полностью содержится внутри другого пакета, который переправляет внутренний пакет между двумя конечными точками. Конечные точки являются границами туннеля. См. также Режим транспорт ировки. Теги. Наращиваемая система для добавления произвольных данных в mbuf или кластер mbuf для передачи сведений между различными модулями в сетевом стеке без необходи- необходимости изменения данных пакета. Уведомление об очистке (eviction notice). Сообщение обратного вызова от сервера клиенту, уведомляющее клиента, что его владение объектом завершается. Владение обычно завершается потому, что другой объект хочет изменить объект, которым вла- владеют. См. также Обратный вызов; Владение (lease). Удаленный вызов процедур (RPC). Вызов процедуры, сделанный из клиентского процесса для извлечения подпрограммы в серверном процессе. Обычно клиентский и серверный процессы работают на разных машинах. Удаленный вызов процедур дей- действует во многом подобно локальному вызову процедур: клиент осуществляет вызов процедуры, а затем ожидает результатов, пока процедура выполняется. См. также Маршалинг. Управление заданиями (job control). Средства для управления заданиями. С помо- помощью управления заданиями можно запустить задания, остановить и отменить, а также перемещать их в приоритетный и фоновый режимы. Обработчик терминала предос- предоставляет возможности для автоматической остановки фонового задания, которое пыта- пытается получить доступ к управляющему терминалу, и для уведомления управляющего процесса задания о возникновении такого события. Управление модемом. Для оборудования коммуникации данных поддержка набора сигналов, используемых для обеспечения надежной инициализации и завершения соединений поверх асинхронных линий последовательной передачи, определенных стандартом RS-232. Поддержка управления модемом обычно важна лишь для тех линий последовательной передачи, доступ к которым осуществляется через комму- коммутируемые модемы. Управляющий процесс. Лидер сеанса, который устанавливает соединение с управ- управляющим терминалом. См. такжеЛшЗе/; сеанса.
Глоссарий 751 Управляющий терминал. Псевдотерминал, связанный с сеансом процесса, в котором могут генерироваться сигналы от клавиатуры. Управляющий терминал для процесса обычно наследуется от родителя процесса. Упреждающая выборка (prefetching). Получение данных до того, как они потребова- потребовались. Многие машины осуществляют упреждающую выборку машинных инструкций таким образом, что они могут совмещать время, которое тратится на выборку инструк- инструкций из памяти, со временем на их декодирование. Уровень сетевого интерфейса. Уровень программного обеспечения в сетевой подсис- подсистеме FreeBSD, отвечающий за транспортировку сообщений между хостами, под- подключенными к общей среде передачи. Этот уровень главным образом занимается управлением используемой средой передачи и осуществлением необходимой инкапсу- инкапсуляции и разборки пакетов для необходимого протокола уровня соединения. См. также Уровень соединения (link layer). Устройство. В UNIX периферийное оборудование, подключенное к CPU. Устройство подкачки. Устройство, на котором находится область подкачки. Учет блоков. Процесс поддержания числа дисковых блоков, доступных для сохране- сохранения новых данных в Fast Filesystem. Файл fifo. В файловой системе тип файла, который может использоваться для межпро- межпроцессного взаимодействия. Данные, записанные одним процессом в fifo, читаются другим процессом в том порядке, в каком они были отправлены. Название fifo отража- отражает тот факт, что данные передаются способом «первым вошел, первым вышел» (first-in, first-out). Файл дампа ядра (core file). Файл (с названием имя_процесса.соге), который созда- создается системой при получении процессом определенных сигналов. Файл содержит запись состояния процесса в момент поступления сигнала. Эта запись включает содержание виртуального адресного пространства процесса и - на большинстве систем - структуру пользователя. Файл. Объект в файловой системе, который интерпретируется как линейный массив байтов. У файла есть по крайней мере одно имя, и он существует до тех пор, пока все его имена не будут удалены явным образом. Файловая система /ргос. Основанный на файловой системе интерфейс активных про- процессов, предоставляющий возможности отладки процессов. Каждый процесс пред- представлен элементом каталога в псевдокаталоге /ргос. Приложения получают доступ к виртуальному адресному пространству процесса, открывая файл в /ргос, который связан с процессом, а затем используют системные вызовы read и write, как если бы процесс был обычным файлом. Файловая система. Коллекция файлов. Файловая система UNIX иерархическая, с организованными в каталоги файлами. Файловые системы включают возможности для именования файлов и управления доступом к файлам. Файловая система находится
752 Глоссарий на одном логическом устройстве, которое может представлять часть одного диска или набор дисков, объединенных вместе. Файловая структура. Структура данных, используемая ядром для хранения информа- информации, связанной с дескрипторами одного или более открытых файлов. Обычно каждый дескриптор открытого файла ссылается на уникальную файловую структуру. Однако файловые структуры могут быть общими, когда открытые дескрипторы дублируются с помощью системных вызовов dup и dup2, наследуются через системный вызов fork или получаются в сообщении посредством межпроцессного взаимодействия. Файловое смещение. Смещение в байтах, связанное с дескриптором открытого файла. Файловое смещение дескриптора файла устанавливается явным образом с по- помощью системного вызова Iseek или неявным образом в результате системных вызовов read или write. Физический блок. Один или более смежных секторов диска, которые система сопо- сопоставляет с логическим блоком. Физическое отображение (physical mapping- pmap). Состояние программного обес- обеспечения, называемое также структурой ртар, необходимое для управления машинно- зависимым преобразованием и доступом к таблицам, которые либо прямо, либо кос- косвенно используются аппаратурой управления памятью. Это состояние отображения включает, вдобавок к преобразованию адресов, информацию о правах доступа. Флаги устройства. Данные, указанные в конфигурационном файле системы и переда- передаваемые драйверу устройства. Использование этих флагов между различными драй- драйверами устройств отличается. Драйверы для терминальных устройств используют эти флаги для указания строк терминала, в которых драйвер должен игнорировать при вводе управляющие сигналы модема. Фоновый процесс. В системах управления процессами, ориентированными на управле- управление заданиями, процесс, группа которого отличается от группы его управляющего терми- терминала; таким образом, этот процесс в настоящее время заблокирован для доступа от боль- большинства терминалов. В противном случае фоновым является процесс, завершения которого командный интерпретатор не ждет, т.е. процесс был запущен с оператором «&». Противоположным фоновому является приоритетный (foregivund) процесс. Фрагмент. В файловой системе часть блока. Файловая система выделяет файлу новое дисковое пространство в виде полного блока или одного или более фрагментов блока. Файловая система использует фрагменты вместо полных блоков для уменьшения потерь памяти, когда размер полного блока большой. Холодная загрузка. Начальная фаза процедуры загрузки. Термин происходит от того факта, что программное обеспечение не делает никаких предположений о состоянии машины, как если бы машина была только что включена, будучи холодной. Холостой цикл (idle loop). Блок кода в ядре, который выполняется, когда больше нечего делать. В FreeBSD холостой цикл обнуляет страницы в свободном списке, ожидая добавления потока в очередь запуска.
Глоссарий 753 Центральный процессор (central processing unit- CPU). Основная вычислительная единица в компьютере. CPU является вычислительным элементом, выполняющим приложение. В многопроцессорной системе имеется более одного CPU. В системе могут присутствовать и другие процессоры (например, для обработки ввода/вывода). Циклический мьютекс (spin mutex). Циклический мьютекс не будет освобождать процессор, когда он не может немедленно обеспечить запрошенную блокировку, но за- зациклится в ожидании освобождения мьютекса другим процессором. Частота отказов (fault rate). Частота, с которой процесс генерирует отказы страниц. Для строки ссылки частота отказов определяется в независимом от времени виде как число отказов страницы, деленное на длину строки ссылки. Чистая подкачка по требованию. Подкачка по требованию без опережающей под- подкачки. Шина. Стандартное электрическое и механическое соединение компонентов ком- компьютера. Широковещание (broadcast). Передача всем станциям. В сети широковещательное сообщение передается всем подключенным к общей среде передачи станциям. Шифрование с открытым ключом (public key encryption). Криптографическая сис- система, в которой ключи, используемые для шифрования данных, могут быть общедос- общедоступными, в отличие от систем, требующих сохранения в тайне всех ключей, чтобы обеспечить безопасность данных. Шлюз (gateway). См. Маршрутизатор. Шторм возврата. Состояние сбоя, которое может возникнуть, когда сервер перегру- перегружен при возврате к обслуживанию после периода недоступности. Если имеется сильно сдерживаемая потребность в сервере, он может быть затоплен запросами. Если сервер просто игнорирует запросы, которые он не может обработать, клиенты будут быстро отправлять повторные запросы. Поэтому сервер обычно отвечает «попробуйте позже» на запросы, которые не может обслужить в данное время. Клиенты, получив такой ответ, будут ждать существенно дольше по сравнению с обычным временем тайм-аута перед повторной отправкой запроса. Элемент каталога. Элемент, представленный в файле каталога структурой записи с переменной длиной. Каждая структура содержит ASCII строку, которая представляет имя файла, число байтов памяти, предоставленных для строки, число байтов памяти, предос- предоставленных для элемента, тип файла, на который ссылается элемент, и номер индекса, ассо- ассоциированный с именем файла. По соглашению элемент каталога с номером индекса ноль рассматривается как свободный и занимаемое им место можно использовать. Элемент таблицы страниц (page-table entry- РТЕ). Зависящая от аппаратуры струк- структура данных, идентифицирующая состояние страницы виртуального адресного про- пространства. Когда виртуальная страница находится в памяти, РТЕ содержит номер фрейма страницы, который нужен аппаратному обеспечению для отображения вирту- виртуальной страницы на физическую.
754 Глоссарий Элемент файла. См. Структура файла. Эмулирование. Имитирование. FreeBSD может имитировать интерфейс системных вызовов других вариантов операционной системы UNIX. Например, FreeBSD может исполнять двоичные файлы, откомпилированные для Linux. Эффективный GID. См. Эффективный идентификатор группы. Эффективный UID. См. Эффективный идентификатор пользователя. Эффективный идентификатор группы (эффективный GID) (effective group identifier (effective GID). Первый элемент в массиве групп. Эффективный GID, вместе с другими GID в массиве групп, используется файловой системой для проверки групповых прав доступа. Эффективный GID устанавливается, когда выполняется программа установки группового идентификатора. См. также Мандат; Групповой идентификатор; Действи- Действительный групповой идентификатор; Сохраненный групповой идентификатор. Эффективный идентификатор пользователя (эффективный UID) (effective user identifier (effective UID). UID, который система использует для проверки многих прав доступа пользователя. Например, эффективный UID используется файловой системой для проверки права доступа владельца к файлам. Эффективный UID устанавливается при вьшолнении программы установки идентификатора пользователя. См. также Мандат; Действительный идентификатор пользователя; Сохраненный идентификатор пользо- пользователя; Идентификатор пользователя. Ядро (kernel). Основная управляющая программа, обеспечивающая базовые систем- системные возможности. Ядро FreeBSD создает и управляет процессами, предоставляет функции для доступа к файловой системе и обеспечивает средства коммуникации. Ядро FreeBSD является единственной частью FreeBSD, которую пользователь не может заменить.
Предметный указатель # #!,83 /dev, 53-54, 262-263,321-323, 341-343, 348-349 /dev/console, 700-701 /dev/fd, 312-313 /dev/kmem, 361-362, 706-708 /dev/mem, 265-266, 270-271, 361-362 /dev/null, 265-266 работа, 321-323 /etc, 411 /etc/defaults/rc.conf, 700-701 /etc/exports, 464-465 /etc/gettytab, 703-704 /etc/group, 703-704 /etc/master.passwd, 703-704 /etc/rc, 361-362, 700-701 /etc/rc.conf, 700-701 /etc/red, 700-702 /etc/ttys, 700-701 /kern, 312-313 /proc файловая система, 56,160-162, 312-313 /sys/kern/sched_4bsd.c, 124-125 /sys/kern/schedule.c, 124-125 /tmp, 193-194 /usr/sbin/config, 348-349 /var/quotas, 375 /var/run/lock, 466-467 2MSL таймер, 622-624 . См. также максимальное время жизни сегмента 3BSD, 20-21 файловая система, 428-429, 432-433, 436-438 4.0BSD, 20-23, 428-429 4.1BSD, 20-21,65 4.2BSD, 6,13-14, 20-23, 48, 52-53, 55, 59-60, 64-66, 84-85,173-174, 265-266, 280-282, 285, 4.3BSD, 5, 6,13-14, 20-23, 48, 84-85, 231-232, 254-255, 280, 596, 600-601, 706-707 выпуск Reno, 20-22 выпуск Tahoe, 20-24 файловая система, 294 4.4BSD, 5,13-14, 20-22, 28-29, 212, 596, 706-709 Lite, 6, 21-22, 28-29 NFS, 455-457 автоконфигурирование, 335-336 буферный кеш, 245-246 виртуальная память, 49-50 выгрузка (swap out), 237-238 дизайн mbuf, 523-524 замещение страниц, 229-230, 233-235 наращиваемая файловая система, 305-307 объект копирования, 17 пейджер подкачки (swap pager), 218-219 планировщик, 124-126 поддерживаемые архитектуры, 20-21 файловая система, 294, 444 реализация, 278-279 адреса версии 18, 598-599 групповой маршрутизатор, 611-612 демультиплексирование пакета, 601-602 заголовок протокола, 606-607 обработка ввода, 608-612 обработка вывода, 607-609 обработка широковещательных сооб- сообщений, 607-608 опции,606-607
756 Предметный указатель ответственность, 606-607 пересылка пакета, 609-612, 647-648 псевдозаголовок, 603, 605-606, 629 фрагментация, 597, 606-607, 608-610 А ABI. See application binary interface accept системный вызов, 516-517, 530-535, 548, 626-627, 654-655 определение, 515-516 access оператор vnode, 352-353 access системный вызов, 306-307 accton, 701-702 ACL. См. список контроля доступа ACPI. См. расширенный интерфейс конфигурирования и энергообеспечения ad_done(), 335-336 ad_start(), 334-335 adjtime системный вызов, 88 adstrategyO, 334-336 advlock оператор vnode, 352-353 AFINET, 514-515, 654-655 AFJNET6, 654-655 АН. См. заголовок аутентификации ahcactionO, 333-334 ahc_done(), 333-334 aioerror системный вызов, 278-280 aioread системный вызов, 278-279,313 aioreturn системный вызов, 280 aiosuspend системный вызов, 280 aiowaitcomplete системный вызов, 280 aiowrite системный вызов, 278-279, 313 Allman, Eric, 13-14 allocbuf(), 303-305 allocdirect структура, 392-399 allocindir структура, 393-394, 397-401 ANSI, 318-319 AOUT исполняемый формат, 82 API. См. интерфейс прикладного программирования APIC. См. расширенный программируемый контроллер прерываний Apple OS/X операционная система, 17, 356-357, 367 ARP. См. протокол разрешения адресов ARPANET, 20-21, 597 arpintr(), 592-593 arpresolve(), 592-593 AST. См. асинхронная системное прерывание AT&T, 21-24, 26-27 АТА диск, 41-42, 317-321, 333-336, 341-347 запрос ввода/вывода, 334-336 уровень, 333-336 АТА. См. АТА диск ata_completed(), 335-336 ata_finish(), 335-336 ata_interrupt(), 335-336 ata_start(), 334-335 atatransactionO, 334-335 ATAPI устройства, 320-322 ATAPI. Cm. ATAPI устройства ATTA CHED флаг, 392-393 определение, 392-393 В В язык программирования, 18 b_to_q(), 497-498, 500-501 Babaoglu, Ozalp, 20-21 Bach, Maurice, 7 bawrite(), 301-302 bcopyO, 253-254 BCPL, 18 bdwriteO, 301-302 Bell Laboratories, 17-19 Berkeley Software Design Inc., 26-28 bind системный вызов, 603, 654-655 определение, 515-516 biod, 466-467 biodone(), 333-336, 394-396 BIOS. См. базовая система ввода- вывода blkatoff оператор vnode, 423-424 bmsafemap структура, 393-401 Bolt Beranek и Newman, 65
Предметный указатель 757 boot, 685-690, 704-705, 708-709 /boot/device.hints, 338 /boot/kernel/kernel, 685-686 работа, 687-689 флаги, 687-688 bootjime, 480-482 BSD с открытым исходным кодом, 23-28 bqrelse(), 301-302 bread(), 270-271,301-305 brelse(), 301-302 bremfree(), 303-304 BSD, 325-326 открытый исходный код, 23-28 приобретение, 10 BSDI. См. Berkeley Software Design Inc. bss сегмент, 82 bufструктура,270-271 bufdaemon, 69, 699-700 bus_add_child(), 347-349 bus_child_detached(), 347-349 busdriveraddedO, 347-348 busprobenomatchO, 348-349 bus_read_ivar(), 348-349 bus_write_ivar(), 348-349 bwriteO, 270-271, 301-302 bzero(), 253-254 С С язык программирования, 17-18, 43-44, 73-74 САМ. См. метод общего доступа camisrO, 333-334 canrecurse флаг, 123-124 catq(), 497-498 ССВ. См. управляющий блок метода общего доступа CD9660 файловая система, 312-313 CDB. См. блок дескриптора команды cdevsw структура, 262-263, 265-266, 268-270 CD-ROM, 9-10,26,56,310-313,321-322, 331-332,334-335 chdir системный вызов, 58-59 chflags системный вызов, 360-361, 413-414 chkdqQ, 376-379, 435-436 chmod системный вызов, 59-60, 413-414 Chorus операционная система, 39 chown системный вызов, 59-60, 413-414 chroot системный вызов, 58-59, 153-157 CIDR. См. бесклассовая междоменная маршрутизация CIFS. См. общая файловая система Интернета CLEAN список буферов, 303-304, 313 close оператор vnode, 352-353 close системный вызов, 51-52,276-277, 280, 298-299, 305-306, 474-476, 490-491, 517-518, 541-543, 627-628 closedirO, 367 COMPLETE флаг, 392-401 определение, 392-393 Computer Systems Research Group, 6, 13-14,17,21-25,27-31,65 config, 237-238, 341-343 connect системный вызов, 515-516, 531-533, 534-535, 603-607, 624-625, 654-655, 671-672 определение, 515-516 coredumpO, 145 cpu_exitO, 138-139 cpu_switch(), 130-131 create оператор vnode, 351, 353-354 cron, 701-702 crypto_done(), 675-676 crypto_freesession(), 675-676 cryptoinvokeO, 675-676 crypto_newsession(), 674-676 cryptoprocO, 675-676 crypto_register(), 674-675 csh оболочка, 150-151 CSRG. Cm. Computer Systems Research Group CTSS операционная система, 18 cursig(), 142-143, 145 cv_broadcast(), 124-125 cvsignalO, 124-125 cvtimedwaitQ, 124-125
758 Предметный указатель cvtimedwaitsigO, 124-125 cv_wait(), 124-125 cv_wait_sigO, 124-125 cv_waitq_removeO, 124-125 С-блок, 496-498 С-спиок, 496-498, 500-503, 506-507 D dadone(), 333-334 DARPA. См. Defense Advanced Research Projects Agency dastartO, 332-333 dastrategy(), 332-334 data сегмент, 46-47, 82, 84-85, 207-208 расширение, 207-208 DCD. См. обнаружение несущей DCE. См. оборудование передачи данных Defense Advanced Research Projects Agency, 20-23, 65, 597 организационный комитет, 21-22 defrtrlistupdateO, 656-657 DEPCOMPLETE флаг, 392-402 определение, 392-393 dev_t, 322-323 devclass, 341-343 devd,348-349 device_attach(), 340-343 deviceidentifyO, 340-341 deviceprobeO, 340-341 device_t, 322-323 devinfo, 344-347, 349 df, 408-409 diradd структура, 394-396, 399-402, 404-405 DIRCHG флаг, 404-405 dirrem структура, 392-393, 403-407 DIRTY список буферов, 302-304,313 disksort(), 271-273, 332-335 алгоритм для, 271-272 DMA. См. непосредственный доступ к памяти DNS. См. система доменных имен doadump(), 705-706 dquot структура, 376-379 DTE. См. терминальное оборудование dtom(), 522-523, 524-526 DTR. См. готовность терминала dump, 267, 360-361, 422-423 действующий (live), 422-423 dumpsys(), 705-706 dup системный вызов, 53-54, 60-61, 276-278 реализация, 277-278 dup2 системный вызов, 53-54, 277-278 Е EAGAIN системная ошибка, 137-138, 277-278, 502-503, 537-538, 543-545 EINTR системная ошибка, 74-75, 110-111,137-138 ELF формат исполняемых файлов, 82 EIz, Robert, 23-24, 375 EMPTY список буферов, 303-304 errno, 43-44, 73-74, 504-505, 667-668 ESP. См. полезная нагрузка инкапсу- инкапсуляции безопасности (encapsulating- security payload) ether_demux(), 592-593 Ethernet, 20-21, 65, 554, 597 exec системный вызов, 44-45, 52-53, 85-86, 89-91, 96,101, 135-136,147-149, 160-162,178-179,201, exit системный вызов, 44-45,135-138, 204-205,210-211 работа, 137-138,210-211 состояние, 44-45, 104-105, 138-139 exit(), 137-138,145 extattrctl системный вызов, 413-414 F fchdir системный вызов, 154-155 fchflags системный вызов, 360-361, 413-414 fchmod системный вызов, 59-60, 413-414 fchown системный вызов, 59-60, 413-414 fcntl системный вызов, 22-23, 276-279, 501-502 fdesc файловая система, 312-313
Предметный указатель 759 Federal Information Processing Standard, 22-23 FFS. См. быстрая файловая система fhopen системный вызов, 413-414 fifo, 51-52, 274-275 find, 411 flock системный вызов, 457-458 fork системный вызов, 18, 44-45, 52-53, 60-61, 96,101,107-108, 113, 135-138, 147-149, free(), 50-51, 180, 254-255 freeblks структура, 392-396, 403-407 FreeBSD в качестве системы реального времени, 103-104, 131-132, 193-196 дизайн IPC, 512-513, 517-518 модель разработки, 29-32 переносимость, 40 цели, 33 ядро, деление программного обеспечения в, 42-43 freefile структура, 394-396, 404-407 freefrag структура, 394-397 fsck, 267, 272-273, 389, 410, 420-422, 428-429, 432-433, 700-702 зависимости, мягкие обновления, 410 фоновый, 420-422 fstat системный вызов, 59-60, 566-567 fsync зависимости, мягкие обновле- обновления, 406-408 fsync оператор vnode, 423-424 fsync системный вызов, 195-196, 292-293, 301-302, 313, 356-358, 387-389,399-401, 406-408, 413-414, ftp, 154-155 ftruncate системный вызов, 413-414 futimes системный вызов, 413-414 G gdown, 69, 328-329 g_up, 69, 328-329, 335-336 gdb, 159-160, 705-706 GENIE операционная система, 18 GEOM. См. уровень геометрии getattr оператор vnode, 352-353 getblkQ, 303-305 getc(), 496-498, 502-503 getdirentries системный вызов, 367 getfsstat системный вызов, 296-297 gethostbyname(), 654-655 gethostbyname2 определение биб- библиотечного вызова, 654-655 gethostbyname2(), 654-655 getlogin системный вызов, 703-704 getnewbuf(), 303-304 getnewvnode(), 297-298, 300 getpeername системный вызов, 517-518 getrusage системный вызов, 93-94 getsockname системный вызов, 517-518 getsockopt системный вызов, 517-518, 563-564, 567-568 gettimeofday системный вызов, 86-87 getty, 700-704 GID. См. идентификатор группы gsignalO, 141-142 Н handlewritteninodeblockO, 394-396 hardclockO, 78-80, 93-94,129-130 hardwarecachefetch, 225-227 Harris, Guy, 23-24 HBA. См. адаптер главной шины I ICMP. См. протокол управляющих сообщений Интернета icmperrorO, 647-648 icmpinputO, 646-647 IEEE. См. Institute of Electrical and Electronic Engineers IETF. См. проблемная группа проектирования Интернета (Internet Engineering Task Force) ifdata структура, 559-560 if_done(), 563-564 if_outputO, 562-563, 657-660 if_start(), 563-564 ifaddr структура, 557-558, 562-563, 579-580 ifconfig, 506 ifnet структура, 557-558, 562-563
760 Предметный указатель IGMP. См. протокол управления группами Интернета imgact, 82 in_pcballoc(), 603 in_pcbbind(), 603 in_pcbconnect(), 603, 624-625 in_pcbdetach(), 606-607 in_pcblookup(), работа, 605-606 inactive оператор vnode, 297-299, 352-353, 364-365 indirdep структура, 397-399 init, 44-45, 68-69, 109-110, 159-160, 232-233, 361-362, 693-694, 699-704 initiatewriteinodeblockO, 394-396 inode wait, 394-397, 403-405 inode, 290-291, 425-426, 448-449 выделение, 355-356 зависимости, мягкие обновления, 393-396 кеш, 363-365 локальность ссылки, 436-437 номер, 309-310, 357-358, 362-366, 368-369, 393-394, 399-401, 404-405, 415-416,458-460 определение, 353-363 содержание, 353-354 управление, 363-365 inodedep структура, 393-401, 406-407 inpcb структура, 601-602, 624-625 Institute of Electrical and Electronic Engineers, 21-22,320-321 Interdata 8/32, 19-20 ioctl оператор vnode, 352-353 ioctl системный вызов, 53-54,150-151, 275-276, 278-279, 489-493, 495,503- 505, 539-540, 557-558, 562-563, ioctl, символьное устройство, 269-270 iovec структура, 289-291 IP. См. Протокол Интернета ip_forward(), 672-673 ip_input(), 672-673 ip_output(), 604-608, 611-612, 634-635, 673-674 работа, 607-609 ip6_output(), 657-658 I PC. См. межпроцессное взаимодействие IPC_NOWAIT, 545-546 I PI. См. межпроцессорное прерывание ipintr(), работа, 608-612 IPSec, 557-558, 596, 607-612, 649-650, 659-666, 669, 671-679 заголовок аутентификации, 663-664 обзор, 660-662 протокол инкапсуляции безопасно- безопасности, 665-666 реализация, 671-674 ipseccommoninputO, 672-673 ipsec4_process_packet(), 673-674 IPv6, 64-65, 531-532, 596, 647-660, 663- 665, 669, 671-672, 677-679 автоконфигурирование, 654-660 адреса, 649-651 введение, 647-650 изменения API сокетов, 652-655 форматы пакетов, 651-653 IRQ. См. запрос прерывания ISA шина, 41-42, 318-322, 338-341, 348-349 ISA. См. ISA шина ISO. См. Международная организация по стандартизации ISP. См. провайдер служб Интернета issignal(), работа, 145 ITS операционная система, 21-22 J jail системный вызов, 155-157 jailkill системный вызов, 159-160 job, 91-92,150-151 Joy, William, 20-21 К КАМЕ, 65, 647-648, 660-661 KERNCONF, 703-704 kernfs файловая система, 312-313 kill системный вызов, 140-141 killpg системный вызов, 150-151 kmem_alloc(), 178-179 kmem_alloc_pageable(), 178-179 kmem_alloc_wait(), 178-179 kmemfreeO, 178-179 kmemfreewakeupO, 178-179 kmemmallocO, 178-179
Предметный указатель 761 kqueue, 274-276 ksecreate системный вызов, 106 ktrace системный вызов, 413-414 L LAN. См. локальная сеть Lawson, Nate, 11 Ibolt, 113, 500-501 Ichmod системный вызов, 413-414 Ichown системный вызов, 413-414 linesw структура, 489-490 link оператор vnode, 351 link системный вызов, 59-60, 413-414. См. также связи файловой системы (filesystem links) Linux операционная система, 5-7, 21-22,26,33,83,107-108 LISP язык программирования, 20-21 listen системный вызов, 515-516, 531-533,626-627 определение, 515-516 Lite, 4.4BSD, 6, 21-22, 28-29 Ilinfo_nd6, 658-659 ln_hold, 658-659 lock оператор vnode, 352-353 LOCKED список буферов, 302-303 lockinitO, 123-124 login, 89-90,361-362, 703-704 lookup оператор vnode, 295, 352-353 Iost+found,410 LRU. См. наиболее давний Is, 436-437 Iseek системный вызов, 52-53, 275-276 Istat системный вызов, 373-374 Iutimes системный вызов, 413-414 М m_adj(), 524-525 таНосядра, 180-183 реализация, 181-183 требования, 180-182 тсоруО, 634-635 m_copydata(), 524-525, 634-635 т_сорут(), 524-525 М_ЕХТ, 521-522 m_free(), 524-525 m_freem(), 524-525 m_get(), 524-525 m_gethdr(), 524-525 mhdr структура, 519-520 M_PKTHDR, 519-521 M_PREPEND(), 525-526 m_pullup(), 525-526, 605-606, 629 MAC. См. обязательный контроль доступа Mach операционная система, 21-22, 39, 49-50, 173-174, 199-200, 212, 217, 242-243, 251-252 Macklem, Rick, 463-464 make, 103-104 malloc(), 50-51,84-85,173-175,178-180, 207-208, 254-255, 304-305, 556-557 MAPSHARED, 300 Massachusetts Institute of Technology, 18,21-22 maxcontig, 444-445 maximumleaseterm, 476-477, 480-482 maxusers, 523-524 mb_alloc(), 524-525 MBR. См. главная загрузочная запись mbuf, 177-178, 519-524 алгоритм управления хранилищем, 523-525 вспомогательные процедуры, 524-526 выделение, 523-525 дизайн, 4.4BSD, 523-524 дизайн, 522-524 кластер, 519-526 описание структуры данных, 519-523 MFC. См. текущее mi startup(), 690-691 mi_switch(), 113-115,130-132 MIB. См. база управляющей информации MINIX операционная система, 21-22 mkdir оператор vnode, 351 mkdir системный вызов, 59-60, 66, 402,413-414 mkdir структура, 394-396, 400-402 MKDIR_BODY флаг, 400-401 MKDIR PARENT флаг, 400-401
762 Предметный указатель mkfifo системный вызов, 413-414 mknod оператор vnode, 351 mknod системный вызов, 413-414 mlock системный вызов, 193-196, 229-230, 251-252 определение, 193-194 mmap оператор vnode, 352-353 mmap системный вызов, 48, 50-51, 85-86,174-175,192-194, 202-203, 208-210, 213-214, 248-249,300 интерфейс, 192-196 определение, 192-193 MMU. См. блок управления памятью mount системный вызов, 56, 269-270, 305-306, 308-311, 464-466, 701-702 mount, 422-423, 465-466 опции монтирования, 296-297 mountd, 464-466, 471-472 mprotect системный вызов, 193-194, 209-210,250-251 определение, 193-194 MS-DOS операционная система, 456-458 msgrcv системный вызов, 545-547 msgsnd системный вызов, 545-546 MSL. См. максимальное время жизни сегмента msleepQ. См. sleep() msqmtx, 545-546 msync системный вызов, 195-196, 213-214,215-217 определение, 195-196 mtod(), 524-525 MTU. См. максимальный блок передачи mtxdestroyO, 122-123 mtx_init(), 121-123 mtxJockO, 122-123 mtx_trylock(), 122-123 mtxunlockO, 122-123 Multics операционная система, 18, 21-22 munlock системный вызов, 195-196 munmap системный вызов, 193-194, 197-198, 202-203, 208-209, 249-250 определение, 193-194 реализация, 208-210 N Nagle, John, 635-636 National Bureau of Standards, 21-22 nd_input(), 657-658 nd6_na_input(), 659-660 nd6_output(), 657-660 nd6_timer(), 659-660 NetBSD, 5,6,9-10,17, 25-26,28-30,295, 422-423 Network Disk Filesystem, 455-456 newblk структура, 393-394 newbus, 57-58, 321-322,335-336 newfs, 362-363, 429-430, 432-433 nextcO, 497-498 NFS. См. сетевая файловая система nfsd, 464-466, 468, 470-474, 482^183 nfsiod, 466-468 nfssvc системный вызов, 465-467 nice, 45-46, 92-93, 238-239 Not-Quite Network Filesystem, 464-467, 476-477, 479-484 Novell, 20-23 NQNFS. Cm. Not-Quite Network Filesystem NT файловая система, 59-60 NTP. См. протокол сетевого времени nullfs файловая система, 307-309 О O_ASYNC флаг, 277-278, 410-411 Olson, Arthur, 23-24 open оператор vnode, 352-353 open системный вызов, 51-54, 60-61, 213-214, 269-270, 275-276, 305-306, 352-354, 363-365, 372-374, 413^114, 490-491, 498-499, 517-518, 543-544 OpenBSD, 5, 6,10,17, 26, 29-30, 660-661, 670-671, 673-674 opendirf), 367 OS/X операционная система, Apple, 17, 356-357, 367 OSF/1, 107-108
Предметный указатель 763 OSL См. Международная организа- организация по стандартизации Р pagedaemon, 69, 104-105, 178-179, 183-184, 188-189, 212-214, 215-217, 219-221, 229-238, 251-252-255, 413-414, 699-700 работа, 232-237 pagedep структура, 399-401, 403-407 pagein() работа, 221-225 pagezero, 69, 699-700 PC. См. персональный компьютер PCL См. соединение периферических компонентов PCMCIA, 317-318 PDP-11,19-20, 73-74, 101 PDP-7,17, 101 PF_KEY основной заголовок, 667-668 расширение адреса, 669 расширение связи, 667-668 PF_KEY_V2, 665-666 pfctlinputO, 646-647 pgo_alloc(), 213-214 pgo_dealloc(), 213-214 pgogetpageO, 217 pgo_getpages(), 213-216 pgo_haspage(), 213-214, 217 pgoinitO, 213-214 pgo_putpages(), 213-217, 219-220 physioO, 267, 269-270 PID. См. идентификатор процесса ping, 159-160, 588-589, 646-647 Plan 9,19-20 pmap, 242-255 инициализация, 245-246 модуль, 175-176, 242-245, 254-255 структура, 175-176 функции,244-245 pmap_bootstrap(), 244-246 pmap_change_wiring(), 244-245, 251-252 pmapclearmodifyO, 244-245, 251-252 pmapclearptesQ, 251-252 pmap_dear_reference(), 244-245, 251-252 pmap_copy_page(), 244-245, 253-254 pmap_enter(), 244-245, 248-251 реализация, 248-250 pmapgrowkernelO, 244-246 pmap_init(), 244-246 pmap_is_modifiedO, 244-245, 253-254 pmap_page_protect(), 244-245, 250-252 pmap_pinit(), 244-245, 254-255 pmapprotectO, 244-245, 250-252 pmap_qenter(), 244-245, 250-251 pmapqremoveO, 244-245, 250-251 pmap_release(), 244-245, 254-255 pmapremoveO, 244-245, 249-254 реализация, 249-251 pmap_remove_all(), 251-252 pmaptsreferencedO, 244-245, 251-254 pmap_zero_page(), 244-245, 253-254 poll оператор vnode, 352-353 poll системный вызов, 269-270, 283-287, 490-491 pollfd структура, 285 portal файловая система, 296-297, 312-313 portmap, 464-466 POSIX. См. интерфейс переносимой операционной системы postsigO, 142-143, 145-147 работа, 145-147 ррр,506 РРР См. протокол двухточечного соединения pr_ctlinput(), 556-557, 570-571, 583-584, 605-606, 646-647 prctloutputO, 556-557, 563-564, 567-568, 605-606 pr_drain(), 554-555 prfasttimoO, 554-555 pr_input(), 556-557, 568-570 pr_output(), 556-557, 568-569 prjpfilO, 554-555 pr_slowtimo(), 554-555 pr_usrreqs(), 556-557, 563-565, 567-568 prison структура, 156-157
764 Предметный указатель procfs файловая система, 312-313 profclockO, 78-79, 88 profit системный вызов, 98 protosw структура, 526-527 pru_abort(), 566-567 pruacceptO, 565-566 pru_attach(), 563-565 prubindO, 563-565 pruconnectO, 563-565 pru_connect2(), 566-567 pru_control(), 566-567 pru_detach(), 564-566 prudisconnectO, 565-566 pru_fasttimo(), 566-567 pruJistenO, 564-565 pru_peeraddr(), 566-567 pru_rcvd(), 565-566 pru_rcvoob(), 566-567 prusendO, 563-566 prusenseO, 566-567 pru_shutdown(), 565-566 pru_slowtimo(), 566-567 prusockaddrO, 566-567 ps, 106-107, 110-111 psstrings структура, 85-86 psignal(), 141-142, 144-145 работа, 144-145 PSL. См. длинное слово состояния процессора РТЕ. См. элемент таблицы страниц ptrace системный вызов, 117-118, 159-162 putc(), 497-498 pventry структура, 243-253-254, 255 Q q_to_b(), 497-498 quota.group, 375 quota.user, 375 quotacheck, 378-379 quotactl системный вызов, 413-414 R RAID. См. избыточный массив недорогих дисков read оператор vnode, 422-424 read системный вызов, 49-52, 55-56, 64, 161-162, 275-276, 286-287, 290-291, 300, 305-306, 313, 489-490, 501-504, 517-518,535-536 READ10, 332-333 readdir оператор vnode, 352-353 readdir(), 367 readlink оператор vnode, 352-353 readv системный вызов, 55-56, 289-290 reboot системный вызов, 704-705, 708-709 работа, 704-706 reclaim оператор vnode, 297-299, 352-353, 364-365 recv системный вызов, 55-56 recvfrom системный вызов, 55-56, 535-536, 654-655 recvitO, 535-536 recvmsg системный вызов, 55, 535-536, 541 структуры данных для, 516-517 remove оператор vnode, 351 rename оператор vnode, 351 rename системный вызов, 59-60, 413-414, 461-462 добавление, 59-60 resetpriority(), 129-130,131-132 revoke системный вызов, 298-299, 413-414, 495, 504-505, 701-702 rewinddir(), 367 RFC. См. запрос для комментариев rfork системный вызов, 106-107,135-136 RFS. См. удаленная файловая система rip_input(), 646-647 Ritchie, Dennis, 17-18, 21-22 rlimit структура, 107-108 rm, 408-409 rmdir оператор vnode, 352-353 rmdir системный вызов, 59-60, 402, 407-408,413-414 root пользователь, 88 rootO, 321-322, 339-347 roundrobin(), 128-129, 131-132 routed, 584-585 RPC. См. удаленный вызов процедур
Предметный указатель 765 rpc.lockd, 462-467 rpc.statd, 464-467 RS-232 линия последовательной передачи, 487-488, 497-498 rtalloc(), 583-584 rtalloc ign(), 608-609 rtentry, 658-659 структура, 577-578, 607-608 rtfree, 583-584 rtprio системный вызов, 109-110 rtredirect(), 584-585, 646-647 RTT. См. время обращения runq_add(), 130-131 runq_choose(), 130-131 работа, 130-131 runq_remove(), 130-131 S SA. См. ассоциация безопасности Samba, 455-456 Santa Cruz Operation, 5, 20-21 savecore, 705-706 sbappendstreamO, 632-634 sblock(), 537-538 sbrk системный вызов, 84-85,173-175, 202-203, 207-208 sbunlockO, 537-538 SC22WG15, 22-23 sched_balance(), 134-135 schedlock, 115-116 schedcpuO, 128-129, 131-132 schednetisr(), 573-574 scheduled), 237-238 SCO. Cm. Santa Cruz Operation SCSI. См. интерфейс малых компьютерных систем seekdir(), 367 select системный вызов, 269-270, 283-289, 313, 529, 626-627 код драйвера устройства для, 288-289 мотивация для, 282-287 поддержка драйвера устройства для, 269-270, 286-288 реализация, 286-290 selinfo структура, 287-290 selrecord(), 287-290 seltrue(), 269-270 selwait, 287-289 selwakeup(), 286-289, 501-502 semctl системный вызов, 549 semget системный вызов, 544-545, 549 semop системный вызов, 544-545, 549 send системный вызов, 55-56, 64, 527-528, 667-668 sendfile системный вызов, 50-51 sendit(), 535-536 sendmsg системный вызов, 55, 535-536, 563-564, 603 структуры данных для, 516-517 sendsigO, 146-147 sendto системный вызов, 55-56,535-536, 563-564,603, 654-655 setattr оператор vnode, 352-353 seteuid системный вызов, 91-92 setlogin системный вызов, 703-704 setpgid системный вызов, 147-149 setpriority системный вызов setrunnable(), ИЗ, 129-130,131-132, 144 setsid системный вызов, 148-149 setsockopt системный вызов, 517-518, 542-543, 563-564, 567-568, 600-601, 604-605, 637-638 settimeofday системный вызов, 86-87 sh оболочка, 83, 700-701 shmat системный вызов, 546-547 shmdt системный вызов, 546-547, 549 shmem системный вызов, 192-193, 546-547 shmget системный вызов, 546-547 shutdown системный вызов, 517-518, 539-540, 627-628 SI_ORDER_FIRST, 692-693 SI_ORDER_SECOND, 692-693 sigaction системный вызов, 140-141, 145 SIGALRM, 88 sigaltstack системный вызов, 140-141 SIGCHLD, 144, 148-149,160-161 S1GCONT, 140-141, 144-145 SIGHUP, 152, 495, 504-505
766 Предметный указатель SIGINT, 91-92 SIGIO, 277-278, 501-502, 527-528 SIGKILL, 45-46, 140-141, 145 sigpause системный вызов, 115-116 sigprocmask системный вызов, 140-141 SIGPROF, 88, 98 sigreturn системный вызов, 141-142, 146-147 SIGSTOP, 45-46, 140-141, 161-162 sigsuspend системный вызов, 140-141 sigtramp(), 146-147 SIGTRAP, 160-162 SIGTSTP, 163, 502-503 SIGTTIN, 150-152, 502-503 SIGTTOU, 144, 150-152, 499-500 SIGURG, 527-528 SIGVTALRM, 88 SIGWINCH, 493-494 sleepO, 109-110, 113-115, 128-129, 130-131,140-143, 232-233, 263-264, 534-535 использование sleepQ, 109-110, 113 прерываемый, 109-110, 142-143 работа, 115-116 реализация, 109-110, 113-114 SMP. См. симметричная мно- многопроцессорная обработка SMT. См. симметричная много- поточность SOJLINGER, 542-543 socantrcvmoreO, 632-633 SOCKSTREAM, 514-515 sockaddr_dl, 558-559 sockaddrin, 654-655 sockaddr_in6, 654-655 socket системный вызов, 22-23, 51-54, 64-65, 275-276, 514-516, 525-526, 531-533, 563-564, 567-568 определение, 514-515 socketpair системный вызов, 566-567 soconnect(), 534-535 softclock(), 78-81,88 softdep_disk_io_initiation(), 394-396 softdep_disk_write_complete(), 394-396 softdepupdateinodeblockQ, 394-396 sohasoutofband(), 632-633 soisconnected(), 534-535 soisconnectingO, 624-625 soisdisconnected(), 632-633 solisten(), 531-533 sonewconnO, 531-533, 626-627 soreceiveO, 464-465, 537-541, 548 sorwakeupO, 539-540 sosend(), 464-465, 535-538, 548, 633-634, 677 Spec 1170, 22-23 SPI. См. индекс параметра безопасно- безопасности ssh, 63, 487-488, 701-702 stat системный вызов, 59-60, 297-298, 305-306, 360-361, 368-369 stat структура, 275-276,360-361, 566-567 statclockO, 78-79, 93-94 statfs системный вызов, 296-297 strategy оператор vnode, 394-396 strategyO, 304-305, 331-335 STREAMS, 285, 568-569 su, 361-362 Sun Microsystems, 23-24, 62, 290-293, 444, 455-456, 458-460, 463-464, 466-467, 470^171, 493-494 swapinO, 116-117 работа, 237-239 swapoff, 219-220 swapper, 69, 237-239, 693-694, 699-700 swinet, 553-554 swi_sched(), 131-132 swp_pager_async_iodone(), 219-220 symlink оператор vnode, 351 symlink системный вызов, 413-414 sync системный вызов, 292-293, 413-414 syncache, 626-627 syncer, 69, 313, 390-391, 699-700 syscallO, 72-73 sysctl системный вызов, 232-233, 310-311, 562-563, 611-612, 706-709 реализация, 706-709 syslogd, 701-702 System V, 5-7, 26-27, 107-108, 697-698 блокировка диапазона, 280
Предметный указатель 767 интерфейс опроса, 285 очередь сообщений, 511, 545-547 разделяемая память, 192-193, 215-216,546-547 семафоры, 542-545, 698-699 терминальный драйвер, 490-491 Т tasklist, 390-391, 396-397, 403-405 ТСВ. См. управляющий блок потока TCP. См. протокол управления передачей tcp_attach(), 624-625 tcp_close(), 627-628 tcp_connect(), 624-625 tcp_ctloutput(), 637-638 tcpdelackO, 632-633 tcphcgetO, 625-626 tcphcpurgeO,625-626 tcpinputO, 620, 630-633, 644-646 работа, 629-633 tcpoutputO, 620, 624-625, 630-635, 637-639 работа, 634-635 tcpsIovvtimoO, 621 tcpusrsendO, 620, 633-634 tcp_usr_shutdownO, 627-628 tcpcb структура, 601-602, 624-625 tcsetattr системный вызов tcsetpgrpO, 150-151 TDF_NEEDRESCHED, 131-132, 145 telldirO, 367 telnet, 63, 487-488 TENEX операционная система, 21-22 termios структура, 490-491 text сегмент, 46-47, 82, 84-85 . См. также разделяемый сегмент text Thompson, Ken, 17-18, 21-22, 39 thread_exit(), 137-138 timeout(), 79-81 TLB. См. буфер быстрого преобразова- преобразования адреса TOPS-20 операционная система, 21-22 Toy Story, 10 trap(), 72-73 truncate оператор vnode, 423-424 truncate системный вызов, 60-61, 408-409,413-414 добавление, 60-61 T-shirt, демон, 10 ttioctlO, 503-505 ttread(), 502-503 ttselect(), 269-270 ttstart(), 500-501, 506 ttwakeup(), 501-502 ttwriteO, 499-503 tty driver, 489-490 tty драйвер. См. драйвер терминала tty структура, 493-494 ttydoseO, 506 ttyinput(), 501-503 ttylcloseO, 504-505 ttymodem(), 504-505 ttyoutput(), 500-501 ttypollO, 489-490 Tunis операционная система, 21-22, 39 U UDP. См. протокол пользовательских дейтаграмм udpappendO, 605-606 udpattachO, 603 udp_bind(), 603 udp_detach(), 606-607 udp_input(), 605-606 udpoutputO, 604-605 udp_send(), 604-605 UFS, 369-370, 384-385, 431 ufs_bmap0, 434-435, 445 UFS1, 355-357, 360-363, 369-370, 428-433, 446-447 UFS2, 355-357, 360-363, 369-370, 407-409, 429-433, 446-447 UID. См. идентификатор пользователя uio структура, 267-268, 289-291, 423-424, 499-500, 502-504 uiomove(), 269-270, 290-291, 500-501 реализация, 289-291 ULE планировщик, 124-126, 131-136 uma_zalloc(), 183-184 umazcreateQ, 183-184
768 Предметный указатель uma_zfree(), 183-184 uma_zone_set_max(), 183-184 umapfs файловая система, 308-309, 471-472 undelete системный вызов, 310-311, 413-414 union файловая система, 308-311 UNIX 32V, 19-21 System III, 19-22 System V, 19-22 System V, Release 17, 20-21 Группа поддержки, 19-21 история, 17-22 объединенная файловая система, 454 Руководство программиста, 18 системная лаборатория8у51ет Labora- Laboratory, 20-21, 26-28 unlink системный вызов, 59-60, 408-409, 413^14, 461^62 unlock оператор vnode, 352-354 unmount, 407-408 unmount системный вызов, 305-306, 413-414 unputc(), 497-498 update оператор vnode, 394-397,399-401, 403^105,422^123 updatepriO, 129-130 ureadcO, 502-503 USB. См. универсальная последова- последовательная шина USENET, 23-24, 446-447 USL. См. системная лаборатория UNIX UTC. См. Универсальное скоординированное время utimes системный вызов, 360-361, 413-414 V V Kernel операционная система, 39 valloc оператор файловой системы, 422-423 VAX, 19-21 аппаратура управления памятью, 48 vfork системный вызов, 107-108, 135-136, 147-148, 204-205, 254-255 проблемы реализации, 204-205 работа, 204-205 см. также создание процесса vfree оператор файловой системы, 422-423 vfs.usermount, 310-311 vget оператор файловой системы, 422-423 vgoneO, 298-299 vm_daemon(), 69, 237-238 vm_fault(), 93-94, 221-223, 242-243, 251-252 vm_forkproc(), 137-138 vmmap структура, 175-176, 177-179, 243-244 vmmapentry структура, 175-176, 177-179, 182-183, 185-189, 195-198, 201, 203-204, 206-211, 221, 248-249, 250-251, 255 vmobject структура, 175-177, 291-292 vmpage структура, 176-177, 186-188, 190, 212-216, 228-229, 245-246, 249-250, 253-254 vm_page_alloc(), 231-232 vm_page_io_finish(), 219-220 vmpagestartupO, 245-246 vm_page_test_dirty(), 251-254 vm_pageout(), 232-233 vm_pageout_scan(), 232-235 VMS операционная система, 21-23, 229-230 замещение страниц в, 229-230 vmspace структура, 175-176, 185-186, 201, 204-207 vmspace_exec(), 254-255 vmspace_fork(), 254-255 vmspace_free(), 254-255 vnlru, 69, 699-700 vnode, 56, 274-275, 290-291, 527-528 кеш, 190 операции, 292-295 описание, 291-295
Предметный указатель 769 vnode_pager_setsize(), 213-214 vopaccessargs структура, 307-308 W wait системный вызов, 44-45, 93-94, 101, 108-109,113-114, 147-148, 161-162,204-205,210-211 wait4 системный вызов, 44-45, 137-138,160-161 работа, 138-139 wakeup(), 115-117,129-130, 131-132, 134-135, 219-220 работа, 116-117 реализация, 116-117 wakeup_one(), 116-117 Windows операционная система, 6 worklist структура, 390-391, 393-394 write оператор vnode, 423-424 write системный вызов, 43-44, 49-56, 64, 161-162, 275-276, 283-284, 286-287, 290-291, 313, 375, 413-414, 434-436, 468, 474-476, 489-490, 500-501, 517-518, 527-528, 535-536, 667-668 writeslack, 476-477, 479-483 writev системный вызов, 55-57, 289-290 X Х.25, 571-572 X/OPEN, 5, 20-23 XDR. См. представление внешних данных Xerox NS протоколы, 596 xform-switch структура, 672-674 XINU операционная система, 21-22 xpt_action(), 332-334 xptdoneO, 333-334 xpt_schedule(), 332-333 XPTSCSIJO, 332-333 xterm, 63, 487-488 Z zalloc(), 50-51 zalloc, 182-184 zfreeQ, 50-51 абсолютный путь, 58-59 аварийный дамп, 111-112, 263-264, 270-271,704-707 автоконфигурирование, 335-336, 648-649, 654-655, 695-696 4.4BSD, 335-336 IPv6, 654-660 вклад, 22-23 поддержка драйверов устройств для, 263-264, 337-349 ресурс, 344-349 структуры данных, 341-347 фаза, 338 автономный библиотека ввода/вывода, 687-688 драйвер устройства, 687-688 программа, 687-689 адаптер главной шины, 318-319,331-332 административный контроль, 152-160 адрес сокета, 530-532 адреса Интернета групповые, 600-601 демультиплексирование пакетов, 600-601 структура, 531-532 широковещательные, 599-601 адреса, IPv6, 649-651 адресное пространство. См. виртуаль- виртуальное адресное пространство активный процесс, 149-151, 495 алгоритм TCP, 620-628 для disksort(), 271-272 для физического ввода/вывода, 268 медленного старта TCP, 638-643 управления памятью mbuf storage- management, 523-525 элеваторной сортировки, 271-272 алгоритм глобального замещения страниц, 229-230 алгоритм долговременного планирования, 127-128 алгоритм кратковременного планирования, 127-128
770 Предметный указатель алгоритм локального замещения страниц, 229-230 алгоритм медленного старта, TCP, 638-643 алгоритм управления хранилищем, mbuf, 523-525 анонимный объект, 185-186, 188-189 аргументы, маршалинг, 457-458 архитектура PC,316-319 асимметричная криптография, 673-674 асинхронное системное прерывание, 131-132 асинхронный ввод/вывод, 277-280 ассемблера язык в ядре, 42-43, 73-74, 130-131, 264-265, 687-691, 692-694 атака отказа в обслуживании, 626-627 атрибута обновление, файлового хранилища, 422-423 атрибутами манипулирование, файло- файловой системы, 352-353 аутентификации данные, 664-665 аутентификации заголовок, 652-653, 662-666, 670-671 аутентификация Kerberos, 465-466, 471-474 Б база управляющей информации, 707-708 базисное дерево поиска, 579-580 базовая система ввода-вывода, 272-273, 318-319, 687-689 PNP, 318-319 базовые службы, 690-693 безопасность, 659-676 ассоциация, 660-662, 664-673 введение, 659-661 индекс параметра, 660-665, 669, 672-673 проблемы, NFS, 471-474 протоколы, 663-666 реализация протоколов, 671-674 система, 152-160 соединение, режим транспортировки, 662 соединение, туннельный режим, 662 уровень, ядро, 361-362 безопасность системы, 152-160 безопасный режим, 361-362 бесклассовая междоменная маршрутизация, 598-600, 647-648, 651 библиотека С, 86-87 системные вызовы в, 73-74 библиотека, совместно используемая, 85-86 битовые карты зависимостей, мягкие обновления,393-394 блок дескриптора команды, 331-332 блок серверных сообщений, 455-456 блок управления памятью, 168,224-225, 239-241,244-246, 251-252 дизайн, 238-241 блок файла выделение, FFS, 435-436, 439-443 запись, 434-436 локальность ссылки, 438 чтение, 433-434 блокировка буфера данных сокета, 537-538 дескриптора файла, 280-283 необязательная, 281-282, 352-353 обязательная, 281-282 ресурсов на многопроцессорной сис- системе с разделяемой памятью, 117-124, 531-533 ресурсов при избегании взаимо- взаимоблокировки, 118-121, 543-545, 549 семантика, файла, 378-381 блокировка диапазона, System V, 280 блокировки файла, 276-277, 280-283, 378-385 NFS, 457-458 реализация, 281-283, 380-385 семантика, 378-381 блочная кластеризация, 425-426, 434-436, 442-447 блочный ввод/вывод, 425-428 буфер быстрого преобразования адреса, 239-241, 248-254 буфер ввода/вывода, 270-271 буферирование политика, протокол, 586-587 сети, 586-589
Предметный указатель 771 терминала, 496-498 файловой системы, 425-428 буферный кеш, 355-356, 425-428 4.4BSD, 245-246 выделение памяти, 304-305 интерфейс, 301-302 реализация, 303-305 согласованность, 304-305 структура, 302-304 управление, 300-305 эффективность, 300 быстрая повторная отправка, TCP, 643-646 быстрая файловая система, 422-423, 426-449 ffs_balloc(), 435-436, 439-442 ffs_read(), 433-435, 445 ffs_realloccg(), 440-442 ffs_write(), 435-436 выделение блока файла, 435-436, 439-443 выделение фрагмента, 441-443 группа цилиндров, 429-431 карта кластеров, 445 оптимизация хранения, 432-436 организация, 428-433 параметрирование, 436-437 пересмотр, 426-434 политики размещения, 436-438 процедуры локального распределения, 439-440 расширение блока файла, 440-441 реализация, 428-434, 436-447 резерв свободного пространства, 362-363, 435-436, 448-449 структура диска, 428-433 таблица дескрипторов фрагментов, 441-442 файловый ввод/вывод, 433-436 фрагментация, 432-436 быстрое повторное использование соединения,631-632 В ввод/вывод разбрасывания- собирания, 55-56, 66, 535-536 верхняя половина, 70 драйвера терминала, 489-490 драйвера устройства, 263-264 ядра, 70-71 вино, 13-14 виртуальная память, 20-21 4.4BSD, 49-50 аппаратные требования для, 172-174 выделение отображения, 248-251 вычисление использования, 202-204, 207-208 для многопроцессорной системы с разделением времени, 48 дублирование, процесс, 203-205 зависимости от машины, 238-255 защита от изменений, 209-210 защита отображения, 250-252 изменение размера, 207-208 инициализация, 244-249, 253-254 интерфейс, 4.2BSD, 21-22 манипулирование, 207-210 обзор, 173-177 объект, 186-192 отображения, 177-179 переносимость реализации, 238-255 преимущества, 172 ресурсы, процесс, 185-192 семафоры, 191-193 согласованность кеша, 214-215 структуры данных, 174-177 схема, 173-175 виртуальная частная сеть, 662 владение кешем записи, 477-479 владение кешем чтения, 477-478 внеполосные данные, 537-540, 566-567, 590-592 передача, 535-536 получение, 539-540 возвращение из системного вызова, 74-75 возвращение из ядра, 73-74 восстановление из запаса, 233-235 время, 77-79, 86-87 виртуальное процесса, 88 дня, 69 интервал, 88 квант, 126 представление, 86-87 регистр времени дня, 86-87
772 Предметный указатель синхронизация, сеть, 86-87 слайс, 103-104, 126 стабильный идентификатор, 461-462 часов, 86-87 время обращения, 470-471, 473-475, 623-624, 677 оценка TCP, 623-625 тайм-аут RPC, 470-471 время часов, 86-87 выгрузка страницы, 219-220 выделение памяти буферный кеш, 304-305 ядро, 50-51 выпуск Netl, 21-22 выпуск Net2, 21-22 вытеснение потока, 131-132 ядра, 71-72 ввод/вывод асинхронный, 277-280 виды ядра, 261-264 запрос, АТА, 334-336 запрос, САМ, 331-334 не блокирующий, 277-278, 283-284, 498-499, 504-505, 534-537, 539-540 очередь, 263-265 перенаправление, 52-53 разбросанный (scatter/gather), 55-57, 66, 535-536 системный дизайн, 51-56 управляемый сигналами, 277-278, 283-284 физически, 267-268 вектор ввода/вывода, 289-291 верхняя граница сокета, 529, 536-537, 586-587 терминала, 499-500 взаимоблокировка памяти, 202-203, 218-221,236-238 взаимоблокировка (тупик), 244-245 избежание в ходе системного вызова fork, 203-204 избежание при блокировании ресурсов, 118-121, 543-545, 549 моментальный снимок, 417-419 определение, 379-381, 449 память, 202-203, 218-221, 236-238 сети, 282-284 владение NFS, 464-465,476-482 кеширования записью, 477-479 кеширования чтения, 477-478 не кеширующее, 477-479 получение, NFS, 480-481 внешнее представление данных, 458-460 восстановление после сбоя, NFS, 480-483 Восьмая редакция UNIX, 19-20 время интервала,88 вторичное хранилище, 166 вход в систему, 69-70 вход в ядро, 71-72 входящий, 553-554 выделение inode, 355-356 mbuf, 523-525 адресного пространства ядра, 178-184 блока файла FFS, 435-436, 439-443 дескриптора, 534-535 идентификатора процесса, 136-137 отображения виртуальной памяти, 248-251 памяти ядра, 50-51 пространства каталога, 366-367 ресурса ядра, 202-204 фрагмента FFS, 441-443 выключение системы, 704-706 Г гибкая ссылка, 372. См. также сим- символическая ссылка главная загрузочная запись, 323-327 глобальная таблица vnode, преимуще- преимущества, 297-298 готовность терминала, 497-499, 504-505 Гринвичское время. См. Универсаль- Универсальное скоординированное врея группа процесса, 46-47, 91-92,147-149, 150-151 идентификатор, 147-148, 277-278, 527-528 иерархия, 108-109 использование управлением зада- заданиями, 46-47 лидер, 147-148
Предметный указатель 773 покинутый,152 связывание с сокетом, 150—151, 527—528 терминал, 150-151,495,501-505 группа процессов терминала, 150-151, 495, 501-505 д двоичный интерфейс приложения, 335-338 двойной косвенный блок, 355-356 Девятая редакция1Л\1Х, 19-20 действительный GID. См. действи- действительный идентификатор группы действительный UID. См. действитель- действительный идентификатор пользователя действительный идентификатор поль- пользователя, 89 дежурный пакет, 622-623 дежурный таймер, 622-623 демон NFS, 464-468 маршрутизации, 584-585 процесс, 282-283 тенниска, 10 демон сбрасывания страниц. См. pagedaemon дескриптор, 51-52 выделение, 534-535 дублирование, 277-279 использование, 51-53 мультиплексирование, 282-287 таблица, 52-53, 274-275 управление, 52-54, 274-279 Десятая редакция UNIX, 19-20 деятельность системы, 72-73 дизайн 4.2BSD IPC, 22-23 FreeBSD IPC, 512-513,517-518 mbuf, 522-524 NFS, 456-458 сети, 65 системы ввода/вывода, 51-56 управления памятью, 48-50 динамический dump, 422-423 диск SCSI, 331--332 интерфейс устройства, 270-271 метка, 272-273 операции устройства, 270-271 подсистема, 318-322 раздел, 263-264, 272-273, 423-424 структура, FFS, 428-433 устройство, 270-273 дисциплина линии связи, 489-491, 498-499, 506 close(), 504-506 output(), 499-501 РРР, 506 длинное слово состояния процессора, 71-74 добавочный заголовок, 651-652, 667-668 домашний каталог, 58-59 домен Интернета, 20-21, 64 домен. См. коммуникационный домен демультиплексирование пакетов IP, 601-602 адреса Интернет, 600-601 добровольное переключение контек- контекста, 113-118 драйвер терминала, 489-491 close(), 504-506 ioctl(), 490-493, 503-505 open(), 498-499 System V, 490-491 аппаратное состояние, 493-494 верхняя половина, 489-490 интерфейс пользователя, 21—22, 490- 493 нижняя половина, 489-490 очереди данных, 493-494, 496-500, 502-504 переходы модема, 504-505 программное состояние, 493-494 размер окна, 493-494, 498-499 режимы, 488-490, 493-494, 501-502 специальные символы, 488-489, 493-494 управление модемом, 497-499 функции,490-493 драйвер устройства, 53-54, 261-264 верхняя половина, 263-264 код для системного вызова select, 288-289
774 Предметный указатель максимальный размер передачи, 265-266 нижняя половина, 263-264 обработка прерываний, 264—265 поддержка автоконфигурирования, 263-264, 337-349 поддержка системного вызова select, 269-270, 286-288 процедура зондирования, 340-341 процедура присоединения, 339-343 разделы, 263-264 дублирование виртуальной памяти процесса, 203-205 Ж жесткий лимит, 94-95 заблокированная файловая система, 298-299 зависимости восстановления, мягкие обновления,404-407 зависимости косвенных блоков, мяг- мягкие обновления,397-401 зависимости прямого блока, мягкие обновления,394-397 зависимости, машина виртуальной памяти, 238-255 зависимости, мягкие обновления, 385-391 зависимости усечения, мягкие обнов- обновления, 403-405 зависимости числа ссылок, мягкие обновления,407-410 зависимость обновления, 386-387 заголовок exec, 82 закрывание-при-исполнении (close- оп-ехес), 276-279 замещение страницы, 20-21,169-171, 228-237 4.4BSD, 229-230, 233-235 в операционной системе VMS, 229-230 критерии для, 228—232 заполнение пулями стека пользова- пользователя, 84-85 запрос для комментариев, 627-628, 647-650, 660-661 запрос опознавания, 566-567 запрос прослушивания, 564-565 запрос соединения,564-565 запрос управления, 566-567 запуск потоков ядра, 699-701 запуск системы, 685-688 начальное состояние, 689-690 сценарии, 700-702 зарезервированная страница, 178-179, 215-216, 242-246, 251-252 определение, 178-179 список, 229-230 защита, отображение виртуальной памяти, 250-252 значение проверки целостности, 664-665 зондирование, 338 зона, красная, 50-51,180 зональный распределитель, 182-184 зондирование окна, 622-623 И игнорируемый сигнал, 45-46 идемпотентный, 458-460 идентификатор группы, 88-91, 96,158, 308-309, 471-472 использование при проверке доступа к файлу, 89 идентификатор пользователя, 88-91, 96, 156-158, 307-309, 457-458, 471-474 использование при проверке доступа к файлу, 89 идентификатор процесса, 43-44,92-93, 104-108, 135-138, 147-149, 162-163, 201, 495, 585-586 выделение, 136-137 идентификатор хоста, 91-92 идентификация устройства, 339-343 идентичность, 338 избыточный массив недорогих дисков, 316-317, 320-321, 323-326 инициализация системы на уровне пользователя, 700-704 именование разделяемая память, 192-194 файловая система, 364-374 именованный объект, 188-189
Предметный указатель 775 имя поиск, файловая система, 368-371 регистрации, 92-93 создание, файловая система, 351 трансляция, файловая система, 58-59, 370-372 удаление, файловая система, 351 имя файла, 57-58 whiteout, 309-310 кеш, 298-300 отрицательное кеширование, 300 имя пути, 58-59 преобразование, 295-297 имя хоста, 91-92 индивидуальная память, 195-201 индивидуальное отображение, 192-193, 195-197 инвертированная таблица страниц, 239-240 инициализация виртуальной памяти, 244-249, 253-254 системы уровня пользователя, 700-704 см. также начальная загрузка файловой системы, 701-702 ядра, 688-690 инкапсуляция, 552-553, 557-558 интерактивная программа, 103-104 интерпретатор, 82 интерфейс mmap системный вызов, 192-196 адреса сети, 557-560 буферный кеш, 301-302 виртуальной файловой системы, 290-297 возможности сети, 559-563 вывода сети, 562-564 дискового устройства, 270-271 коммутатора линии, 489-490 пейджера, 212-221 протокол-протокол, 567-571 протокол-сеть, 570-574 сети, 557-564 символьного устройства, 261-263, 265-266,268-271,489-490 сокет-протокол, 563-568 интерфейс виртуальной файловой системы, 290-297 интерфейс коммутации канала (line switch interface), 489-490 интерфейс небольших компьютерных систем, 317-322,331-334, 339-343 диск, 331-332 шина, 338 интерфейс опроса, System V, 285 интерфейс сокет-протокол, 563-568 интерфейс переносимой операцион- операционной системы, 6, 6, 21-23, 91-92,152, 191-192, 278-282, 378-379, 389, 490-492 интерфейс прикладного программирования, 64, 335-336 интерфейс символьного устройства, 261-263, 265-266, 268-271, 489-490 исключение точки останова, 160-161 исключение трассировки, 160-161 использование дескриптора, 51-53 использование страницы, 251-254 история UNIX, 17-22 удаленных файловых систем, 454-457 управления заданиями, 21-22 управления процессом, 101 К Калифорнийский университет в Беркли,20-21 канал, 51-54, 274-275, 512-513 реализация, 52-53 системный вызов, 51-54, 275-276 канал ожидания, 111-115, 123-124 канальный уровень, 552-553 путь, 557-558 канонический режим, 63, 488-489 карусель, 126 касторовое масло (castor oil), 496-497 каталог, 57-58, 364-365 выделение пространства, 366-367 зависимости, мягкие обновления, 399-403 кеш смещений, 368-371 операции,59-60 структура, 366-371 таблица, 241 элемент, 57-58,354-355
776 Предметный указатель квоты вклад, 22-23 ограничения, 375 реализация, 373-379 формат записи, 375 кластеризация, блок, 425-426,434-436, 442-447 кластеризация, страница, 213-214, 224-225, 233-235, 254-255 кластеризация страниц, 213-214, 224-225, 233-235, 254-255 клиент-серверные взаимодействие, NFS, 468-470 модель, 531-533 приложение, 62 программирование, 514-515 клиентский процесс, 62 клонирование маршрута, 578-579, 591-592 ключ, 542-543 управление, 665-672 код типа исключения, 72-73 коммуникационный домен, 64, 512-513,525-527 структуры данных, 526-527 коммуникационный протокол. См. протокол конвейер,46-47, 53-54 контроль доступа к файловой системе, 60-61 контрольная сумма, 598-599, 603-606, 608-609, 629, 634-635 конфигурация файл, 703-704 ядра, 703-705 копирование объекта, 4.4BSD, 199-200 копирование-при-записи (сору-оп- write), 20-21, 49-50, 203-204, 254-255 корневая файловая система, 59-60, 685 корневой каталог, 57-58 косвенный маршрут, 575-576 красная область, 50-51, 180 криптографический дескриптор, 674-675 криптография асимметричная, 673-674 подсистема, 673-676 сеанс, 673-674 симметричная, 673—674 куча, 84-85 кеш inode, 363-365 vnode, 190 буферный, 355-356, 425-428 имени файла, 298-300 смещения каталога, 368-371 списка страниц, 231-232 кеш страниц, 262-263, 267 кеш трассировки,442-445 Л лимиты в системе, 375 линейный режим (line mode), 488-489 липкий бит, 254-255 логический блок, 425-426 локальная сеть, 635-636, 662, 699-700 локальность ссылки, 170-171, 436-438 локальный домен, 64, 351 структура адреса, 531-532 М магическое число, 82 максимальное время жизни сегмента, 615-619, 678-679. См. также таймер 2MSL максимальный блок передачи, 470-471, 579-580, 586-587, 624-627, 679 мандат, 88-91,107-108,136-137,161-162, 201, 308-309,461-462,471-474 маршалинг аргументов, 457-458 маршрут, клонирование, 578-579, 591-592 маршрут подстановки, 575-576, 605-606 маршрутизатор, 554, 573-574 запрос, 656-657 многоадресная рассылка IP, 611-612 объявление, 654-655 элемент, 656-657 маршрутизация, 573-587 взаимодействие с ICMP, 646-647 демон,584-585 интерфейс, 585-587 информационный протокол, 584-585
Предметный указатель 777 механизм, 575-585 перенаправление, 583-584 поиск, 579-582 политика, 584-585 сокет, 585-586 таблицы, 575-585 типы, 575-576 маска сети, 598-599 массив дополнительных групп, 90-91 Международная организация по стандартизации модель, 552-553, 597 набор протоколов, 538-539, 590-591, 596 файловая система, 312-313 межпроцессное взаимодействие, 20-21, 39, 52-53, 55, 64-65, 93-94, 511-547, 549, 697-698 выключение сокета, 541-543 дизайн, 4.2BSD, 22-23 дизайн, FreeBSD, 512-513, 517-518 запуск, 697-700 локальное межпроцессное взаимодей- взаимодействие, 542-547 модель, 511-518 надежная доставка, 536-537 очередь сообщений, 511, 545-547, 549 передача данных, 535-538 передачи данных, 534-541 получение данных, 537-541 разделяемая память, 191-201, 546-547 семафоры, 543-546 структуры данных, 525-533 управление памятью в, 519-526 уровни,517-519 установка соединения, 531-535 межпроцессорное прерывание, 134-135 метаданные, 195-196,300-302,304-305, 310-311,326-327, 384-389, 407-408,410, 417-418,446-447-448 метод общего доступа, 41-42, 318-322, 324-325, 329-334, 338, 348-349 запрос ввода/вывода, 331-334 управляющий блок, 331-334 уровень, 329-334 метрики кеша хоста, TCP, 625-626 метрики маршрута, 579-580, 586-587 механизм пересылки, 575 младший номер устройства, 262-263 многозадачное программирование, 101-102 многопроцессорная система блокирование ресурсов разделяемой памяти, 117-124,531-533 виртуальная память для разделяемой памяти, 48 многоуровневая очередь обратной связи, 126 модель разработки, FreeBSD, 29-32 моментальный снимок, 412-413 в большой файловой системе, 417-419 видимый пользователю, 421-423 поддержка, 415-418 производительность, 419-421 создание, 412-416 тупик, 417-419 мотивация для системного вызова select, 282-287 мьютекс, сетевой поток, 573-574 мьютекс, циклический, 118-123 мягкие обновления, 384-413 битовые карты зависимостей, 393-394 зависимости fsck, 410 зависимости fsync, 406-408 зависимости inode, 393-396 зависимости каталога, 399-403 зависимости косвенного блока, 397-401 зависимости повторного использования, 404-407 зависимости прямого блока, 394-397 зависимости усечения, 403-405 зависимости числа ссылок, 407-410 зависимости, 385-391 обзор, 384-386 производительность, 410-413 структуры, 390-394 мягкое ограничение, 94-95 Н наиболее давний, 190, 377-379 наращиваемая файловая система, 305-313 4.4BSD, 305-307 начальная загрузка первого уровня, 272-273
778 Предметный указатель начальная загрузка, 42-43, 66, 685, 687-689 см. также boot установка времени при, 86-87 начальный номер последовательности, 612-614 не активный, возвращение из, 233-235 небезопасный режим, 361-362 не блокирующий ввод/вывод, 277-278, 283-284, 498-499, 504-505, 534-535, 536-537, 539-540 неизменный файл, 361-362 не кеширующее владение, 477-479 нелокальный переход необязательная блокировка, 281-282, 352-353 непосредственный обработка ввода, 589-591 обработка вывода, 590-591 режим, 63, 488-489 сокет, 53-54, 551, 588-591, 598-599, 646-647 управляющий блок, 588-590 непосредственное устройство, 267-268 интерфейс, 265-266 непосредственный доступ к памяти, 268, 329-331,333-336, 348-349 нижняя граница сокета, 529 терминала, 499-502 нижняя половина, 70 драйвера терминала, 489-490 драйвера устройства, 263-264 ядра, 70-71 номер поколения, 458-460 номера последовательностей, TCP, 612-614 нуль-модемное соединение, 498-499 О обзор, мягкие обновления, 384-386 область, 185-186 облегченный процесс, 104-105,163 обмен ключами Интернета, 665-666 обнаружение несущей, 497-499 обновление буфера (buffer update), 394-396 обновление размера файлового храни- хранилища, 423-424 обновление содержания, файловое хранилище, 422-423 оболочка csh, 150-151 sh, 83,700-701 регистрации, 39 оборудование передачи данных, 497-498 обработка исключений, 69,72-73,76-77, ИЗ обработка сдерживания источника, TCP, 640-641 обработка часового пояса, 23-24 общее отображение, 192-193 объект, 173-174 виртуальная память, 186-192 тень, 175-176, 188-189, 195-201 элемент файла, 275-276, 278-279 обязательная блокировка, 281-282 обязательный контроль доступа, 357-359 ограничение очереди, сеть, 588-589 окно отправки, 617-619 оконная система, 150-151, 493-494. См. также система X Window октет, 598-599 оператор vnode access, 352-353 advlock, 352-353 blkatoff, 423-424 close, 352-353 create, 351,353-354 fsync, 423-424 getattr, 352-353 inactive, 297-299, 352-353, 364-365 ioctl, 352-353 link, 351 lock, 352-353 lookup, 295, 352-353 mkdir, 351 mknod, 351 minap, 352-353 open, 352-353 poll, 352-353
Предметный указатель 779 read, 422-424 readdir, 352-353 readlink, 352-353 reclaim, 297-299, 352-353, 364-365 remove, 351 rename, 351 rmdir, 352-353 setattr, 352-353 strategy, 394-396 symlink, 351 truncate, 423-424 unlock, 352-354 update, 394-397, 399-401, 403-405, 422-423 write, 423-424 операции терминала, 498-506 файлового хранилища, 422-424 файловой системы, 351-354 опережающая подкачка, 169-170 опережающий ввод с клавиатуры, 488-489 определение MTU пути, 579-580, 625- 626, 679 определение соседа, 654-655, 657-658 опрос ввода/вывода, 283-284 опции отметки времени, TCP, 615-616, 624-625 опция масштабирования окна, TCP, 615-616 опция префикса, 656-657 организация, FFS, 428-433 основная (оперативная) память, 166 оставленная группа процессов, 152 отказ страницы, 168 отладка системы, 705-707 относительное имя пути, 58-59 отображение физической в виртуальную, 245-249 отображение, 242-243 структура, 242-243 физической в виртуальную, 245-249 отображения виртуальной памяти, 177-179 отрицательное котирование имен файлов, 300 очередь запуска, 108-109, 126 управление, 129-132 очередь ожидания, 108-109 очередь сообщения, 64 System V, 511,545-547 П пакет очередь, 572-574 передача, 570-572 пересылка, IP, 609-612, 647-648 получение, 512—51А фильтр, 554-555,561 паника, 704-705. См. также систем- системный сбой пейджер, 186-190 vnode, 188-189,213-215 интерфейс, 212-221 определение, 212-214 подкачки, 188-189,217-221 устройство, 188-189,214-216 физической страницы, 215-217 пейджер vnode, 188-189, 213-215 пейджер физической памяти, 215-217 перекрытие памяти, 168 перекрытие, 42-43 переменные последовательностей, TCP, 616-620 перемещение программы, 688-689 перемещение управляющего терминала, 298-299 переносимость FreeBSD, 40 Седьмая редакция UNIX, 19-20 управление памятью, 48 персональный компьютер, 71-73, 77-78, 79-80, 86-87,431 архитектура, 316-318 рост стека в, 84-85 планирование, 102-103 долговременный алгоритм, 127-128 класс, 109-110 кратковременный алгоритм, 127-128 параметры, 43-44 поток, 117-118, 126-136 приоритет, 109-110 процесс, 69, 79-80, 85-86, 103-104
780 Предметный указатель планировщик, 4.4BSD, 124-126 подкачка выгрузка, 104-105,236-238 выгрузка, 4.4BSD, 237-238 область, 172 пейджер, 188-189,217-221 пейджер, 4.4BSD, 218-219 пространство, 172,217 разделы, 218-219 управление пространством, 217-221 устройство, 172 подкачка страниц, 20-21, 48, 84-85, 168-170, 172,186-188, 190-192, 221-229 параметры, 231-233 системы, характеристики, 169—170 подкачка процессов, 48, 85-86, 170-172,236-239 в FreeBSD, причины для, 236-237 подсеть, 599-600 политика замещения, 169-170 политика оптимального замещения, 169-170 политика размещения, 169-170 постоянный модуль ядра, 690-691 поток, 104-105, 191-192 вытеснение, 131-132 мьютекс, сеть, 573-574 очереди, 108-109 планирование, 117-118, 126-136 приоритет, 108-109, 113 приоритет, в состоянии сна, 109-110 приоритет, вычисление, 116—117, 127-129 состояние, 113 состояние, изменение, 116-117 структура, 110-111 управляющий блок, 71-72, 106, 111-113 приемное окно, 617-619 принудительное перемещение приобретение BSD, 9-10 приоритет процесса, 45-46, 74-75,92-93 вычисление, 79-80 производительность моментальный снимок, 419-421 мягкие обновления, 410-413 см. также производительность сис- системы предотвращение малых пакетов, 677 реализация TCP, 635-638 пробуксовка, 104-105 программа с устанавливаемым иден- идентификатором группы, 89 программа с устанавливаемым иден- идентификатором пользователя, 89 программное прерывание, 77-78 производительность системы, 73-74, 77-78, 82, 84-85, 86-87, 102-103, 130-131,537-538 производительность удаленной фай- файловой системы, 473-477 простой косвенный блок, 355-356 пространство виртуальных адресов, 167-168 процесс, 185-186 размещение пользователя, 82-86 пространство последовательностей, 612-614 просроченное преобразование, 224-227 просроченные данные, 473-474 протокол,64 NFS, 461-464 возможности сети, 556-558 интерфейс протокола, 567-571 переключение, 526-527 политика буферирования, 586-587 процедура управления выводом, 567-568 сетевой интерфейс, 570-574 структура переключения, 554-555 управляющий блок, 601-603 протокол без состояния, 461-462 протокол двухточечного соединения, 506 протокол пользовательских дей- дейтаграмм, 156-158,457-460, 461-462, 465-466, 469-472, 483-484, 583-584, 597-599, 600-607, 612, 621, 633-634, 644-647, 677-678 ввод, 605-606 вывод, 604-606 инициализация, 603-605 управляющие операции, 605-607
Предметный указатель 781 протокол управления передачей, 6,17, 20-21, 65,156-158, 312-313, 457-460, 465-466, 470-471-472, 483-484, 554, 579-580, 583-584, 590-591, 597-599, 600-603, 611-647, 677-679 syncache, 626-628 алгоритм медленного старта, 638-643 алгоритм, 620-628 буферирование данных, 641-642 быстрая повторная передача, 643-646 выключение соединения, 615-616, 627-628 диаграмма состояния, 617-619 заголовок пакета, 614-615 кеш хоста, 624-625 метрики кеша хоста, 625-626 номера последовательностей, 612-614 обновления окна, 637-638 обработка ввода, 629-633 обработка вывода, 633-646 обработка повторной передачи, 638-639 обработка сдерживания источника, 640-641 обработка синдрома незначительного окна, 634-636 обработка срочных данных, 632-633 опции,612-614 опция максимального размера сег- сегмента, 615-616, 624-625 опция масштабирования окна, 615-616 опция отметки времени, 615-616, 624-625 отложенные подтверждения в, 632-633, 637-638 оценка времени обращения, 623-625 переменные последовательностей, 616-620 политика отправки, 620-621, 633-646 предсказание заголовка, 630-631 приемное окно, 629 процедуры таймера, 621 реализация предотвращения неболь- небольших пакетов, 635-638 реализация, использование 4BSD, 22-23 свойства, 612 состояния соединения, 614-617 таймеры, 621-624 управление перегрузкой, 638-643 управление потоком в, 612-614 установка соединения, 614-616, 624-628 профилирование процесса, 76, 88 таймер, 78-79, 88 процедура запроса пользователя процедура запуска, 332-335, 500-501, 506 процедуры запросов пользователей, 556-557, 563-567 операции,563-567 процесс, 43-44,101 виртуальное адресное пространство, 185-186 виртуальное время, 88 дублирование виртуальной памяти, 203-205 завершение, 137-138,210-211 контекст, 43-44 облегченный, 104-105, 163 организация состояния, 104-111 отладка, 144, 159-162 планирование, 69, 79-80, 85-86, 103-104 профилирование, 76, 88 ресурсы виртуальной памяти, 185-192 создание, 135-138,201-205 состояние, изменения, 138-139, 144-145, 160-161 структура, 70-71, 102-103, 107-110, 113 таблица открытых файлов, 363-365 учет ресурсов, 78-79, 96-97, 138-139 флаги, 160-161 ядра, 68-69 процессорное притяжение, 131-133, 183-184 процессы zombie, 108-109,138-139 псевдозаголовок, IP, 603, 605-606, 629 псевдотерминал, 9-10, 38, 40, 46-47, 63, 158, 287-289, 298-299, 322-323, 487-490, 493, 498-503, 507 пути через узел сети, 553-554 Р работа /dev, 321-323 рабочая станция, 166 рабочий набор, 170-171
782 Предметный указатель раздел. См. раздел диска разделение ресурса, 117-124 разделяемая библиотека, 85-86 разделяемая память, 65, 191-201, 511 System V, 192-193, 215-216, 546-547 именование, 192-194 разделяемый сегмент text, 20-21 размер окна, 493-494, 498-499 размер резидентного набора, 231-232 раскраска страниц, 227-229 регистрационная оболочка, 40 регистрационное имя, 92-93 режим пользователя, 101,172 ресурс автоконфигурирование, 344-349 виртуальной памяти процесса, 185-192 использование, 93-94 предел, 43-44, 92-94 разделение, 117-124 учет, процесс, 78-79, 96-97, 138-139 решение локальный-удаленный, 575-576 родительский каталог, 58-59 родительский процесс, 43-44,108-109, 135-136 С сбор статистики, 78-79, 93-94 сеанс, 46-47, 91-92,147-151, 495 заголовок, 148-149 сегмент, 167-168, 612-614 bss, 82 data, 46-47, 82, 84-85, 207-208 stack, 46-47, 82, 207-208 text, 46-47, 82, 84-85 Седьмая редакция UNIX, 19-20 переносимость, 19-20 семафоры, 65, 511 System V, 542-545, 698-699 виртуальная память, 191-193 семейство протоколов, 514-515, 551 серверный процесс, 62 Сетевая файловая система, 62, 291-292, 298-299, 301-302, 307-311, 352-354, 454-484, 529, 699-700 4.4BSD, 455-457 асинхронная запись, 474-475 блокировка файла, 457-458 взаимодействие клиент-сервер, 468-470 владение, 464-465, 476-482 владение, получение, 480-481 восстановление после сбоев, 480-483 демоны, 464-468 дизайн, 456-458 жесткое монтирование, 468 мягкое монтирование, 469-470 обзор, 62 описатель файла, 458-460 отложенная запись, 474—475 прерываемое монтирование, 469—470 проблемы безопасности, 471-474 протокол, 461-464 реализация, 463-468 структура, 457-474 транспорт RPC, 469-472 шторм восстановления, 482-483 сетевой интерфейс, 557-564 адреса, 557-560 возможности, 559-563 вывод, 562-564 уровень, 552-553 сеть адрес, тюрьма, 155-160 архитектура, 551 буферирование, 586-589 возможности протокола, 556-558 дизайн, 65 добавления в FreeBSD, 65 маска мьютекс потока, 573-574 ограничение очереди, 588-589 порядок байтов, 598-599 поток данных, 553-554 поток, 608-609 протокол времени, 696-697 синхронизация времени, 86-87 таймер, 79-80, 554-555 тупик, 282-284 управление перегрузкой, 586-589 уровень, 552—553 сигнал, 45-46, 107-108, 138-152 доставка, 145-147 код запуска сигнала, 146-147 маскирование, 140-141 обработчик, 45-46, 138-141
Предметный указатель 783 ограничения на отправки, 140-141 отправка, 140-146 приоритет, 45-46 проверка наличия, 74-75 сравнение с другими системами, 141-142 стек, 45-46, 140-141 управляемый вводом/выводом, 277-278, 283-284 символ остановки, 500-501 символ удаления слова, 488-489 символическая ссылка, 372-374 симметричная криптография, 673-674 симметричная многопоточность, 131-135 симметричная многопроцессорная обработка, 117-118,131-135, 523-524, 531-533, 673-674, 689-693 синдром незначительного окна, 634-635 обработка TCP, 634-636 синхронизация, 117-124 сетевое время, 86-87 система X Window, 493-494, 635-636 система потокового ввода/вывода, 20-21 система реального времени, FreeBSD в качестве, 103-104, 131-132, 193-196 системная ошибка, EAGAIN, 137-138, 277-278, 502-503, 537-538, 543-545 системная ошибка, EINTR, 74-75, 110-111,137-138 системные вызовы accept, 516-517, 530, 531-535, 548, 626-627, 654-655 access, 306-307 adjtime, 88 aio_error, 278-280 aio_read, 278-279, 313 aio_retum, 280 aio_suspend, 280 aio_waitcomplete, 280 aio_write, 278-279, 313 bind, 603, 654-655 chdir, 58-59 chflags, 360-361, 413-414 chmod, 59-60,413-414 chown, 59-60, 413-414 chroot, 58-59, 153-157 close, 51-52, 276-277, 280, 298-299, 305-306, 474-476, 490-491,517-518, 541-543,627-628 connect, 515-516, 531-535, 603-607, 624-625, 654-655, 671-672 dup, 53-54, 60-61,276-278 dup2, 53-54, 277-278 exec, 52-53, 85-86, 89-91, 96, 101, 135-136, 147-149, 161-162, 178-179, 201, 203-207, 210-211, 213-214, 248-249, 254-255, 276-277, 703-704 exit, 44-45, 135-138,204-205,210-211 extattrctl, 413-414 fchdir, 154-155 fchflags, 360-361,413-414 fchmod, 59-60, 413-414 fchown, 59-60,413-414 fcntl, 22-23, 276-279, 501-502 fhopen, 413-414 flock, 457-458 fork, 18, 44-45, 52-53, 60-61, 96, 101, 107-110, 113, 135-138, 147-149, 160-162, 201-205, 213-214, 232-233, 248-251, 254-255, 276-277 fstat, 59-60, 566-567 fsync, 195-196, 292-293, 301-302, 313, 356-358, 387-389, 399-401, 406-408, 413-414, 426-428, 435-436, 444, 448-449, 474-475 ftruncate, 413-414 futimes, 413-414 getdirentries, 367 getfsstat, 296-297 getlogin, 703-704 getpeemame, 517-518 getrusage, 93-94 getsockname, 517-518 getsockopt, 517-518, 563-564, 567-568 gettimeofday, 86-87 ioctl, 53-54, 150-151, 275-276, 278-279, 489-493, 495, 503-505, 539-540, 557-558, 562-563, 566-567 jail, 155-157 jailkill, 159-160 kill, 140-141 killpg, 150-151 kse_create, 106 ktrace, 413-414 Ichmod, 413-414
784 Предметный указатель lchown, 413-414 link, 59-60, 413-414 listen, 515-516, 531-533, 626-627 lseek, 52-53,275-276 lstat, 373-374 lutimes, 413-414 mkdir, 59-60, 66, 402, 413-414 mkfifo, 413-414 mknod, 413-414 mlock, 195-196, 229-230, 251-252 mmap, 48, 50-51, 85-86, 174-175, 193-194,202-203,208-210,213-214, 248-249, 300 mount, 56, 269-270, 305-306, 308-309, 310-311,464-466,701-702 mprotect, 209-210, 250-251 msgrcv, 545-547 msgsnd, 545-546 msync, 195-196, 213-214, 215-217 munlock, 195-196 rnunmap, 193-194, 197-198, 202-203, 208-209, 249-250 nfssvc, 465-467 open, 51-54, 60-61, 213-214, 269-270, 275-276, 305-306, 352-354, 363-365, 373-374, 413-414, 490-491, 498-499, 517-518,543-544 pipe, 51-54, 275-276 poll, 269-270, 283-287, 490-491 profil, 98 ptrace, 117-118, 159-162 quotactl, 413-414 read, 49-52,55-56,64, 161-162,275-276, 286-287, 290-291, 300, 305-306, 313, 489-490, 501-504, 517-518, 535-536 readv, 55-56, 289-290 reboot, 704-705, 708-709 recv, 55-56 recvfrom, 55-56, 535-536, 654-655 recvmsg, 55, 535-536, 541 rename, 59-60, 413-414, 461-462 revoke, 298-299, 413-414, 495, 504- 505,701-702 rfork. 106-107, 135-136 rmdir, 59-60, 402, 407-408, 413-414 rtprio, 109-110 sbrk, 84-85, 173-175, 202-203, 207-208 select, 269-270, 283-289, 313, 529, 626-627 semctl, 549 semget, 544-545, 549 semop, 544-545, 549 send, 55-56, 64, 527-528, 667-668 sendfile, 50-51 sendmsg, 55, 535-536, 563-564, 603 sendto, 55-56, 535-536, 563-564, 603, 654-655 seteuid, 91-92 setlogin, 703-704 setpgid, 147-149 setpriority setsid, 148-149 setsockopt, 517-518, 542-543, 563-564, 567-568, 600-601, 604-605,637-638 settimeofday, 86-87 shmat, 546-547 shmdt, 546-547, 549 shmem, 192-193,546-547 shmget, 546-547 shutdown, 517-518, 539-540, 627-628 sigaction, 140-141, 145 sigaltstack, 140-141 sigpause, 115-116 sigprocmask, 140-141 sigreturn, 141-142, 146-147 sigsuspend, 140-141 socket, 22-23, 51-54, 64-65, 275-276, 514-516, 525-526, 531-533, 563-564, 567-568 socketpair, 566-567 stat, 297-298, 305-306, 360-361, 368-369 statfs, 296-297 symlink, 413-414 sync, 292-293,413-414 sysctl, 232-233, 310-311, 562-563, 611-612,706-709 tcsetattr truncate, 60-61, 408-409, 413-414 undelete, 310-311,413-414 unlink, 59-60, 408-409, 413-414, 461-462 unmount, 305-306, 413-414 utimes, 360-361,413-414 vfork, 107-108, 135-136, 147-148, 204-205, 254-255
Предметный указатель 785 wait, 44-45, 93-94, 101, 108-109, 113-114, 147-148, 161-162,204-205, 210-211 wait4, 44-45, 137-138, 160-161 write, 43-44, 49-56, 64, 161-162, 275-276, 283-284, 286-287, 290-291, 313, 375, 413-414, 434-436, 468, 474-476, 489-490, 500-501,517-518, 527-528, 535-536, 667-668 writev, 55-56, 289-290 системный вызов, 39, 42-43, 70-71 возвращение из, 74-75 обработка результатов, 73-74 обработка, 49-50, 72-76, 113 прерываемый, 73-74 системный сбой, 59-60, 62, 270-271, 280, 282-283, 301-302, 339, 378-379, 384-385, 386-387, 389, 410, 421-422, 426-429, 448-449, 461-463, 466-471, 474-477, 480-484, 614-617, 622-623, 691-692, 696-697, 701-702, 705-707 следующий заголовок, 652-653 совмещение виртуальных адресов, 241 соединение периферических компо- компонентов (PCI), 41-42, 316-318, 320-322, 339-349 сокет, 51-52, 55, 64, 261-262, 274-275, 513-514,525-526,551 адрес, 530-532 блокирование буфера данных, 537-538 буферирование данных, 527-528, 536-539 выключение, 541-543 использование, 514-518 обработка ошибок, 534-535 опции,563-564 очередь соединений,529, 531-533 переходы состояний во время взаимо- взаимодействия, 533 переходы состояний во время выключения, 542-543 связывание группы процессов с, 150-151,527-528 состояния, 530 структура адреса, 514-515 структуры данных, 527-530 типы, 513-514, 525-526 сокет с надежной доставкой сообще- сообщений, 593-594 сокет последовательной передачи пакетов, 513-514 сокет потока, 513-514 состояние гонки, 147-149,191-192, 223-224, 282-283, 408-409 сохраненный GID, 91-92 сохраненный UID, 90-91 специальное устройство, 274-275 специальный файл, 53-54, 274-275 списки страниц, 229-232 активных, 229-230 зарезервированных, 229—230 копированных, 231-232 не активных, 229-230, 251-252 свободных, 231-232 средняя нагрузка, 127-129 срочная доставка, 590-591 срочные данные, 590-591 обработка TCP, 632-633 передача, стили, 537-538 стандартная ошибка, 52-53 стандартный ввод, 52-53 стандартный вывод, 52-53 старший номер устройства, 262-263 статистика системы, 78-79 стек заполнение нулями пользователем, 84-85 рост на PC, 84-85 сегмент, 46-47, 82, 207-208 сторожевой таймер, 79-80 страница, зарезервированная, 178-179, 215-216, 242-246, 251-252 строка доступа, 169-170 структура пользователя, 71-72,102-103 структура потока, 110-111 структуры, мягкие обновления, 390-394 суперблок,428-429 суперпользователь, 88, 280 схема виртуальной памяти, 173-175 схема скользящего окна, 612-614
786 Предметный указатель таблица, прямое отображение страницы, 239-240 таблица страниц, 241 прямого отображения, 239-240 страницы, 241 таблица страниц обратного отображе- отображения, 239-240 таймер 2MSL, 622-624 виртуального времени, 78-79, 88 приращение, 622-623 профилирующий, 78-79, 88 процедуры, TCP, 621 разрешение, 86-87 реального времени, 79-80, 88 сетевой, 79-80, 554-555 сторожевой, 79-80 таймер виртуального времени, 78-79, 88 таймер повторной передачи, 621, 626- 627 таймер реального времени, 79-80, 88 таймер соединения, 622-623 теневой объект, 175-176,188-189, 195-201 свертывание, 197-200 цепочка, 197-200 терминал, 63, 487-488 буферирование, 496-498 ввод, 501-504 вывод, 501-502 мультиплексирование, 261-262 операции, 498-506 тик, 77-78 транспортный режим, 660-661 соединение безопасности, 662 транспортный уровень, 552-553 трассируемый процесс, 144, 160-161 тройной косвенный блок, 355-356 туннелирование, 552-553 туннельный режим, 662 теги, 523-524 У удаленная файловая система, 455-456 удаленные файловые системы, история, 454-457 удаленный вызов процедур, 457-460, 461-476, 477-484 транспортировка, NFS, 469-472 универсальная последовательная шина, 317-322, 331-332, 348-349 Универсальное скоординированное время, 86-87, 97 управление модемом, 497-499 игнорирование, 498-499 управление памятью, 46-49, 166-255 аппаратура, VAX, 47-48 в IPC, 519-526 дизайн кеша, 224-228 дизайн таблицы страниц, 241-243 дизайн, 47-51 переносимость, 48 система, 166 цели, 166-174 ядро, 176-184 управление процессом, 43-46, 82-86, 101-162 история, 101 упреждающая выборка Ф физический блок, 425-426 физический ввод/вывод, 267-268 алгоритм для, 268 физическое отображение, 242-243 ц циклический мыотекс, 118-123 Ш Шестая редакция UNIX, 18-19
Содержание Предисловие 5 Об авторах 13 Часть I. Обзор 15 Глава 1. История и цели 17 1.1. История UNIX 17 Происхождение 17 Исследовательский UNIX 18 AT&T UNIX System III и System V 20 Berkley Software Distributions 20 UNIX в мире 21 1.2. BSD и другие системы 22 Влияние сообщества пользователей 23 1.3. Переход BSD на открытый исходный код 24 Networking Release 2 24 Судебный процесс 26 4.4BSD 28 4.4BSD-Lite Release 2 28 1.4. Модель разработки FreeBSD 29 Ссылки 34 Глава 2. Обзор дизайна FreeBSD 38 2.1. Средства FreeBSD и ядро 38 Ядро 39 2.2. Организация ядра 40 2.3. Службы ядра 43
788 Содержание 2.4. Управление процессами 44 Сигналы 45 Группы процессов и сеансы 46 2.5. Управление памятью 47 Проектные решения по управлению памятью BSD 48 Управление памятью внутри ядра 50 2.6. Система ввода/вывода 51 Дескрипторы и ввод/вывод 51 Управление дескрипторами 53 Устройства 54 Механизм взаимодействия сокетов 54 Разбросанный ввод/вывод 55 Поддержка нескольких файловых систем 56 2.7. Устройства 56 2.8. Файловые системы 57 Файловое хранилище 61 2.9. Сетевая файловая система 62 2.10. Терминалы 63 2.11. Межпроцессное взаимодействие 64 2.12. Сетевая коммуникация 65 2.13. Реализация работы в сети 65 2.14. Работа системы 66 Упражнения 66 Ссылки 67 Глава 3. Службы ядра 68 3.1. Организация ядра 68 Системные процессы 68 Системный вход 69 Организация времени выполнения 70 Вход в ядро 72 Возвращение из ядра 73 3.2. Системные вызовы 74 Обработка результатов 74 Возвращение из системного вызова 75
Содержание 789 3.3. Исключения и прерывания 76 Исключения 76 Прерывания от устройств ввода/вывода 76 Программные прерывания 77 3.4. Прерывания от часов 78 Статистика и планирование процессов 79 Тайм-ауты 80 3.5 Службы управления памятью 82 3.6. Службы времени 86 Реальное время 86 Внешнее представление 87 Корректировка времени 87 Интервальное время 88 3.7. Идентификаторы пользователя, группы и другие идентификаторы 88 Идентификаторы хостов 92 Группы процессов и сеансы 92 3.8. Службы ресурсов 93 Приоритеты процессов 93 Использование ресурсов 94 Ограничения ресурсов 94 Квоты файловой системы 95 3.9. Службы управления системой 96 Учет использования ресурсов 96 Упражнения 97 Ссылки 98 Глава 4. Управление процессами 101 4.1. Введение в управление процессами 101 Многозадачное программирование 102 Планирование 103 4.2. Состояние процесса 105 Структура процесса 107 Структура потока 1 И 4.3. Переключение контекста 112 Состояние потока 113 Переключение контекста на низком уровне 114 Добровольное переключение контекста 114
790 Содержание Синхронизация 117 Синхронизация с помощью мьютекса 121 Блокировки менеджера блокировок 123 Другие виды синхронизации 124 4.4. Планирование потоков 125 Планировщик 4.4BSD 126 Планирование потоков с разделением времени 126 Вычисления приоритета потоков 127 Процедуры вычисления приоритета потока 129 Очереди выполнения потоков и переключение контекста 129 Планировщик ULE 132 4.5. Создание процесса 135 4.6. Завершение процесса 137 4.7. Сигналы 139 История сигналов 142 Отправка сигнала 143 Доставка сигнала 145 4.8. Группы процессов и сеансы 147 Сеансы 148 Управление заданиями 150 4.9. Тюрьмы 152 Семантика тюрьмы 155 Реализация тюрьмы 157 Ограничения тюрьмы 158 4.10. Отладка процессов 159 Упражнения 162 Ссылки 164 Глава 5. Управление памятью 166 5.1. Терминология 166 Процессы и память 167 Страничная подкачка 168 Алгоритмы замещения 170 Модель рабочего набора 171 Подкачка процессов 171 Преимущества виртуальной памяти 172 Аппаратные требования для виртуальной памяти 172
Содержание 791 5.2. Обзор системы виртуальной памяти FreeBSD 173 5.3. Управление памятью ядра 177 Отображения и подотображения ядра 177 Выделение адресного пространства ядра 179 МаИосядра 180 Зональный распределитель ядра 183 5.4. Ресурсы процесса 185 Виртуальное адресное пространство процесса FreeBSD 185 Передача отказов страниц 186 Отображение на объекты 188 Объекты 189 Объекты к страницам 190 5.5. Разделяемая память 191 Модель mmap 192 Разделяемое отображение 195 Индивидуальное отображение 195 Сворачивание теневых цепочек 198 Индивидуальные моментальные снимки 200 5.6. Создание нового процесса 201 Резервирование ресурсов ядра 202 Дублирование адресного пространства пользователя 203 Создание нового процесса без копирования 205 5.7. Исполнение файла 206 5.8. Манипулирование процесса своим адресным пространством 207 Изменение размера процесса 207 Отображение файлов 208 Изменение защиты 210 5.9. Завершение процесса 210 5.10. Интерфейс пейджера 212 Пейджер vnode 214 Пейджер устройств 215 Пейджер физической памяти 216 Пейджер подкачки 217 5.11. Страничная подкачка 221 Дизайн аппаратного кеша 225 Раскраска страниц (page coloring) 227
792 Содержание 5.12. Замещение страниц 229 Параметры страничной подкачки 232 Демон выгрузки страниц 232 Подкачка процессов 237 Процесс подкачки 238 5.13. Переносимость 239 Роль модуля ртар 242 Инициализация и запуск 245 Выделение и освобождение отображения 248 Изменение для отображений атрибутов доступа и резервирования... 250 Управление информацией об использовании страницы 252 Инициализация физических страниц 253 Управление внутренними структурами данных 254 Упражнения 254 Ссылки 256 Часть III. Система ввода/вывода 259 Глава 6. Обзор системы ввода/вывода 261 6.1. Отображение ввода/вывода от пользователя на устройство 261 Драйверы устройств 263 Очередь ввода/вывода 264 Обработка прерываний 264 6.2. Символьные устройства 265 Непосредственные устройства и физический ввод/вывод 267 Символьно-ориентированные устройства 268 Точки входа для драйверов символьных устройств 269 6.3. Дисковые устройства 270 Точки входа для драйверов дисковых устройств 270 Сортировка запросов дискового ввода/вывода 271 Метки дисков 272 6.4. Управление дескрипторами и службы дескрипторов 274 Открытые элементы файлов 275 Управление дескрипторами 277 Асинхронный ввод/вывод 279 Блокировка дескриптора файла 280 Мультиплексирование ввода/вывода для дескрипторов 283 Реализация select 286 Перемещение данных внутри ядра 289
Содержание 793 6.5. Интерфейс виртуальной файловой системы 291 Содержимое vnode 291 Операции vnode 293 Трансляция имен путей 295 Службы экспортированных файловых систем 296 6.6. Службы, независимые от файловой системы 297 Кеш имен 299 Управление буферами 300 Реализация управления буферами 303 6.7. Наращиваемые файловые системы 305 Простые уровни файловых систем 307 Файловая система union 309 Другие файловые системы 312 Упражнения 313 Ссылки 314 Глава 7. Устройства 315 7.1. Обзор устройств 315 Архитектура ввода/вывода PC 316 Структура подсистемы ввода/вывода запоминающих устройств большой емкости FreeBSD 319 Именование устройств и доступ к ним 322 7.2. Уровень GEOM 323 Терминология и топологические правила 324 Изменение топологии 325 Функционирование 328 Топологическая гибкость 329 7.3. Уровень САМ 330 Подсистема SCSI 331 Путь запроса ввода/вывода через подсистему САМ 332 7.4. Уровень АТА 334 Путь запроса ввода/вывода через подсистему АТА 334 7.5. Конфигурирование устройств 336 Идентификация устройств 339 Структуры данных автоконфигурирования 342 Управление ресурсами 345 Упражнения 348 Ссылки 349
794 Содержание Глава 8. Локальные файловые системы 351 8.1. Управление иерархическими файловыми системами 351 8.2. Структура mode 353 Изменения формата inode 355 Расширенные атрибуты 357 Возможности новой файловой системы 359 Флаги файлов 361 Динамические inode 362 Управление массивом inode 363 8.3. Именование 365 Каталоги 366 Отыскание имен в каталогах 368 Преобразование имен путей 370 Ссылки 372 8.4. Квоты 374 8.5. Блокировки файлов 378 8.6. Мягкие обновления 384 Зависимости обновлений в файловой системе 386 Структуры зависимостей 390 Отслеживание зависимости битовой карты 393 Отслеживание зависимости inode 394 Отслеживание зависимостей прямых блоков 396 Отслеживание зависимостей косвенного блока 397 Отслеживание зависимостей для новых косвенных блоков 399 Отслеживание зависимости нового элемента каталога 400 Отслеживание зависимостей нового каталога 401 Отслеживание зависимости удаления элемента каталога 403 Усечение файлов 403 Повторное использование inode файлов и каталогов 404 Отслеживание зависимостей переименования элемента каталога 404 Отслеживание зависимостей удаления файла 404 Требования fsync для мягких обновлений 406 Требования удаления файла для мягких обновлений 407 Требования мягкого обновления для fsck 410 Производительность мягких обновлений 410 8.7. Моментальные снимки файловой системы 412 Создание моментального снимка файловой системы 412 Хранение моментальных снимков файловой системы 415
Содержание 795 Моментальные снимки больших файловых систем 417 Производительность моментальных снимков 420 Фоновая fsck 421 Видимые для пользователя моментальные снимки 422 Динамические дампы 422 8.8. Локальное файловое хранилище 423 Обзор файлового хранилища 423 Пользовательский ввод/вывод в файле 425 8.9. Быстрая файловая система Беркли 428 Организация быстрой файловой системы Беркли 429 Загрузочные блоки 431 Оптимизация использования хранилища 432 Политики размещения 437 Механизмы выделения 439 Кластеризация блоков 443 Выделение, основанное на экстентах 446 Упражнения 447 Ссылки 449 Глава 9. Сетевая файловая система 454 9.1. История и обзор 454 9.2. Структура и работа NFS 458 Протокол NFS 461 Реализация NFS FreeBSD 464 Клиент-серверные взаимодействия 468 Транспортные проблемы RPC 470 Проблемы безопасности 471 9.3. Методики по повышению производительности 473 Владения 476 Восстановление после сбоев 481 Упражнения 483 Ссылки 484 Глава 10. Управление терминалами 487 10.1. Режимы обработки терминалов 488 10.2. Дисциплины линии связи 489 10.3. Интерфейс пользователя 491 10.4. Структура tty 493
796 Содержание 10.5. Группы процессов, сеансы и управление терминалом 495 10.6. С-списки 496 10.7. RS-232 и управление модемом 497 10.8. Операции терминала 498 Open 499 Дисциплина выходной линии связи 499 Вывод на терминал 501 Ввод с терминала 501 Процедура ioctl 504 Переходы модема 505 Закрытие терминальных устройств 505 10.9. Другие дисциплины линии связи 506 Упражнения 507 Ссылки 508 Часть IV. Межпроцессное взаимодействие 509 Глава 11. Межпроцессное взаимодействие 511 11.1. Модель межпроцессного взаимодействия 511 Использование сокетов 514 11.2. Структура реализации и обзор 517 11.3. Управление памятью 519 Mbuf 519 Алгоритмы управления хранилищем 524 Вспомогательные процедуры mbuf 525 11.4. Структуры данных 526 Коммуникационные домены 527 Сокеты 528 Адреса сокетов 531 Блокировки 531 11.5. Установление соединения 532 11.6. Обмен данными 535 Передача данных 536 Получение данных 538 11.7. Отключение сокета 541
Содержание 797 11.8. Локальное межпроцессное взаимодействие 542 Семафоры 544 Очереди сообщений 545 Разделяемая память 547 Упражнения 548 Ссылки 549 Глава 12. Сетевая коммуникация 551 12.1. Внутренняя структура 552 Поток данных 553 Коммуникационные протоколы 554 Сетевые интерфейсы 557 12.2. Интерфейс между сокетами и протоколами 563 Процедуры пользовательских запросов протокола 564 Процедура управления выводом протокола 567 12.3. Интерфейс протокол-протокол 568 pr_output 569 pr_input 569 pr_ctlinput 570 12.4. Интерфейс между протоколом и сетевым интерфейсом 571 Передача пакета 571 Прием пакетов 572 12.5. Маршрутизация 574 Таблицы маршрутизации ядра 576 Поиск маршрутизации 580 Перенаправления маршрутизации 583 Интерфейс таблицы маршрутизации 583 Политики маршрутизации уровня пользователя 585 Интерфейс маршрутизации уровня пользователя: сокет маршрутизации 585 12.6. Буферирование и управление перегрузкой 587 Политики буферирования протокола 587 Ограничение очереди 588 12.7. Непосредственные сокеты 588 Управляющие блоки 589 Обработка ввода 589 Обработка вывода 590
798 Содержание 12.8. Дополнительные темы сетевой подсистемы 590 Внеполосные данные 590 Протокол разрешения адресов 591 Упражнения 593 Ссылки 594 Глава 13. Сетевые протоколы 596 13.1. Сетевые протоколы IPv4 597 Адреса IPv4 599 Широковещательные адреса 599 Многоадресная рассылка Интернета 600 Порты и связи Интернета 601 Управляющие блоки протоколов 601 13.2. Протокол пользовательских дейтаграмм (UDP) 603 Инициализация 603 Вывод 604 Ввод 605 Управляющие операции 606 13.3. Протокол Интернета (IP) 606 Вывод 607 Ввод 609 Пересылка 610 13.4. Протокол управления передачей (TCP) 612 Состояния TCP-соединения 614 Переменные, описывающие последовательность 617 13.5. Алгоритмы TCP 620 Таймеры 621 Оценка времени обращения 623 Установление соединения 624 KemSYN 627 Отключение соединения 628 13.6. Обработка ввода TCP 629 13.7. Обработка вывода TCP 633 Отправка данных 634 Избегание синдрома незначительного окна 635 Избежание небольших пакетов 636 Отложенные подтверждения и обновления окон 637 Состояние повторной передачи 638
Содержание 799 Медленный старт 638 Обработка сдерживания источника 641 Задание размеров буфера и окна 641 Избежание перегрузки с помощью медленного старта 642 Быстрая повторная передача 644 13.8. Протокол управляющих сообщений Интернета (ICMP) 646 13.9. IPv6 648 Адреса IPv6 649 Форматы пакета IPv6 651 Изменения API сокетов 653 Автоконфигурирование 655 13.10. Безопасность 660 Обзор IPSec 661 Протоколы безопасности 663 Управление ключами 666 Реализация IPSec 671 Криптографическая подсистема 674 Упражнения 677 Ссылки 679 Часть V. Работа системы 683 Глава 14. Запуск и выключение 685 14.1. Обзор 685 14.2. Начальная загрузка 687 Программа boot 687 14.3. Инициализация ядра 688 Запуск на языке ассемблера 689 14.4. Инициализация модуля ядра 690 Базовые службы 691 Инициализация потоков ядра 693 Инициализация модулей устройств 694 Загружаемые модули ядра 696 Запуск межпроцессного взаимодействия 698 Запуск потоков ядра 700 14.5. Инициализация уровня пользователя 700 /sbin/init 701 Сценарии запуска системы 701
800 Содержание /usr/libexec/getty 702 /usr/bin/login 703 14.6. Работа системы 703 Конфигурация ядра 703 Выключение системы и автоматическая перезагрузка 704 Отладка системы 705 Передача информации ядру и от ядра 706 Упражнения 708 Ссылки 709 Глоссарий 710 Предметный указатель 755
АРХИТЕКТУРА КНИГА о КУДИи-ОБРАЗ
tour* Open Source Forum Russia i '"'¦ • ? Linux *h« '•¦ "^ в '• 4ii ;0*\, tilt; П III 4^ •' з ¦•« itll w ы