- PVSM.RU - https://www.pvsm.ru -
Когда кто-то произносит слово многоядерный, то мы бессознательно подразумеваем SMP. Это успешно срабатывало для нас до недавнего времени, пока ARM не объявила о big.LITTLE. Архитектура ARM big.LITTLE [1] является первым массово производимым примером архитектуры AMP [2], и как мы увидим далее, она поднимает планку сложности многоядерного программирования еще выше.
Все началось с сообщения об ошибке [3] с телефона с процессором, используемым чипсетом Exynos на телефонах Samsung в Европе. Приложения, созданные с помощью нашего ПО, падали с SIGILL в совершенно случайных местах. Ничто не могло разумно объяснить, что происходило, а само падение происходило с валидными процессорными инструкциями. Это сразу же заставило нас подозревать неудачную очистку кэша инструкций.
После рассмотрения всего JIT кода на предмет сброса кэша мы были уверены, что вызывали __clear_cache правильно. Это сподвигло нас посмотреть на то, как другие виртуальные машины [4] или компиляторы [5] производят сброс кэша на ARM64, и мы нашли список опечаток/исправлений для спецификации Cortex A53 [6]. Описания перечисленных проблем от ARM являются неопределенными и трудно воспринимаемыми, поэтому мы попробовали все же найти обходной путь. Но и здесь ничего не получилось.
Затем мы зашли с другой стороны. А может проблема в обработчике сигналов? Нет. Неуклюжая эмуляция процессора в пользовательском пространстве? Нет. Сломанная реализация libc? Хорошая попытка. Неисправное оборудование? Мы воспроизвели это на нескольких устройствах. Плохая удача или карма? Да!
Некоторые из нас не могли уснуть с такой удивительной головоломкой перед собой и продолжали смотреть на дампы приложений. Но была одна забавная вещь: неисправный адрес всегда был на третьей или четвертой строке дампов памяти.

Это была наша единственная зацепка, а когда дело касается такой трудной для понимания ошибки, то ни о каких-либо случайностях и речи быть не может. Наши дампы памяти были выровнены по 16 байт, в то время как SIGILL всегда происходило в диапазоне между [7] 0x40-0x7f или 0xc0-0xff. Поэтому мы отформатировали снимки памяти таким образов, чтобы легче было проверить работу аллокатора:
$ grep SIGILL *.log
custom_01.log:E/mono (13964): SIGILL at ip=0x0000007f4f15e8d0
custom_02.log:E/mono (13088): SIGILL at ip=0x0000007f8ff76cc0
custom_03.log:E/mono (12824): SIGILL at ip=0x0000007f68e93c70
custom_04.log:E/mono (12876): SIGILL at ip=0x0000007f4b3d55f0
custom_05.log:E/mono (13008): SIGILL at ip=0x0000007f8df1e8d0
custom_06.log:E/mono (14093): SIGILL at ip=0x0000007f6c21edf0
[...]
С помощью этого сформулировали первую хорошую гипотезу: неудачный сброс кэша происходил всегда на старших 64 байтах каждого 128-байтового блока. Эти цифры, если вы имеет дело с низкоуровневым программированием, сразу же напомнят о размерах кэш-линий. С этого момента все начало приобретать смысл.
Ниже приведен псевдо-код того, как libgcc делает сброс кэша на arm64 [8]:
void __clear_cache (char *address, size_t size)
{
static int cache_line_size = 0;
if (!cache_line_size)
cache_line_size = get_current_cpu_cache_line_size ();
for (int i = 0; i < size; i += cache_line_size)
flush_cache_line (address + i);
}
В примере выше get_current_cpu_cache_line_size представляет собой процессорную инструкцию, которая возвращает размер кэш-линий, а flush_cache_line очищает кэш-линию по заданному адресу.
В то время мы использовали собственную реализацию данной функции, поэтому решили отдельно запустить ее и вывести размеры кэш-линий процессором. И вдруг оно напечатало 128 и 64. Мы дважды проверили, что это было на самом деле. После этого мы взяли справочник данного процессора, и оказалось, что у старших ядер (big) размер кэш-линий составляет 128 байт, а младших (LITTLE) — 64.
Выходило так, что сначала __clear_cache мог быть вызван на big-ядре с 128 байтными кэш-линиями инструкций, а потом на одном из LITTLE-ядер, пропуская все остальные при сбросе. Проще некуда. Мы удалили кэширование и все заработало.
Некоторые процессоры ARM big.LITTLE могут иметь ядра с различными размерами кэш-линий, и в значительной степени ни один код не готов иметь дело с этим, т.к. предполагается, что все ядра являются симметричными.
Хуже того, даже набор инструкций ARM не готов к этому. Проницательный читатель может догадаться, что вычисление строки кэша при каждом вызове недостаточно для пользовательского кода: может так произойти, что процесс запускается на одном ядре, а выполняет __clear_cache с определенным размером строки кэша на другом, что может оказаться неправда. Таким образом, мы должны попытаться выяснить глобальный минимальный размер кэш-линий среди всех ядер. Здесь находится наше исправление для Mono: Pull Request [9]. Другие проекты позаимствовавшие наше исправление уже: Dolphin [10] и PPSSPP [11].
Автор: szKarlen
Источник [12]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/programmirovanie/236853
Ссылки в тексте:
[1] big.LITTLE: https://en.wikipedia.org/wiki/ARM_big.LITTLE
[2] архитектуры AMP: http://www.embedded.com/design/mcus-processors-and-socs/4429496/Multicore-basics
[3] с сообщения об ошибке: https://bugzilla.xamarin.com/show_bug.cgi?id=39859
[4] виртуальные машины: https://github.com/v8/v8/blob/fec99c689b8587b863df4a5c4793c601772ef663/src/arm64/cpu-arm64.cc#L40
[5] компиляторы: https://github.com/llvm-mirror/compiler-rt/blob/ff75f2a0260b1940436a483413091c5770427c04/lib/builtins/clear_cache.c#L146
[6] список опечаток/исправлений для спецификации Cortex A53: https://silver.arm.com/download/Unspecified/BX500-DA-10400-r0p0-08rel0/Cortex_A53_MPCore_Software_Developers_Errata_Notice_v18.pdf
[7] всегда происходило в диапазоне между: https://gist.github.com/lewurm/97dff0a56929b56a0fc5ab49af06fd06
[8] делает сброс кэша на arm64: https://android.googlesource.com/toolchain/gcc/+/master/gcc-4.9/libgcc/config/aarch64/sync-cache.c#54
[9] Pull Request: https://github.com/mono/mono/pull/3549
[10] Dolphin: https://github.com/dolphin-emu/dolphin/pull/4204
[11] PPSSPP: https://github.com/hrydgard/ppsspp/pull/8769
[12] Источник: https://habrahabr.ru/post/320342/?utm_source=habrahabr&utm_medium=rss&utm_campaign=best
Нажмите здесь для печати.