共享内存概念
允许两个不相关的进程访问同一个逻辑内存,共享内存是两个正在运行的进程之间共享和传递数据的一种非常有效的方式。不同进程之间共享的内存通常为同一段物理内存。进程可以将同一段物理内存连接到他们自己的地址空间中,所有的进程都可以访问共享内存中的地址。如果某个进程向共享内存写入数据,所做的改动将立即影响到可以访问同一段共享内存的任何其他进程。
注意:
共享内存并未提供同步机制,也就是说,在第一个进程结束对共享内存的写操作之前,并无自动机制可以阻止第二个进程开始对它进行读取,所以我们通常需要用其他的机制来同步对共享内存的访问,例如信号量。
1、CreateFileMapping 创建一个内存文件映射对象
HANDLE CreateFileMapping(
HANDLE hFile, // handle to file to map
LPSECURITY_ATTRIBUTES lpFileMappingAttributes,
// optional security attributes
DWORD flProtect, // protection for mapping object
DWORD dwMaximumSizeHigh, // high-order 32 bits of object size
DWORD dwMaximumSizeLow, // low-order 32 bits of object size
LPCTSTR lpName // name of file-mapping object
通过这个API函数 将创建一个内存映射文件的内核对象,用于映射文件到内存。与虚拟内存一样,
内存映射文件可以用来保留一个地址空间的区域,并将物理存储器提交给该区域。
它们之间的差别是:
物理存储器来自一个已经位于磁盘上的文件,而不是系统的页文件
hFile:
用于标识你想要映射到进程地址空间中的文件句柄。该句柄可以通过调用CreateFile函数返回。
这里,我们并不需要一个实际的文件,所以,就不需要调用 CreateFile 创建一个文件,
hFile 这个参数可以填写 INVALID_HANDLE_VALUE;
lpFileMappingAttributes:
参数是指向文件映射内核对象的 SECURITY_ATTRIBUTES结构的指针,通常传递的值是NULL;
flProtect:
对内存映射文件的安全设置(PAGE_READONLY 以只读方式打开映射;PAGE_READWRITE 以可读、
可写方式打开映射;PAGE_WRITECOPY 为写操作留下备份)
dwMaximumSizeHigh:
文件映射的最大长度的高32位。
dwMaximumSizeLow:
文件映射的最大长度的低32位。如这个参数和dwMaximumSizeHigh都是零,就用磁盘文件的实际长度。
lpName:
指定文件映射对象的名字,别的进程就可以用这个名字去调用 OpenFileMapping 来打开这个 FileMapping 对象。
如果创建成功,返回创建的内存映射文件的句柄,如果已经存在,则也返回其句柄,但是调用 GetLastError()
返回的错误码是:183(ERROR_ALREADY_EXISTS),如果创建失败,则返回NULL;
2、MapViewOfFile 将内存映射文件映射到进程的虚拟地址中
LPVOID MapViewOfFile(
HANDLE hFileMappingObject, // file-mapping object to map into
// address space
DWORD dwDesiredAccess, // access mode
DWORD dwFileOffsetHigh, // high-order 32 bits of file offset
DWORD dwFileOffsetLow, // low-order 32 bits of file offset
DWORD dwNumberOfBytesToMap // number of bytes to map
);
hFileMappingObject:
CreateFileMapping()返回的文件映像对象句柄。
dwDesiredAccess:
映射对象的文件数据的访问方式,而且同样要与CreateFileMapping()函数所设置的保护属性相匹配。
dwFileOffsetHigh:
表示文件映射起始偏移的高32位.
dwFileOffsetLow:
表示文件映射起始偏移的低32位.
dwNumberOfBytesToMap:
文件中要映射的字节数。为0表示映射整个文件映射对象。
3、OpenFileMapping 打开一个命名的文件映射内核对象
HANDLE OpenFileMapping(
DWORD dwDesiredAccess, // access mode
BOOL bInheritHandle, // inherit flag
LPCTSTR lpName // pointer to name of file-mapping object
);
dwDesiredAccess:
同MapViewOfFile函数的dwDesiredAccess参数
bInheritHandle :
如这个函数返回的句柄能由当前进程启动的新进程继承,则这个参数为TRUE。
lpName :
指定要打开的文件映射对象名称。
在接收进程中打开对应的内存映射对象:
在数据接收进程中,首先调用OpenFileMapping()函数打开一个命名的文件映射内核对象,
得到相应的文件映射内核对象句柄hFileMapping;如果打开成功,则调用MapViewOfFile()
函数映射对象的一个视图,将文件映射内核对象hFileMapping映射到当前应用程序的进程地址,
进行读取操作。(当然,这里如果用CreateFileMapping也是可以获取对应的句柄)
4、事件机制
HANDLE CreateEvent(
LPSECURITY_ATTRIBUTES lpEventAttributes, //SD
BOOL bManualReset, //reset type
BOOL bInitialState, //initial state
LPCTSTR lpName //object name
);
该函数创建一个Event同步对象,并返回该对象的Handle
lpEventAttributes: 一般为NULL
bManualReset:
创建的Event是自动复位还是人工复位,
true: 人工复位, 一旦该Event被设置为有信号,则它一直会等到ResetEvent()API被调用时才会恢复为无信号.
false:自动复位,Event被设置为有信号,则当有一个wait到它的Thread时,该Event就会自动复位,变成无信号.
bInitialState: 初始状态,true,有信号,false无信号
lpName: Event对象名
一个Event被创建以后,可以用OpenEvent()API来获得它的Handle
用CloseHandle()来关闭它
用SetEvent()或PulseEvent()来设置它使其有信号
用ResetEvent()来使其无信号
用WaitForSingleObject()或WaitForMultipleObjects()来等待其变为有信号.
5、PulseEvent() 与 () SetEvent() 区别
PulseEvent():相当于发送了一个事件信号脉冲
它使一个Event对象的状态发生一次脉冲变化,从无信号变成有信号再变成无信号,整个操作是原子的.
对自动复位的Event对象,它仅释放第一个等到该事件的thread(如果有),
而对于人工复位的Event对象,它释放所有等待的thread.
系统核心对象中的Event事件对象,在进程、线程间同步的时候是比较常用,发现它有两个出发函数,
一个是 SetEvent,还有一个PulseEvent,两者的区别是:
SetEvent 为设置事件对象为有信号状态;而PulseEvent也是将指定的事件设为有信号状态,
不同的是
如果是一个人工重设事件,正在等候事件的、 被挂起的 所有线程 都会进入活动状态(类似pthread_cond_broadcast),函数随后将事件设回,并返回;
如果是一个自动重设事件,则正在等候事件的、被挂起的 单个线程 会进入活动状态,事件随后设回无信号,并且函数返回。
也就是说在自动重置模式下PulseEvent和SetEvent的作用没有什么区别,
但在手动模式下PulseEvent就有明显的不同,可以比较容易的控制程序是单步走,还是连续走。
如果让循环按要求执行一次就用PulseEvent,如果想让循环连续不停的运转就用 SetEvent ,
在要求停止的地方发个ResetEvent就OK了。
6、创建一个线程
c语言库 process.h 中的函数, 用来创建一个线程
unsigned long _beginthreadex(
void *security, // 安全属性,为NULL时表示默认安全性
unsigned stack_size, // 线程的堆栈大小,一般默认为0
unsigned(_stdcall *start_address)(void *), // 所要启动的线程函数
void *argilist, // 线程函数的参数,是一个void*类型,传递多个参数时用结构体
unsigned initflag, // 新线程的初始状态,0表示立即执行,CREATE_SUSPENDED表示创建之后挂起
unsigned *threaddr // 用来接收线程ID
);
返回值 : // 成功返回新线程句柄, 失败返回0
共享内存设计方式
第一种:采用加锁和事件机制
#pragma once
#include <string>
#include <Windows.h>
#include <process.h>
#include <assert.h>
#include <io.h>
#include <direct.h>
#include <vector>
#include "dllexport.h"
#include "cJSON.h"
using namespace std;
typedef int TID; // 线程ID。
typedef void* THD; // 线程句柄
#define KN_THREAD_BUSY 0 // 线程执行状态(线程池或线程数组)。
#define KN_THREAD_IDLE 1 // 线程空闲(挂起)状态(线程池或线程数组)。
#define KN_WAIT_FOREVER 0xFFFFFFFF // 无限制等待。
#define KN_EXEC_PATH 256 // 定义执行文件(进程)的路径长
/** 线程回调函数类型宏定义。
输入:char* 接收消息, int 消息长度。
返回:无。
注释:失效不用了。
*/
typedef unsigned long (knThreadCb)(void * arg);
// IPC共享内存配置句柄句柄。
typedef struct knIpcShareData {
int key; // 共享内存Key。
int memheight; // 每块共享内存占用字节数。
int memwidth; // 共享内存块总数。
void *smi; // 共享内存句柄。
void *pEvent; // 共享区事件。
} IPCS;
static void callback(void *data, void *pClass);
class knShareMemory
{
public:
DLLMODE knShareMemory();
DLLMODE ~knShareMemory();
//创建目标进程间共享内存区
DLLMODE int ipcCreateIPCSM();
/*
函数:关闭目标进程间共享内存区。
输入:int release_ 释放配置链表:0 释放。
返回:无。
*/
DLLMODE void ipcCloseIPCSM(int release_);
//设置IPC共享内存区事件。
DLLMODE int ipcSetSmEvent(IPCS *ipcs);
/*
函数:等待进程事件。
输入:void *pevt_ 进程通知事件句柄。
返回:0 成功;-1 失败。
注释: 在没有通知事件脉冲时,程序阻塞在这里。
*/
DLLMODE INT ipcWaitSmEvent(IPCS *ipcs);
/*
函数:向共享内存写入数据。
输入:void *sm_ 共享内存标识(in), void *buf_ 写入数据(in),
INT size_ 允许写入的数据(in), int bnb_ 写入内存块编号。
返回:实际写入的字节数据;-1 失败。
注释:支持多个进程向一块内存写数据。
*/
DLLMODE INT knWriteToShareMem(void *sm_, void *buf_, INT size_, int bnb_);
/*
函数:读共享内存数据
输入:void *sm_ 共享内存标识(in), void *buf_ 读出数据(in-out),
int flag_ 读出模式:0读后不销毁数据,1读后销毁数据, int bnb_ 内存块编号。
返回:实际读出的字节数;-1 失败;0无数据。
注释:支持多个进程读取同一共享内存。
*/
DLLMODE INT knReadFromShareMem(void *sm_, void *buf_, int flag_, int bnb_);
//显示共享内存信息
DLLMODE void ipcShowIpcSm(IPCS *ipcs_);
/*
函数:创建1个工作线程实例。
输入:void *cbfunc_ 线程回调函数句柄;
void *param_ 线程回调函数参数指针;
int flag_ 线程状态;状态:KN_THREAD_BUSY 立刻执行;KN_THREAD_IDLE 空闲;
THD *hd_ 线程句柄(in_out);
TID *id_ 线程ID(in_out);
返回:0 成功;-1 失败。
*/
DLLMODE int knCreateThread(void *cbfunc_, void *param_, int flag_, THD *hd_, TID *id_);
/** 函数:秒睡眠.
输入:INT seconds_秒,如果超时定义KN_WAIT_FOREVER,则无限等待.
返回:无.
类型:底层封装函数。支持Windows与Linux。
*/
DLLMODE int knSleep(INT seconds_);
/** 函数:启动进程。
输入:char *path_ 启动进程根路径(路径字符串结尾必须用斜杠结束),char *proc_
启动进程名。
返回:进程PID, -1失败
注释:proc_必须是执行进程的全路径,本函数还要解析分离出进程路径,并进行相关操作。
*/
DLLMODE int knOpenProcByName(char *path_, char *proc_);
//读取json文件
int readJson();
public:
vector<IPCS > m_vector;//所有的内存块信息
char m_note[128];
char m_path[128];
char m_proc[64];
void * m_shareData;
};
第二种:采用无锁循环队列
#ifndef TESTDATAMEMORY_H_
#define TESTDATAMEMORY_H_
#include "dllexport.h"
#include"NoviceSharedMemory.h"
#define MAX_NUM 50
//用户数据定义
struct StructPackage
{
unsigned short test0;
char buffer[1024];
void *str;
};
//内存块的数据结构
struct StructData
{
StructPackage Package[MAX_NUM];
int Index;
};
class CTestDataMemory : public novice::CNoviceSharedMemory
{
public:
DLLMODE CTestDataMemory();
DLLMODE ~CTestDataMemory();
//key:共享内存标识符
DLLMODE bool OpenMemory(const std::string &key);
DLLMODE void CloseMemory();
DLLMODE void WriteData(const StructPackage *pData, int &Index);
DLLMODE void WriteData(const StructPackage &Data, int &Index);
DLLMODE void ReadData(StructPackage &Data, int &Index);
private:
DLLMODE StructData * GetPtr();
};
#endif