共享内存机制
System V 共享内存机制
原理及实现:
system V IPC 机制下的共享内存本质是一段特殊的内存区域,进程间需要共享的数据被放在该共享内存区域中, 所有需要访问该共享区域的进程都要把该共享区域映射到本进程的地址空间中去。
这样一个使用共享内存的进程可以将信息写入该空间, 而另一个使用共享内存的进程又可以通过简单的内存读操作获取刚才写入的信息, 使得两个不同进程之间进行了一次信息交换, 从而实现进程间的通信。
共享内存允许一个或多个进程通过同时出现在它们的虚拟地址空间的内存进行通信, 而这块虚拟内存的页面被每个共享进程的页表条目所引用, 同时并不需要在所有进程的虚拟内存都有相同的地址。进程对象对于共享内存的访问通过 key(键)来控制,同时通过 key 进行访问权限的检查。
对于不同的进程A和B,虚拟内存通过OS的转化,会对应不同的物理内存区域,共享内存的实质就是使得不同进程A和B,无论是相同的虚拟内存(父子进程)还是不同的虚拟内存,都可以通过OS映射到同一块物理内存上。
请思考如下问题:
不同进程相同的虚拟地址,是否可以映射到不同的物理地址?
可以,父子进程的写时复制就是这种情况,父子进程的虚拟地址是相同的。
不同进程相同虚拟地址,是否可以映射到相同的物理地址?
共享内存,先对共享内存进行连接,再fork()就可以实现。
不同进程的不同虚拟地址,是否可以映射到相同的物理地址?
可以通过mmap实现共享内存。
函数定义如下:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
key_t ftok(const char *pathname, int proj_id);
int shmget(key_t key, int size, int shmflg);
void *shmat(int shmid, const void *shmaddr, int shmflg);
int shmdt(const void *shmaddr);
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
ftok()函数
key_t ftok(const char *pathname, int proj_id);
函数 ftok 用于创建一个关键字,可以用该关键字关联一个共享内存段。
参数 pathname 为一个全路径文件名,并且该文件必须可访问。
参数 proj_id 通常传入一非 0 字符
通过 pathname 和 proj_id 组合可以创建唯一的 key
如果调用成功,返回一关键字,否则返回-1
ftok.c
#include <func.h>
int main(int argc, char* argv[])
{
ARGS_CHECK(argc,2);
key_t key;
key=ftok(argv[1],1);
printf("key=%d\n",key);
return 0;
}
shmget()函数
int shmget(key_t key, int size, int shmflg);
函数 shmget 用于创建或打开一共享内存段,该内存段由函数的第一个参数唯一创建。 函数成功,则返回一个唯一的共享内存标识号 (相当于进程号, 唯一的标识着共享内存),失败返回-1。
参数 key 是一个与共享内存段相关联关键字, 如果事先已经存在一个与指定关键字关联的共享内存段,则直接返回该内存段的标识,表示打开,如果不存在,则创建一个新的共享内存段。
key 的值既可以用 ftok 函数产生,也可以是 IPC_PRIVATE,用于创建一个只属于创建进程的共享内存,主要用于父子通信。
参数 size 指定共享内存段的大小,以字节为单位。
参数 shmflg 是一掩码合成值,可以是访问权限值与(IPC_CREAT 或 IPC_EXCL)的合成。**IPC_CREAT 表示如果不存在该内存段,则创建它。IPC_EXCL 表示如果该内存段存在,则函数返回失败结果-1。**如果调用成功,返回内存段标识,否则返回-1 。
shmget.c
#include <func.h>
int main(int argc, char* argv[])
{
int shmid;
shmid=shmget(1000,1<<20,IPC_CREAT|0600); //IPC_CREAT创建,加上0600
ERROR_CHECK(shmid,-1,"shmget");
return 0;
}
shmat()函数
void *shmat(int shmid, const void *shmaddr, int shmflg);
函数 shmat 将共享内存段映射到进程空间的某一地址。
参数 shmid 是共享内存段的标识 通常应该是 shmget 的成功返回值 。
参数 shmaddr 指定的是共享内存连接到当前进程中的地址位置。通常是 NULL,表示让系统来选择共享内存出现的地址。
参数 shmflg 是一组位标识,通常为 0 即可。
调用成功时返回一个指向共享内存第一个字节的指针,如果调用失败返回-1.。
shmat.c
#include <func.h>
int main(int argc, char* argv[])
{
int shmid;
shmid=shmget(1000,1<<20,IPC_CREAT|0600); //IPC_CREAT创建,加上0600
ERROR_CHECK(shmid,-1,"shmget");
char*p;
p=(char*)shmat(shmid,NULL,0);
ERROR_CHECK(p,(char*)-1,"shmat");
strcpy(p,"hello");
while(1);
return 0;
}
shmdt()函数
int shmdt(const void *shmaddr);
函数 shmdt 用于将共享内存段与进程空间分离
参数 shmaddr 通常为 shmat 的成功返回值。
函数成功返回 0,失败时返回-1。注意,将共享内存分离并没删除它,只是使得该共享内存对当前进程不再可用。
shmdt.c
#include <func.h>
int main(int argc, char* argv[])
{
int shmid;
shmid = shmget(1000,1<<20,IPC_CREAT|0600);
ERROR_CHECK(shmid,-1,"shmget");
char *p;
p=(char*)shmat(shmid,NULL,0);
ERROR_CHECK(p,(char*)-1,"shmat");
strcpy(p,"hello");
int ret;
sleep(3);
//puts(p);
ret = shmdt(p);
ERROR_CHECK(ret,-1,"shmdt");
puts(p);
while(1);
return 0;
}
shmctl()函数
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
函数 shmctl 是共享内存的控制函数,可以用来删除共享内存段。
参数 shmid 是共享内存段标识 通常应该是 shmget 的成功返回值
参数 cmd 是对共享内存段的操作方式, 可选为 IPC_STAT,IPC_SET,IPC_RMID。通常为 IPC_RMID,表示删除共享内存段。
IPC_STAT :Copy information from the kernel data structure associated with shmid into the shmid_ds structure pointed to by buf. The caller must have read permission on the shared memory segment.
IPC_SET :Write the values of some members of the shmid_ds structure pointed to by buf to the kernel data structure associated with this shared memory segment, updating also its shm_ctime member. The following fields can be changed: shm_perm.uid, shm_perm.gid, and (the least significant 9 bits of) shm_perm.mode. The effective UID of the calling process must match the owner (shm_perm.uid) or creator (shm_perm.cuid) of the shared memory segment, or the caller must be privileged.
IPC_RMID : Mark the segment to be destroyed. The segment will actually be destroyed only after the last process detaches it (i.e., when the shm_nattch member of the associ‐ated structure shmid_ds is zero).
参数 buf 是表示共享内存段的信息结构体数据,通常为 NULL。
有进程连接,执行返回 0,标记删除成功,但只有最后一个进程解除连接后,共享内存才真正被删除。
shmctl_ipc_state.c
#include <func.h>
int main(int argc, char* argv[])
{
int shmid;
shmid = shmget(1000,1<<20,IPC_CREAT|0600);
ERROR_CHECK(shmid,-1,"shmget");
int ret;
struct shmid_ds buf;
ret=shmctl(shmid,IPC_STAT,&buf);
ERROR_CHECK(ret,-1,"shmctl");
printf("%d %o %ld %ld\n",buf.shm_perm.uid,buf.shm_perm.mode,buf.shm_segsz,buf.shm_nattch);
return 0;
}
shmctl_ipc_set.c
#include <func.h>
int main(int argc, char* argv[])
{
int shmid;
shmid = shmget(1000,1<<20,IPC_CREAT|0600);
ERROR_CHECK(shmid,-1,"shmget");
int ret;
struct shmid_ds buf;
ret=shmctl(shmid,IPC_STAT,&buf);
ERROR_CHECK(ret,-1,"shmctl");
printf("%d %o %ld %ld\n",buf.shm_perm.uid,buf.shm_perm.mode,buf.shm_segsz,buf.shm_nattch);
buf.shm_perm.mode = 0666;
shmctl(shmid,IPC_SET,&buf);
return 0;
}
shmctl_ipc_rmid.c
#include <func.h>
int main(int argc, char* argv[])
{
int shmid;
shmid = shmget(1000,1<<20,IPC_CREAT|0600);
ERROR_CHECK(shmid,-1,"shmget");
int ret;
ret=shmctl(shmid,IPC_RMID,NULL);
ERROR_CHECK(ret,-1,"shmctl");
return 0;
}
shmid_ds结构体
struct shmid_ds {
struct ipc_perm shm_perm; /* Ownership and permissions */
size_t shm_segsz; /* Size of segment (bytes) */
time_t shm_atime; /* Last attach time */
time_t shm_dtime; /* Last detach time */
time_t shm_ctime; /* Last change time */
pid_t shm_cpid; /* PID of creator */
pid_t shm_lpid; /* PID of last shmat(2)/shmdt(2) */
shmatt_t shm_nattch; /* No. of current attaches */
...
};
ipc_perm结构体
struct ipc_perm {
key_t __key; /* Key supplied to shmget(2) */
uid_t uid; /* Effective UID of owner */
gid_t gid; /* Effective GID of owner */
uid_t cuid; /* Effective UID of creator */
gid_t cgid; /* Effective GID of creator */
unsigned short mode; /* Permissions + SHM_DEST and SHM_LOCKED flags */
unsigned short __seq; /* Sequence number */
};
shmat_fork_read_write.c
#include <func.h>
int main(int argc, char* argv[])
{
int shmid;
shmid=shmget(1000,1<<20,IPC_CREAT|0600); //IPC_CREAT创建,加上0600
ERROR_CHECK(shmid,-1,"shmget");
if(fork()>0) //父进程代码
{
char*p_addr = (char*)shmat(shmid,NULL,0); //获得该段共享内存的首地址
memset(p_addr,'\0',1<<20); //初始化为0
strcpy(p_addr,"I hen niu"); //写入内容
printf("parent %d write buffer:%s\n",getpid(),p_addr);
wait(NULL); //防止僵尸进程
shmctl(shmid,IPC_RMID,0); //删除共享内存
exit(0);
}
else
{
sleep(10);
char*c_addr = (char*)shmat(shmid,NULL,0);
printf("child pid=%d,shmid=%d,read buffer:%s\n",getpid(),shmid,c_addr);
exit(0);
}
return 0;
}
shmat_fork_add.c
#include <func.h>
#define N 10000000
int main(int argc, char* argv[])
{
int shmid;
shmid = shmget(1000,1<<20,IPC_CREAT|0600);
ERROR_CHECK(shmid,-1,"shmget");
int *p;
p = (int*)shmat(shmid,NULL,0);
ERROR_CHECK(p,(int*)-1,"shmat");
*p = 0;
if(!fork())
{
int i;
for(i=0;i<N;i++)
{
p[0]=p[0]+1;
}
}
else
{
int i;
for(i=0;i<N;i++)
{
p[0]=p[0]+1;
}
wait(NULL);
printf("result=%d\n",p[0]);
}
return 0;
}
本文深入探讨了SystemV共享内存机制,详细介绍了ftok()、shmget()、shmat()、shmdt()和shmctl()等关键函数的使用方法,以及如何通过这些函数实现进程间通信。
1883





