目录
上篇文章《【进程间通信】——共享内存收发》介绍了一个进程发,另一个进程收,本章介绍两个进程分别进行收发,其中基于共享内存的动态库仍然保持不变。
一、基于共享内存的动态库
1.1 源码
头文件shared_memory.h
内容如下,通过结构体SharedData
定义了传输的消息类型,此时结构体内声明了一个uint8类型的数组,数组长度为1024。
// shared_memory.h
#ifndef SHARED_MEMORY_H
#define SHARED_MEMORY_H
#include <cstdint>
struct SharedData {
uint16_t len;
uint8_t data[1024]; // 分配一个足够大的空间来存储数据
};
bool create_shared_memory(const char* name);
bool write_shared_memory(const char* name, const uint8_t* data, uint16_t len);
bool read_shared_memory(const char* name, uint8_t* data, uint16_t len);
bool close_shared_memory(const char* name);
#endif
函数定义文件shared_memory.cpp
主要包含创建共享内存空间、向共享内存地址写数据、从共享内存地址读数据、销毁共享内存空间。
// shared_memory.cpp
// g++ -shared -fPIC shared_memory.cpp -o libsharedmemory.so -lrt
#include "shared_memory.h"
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <cstring>
#include <iostream>
#include <thread>
#include <chrono>
#include <atomic>
#include <sys/syscall.h>
#include <linux/futex.h>
#include <sys/time.h>
#include <errno.h>
bool create_shared_memory(const char* name) {
int shm_fd = shm_open(name, O_CREAT | O_RDWR, 0666);
if (shm_fd == -1) {
std::cerr << "Failed to create shared memory." << std::endl;
return false;
}
if (ftruncate(shm_fd, sizeof(SharedData)) == -1) {
std::cerr << "Failed to set shared memory size." << std::endl;
return false;
}
close(shm_fd);
return true;
}
bool write_shared_memory(const char* name, const uint8_t* data, uint16_t len) {
int shm_fd = shm_open(name, O_RDWR, 0666);
if (shm_fd == -1) {
std::cerr << "Failed to open shared memory." << std::endl;
return false;
}
SharedData* shared_data = reinterpret_cast<SharedData*>(mmap(nullptr, sizeof(SharedData), PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0));
shared_data->len = len;
memcpy(shared_data->data, data, len);
munmap(shared_data, sizeof(SharedData));
close(shm_fd);
return true;
}
bool read_shared_memory(const char* name, uint8_t* data, uint16_t len) {
int shm_fd = shm_open(name, O_RDONLY, 0666);
if (shm_fd == -1) {
std::cerr << "Failed to open shared memory." << std::endl;
return false;
}
SharedData* shared_data = reinterpret_cast<SharedData*>(mmap(nullptr, sizeof(SharedData), PROT_READ, MAP_SHARED, shm_fd, 0));
len = shared_data->len;
memcpy(data, shared_data->data, len);
munmap(shared_data, sizeof(SharedData));
close(shm_fd);
return true;
}
bool close_shared_memory(const char* name) {
return shm_unlink(name) == 0;
}
此时不再在函数定义文件shared_memory.cpp
指定固定的共享内存名称,而是作为一个参数传入,同样在写数据时,也是将要写入的变量传入,通过memcpy函数写入到共享内存中;在读数据时,将准备好的变量传入,通过memcpy函数拷贝出来。
在写和读共享内存时,通过mmap()函数映射临时变量shared_data
到共享内存空间后,在操作完数据后需要通过munmap()函数解除进程虚拟地址空间的映射。
1.2 编译命令
g++ -shared -fPIC shared_memory.cpp -o libshared_memory.so
注意其它程序要动态链接该库,需要添加动态库寻找的路径:
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:.
最后的.
代表动态库处于当前目录。
1.3 函数解释
1.3.1 shm_open()函数
shm_open是一个POSIX共享内存API的函数,用于创建或打开一个命名的共享内存对象。这个函数的原型如下:
int shm_open(const char *name, int oflag, mode_t mode);
参数:
- name:共享内存对象的名称,通常以一个斜杠(/)开头。
- oflag:打开或创建共享内存对象的标志,如O_CREAT(创建)、O_RDWR(读写模式)等。
- mode:设置共享内存对象的权限,如S_IRUSR(用户读权限)和S_IWUSR(用户写权限)等。
- 返回值:成功时返回一个非负整数的文件描述符,失败时返回-1。
1.3.2 ftruncate()函数
ftruncate函数用于调整文件大小,通常用于设置共享内存对象的大小。这个函数的原型如下:
int ftruncate(int fd, off_t length);
参数:
- fd:文件描述符,通常是shm_open函数的返回值。
- length:文件的新大小。
- 返回值:成功时返回0,失败时返回-1。
1.3.3 mmap()函数
mmap函数用于将一个文件或共享内存对象映射到进程的虚拟地址空间。这个函数的原型如下:
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
参数:
- addr:建议的映射起始地址,通常设置为NULL,让系统自动选择。
- length:映射区域的大小。
- prot:映射区域的保护属性,如PROT_READ(可读)、PROT_WRITE(可写)等。
- flags:映射类型,如MAP_SHARED(共享映射)或MAP_PRIVATE(私有映射)等。
- fd:文件描述符,通常是shm_open函数的返回值。
- offset:文件偏移量。
- 返回值:成功时返回映射区域的起始地址,失败时返回MAP_FAILED。
1.3.4 munmap()函数
munmap函数用于解除进程虚拟地址空间的映射。这个函数的原型如下:
int munmap(void *addr, size_t length);
参数:
- addr:映射区域的起始地址。
- length:映射区域的大小。
- 返回值:成功时返回0,失败时返回-1。
1.3.5 shm_unlink()函数
shm_unlink函数用于删除一个命名的共享内存对象。当共享内存对象的引用计数降至0时,它会被系统释放。这个函数的原型如下:
int shm_unlink(const char *name);
参数:
- name:共享内存对象的名称,通常与shm_open函数中使用的相同。
- 返回值:成功时返回0,失败时返回-1。
1.3.6 close()函数
close函数用于关闭一个文件描述符。这个函数的原型如下:
int close(int fd);
参数:
- fd:要关闭的文件描述符。
- 返回值:成功时返回0,失败时返回-1。
二、sender.cpp
2.1 源码
// sender.cpp
// export LD_LIBRARY_PATH=$PWD:$LD_LIBRARY_PATH
// g++ sender.cpp -o sender -L. -lsharedmemory -lrt -lpthread
#include <iostream>
#include <string.h>
#include "shared_memory.h"
#include <thread>
#include <chrono>
#include <atomic>
#include <cstring>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <linux/futex.h>
#include <sys/time.h>
#include <errno.h>
int main() {
const char* shared_memory_name = "my_shared_memory";
bool succes = create_shared_memory(shared_memory_name);
uint8_t data[] = {1, 2, 3, 4, 5};
uint16_t len = 5;
if(write_shared_memory(shared_memory_name, data, len)){
std::cout << "Data written to shared memory: ";
for (int i = 0; i < len; ++i) {
std::cout << (int)data[i] << " ";
}
std::cout << std::endl;
} else {
std::cerr << "Failed to create shared memory." << std::endl;
}
char ch = getchar();
// 再次从共享内存中读取数据并打印出来
uint8_t data2[5];
uint16_t len2 = 5;
if(read_shared_memory(shared_memory_name, data2, len2)){
std::cout << "Data read from shared memory: ";
std::cout << "len2=" <<len2<< std::endl;
for (int i = 0; i < len2; ++i) {
std::cout << static_cast<int>(data2[i]) << " ";
}
std::cout << std::endl;
} else {
std::cerr << "Failed to read shared memory." << std::endl;
}
std::cout << "Closing shared memory." << std::endl;
close_shared_memory(shared_memory_name);
return 0;
}
2.2 编译运行命令
编译:
g++ sender.cpp -o sender -L. -lshared_memory -lrt
-
g++
:C++ 编译器。 -
sender.cpp
:要编译的源文件。 -
-o sender
:指定输出文件名。 -
-L.
:告诉编译器在当前目录中查找共享库文件。 -
-lshared_memory
:告诉编译器共享库的名称
运行:
export LD_LIBRARY_PATH=$PWD:$LD_LIBRARY_PATH
./sender
注意运行该文件时,需要export命令添加共享内存动态库的路径,这里用$PWD
获取动态库目录,并添加到$LD_LIBRARY_PATH
目录之前。
2.3 源码解释
sender写入共享内存数据后,receiver接收数据,并更新数据,再次写入共享内存中,然后sender从共享内存中读出指定数量为len
的数据。
三、receiver.cpp
3.1 源码
// receiver.cpp
// export LD_LIBRARY_PATH=$PWD:$LD_LIBRARY_PATH
// g++ receiver.cpp -o receiver -L. -lsharedmemory -lrt -lpthread
#include <iostream>
#include <string.h>
#include "shared_memory.h"
#include <thread>
#include <chrono>
#include <atomic>
#include <cstring>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <linux/futex.h>
#include <sys/time.h>
#include <errno.h>
int main() {
const char* shared_memory_name = "my_shared_memory";
uint8_t data[] = {0, 0, 0, 0, 0};
uint16_t len = 5;
// 从共享内存读出数据并打印出来
read_shared_memory(shared_memory_name, data, len);
std::cout << "Data read from shared memory: ";
for (int i = 0; i < len; ++i) {
std::cout << static_cast<int>(data[i]) << " ";
}
std::cout << std::endl;
// 再将这些数据全部+1再次写入
for (int i = 0; i < len; ++i) {
data[i] += 1;
}
bool success = write_shared_memory(shared_memory_name, data, len);
if (success) {
std::cout << "Data write to shared memory: ";
for (int i = 0; i < len; ++i) {
std::cout << static_cast<int>(data[i]) << " ";
}
std::cout << std::endl;
} else
std::cerr << "Failed to write shared memory." << std::endl;
read_shared_memory(shared_memory_name, data, len);
std::cout << "Data read from shared memory: ";
for (int i = 0; i < len; ++i) {
std::cout << static_cast<int>(data[i]) << " ";
}
std::cout << std::endl;
return 0;
}
3.2 编译运行命令
编译:
g++ receiver.cpp -o receiver -L. -lshared_memory -lrt
运行:
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:.
./receiver
注意运行该文件时,需要export命令添加共享内存动态库的路径。
3.3 源码解释
在上一篇文章的基础上,将数据更新,重新又写入共享内存中。
四、注意事项
1 注意std::cout
无法输出uint
类型数据,需要将其转换为int数据进行打印输出。
2 有个小缺陷,这里的len
在收和发文件中必须相同,可以在数组中填充空的数据。
3 注意这里共享内存名称没加/
似乎也可以。