说明:只供学习交流,转载请注明出处
一,共享内存的基本概念
共享内存是IPC(进程间通信)中最简单的方式之一。共享内存充许两个或多个进程访问同一块内存,就如同malloc()函数向不同进程返回了指向同一个物理内存的指针。当一个进程改变了这块地址中的内容的时候,其它进程都会察觉到这个更改。因为所有进程共享同一块内存。共享内存在各种进程间通信中具有最高的效率。访问共享内存区域和访问进程独有的内存区域一样快,并不需要通过系统调用或者其他切入内核的过程来完成。同时它也避免了对数据的各种不必要的复制。
由于多个进程同时需要对共享内存空间进行操作,必须对共享内存空间的访问采用某种同步方式。常见的方式是使用信号量来确保在某个时刻只有一个进程访问共享内存。
二,创建共享内存
要使用共享内存进行进程间通信,首先需要创建共享内存资源。shmget函数用于创建新的共享内存段,或是对一个已经存在的共享内存段进行存取。shmget函数的具体信息如表所示:
shmget函数
头文件 | #include <sys/ipc.h> #include <sys/shm.h> | ||
函数原型 | int shmget(key_t key, size_t size, int shmflg); | ||
返回值 | 成功 | 失败 | 是否设置errno |
共享内存段标识符 | -1 | 是 |
函数功能:分配共享内存
参数说明:
key:key为共享内存的键值,当key的取值为IPC_PRIVATE,或key不取IPC_PRIVATE,同时内核中没有与key相对应的共享内存段存在,shmflg设置了IPC_CREAT,则函数shmget()将创建一块新的共享内存段。如果shmflg参数同时设置了IPC_CREAT和IPC_EXCL,且内核中存在与key相关的共享内存段,shmget函数将调用失败。errno将设置为EEXIST。
size:size用于指定创建的共享内存段的大小。
shmflg:表示的操作类型,也可用于设置共享内存的访问权限,两者通过“|”链接表示。取值如下:
IPC_CREAT:如果共享内存不存在,则创建一个共享内存,否则打开操作。
IPC_EXCL:只有在共享内存不存在的时候,新的共享内存才建立,否则就产生错误。
SHM_HUGETLB:给共享内存分配HUGETLB内存页。
mode_flags(最后9位):指定创建的共享内存的访问权限。
错误信息:
EACCES:用户无访问共享内存段的权限。
EEXIST:参数shmflg设置了IPC_CREAT |IPC_EXCL,而key相关的共享内存段存在。
EINVAL:参数size无效或与key相关的共享内存段存在,但参数指定的size大小超过共享内存段大小。
ENFILE:打开的文件达到系统限制。
ENOENT:与key相关的共享内存段不存在,而shmflg参数未使用IPC_CREAT。
ENOMEM:段空间不足。
ENOSPC:超出系统对共享内存标识符的限制。
EPERM:设置了SHM_HUGETLB标志,但调用进程没有相应的权限。
实例:
程序演示了shmget函数的使用方法。在调用shmget函数时使用了不同的参数。示范了在不同调用参数的情况下,shmget函数的不同返回结果和错误信息。
程序中shmget参数调用示例
参数key | 参数shmflg | shmget调用结果 | errno信息 |
IPC_PRIVATE | 无要求 | 成功 | 无 |
系统中不存在相同的key内存段 | IPC_CREAT|权限值 | 成功 | 无 |
系统中存在相同的key内存段 | IPC_CREAT| IPC_EXCL|权限值 | 失败 | EEXIST |
系统中存在相同的key内存段 | IPC_CREAT | 权限值 | 成功 | 无 |
具体代码如下:
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
int main(void)
{
key_t key;
int shmid;
int proj_id;
key = IPC_PRIVATE;
shmid = shmget(key, 1024, 0600);
if ( shmid == -1)
{
perror("cannot create a shared memory segment");
}
else
{
printf("1.key=IPC_PRIVATE, shared memory segment shmid=%d\n",shmid);
}
proj_id = 1;
key=ftok("./program", proj_id);
if (key == -1)
{
perror("cannot generate IPC key");
}
shmid = shmget(key, 1024, IPC_CREAT|0660);
if (shmid == -1)
{
perror("cannot create a shared memory segment");
}
else
{
printf("2,key=%d generated by ftok,shared memory shmid=%d\n", key,shmid);
}
shmid=shmget(key,1024,IPC_CREAT|IPC_EXCL|0660);
if ( shmid == -1 )
{
perror("cannot create a shared memory segment");
}
shmid=shmget(key, 1024, IPC_CREAT|0660);
if ( shmid == -1 )
{
perror("cannot create a shared memory segment");
}
else
{
printf("Access the existing shared memory segment\n");
}
return (0);
}
运行结果:
[root@localhost test]# ./shmget
1.key=IPC_PRIVATE, shared memory segment shmid=1245199
2,key=16913142 generated by ftok,shared memory shmid=1114125
cannot create a shared memory segment: File exists
Access the existing shared memory segment
[root@localhost test]#
三,共享内存的基本数据结构
系统为每个共享内存段维护着对应的结构体shmid_ds的实例,该结构体的具体定义如下:
struct shmid_ds {
structipc_perm shm_perm; /* operation perms */
int shm_segsz; /* size of segment (bytes) */
__kernel_time_t shm_atime; /* last attach time */
__kernel_time_t shm_dtime; /* last detach time */
__kernel_time_t shm_ctime; /* last change time */
__kernel_ipc_pid_t shm_cpid; /*pid of creator */
__kernel_ipc_pid_t shm_lpid; /*pid of last operator */
unsignedshort shm_nattch; /* no. of current attaches */
unsignedshort shm_unused; /* compatibility */
void *shm_unused2; /* ditto - used by DIPC */
void *shm_unused3; /* unused */
};
结构体ipc_perm用于保存共享内存段的访问权限及共享内存段的创建者等信息。结构体ipc_perm具体定义如下:
struct ipc_perm
{
__kernel_key_t key;
__kernel_uid_t uid;
__kernel_gid_t gid;
__kernel_uid_t cuid;
__kernel_gid_t cgid;
__kernel_mode_t mode;
unsignedshort seq;
};
shm_segsz:段大小。
shm_atime:最后一次调用shmat函数的时间。
shm_dtime:最后一次调用shmdt函数的时间。
shm_ctime:最后一次调用shmctl函数的时间。
shm_cpid:创建共享内存段的进程的进程号。
shm_lpid:最后一次调用shmat或shmclt函数的进程号。
shm_nattch:当前连接的进程数。
当创建了新的共享内存段,共享内存段将被初始化为0,与其相关的数据结构shmid_ds将按以下情况进行初始化。
shm_perm.cuid和shm_perm.uid将被设置为调用进程的有效用户ID。
shm_perm.cgid和shm_perm.gid将被设置为调用进程的有效组ID。
shm_perm.mode的后9位将被设置为shmflg的后9位值。
shm_segsz被设置为size的值。
shm_lpid、shm_nattch、shm_atime和shm_dtime被设置为0.
shm_ctime被设置为当前的系统时间。
四,shmctl函数
shmctl函数提供了对共享内存的多种控制操作,该函数的具体信息如下表所示:
头文件 | #include <sys/ipc.h> #include <sys/shm.h> | ||
函数原型 | int shmctl(int shmid, int cmd, struct shmid_ds *buf); | ||
返回值 | 成功 | 失败 | 是否设置errno |
返回结果依赖于cmd参数 | -1 | 是 |
说明:shmctl函数根据参数cmd给出的控制信息,对共享内存标识符为shmid的共享内存段进行控制。参数buf为指向shmid_ds结构体的指针。
cmd参数可取下面的值:
IPC_STAT:将内核中共享内存与指定共享内存标识符shmid相关的数据复制到指向shmid_ds结构体的buf指针中。
IPC_SET:使用buf指向的shmid_ds结构体中的参数,设置指定共享内存标识符shmid中的相关字段。
IPC_RMID:删除指定共享内存标识符shmid的共享内存段。进行必须确保共享内存段被成功地删除,否则共享内存所占用的空间将不能释放。
IPC_INFO(Linux特有参数):获得系统共享内存限制和相关的参数,将其保存在buf参数指向的内存空间中。buf为指向shminfo结构体的指针,该结构体在<sys/shm.h>中定义。该定义在定义了_GNU_SOURCE宏时是可见的。shminfo结构体的定义如下:
struct shminfo{
intshmmax; //最大共享内存段
intshmmin;//最小共享内存段,为1
intshmmni;//最大共享内存段数
intshmseg; //进程可以访问的最大共享内存段数,未使用
intshmall; //系统最大共享内存页
};
SHM_INFO(Linux特有的参数):获得共享内存段消耗的系统资源信息,将其保存在buf参数指向的内存空间中。buf为指向shm_info结构体的指针,与shminfo类似。
该结构体也是在<sys/shm.h>中定义,并在定义了_GNU_SOURCE宏时是可见。
shm_info结构体的定义如下:
struct shm_info {
intused_ids; //当前系统中存在的共享内存段
unsignedlong shm_tot; //总的共享内存页数
unsignedlong shm_rss; //驻留的共享内存页数
unsignedlong shm_swp; //交换的共享内存页数
unsignedlong swap_attempts; //在Linux 2.4以后的内核中就不再使用了
unsignedlong swap_successes; //在Linux 2.4以后的内核中就不再使用了
};
SHM_STAT(Linux特有的参数):与使用IPC_STAT获得的结果相同。只是shmid不是共享内存标识符,而是内核中维护的内部共享内存段数组的索引值。
SHM_LOCK(Linux特有的参数):阻止共享内存段的交换。
SHM_UNLOCK(Linux特有的参数):对共享内存解锁,充许其换出。
错误信息:
EACCES:无对共享内存的访问权限。
EFAULT:cmd中设置了IPC_SET或IPC_STAT参数,但buf指向非法地址空间。
EIDRM:共享内存标识符指向已删除的标识符。
EINVAL:shmid为非法共享内存标识符或非法的cmd参数。
ENOMEM:在Linux 2.6.9以后的内核版本中支持,cmd参数中设置了SHM_LOCK,所要创建的共享内存段大小超出了调用进程限制。
EOVERFLOW:cmd参数设置了IPC_STAT,获得的GID或UID值过大,无法保存在buf指向的数据结构中。
EPERM:cmd参数设置了IPC_SET或IPC_RMID,调用进程的有效用户ID与共享内存的创建者或所有者不一致,进程没有相应的权限。
实例:
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#define LINUX_ENV
#ifndef LINUX_ENV
#define _GNU_SOURCE
#endif
int main(void)
{
key_t key;
int proj_id;
int shmid;
struct shmid_ds buffer;
proj_id = 2;
key = ftok("./program", proj_id);
if ( key == -1 )
{
perror("Cannot generate the IPC key");
return (1);
}
shmid = shmget(key, 1024, IPC_CREAT|0660);
if ( shmid == -1 )
{
perror("Cannot create a shared memory segment");
return (1);
}
printf("shmid = %d\n", shmid);
shmctl(shmid, IPC_STAT, &buffer);
printf("======shared memory info======\n");
printf("effective user id : %d\n", buffer.shm_perm.uid);
printf("effective group id : %d\n", buffer.shm_perm.gid);
printf("message queue's creator user id : %d\n", buffer.shm_perm.cuid);
printf("message queue's creator group id : %d\n", buffer.shm_perm.cgid);
printf("access mode : %x\n", buffer.shm_perm.mode);
printf("PID of creator : %d\n", buffer.shm_cpid);
printf("No.of current attaches : %d\n", buffer.shm_nattch);
#ifdef LINUX_ENV
shmctl(shmid, SHM_INFO, &buffer);
struct shm_info *mem_info;
mem_info = (struct shm_info*)(&buffer);
printf("# of currently existing segments : %d\n", mem_info->used_ids);
printf("Total number of shared memory pages : %d\n", mem_info->shm_tot);
printf("# of resident shared memory pages : %d\n", mem_info->shm_rss);
printf("# of swapped shared memory pages: %d\n", mem_info->shm_swp);
#endif
return (0);
}
运行结果:
root@localhost test]# ./shmctl
shmid = 1277968
======shared memory info======
effective user id : 0
effective group id : 0
message queue's creator user id : 0
message queue's creator group id : 0
access mode : 1b0
PID of creator : 29900
No.of current attaches : 0
# of currently existing segments : 17
Total number of shared memory pages : 967
# of resident shared memory pages : 840
# of swapped shared memory pages: 0
[root@localhost test]#