共享内存区域是被多个进程共享的一部分物理内存。如果多个进程都把该内存区域映射到自己的虚拟地址空间,则这些进程就都可以直接访问该共享内存区域,从而可以通过该区域进行通信。共享内存是进程间共享数据的一种最快的方法,一个进程向共享内存区域写入了数据,共享这个内存区域的所有进程就可以立刻看到其中的内容。这块共享虚拟内存的页面,出现在每一个共享该页面的进程的页表中。但是它不需要在所有进程的虚拟内存中都有相同的虚拟地址。

共享内存的实现,分为两个步骤:
a. 创建共享内存,使用 shmget 函数。
b. 映射共享内存,将这段创建的共享内存映射到具体的进程空间去,使用 shmat 函数。

对于每个共享内存区,内核维护如下的信息结果,它定义在<sys/shm.h>头文件中:

           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; /* current #attached */
shmat_t shm_cnattch; /* in-core #attached */
};

下面是ipc_per 结构,它含有本共享内存区的访问权限。

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 */
};

1.shmget函数(创建共享内存)

shmget函数创建一个新的共享内存区,或者访问一个已存在的共享内存区。

#include <sys/shm.h>
int shmget(key_t key,size_t size,int oflag);

返回值是一个称为共享内存区标识符的整数,其他三个shmXXX函数就用它来指代这个内存区。

key即可以是ftok的返回值,也可以是IPC_PRIVATE。

size以字节为单位指定内存区的大小。当时机操作为创建一个新的共享内存区时,必须指定一个不为0的size值。如果实际操作为访问一个已存在的共享内存区,那么size应为0.

oflag是读写权限的组合。它还可以与IPC_CREAT或IPC_CREAT | IPC_EXCL按位或。

当实际操作为创建一个新的共享内存区时,该内存区被初始化为size字节的0.

注意,shmget创建或打开一个共享内存区,但并没有给调用进程提供访问该内存区的手段。这是shmat函数的目的。


2.shmat函数(共享内存映射)

由shmget创建或打开一个共享内存区后,通过调用shmat把它附接到调用进程的地址空间。

#include <sys/shm.h>
void* shmat(int shmid,const void *shmaddr,int flag);

其中shmid是由shmget返回的标识符。shmat的返回值是所指定的共享内存区在调用进程内的起始地址。确定这个地址的规则如下:

(1)如果shmaddr是一个空指针,那么系统替调用者选择地址。这是推荐的方法。

(2)如果shmaddr是一个非空指针,那么返回地址取决于调用者是否给flag参数指定了SHM_RND值:

a.如果没有指定SHM_RND,那么相应的共享内存区附接到由shmaddr参数指定的地址;

b.如果指定了SHM_RND,那么相应的共享内存附接到由shmaddr参数指定的地址向下舍入一个SHMLBA常值。LBA代表“低端边界地址”。
   默认情况下,只要调用进程具有某个共享内存区得读写权限,它附接该内存区后就能够同时读写该内存区。flag参数中也可以指定SHM_RDONLY值,它限定只读访问。flag参数默认是0,表示共享内存可读写。


3.shmdt函数(共享内存解除映射)

当一个进程完成某个共享内存区的使用时,它可以调用shmdt断接这个内存区。

#include <sys/shm.h>
int shmdt(const void *shmaddr);

当一个进程终止时,它当前附接着的所有共享内存区都自动断接掉。

注意本函数调用并不删除所指定的共享内存区。这个删除工作通过以IPC_RMID命令调用shmctl完成。

4.shmctl函数

shmctl提供了对一个共享内存区的多种操作。

#include <sys/shm.h>
int shmctl(int shmid,int cmd,struct shmid_ds *buff);

该函数提供了三个命令:

IPC_RMID   从系统中删除由shmid标识的共享内存区并拆除它。

IPC_SET     给所指定的共享内存区设置其shmid_ds结构的以下三个成员:shm_perm.uid,shm_perm.gid和shm_perm.mode,他们的值来自buff参数指向的结构中的相应成员。shm_ctime的值也用当前时间替换。

IPC_STAT    (通过buff参数)向调用者返回所指定共享内存区当前的shmid_ds结构。

5.shmget程序(shmget.c)
  下面是给出的shmget程序使用指定的路径名和长度创建一个共享内存区。

#include "unpipc.h"
int main(int argc,char ** argv)
{
int c,id,oflag;
char *ptr;
size_t length;
oflag = SVSHM_MODE | IPC_CREAT;
while( ( c= getopt(argc,argv,"e") ) != -1 )
{
switch(c){
case 'e':
oflag |= IPC_EXCL;
break;
}
}
if(optind != argc -2)
err_quit("usage: shmget [-e] <pathname> <length>"); length = atoi(argv[optind + 1]);
id = shmget(ftok(argv[optind],0),length,oflag);
ptr = shmat(id,NULL,0);
exit(0);
}

20    shmget创建由用户指定其名字和大小的共享内存区。作为命令行参数传递进来的路径名由ftok映射成一个system V IPC键。如果指定了- e选项,那么一旦该内存已存在就会出错。如果我们知道该内存区已存在,那么在命令行上的长度参数必须指定为0.

21    shmat把该内存区附接到当前进程的地址空间。本程序然后终止,不过既然system V共享内存区至少具有随内核的持续性,那么这不会删除该共享内存区。

6.shmrnid程序(shmrmid.c)
  如下给出的只是以一个IPC_RMID命令调用shmctl,以便从系统中删除一个共享内存区。

#include "unpipc.h"
int main(int argc,char ** argv)
{
int id;
if(argc != 2)
err_quit("usage: shmrmid<pathname>"); id = shmget(ftok(argv[1],0),0,SVSHM_MODE);
shmctl(id,IPC_RMID,NULL);
exit(0);
}


7.shmwrite程序(shmwrite.c)

下面给出了shmwrite.c程序,它往一个共享内存区中写入一个模式:0.1.2........254.255.0.1等等。

#include "unpipc.h"
int main(int argc,char **argv)
{
int i,id;
struct shmid_ds buff;
unsigned char *ptr; if(argc != 2)
err_quit("usage: shmwrite <pathname>"); id = shmget(ftok(argv[1],0),0,SVSHM_MODE);
ptr = shmat(id,NULL,0);
shmctl(id,IPC_STAT,&buff); /*set: ptr[0] = 0,ptr[1] = 1,etc.*/
for(i = 0;i < buff.shm_segsz;i++)
*ptr++ = i%256;
exit(0);
}

11-13   使用shmget打开所指定的共享内存区后由shmat把它附接到当前进程的地址空间。其大小通过以一个IPC_STAT命令调用shmctl取得。
16-17   往该共享内存区中写入给定的模式。

8.shmread程序(shmread.c)

下面给出的代码验证shmwrite写入的模式。

#include "unpipc.h"
int main(int argc,char** argv)
{
int i,id;
struct shmid_ds buff;
unsigned char c,*ptr; if(argc != 2)
err_quit("usage: shmread <pathname>"); id = shmget(ftok(argv[1],0),0,SVSHM_MODE);
ptr = shmat(id,NULL,0);
shmctl(id,IPC_STAT,&buff); /*check that ptr[0] = 0,ptr[1] = 1,etc.*/
for(i = 0;i < buff.shm_segsz;i++)
if((c = *ptr++) != (i%256))
err_ret("ptr[%d] = %d",i,c);
exit(0);
}

11-13   打开并附接所指定的共享内存区。其大小通过以一个IPC_STAT命令调用shmctl获取。
16-18   验证由shmwrite写入的模式。

示例代码:

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h> #define PERM S_IRUSR | S_IWUSR int main(int argc, char **argv)
{
int shmid;
char *p_addr, *c_addr; if(argc != 2){
fprintf(stderr,"Usage: %s\n", argv[0]);
exit(0);
} //创建1k的共享内存,可读可写
if((shmid = shmget(IPC_PRIVATE, 1024, PERM)) == -1){
fprintf(stderr, "Create share memory error: %s\n",strerror(errno));
} //创建子进程
if(fork()){ //父进程,写操作
p_addr = shmat(shmid, NULL , 0); //映射到父进程中
//地址为NULL,说明让系统自动指定地址。
memset(p_addr, '\0', 1024);
strncpy(p_addr, argv[1], 1024); //拷贝命令行输入字符到共享内存
wait(NULL);
shmctl(shmid,IPC_RMID,NULL);
exit(0);
} else{
sleep(1); //子进程,读操作
c_addr = shmat(shmid, 0 ,0); //共享内存映射到子进程
printf("Client get %s\n", c_addr);
exit(0);
}
}

运行结果:

huangcheng@ubuntu:~$ ./a.out huangcheng
Client get huangcheng

UNIX环境高级编程——System V 共享内存区的更多相关文章

  1. UNIX环境高级编程——system V消息队列

    unix早期通信机制中的信号能够传送的信息量有限,管道则只能传送无格式字节流,这远远是不够的.     消息队列(也叫报文队列)客服了这些缺点:     消息队列就是一个消息的链表.     可以把消 ...

  2. UNIX环境高级编程——system V信号量

    1. 信号量(semaphore)主要用于保护临界资源.进程可以根据它判断是否能访问某些共享资源.信号量除了用于访问控制外,还可用于进程同步,也就是进程间通信.2. 信号量分类:a. 二值信号量: 信 ...

  3. System V 共享内存区

    1.概述 系统调用mmap通过映射一个普通文件实现共享内存.System V 则是通过映射特殊文件系统shm中的文件实现进程间的共享内存通信.也就是说,每个共享内存区域对应特殊文件系统shm中的一个文 ...

  4. System V共享内存区

    要点 shell查看命令:ipcs -m 主要函数 #include <sys/shm.h> //oflag=IPC_CREAT|IPC_EXCL|0644组合 //创建一个内存共享区 i ...

  5. UNIX环境高级编程——system函数

    system函数 功能:调用fork产生子进程,由子进程来调用:/bin/sh -c command来执行参数command所代表的命令,阻塞当前进程直到command命 令执行完毕. int sys ...

  6. UNIX环境高级编程——IPC总结

    IPC主要包括:管道,消息队列,信号量,共享内存, 套接字(SOCKET). 一.IPC对象的持久性 每种IPC机制都会借助一种数据结构,这种数据结构的实例称为该IPC机制的对象(相应的,用于同步互斥 ...

  7. Linux进程通信之System V共享内存

    前面已经介绍过了POSIX共享内存区,System V共享内存区在概念上类似POSIX共享内存区,POSIX共享内存区的使用是调用shm_open创建共享内存区后调用mmap进行内存区的映射,而Sys ...

  8. Unix环境高级编程:文件 IO 原子性 与 状态 共享

    参考 UnixUnix环境高级编程 第三章 文件IO 偏移共享 单进程单文件描述符 在只有一个进程时,打开一个文件,对该文件描述符进行写入操作后,后续的写入操作会在原来偏移的基础上进行,这样就可以实现 ...

  9. (一) 一起学 Unix 环境高级编程 (APUE) 之 标准IO

    . . . . . 目录 (一) 一起学 Unix 环境高级编程 (APUE) 之 标准IO (二) 一起学 Unix 环境高级编程 (APUE) 之 文件 IO (三) 一起学 Unix 环境高级编 ...

随机推荐

  1. Mysql锁机制--读锁

    Mysql 系列文章主页 =============== 1 准备数据 1.1 建表 1.1.1 建立 Employee表 DROP TABLE IF EXISTS employee; CREATE ...

  2. 利用Runtime实现简单的字典转模型

    前言 我们都知道,开发中会有这么一个过程,就是将服务器返回的数据转换成我们自己定义的模型对象.当然服务器返回的数据结构有xml类型的,也有json类型的.本文只讨论json格式的. 大家在项目中一般是 ...

  3. python学习之路前端-CSS

    CSS概述 css是英文Cascading Style Sheets的缩写,称为层叠样式表,用于对页面进行美化. 存在方式有三种:元素内联.页面嵌入和外部引入,比较三种方式的优缺点. 语法:style ...

  4. python序列化pickle/cPickle

    一.pickle/Cpickle简介 Python序列化的概念很简单.内存里面有一个数据结构,你希望将它保存下来,重用,或者发送给其他人.你会怎么做?这取决于你想要怎么保存,怎么重用,发送给谁.很多游 ...

  5. 模仿天猫实战【SSM版】——后台开发

    上一篇文章链接:模仿天猫实战[SSM版]--项目起步 后台需求分析 在开始码代码之前,还是需要先清楚自己要做什么事情,后台具体需要实现哪些功能: 注意: 订单.用户.订单.推荐链接均不提供增删的功能. ...

  6. How to Change Default Web ADI Upload Parameters for FlexField Import / Validation

    How to Change Default Web ADI Upload Parameters for FlexField Import / Validation (文档 ID 553345.1) 转 ...

  7. Ruby 2.x 命名参数特性简介

    我以前曾有一个梦想,就是我的爹是李嘉诚-,那个-,不是啦,我的梦想是ruby像ObjC,或是现在的swift那样给方法提供命名参数. 之前的ruby只能用hash来模拟这个行为,不过你没法很容易的定义 ...

  8. Ubuntu批量修改文件名后缀

    比如把当前文件夹下所有scss文件后缀改为less rename 's/\.scss/\.less/' ./*

  9. python获取指定时间差的时间

    在分析数据的时间经常需要截取一定范围时间的数据,比如三天之内,两小时前等等时间要求的数据,因此将该部分经常需要用到的功能模块化,方便以后以后用到的时候复用.在此,也分享给大家. <span st ...

  10. Android Studio 中设置代码块自动补齐

    AS中很多提示键,并不如Eclipse中做的好,需要我们自己去自定义.这里以switch...case为例,讲解一下如何设置代码自动补全. 1.进入settings -->  Editor -- ...