Библиотека DSP elcore-runtime

Библиотека elcore-runtime содержит функции для разработки DSP-библиотек.

__elcore_before_main()

int __elcore_before_main()

Пользовательская библиотека является статически скомпонованным исполняемым ELF-файлом.

Код инициализации глобальных объектов выполняется до вызова функции __elcore_before_main() и эти объекты могут использоваться во время выполнения заданий. Реализация функции __elcore_before_main() из библиотеки elcore-runtime имеет следующий вид:

int __elcore_before_main() {
  asm("STOP");
  asm("NOP");
  return 0;
}

ElcoreCL во время вызова eclCreateProgramWithBinary() запускает задание на DSP, которое выполняет код начиная с точки входа ELF-файла, а приведённая реализация __elcore_before_main() останавливает выполнение до того, как будет вызвана функция main() и выполнены деструкторы глобальных объектов.

Деструкторы глобальных объектов не вызываются никогда.

elcore_main_wrapper()

void elcore_main_wrapper(char *args, uint32_t *retval)

Описание аргументов функции elcore_main_wrapper():

  • args — буфер, содержащий список строк-аргументов, разделенных пустым символов 0. Список должен заканчиваться строкой нулевой длины;

  • retval — буфер для сохранения кода возврата;

Функция elcore_main_wrapper() является оберткой над функцией main(int argc, char *argv[]) ELF-файла, в которой выполняется формирование аргументов argc и argv, вызов функции main() и сохранение кода возврата в выходной аргумент retval.

При необходимости запустить задание, которое начинается с функции main(), пользователю следует вызывать функцию c:function:: void elcore_main_wrapper(), используя ELcoreCL-функции eclCreateKernel() и eclEnqueueNDRangeKernel().

elcorecl_run_wrapper()

void elcorecl_run_wrapper(char *args, uint32_t *retval, char *shared_mem, uint32_t shared_mem_size)

Описание аргументов функции elcorecl_run_wrapper():

  • args — буфер, содержащий список строк-аргументов, разделенных пустым символов 0;

  • retval — буфер для сохранения кода возврата;

  • shared_mem — указатель на память, общую для всех DSP;

  • shared_mem_size — размер памяти, общей для всех DSP.

Функция elcorecl_run_wrapper() является оберткой над функцией main_with_share_mem(int32_t argc, char *argv[], char *shmem_ptr, int32_t shmem_size), в которой выполняется формирование аргументов argc и argv, вызов функции main_with_share_mem() и сохранение кода возврата в специальный буфер. Функция main_with_share_mem() является аналогом функции main() за исключением того, что принимает дополнительно память, общую для всех DSP.

Описание аргументов функции main_with_share_mem():

  • argc, argv — количество и массив аргументов;

  • shmem_ptr — указатель на память, общую для всех DSP;

  • shmem_size — размер памяти, общей для всех DSP.

При необходимости запустить задание, которое начинается с функции main_with_share_mem(), пользователю следует вызывать функцию c:function:: void elcorecl_run_wrapper(), используя ELcoreCL-функции eclCreateKernel() и eclEnqueueNDRangeKernel().

Пользователь библиотеки не должен самостоятельно вызывать elcorecl_run_wrapper() внутри DSP-функции.

elcore_job_launcher()

void elcore_job_launcher()

Функция elcore_job_launcher() необходима для корректной остановки задания и имеет следующую реализацию:

elсorecl_job_launcher:
   JS R8.L, R15.L
   STOP
   NOP

Библиотека ElcoreCL во время вызова eclCreateProgramWithBinary() находит адрес elcore_job_launcher() и передаёт его в драйвер при каждом запуске задания драйверу. Драйвер устанавливает R8.L на адрес функции, вызываемой пользователем, а program counter на адрес elcore_job_launcher().

Таким образом, если функция закончила выполнение штатно, задание должно остановиться внутри elcore_job_launcher().

Пользователь библиотеки ElcoreCL:

  • не должен сам вызывать или как-то иначе использовать elcore_job_launcher();

  • не должен вставлять оператор STOP в запускаемые на DSP функции.

elcore_kernel_wrapper

void elcore_kernel_wrapper(void *spawn_mem, kernel_spawn_internal_data *data, ...)

Описание аргументов функции elcore_kernel_wrapper():

  • spawn_mem — указатель на некэшируемую область памяти;

  • data — структура kernel_spawn_internal_data со следующими полями:
    • nc_mem_size — размер передаваемой некэширумой памяти spawn_mem;

    • context_capacity — количество DSP в ELcoreCL-контексте;

    • index_in_context[16]` — порядковый номер ядер в ELcoreCL-контексте;

    • kernel[16] — вызываемый DSP-kernel;

    • argc[16] — количество аргументов для kernel.

  • shared_mem — указатель на память, общую для всех DSP;

  • shared_mem_size — размер памяти, общей для всех DSP.

Функция elcore_kernel_wrapper() является дополнительной оберткой над DSP-kernel’ом при запуске ELcoreCL-функцией eclEnqueueKernelWithSpawn().

Функция elcore_kernel_wrapper():

  • Сохраняет в глобальных переменных spawn_mem, nc_mem_size, context_capacity и index_in_context;

  • выделяет память под context_capacity стеков из своей кучи;

  • запускает kernel;

  • после завершения kernel выставляет флаги завершения вспомогательных ядер.

Пользователь библиотеки не должен самостоятельно вызывать elcore_kernel_wrapper() внутри DSP-функции.

spawner_loop()

void spawner_loop()

Функция spawner_loop() содержит основной цикл работы вспомогательных DSP, необходимых для корректной работы функций elcore_spawn() и elcore_sync() в рамках модели spawn/sync (см. Модель параллельного программирования spawn/sync).

Функция spawner_loop() выполняет поиск активных заданий в очереди заданий, размещаемой в общей некэшируемой памяти. При появлении активного задания функция spawner_loop() выполняет попытку атомарного захвата. В случае успешного захвата функция spawner_loop() выполняет переход в виртуальное адресное пространство основного DSP, породившего данное задание для его выполнения.

После выполнения задания функция spawner_loop() переходит в исходное адресное пространство для поиска очередного активного задания.

Пользователь библиотеки не должен самостоятельно вызывать spawner_loop() внутри DSP-функции.

__init_elcore_environ()

int __init_elcore_environ()

Функция __init_elcore_environ() настраивает окружение для DSP путем копирования переменных окружения CPU-процесса.

Функция __init_elcore_environ() вызывается во время выполнения функции __start — точки входа в программу.

get_core_id()

int get_core_id()

Функция get_core_id() необходима для получения DSP-функцией порядкового номера ядра, на котором она запущена.

elcore_spawn()

int elcore_spawn(void *function, size_t argc, ...)

Функция elcore_spawn() предназначена для порождения параллельной ветви программы для выполнения function в рамках модели spawn/sync (см. Модель параллельного программирования spawn/sync).

Функция elcore_spawn() формирует в некэшируемой очереди новое задание. Свободные вспомогательные DSP просматривают очередь и при появлении активного задания выполняют попытку атомарного захвата. Вспомогательный DSP, который успешно захватил задание, переключается в адресное пространство основного DSP и выполняет его.

Описание аргументов функции elcore_spawn():

  • function — указатель на запускаемую функцию;

  • argc — количество аргументов, передаваемых в функцию function. Максимально допустимое значение — 32.

  • список из argc аргументов, передаваемых в функцию function.

В случае успеха функция elcore_spawn() возвращает идентификатор задания. Ожидание завершения задания осуществляется с помощью функции elcore_sync().

Функция elcore_spawn() определена в заголовочном файле elcore-runtime.h.

elcore_sync()

int elcore_sync(int task_id, void *ret)

Функция elcore_sync() ожидает завершения задания task_id, созданного elcore_spawn() и выполняет запись возвращаемого значения задания, если указатель ret не равен нулю.

Если задание task_id в момент вызова функции elcore_sync() не было захвачено ни одним из вспомогательных ядер, функция elcore_sync() выполняет задание самостоятельно.

Если задание task_id в момент вызова функция elcore_sync() было захвачено одним из вспомогательных ядер, но еще не завершено, функция elcore_sync() ожидает его завершения, параллельно выполняя по одному заданию из конца очереди до тех пор, пока задание task_id не будет завершено.

Описание аргументов функции elcore_sync():

  • task_id — идентификатор задания, созданный с помощью elcore_spawn();

  • ret — указатель на переменную для записи возвращаемого значения задания;

В случае успеха функция elcore_sync() возвращает нулевое значение.

Функция elcore_sync() определена в заголовочном файле elcore-runtime.h.

Системные вызовы

В elcore-runtime реализуются системные вызовы, аналогичные библиотеке newlib. Системные вызовы реализованы в отдельных файлах. Реализация конкретного системного вызова выбирается приоритетом линковщика.

Управление процессами:

  • execve() — реализован «заглушкой» (при использовании процессор будет остановлен по исключению TRAP 10).

  • _exit() — реализован «заглушкой».

  • fork() — реализован «заглушкой».

  • getpid() — реализован «заглушкой».

  • kill() — реализован «заглушкой».

  • wait() — реализован «заглушкой».

Работа с файлами, ввод/вывод:

  • fstat()

  • isatty()

  • chdir()

  • chown() — реализован «заглушкой».

  • close()

  • link()

  • lseek()

  • open()

  • read()

  • readlink() — реализован «заглушкой».

  • stat()

  • symlink() — реализован «заглушкой».

  • unlink()

  • write()

Работа со временем:

  • gettimeofday()

  • times()

Работа с памятью:

  • sbrk()

Профилирование:

  • profil()

elcore-runtime дополнительно реализует вспомогательные системные вызовы, необходимые для настройки окружения DSP:

  • _syscall_get_env() — получение списка переменных окружений CPU-процесса, разделенных символами \0 и его размера.

  • _syscall_get_kernel_name() — получение имени вызываемой функции для данного задания.

Поддержка инструментального профилирования gprof

Библиотека elcore-runtime реализует необходимый для профилирования функционал согласно Implementation of Profiling:

  • mcount()

  • moncontrol()

  • _mcleanup()

Сэмплирование осуществляется в драйвере с использованием таймеров. Включение/завершение сэмплирования осуществляется с помощью системного вызова profil().

При завершении сэмплирования системный вызов profil() возвращает количество потерянных сэмплов. Если количество потерянных сэмплов достаточно велико по сравнению с общим временем выполнения программы, то результаты профилирования считаются невалидными.

Особенность реализации:

  • Профилирование выполняется отдельно для каждого задания в рамках одного ELF-файла.

  • По окончанию профилирования задания в рабочей директории создается файл gmon-elcore<elnum>-<function_name>-<job_number>.out, где:

    • elnum — номер DSP, на котором выполнялось данное задание.

    • function_name — имя вызываемой функции для данного задания.

    • job_number — порядковый номер данного задания в рамках одного ELF-файла.

Предупреждение

Профилируемое приложение должно завершаться штатно. В противном случае, gmon-файл не будет создан.