В статье «СКОРОСТЬ ЗАГРУЗКИ СЕРВЕРОВ ЭЛЬБРУС» мы сравнили время работы программы начального старта (ПНС) Эльбрус с длительностью загрузки ОС на различном оборудовании Intel.
Исследование показало, что даже при подкачке ОС с NVME, более высоких частотах ЦП, меньшем числе южных мостов и равных объемах ОЗУ, скорость загрузки Intel не превосходит по времени Эльбрус. В этой статье рассмотрим, как ПНС Эльбрус ускоряет загрузку. Ниже список основных инструментов, обеспечивающих ускорение:
Отдельно можно выделить загрузку ОС с SSD. Но это уже больше аппаратный, чем программный подход. По перечню видно, что ПНС Эльбрус, по сути компактная ОС, где применяются общеизвестные механизмы построения операционных сред. Рассмотрим детально работу ключевых механизмов в контексте ускорения.
РЕЖИМ BROADCAST ДЛЯ MC И PHY
В архитектуре E2K.V6 реализована поддержка быстрой настройки ОЗУ путем рассылки широковещательных сообщений контроллерам памяти (MC) и физическим уровням (PHY) DDR4. Более полные сведения о специфике V6 можно почерпнуть из документации. Поддержка BroadCast реализована в файле ddr4bc.c. Ниже doxygen-заголовок этого файла.
/**
* @file ddr4bc.c
* @note DDR4 BroadCast (BC) - широковещательный режим конфигурирования и настройки DDR4: PHY и MC.
* @brief Реализация инициализации PHYs и MCs DDR4 в режиме BroadCast.
* @internal Implementation. ЦЕЛЬ - нужно добавить возможность инициализировать PHYs и MCs в режиме BC (MC_CH=0xf).
* Разрешение включения данного режима - флаг yBroadCast в NVRAM, управляемый через верхнюю консоль BOOT. Запрещено включать режим, если:
* 1) хотя бы один канал памяти не используется
* 2) не заполнен 0-ой слот в каком-то канале
* 3) каналы заполнены несимметрично по слотам или размерам
* 4) в каналах установлены разные модели планок памяти
* 5) включен debug режим при инициализации PHY
* @note Надо учесть, что в BroadCast работают только конф.команды записи.
* ПОЭТОМУ при конфигурационном чтении нужно всегда заранее задавать № канала в регистре MC_CH. ОТСЮДА:
* 1) Опрос PHYs по статусу завершения тренировки остается локальным (последовательным, канал за каналом).
* 2) Иниц. MC_TIM1/TIM2 надо оставить локальной, т.к. часть полей настраивается по результатам тренировки на основе инфы из PHY соответствующего MC.
* 3) Запись MCx_CTL.mcstart = 1 надо оставить локальной, иначе рефреши во всех MC будут выполняться синхронно с неизвестным эффектом для системы.
* @bug http://bugzilla.lab.sun.mcst.ru/bugzilla-mcst/show_bug.cgi?id=133704
* @see e16_mc_config.pdf @author Igor Petrov
* @link http://www.lab.sun.mcst.ru/~E16C/4 Конфигурация DDR4PHY
* @link http://www.lab.sun.mcst.ru/honey/elbrus_16c/doc/hw/mc/e16_MC.pdf
* @author Copyright (C) 2019, msct.ru
* @addtogroup DDR4BC
* @{
*/
Режим BroadCast опирается на технологию Cache-As-Ram. CAR позволяет использовать кэш процессора в качестве ОЗУ до инициализации самого ОЗУ. Оболочка BroadCast имплементирована в функции DDR4BC_init_channels. Её код приведен в ПРИЛОЖЕНИИ-1.
АЛГОРИТМ ШИРОКОВЕЩАТЕЛЬНОЙ НАСТРОЙКИ RAM
Обобщенно в функции DDR4BC_init_channels выполняем следующее. Широковещательно запрещаем работу всех каналов. В режиме отладки покажем частоту памяти. Запомним № MC плюс рабочая установка канала. Запускаем физические уровни DDR4 ЦП широковещательно. Если нет сбоя PHY, то по каждому MC локально ждем статуса завершения настройки PHY. Далее на лету регулируем уровень VREFDQ. Широковещательно настраиваем ряд регистров контроллеров DDR4: TIM0/2, CFG, PERF0, CTL и пр. После настройки PHYs широковещательно инициализируем контроллеры памяти — mc_channel_regs_init_all(node,MC_ALL). Далее в цикле локально по каждому MC ждем завершения инициализации. Если включен DEBUG, показываем состояния всех регистров PHY_0 и MC_0. Если нужно, отрабатываем широковещательно для MC режим selfrefresh. Как итог, выдаем полученную максимальную маску MC в режиме BC.
МНОГОПОТОЧНАЯ ИНИЦИАЛИЗАЦИЯ
Многопоточность не является чем-то сверхъестественным, а ее использование для повышения скорости работы стало нормой. В частности, ПНС E2K.V6 применяет многопоточность для сокращения времени запуска ядер ЦП. Базовый алгоритм — последовательный. В нем ядра ЦП настраиваются в цикле одно за другим. В многопоточном режиме кооперативный планировщик на BSP-ядре раздает AP-ядрам задания на запуск путем рассылки программных сообщений. В свою очередь, AP-ядра после самонастройки отправляют ему сообщения об окончании инициализации. Процесс заканчивается, когда планировщик дождался ответа от всех рецепиентов. Для ускорения загрузки сообщения доставляются через NBSR регистры. При этом каждому ядру соответствует свой регистр. Интерфейс многопоточности представлен в файле multi_thread.h и содержит ряд опорных функций:
/// =============================================================================
/// @interface
/// =============================================================================
MTHR_init(); /// @~ Инициализация
/// @~ Сеттеры и Геттеры
MTHR_setMSG( val,...);
MTHR_getMSG( ...);
MTHR_putMSG( val,...);
MTHR_letMSG( ...);
MTHR_set_cmos_default(); /// @~ Задать флаг в энергонезависимой (NVRAM,FRAM,EEPROM,FLASH) по умолчанию
MTHR_invert_flag_via_console(); /// @~ Инвертировать флаг режима в энергонезависимой через консоль
MTHR_exist ( void ); /// @~ Проверка наличия режима
/// @~ Обмен сообщениями
MTHR_saveMSGs ( void );
MTHR_undoMSGs ( void );
MTHR_initMSG ( u8 cpu, u8 core, u32 val );
MTHR_initMSGs( void );
MTHR_showMSG ( u8 cpu, u8 core );
MTHR_showMSGs( void );
КАК ЭТО РАБОТАЕТ
Обобщенный алгоритм многопоточной инициализации такой. После подачи питания настраивается BSP-ядро, которое детектирует материнку и запускает BISTы. Затем инициализирует южный мост, I2C-SPI, TTY, конфигурирует линки, нумерует ЦП, разрешает когерентность, подкачивает ROM_DATA и BSS, формирует стеки, определяет тип и размер RAM и потом широковещательно настраивает BSP ОЗУ. Далее BSP-ядро выполняет маршрутизацию, создает карту регионов памяти и выделяет место для размещения кучи. После настройки BSP наступает очередь SMP_deploy(). Вызывается планировщик, который ищет живые AP-ядра и многопоточно поднимает их. AP-ядра настраивают себя, выполняя действия схожие с теми, что делались на BSP. После развертывания SMP строятся PCI-деревья, устанавливаются драйвера и если надо загружается ОС. Доступ к общим ресурсам (RAM, TTY, FLASH и пр.) синхронизируется через SpinLock — низкоуровневый примитив синхронизации, применяемый в многопроцессорных системах для взаимного исключения исполнения на базе цикла активного ожидания. Спин-блокировка применяется, когда ожидание захвата предполагается недолгим или контекст выполнения не предполагает перехода в состояние пассивного ожидания.
РЕЖИМ SILENCE
Включение режима тишины в параметрах настройки ПНС Эльбрус приводит к тому, что функции TTY перестают генерировать выходные данные. Какой практический результат наличия данной опции? За счет запрета вывода в консоль удалось реально повысить скорость загрузки. Кроме того в режиме SILENCE все технологические паузы автоматом приводятся к минимуму вплоть до 0.
НАСТРОЙКА ПАРАМЕТРОВ РАБОТЫ
Режим тишины не единственный конфигурационный параметр. Конфигурирование в целом — это важнейший управляющий функционал среды ПНС Эльбрус. Благодаря ему становится возможной тонкая и точная настройка оборудования и ПО. Конфигурирование обеспечивает параметрический доступ к отдельным составляющим системы, что облегчает разработку и упрощает наладку. Как было показано в статье, время перезапуска напрямую зависит от аппаратной конфигурации. «Объем RAM играет тут первую скрипку. Увеличение числа ЦП, ядер ЦП и южных мостов замедляет загрузку. Частота ЦП и особенно скорость дисков существенно влияют на время загрузки». Для ускорения работы системы и/или оптимизации её работы на помощь оператору E2K-машин приходит конфигурирование. Что в этой связи предлагает МЦСТ в качестве возможностей по настройке E2K-машин? Конфигуратор ПНС Эльбрус версии 6 позволяет:
ПНС позволяет включить/выключить из конфигурации указанные модули памяти в заданных слотах
Или позволяет оператору задать тип материнской платы для работы
ПНС позволяет отказаться от работы в режиме АПМДЗ
ПНС позволяет оператору отключить режим мультилинка
В ПЕРСПЕКТИВЕ
Время работы ПНС Эльбрус на E2C3 около 7 секунд. В этом легко убедиться, включив любую такую машину. В дальнейшем разработчики МЦСТ предполагают реализовать параллельную загрузку драйверов ПНС и сократить аппаратные задержки в RC-цепочках при включении питания. Ожидаемый выигрыш тут 2-3 секунды. Поэтому в будущем ПНС Эльбрус станет работать еще быстрее …
ПРИЛОЖЕНИЕ-1
/**
* @brief Функция DDR4BC_init_channels()
* @internal Инициализация памяти в режиме BroadCast (широковещательно)
* @note В режиме BC общие SPD настройки берутся из канала 0.
* @param[in] u8 node
* @return void
*/
u8 no_stk DDR4BC_init_channels( u8 node )
{ _PROC(""); /// @~ Покажем вход в функцию в режиме отладки
int micro_secs = -2 ; /// @~ Пауза две микросекунды
u8 trys = 0 ; /// @~ Число попыток перетренировки DDR4 PHY
/// „„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„
/// @internal
/// ”””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””
MC_CTL_disable_channel( MC_ALL ); /// @~ Disable all channels
{
if ( is_watch() ) mc_show_freq( node, MC_0 ); /// @~ Покажем тек.freq работы MC
DDR4BC_assign_mc( MC_ALL ) ; /// @~ Запомнить номер MC, плюс рабочая установка канала
/// @~ Если нет сбоя тренера PHY тогда:
if ( OKEY == ddr4_init( MC_ALL) ) PHY_waitInitDoneForAll_MCs(); /// @~ по каждому MC - жду статуса завершения init
else HALT( TRUE, "ERR BC" );
{ PWR_t pwr = { .v = MC_PWR_let( MC_0 ) } ; /// @bug 131486: Низкая FreqMEM у чипов RCD от Montage
pwr .s.pdmod = PWR_PDMOD_0 ; /// @~ Режим энергосбережения отключен
MC_PWR_put( MC_ALL, pwr.v ) ;
}
/// @~ Fly-Настройка уровня VREFDQ.
if ( IsDDR4_VREFDQ_CHANGE_EN() ) /// @~ If в CMOS флаг DDR4_VREFDQ_CHANGE_EN
{ ODT_t odt_r = { MC_ODT_let( MC_0 ) }; /// @~ Rd val of SDRAM VREFDQ from MC_ODT
u8 vrefdq = odt_r.v & 0x7f ;
if ( odt_r.s.rdodt2 & 0x1 ) /// @~ Change SDRAM VREFDQ
{ MC_TIM0_init ( node, MC_ALL ) ;
DDR4BC_init_mc_reg( node, MC_ALL, MC_TIM2_init );
MC_CFG_init ( node, MC_ALL ) ;
MC_PERF0_init ( node, MC_ALL ) ;
MC_CTL_start_channel( MC_ALL ) ;
CTL_t ctl_r = { MC_CTL_let( MC_0 ) }; /// @~ Читаем содержимое регистра CTL
ctl_r.s.mrs_en = ON; MC_CTL_put( MC_ALL, ctl_r.v ); /// @~ Set MC_CTL.mrs_en = 1
MOPMd_t mopmd = { vrefdq }; mopmd.s.VDQTRG = ON; mopmd.s.RCDcw = 0x7;
MC_MOPMd_put( MC_ALL, mopmd.v ); /// @~ Set MC_MOPMd.VREFDQ
ECHO2( "MC", MC_ALL, " VrefDq:", vrefdq )
MC_MOPMd_init( node, MC_ALL ) ; /// @~ Set correct MC_MOPMd.tCCD_L
ctl_r.s.mrs_en = 0; MC_CTL_put( MC_ALL, ctl_r.v ); /// @~ Set MC_CTL.mrs_en = 0
}
}
/// @~ END - Настройка на лету уровня VREFDQ
/// @~ После настройки PHY настраиваем МС
mc_channel_regs_init_all ( node, MC_ALL );
for ( u8 ch = 0; ch < ddr4.mc.max; ch++ )
{ MC_CTL_enable_channel( ch );
MC_CTL_start_channel( ch ); /// @~ Set CTL.mcstart = 1 последовательно каждому каналу
MC_STATUS_t mc_st = { .v = MC_STATUS_let( ch ) }; /// @~ Читаем последовательно статусы
/// @~ Задана перетренировка каналов памяти
if ( mc_st.s.phyccm_par_err || mc_st.s.dmem_par_err || mc_st.s.bridge_par_err )
WARN3( "Need ban ERR MC", ch, " trys:", trys++, " sts=", mc_st.v );
/// @~ Если в рег.статуса есть сбои то HALTs
HALT( (u8)mc_st.s.phyccm_par_err, "SRAM PARITY" );
HALT( (u8)mc_st.s.dmem_par_err , "DMEM PARITY" );
HALT( (u8)mc_st.s.bridge_par_err, "BRDG PARITY" );
}
if ( is_watch() ) { DDR4_show_all_regs( node, MC_0 ) ; /// @~ Show regs for PHY
MC_show_all_regs ( node, MC_0 ) ; /// @~ Show regs for MC
}
if ( IsGearDown() )
{ CFG_t cfg_r = { .v = MC_CFG_let( MC_0 ) } ; /// @~ Читаем MC_CFG в его в текущем состоянии
cfg_r .s.mt = TRUE ; /// @~ Включаем режим расширения фазы адреса
MC_CFG_put( MC_ALL, cfg_r.v ) ; /// @~ Устанавливаем MC_CFG.mt = ON
_ECHO( "SelfRefresh MC:", MC_ALL ) ; /// @~ Выводим сообщение + № инициализированного MC
PWR_t pwr_r = { .s.pdmod = PDMOD_SELFREFRESH , /// @~ Режим self-refresh
.s.memhot_en = TRUE , /// @~ Прореживание запросов для защиты от перегрева
.s.memhot_sense = 0 , /// @~ Интервал в тактах mc_clk, входного MEM_HOT
.s.pdg = TRUE , /// @~ Режим power-down одновременно ко всем Rankes
.s.pdtmr = PWR_PDTMR_MIN , /// @~ Таймер режима энергосбережения в Min
.s.memhot_throttle = 0 } ; /// @~ Интервал в тактах для выдачи по одной команде
MC_PWR_put( MC_ALL, pwr_r.v ); clk_mdelay(-10); /// @~ Set SelfRefresh
pwr_r.s.pdmod = PDMOD_NONE; MC_PWR_put( MC_ALL, pwr_r.v);/// @~ Exit selfrefresh
}
}
return MC_get_max_mask() ; /// @~ Выдаем максимальную маску MC в режиме BC
} /// @~ „„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„ End Function