转载时请注明出处和作者联系方式
文章出处:http://www.limodev.cn/blog
作者联系方式:李先静 <xianjimli@gmail.com>
共享内存
大家都知道,进程的地址空间是独立的,它们之间互不影响。比如同样地址为0xabcd1234的内存,在不同的进程中,它们的数据是完全不同的。这 样做的好处有:首先是每个进程的地址空间变大了,让编写程序更为容易。其次是一个进程崩溃了,不会影响其它进程,提高了系统的稳定性。
要做到进程的地址空间独立,光靠软件是难以实现的,通常还依赖于硬件的帮助。这种硬件称为MMU(Memory Manage Unit),即所谓的内存管理单元。在这种体系结构下,内存分为物理内存和虚拟内存两种。物理内存就是实际的内存,机器上装了多大内存条就有多大的物理内 存。而应用程序使用的是虚拟内存,访问内存数据时,由MMU根据页表把虚拟内存地址转换对应的物理内存地址。
MMU把各个进程的虚拟内存映射到不同的物理内存上,这样就能保证进程的虚拟内存地址是独立的。由于物理内存远远少于各个进程的虚拟内存的总和,操 作系统会把暂时不用的内存数据写到磁盘上去,把腾出来的物理内存分配给有需要的进程使用。一般会创建一个专门的分区存放换出的内存数据,这个分区称为交换 分区。
从虚拟内存到物理内存的映射并不是一个字节一个字节映射的,而是以一个称为页(page)最小单位的进行的。页的大小视具体硬件平台而定,通常是 4K。当应用程序访问的虚拟内存的页面不在物理内存里时,MMU产生一个缺页中断,并挂起当前进程,缺页中断处理函数负责把相应的数据从磁盘读入内存,然 后唤醒挂起的进程。
进程地址空间独立有它的好处,但我们需要在进程之间共享数据,比如两个进程共享同一段可执行代码或者共享其它数据,这就需要共享内存。实现共享内存 非常容易,只要把两个进程的虚拟内存页面映射同一个物理内存页面就行了。
在程序中使用共享内存非常简单,操作系统或者函数库提供了一些API。如Linux提供了:
void * mmap(void *start, size_t length, int prot , int flags, int fd, off_t offset);
int munmap(void *start, size_t length);
mmap是Linux下创建共享内存最正统的方法,其它共享内存接口都是以它为基础进行包装的。 mmap可以通过参数 start指定映射的地址,但是这个不能保证成功,实际的地址应以返回值为准。如果需要在不同进程之间传递共享内存的地址,最好是在所有进程中,把共享内 存映射到同样的地址,为了保证映射成功,需要精心选择一些不常用的内存地址,并在程序初始化时就映射好。
示例:
下面我们用下循环队列(FIFO Ring),在两个进程之间传递数据,由于循环队列在一个读一个写的情况下不需要加锁,暂时我们可以避开进程间同步的问题。
o 创建共享内存
static int g_shmem_fd = -1; void* shmem_alloc(size_t size) { g_shmem_fd = open("/dev/shm/demo", O_RDWR); if(g_shmem_fd < 0) { char* buffer = calloc(size, 1); g_shmem_fd = open("/dev/shm/demo", O_RDWR | O_CREAT, 0640); write(g_shmem_fd, buffer, size); free(buffer); } void* addr = mmap(0, size, PROT_READ | PROT_WRITE, MAP_SHARED, g_shmem_fd, 0); return addr; }
o 释放共享内存
void shmem_free(void* addr, size_t size) { munmap(addr, size); close(g_shmem_fd); return; }
这里用了一个全局变量 g_shmem_fd来保存文件描述符,这里不好的,在后面的内存管理器中,我们会把它封装到内存管理器。
o 创建循环队列
FifoRing* fifo_ring_create(size_t length) { FifoRing* thiz = NULL; return_val_if_fail(length > 1, NULL); thiz = (FifoRing*)shmem_alloc(sizeof(FifoRing) + length * sizeof(void*)); if(thiz != NULL) { if(thiz->inited == 0) { thiz->r_cursor = 0; thiz->w_cursor = 0; thiz->length = length; thiz->inited = 1; } } return thiz; }
这里的 shmem_alloc代替了以前的malloc。
o 销毁循环队列
void fifo_ring_destroy(FifoRing* thiz) { if(thiz != NULL) { shmem_free(thiz, sizeof(FifoRing) + thiz->length * sizeof(void*)); } return; }
这里的shmem_free代替了以前的free。
循环队列的其它函数实现不变,有兴趣的读者可以参考完整的示例代码。