Pull to refresh
0
Promwad
Контрактная разработка и производство электроники

OpenMCAPI: одновременный запуск Linux и RTOS на многоядерных процессорах

Reading time10 min
Views15K


В повседневной практике разработчика встраиваемых систем приходится сталкиваться с необходимостью запуска двух и более разноплановых ОС на n-ядерных системах на кристалле. Это, как правило, Linux и специализированная RTOS. На плечи Linux ложится работа с тяжеловесными стеками протоколов, а RTOS же занимается задачами реального времени.
 
Одна из основных задач, которая встает при такой организации системы — обеспечение механизма взаимодействия, то есть межъядерный обмен данными. Если вам интересно узнать один из вариантов решения на базе открытой библиотеки OpenMCAPI, пролистать пару десятков строк программного кода и увидеть реальные цифры пропускной способности при использовании этой библиотеки, добро пожаловать под кат.

Задача межъядерного обмена данными успешно решается за счет использования разделяемой памяти и межъядерных прерываний с написанием своей прослойки взаимодействия и портированием ее на различные ОС. Для приведения такого API к стандартизированному виду Multicore Association (MCA) разработала и выпустила в свет первую версию спецификации MCAPI (Multicore Communications API), вскоре была выпущена и вторая версия.
 
Рассматриваемая библиотека OpenMCAPI основана на спецификации MCAPI 2.0, разработана компанией Mentor Graphics Corporation и имеет открытый исходный код под свободной лицензией BSD/GPL. Исходные коды можно получить, воспользовавшись сайтом проекта, там же находится краткая информация по запуску и портированию.
 
Библиотека OpenMCAPI изначально предоставляет возможность работы под управлением ОС Linux с использованием виртуального транспорта либо разделяемой памяти (но только на платформах mpc85xx и mv78xx0).
 
Предлагаемая структура взаимодействия Linux и RTOS через OpenMCAPI c разделением на абстрактные уровни имеет следующий вид (см. рис 1):
 

Рисунок. 1. Структура взаимодействия Linux и RTOS через OpenMCAPI
           
Рассмотрим реализацию приведенной структуры на примерах исходного кода порта для Linux:
  1. MCAPI Generic — реализация внешнего MCAPI API.
     
  2. OS Layer — часть уровня MCAPI Generic, который содержит код, зависящий от операционной системы. Эта часть представлена в файле libmcapi/mcapi/linux/mcapi_os.c и содержит реализацию:
  3. Transport Generic — это уровень абстракции, который предоставляет механизм для работы с разделяемой памятью на уровне пользовательского пространства (userspace). Он представлен файлами libmcapi/shm/shm.c и libmcapi/shm/linux/shm_os.c и содержит реализацию:
  4. OS Specific Driver представлен модулем ядра Linux, обеспечивающим прямой доступ к оборудованию из пользовательского пространства. Модуль находится в папке libmcapi/shm/linux/kmod и содержит реализацию:

Для полного понимания механизма взаимодействия через транспорт, использующий разделяемую память, который реализован в библиотеке OpenMCAPI, необходимо рассмотреть механизм межъядерного сигнализирования и структуру данных в разделяемой памяти.
 
Дальнейшее рассмотрение будет проводиться на основе платформы mpc85xx (чип P1020 компании Freescale). Программное обеспечение: ядро Linux версии 2.6.35 с патчами, которое поставляется с комплектом средств разработки (SDK) Freescale QorIQ_SDK_V1_03 (доступен для скачивания после регистрации на их сайте), в качестве операционной системы реального времени (RTOS) использована RTEMS, исходные коды которой можно получить в git-репозиторие по ссылке git://git.rtems.org/rtems.git.
 
Для реализации межъядерного сигнализирования Freescale предоставляет как минимум два механизма:
  1. Interprocessor Interrupts (IPIs) — межъядерные прерывания, до 4 штук с поддержкой мультикастовых прерываний.
  2. Message Interrupts (MSGRs) — межъядерные 32-битные сообщения с генерированием прерывания при записи сообщения в регистр, до 8 штук.

В библиотеке оpenmcapi при реализации OS Specific Driver для этой платформы используется механизм MSGRs.
 
Рассмотрим структуру данных, содержащуюся в разделяемой памяти (см. рис. 2):
 

Рисунок 2. Структура данных в разделяемой памяти
 
Область разделяемой памяти по использованию пространству можно разбить на два блока:
  • Первый блок — область SHM_MGMT_BLOCK, представлена структурой:

/* SM driver mamagement block */
struct _shm_drv_mgmt_struct_
{
    shm_lock                    shm_init_lock;
    mcapi_uint32_t              shm_init_field;
    struct _shm_route_          shm_routes[CONFIG_SHM_NR_NODES];
    struct _shm_buff_desc_q_    shm_queues[CONFIG_SHM_NR_NODES];
    struct _shm_buff_mgmt_blk_  shm_buff_mgmt_blk;
};

Структура содержит следующие элементы:
  1. Глобальная блокировка разделяемой памяти — shm_init_lock, используемая для разграничения доступа n-ядер к разделяемой (shared) области.
  2. Переменная shm_init_field содержит ключ окончания инициализации мастера, принимает значение SHM_INIT_COMPLETE_KEY по окончанию инициализации.
  3. Shm_routes — таблица маршрутизации со связями межъядерных сообщений, содержит CONFIG_SHM_NR_NODES связей по числу участвующих в обмене ядер (узлов). В нашем случае 2 узла.
  4. Shm_queues — очереди сообщений с привязкой к конкретному узлу, содержит CONFIG_SHM_NR_NODES. В нашем случае 2 очереди.
  5. Shm_buff_mgmt_blk — структура управления буферами (SHM_BUFFER) в области данных.

 
  • Вторая область — область данных, содержит SHM_BUFF_COUNT (по умолчанию 128) структур SHM_BUFFER. Эта область служит непосредственно для хранения передаваемых данных. Структура SHM_BUFFER состоит из массива размером MCAPI_MAX_DATA_LEN и дополнительной структуры управления элементом.

Перед тем как рассмотреть процесс портирования необходимо привести блок-схему работы низкоуровневого механизма коммуникации через разделяемую память (механизм реализован в OpenMCAPI, см. рис. 3):
 


Рисунок. 3. SDL-диаграммы низкоуровневого механизма коммуникации через разделяемую память (реализован в OpenMCAPI).
 
Некоторые пояснения к диаграммам:
  • “HW-Notification” — диаграмма описывает процесс отсылки уведомления удаленному или текущему ядру:
    1. Вызов функции принимает id ядра, для которого предназначено сообщение (функция openmcapi_shm_notify).
    2. Если уведомление предназначено для удаленного ядра “target id”, то генерируется удаленное сообщение через механизм MSGRs (рассмотрен выше) со значением в поле данных равным единице (см. блок 3), иначе происходит явный вызов обработчика прерывания interrupt_handle (диаграмма “HW-Receive”), см. блок 4.

  • “HW-Receive” — диаграмма описывает процесс приема уведомления от удаленного или текущего ядра:
    1. Interrupt_handle является обработчиком прерывания, настроенным на срабатывание при приеме сообщения по MSGRs, используется также для явного вызова.
    2. В блоках 2—6 организована проверка статуса всех сообщений MSGRs, если поле данных MSGR-сообщения не равно 0, происходит разблокировка потока, который производит обработку данных в области разделяемой памяти.
    3. В блоках 7—8 происходит проверка места вызова обработчика прерывания (“interrupt_handler”). Если вызов был в прерывании, сбрасывается флаг присутствия сообщения MSGR.


Перед тем как перейти к описанию портирования для RTEMS, вкратце рассмотрим эту ОС.
 

 
RTEMS (Real-Time Executive for Multiprocessor Systems) — это RTOS c открытым исходным кодом, полнофункциональная операционная система реального времени с поддержкой множества открытых стандартных интерфейсов прикладного программирования (API), стандартов POSIX и BSD-сокетов. Она предназначена для использования в космических, медицинских, сетевых и многих других встраиваемых устройствах. RTEMS содержит широкий набор процессорных архитектур, таких как ARM, PowerPC, Intel, Blackfin, MIPS, Microblaze и др. Содержит большой стек реализованных сетевых протоколов, в частности tcp/ip, http, ftp, telnet. Предоставляет стандартизированный доступ к RTC, NAND, UART и другому оборудованию.
 
Перейдем к процессу портирования OpenMCAPI. Исходя из документа, расположенного по ссылке [1] требуется:
  1. Реализовать OS Layer, файлы:
    • libmcapi/mcapi/rtems/mcapi_os.c;
    • libmcapi /include/rtems/mgc_mcapi_impl_os.h.

  2. Реализовать поддержку совместимого транспорта разделяемой памяти, файл:
    • libmcapi/shm/rtems/shm_os.c.

  3. Добавить рецепты в waf-сборщик, который используется для OpenMCAPI.

Так как целевая платформа P1020 (powerpc, 500v2) и портирование проводилось на RTOS, где допускается отсутствие разделения пространства kernel/user space, отпадает необходимость в написании:
  1. libmcapi/include/arch/powerpc/atomic.h;
  2. libmcapi/shm/rtems/kmod/.

Также отпадает необходимость в реализации OS Layer, так как RTEMS поддерживает POSIX-совместимые вызовы, файлы mcapi_os.c и mgc_mcapi_impl_os.h просто были скопированы из реализации для Linux.
 
Реализация транспорта разделяемой памяти полностью выполнена в файле shm_os.c и включает адаптацию вызовов из уровня абстракции Transport Generic (файл libmcapi/shm/shm.c) и реализацию механизма обмена через MSGRs.
 
Функции, требующие реализации:

1) mcapi_status_t openmcapi_shm_notify (mcapi_uint32_t unit_id, mcapi_uint32_t node_id) — функция отправляет нотификацию удаленному ядру(ам), реализация представлена диаграммой (см. рис 3). Исходный код приведен ниже:



/* send notify remote core */
mcapi_status_t openmcapi_shm_notify(mcapi_uint32_t unit_id,
                                    mcapi_uint32_t node_id)
{
 	mcapi_status_t mcapi_status = MCAPI_SUCCESS;
 	int rc;

	rc = shm_rtems_notify(unit_id);
if (rc) {
mcapi_status = MGC_MCAPI_ERR_NOT_CONNECTED;
	}
    	return mcapi_status;
}

static inline int shm_rtems_notify(const mcomm_core_t target_core)
{
	struct mcomm_qoriq_data *const data = &mcomm_qoriq_data;
	/* If the target is the local core, call the interrupt handler directly. */
	if (target_core == mcomm_qoriq_cpuid()) {
		_mcomm_interrupt_handler(NO_IRQ, data);
	} else {
		mcomm_qoriq_notify(target_core);
	}
	return 0;
}

/* Wake up the process(es) corresponding to the mailbox(es) which just received
 * packets. */
static int _mcomm_interrupt_handler(rtems_vector_number irq, struct mcomm_qoriq_data *data)
{
	register int i;
	void *mbox = data->mbox_mapped;
	for (i = 0; i < data->nr_mboxes; i++) {
		int active;
		switch (data->mbox_size) {
		case 1:
			active = readb(mbox);
			break;
		case 4:
			active = readl(mbox);
			break;
		default:
			active = 0;
		}
		if (active) {
			LOG_DEBUG("%s: waking mbox %d\n", __func__, i);
(void) rtems_event_send( data->rid, MMCAPI_RX_PENDING_EVENT );
		}
		mbox += data->mbox_stride;
	}
	if (irq != NO_IRQ) {
		mcomm_qoriq_ack();
	}
	return 0;
}


2) mcapi_uint32_t openmcapi_shm_schedunitid(void) — функция возвращает номер текущего ядра (то есть ядра, исполняющего этот код), реализуется тривиально чтением регистра процессора. Исходный код приведен ниже:

/* Get current cpu id */
mcapi_uint32_t openmcapi_shm_schedunitid(void)
{
 	return (mcapi_uint32_t) ppc_processor_id();
}


3) mcapi_status_t openmcapi_shm_os_init(void) — функция создает и запускает низкоуровневый поток приема данных, реализуется посредством вызова функций rtems_task_create и rtems_task_start. Исходный код приведен ниже:

/* Now that SM_Mgmt_Blk has been initialized, we can start the RX thread. */
mcapi_status_t openmcapi_shm_os_init(void)
{
	struct mcomm_qoriq_data *const data = &mcomm_qoriq_data;
	rtems_id id;
	rtems_status_code sc;

	if( RTEMS_SELF != data->rid ) {
 		return MCAPI_ERR_GENERAL;
	}
	
	sc = rtems_task_create(
	  rtems_build_name( 'S', 'M', 'C', 'A' ),
	  MMCAPI_RX_TASK_PRIORITY,
	  RTEMS_MINIMUM_STACK_SIZE,
	  RTEMS_DEFAULT_MODES,
	  RTEMS_DEFAULT_ATTRIBUTES,
	  &id);
	if( RTEMS_SUCCESSFUL != sc ) {
		return MCAPI_ERR_GENERAL;
	}

	/* global save task id */
	data->rid = id;
	
	sc = rtems_task_start( id, mcapi_receive_thread, 0 );
	if( RTEMS_SUCCESSFUL != sc ) {
		perror( "rtems_task_start\n" );
		return MCAPI_ERR_GENERAL;
	};

    	return MCAPI_SUCCESS;
}

static rtems_task mcapi_receive_thread(rtems_task_argument argument)
{
 int rc;
 do {
 	rc = shm_rtems_wait_notify(MCAPI_Node_ID);
        	if (rc < 0) {
            		perror("shm_rtems_wait_notify");
            		break;
        	}

        	MCAPI_Lock_RX_Queue();
       	 /* Process the incoming data. */
        	shm_poll();
        	MCAPI_Unlock_RX_Queue(0);
} while (1);
printk("%s exiting!\n", __func__);
}

static inline int shm_rtems_wait_notify(const mcapi_uint32_t unitId)
{
	rtems_event_set event_out;
	int ret = 0;
	
	while(1) {
		LOG_DEBUG("mcomm_mbox_pending start\n");
		
	    	(void) rtems_event_receive(
					MMCAPI_RX_PENDING_EVENT,
					RTEMS_DEFAULT_OPTIONS,
					RTEMS_NO_TIMEOUT,
					&event_out
				  );
		LOG_DEBUG("rtems_event_receive\n");
				  
		ret = mcomm_mbox_pending(&mcomm_qoriq_data, 
					 (mcomm_mbox_t)unitId);
								 
		LOG_DEBUG("mcomm_mbox_pending end ret=%d\n", ret);
		if(ret != 0) {
			return ret;
		};
	}
	return 0;
}


4) mcapi_status_t openmcapi_shm_os_finalize(void) — функция останавливает низкоуровневый поток приема данных, реализуется посредством вызова функции rtems_task_delete. Исходный код приведен ниже:

/* Finalize the SM driver OS specific layer. */
mcapi_status_t openmcapi_shm_os_finalize(void)
{
	struct mcomm_qoriq_data *const data = &mcomm_qoriq_data;
    	rtems_id id = data->rid;
	rtems_status_code sc;
	
	sc = rtems_task_delete(id);
	if( RTEMS_SUCCESSFUL != sc ) {
		return MCAPI_ERR_GENERAL;
	}
	
    	return MCAPI_SUCCESS;
}


5) void *openmcapi_shm_map(void) — функция подготовки и настройка интерфейса MSGRs, подготовка разделяемой памяти. Исходный код приведен ниже:

/* full open mcom device and get memory map addres*/
void *openmcapi_shm_map(void)
{
 	void *shm;
     	int rc;
     	size_t shm_bytes;
 
     	// low level init //
     	mcomm_qiroq_probe();
     	shm_bytes = shm_rtems_read_size();
     	if (shm_bytes <= 0) {
         		perror("read shared memory size\n");
         		return NULL;
     	}
     
     	/* initialized device. */
     	rc = shm_rtems_init_device();
 if (rc < 0) {
 	perror("couldn't initialize device\n");
goto out;
 }
 
     	shm = shm_rtems_read_addr();
     	if (shm == NULL) {
         		perror("mmap shared memory");
         		goto out;
     	}
      	return shm;
out:
     	return NULL;
}

static size_t shm_rtems_read_size(void)
{
	struct mcomm_qoriq_data *const data = &mcomm_qoriq_data;
	return (size_t) (data->mem.end - data->mem.start);
}

static inline int shm_rtems_init_device(void)
{
    	struct _shm_drv_mgmt_struct_ *mgmt = NULL; /* xmmm */
	return mcomm_dev_initialize(&mcomm_qoriq_data, 
    (uint32_t)&mgmt->shm_queues[0].count, 
	    CONFIG_SHM_NR_NODES, 
   sizeof(mgmt->shm_queues[0].count), 
	    ((void *)&mgmt->shm_queues[1].count - (void *)&mgmt->shm_queues[0].count));
}

static void *shm_rtems_read_addr(void)
{
	struct mcomm_qoriq_data *const data = &mcomm_qoriq_data;
	return (void*)data->mem.start;
}


6. void openmcapi_shm_unmap(void *shm) — функция закрывает интерфейс MSGRs, отменяет использование разделяемой памяти. Исходный код приведен ниже:

/* full close mcom device and revert memory */
void openmcapi_shm_unmap(void *shm)
{
	/* deinitialized device. */
	shm_rtems_deinit_device();
	// low level deinit //
	mcomm_qoriq_remove();
}
static inline int shm_rtems_deinit_device(void)
{
	return mcomm_dev_finalize(&mcomm_qoriq_data);
}

Отдельно следует рассмотреть реализацию функции низкоуровневого потока приема mcapi_receive_thread (исходный код см. выше). При запуске потока вызовом функции rtems_event_receive он переводится в режим ожидания события (реализуется доступным в RTEMS механизмом событий). Далее при приходе события запуска, отсылаемого в обработчике interrupt_handler (см. рис. 3, диаграмма “HW-Receive”), происходит обработка изменений в области разделяемой памяти (вызов внутренней функции openmcapi — shm_poll()), c ее предварительной блокировкой, после чего поток возвращается в состояние ожидания.
 
Ниже приводятся результаты, полученные при взаимодействии Linux и RTEMS через OpenMCAPI. Тестовый стенд представляет собой отладочная плата от Freescale P1020RDB-PB с установленным процессором P1020 (2 ядра). Частоты: частота ядра — 800 МГц, DDR2 — 400 МГц, CCB — 400 МГц. На ядрах 0/1 были запущены соответственно Linux/RTEMS. Обмен был двухсторонним, замерялось время, затраченное на 10000 двухсторонних посылок. Результаты тестов сведены в таблицу:
 

Описание теста
Времени на одну посылку, мкс
1
Симметричные пакеты размером 512 байт
37,5
2
Симметричные пакеты размером 52430 байт
121
3
Симметричные пакеты размером 100 кБайт
346
4
Ассиметричные пакеты размерами 1к/100к-linux/rtems
185

Из всего выше изложенного можно сделать вывод, что библиотека OpenMCAPI предоставляет собой достойный вариант реализации спецификации MCAPI, имеющей четкую структуру исходного кода, облегчающую портирование; наглядные примеры портирования (платформы powerpc и arm); свободную лицензию и производительность, достаточную для большинства применений.
 
[!?] Вопросы и комментарии приветствуются. На них будет отвечать автор статьи Руслан Филипович, программист дизайн-центра электроники Promwad.
Tags:
Hubs:
+33
Comments15

Articles

Change theme settings

Information

Website
promwad.com
Registered
Founded
Employees
101–200 employees
Location
Литва
Representative
Alexandra Goncharik