上一篇文章我们介绍了怎么在ROS中应用LCM与外部进行通信。本篇我们回到ROS的内部,看看如何实现ROS节点间的内存共享,以实现更加快速的内部通信。 首先,我们需要清楚为什么要用内存共享? 答案是ROS提供的服务或者话题都是通过网络来实现的。这样做虽然更具普遍性,照顾到ROS节点可能架设在不同的硬件上这一点,然对于在同一台设备的两个节点间传输数据是非常不友好的。因为其既没必要性又浪费大量资源,且效率低下,容易造成网络堵塞,延时严重。 那么怎么改善最好呢,自然是进程间的内存共享。我们直接让两个节点共享一片物理内存,在里面做一个队列数据结构,一个往里面写,一个去里面读。 下面我们就来实现这一功能。跟上篇文章一样,我们先实现纯净的Linux C++版本,然后再把代码移植到ROS的节点中。 实现Linux进程间的内存共享,主要参考这篇文章(因为写的很好,运行顺利,这部分只做了一点点修改,其他照抄,见谅见谅): “写”进程,流程如下: 1) 获得key, ftok() 2) 使用key来创建一个共享内存 shmget() 3) 映射共享内存(得到虚拟地址), shmat() 4) 使用共享内存, 往共享内存中写入数据 5) 解除映射 shmdt() 6) 如果共享内存不再使用,可以使用shmctl()销毁共享内存 代码如下:
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
int main()
{
// 生成一个key
key_t key = ftok("./", 66);
// 创建共享内存,返回一个id
int shmid = shmget(key, 8, IPC_CREAT|0666|IPC_EXCL);
if(-1 == shmid)
{
perror("shmget failed");
exit(1);
}
// 映射共享内存,得到虚拟地址
void *p = shmat(shmid, 0, 0);
if((void*)-1 == p)
{
perror("shmat failed");
exit(2);
}
// 写共享内存
int *pp = (int *)p;
*pp = 0x12345678;
*(pp + 1) = 0xffffffff;
// 解除映射
if(-1 == shmdt(p))
{
perror("shmdt failed");
exit(3);
}
printf("解除映射成功,点击回车销毁共享内存\n");
getchar();
// 销毁共享内存
if(-1 == shmctl(shmid, IPC_RMID, NULL))
{
perror("shmctl failed");
exit(4);
}
return 0;
}
“读”进程,流程如下: 1) 获得key, ftok() 2) 使用key来获得一个共享内存 shmget() 3) 映射共享内存(得到虚拟地址), shmat() 4) 使用共享内存, 读取共享内存中的数据 5) 解除映射 shmdt() 代码如下:
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
int main()
{
// 生成一个key
key_t key = ftok("./", 66);
// 获取共享内存,返回一个id
int shmid = shmget(key, 0, 0);
if(-1 == shmid)
{
perror("shmget failed");
exit(1);
}
// 映射共享内存,得到虚拟地址
void *p = shmat(shmid, 0, 0);
if((void*)-1 == p)
{
perror("shmat failed");
exit(2);
}
// 读共享内存
int x = *(int *)p;
int y = *((int *)p + 1);
printf("从共享内存中都取了:0x%x 和 0x%x \n", x, y);
// 解除映射
if(-1 == shmdt(p))
{
perror("shmdt failed");
exit(3);
}
return 0;
}
运行结果如下:
下面我们直接把代码移植到ROS中(其实非常简单,就是照抄,加上头尾而已,跟上篇文章相似): writeshmInRos.cpp
#include <ros/ros.h>
#include <iostream>
#include "lcm/lcm-cpp.hpp"
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
int main(int argc, char** argv)
{
ros::init(argc, argv, "image_publisher");
ros::NodeHandle nh;
// 生成一个key
key_t key = ftok("./", 66);
// 创建共享内存,返回一个id
int shmid = shmget(key, 8, IPC_CREAT|0666|IPC_EXCL);
if(-1 == shmid)
{
perror("shmget failed");
exit(1);
}
// 映射共享内存,得到虚拟地址
void *p = shmat(shmid, 0, 0);
if((void*)-1 == p)
{
perror("shmat failed");
exit(2);
}
// 写共享内存
int *pp = (int *)p;
*pp = 0x12345678;
*(pp + 1) = 0xffffffff;
// 解除映射
if(-1 == shmdt(p))
{
perror("shmdt failed");
exit(3);
}
printf("解除映射成功,点击回车销毁共享内存\n");
getchar();
// 销毁共享内存
if(-1 == shmctl(shmid, IPC_RMID, NULL))
{
perror("shmctl failed");
exit(4);
}
ros::spinOnce();
}
readshmInRos.cpp
#include <ros/ros.h>
#include <iostream>
#include "lcm/lcm-cpp.hpp"
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
int main(int argc, char** argv)
{
ros::init(argc, argv, "image_publisher");
ros::NodeHandle nh;
// 生成一个key
key_t key = ftok("./", 66);
// 获取共享内存,返回一个id
int shmid = shmget(key, 0, 0);
if(-1 == shmid)
{
perror("shmget failed");
exit(1);
}
// 映射共享内存,得到虚拟地址
void *p = shmat(shmid, 0, 0);
if((void*)-1 == p)
{
perror("shmat failed");
exit(2);
}
// 读共享内存
int x = *(int *)p;
int y = *((int *)p + 1);
printf("从共享内存中都取了:0x%x 和 0x%x \n", x, y);
// 解除映射
if(-1 == shmdt(p))
{
perror("shmdt failed");
exit(3);
}
ros::spinOnce();
}
运行结果如下:
到此就实现了最简单的节点间的内存共享,当然它离实用还十万八千里,后面有机会会介绍如何改造它,让它实用起来。