3. Отличия в интерфейсах¶
Глава описывает любые отличия в поведении системных компонент на Эльбрусе, из-за которых может понадобиться адаптация программ для их корректной работы.
3.1. Совместимость с компиляторами¶
Компилятор lcc стремится к максимальной совместимости с gcc. Версия lcc-1.24 соответствует gcc-7.3.0.
3.1.1. Конструкции языка¶
3.1.1.1. Variable length array inside a struct¶
Не поддержана недокументированная функция gcc: Variable length arrays (VLA) в середине структуры.
3.1.1.2. Nested functions¶
Не поддержан элемент диалекта GNU C: nested functions.
3.1.2. gcc builtins¶
Ряд билтинов (builtin) gcc поддержан с ограничениями. Некоторые из них вызваны особенностями реализации компилятора, другие отсутствием практической необходимости. С выходом новых версий lcc расширяется состав поддержанных билтинов.
Ограничения перечислены в разделе документации на компилятор:
/opt/mcst/doc/builtin_gnu.html
.
3.1.3. Прагмы¶
Информация о поддержанных прагмах в документации компилятора:
/opt/mcst/doc/pragma.html
При сборке ПО иногда встречается особенность с неподдерживаемыми выражениями в прагмах. Для архитектуры Эльбрус следует использовать только константы и переменные.
Вместо
#pragma omp parallel for if(!singleThreaded) schedule(dynamic)
нужно написать
bool multiThreaded = !singleThreaded;
#pragma omp parallel for if(multiThreaded) schedule(dynamic)
3.1.4. OpenMP¶
3.1.4.1. Возможности¶
Поддержан стандарт OpenMP 3.1.
Доступны языки C, C++, Fortran.
3.1.4.2. Ограничения¶
Для e2k не поддержано в режиме -m128.
Nested параллелизм не поддержан. Если при исполнении уже распараллеленного цикла встречаются циклы, которые нужно распараллелить, то эти (вложенные) циклы будут исполняться последовательно.
Не поддержан clause collapse.
Для C/C++ после директивы
#pragma omp
всегда должен следовать statement языка. Проблемы могут возникнуть для#pragma omp barrier
и#pragma omp flush
, если за ними нет statement’а. Для обхода проблемы рекомендуется в следующей строке поставить пустой statement, например “0;” или “;”Переменные, перечисленные в clause’ах private, lastprivate, firstprivate и threadprivate должны иметь скалярный базовый тип или массив скалярного базового типа. В противном случае результат программы неопределен.
Директива
#pragma omp for
не поддержана для итераторов C++.Для C/C++ clause’ы if и num_threads своими параметрами могут иметь только константы и переменные целого типа, выражения не допускаются.
3.1.4.3. Справочный файл¶
В компиляторе информация об OpenMP хранится здесь:
/opt/mcst/doc/openmp.html
3.2. Системные интерфейсы¶
3.2.1. makecontext¶
Функция makecontext()
для управления контекстом пользователя реализована на Эльбрусе с другой семантикой.
Вместо вызова makecontext()
необходимо вызывать makecontext_e2k()
. Функции дано другое название, чтобы отображать несовместимость с реализацией на других архитектурах.
makecontext_e2k()
- в отличие от makecontext()
, дополнительно выделяет
аппаратные стеки, из-за чего является более тяжеловесным.
Можно вызвать makecontext_e2k()
дважды на один и тот же контекст, не выполняя freecontext_e2k()
.
Для ядер >= 5.4 контекст будет переиспользован напрямую, что гораздо быстрее, чем освобождать его через freecontext_e2k()
и выделять новый. Перед этим следует убедиться,
что контекст не используется. При повторном использовании makecontext можно подменять стек, т.е. с точки зрения пользователя вызов
«makecontext + makecontext» полностью эквивалентен вызову «makecontext + freecontext + makecontext», за исключением производительности.
makecontext_e2k()
возвращает значение int
, а не void
. Значение вызова необходимо проверять на статус ошибки (< 0).
Возможные ошибки:
EFAULT - не удалось обратиться по указателю ucp или ucp->uc_stack;
ENOMEM - в системе недостаточно памяти;
EINVAL - подозрительное содержимое ucontext_t;
EBUSY - ucp->uc_stack уже отдан под другой контекст.
3.2.2. freecontext¶
При использовании makecontext_e2k()
в конце области видимости необходимо вызвать freecontext_e2k()
.
freecontext_e2k()
- освобождает связанные с контекстом аппаратные стеки.
Если контекст в данный момент занят, то реальное освобождение произойдёт,
только когда он перестанет использоваться. Попытка освободить текущий контекст
(используемый текущим потоком) игнорируется.
3.2.3. swapcontext/setcontext¶
swapcontext()
/setcontext()
- при ошибке возвращают не только ENOMEM.
Возможные ошибки:
EFAULT - подан плохой указатель или в системе недостаточно памяти;
ENOMEM - в системе недостаточно памяти;
ESRCH - некорректный ucp;
EBUSY - контекст ucp уже используется.
3.2.3.1. Особенность переключения контекстов¶
Для архитектуры Эльбрус, помимо доступного пользовательскому приложению контекста (%r, %b[],содержимое стека данных), существует также и привилегированный контекст, доступный на запись лишь ядру ОС, например:
указатели на стеки (%pcsp, %psp, %usd, %sbr);
содержимое стека связующей инфорации (он же «стек возвратов»);
при исполнении в обработчике сигнала: указатель на стек из прерванных сигналами контекстов (%tir, trap cellar, %ctpr, %g16-%g31, …)
Это означает, что каждый сохранённый пользовательский контекст состоит из двух половин: ровно одной привилегированной, хранящейся в ядре ОС; и >= 1 непривилегированной, хранящейся в памяти приложения (struct ucontext).
Ввиду этого, во-первых, makecontext помимо struct ucontext дополнительно инициализирует
привилегированную половинку нового контекста (в основном это два стека -
связующей и процедурный), и во избежание утечки памяти при освобождении struct
ucontext нужно дополнительно освободить привилегированную половинку вызовом
freecontext_e2k()
. Во-вторых, для связи этих двух половинок заводится хеш-таблица, и swapcontext
должен найти два элемента этой таблицы.
В итоге: примерно равный вклад в замедление вносят: организация хэш-таблицы; необходимость полноценного системного вызова; большой размер контекста.
3.2.4. sys_get_backtrace/sys_set_backtrace¶
Функции sys_get_backtrace()
и sys_set_backtrace()
используются для чтения и
модификации адресов возврата в стеке связующей информации. sys_get_backtrace()
считывает адреса в
буфер, sys_set_backtrace()
- записывает.
Если при записи в буфер положить специальное значение -1UL, то адрес возврата будет подменен на пустую функцию, просто выполняющую return, это полезно для пропуска функций, у который нет обработки исключений.
long sys_get_backtrace(unsigned long *buf, size_t count,
size_t skip, unsigned long flags);
long sys_set_backtrace(const unsigned long *buf, size_t count,
size_t skip, unsigned long flags);
Здесь:
count - размер буфера;
skip - сколько пропустить фреймов в стеке связующей;
flags - должен быть равен 0.
При успехе возвращают число прочитанных/записанных адресов (даже если это число меньше чем count). При неудаче возвращают код ошибки.
В случае, если удалось записать не все адреса, код ошибки можно получить, повторив вызов sys_set_backtrace со skip увеличенным и count уменьшенным на число успешно записанных адресов.
sys_get_backtrace/sys_set_backtrace возвращают ошибку:
EFAULT В случае если буфер недоступен;
EINVAL В случае нулевого flags.
sys_set_backtrace возвращает ошибку:
ESRCH Если один из IP указывает на незамапированную область.
- EPERM В случае если сработала проверка:
Либо старый и новый IP имеют различные разрешения r/w/x;
Либо старый и новый IP соответствуют различным исполняемым файлам.
Если требуется просто заменить ip возврата текущий функции, то sys_set_backtrace()
может оказаться слишком тяжеловесным, тогда можно воспользоваться fast_sys_set_return()
:
#if __ptr32__
# define FAST_SYS_ENTRY "5"
#elif __ptr64__
# define FAST_SYS_ENTRY "6"
#else
# error
#endif
#include <asm/unistd.h>
unsigned long ip = <return ip>;
int ret;
asm ("{sdisp %%ctpr1, " FAST_SYS_ENTRY "}\n" \
"{adds 0, %1, %%b[0]\n"\
"addd 0, %2, %%db[1]\n"\
"adds 0, 0, %%b[2]\n"\
"call %%ctpr1, wbs=%#}\n"\
"adds 0, %%b[0], %0\n"\
: "=r" (ret)
: "i" (__NR_fast_sys_set_return), "r" (ip)
: "ctpr1", "ctpr2", "ctpr3", "memory",
"b[0]", "b[1]", "b[2]", "b[3]", "b[4]", "b[5]", "b[6]", "b[7]",
"g16", "g17", "g18", "g19", "g20", "g21", "g22", "g23",
"g24", "g25", "g26", "g27", "g28", "g29", "g30", "g31");
3.2.5. sys_access_hw_stacks¶
Функция sys_access_hw_stacks()
используется для чтения и записи аппаратных стеков
текущего потока в памяти, предполагается такое использование:
получаем размер через E2K_GET_*_STACK_SIZE
выделяем буфер при необходимости
читаем в буфер сколько нужно/откуда нужно через E2K_READ_*_STACK_EX
правим в буфере
пишем обратно только изменённую часть буфера через E2K_WRITE_*_STACK_EX
Для подключения нужных определений следует использоваь:
#include <asm/e2k_syswork.h>
1) E2K_GET_CHAIN_STACK_SIZE и E2K_GET_PROCEDURE_STACK_SIZE используются для получения размера аппаратных стеков.
__u64 chain_size, procedure_size;
sys_access_hw_stacks(E2K_GET_CHAIN_STACK_SIZE, NULL, NULL, 0,
&chain_size)
sys_access_hw_stacks(E2K_GET_PROCEDURE_STACK_SIZE, NULL, NULL, 0,
&procedure_size)
2) Для чтения/записи аппаратных стеков по смещениям относительно их реальной базы (реальная база может быть меньше прописанного в регистре значения %pcsp.base). Предполагается выравнивание offset и size на 8 для процедурного стека и на 32 для связующей (в связующую пишем только фреймы целиком).
__u64 offset;
void *buffer;
unsigned long size;
sys_access_hw_stacks(mode, &offset, buffer, size, NULL)
Здесь:
mode - один из E2K_READ_CHAIN_STACK_EX, E2K_READ_PROCEDURE_STACK_EX, E2K_WRITE_PROCEDURE_STACK_EX, E2K_WRITE_CHAIN_STACK_EX.
offset - смещение в стеке, откуда читать или куда писать
buffer - буфер в пользователе, в который/из которого копировать
size - сколько копировать
При попытке считать/записать больше, чем есть в стеке, функция вернёт ошибку - EINVAL.