Библиотека 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() необходима для корректной остановки задания.

Библиотека 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 реализуются системные вызовы. Системные вызовы реализованы в отдельных файлах. Реализация конкретного системного вызова выбирается приоритетом линковщика.

Стандартные системные вызовы

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

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

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

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

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

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

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

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

  • fstat()

  • getrandom()

  • isatty()

  • chdir()

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

  • close()

  • link()

  • lseek()

  • open()

  • read()

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

  • stat()

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

  • unlink()

  • write()

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

  • gettimeofday()

  • times()

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

  • sbrk()

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

  • profil()

Нестандартные системные вызовы

pmmap()

void *pmmap(void *pref_vaddr, uint32_t length, uint64_t paddr)

Системный вызов pmmap() позволяет отобразить физическую область памяти, начиная с адреса paddr и размером length байт в виртуальное адресное пространство DSP, начиная с адреса pref_vaddr.

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

Значения paddr, pref_vaddr и length должны быть кратны 4 КБ (минимальный размер MMU-страницы). При необходимости использования страниц большего размера значения параметров должны быть кратны размеру страниц (2 МБ, 1 ГБ).

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

paddr не должен указывать на диапазоны адресов, которые соответствуют областям PRAM, XYRAM и регистров DSP-ядер, а также ведут к обращению в EPort. Доступ к такой памяти возможен только по физическим адресам без необходимости дополнительного отображения.

Если pref_vaddr равен NULL, отображение выполняется в первый свободный участок виртуального адресного пространства DSP.

Если pref_vaddr не равен NULL, отображение в адресное пространство DSP выполняется, начиная с адреса pref_vaddr только тогда, когда диапазон адресов pref_vaddr - pref_vaddr + length свободен. В противном случае, отображение выполняется в первый свободный участок виртуального адресного пространства DSP.

В случае успеха системный вызов pmmap() возвращает указатель на начало отображенной области памяти в адресном пространстве DSP.

В случае ошибки системный вызов pmmap() возвращает NULL.

Системный вызов pmmap() определен в заголовочном файле elcore-runtime.h.

pmunmap()

int pmunmap(void *vaddr, uint32_t length)

Системный вызов pmunmap() удаляет отображение, начиная с адреса vaddr и размером length байт, созданное ранее с помощью системного вызова pmmap().

В случае успеха системный вызов pmunmap() возвращает нулевое значение.

Системный вызов pmunmap() определен в заголовочном файле elcore-runtime.h.

_syscall_get_env()

int _syscall_get_env(char *env, void *size)

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

В случае успеха системный вызов _syscall_get_env() возвращает нулевое значение.

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

_syscall_get_kernel_name()

int _syscall_get_kernel_name(char*)

Системный вызов _syscall_get_kernel_name() позволяет получить имя вызываемой функции для данного DSP-задания.

В случае успеха системный вызов _syscall_get_kernel_name() возвращает нулевое значение.

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

Поддержка инструментального профилирования 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-файл не будет создан.