linux 共享内存操作

shmget
int shmget(key_t key, size_t size, int flag);
key: 标识符的规则
size:共享存储段的字节数
flag:读写的权限
返回值:成功返回共享存储的id,失败返回-1

key_t key
-----------------------------------------------
   key标识共享内存的键值: 0/IPC_PRIVATE。 当key的取值为IPC_PRIVATE,则函数shmget()将创建一块新的共享内存;如果key的取值为0,而参数shmflg中设置了IPC_PRIVATE这个标志,则同样将创建一块新的共享内存。


   在IPC(InterProcess Communication)的通信模式下,不管是使用消息队列还是共享内存,甚至是信号量,每个IPC的对象(object)都有唯一的名字,称为“键”(key)。通过“键”,进程能够识别所用的对象。“键”与IPC对象的关系就如同文件名称之于文件,通过文件名,进程能够读写文件内的数据,甚至多个进程能够共用一个文件。而在IPC的通讯模式下,通过“键”的使用也使得一个IPC对象能为多个进程所共用。
   Linux系统中的所有表示System V中IPC对象的数据结构都包括一个ipc_perm结构,其中包含有IPC对象的键值,该键用于查找System V中IPC对象的引用标识符。如果不使用“键”,进程将无法存取IPC对象,因为IPC对象并不存在于进程本身使用的内存中。
   通常,都希望自己的程序能和其他的程序预先约定一个唯一的键值,但实际上并不是总可能的成行的,因为自己的程序无法为一块共享内存选择一个键值。因此,在此把key设为IPC_PRIVATE,这样,操作系统将忽略键,建立一个新的共享内存,指定一个键值,然后返回这块共享内存IPC标识符ID。而将这个新的共享内存的标识符ID告诉其他进程可以在建立共享内存后通过派生子进程,或写入文件或管道来实现。


int size(单位字节Byte)
-----------------------------------------------
   size是要建立共享内存的长度。所有的内存分配操作都是以页为单位的。所以如果一段进程只申请一块只有一个字节的内存,内存也会分配整整一页(在i386机器中一页的缺省大小PACE_SIZE=4096字节)这样,新创建的共享内存的大小实际上是从size这个参数调整而来的页面大小。即如果size为1至4096,则实际申请到的共享内存大小为4K(一页);4097到8192,则实际申请到的共享内存大小为8K(两页),依此类推。


int shmflg
-----------------------------------------------
   shmflg主要和一些标志有关。其中有效的包括IPC_CREAT和IPC_EXCL,它们的功能与open()的O_CREAT和O_EXCL相当。
IPC_CREAT   如果共享内存不存在,则创建一个共享内存,否则打开操作
IPC_EXCL    只有在共享内存不存在的时候,新的共享内存才建立,否则就产生错误

   如果单独使用IPC_CREAT,shmget()函数要么返回一个已经存在的共享内存的操作符,要么返回一个新建的共享内存的标识符。如果将IPC_CREAT和IPC_EXCL标志一起使用,shmget()将返回一个新建的共享内存的标识符;如果该共享内存已存在,或者返回-1。IPC_EXEL标志本身并没有太大的意义,但是和IPC_CREAT标志一起使用可以用来保证所得的对象是新建的,而不是打开已有的对象。对于用户的读取和写入许可指定SHM_R和SHM_W,(SHM_R>3)和(SHM_W>3)是一组读取和写入许可,而(SHM_R>6)和(SHM_W>6)是全局读取和写入许可。

需要注意的是,使用参数要加上 | 0666 作为校验,在有些Linux系统中,如果不加此校验,则不能顺利获取共享空间的值(如Ubuntu)。此外,有两个常用参数,一般要同时出现,他们是:S_IRUSH | S_IWUSR 。由于这两个参数非常常用,程序员一般做这样的操作
#define PERM S_IRUSR | S_IWUSR | IPC_CREAT
  这样一来,第三个参数就可以直接用PERM来表示了!

返回值
-----------------------------------------------
成功返回共享内存的标识符;不成功返回-1,errno储存错误原因。
   EINVAL        参数size小于SHMMIN或大于SHMMAX。
   EEXIST        预建立key所致的共享内存,但已经存在。
   EIDRM         参数key所致的共享内存已经删除。
   ENOSPC        超过了系统允许建立的共享内存的最大值(SHMALL )。
   ENOENT        参数key所指的共享内存不存在,参数shmflg也未设IPC_CREAT位。
   EACCES        没有权限。
   ENOMEM        核心内存不足。


struct shmid_ds
-----------------------------------------------
   shmid_ds数据结构表示每个新建的共享内存。当shmget()创建了一块新的共享内存后,返回一个可以用于引用该共享内存的shmid_ds数据结构的标识符。

include/linux/shm.h

   struct shmid_ds {
       struct ipc_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 */
       unsigned short     shm_nattch;    /* no. of current attaches */
       unsigned short     shm_unused;    /* compatibility */
       void               *shm_unused2; /* ditto - used by DIPC */
       void               *shm_unused3; /* unused */
   };


struct ipc_perm
-----------------------------------------------
   对于每个IPC对象,系统共用一个struct ipc_perm的数据结构来存放权限信息,以确定一个ipc操作是否可以访问该IPC对象。

   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;
       unsigned short   seq;
};
//----------------------------------------

shmat
void *shmat(int shmid, const void *addr, int flag);
shmid:共享存储的id
addr:一般为0,表示连接到由内核选择的第一个可用地址上,否则,如果flag没有指定SHM_RND,则连接到addr所指定的地址上,如果flag为SHM_RND,则地址取整
flag:如前所述,一般为0
返回值:如果成功,返回共享存储段地址,出错返回-1
共享存储器的执行方式是将一个储存器区段标记为共用,这时各进程可以把这个区段映射到该进程本身的虚拟地址里。建立共享存储器可通过shmget系统调用,shmget执行后,核心程序就保留一块指定大小的空间,同时关于此共享存储器的一切数据,如区段的长度,区段的存取权,区段建立者的进程识别码等存入一个叫shmid_ds的结构。现在共享存储器虽然已经建立了,可是仍无法连上它,这时就须通过shmat系统调用得到一个指向共享存储器基址的指针,通过此指针,就可以如同于操作一般存储器似的取用共享存储器。shmdt进行相反的工作,用来脱离已连上的共享存储器。


shmdt
int shmdt(void *addr);
addr:共享存储段的地址,以前调用shmat时的返回值
shmdt将使相关shmid_ds结构中的shm_nattch计数器值减1

当一个进程不再需要共享内存段时,它将调用shmdt()系统调用取消这个段,但是,这并不是从内核真正地删除这个段,而是把相关shmid_ds结构的 shm_nattch域的值减1,当这个值为0时,内核才从物理上删除这个共享段


shmctl
int shmctl(int shmid,int cmd,struct shmid_ds *buf)
shmid:共享存储段的id
cmd:一些命令

IPC_STAT 得到共享内存的状态
IPC_SET 改变共享内存的状态
IPC_RMID 删除共享内存

IPC_RMID 命令实际上不从内核删除一个段,而是仅仅把这个段标记为删除,实际的删除发生在最后一个进程离开这个共享段时。

请注意,共享内存不会随着程序结束而自动消除,要么调用shmctl删除,要么自己用手敲命令去删除,否则永远留在系统中。


=======================================================================

使用共享内存的目的:
共享内存共享内存是进程间通信中最简单的方式之一。
共享内存允许两个或更多进程访问同一块内存,就如同 malloc() 函数向不同进程返回了指向同一个物理内存区域的指针。
当一个进程改变了这块地址中的内容的时候,其它进程都会察觉到这个更改。
使用共享内存的流程:
1.进程必须首先分配它。
2.随后需要访问这个共享内存块的每一个进程都必须将这个共享内存绑定到自己的地址空间中。
3.当完成通信之后,所有进程都将脱离共享内存,并且由一个进程释放该共享内存块。
分配:
int segment_id = shmget (shm_key, int size , shmflag );
1.进程通过调用shmget(Shared Memory GET,获取共享内存)来分配一个共享内存块。 该函数的第一个参数是一个用来标识共享内存块的键值。
彼此无关的进程可以通过指定同一个键以获取对同一个共享内存块的访问。
不幸的是,其它程序也可能挑选了同样的特定值作为自己分配共享内存的键值,从而产生冲突。
用特殊常量IPC_PRIVATE作为键值可以保证系统建立一个全新的共享内存块。
2.该函数的第二个参数指定了所申请的内存块的大小。
因为这些内存块是以页面为单位进行分配的,实际分配的内存块大小将被扩大到页面大小的整数倍。
3.第三个参数是一组标志,通过特定常量的按位或操作来shmget。这些特定常量包括:
IPC_CREAT:
 这个标志表示应创建一个新的共享内存块。通过指定这个标志,我们可以创建一个具有指定键值的新共享内存块。
IPC_EXCL:
 这个标志只能与 IPC_CREAT 同时使用。当指定这个标志的时候,如果已有一个具有这个键值的共享内存块存在,则shmget会调用失败。
 也就是说,这个标志将使线程获得一个“独有”的共享内存块。如果没有指定这个标志而系统中存在一个具有相通键值的共享内存块,
 shmget会返回这个已经建立的共享内存块,而不是重新创建一个。
模式标志:
 这个值由9个位组成,分别表示属主、属组和其它用户对该内存块的访问权限。其中表示执行权限的位将被忽略。
 指明访问权限的一个简单办法是利用<sys/stat.h>中指定,并且在手册页第二节stat条目中说明了的常量指定。
 例如,
  S_IRUSR和S_IWUSR分别指定了该内存块属主的读写权限,
  S_IROTH和S_IWOTH则指定了其它用户的读写权限。
绑定和脱离:
pst= shmat(iShm_id, NULL, 0)
一个进程获取对一块共享内存的访问,这个进程必须先调用 shmat(SHared Memory Attach,绑定到共享内存)。
将 shmget 返回的共享内存标识符 SHMID 传递给这个函数作为第一个参数。
第二个参数是一个指针,指向您希望用于映射该共享内存块的进程内存地址;如果您指定NULL则Linux会自动选择一个合适的地址用于映射。
第三个参数是一个标志位,包含了以下选项:   
 SHM_RND
  表示第二个参数指定的地址应被向下靠拢到内存页面大小的整数倍。
  如果您不指定这个标志,您将不得不在调用shmat的时候手工将共享内存块的大小按页面大小对齐。
 SHM_RDONLY
  表示这个内存块将仅允许读取操作而禁止写入。 如果这个函数调用成功则会返回绑定的共享内存块对应的地址。
  通过 fork 函数创建的子进程同时继承这些共享内存块;
  如果需要,它们可以主动脱离这些共享内存块。
  当一个进程不再使用一个共享内存块的时候应通过调用 shmdt(Shared Memory Detach,脱离共享内存块)
  函数与该共享内存块脱离。将由 shmat 函数返回的地址传递给这个函数。
  如果当释放这个内存块的进程是最后一个使用该内存块的进程,则这个内存块将被删除。
  对 exit 或任何exec族函数的调用都会自动使进程脱离共享内存块。
控制和释放共享内存块:
shmctl(iShm_id,IPC_RMID,0)<0
调用 shmctl("Shared Memory Control",控制共享内存)函数会返回一个共享内存块的相关信息。同时 shmctl 允许程序修改这些信息。
该函数的第一个参数是一个共享内存块标识。要获取一个共享内存块的相关信息,则为该函数传递 IPC_STAT 作为第二个参数,同时传递一个指向一个 struct shmid_ds 对象的指针作为第三个参数。
要删除一个共享内存块,则应将 IPC_RMID 作为第二个参数,而将 NULL 作为第三个参数。当最后一个绑定该共享内存块的进程与其脱离时,该共享内存块将被删除。
应当在结束使用每个共享内存块的时候都使用 shmctl 进行释放,以防止超过系统所允许的共享内存块的总数限制。调用 exit 和 exec 会使进程脱离共享内存块,
但不会删除这个内存块。 要查看其它有关共享内存块的操作的描述,请参考shmctl函数的手册页。

共享内存的总体大小是有限制的,这个大小通过SHMMAX参数来定义(以字节为单位),
您可以通过执行以下命令来确定 SHMMAX 的值:
cat /proc/sys/kernel/shmmax

修改共享内存:
设置 SHMMAX
# >echo "2147483648" > /proc/sys/kernel/shmmax
您还可以使用 sysctl 命令来更改 SHMMAX 的值:
# sysctl -w kernel.shmmax=2147483648
最后,通过将该内核参数插入到 /etc/sysctl.conf 启动文件中,您可以使这种更改永久有效:
# echo "kernel.shmmax=2147483648" >> /etc/sysctl.conf


你可能感兴趣的:(c,linux,共享内存)