Использование нескольких процессоров — SMP


Компьютеры с несколькими процессорами существуют несколько десятков лет, но сейчас наблюдается взрывообразный рост их популярности. FreeBSD обеспечивает поддержку нескольких процессоров, начиная с версии 3, однако многопроцессорные и многоядерные архитектуры еще не столь широко распространены. В современных системах используется принцип симметричной многопроцессорной обработки (Symmetric Multiprocessing, SMP), который подразумевает систему с несколькими идентичными процессорами. (Да, в некоторых многопроцессорных системах не требуется, чтобы процессоры были идентичными. Возможно, как раз такая система имеется в вашем компьютере — в виде нескольких видеокарт, у каждой из которых есть свой специальный микропроцессор, отвечающий за вывод графических изображений.)

По сравнению с одним процессором SMP-система обладает многими преимуществами — и она не просто «более мощная». Если рассуждать на микроуровне, процессор за раз может выполнить только одну операцию. Процессы в компьютере конкурируют за время процессора. Если процессор выполняет запрос к базе данных, он не примет пакет, который пытается доставить карта Ethernet. Каждую долю секунды процессор переключает контекст и работает с другим процессом, назначенным ядром. Такое переключение происходит довольно часто и достаточно быстро, поэтому кажется, что одновременно выполняются несколько операций — подобно тому, как картинка на телеэкране движется за счет очень быстрой смены отдельных кадров.

В качестве программного обеспечения рабочего стола я использую WindowMaker, для отображения содержимого архивов почтовой рассылки — Firefox, a OpenOffice.org принимает текст, который я ввожу с клавиатуры. В систему постоянно прибывают сетевые пакеты, экран отображает текст, МРЗ-плеер передает мелодии Blue Oyster Cult в мои наушники — все это происходит одновременно. В действительности, «одновременно» это происходит только для моего слабого мозга. На самом деле компьютер просто очень быстро переключается с одной задачи на другую. В одну миллисекунду он посылает очередной звуковой фрагмент в мои наушники, в другую — обновляет изображение текста на экране.

При наличии нескольких процессоров компьютер может выполнять несколько операций одновременно. Это замечательно, однако такая способность чрезвычайно увеличивает сложность системы.

Допущения о работе ядра

Для понимания многопроцессорной обработки и проблем, связанных с ней, необходимо углубиться в работу ядра. Все операционные системы, в которых реализуется SMP, сталкиваются с одними и теми же проблемами. Следовательно, теория, представленная здесь, подходит для различных платформ. Однако FreeBSD в какой-то мере отличается от других операционных систем, поскольку опирается на 30-летнее наследие UNIX, а дальнейшая разработка FreeBSD не прекращается ни на месяц. Следующее описание представляет собой грубое упрощение. Конструкция ядра — мудреная тема. Почти невозможно описать ее точно на уровне, понятном для непрограммистов. Вот объяснение работы ядра в самом общем виде.

FreeBSD использует процессор по принципу квантов времени (time slices). Квант времени — это временной интервал, в течение которого процессор занимается одной задачей. Один процесс может использовать процессор в течение всего кванта либо до того момента, когда задача будет выполнена. После этого может быть запущен другой процесс. Для выделения квантов времени и их распределения между программами ядро применяет систему приоритетов. Если в системе появляется процесс, приоритет которого выше приоритета текущего процесса, ядро прерывает обработку первого процесса, или вытесняет его. Обычно такой режим называют вытесняющей многозадачностью (preemptive multitasking).

Хотя ядро запущено, это еще не процесс. Ядро запускает процессы. Процесс имеет определенные структуры данных, задаваемые ядром, и ядро управляет ими так, как считает нужным. Ядро можно представить как особый процесс, сильно отличающийся от других процессов. Он не может быть прерван другими программами — нельзя набрать pkill kernel и перезагрузить систему.

Ядро сталкивается с особыми проблемами, неведомыми остальным частям системы. Представьте, что имеется некоторая программа, которая выполняет передачу данных через сеть. Ядро принимает данные от программы и помещает их в область памяти, доступную для сетевой карты. Если компьютер занимается только одной какой-то задачей, то с той областью памяти или с сетевой картой ничего не случится, пока ядро не вернется к решению этой задачи. Однако при наличии нескольких процессоров компьютер одновременно может решать сразу несколько задач. Что если разные процессоры одновременно станут передавать данные сетевой карте? Сетевая карта попадет в положение человека, которому в одно ухо кричит начальник, а в другое — мама, и что бы он ни сделал, никому не нравится. А что если один процессор попытается выделить область памяти для решения сетевых задач, а другой ту же самую область памяти — для нужд файловой системы? Ядро просто запутается, и полученные результаты вас точно не удовлетворят.

В старых ядрах разных UNIX, в том числе FreeBSD, трудности с SMP преодолеваются так: ядро объявляется невытесняемым — его работа не может быть прервана. За счет этого управление ядром становится простым и ясным: если какой-либо модуль ядра занимает участок памяти, он может рассчитывать на данный участок при выполнении следующей команды. Никакой другой модуль ядра не может захватить эту память. Когда компьютеры могли выполнять всего одну операцию в каждый конкретный момент времени, можно было делать некоторые допущения. Но стоит начать выполнять одновременно несколько операций — и все допущения идут прахом.

SMP: первая попытка

Первая реализация поддержки SMP в FreeBSD была довольно проста. Процессы распределялись между процессорами так, чтобы нагрузка была приблизительно равномерной. Для ядра существовала специальная «блокировка» (lock). Для выполнения кода ядра процессор должен был захватить «блокировку», проверив перед этим, доступна ли она. Если «блокировка» была доступна, он захватывал ее и запускал ядро. Если же «блокировка» была недоступна, процессор знал, что ядро выполняется другим процессором, и переключался на обработку чего-то другого. Эта «блокировка» называлась Big Giant Lock (BGL). В такой системе ядро знало, что его данные не затронет ни один процесс. По существу, данная схема гарантировала, что ядро будет запущено только на одном процессоре, как это всегда и было.

Такая стратегия была довольно эффективной в случае двух процессоров. На двухпроцессорной машине можно было запустить базу данных среднего уровня и веб-сервер и быть уверенным, что подсистема процессоров не станет причиной снижения производительности. Если один процессор был занят обслуживанием веб-страниц, то другой мог обрабатывать запросы к базам данных. Однако на машине с восемью процессорами вас поджидали трудности. Значительную часть времени система ждала, пока освободится BGL!

Такая упрощенная реализация SMP неэффективна и немасштабируема. В руководствах по SMP этот метод упоминается редко, поскольку он довольно неуклюж. Тем не менее он лучше методов организации SMP, предлагаемых другими системами. Например, по умолчанию в двухпроцессорной системе Windows 2000 один процессор выделяется для пользовательского интерфейса, а другой — для всего остального. Это решение тоже редко упоминается в руководствах, хотя благодаря ему интерфейс становится быстрым, и мышь не замирает при увеличении нагрузки на систему.*

Современная реализация SMP

Новый метод симметричной многопроцессорной обработки данных был реализован в версии FreeBSD 5.0 и продолжает совершенствоваться по сей день. В операционной системе FreeBSD блокировка BGL была раздроблена на множество маленьких блокировок, и теперь каждая подсистема ядра использует для решения своих задач минимальную возможную блокировку.

Изначально блокировки были реализованы в таких базовых элементах инфраструктуры ядра, как планировщик (часть ядра, которая занимается распределением квантов времени между задачами), сетевой стек, стек дисковых операций ввода-вывода и т. д. Это сразу положительно сказалось на производительности, потому что в то время, как один процессор занимался планированием задач, другой мог заниматься обработкой сетевого трафика. Затем блокировки были сдвинуты еще глубже — в различные компоненты ядра. Для каждой части сетевого стека была создана своя блокировка, для каждой части подсистемы ввода-вывода — своя и т.д., что позволило ядру одновременно выполнять несколько операций. Такие отдельные подпроцессы ядра называются потоками (threads). Каждый тип блокировки предъявляет свои собственные требования. Вам встретится упоминание самых разных типов блокировок, таких как мьютексы, sx-блокировки, rw-блокировки, спин-блокировки и семафоры. У каждого из этих типов есть как преимущества, так и недостатки, и задействовать их в программном коде ядра нужно особенно аккуратно.

Проблема дробления блокировок на самом деле гораздо сложнее, чем может показаться. Если раздробить блокировки чересчур тонко, то основным занятием ядра станет не обработка данных, а обслуживание блокировок. Если же раздробить блокировки недостаточно тонко, то система будет простаивать в ожидании освобождения блокировок. Степень дробления, достаточная для обеспечения высокой эффективности в двухпроцессорной системе, может оказаться совершенно неприемлемой в тридцатидвухпроцессорной системе. Поиск оптимального решения занял годы и будет продолжаться в течение всей жизни FreeBSD.

Проблемы SMP: взаимоблокировка, клинч и нарушение порядка приобретения блокировок

Все блокировки ядра применяются по сложным правилам и взаимодействуют друг с другом миллионом способов. Правила защищают от непреднамеренного взаимовлияния блокировок. Предположим, что потоку ядра А потребовались ресурсы Y и Z; потоку ядра В также потребовались ресурсы Y и Z, но при этом поток В должен сначала захватить ресурс Z, а потом Y. Если поток А захватит ресурс Y, а поток В в это же время захватит ресурс Z, то поток А остановится в ожидании освобождения ресурса Z, а поток В — в ожидании освобождения ресурса Y. Такой клинч дестабилизирует систему и, скорее всего, приведет к ее остановке. Правильная установка блокировок поможет избежать этой проблемы.

Возможно, вам приходилось увидеть на экране сообщение о неверном порядке приобретения блокировок — Lock Order Reversal (LOR), которое означает, что порядок приобретения блокировок был нарушен. Хотя это предупреждение не всегда свидетельствует о неминуемой гибели, тем не менее его не следует оставлять без внимания.

Параметр настройки ядра WITNESS позволяет активизировать функцию слежения за порядком приобретения блокировок. По умолчанию, в версии FreeBSD-current (глава 13) параметр WITNESS включен, и если вы отправите отчет о проблемах в вашей системе, команда разработчиков может попросить вас активизировать этот параметр. WITNESS вынуждает ядро инспектировать каждую операцию приобретения блокировки на предмет нарушения порядка следования, что снижает производительность системы. Тем не менее использование параметра WITNESS, чтение сообщений и реакция на них- это неоценимый способ помочь в улучшении FreeBSD.

Реакция на нарушение порядка приобретения блокировок

При получении одного из сообщений Lock Order Reversal, скопируйте его целиком. Эти сообщения дополнительно протоколируются в файле /var/log/messages. Получив сообщение, проверьте страницу с сообщениями Lock Order Reversal по адресу http://sources.zabbadoz.net/freebsd/lor.html. Здесь содержится список всех ранее встреченных сообщений о нарушении порядка приобретения блокировок. Сравните первые несколько строк из этого списка со своим сообщением LOR. Если ваше сообщение присутствует в списке, сопутствующий комментарий позволит вам понять, что делать. Многие сообщения LOR, представленные на этой странице, не представляют опасности, а некоторые уже исправлены в более поздних версиях FreeBSD.

Если ваше сообщение LOR отсутствует в списке на сайте, просмотрите архивы почтовых рассылок. Если ваше сообщение LOR встретится в почтовой рассылке, прочитайте сообщение и выполните рекомендуемые действия. Не стоит посылать сообщение «и у меня тоже» в почтовую рассылку, если разработчик явно не выразил желание получать уведомления о сообщении LOR данного типа.

Если вам встретилось совершенно новое сообщение LOR, поздравляю! Обнаружение нового сообщения LOR такое же редкое событие, как открытие нового вида насекомых, — вы не сможете дать этому сообщению свое имя, но это очень поможет проекту FreeBSD. Отошлите отчет о проблеме, как описано в главе 21. Укажите полную информацию о своей системе, особенно о том, какие задачи решались во время появления сообщения.

Процессоры и SMP

Существует три вида многопроцессорных систем: системы с несколькими процессорами, многоядерные процессоры и процессоры с поддержкой технологии HyperThreading. Понимать различия между ними совершенно необходимо, так как поведение системы и приложений напрямую зависит от различий в типах процессоров.

Несколько процессоров — это отдельные чипы на материнской плате (то есть то, что мы привыкли называть процессорами), способные сотрудничать друг с другом. Все процессоры в таких системах независимы, каждый из них обладает полным комплектом особенностей (такими как регистры, кэш и т. д.) и каждый управляется своим собственным аппаратным окружением.

Многоядерный процессор содержит несколько вычислительных устройств (ядер) в одном чипе. Популярность многоядерных процессоров продолжает расти, потому что многоядерные процессоры менее энергоемки и меньше нагреваются, чем сопоставимые многопроцессорные системы. Ядра могут совместно использовать такие встроенные аппаратные компоненты, как регистры и кэш. Каждое ядро в среднем работает медленнее, чем отдельный процессор, но наличие нескольких ядер позволяет параллельно выполнять несколько операций. Многоядерные процессоры более производительны в случае многопоточных приложений или в системах с несколькими процессами, а приложения, выполняющиеся как монолитный процесс, показывают лучшую производительность на одноядерном процессоре. На компьютере, особенно на сервере, может быть несколько многоядерных процессоров.

HyperThreading — это технология компании Intel, позволяющая одноядерному процессору разбить себя на два виртуальных процессора. Однако виртуальный процессор нельзя назвать полноценным процессором, поскольку второй виртуальный процессор задействуется, только когда первый (основной) простаивает в ожидании какого-либо события. Строго говоря, SMP — это симметричная многопроцессорная обработка, то есть все процессоры должны быть идентичны. Виртуальный процессор не обладает такой же производительностью, как реальный, поэтому их нельзя рассматривать как идентичные. Теоретически возможно написать планировщик задач, который будет использовать преимущества этой технологии, но для большинства типов решаемых задач в этом практически нет никакой пользы. Ни одна из UNIX-подобных операционных систем не обладает планировщиком, который бы полностью поддерживал все возможности технологии HyperThreading. Кроме того, эта технология таит в себе различные проблемы, связанные с безопасностью. Задача, исполняемая на одном виртуальном процессоре, может перехватывать данные, например ключи шифрования, принадлежащие задаче, исполняемой на другом виртуальном процессоре. По этой причине в операционной системе FreeBSD поддержка HyperThreading отключена по умолчанию. Если вам будет интересно попробовать эту технологию в действии на процессорах Intel, установите параметр sysctl machdep.hyperthreading_allowed в значение 1 и оцените производительность приложений. Для систем, где отсутствуют внешние пользователи, описанная выше проблема безопасности, вероятно, не имеет большого значения. (Если у вас реализованы виртуальные системы в клетках, это означает, что у вас имеются внешние пользователи.)

Применение SMP

Применяя SMP, помните, что несколько процессоров не обязательно ускоряют выполнение операций. Один процессор может выполнять определенное количество операций в секунду; наличие второго процессора означает, что компьютер может выполнять в два раза больше операций в секунду, однако эти операции не обязательно выполняются быстрее.

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

Один процессор может выполнять только одну операцию в каждый конкретный момент времени, один процесс может выполняться только на одном процессоре в каждый конкретный момент времени. Большинство процессов не могут выполнять свою работу сразу на нескольких процессорах. Исключение составляют многопоточные программы, как будет показано ниже, в этой же главе. Некоторые программы преодолевают это ограничение, запуская сразу несколько процессов, что позволяет операционной системе распределять их между процессорами. Такая методика уже много лет применяется в Apache. Многопоточные программы написаны специально для выполнения на нескольких процессорах и используют другую методику. Многие многопоточные программы просто создают несколько потоков обработки данных и распределяют исполнение потоков между несколькими процессорами, что представляет собой простую и достаточно эффективную реализацию параллельных вычислений. Другие программы вообще не используют преимущества многопроцессорных систем. Если один из процессоров занят на все сто процентов, а другие практически простаивают, следовательно, запущена программа, не поддерживающая многопроцессорную обработку данных. Более подробно о вопросах производительности мы поговорим в главе 19.

SMP и make(1)

Утилита make(1), которая выполняет сборку программ, может запускать сразу несколько процессов. Если программа написана аккуратно, ее сборка может выполняться сразу несколькими процессами. Такой подход не поможет увеличить скорость сборки маленьких программ, но при сборке крупных программных продуктов, таких как сама операционная система FreeBSD (глава 13) или OpenOffice.org, наличие нескольких процессоров может существенно ускорить процесс сборки. Чтобы указать системе, сколько процессов следует запускать одновременно, нужно передать утилите make(1) ключ -j и число процессов. Оптимальным выбором будет число процессоров или ядер плюс один. Например, в двухпроцессорной системе, где каждый процессор имеет по два ядра, можно было бы запустить пять процессов сборки программы.

# make -j5 all install clean

He все программы можно собирать с ключом -j. Если дела пойдут плохо, уберите ключ -j и повторите попытку.

В главе 19 представлены различные способы оценки и измерения производительности.

Комментарии запрещены.