第48章System V共享内存

本章将介绍 System V共享内存。共享内存允许两个或多个进程共享物理内存的同一块区域(通常被称为段)。由于一个共享内存段会成为一个进程用户空间内存的一部分,因此这种IPC机制无需内核介入,所有需要做的就是让一个进程将数据复制进共享内存中,并且这部分数据会对其他所有共享一个段的进程可用。与管道或者消息队列要求发送进程将数据从用户空间的缓冲区复制进内核和接受进程将数据从内核内存复制进用户空间的缓冲区的做法相比,这种IPC技术更快。每个进程也存在通过系统调用来执行复制操作的开销。

  • 另一方面,共享内存这种IP机制不由内核控制意味着通常需要通过某些同步方法使得进程不会出现同时访问这块内存的情况(如两个进程同时进行更新操作,或者一个进程在从共享内存中获取数据的而同时另一个进程正在更新这些数据)System V信号量天生就是完成这种同步的一种方法,当然也可以使用其他方法,如POSIX信号量,文件锁
    在mmap术语中,一块内存区域会被映射到一个地址,而在SystemV术语中一个共享内存段是附加到一个地址上的。这些术语是等价大,这些术语是等价的,之所以属于上存在差异,是因为两组API起源不同

48.1概述

为使用一个共享内存端通常需要执行以下步骤

  • 调用shmget()创建一个新共享内存段或取得一个既有共享内存段的标识符(即有其他进程创建的共享内存段),这个调用将返回后续拥戴的共享内存标识符。
  • 使用shmat()来附上共享内存段,即 使该段成为调用进程的续集你内存的一部分
  • 此刻程序中可以像对待其他可用内存那样对待这个共享内存段,未引用这块共享内存,需要使用shmat()调用返回的adr值,他是一个指向进程的虚拟地址空间中该共享内存段的起点的指针,
  • 调用shmdt()来分离共享内存段,在这个调用之后,进程就无法再引用这块共享内存可,这一步是可选的,并且在进程终止时,会自动完成这一步。
  • 调用shmctl()用来删除共享内存段,只有当所有附加内存段的进程都与之分离后内存段才会被销毁,只有一个进程需要执行这一步(如果多个进程使用shmctl()的话会不会销毁?待验证)

48.2创建或打开一个共享内存段

shemget()系统调用会创建一个新共享内存段或获取一个既有段的标识符。新创建的内存段中的内容会被初始化为0。

#include <sys/types>
#include <sys/shm.h>

int shmget(key_t key,size_t size,int shmflg);
	Returns shared memory segment identifier on success,or -1 on error

但是用shmget()系统调用创建一个新共享内存段时,size则是一个整数,他表示需分配的段的字节数,内核是以系统分页的帧数倍来分配共享内存的,因此实际上size会被提升到最近的系统分页大小的整数倍。如果使用shmget()来获取一个既有段的标识符,那么size对段不会产生任何效果,但他必须要小于或等于段的大小。
shmflg参数执行的任务与其他 IPC get调用中执行的任务一样,即指定施加于新共享内存段上的权限或需检查的既有内存段的权限。Linux 也允许使用下列非标准标记。

SHM_HUGTLEB(linux 2.6起)
特权(CAP _IPC_LOCK)进程能够使用这个标记创建一个使用巨页(huge page)的共享内存段。巨页是很多现代硬件架构提供的一项特性来管理使用超大分页尺寸的内存,(如X86-32允许使用4MB的分页大小来替代4KB的分页大小。)在那些拥有大量内存的系统上并且应用程序需要使用大量内存块时,使用巨页可以降低内存管理单元的超前转换缓冲器(translation lok-asidebuffer,TLB)中的条目数量,这之所以会带来益处是因为TLB中的条目是一种稀缺资源。
SHM_NORESEVE(自lnux 2.6.15 起)
这个标记在shemget中起的作用与MAP_NORESERVER标记在mmap()所起的作用一样。
shemget()成功时返回新或既有共享内存段的标识符

48.3 使用共享内存。

shmat()系统调用将shmid标识的共享内存段附加到调用进程的虚拟地址空间中

#include <sys/types.h>
#include <sys/shm.h>
void *shmat(int shamid,const void *shmaddr,int shmflg);
	Returns address at which shared memory is attached on success, or (void *)-1 on error

shmadr参数和shmflg位掩码参数中SHM_RND位的设置控制着段是如何被附加上去的

  • 如果shmaddr是NULL,那么段会被附加到内核所选择的一个合适的地址处,这是附加一个段的优选方法。

  • 如果shmaddr不为NULL,并且没有设置SHM_RND,那么段会被附加到由shmadr指定的地址处,他必须是系统分页大小的一个倍数(否则会发生EINVAL错误)。

  • 如果shmaddr不为NULL并且设置了SHM_RND,那么段会被映射到的地址在shmaddr中提供的地址被舍入到最近的常量SHMLBA(shared memory low boundary address)的倍数,这个常量等于系统分页大小的某个倍数。将一个段附加到值为SHMLBA的倍数的地址处在一些结构上是非常重要的,因为这样才能够提升CPU快速缓冲存在不一致的视图的情况。
    -为shmaddr指定一个非NULL值(即上面列出的第二种和第三种情况)不是一种推荐的做法,其原因如下。

  • 他降低了一个应用程序的可移植性。在一个UNIX实现上有效的地址,在另一个实现上可能是无效的。

  • 试图将一个共享内存段附加到一个正在使用中的特定地址处的操作会失败。例如当一个应用程序(可能在一个函数库中)已经在改地址处附加了另一个段或创建一个内存映射是会发生这种情况。

  • shmat()函数的返回结果是安徽附加共享内存段的地址。开发人员可以像对待普通的C指针那样对待这个值,段与进程的虚拟内存的其他部分看起来毫无差异,通常会将shma(),的返回值赋值给一个指向摸个由程序员定义的结构的指针,以便在该结构上使用该结构。要附加一个共享内存段以供只读访问,那么就需要在shmflg中指定SHM_RDONLY标记。试图更新只读段中的内容就会导致段错误(SIGSEGV信号的发生)。如果没有指定SHM_RDONLY,那么既可以读取内存又可以修改内存。

在一个进程中可以多次附加同一个共享内存段,即使一个附加操作是只读的而另一个是读写也没有关系。每个附加点上内存中的内容都是一样的,因为虚拟内存页表中的不同条目引用的是同样的内存物理界面

最后一个可以在shmflg中指定的值是SHM_REMAP.在指定了这个标记之后shmaddr的值必须为非NULL,这个标记要求shmat()调用替换起点在shmadd处长度为共享内存段的长度的任何既有共享内存段或内存映射。一般来讲,如果试图将一个共享内存段附加到一个已经在用的地址范围时会导致EINVAL错误的发生。SHM_REAM是一个非标的linux扩展
图1 shmat()的shmflg位掩码值

描述
SHM_RDONLY附加只读段
SHM_REMAP替换位于shmaddr处的任意既有映射
SHM_RND将shmaddr四舍五入为SHMLBA字节的倍数

当一个进程不在需要一个共享内存段时就可以调用shmdt()来讲该分段分理出虚拟地址空间。shmaddr参数标示出了待分离的段,他应该是由之前的shmat()调用返回的一个值。

#include <sys/types.h>
#include <sys/shm.h>

int shmdt(const void * shmaddr);
	Return 0 on success ,or -1 on error

分离一个共享内存段是与删除它是不同的,删除是通过shmctl()IPC_RMID操作完成的。
通过fork(()创建的子进程会继承其其父进程附加的共享内存段。因此,共享内存为父进程和子进程之间提供了一种简单的IPC方法。
在一个exec()中,所有的附加的共享内存段都会被分离。在进程终止之后共享内存段也会自动被分离。

48.4 示例:通过共享内存传输数据

下面介绍一个使用System V 共享内存和信号量的示例程序。这个应用程序 有两个程序构成:读者和写者。写者从标准输入读取数据块并将数据复制(“写”)到一个共享内存段中。读者将共享内存段中的数据块复制(“读”)到标准输出中。实际上,程序在某种程度上将共享内存当成了管道来处理。
两个程序使用了二元信号量协议(system v 信号量)来确保:

  • 一次只有一个进程访问共享内存段
  • 进程交替的访问段(即写者写入一些数据,然后读者读取这些数据,然后写者再次写入数据,以此类推)
  • 下图描述了这两个信号量的使用,注意写者对两个信号量都进行了初始化,这样他就能成为两个程序中第一个能够访问共享内存段的程序了,即写者的信号量初始时是可用的,而读者的信号量初始时是正在被使用的。

使用信号量确保对共享内存的互斥、交替的访问程序清单48-2是写者程序,这个程序按照顺序完成下列任务。

  • 创建一个包含两个信号量的几个,写着和读者程序会使用这两个信号来确保他们呢交替地访问共享内存段(1),信号量被初始化为写者首先访问共享内存段。由于是写者来创建信号量集地,因此必须在启动读者之前启动写者。

  • 创建共享内存段并将其附加到写者地虚拟地址内存空间中系统所选择地一个地址处(2)

  • 进入一个循环将数据从标准输入传输到共享内存段(3).每个循环迭代需要按序完成下面地任务:
    –预留(减小)写者地信号量(4).
    –从标准输入中读取数据并将数据复制到共享内存段(5)
    –释放(增加)读者的信号量(6)

  • 当标准输入中没有可用的数据时循环终止(7).在最后一次循环中,写者通过传递一个长度为0的数据块(shmp->cnt为0)来通知读者没有更多的数据了。

  • 在退出循环时,写者再次预留其信号量,这样就能直到读者已经完成了对共享内存的最后一次访问了(8).写者随后删除了共享内存和信号量集(9).
    程序48.3是读者程序,他将共享内存段中的数据块传输到标准输出中。读者按序完成下面的任务。

  • 获取写程序创建的信号量集合共享内存段的ID(1)

  • 附加共享内存段供只读访问(2)

  • 进入一个循环从共享内存中传输数据(3)。在每个循环迭代中需要按序完成下面的任务。
    –预留就(减小)读取的信号量(4)
    – 检查shmp->cnt是否为0,如果为0就退出循环(5)
    –将共享内存段中的数据块写入标准输出中(6)
    – 释放(增加)写者的信号量

  • 在退出循环之后分离共享内存段(8)并释放写者的信号量(9)这样写者就能够删除IPC对象了。
    程序清单那 48-2 :将stdin中的数据块传输到一个System v共享内存中

// svshm_xfr_writer.c
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/sem.h>
#include <sys/shm.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>

#define SHM_KEY 0x1234    //key for shared memory segment
#define SEM_KEY 0X5678    //KEY for semaphore set
#define OBJ_PERMS  (S_IRUSR |S_IWUSR|S_IRGRP|S_IWGRP)
            /*Permissoins for our IPC objects*/
#define WRITE_SEM 0 /* Writer has access to shared memiory*/
#define READ_SEM 1  /* Reader has access to shared memory*/
#ifndef BUF_SIZE      /*ALLOW 'cc-D'to override definition*/
#define BUF_SIZE 1024 /*Size of transfer buffer*/
#endif

struct shmsg{ /*Defines structure of shared memory segment*/
    int cnt;        /*"Number of bytes used in 'buf'"*/
    char buf[BUF_SIZE]; /*Data being transferred*/
};
union semun {                   /* Used in calls to semctl() */
    int                 val;
    struct semid_ds *   buf;
    unsigned short *    array;
#if defined(__linux__)
    struct seminfo *    __buf;
#endif
};
typedef enum { FALSE, TRUE } Boolean;
Boolean bsUseSemUndo = FALSE;
Boolean bsRetryOnEintr = TRUE;

int                     /* Initialize semaphore to 1 (i.e., "available") */
initSemAvailable(int semId, int semNum)
{
    union semun arg;
    arg.val = 1;
    return semctl(semId, semNum, SETVAL, arg);
}
int                     /* Initialize semaphore to 0 (i.e., "in use") */
initSemInUse(int semId, int semNum)
{
    union semun arg;
    arg.val = 0;
    return semctl(semId, semNum, SETVAL, arg);
}

/* Reserve semaphore (blocking), return 0 on success, or -1 with 'errno'
   set to EINTR if operation was interrupted by a signal handler */

int                     /* Reserve semaphore - decrement it by 1 */
reserveSem(int semId, int semNum)
{
    struct sembuf sops;
    sops.sem_num = semNum;
    sops.sem_op = -1;
    sops.sem_flg = bsUseSemUndo ? SEM_UNDO : 0;

    while (semop(semId, &sops, 1) == -1)
        if (errno != EINTR || !bsRetryOnEintr)
            return -1;
    return 0;
}

int                     /* Release semaphore - increment it by 1 */
releaseSem(int semId, int semNum)
{
    struct sembuf sops;
    sops.sem_num = semNum;
    sops.sem_op = 1;
    sops.sem_flg = bsUseSemUndo ? SEM_UNDO : 0;

    return semop(semId, &sops, 1);
}
int main(int argc,char *argv[])
{
    int semid, shmid,bytes,xfrs;
    struct shmsg *shmp;
    union semun dummy;
    semid = semget(SEM_KEY , 2, IPC_CREAT | OBJ_PERMS);
    if(semid == -1)
    {
        perror("semget:");
        return -1;
    }
    if(initSemAvailable(semid,WRITE_SEM) == -1)
    {
        perror("initSemAvailable:");
        return -1;
    }
    if(initSemInUse(semid,READ_SEM) == -1)
    {
        perror("initSemInUse:");
        return -1;
    }
    shmid = shmget(SHM_KEY,sizeof(struct shmsg),IPC_CREAT | OBJ_PERMS);
    if(shmid == -1)
    {
        perror("shmget:");
        return -1;
    }
    shmp=  shmat(shmid,NULL,0);
    if(shmp == (void *)-1)
    {
        perror("shmat:");
        return -1;
    }
    /*Transfer blocks of data from stdin to shared memory*/
    for(xfrs = 0,bytes = 0;;xfrs++,bytes+=shmp->cnt){
        if(reserveSem(semid,WRITE_SEM) == -1)    //wait for our run
        {
            perror("reserveSem:");
            return -1;
        }
        shmp->cnt = read(STDIN_FILENO,shmp->buf,BUF_SIZE);
        if(shmp->cnt == -1)
        {
            perror("read:");
            return -1;
        }
        if(releaseSem(semid,READ_SEM) == -1)  /*Give reader a turn*/
        {
            perror("releaseSem");
            return -1;
        }
           /* Have we reached EOF? We test this after giving the reader
           a turn so that it can see the 0 value in shmp->cnt. */
        if (shmp->cnt == 0)
            break;
    }
    /*Wait until reader has let us have one more turn.
    we kuno reader has finished,and so we can delete the IPC objects.*/

    if(reserveSem(semid,WRITE_SEM) == -1)
    {
        perror("reserveSem:");
        return -1;
    }
    if(semctl(semid,0,IPC_RMID,dummy) == -1)
    {
        perror("semctl:");
        return -1;
    }
    if(shmdt(shmp) == -1)
    {
        perror("shmdt");
        return -1;
    }
    if(shmctl(shmid,IPC_RMID,0) == -1)
    {
        perror("shmctl:");
        return -1;
    }
        fprintf(stderr,"sent %d bytes (%d xfrs)\n",bytes,xfrs);
    exit(EXIT_SUCCESS);
}

程序清单48-3:将一个System V 共享内存段中的数据块传输到stdout中

// svshm_xfr_reader.c
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/sem.h>
#include <sys/shm.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#define SHM_KEY 0x1234    //key for shared memory segment
#define SEM_KEY 0X5678    //KEY for semaphore set

#define OBJ_PERMS (S_IRUSR |S_IWUSR|S_IRGRP|S_IWGRP)
            /*Permissoins for our IPC objects*/
#define WRITE_SEM 0 /* Writer has access to shared memory*/
#define READ_SEM 1  /* Reader has access to shared memory*/
#ifndef BUF_SIZE      /*ALLOW 'cc-D'to override definition*/
#define BUF_SIZE 1024 /*Size of transfer buffer*/
#endif
struct shmsg{ /*Defines structure of shared memory segment*/
    int cnt;        /*"Number of bytes used in 'buf'"*/
    char buf[BUF_SIZE]; /*Data being transferred*/
};
typedef enum { FALSE, TRUE } Boolean;
Boolean bsUseSemUndo = FALSE;
Boolean bsRetryOnEintr = TRUE;
int                     /* Reserve semaphore - decrement it by 1 */
reserveSem(int semId, int semNum)
{
    struct sembuf sops;
    sops.sem_num = semNum;
    sops.sem_op = -1;
    sops.sem_flg = bsUseSemUndo ? SEM_UNDO : 0;

    while (semop(semId, &sops, 1) == -1)
        if (errno != EINTR || !bsRetryOnEintr)
            return -1;

    return 0;
}
int                     /* Release semaphore - increment it by 1 */
releaseSem(int semId, int semNum)
{
    struct sembuf sops;
    sops.sem_num = semNum;
    sops.sem_op = 1;
    sops.sem_flg = bsUseSemUndo ? SEM_UNDO : 0;
    return semop(semId, &sops, 1);
}
int main(int argc,char *argv[])
{
    int semid,shmid,xfrs,bytes;
    struct shmsg *shmp;
    /*get IDs for semaphore set and shared memory created by writer*/
    semid = semget(SEM_KEY,0,0);
    if(semid == -1)
    {
        perror("semget:");
        return -1;
    }
    shmid  = shmget(SHM_KEY, 0, 0);
    if (shmid == -1)
    {
        perror("shmget");
        return -1;
    }
    shmp = shmat(shmid,NULL,SHM_RDONLY);
    if(shmp == (void *)-1)
    {
        perror("shmat:");
        return -1;
    }
    /*Transfer blocks of data from shared memory to stdout*/

    for(xfrs = 0,bytes = 0;;xfrs++){
        if(reserveSem(semid,READ_SEM) == -1)   //wait for our run
        {
            perror("reserveSem:");
            return -1;
        }
        if(shmp->cnt == 0)  //wait encountered EOF
            break;
        bytes+= shmp->cnt;
        if(write(STDOUT_FILENO,shmp->buf,shmp->cnt) !=shmp->cnt)
        {
            printf("partial/failed write\n");
        }
        if(releaseSem(semid,WRITE_SEM) == -1)  /*Give write a turn*/
        {
            perror("releaseSem:");
            return -1;
        }
    }
    if(shmdt(shmp) == -1)
    {
        perror("kshmdt:");
        return -1;
    }
    /*Give writer one more turn,so it can clean up*/
    if(releaseSem(semid,WRITE_SEM) == -1)
    {
        perror("releaseSem:");
        return -1;
    }
    fprintf(stderr,"Received %d bytes (%d xfrs)\n",bytes,xfrs);
    exit(EXIT_SUCCESS);
}

下面的shell 会话演示了如何使用程序清单48-2和程序清单48-3中的程序。这里再调用读者时将文件/etc/services 作为输入,然后调用了读者并将其输出定向到另一个文件中。
在这里插入图片描述

周六写

48.5 共享内存在虚拟内存中的位置

允许内核选择在何处附加共享内存段,那么内存布局就会如下图那样,段被附加在==向上生长的堆和向下生长的栈之间未被分配的空间中。==为给栈和堆的增长腾出空间,附加内存段的虚拟地址从0x40000000开始。内存映射和共享库也是放在这个位置(共享内存映射和内存段默认被放置的位置可能会有些不同。这依赖于内核版本和进程的RLIMIT_STACK资源的限制。)
共享内存,内存映射,共享库的位置

地址0x40000000被定义成了内核常量TASK_UNMAPPED_BASE。通过将这个常量定义成一个不同的值,并且重建内核可以修改这个地址的值,
如果在调用shmat()(或者mmap())时采用了不推荐的方法,即显示指定一个地址,那么一个共享内存段(或者内存映射)可以被放置低于YASK_UNMAPPED_BASE的地址处。

通过linux特有的/proc/PID/maps文件能够看到一个程序映射的共享内存段和共享库的位置。下面是/proc/PID/maps文件中每行所包含的列。其顺序从左至右。

  1. 一对由连字符隔开的数字,他们分别表示内存段被映射到的虚拟地址范围(以十六进制表示)和段尾之后第一个字节的地址
  2. 内存段包保护位和标记位。前三个字母表示段的保护位:读(r)、写(w)以及执行(x).使用链接字符(-)来替换其中任意字母表示禁用相应的保护位。最后一个字母表示内存段的映射标记,其值要么是私有§要么是共享(s)
  3. 段在对应的映射文件中的十六进制偏移量(以字节计数)。这个列以及随后的两列的含义在讲解mmap()时会变得更加清晰对于system V共享内存段来讲,偏移量总是0,
  4. 相应的映射文件所位于的设备的设备号
  5. 映射文件的i-node号或system V共享内存段的标识符。
  6. 与这个内存段相关联的文件名或者其他标识标签

48.6 在共享内存中存储指针

每个进程都可能会用到不同的共享库和内存映射,并且可能会附加不同的共享内存段集,因此没如果遵循推荐的做法,让内核来选择将共享内存段附加到何处,那么一个段在各个进程中可能会附加到不同地址上。正是因为这个原因,在共享内存段中存储指向段中其他地址的引用时应该使用相对偏移量,而不是绝对指针
例如,假设一个共享内存段的起始地址为baseaddr(即baseaddr 的值为shmat()的返回值)。再假设需要在P指向的位置处存储一个指针,该指针指向的位置与tagetz指向的位置相同。如下图所示。如果在段中构建一个链表或二叉树,那么这种操作就是一种非常典型的一种操作,在C中设置p的传统做法如下所示:
*p = target //place pointer in *p(wrong!)
在共享内存段中使用指针
上面这段代码存在的问题是,当共享内存段被附加到另一个进程中时target指向的位置可能会位于一个不同的虚拟地址处,这意味着在那个进程中那个策划中存储在
p中的值是无意义的,正确的错发是在*p中存储一个偏移量,如下图所示:

*p = (taget -baseaddr);  /*place offset in *p*/
在解引用上面指针时需要颠倒上面的步骤。
taget = baseaddr + *p;   //interpret offset

这里假设在各个进程中baseaddr指向共享内存段的起始位置(即各个进程中shmat()的返回值)给定这种假设,那么就能正确的对偏移量进行解释,不管共享内存段被附加在进程的虚拟地址空间中的何处。
或者如果是将一组固定大小的结构连接起来的话可以讲共享内存段(或部分共享内存段)强制转换成一个数组,然后使用下表数组作为指针来在一个结构中引用另一个结构这块不咋懂,有小伙伴研究过的话可以评论一下 谢谢

48.7共享内存控制操作

shmctl()系统调用在shmid标识的共享内存段上执行一组控制操作。

#include<sys/types.h>
#include <sys/shm.h>
int shmctl(int shmid,int cmd,struct shmid_ds *buf);
	Return 0 on success or -1 on error

cmd参数规定了待执行的控制操作。buf参数是IPC_STAT和IPC_SET操作会用到的,并且在其他操作时将这个参数的值指定为NULL;
###常规控制操作
下列操作,与其他System V IPC对象上的操作是一样的。
IPC_RMID
标记这个共享内存内存段以及其关联shmid_ds数据结构以便删除。如果当前没有进程附加该段,那么就会立即执行删除操作,否则就在所有进程都已经与该段分离(即当shmid_ds数据结构中shm_nattch字段的值为0时)之后再执行删除操作。在一些应用程序中可以通过在所有进程将共享内存附加到其虚拟地址空间之后立即使用shmctrl()(书中是shmat(),不过我认为应该是shmctrl)将共享内存标记为删除来确保在应用程序退出时干净的清除共享内存段。这种做法与在打开一个文件之后立即断开该文件的链接的做法是类似的。

加锁和解锁共享内存

一个共享内存可以被锁进RAM中,这样他就永远不糊被交换出去了,这种做法能够带来性能上的提升,因为一旦段中的所有分页都驻留在内存中,就能够保证一个应用程序在访问分页时永远不会因发生分页故障而被延迟。通过shmctrl()可以完成这两种操作。

  • SHM_LOCK操作讲一个共享内存段锁进内存。
  • SHM_UNLOCK操作为一个共享内存解锁以允许它被交换出去。
  • SUSv3并没有规定这些操作,并且所有UNIX实现也都没有提供这些操作。
    自从linux2.6.10开始,非特权进程能够在一个共享内存段上执行加锁和解锁操作。其前提是进程的有效用户ID与段的所有者和创建者的用户ID匹配并且(在执行SHM_LOCK操作的情况下)进程具备足够高的RLIMIT_MEMLOCK资源限制,细节参考50.2节。
    锁住一个内存无法确保在shmctrl()调用结束时段的所有分页都驻留在内存中,非驻留分页会在附加该共享内存段的进程引用这些分页时因分页故障而一个一个地被锁进内存。一旦分页因分页故障被锁进内存,那么分页会一直驻留在内存中直到被解锁为止,即使所有进程都与该段分离之后也不会改变。(换句话说,SHM_LOCK操作为共享内存段设置了一个属性,而不是为调用进程设置了一个属性)

48.4 共享内存关联数据结构

每个共享内存段都有一个关联的shmid_ds数据结构,其形式如下。

struct shmid_ds{
	struct ipc_perm_shm_perm;   //owership and permissions
	size_t shm_segsz;      //size of segment in bytes
	time_t shm_atime;       //time of last shmat();
	time_t shm_dtime;       //time of last shmdt()
	time_t shm_ctime;    //time of last change;
	pid_t shm_cpid;   		//PID of creator
	ptd_t shm_lpid;  		//PID of last shmat() /shmdt()
	shmatt_t shm_nattch    //number of currently attached process
}

SUSv3 要求实现提供上面给出的所有字段。其他一些UNIX实现在shmid_ds结构中包含了额外的非标准字段。
各种共享内存系统调用会隐式的更新shmid_ds结构中的字段,使用shmctrl() IPC_SET操作可以显示的跟新shm_perm 中的字段
shm_nattch
这个字段统计当前附加该段的进程数。在创建段时会将这个字段初始化为0,然后每次成功调用shmat()会递增这个字段的值,每次成功调用shmdt()会递减这个值

48.9共享内存的限制

大多数UNIX实现会对Sysyem V共享内存施加各种各样的限制。下面是一份Linux共享内存的限制列表,库好重列出了当限制达到时受影响的系统调用及其返回错误。
SHMMNI
这是一个系统级别的限制,它限制了创建的共享内存表标识符的数量(shemget() ENOSPC)
SHMMIN
这个是一个共享内存段的最小大小(字节数),这个限制被定义成了1(无法修改这个值),但是是极限值是系统分页大小(shmget() EINVAL)
SHMMAX
这是一个共享内存段的最大大小(字节数)SHMMAX的实际上限依赖于可用的RAM和交换空间(shmget() EINVAL)
SHMALL
这是一个系统级别的限制,它限制了共享内存中的分页总数,其他大多数UNIX实现并没有提供这个限制,SHMALL的实际上限依赖于可用的RAM和交换空间。(shmget() ENOSPC)

48.10 总结

	共享内存允许两个或者多个进程共享内存中的同一个分页。通过共享内存交换数据无需内核干涉,
	一旦一个进程将数据复制到一个共享内存段之后,数据立刻对其他进程可见。
	共享内存是一种快速的IPC机制,尽管这种速度上的提升通常会因必须要使用某种同步技术而被抵消掉一部分,如使用一个System V 信号量来同步对共享内存访问。
	在附加一个共享内存段时推荐的做法是允许内核选择将段附加在进程的虚拟地址空间的何处,
	这意味这在不同进程中虚拟地址可能是不同的,正因为这个原因,所有对段中地址的引用都应该表示成为相对偏移量,而不是一个绝对指针。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值