CPU hotplug

Инженер Red Hat, Peter Zijlstra, переделал механизм CPU hotplug в ядре.

Еще совсем недавно, технология добавления микропроцессоров без остановки системы не была очень актуальной для пользователей. Лишь счастливые системные администраторы мэйнфреймов (администрирование мэйнфрейма дает почетное право запостить скриншот с настоящим мэйнфреймовым uname -a, на зависть администраторам Linux-систем) рассказывали, как здорово на лету заменять микропроцессоры без остановки системы. Обычно, количество микропроцессоров в системе изменялось лишь в момент выключения. Все изменилось с массовым внедрением облачных систем и виртуализации. Пользователи все чаще спрашивали - если мы абстрагировались от "физического" оборудования, то можем ли мы добавлять ядра в виртуалки в ЧНН, и отбирать их позже? К сожалению, несмотря на практическую ценность и техническую реализуемость в Linux, делать это не советовали (например, можно обратить внимание на доклад Михаила Кулемина о динамическом управлении ресурсами в KVM c прошедшего Fedora Virtualization Day). Одной из причин была низкая эффективность ряда механизмов в ядре, используемых при подключении и отключении микропроцессоров. Как раз этим и занялся Peter.

В статье на LWN история изменения описана подробно, а мы лишь вкратце раскажем. Суть в том, что нельзя просто так подключить и отключить ядро - kernel должен быть готов к этому. Любой поток может заблокировать CPU hotplug, и сейчас делается так - есть две функции, get_online_cpus() и put_online_cpus(). Первая увеличивает специальный глобальный счетчик на единицу, каждый раз, когда вызвана, а вторая, соответственно, уменьшает на единицу. В каждой из этих двух функций, код очень простой и понятный. Например, в get_online_cpus():


mutex_lock(&cpu_hotplug.lock);
cpu_hotplug.refcount++;
mutex_unlock(&cpu_hotplug.lock);


Чтоб произвести добавление или удаление ядра нужно дождаться, пока счетчик cpu_hotplug.refcount упадет до нуля, захватить мьютекс cpu_hotplug.lock, изменить количество ядер, и отпустить мьютекс cpu_hotplug.lock.

Простой и понятный, к сожалению, совсем не означает "эффективный" и "быстрый". Проблема в том, что изменение количества ядер происходит редко (как уже было сказано, порой один раз за время работы машины), а вот вызывать get_online_cpus/put_online_cpus система может очень часто. Это приводит к серьезной потере производительности, т.к. захват мьютекса и изменение глобального счетчика затрагивает сразу все процессорные ядра системы (на то они и глобальные объекты). Peter пришел к выводу, что для задачи требуется написать специализированный механизм синхронизации, не использующий мьютексы почти никак.

Для начала Peter разделил все потоки на две группы - те, кто полагаются на неизменное количество ядер ("читатели", которые вызывают get_online_cpus/put_online_cpus), и те, кто устанавливают его ("писатели"). Peter решил сделать так - заведем по одному локальному счетчику количества "читателей" на каждом из ядер микропроцессора, так, что его увеличение или уменьшение будет работать независимо от других ядер, а вот "писатель" будет устанавливать специальный глобальный флаг, перенаправляющий новые процессы "читателей" по старому, медленному пути, ждать завершения уже стартовавших процессов быстрых "читателей" (обнуление всех локальных счетчиков "читателей"), а уж потом начинать изменение используя старый медленный алгоритм. Подключение процессора это немного замедлит, зато ускорит работу "читателей". К процессу обсуждения механизма подключился его коллега из Red Hat, Oleg Nesterov, предложивший вариант уменьшения времени ожидания "писателем" с использованием RCU, что еще улучшит ситуацию.

Изменение можно ожидать начиная с версии 3.13 - вряд ли успеют в 3.12. Интересно, что помимо KVM одним из основных потребителей нового механизма может стать Android. Специфика работы мобильного Linux такова, что там постоянно отключаются и включаются заново в работу ядра микропроцессора (энергосбережение). Т.е. в очередной раз разработчики "больших" систем помогают маленьким.