CPU hotplug
Опубликовано 29.10.2013 13:17 пользователем Peter Lemenkov
Инженер 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():
Чтоб произвести добавление или удаление ядра нужно дождаться, пока счетчик 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 такова, что там постоянно отключаются и включаются заново в работу ядра микропроцессора (энергосбережение). Т.е. в очередной раз разработчики "больших" систем помогают маленьким.
Еще совсем недавно, технология добавления микропроцессоров без остановки системы не была очень актуальной для пользователей. Лишь счастливые системные администраторы мэйнфреймов (администрирование мэйнфрейма дает почетное право запостить скриншот с настоящим мэйнфреймовым 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 такова, что там постоянно отключаются и включаются заново в работу ядра микропроцессора (энергосбережение). Т.е. в очередной раз разработчики "больших" систем помогают маленьким.