首页 > 代码库 > Linux共享内存

Linux共享内存

今天Mayuyu要学习Linux中一个重要的东西,即共享内存。我们知道在Linux中提供了多种IPC通信机制,比如信号,信号量,管道,消息队列,共享内存和套接字等。其中共享内存的效率最高。Mayuyu将用以下几个方面来介绍

 

Contens

 

     1. 共享内存的认识

     2. 共享内存的原理

     3. Linux中共享内存的API

     4. 共享内存的读写问题

     5. 关于共享内存的命令

 

 

1. 共享内存的认识

 

   所谓共享内存,就是多个进程间共同地使用同一段物理内存空间,它是通过将同一段物理内存映射到不同进程

   的虚拟空间来实现的。由于映射到不同进程的虚拟空间中,不同进程可以直接使用,不需要像消息队列那样进

   行复制,所以共享内存的效率很高。

 

   共享内存可以通过mmap()映射普通文件机制来实现,也可以System V共享内存机制来实现,System V是通

   过映射特殊文件系统shm中的文件实现进程间的共享内存通信,也就是说每个共享内存区域对应特殊文件系统

   shm中的一个文件。

 

 

2. 共享内存的原理

 

   System V共享内存把所有共享数据放在共享内存区,任何想要访问该数据的进程都必须在本进程的地址空间

   新增一块内存区域,用来映射存放共享数据的物理内存页面。System V共享内存通过shmget函数获得或创建

   一个IPC共享内存区域,并返回相应的标识符,内核在保证shmget获得或创建一个共享内存区,初始化该共享

   内存区相应的shmid_kernel结构,同时还将在特殊文件系统shm中创建并打开一个同名文件,并在内存中建立

   起该文件的相应的dentry及inode结构,新打开的文件不属于任何一个进程,所有这一切都是系统调用shmget

   函数完成的。

 

   上面说到,在每一个共享内存区都有一个控制结构shmid_kernel,它是共享内存区域中一个非常重要的结构,

   它是存储管理和文件系统结合起来的桥梁,定义如下

 

   

 

   该结构体中一个最重要的域是shm_file,它存储了被映射文件的地址,每个共享内存区对象都对应特殊文件

   系统shm中的一个文件,一般情况下,特殊文件系统shm中的文件是不能用read(),write()等方法访问的,

   当采取共享内存的方式把其中的文件映射到进程地址空间后,可以采用访问内存方式对其访问。

 

   对于System V共享内存区来说,kern_ipc_perm的宿主是shmid_kernel结构,shmid_kernel是用来描

   述一个共享内存区域的,这样内核就能够控制系统中所有的共享区域。同时在shmid_kernel结构的file类型

   指针类型shm_file指向文件系统shm中相应的文件。这样,共享内存区域就与shm文件系统中的文件对应起来。

 

 

3. Linux中共享内存的API

 

   System V IPC机制下创建和使用共享内存是通过如下四个API来完成的。

 

  (1)shmget()

 

      创建共享内存

 

     函数原型为:

 

      此函数调用用来创建一个新的共享内存段或者存取一个已经存在的共享内存段。函数调用成功返回共享内

      存段的标识符,否则返回-1。

 

      参数说明如下

 

      key :    用来表示新建或者已存在的共享内存区的关键字。

      size:    共享内存的大小。

      shmflg : 可以指定的特殊标识,IPC_CREAT,IPC_EXCL以及低九位的权限。

 

      shmget的内部包含了许多重要的System V共享内存机制,shmget在把共享内存区域映射到进程空间时,

      并不真正改变进程的页表,当进程第一次访问内存映射区域时,会因为没有物理页表的分配而导致一个缺

      页异常,然后内核再根据相应的存储管理机制为共享内存映射区域分配相应的页表。

 

      

  (2)shmat()

 

      映射共享内存

 

      函数原型为:

 

      在获得新创建的一个共享内存段或者已存在的共享内存段的标识符后,通过shmat调用可以将共享内存段

      映射到进程的内存空间。函数调用成功返回共享内存段的地址,否则返回-1。当一个共享内存段被附加到

      一个新进程后,共享内存段的shm_array结构中的共享计数器会加1。

 

      参数说明

 

      shmid :  共享内存的关键字。

      shmaddr :指定共享内存出现在进程内存地址的位置,通常设置为零,表示让内核指定一个合适的位置。

      shmflg  :附加段的读写属性。值为SHM_RDONLY为只读模式,其它值为读写模式。

 

 

  (3)shmdt()

 

      共享内存解除映射

 

      函数原型为:

 

      此函数调用可以将映射到进程的共享内存段撤销。函数调用成功返回0值,否则返回-1。一个进程与一个

      共享内存分离时,共享内存段的shm_array结构中的共享计数器值减1,直到计数器值为0,系统才真正

      删除这个共享内存页面。

 

  (4)shmctl()

 

      完成对共享内存的控制

 

      函数原型为:

 

      用来查询共享内存的使用情况和进行一些控制操作,函数调用成功返回0,否则返回-1。

 

      而表示将删除shm_id指示的共享内存。

 

      参数说明如下

 

      shmid :  共享内存的标识符。

      cmd   :  IPC_STAT:得到共享内存的状态,把共享内存的shmid_ds结构复制到buf中。
                IPC_SET: 改变共享内存的状态,把buf所指的shmid_ds结构中的uid、gid、mode复制

                          到共享内存的shmid_ds结构内。
                IPC_RMID:删除这片共享内存。

      buf   :  共享内存管理结构体,具体说明参见共享内存内核结构定义部分。

 

 

4. 共享内存的读写问题

 

   下面的读写问题是共享内存的一个经典实例。通过Writer程序将内容写入共享内存,而通过Reader程序将写

   入的共享内存的数据读取出来,通过调用共享内存的API实现,也算是对共享内存的一个简单实践。

 

代码:Writer.cpp

#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>

struct Person
{
	int age;
	char name[25];
};

void _work()
{
	const char *file = "share";
	key_t key = ftok(file, 0);
	if(key == -1)
	{
		perror("ftok error!");
		return;
	}

	int shm_id = shmget(key, 4096, IPC_CREAT);
	if(shm_id == -1)
	{
		perror("shmget error!");
		return;
	}

	Person *_map = (Person*)shmat(shm_id, NULL, 0);

	char c = 'a';
	for(int i = 0; i < 10; i++)
	{
        c++;
		memcpy((*(_map + i)).name, &c, 1);
        (*(_map + i)).age = 20 + i;
	}

	if(shmdt(_map) == -1)
	{
		perror("detach error!");
	}
}

int main(int argc, char **argv)
{
	_work();
    return 0;
}


Reader.cpp

#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <string.h>
#include <stdio.h>

struct Person
{
	int age;
	char name[25];
};

void _work()
{
	const char *file = "share";
	key_t key = ftok(file, 0);
	if(key == -1)
	{
		perror("ftok error!");
		return;
	}

	int shm_id = shmget(key, 4096, IPC_CREAT);
	if(shm_id == -1)
	{
		perror("shmget error!");
		return;
	}

	Person *_map = (Person*)shmat(shm_id, NULL, 0);

    for(int i = 0; i < 10; i++)
	{
        printf("name = %s\n", (*(_map + i)).name);
		printf("age = %d\n\n", (*(_map + i)).age);
	}
    
	if(shmdt(_map) == -1)
	{
		perror("detach error!");
	}
}

int main(int argc, char **argv)
{
	_work();
	return 0;
}


 

5. 关于共享内存的命令

 

   要说到共享内存的命令,首先来介绍一个命令:ipcs,它是用来查看当前进程间通信设施的状态的。后面跟

   的参数种类比较多,比如ipcs -all表示查看所有设施类型,主要包括三种:共享内存消息队列信号量

 

   

 

   当然也可以单独查询某一IPC类型的设备情况

 

   查询共享内存的信息

 

   

 

   第一列就是共享内存的key;

   第二列是共享内存的编号shmid;

   第三列就是创建的用户owner;

   第四列就是权限perms;

   第五列为创建的大小bytes;

   第六列为连接到共享内存的进程数nattach;

   第七列是共享内存的状态status。其中显示“dest”表示共享内存段已经被删除,但是还有用户在使用它,当

   该段内存的mode字段设置为SHM_DEST时就会显示“dest”。当用户调用shmctl的IPC_RMID时,内存先查看

   多少个进程与这个内存关联着,如果关联数为0,就会销毁这段共享内存,否者设置这段内存的mod的mode位

   为SHM_DEST,如果所有进程都不用则删除这段共享内存。

 

   查询消息队列的信息

 

   

 

   查询信号量的信息

 

    

 

      在Linux中可以使用ipcrm命令来删除共享内存,信号量或消息队列的标志。

 

 

 

文档推荐:http://wenku.baidu.com/link?url=LUi4JVQNPw71f_n59u40wy0gH0Yq0l0YqRhnWxpBbxFS2dI72wpg4CLkfec94Ygh_gugRokhcm1mF3rORitvEFwLfzNtm0tE8SSECDoA0Je

 

 

 

Linux共享内存