Эффективная Связь Между Собственными Процессами Arduino И Linux.

Используя скетчи Arduino с платами Intel Galileo и Intel Edison, вы можете оказаться в ситуации, когда вам потребуется добавить дополнительную функциональность с помощью встроенного комплекта разработки ОС Yocto Linux. И здесь нам предстоит решить проблему, о которой мы уже упомянули в заголовке нашего поста: как наладить эффективную «связь» между этими двумя мирами.



Эффективная связь между собственными процессами Arduino и Linux.

Давайте определим некоторые критерии, которые нам необходимо учитывать:



Критерии

1. Никакой передачи данных через диск (SD-карта, eMMC) для уменьшения износа диска и повышения производительности.

2. Связь срабатывает исключительно по каким-то событиям (в частности, нам не нужно периодически проверять статус, но мы хотим получать уведомления о конкретном событии, иначе мы не активируем обмен данными).



Межпроцессное взаимодействие (IPC) в Linux

Программа, созданная в среде Arduino (скетч) и работающая на Intel Galileo или Intel Edison, представляет собой процесс Linux, который выполняется параллельно с другими подобными процессами.

Поскольку мы имеем дело с полноценной Linux-системой, работающей на этих платах, то также можно использовать стандартные инструменты межпроцессного взаимодействия (IPC) между процессами Arduino и собственными процессами.

Существуют разные методы IPC для Linux. Один из них — «IPC с отображением в памяти».

По сути, это означает, что процессы IPC используют одну и ту же область памяти.

В свою очередь, это означает, что любые изменения, внесенные одним процессом, использующим определенную область памяти, немедленно становятся видимыми для всех других процессов.

Такое поведение удовлетворяет нашему первому критерию: при передаче данных мы работаем с последними исключительно в памяти, на диск они не записываются.



Мьютексы и условные переменные

Использование общей памяти сразу же вызывает пару вопросов, таких как: 1. Как вы можете гарантировать, что только один процесс (синхронизация) работает с общими данными в любой момент времени? 2. Как мгновенно уведомить другой процесс (или процессы) об изменении данных (уведомление)? Ниже мы рассмотрим эти два вопроса по очереди.

Мы предоставим возможность использовать «мьютексы» и «условные переменные», которые включены в библиотеку потоков POSIX (Pthreads), доступную в системе Linux.

Синхронизация — Мьютекс

Взаимное исключение, или мьютекс, — стандартный принцип, реализованный, пожалуй, в любой современной многозадачной ОС.

В этом посте мы не будем описывать все принципы и детали, а лишь поделимся конкретной информацией, касающейся мьютексов из набора потоков POSIX (Pthreads).

Чтобы получить дополнительную информацию, обратитесь к бумажным публикациям (например, Tanenbaum, Woodhull: Operating Systems 3rd ed. Pearson 2006) или воспользуйтесь поиском в Интернете.

Как следует из названия библиотеки, пакет Pthreads в первую очередь предназначен для программирования потоков.

В то же время он также предлагает мощные инструменты, применимые для управления процессами.

Более того, Arduino IDE для плат Intel Galileo и Intel Edison поддерживает библиотеку Pthreads (то есть ссылки на эту библиотеку) «из коробки», что обеспечивает простую интеграцию.

Поэтому использование Pthreads для наших целей кажется вполне логичным решением.

Обычно мьютекс гарантирует, что только один поток может получить доступ к определенному критическому разделу кода.

Поскольку здесь мы имеем дело с процессами, давайте использовать мьютексы таким образом, чтобы только один процесс мог получить доступ к запросу pthread_mutex_lock в коде.

Все остальные процессы будут переведены в спящий режим самой ОС — ровно до тех пор, пока мьютекс не получит команду pthread_mutex_unlock, после чего ОС разбудит все остальные процессы, запросив pthread_mutex_lock. Псевдокод:

  
  
  
  
  
  
  
   

pthread_mutex_lock(&mutex); // read / write shared memory data here pthread_mutex_unlock(&mutex);

Это необходимо сделать одинаково как для блокировки записи, так и для блокировки доступа к чтению.

В противном случае частично обновленные данные могут быть доступны во время процесса чтения.

В следующем разделе мы объясним, как элементы Pthreads обеспечивают уведомление при изменении данных.



Примечание.

Условная переменная

Аналогично принципу, когда процесс пытается получить доступ к заблокированному мьютексу, библиотека Pthreads также включает переменные условия.

Условная переменная позволяет потоку или (в нашем случае) процессу запрашивать разрешение на спящий режим до тех пор, пока он не проснется.

Это реализуется с помощью функции pthread_cond_wait. Объединение мьютекса и условной переменной создает следующий псевдокод:

pthread_mutex_lock(&mutex); pthread_cond_wait(&cond_variable, &mutex); // read shared memory data here pthread_mutex_unlock(&mutex);

Другой процесс должен разблокировать мьютекс и сигнализировать об изменении, вызвав pthread_cond_signal. Это выведет процесс из спящего режима.

Более подробную информацию вы можете найти в специализированных изданиях или на соответствующих интернет-ресурсах.

В следующем разделе представлен пример реализации кода.



Выполнение

Некоторые уточнения:
  • Как для мьютекса, так и для условной переменной нам необходимо явно установить атрибуты, чтобы обеспечить возможность межпроцессного использования.

  • Мы решили использовать общую память IPC через файлы, отображенные в памяти, поскольку Arduino IDE не поддерживает все библиотеки, необходимые для реализации прямого распределения памяти IPC. Размещение коммуникационного файла в файловой системе, отображаемой в основной памяти, обеспечивает по существу идентичную функциональность.

    Платформа Yocto Linux, поставляемая с платой Intel Edison, а также образ SD-карты Yocto включает временную папку /tmp, смонтированную в хранилище tmpfs, которое находится в памяти.

    То есть нам подойдет любой файл из этой папки.

    Мы выбрали файл /tmp/arduino, он предназначен исключительно для использования в IPC.

  • Мы предполагаем, что процесс Arduino будет запускать мьютекс и переменную условия, поскольку именно этот процесс запускается при загрузке системы.

  • В нашем примере показана ситуация, когда процесс Arduino прослушивает данные из собственного процесса Linux и выполняется на основе полученных данных.

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

  • Чтобы показать принцип работы, мы поместим два логических значения в отображаемую в память структуру.

    ммапдата , определяющий, включены или выключены индикаторы (встроенные и внешние на IO 8):

    bool led8_on; // led on IO8 bool led13_on; // built-in led

    Очевидно, что здесь можно разместить любые данные.

    Две другие переменные в структуре ммапдата является мьютексом и условной переменной.



Примечание:

Приведенный ниже пример кода предоставляется по лицензии MIT. Ниже вы найдете три файла:
  • mmap.ino : скетч для запуска в среде разработки Arduino IDE
  • mmap.cpp : собственный процесс, отвечающий за отправку данных
  • mmap.h : заголовочный файл – файл для использования в Arduino IDE, а также в Linux.
То есть на стороне Arduino у вас должна быть папка, содержащая файлы mmap.ino и mmap.h в каталоге скетчей Arduino. В Linux вам следует использовать папку, содержащую файлы mmap.cpp и mmap.p. Чтобы запустить скетч, откройте программу mmap в Arduino IDE и загрузите ее на соответствующую плату (Intel Galileo Gen 1, Intel Galileo Gen 2 или Intel Edison).

Пакет разработчика Intel Интернет вещей поставляется с кросс-компилятором.

Кроме того, платформа Yocto, которая поставляется в комплекте с Intel Edison, а также образ SD-карты Yocto или плата Intel Galileo, поставляется с предустановленным компилятором C++.

Для запуска предустановленного компилятора необходимо запустить:

g++ mmap.cpp -lpthread -o mmap

Файлы mmap.cpp и mmap.h должны быть помещены в папку.

Это создаст двоичный файл, который можно запустить следующим образом:

.

/mmap {0,1}{0,1}

.

где "{0,1}" означает 0 или 1. Например, выражение ".

/mmap 00" отключит оба индикатора, а команда .

/mmap 00 включит их.

Некоторая дополнительная информация отображается на мониторе последовательного интерфейса (Ctrl + Shift + M).



mmap.ino



/* * Author: Matthias Hahn <[email protected]> * Copyright (C) 2014 Intel Corporation * This file is part of mmap IPC sample provided under the MIT license * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ #include "mmap.h" using namespace std; /* assume /tmp mounted on /tmpfs -> all operation in memory */ /* we can use just any file in tmpfs. assert(file size not modified && file permissions left readable) */ struct mmapData* p_mmapData; // here our mmapped data will be accessed int led8 = 8; int led13 = 13; void exitError(const char* errMsg) { /* print to the serial Arduino is attached to, i.e. /dev/ttyGS0 */ string s_cmd("echo 'error: "); s_cmd = s_cmd + errMsg + " - exiting' > /dev/ttyGS0"; system(s_cmd.c_str()); exit(EXIT_FAILURE); } void setup() { int fd_mmapFile; // file descriptor for memory mapped file /* open file and mmap mmapData*/ fd_mmapFile = open(mmapFilePath, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR); if (fd_mmapFile == -1) exitError("couldn't open mmap file"); /* make the file the right size - exit if this fails*/ if (ftruncate(fd_mmapFile, sizeof(struct mmapData)) == -1) exitError("couldn' modify mmap file"); /* memory map the file to the data */ /* assert(filesize not modified during execution) */ p_mmapData = static_cast<struct mmapData*>(mmap(NULL, sizeof(struct mmapData), PROT_READ | PROT_WRITE, MAP_SHARED, fd_mmapFile, 0)); if (p_mmapData == MAP_FAILED) exitError("couldn't mmap"); /* initialize mutex */ pthread_mutexattr_t mutexattr; if (pthread_mutexattr_init(&mutexattr) == -1) exitError("pthread_mutexattr_init"); if (pthread_mutexattr_setrobust(&mutexattr, PTHREAD_MUTEX_ROBUST) == -1) exitError("pthread_mutexattr_setrobust"); if (pthread_mutexattr_setpshared(&mutexattr, PTHREAD_PROCESS_SHARED) == -1) exitError("pthread_mutexattr_setpshared"); if (pthread_mutex_init(&(p_mmapData->mutex), &mutexattr) == -1) exitError("pthread_mutex_init"); /* initialize condition variable */ pthread_condattr_t condattr; if (pthread_condattr_init(&condattr) == -1) exitError("pthread_condattr_init"); if (pthread_condattr_setpshared(&condattr, PTHREAD_PROCESS_SHARED) == -1) exitError("pthread_condattr_setpshared"); if (pthread_cond_init(&(p_mmapData->cond), &condattr) == -1) exitError("pthread_mutex_init"); /* for this test we just use 2 LEDs */ pinMode(led8, OUTPUT); pinMode(led13, OUTPUT); } void loop() { /* block until we are signalled from native code */ if (pthread_mutex_lock(&(p_mmapData->mutex)) != 0) exitError("pthread_mutex_lock"); if (pthread_cond_wait(&(p_mmapData->cond), &(p_mmapData->mutex)) != 0) exitError("pthread_cond_wait"); if (p_mmapData->led8_on) { system("echo 8:1 > /dev/ttyGS0"); digitalWrite(led8, HIGH); } else { system("echo 8:0 > /dev/ttyGS0"); digitalWrite(led8, LOW); } if (p_mmapData->led13_on) { system("echo 13:1 > /dev/ttyGS0"); digitalWrite(led13, HIGH); } else { system("echo 13:0 > /dev/ttyGS0"); digitalWrite(led13, LOW); } if (pthread_mutex_unlock(&(p_mmapData->mutex)) != 0) exitError("pthread_mutex_unlock"); }



mmap.cpp



/* * Author: Matthias Hahn <[email protected]> * Copyright (C) 2014 Intel Corporation * This file is part of mmap IPC sample provided under the MIT license * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ /* mmap.cpp Linux native program communicating via memory mapped data with Arduino sketch. Compilation: g++ mmap.cpp -lpthread -o mmap Run: .

/mmap <LED8><LED13> (e.g. .

/mmap 01 -> LED 8 off, LED 13 on) For "random" blink you may run following commands in the command line: while [ 1 ]; do .

/mmap $(($RANDOM % 2))$(($RANDOM % 2)); done */ #include "mmap.h" void exitError(const char* errMsg) { perror(errMsg); exit(EXIT_FAILURE); } using namespace std; /** * @brief: for this example uses a binary string "<led8><led13>"; e.g. "11": both leds on * if no arg equals "00" * For "random" blink you may run following commands in the command line: * while [ 1 ]; do .

/mmap $(($RANDOM % 2))$(($RANDOM % 2)); done */ int main(int argc, char** argv) { struct mmapData* p_mmapData; // here our mmapped data will be accessed int fd_mmapFile; // file descriptor for memory mapped file /* Create shared memory object and set its size */ fd_mmapFile = open(mmapFilePath, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR); if (fd_mmapFile == -1) exitError("fd error; check errno for details"); /* Map shared memory object read-writable */ p_mmapData = static_cast<struct mmapData*>(mmap(NULL, sizeof(struct mmapData), PROT_READ | PROT_WRITE, MAP_SHARED, fd_mmapFile, 0)); if (p_mmapData == MAP_FAILED) exitError("mmap error"); /* the Arduino sketch might still be reading - by locking this program will be blocked until the mutex is unlocked from the reading sketch * in order to prevent race conditions */ if (pthread_mutex_lock(&(p_mmapData->mutex)) != 0) exitError("pthread_mutex_lock"); if (argc == 1) { cout << "8:0" << endl; cout << "13:0" << endl; p_mmapData->led8_on = false; p_mmapData->led13_on = false; } else if (argc > 1) { // assert(correct string given) int binNr = atol(argv[1]); if (binNr >= 10) { cout << "8:1" << endl; p_mmapData->led8_on = true; } else { cout << "8:0" << endl; p_mmapData->led8_on = false; } binNr %= 10; if (binNr == 1) { cout << "13:1" << endl; p_mmapData->led13_on = true; } else { cout << "13:0" << endl; p_mmapData->led13_on = false; } } // signal to waiting thread if (pthread_mutex_unlock(&(p_mmapData->mutex)) != 0) exitError("pthread_mutex_unlock"); if (pthread_cond_signal(&(p_mmapData->cond)) != 0) exitError("pthread_cond_signal"); }



mmap.h



/* * Author: Matthias Hahn <[email protected]> * Copyright (C) 2014 Intel Corporation * This file is part of mmap IPC sample provided under the MIT license * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ #ifndef MMAP_HPP #define MMAP_HPP #include <fcntl.h> #include <unistd.h> #include <sys/mman.h> #include <iostream> #include <string.h> #include <stdlib.h> #include <cstdio> #include <pthread.h> /* assert(/tmp mounted to tmpfs, i.e. resides in RAM) */ /* just use any file in /tmp */ static const char* mmapFilePath = "/tmp/arduino"; struct mmapData { bool led8_on; // led on IO8 bool led13_on; // built-in led pthread_mutex_t mutex; pthread_cond_t cond; }; #endif

Спасибо за внимание! Теги: #linux #Интернет вещей #arduino #galileo #edison #yocto
Вместе с данным постом часто просматривают: