本帖最后由 anger0925 于 2016-7-19 15:28 编辑
在Intel edison上处理 Arduino* sketch 时,大家可能会遇到希望添加来自底层 Yocto* Linux 操作系统的部分功能的情况。 如何实现这两个领域之间的高效通信呢。我们首先定义一些需要遵从的标准。 1,标准 1)磁盘(SD 卡,eMMC)上没有通信,目的是降低磁盘磨损和提升性能; 2)事件触发的通信,例如,尤其是我们不想定期检查状态,但希望在处于闲置状态时得到事件的通知。 2,Linux 上的进程间通信 (IPC) 在Intel Edison 上运行的 Arduino sketch 实际上是 Linux 进程以并行的方式运行至其他 Linux 进程。 由于开发板上运行的 Linux 非常成熟,因此我们还可以使用标准方法实现 Arduino 进程与本机进程之间的进程间通信 (IPC)。 Linux 提供多种 IPC 方法。 其中一种是 “内存映射 IPC”。 从本质上来说,它指的是 IPC 进程共享同一内存。 这意味着,只要共享该内存区域的任何一条进程进行任何更改,其他所有进程就会马上看到它所作出的更改。 它还符合我们第一条不在磁盘上写入通信数据的标准,但只在内存上操作。
3,互斥体和条件变量 共享内存会出现以下问题,比如: 1)如何确保只有一条进程在特定时间运算该共享数据? (同步) 2)如果数据发生变化,如何通知其他进程? (通知) 下面我们来回答这两个问题。 我们使用包含在 POSIX 线程 (Pthread) 库(支持 Linux)之中的 “互斥体” 和 “条件变量”。 4,同步 - 互斥体 互斥 (mutex) 是所有多任务操作系统都会提供的一种标准概念。 顾名思义,Pthread 库主要专注于线程编程。 然而,它还提供适用于进程的强大指令。 而且,分别面向intel Edison 的 Arduino* IDE 也支持 pthread(比如,pthread 库链接),因此可轻松集成。 所以,使用 Pthread 来满足我们的要求似乎是自然而然的选择。 通常,互斥体可确保一条线程仅访问代码的某个关键区域。 此处在处理进程时,我们使用互斥体的方法是,只有一条进程可以在代码中继续 pthread_mutex_lock 请求。 操作系统将其他所有进程设置为睡眠状态,直到互斥体调用 pthread_mutex_unlock,之后操作系统将唤醒其他请求pthread_mutex_lock 的进程。 代码:
必须以同样的方式进行,以锁定写入和读取访问,否则,读取操作会访问“只更新了一半”的数据。 接下来我们将介绍 Pthread 的另一个概念,即如何通知数据变化。 5,通知-条件变量 与互斥体概念类似,如果进程尝试访问已被锁定的互斥体,Pthread 将提供条件变量概念。 条件变量允许线程或(本案例中的)进程请求进入睡眠状态,直到通过变量被唤醒。 为此,需要采用函数 pthread_cond_wait。 互斥体和条件变量结合后,会产生以下代码:
其他进程需要解锁互斥体,并通过调用 pthread_cond_signal 发出数据变化信号。 这样就会唤醒睡眠的进程。 6,实现过程 1)就互斥体和条件变量而言,我们需要明确设置属性,以支持进程间的使用; 2)由于 Arduino* IDE 不附带直接共享内存 IPC 所需的所有库,因此我们选择通过 内存映射文件利用共享内存 IPC。 从本质上讲,将通 信文件放入映射至主内存的文件系统,可以提供相同的功能。 Edison 的 Yocto* Linux 包含 temp folder /tmp mounted totmpfs(在内存 中)。 例如,该文件夹中的所有文件都可以。我们选择文件 "/tmp/arduino"。 它仅适用于 IPC。 3)由于 Arduino 进程会在系统启动时开始,因此我们假设 Arduino 进程是要初始化互斥体和条件变量的进程。 4)为了说明这一概念,我们如此放置数据,以共享内存映射结构 mmapData(定义 IO 8 的内置 LED 和外置 LED 是开启还是关闭)中 的两个布尔变量: bool led8_on; // led on IO8 bool led13_on; 显而易见,这里也可以加入其他任何数据。 mmapData 结构中的其他两个变量分别为互斥体和条件变量。 5)我的例程仅实现 Arduino 进程等待运算 Linux 本机进程的数据的情况。 如要达到其他目的,代码必须进行相应的修改。 7,源码 包含以下三个文件: mmap.ino:放在 Arduino* IDE 上的sketch mmap.cpp:发送数据的本机进程 mmap.h :标头文件 - 用于 Arduino* IDE 和 Linux native 的同一个文件如果用于 Arduino*,Arduino* sketch 目录中应该有一个包含 "mmap.ino" 和 "mmap.h" 的文件夹。 如果用于 Linux native,应该有一个包含 "mmap.cpp" 和 "mmap.h" 的文件夹。 Mmap.h源码:
#ifndef MMAP_HPP #define MMAP_HPP #include #include #include #include #include #include #include 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; }; Mmap.cpp源码:
- #include "mmap.h"
- void exitError(const char* errMsg) {
- perror(errMsg);
- exit(EXIT_FAILURE);
- }
-
- using namespace std;
- /*
- for this example uses a binary string ""; e.g. "11": both leds on
- if no arg equals "00"
- */
- 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 */
- /*O_WRONLY|O_CREAT表示打开方式,是只写,创建方式。S_IRUSR|S_IWUSR表示不同用户权限,usr表示拥有者可以读写
- 返回值:成功则返回文件描述符,否则返回 -1
- */
- 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 */
- /*用法:static_cast < type-id > ( expression )该运算符把expression转换为type-id类型*/
- /* mmap将一个文件或者其它对象映射进内存,采用共享内存通信的一个显而易见的好处是效率高,因为进程可以直接读写内存,而不需要任何数据的拷贝。对于像管道和消息队列等通信方式,则需要在内核和用户空间进行四次的数据拷贝,而共享内存则只拷贝两次数据:一次从输入文件到共享内存区,另一次从共享内存区到输出文件。实际上,进程之间在共享内存时,并不总是读写少量数据后就解除映射,有新的通信时,再重新建立共享内存区域。而是保持共享区域,直到通信完毕为止,这样,数据内容一直保存在共享内存中,并没有写回文件。共享内存中的内容往往是在解除映射时才写回文件的。因此,采用共享内存的通信方式效率是非常高的。
- 用法:void *mmap(void *start, size_t length, int prot, int flags,int fd, off_t offset);
- 成功执行时,mmap()返回被映射区的指针,mmap()返回MAP_FAILED[其值为(void *)-1]
- 参数:
- start:映射区的开始地址。length:映射区的长度。
- prot:期望的内存保护标志,不能与文件的打开模式冲突。是以下的某个值,可以通过or运算合理地组合在一起
- PROT_EXEC //页内容可以被执行
- PROT_READ //页内容可以被读取
- PROT_WRITE //页可以被写入
- PROT_NONE //页不可访问
- flags:指定映射对象的类型
- MAP_SHARED //与其它所有映射这个对象的进程共享映射空间。对共享区的写入,相当于输出到文件
- fd:有效的文件描述词。如果MAP_ANONYMOUS被设定,为了兼容问题,其值应为-1。
- offset:被映射对象内容的起点。
- */
- p_mmapData = static_cast(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 */
- /*描述 pthread_mutex_lock()函数锁住由mutex指定的mutex 对象。如果mutex已经被锁住,调用这个函数的线程阻塞直到mutex可用为止。这跟函数返回的时候参数mutex指定的mutex对象变成锁住状态, 同时该函数的调用线程成为该mutex对象的拥有者。*/
- if(pthread_mutex_lock(&(p_mmapData->mutex)) != 0) exitError("pthread_mutex_lock");
- if (argc == 1) {
- /*cout是c++语言的输出符号。endl是程序的结束符。'n' 只是一个换行符*/
- cout << "8:0" << endl;
- cout << "13:0" << endl;
- p_mmapData->led8_on = false;
- p_mmapData->led13_on = false;
- }
- else if (argc > 1) {
- /*atoi函数:将字符串转化为int类型变量,atol函数:将字符串转化为long类型变量*/
- 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
- /*函数释放有参数mutex指定的mutex对象的锁
- pthread_cond_signal(pthread_cond_t *cond)函数是用来在条件满足时,给正在等待的对象发送信息,表示唤醒该变量,一般和pthread_cond_wait函数联合使用,当它接收到signal发来的信号后,就再次锁住mutex,一旦pthread_cond_wait锁定了互斥对象,那么它将返回并允许wait的线程继续执行。*/
- 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.ino源码:
- #include "mmap.h"
- using namespace std;
- struct mmapData* p_mmapData; // here our mmapped data will be accessed
- int led8 = 8;
- int led13 = 13;
-
- void exitError(const char* errMsg) {
- string s_cmd("echo 'error: ");
- s_cmd = s_cmd + errMsg + " - exiting' > /dev/ttyGS0";
- system(s_cmd.c_str());//c_str()函数返回一个指向正规C字符串的指针, 内容与本string串相同.
- 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*/
- /*ftruncate会将参数fd指定的文件大小改为参数length指定的大小。参数fd为已打开的文件描述词,而且必须是以写入模式打开的文件,如果原来的文件大小比参数length大,则超过的部分会被删去*/
- 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(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;
- /*使用 pthread_mutexattr_init(3C) 可以将与互斥锁对象相关联的属性初始化为其缺省值。在执行过程中,线程系统会为每个属性对象分配存储空间。*/
- 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");
- }
复制代码
8,编译 1)Mmap.ino使用Arduino IDE编译上传。 2)在intel edison的Yocto* Linux系统中预安装了 C++ 编译器,所以使用它编译mmap.cpp。 命令:g++ mmap.cpp -lpthread -o mmap
执行./mmap时,出现Bus Error
我们经常会发现有两种内存转储(core dump)
一种是段错误(segment error)通常是在一个非法的地址上进行取值赋值操作造成。)意味着指针所对应的地址是无效地址,没有物理内存对应该地址。
一种是总线错误(bus error)通常是指针强制转换,导致CPU读取数据违反了一定的总线规则。意味着指针所对应的地址是有效地址,但总线不能正常使用该指针。通常是未对齐的数据访问所致。 Bus error解决方案 open过后,添加 lseek(fd_mmapFile,sizeof(mmapData)-1,SEEK_SET); write(fd_mmapFile,"",1); //write NOp 先使用write()函数向已经打开的文件描述符中写东西。 9,连接硬件,执行看预期结果。 我值使用一个LED,连接到arduino扩展的D8上。
达到预期效果。但是注意,由于我们在mmap.ino中加入了 system("echo 8:1 > /dev/ttyGS0");这样的system语句,会导致串口连接不上。所以需要在mmap.ino文件中删除所有system语句。
参考文档:https://software.intel.com/zh-cn/blogs/2014/09/22/efficient-communication-between-arduino-and-linux-native-processes
0
|
|
|
|