Компьютерные сети. 6-е изд. - Эндрю Таненбаум
Шрифт:
Интервал:
Закладка:
Такое копирование может сильно снизить производительность, поскольку операции с памятью обычно в десятки раз медленнее, чем операции с использованием только внутренних регистров. К примеру, если 20 % инструкций действительно связаны с обращением к памяти (то есть данных нет в кэше), — а это вполне вероятная цифра при обработке входящих пакетов, — среднее время выполнения инструкции снизится в 2,8 раза (0,8 × 1 + 0,2 × 10). Аппаратные улучшения здесь не помогут — проблема в слишком большом числе операций копирования, выполняемых операционной системой.
Продуманная операционная система постарается уменьшить число операций копирования, объединяя процессы обработки на разных уровнях. К примеру, TCP и IP обычно работают вместе («TCP/IP»), поэтому когда обработка переходит от сетевого уровня к транспортному, копировать пользовательские данные пакета не нужно. Еще один популярный прием состоит в том, чтобы выполнять несколько операций на уровне за один проход. Например, контрольные суммы часто вычисляются во время копирования данных (когда это действительно необходимо), и новое значение добавляется в конец.
Минимизируйте число переключений контекста
Схожее правило заключается в том, чтобы по возможности избегать переключений контекста (например, из режима ядра в режим пользователя), поскольку они объединяют в себе негативные свойства прерываний и копирования. Именно во избежание этих затрат транспортные протоколы часто реализуются в ядре. Как и число пакетов, количество переключений контекста можно снизить с помощью библиотечной процедуры, передающей данные во внутренний буфер до тех пор, пока их не наберется достаточное количество. Аналогично на стороне получателя небольшие сегменты следует собирать вместе и передавать пользователю за один раз, минимизируя число переключений контекста.
В идеале входящий пакет вызывает одно переключение контекста из текущего пользовательского процесса в режим ядра, а затем еще одно — при передаче управления принимающему процессу и предоставлении ему прибывших данных. К сожалению, во многих операционных системах происходит еще одно переключение. Например, если диспетчер сети работает в виде отдельного процесса в пространстве пользователя, поступление пакета вызывает передачу управления от процесса пользователя ядру, затем от ядра диспетчеру сети, затем снова ядру и, наконец, от ядра получающему процессу. Эта последовательность переключений контекста показана на илл. 6.51. Все эти переключения для каждого пакета сильно расходуют время центрального процессора и существенно снижают производительность сети.
Илл. 6.51. Четыре переключения контекста для обработки одного пакета в случае, если сетевой менеджер находится в пространстве пользователя
Лучше избегать перегрузки, чем восстанавливаться после нее
Старая пословица, гласящая, что профилактика лучше лечения, справедлива и в деле борьбы с перегрузками в сетях. Когда сеть перегружена, пакеты теряются, пропускная способность растрачивается впустую, увеличивается задержка и т.п. Все эти издержки нежелательны, а процесс восстановления после перегрузки требует времени и терпения. Гораздо более эффективной стратегией является предотвращение перегрузки, напоминающее прививку от болезни, — неприятно, зато избавляет от более крупных неприятностей.
Избегайте тайм-аутов
Таймеры в сетях необходимы, но их следует применять умеренно и стремиться минимизировать количество тайм-аутов. Когда срабатывает таймер, обычно повторяется какое-то действие. Если его повтор необходим, так и следует поступить, однако повторение действия без особой необходимости является расточительным.
Чтобы избегжать излишней работы, следует устанавливать период ожидания с небольшим запасом. Таймер, срабатывающий слишком поздно, несколько увеличивает задержку в случае (маловероятном) потери сегмента. Преждевременно срабатывающий таймер впустую тратит время процессора и пропускную способность, а также напрасно увеличивает нагрузку на, возможно, десятки маршрутизаторов.
6.7.6. Быстрая обработка сегментов
Теперь, когда мы поговорили об общих правилах, рассмотрим несколько методов, позволяющих ускорить обработку сегментов. Дополнительные сведения по этой теме можно найти в следующих источниках: Кларк и др. (Clark et al., 1989); Чейз и др. (Chase et al., 2001).
Накладные расходы по обработке сегментов состоят из двух компонентов: затраты на сегмент и на каждый байт. Нужно действовать сразу на обоих направлениях. Ключ к быстрой обработке сегментов — отделить стандартный, успешный случай (одностороннюю передачу данных) и разобраться с ним. Многие протоколы придают особое значение действиям в нештатной ситуации (например, при потере пакета). Но для быстрой работы протокола разработчик должен пытаться минимизировать время обработки данных в нормальном режиме. Снижение времени обработки при возникновении ошибок является второстепенной задачей.
Для перехода в состояние ESTABLISHED требуется передача последовательности специальных сегментов, но как только оно достигнуто, обработка сегментов не вызывает затруднений (пока одна из сторон не начнет закрывать соединение). Допустим, отправитель находится в состоянии ESTABLISHED и у него есть данные для передачи. Для простоты мы предположим, что транспортная подсистема расположена в ядре, хотя тот же принцип действует, если это процесс в пространстве пользователя или библиотека в передающем процессе. На илл. 6.52 передающий процесс вклинивается в процессы ядра, выполняя примитив SEND. Прежде всего транспортная подсистема проверяет, является ли этот случай нормальным: установлено состояние ESTABLISHED, ни одна сторона не пытается закрыть соединение, передается стандартный полный сегмент (без флага URGENT) и у получателя достаточный размер окна. Если все эти условия выполнены, то никаких дополнительных проверок не требуется, и алгоритм транспортной подсистемы может выбрать быстрый путь. В большинстве случаев именно так и происходит.
В стандартной ситуации заголовки соседних сегментов почти одинаковы. Чтобы извлечь из этого пользу, транспортная подсистема сохраняет в своем буфере прототип заголовка. В начале быстрого пути он как можно скорее пословно копируется в буфер нового заголовка. Затем поверх перезаписываются все отличающиеся поля. Часто они легко выводятся из переменных состояния — например, следующий порядковый номер сегмента. Затем указатель на заполненный заголовок сегмента и указатель на данные пользователя передаются сетевому уровню. Здесь можно применить ту же стратегию (на илл. 6.52 этого нет). Наконец, сетевой уровень передает полученный в результате пакет канальному уровню для отправки.
Илл. 6.52. Быстрый путь от отправителя к получателю обозначен жирной линией. Серым цветом выделены прямоугольники, обозначающие шаги обработки вдоль этого пути
Чтобы увидеть, как работает этот принцип на практике, рассмотрим случай TCP/IP. На илл. 6.53 (а) изображен TCP-заголовок. Поля, одинаковые для заголовков последующих сегментов в однонаправленном потоке, выделены серым цветом. Все, что нужно сделать передающей транспортной подсистеме, — это скопировать пять слов заголовка-прототипа в выходной буфер, заполнить поле порядкового номера (скопировав одно слово), сосчитать контрольную сумму и увеличить на единицу значение переменной, хранящей текущий порядковый номер. Затем она передает заголовок и