共享内存的编程模型

本博文为原创,遵循CC3.0协议,转载请注明出处:http://blog.csdn.net/lux_veritas/article/details/11934083

---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------



1.什么是共享内存的编程模型?

2.共享内存有哪几种实现方式?

2.1 mmap的实现方式

2.2 System V API

2.3 POSIX API

3. 深入分析

4. 总结

5. References


1. 什么是共享内存?

硬件的视角来看共享内存:在一个多处理机系统中,一条RAM可以被多个不同的CPU处理器(核)访问到,这即是一个共享内存的硬件系统。

软件的角度来看共享内存,它是进程间通信(IPC)的一种方式,通过某种手段共享某一段物理内存(页)的多个进程,通过对共享的这部分物理内存页进行读写,完成彼此之间的通信。【1】

大多数软件工程师所说的共享内存,指的是IPC的这种通信方式,而与其他IPC机制相比,共享内存省略了一些数据的拷贝过程,因而成为最快的一种IPC通信方式。本文主要讨论的也是共享内存的IPC机制。


2. 共享内存有哪几种实现方式?

共享内存的机制允许两个或多个进程通过把公共的数据结构放入一个共享的内存区域来访问它们。UNIX有两套事实上的共享内存API,一种是标准的POSIX API,一种是比较老的System V API。【2】

两种API的工作方式与执行流程大体相同,都是间接使用mmap的方式,完成内存文件映射的过程。

此处以System V API为例,结合内核源码,简述其实现共享内存通信的过程。举个简单的范例,两个进程,一个写进程,用于向共享内存段写数据,一个读进程,用于从共享内存段读数据。

write进程:

#include <stdio.h>                                                                                                                                           
#include <sys/ipc.h>                                                                                                                                         
#include <sys/shm.h>                                                                                                                                         
#include <sys/types.h>                                                                                                                                       
#include <string.h>                                                                                                                                          
                                                                                                                                                             
int main()                                                                                                                                                   
{                                                                                                                                                            
    int shm_id;                                                                                                                                              
    key_t key;                                                                                                                                               
    char pathname[20];                                                                                                                                                                                                                                                                     
    int i = 880425;                                                                                                                                          
    int *shmem_int = &i;                                                                                                                                     
                                                                                                                                                             
    strcpy(pathname, "/tmp");                                                                                                                                
    key = ftok(pathname, 0x03);                                                                                                                              
    if(key == -1)                                                                                                                                            
    {                                                                                                                                                        
        printf("ftok error!\n");                                                                                                                             
        return -1;                                                                                                                                           
    }                                                                                                                                                        
    shm_id = shmget(key, 4096, IPC_CREAT|IPC_EXCL|0600);                                                                                                     
    if(shm_id == -1)                                                                                                                                         
    {                                                                                                                                                        
        printf("shmget error!\n");                                                                                                                           
        return -1;                                                                                                                                           
    }                                                                                                                                                        
    printf("shm_id is: %d\n", shm_id);                                                                                                                                                                                                                                
    shmem_int = (int*) shmat(shm_id, NULL, 0);                                                                                                               
    *shmem_int = 12345;                                                                                                                                      
    printf("shared int is: %d\n", *shmem_int);                                                                                                                                   
    shmdt(shmem_int);                                                                                                                                        
                                                                                                                                                             
    return 0;                                                                                                                                                
}

read进程:

#include <stdio.h>                                                                                                                                           
#include <sys/ipc.h>                                                                                                                                         
#include <sys/shm.h>                                                                                                                                         
#include <sys/types.h>                                                                                                                                       
#include <string.h>                                                                                                                                          
                                                                                                                                                             
int main()                                                                                                                                                   
{                                                                                                                                                            
    int shm_id;                                                                                                                                              
    key_t key;                                                                                                                                               
    char pathname[20];                                                                                                                                                                                                                                                                            
    int* shmem_int;                                                                                                                                          
                                                                                                                                                             
    strcpy(pathname, "/tmp");                                                                                                                                
    key = ftok(pathname, 0x03);                                                                                                                              
    if(key == -1)                                                                                                                                            
    {                                                                                                                                                        
        printf("ftok error!\n");                                                                                                                             
        return -1;                                                                                                                                           
    }                                                                                                                                                        
    shm_id = shmget(key, 0, 0);                                                                                                                              
    if(shm_id == -1)                                                                                                                                         
    {                                                                                                                                                        
        printf("shmget error!\n");                                                                                                                           
        return -1;                                                                                                                                           
    }                                                                                                                                                        
    printf("shm_id is: %d\n", shm_id);                                                                                                                                                                                                                                  
    shmem_int = (int*) shmat(shm_id, NULL, 0);                                                                                                               
    printf("answer is: %d\n", *shmem_int);                                                                                                                   
    if(shmdt(shmem_int) == -1)                                                                                                                               
    {                                                                                                                                                        
        printf("shmde error!\n");                                                                                                                            
        return -1;                                                                                                                                           
    }                                                                                                                                                        
                                                                                                                                                             
    return 0;                                                                                                                                                
}

流程大体分为三步:

1.写进程与读进程协商一个相同的关键字,(此处通过ftok函数,利用路径/tmp生成key)。写进程利用该关键字,通过系统调用shmget,生成一个系统内唯一的共享内存描述符id;读进程同样利用该关键字,通过系统调用shmget,获得写进程返回的共享内存描述符id

2.写进程利用第一步返回的id,通过系统调用shmat,创建一段共享内存区域;读进程利用该id,系统调用shmat,链接到写进程创建的该共享内存区域。至此,读、写进程便可通过向共享内存区读、写数据,完成进程间通信

3.分别调用shmdt删除共享内存区域。


3. 深入分析:

1. shmget系统调用:

shmget系统调用最主要的作用就是,根据linux的IDR机制【3】,利用提供的共享内存关键字key,生成一个系统内唯一的共享内存描述符shm_id,或者利用该关键字查找定位系统内已定义的shm_id。并且创建一个struct shmid_kernel结构,该结构用于描述一个共享内存资源,定义如下:

struct shmid_kernel /* private to the kernel */
{	
	struct kern_ipc_perm	shm_perm;
	struct file *		shm_file;
	unsigned long		shm_nattch;
	unsigned long		shm_segsz;
	time_t			shm_atim;
	time_t			shm_dtim;
	time_t			shm_ctim;
	pid_t			shm_cprid;
	pid_t			shm_lprid;
	struct user_struct	*mlock_user;
};

在shmget内部调用的函数,有很多是与其他进程通信机制共享的,即完成相同的功能。


2. shmat系统调用:

shmat系统调用最主要的作用是,在内存中创建共享内存对象,并利用do_mmap函数,将共享内存对象映射到调用进程的线性空间中。每一个想要使用该段共享内存对象的进程,都要利用描述该对象的系统内唯一的shm_id,调用shmat系统调用,将其映射到自己的线性空间中。

shmat调用do_shmat,创建一个file结构,该结构通过struct shmid_kernel间接获得(该描述结构由shmget系统调用生成),代码如下:

        ... ...

        path = shp->shm_file->f_path;
	path_get(&path);
	shp->shm_nattch++;
	size = i_size_read(path.dentry->d_inode);
	shm_unlock(shp);

	err = -ENOMEM;
	sfd = kzalloc(sizeof(*sfd), GFP_KERNEL);
	if (!sfd)
		goto out_put_dentry;

	file = alloc_file(&path, f_mode,
			  is_file_hugepages(shp->shm_file) ?
				&shm_file_operations_huge :
				&shm_file_operations);

        ... ...


对该file进行初始化,利用do_mmap函数,将该file对象映射到调用进程的线性空间中

... ...

user_addr = do_mmap (file, addr, size, prot, flags, 0);

... ...

所以本质上讲,system V IPC机制也是通过内存映射的方式完成对内存资源的共享。


4. 总结:

套用别人的一段总结 【4】

1、 系统V共享内存中的数据,从来不写入到实际磁盘文件中去;而通过mmap()映射普通文件实现的共享内存通信可以指定何时将数据写入磁盘文件中。注:前面讲到,系统V共享内存机制实际是通过映射特殊文件系统shm中的文件实现的,文件系统shm的安装点在交换分区上,系统重新引导后,所有的内容都丢失。

2、 系统V共享内存是随内核持续的,即使所有访问共享内存的进程都已经正常终止,共享内存区仍然存在(除非显式删除共享内存),在内核重新引导之前,对该共享内存区域的任何改写操作都将一直保留。

3、 通过调用mmap()映射普通文件进行进程间通信时,一定要注意考虑进程何时终止对通信的影响。而通过系统V共享内存实现通信的进程则不然。

共享内存允许两个或多个进程共享一给定的存储区,因为数据不需要来回复制,所以是最快的一种进程间通信机制。共享内存可以通过mmap()映射普通文件(特殊情况下还可以采用匿名映射)机制实现,也可以通过系统V共享内存机制实现。应用接口和原理很简单,内部机制复杂。为了实现更安全通信,往往还与信号灯等同步机制共同使用。

共享内存涉及到了存储管理以及文件系统等方面的知识,深入理解其内部机制有一定的难度,关键还要紧紧抓住内核使用的重要数据结构。系统V共享内存是以文件的形式组织在特殊文件系统shm中的。通过shmget可以创建或获得共享内存的标识符。取得共享内存标识符后,要通过shmat将这个内存区映射到本进程的虚拟地址空间。



5. References:

【1】http://en.wikipedia.org/wiki/Shared_memory

【2】http://www.ibm.com/developerworks/cn/aix/library/au-spunix_sharedmemory/

【3】Linux的idr机制介绍 http://www.cnblogs.com/lfsblack/archive/2012/09/15/2686557.html  http://blog.csdn.net/orz415678659/article/details/8539794

【4】http://www.ibm.com/developerworks/cn/linux/l-ipc/part5/index2.html

你可能感兴趣的:(memory,共享内存,shared)