共享内存,顾名思义就是允许两个不相关的进程访问同一个逻辑内存,共享内存是两个正在运行的进程之间共享和传递数据的一种非常有效的方式。不同进程之间共享的内存通常为同一段物理内存。进程可以将同一段物理内存连接到他们自己的地址空间中,所有的进程都可以访问共享内存中的地址。如果某个进程向共享内存写入数据,所做的改动将立即影响到可以访问同一段共享内存的任何其他进程。
注意:
共享内存并未提供同步机制,也就是说,在第一个进程结束对共享内存的写操作之前,并无自动机制可以阻止第二个进程开始对它进行读取,所以我们通常需要用其他的机制来同步对共享内存的访问,例如信号量。
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