Arduino* 与 Linux 原生进程之间的有效通信





0/5 (0投票)
如何实现 Intel® Galileo 或 Intel® Edison 与 Yocto* Linux 操作系统的有效通信。
在使用 Intel® Galileo 或 Intel® Edison 上的 Arduino* 草图时,您可能会遇到需要添加底层 Yocto* Linux 操作系统功能的情况。这就引出了本文的主题:如何在这两个世界之间实现高效通信。让我们先定义一些我们将在接下来的内容中尝试满足的标准。
标准
- 避免在磁盘(SD 卡、eMMC)上进行通信,以减少磁盘磨损并提高性能
- 通过事件触发通信,即我们不希望定期检查状态,而是通过事件获取通知,否则则处于空闲状态
Linux 上的进程间通信 (IPC)
在 Intel® Galileo 或 Intel® Edison 上运行的 Arduino* 草图实际上只是一个与其他 Linux 进程并行运行的 Linux 进程。由于主板上运行着完整的 Linux 系统,因此我们可以使用标准的进程间通信 (IPC) 机制在 Arduino* 进程和原生进程之间进行通信。Linux 上有多种 IPC 方法。其中一种是“内存映射 IPC”。本质上,这意味着 IPC 进程共享同一内存。因此,任何共享该内存区域的进程所做的修改都会立即被所有其他进程看到。这也符合我们不将通信数据写入磁盘而仅在内存中操作的第一个标准。
互斥锁和条件变量
共享内存会引发几个问题,例如:
- 如何确保在给定时间只有一个进程在操作共享数据?(同步)
- 数据修改后如何通知其他进程?(通知)
在接下来的内容中,我们将逐一探讨这两个问题。我们将利用 Linux 上提供的 POSIX 线程 (Pthreads) 库中的“互斥锁”和“条件变量”。
同步 - 互斥锁
互斥锁 (mutex) 是几乎所有现代多任务操作系统提供的标准概念。在本博文中,我们不在此描述概念和细节,只提供关于 POSIX 线程 (Pthreads) 互斥锁的高层信息。有关更多信息,您应该查阅操作系统教材(例如 Tanenbaum、Woodhull:Operating Systems 3rd ed. Pearson 2006)或在线搜索。
顾名思义,Pthreads 库主要专注于线程编程。但是,它也提供了适用于进程的强大命令。此外,Intel® Galileo 和 Intel® Edison 的 Arduino* IDE 开箱即支持 pthreads(即链接到 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 来发出更改信号。这将唤醒睡眠中的进程。
有关更多详细信息,请参阅 Pthreads 的教材或在线教程。在下一节中,我们将介绍一个示例实现。
实现
一些解释
- 对于互斥锁和条件变量,我们需要显式设置属性才能允许进程间使用。
- 我们选择通过内存映射文件利用共享内存 IPC,因为 Arduino* IDE 没有提供直接共享内存 IPC 所需的所有库。将通信文件放在映射到主内存的文件系统中,基本上提供了相同的功能。Edison 附带的 Yocto* Linux 以及 https://software.intel.com/iot 上的 SD 卡 Yocto* 映像都将临时文件夹/tmp 挂载到位于内存中的tmpfs。也就是说,该文件夹内的任何文件都可以。我们选择了文件“/tmp/arduino”。它仅用于 IPC。
- 我们假设 Arduino 进程是初始化互斥锁和条件变量的进程,因为 Arduino 进程会在系统启动时启动。
- 我们仅展示 Arduino* 进程等待 Linux 原生进程数据以对其进行操作的情况。出于其他目的,代码需要相应修改。
- 为了展示概念,我们将要共享的数据放在内存映射结构mmapData 中,该结构包含两个布尔值,用于定义是开启还是关闭内置 LED 和 IO 8 上的外部 LED。
bool led8_on; // led on IO8 bool led13_on; // built-in led
显然,可以将任何其他数据放入其中。mmapData 结构中的另外两个变量是互斥锁和条件变量。
注意
下面的示例代码根据MIT 许可证提供。
下面有三个文件:
- mmap.ino:用于 Arduino* IDE 的草图
- mmap.cpp:发送数据的原生进程
- mmap.h:头文件 - Arduino* IDE 和 Linux 原生进程使用同一个文件
也就是说,在 Arduino* 端,您应该在 Arduino* 草图目录中有一个包含“mmap.ino”和“mmap.h”的文件夹。在 Linux 原生端,您应该有一个包含“mmap.cpp”和“mmap.h”的文件夹。
要运行草图,只需在 Arduino* IDE 中打开“mmap”草图,然后将其上传到正确的板卡(Intel® Galileo Gen 1、Intel® Galileo Gen 2 或 Intel® Edison)。在原生端,https://software.intel.com/iot 上的 Intel IoT devkit 附带一个交叉编译器,或者 Alternatively,Edison 附带的 Yocto* Linux 和 https://software.intel.com/iot 上的 SD 卡 Yocto* 映像或 Intel® Galiileo 板预装了 C++ 编译器。使用预装的编译器,您需要在放置了 mmap.cpp 和 mmap.h 的文件夹中运行:
g++ mmap.cpp -lpthread -o mmap
它将生成一个可执行二进制文件,您可以如下执行:
./mmap {0,1}{0,1}
{0,1} 表示 0 或 1。即 "./mmap 00" 会关闭两个 LED,而 "./mmap 11" 会开启两个 LED。在 Arduino* IDE 的串口监视器 [CTRL+SHIFT+M] 以及启动 Linux 原生进程的控制台中,还会显示一些额外输出,显示已设置的数据。
mmap.ino
/*
* Author: Matthias Hahn <matthias.hahn@intel.com>
* 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 <matthias.hahn@intel.com>
* 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 <matthias.hahn@intel.com>
* 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