SHMOP - Linux手册页

时间:2019-08-20 17:59:24  来源:igfitidea点击:

Linux程序员手册 第2部分
更新日期: 2020-04-11

名称

shmat,shmdt-System V共享内存操作

语法

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

void *shmat(int shmid, const void *shmaddr, int shmflg);

int shmdt(const void *shmaddr);

说明

shmat()

shmat()将shmid标识的System V共享内存段附加到调用进程的地址空间。附加地址由shmaddr使用以下条件之一指定:

*
如果shmaddr为NULL,则系统选择合适的(未使用)页面对齐地址来附加该段。
*
如果shmaddr不为NULL,并且在shmflg中指定了SHM_RND,则附加将在等于shmaddr的地址处舍入到最接近SHMLBA的倍数。
*
否则,shmaddr必须是发生连接的页面对齐地址。

除了SHM_RND,还可以在shmflg位掩码参数中指定以下标志:

SHM_EXEC(Linux-specific; since Linux 2.6.9)
允许执行段的内容。调用方必须对该段具有执行权限。
SHM_RDONLY
附加该段以进行只读访问。该进程必须对该段具有读取权限。如果未指定此标志,则将附加该段以进行读写访问,并且该进程必须具有对该段的读取和写入权限。没有只写共享内存段的概念。
SHM_REMAP(Linux-specific)
此标志指定该段的映射应替换从shmaddr到该段大小的范围内的任何现有映射。 (通常,如果此地址范围内已经存在映射,则将导致EINVAL错误。)在这种情况下,shmaddr不能为NULL。

调用过程的brk(2)值不会被附加更改。该段将在流程退出时自动分离。在进程的地址空间中,同一段可以作为读取段和作为读写段连接,并且可以不止一次。

成功的shmat()调用将更新与共享内存段关联的shmid_ds结构的成员(请参见shmctl(2)),如下所示:

*
shm_atime设置为当前时间。
*
shm_lpid设置为调用进程的进程ID。
*
shm_nattch递增1。

shmdt()

shmdt()从调用进程的地址空间中分离位于shmaddr指定的地址处的共享内存段。当前,要分离的段必须以等于附加shmat()调用返回的值的shmaddr附加。

成功调用shmdt()时,系统将更新与共享内存段关联的shmid_ds结构的成员,如下所示:

*
shm_dtime设置为当前时间。
*
shm_lpid设置为调用进程的进程ID。
*
shm_nattch减一。如果它变为0,并且将该段标记为删除,则该段将被删除。

返回值

成功后,shmat()返回附加的共享内存段的地址;如果出现错误,则返回(void *)-1,并且将errno设置为指示错误原因。

成功时,shmdt()返回0;否则,返回0。如果返回错误-1,则返回错误,并且设置errno以指示错误原因。

错误说明

shmat()失败时,errno设置为以下之一:

EACCES
调用过程没有所请求的附加类型所需的权限,并且在控制其IPC名称空间的用户名称空间中不具有CAP_IPC_OWNER功能。
EIDRM
shmid指向已删除的标识符。
EINVAL
无效的shmid值,未对齐(即未页面对齐且未指定SHM_RND)或无效的shmaddr值,或者无法在shmaddr附加段,或者指定了SHM_REMAP且shmaddr为NULL。
ENOMEM
无法为描述符或页表分配内存。

shmdt()失败时,errno设置如下:

EINVAL
shmaddr上没有附加共享内存段;或者,shmaddr在页面边界上未对齐。

遵循规范

POSIX.1-2001,POSIX.1-2008,SVr4。

在SVID 3(或更早版本)中,shmaddr参数的类型从char *更改为const void *,并且将shmat()的返回类型从char *更改为v​​oid *。

备注

fork(2)之后,子代继承附加的共享内存段。

execve(2)之后,所有附加的共享内存段都将从进程中分离。

_exit(2)上,所有附加的共享内存段都从进程中分离。

在shmaddr等于NULL的情况下使用shmat()是连接共享内存段的首选可移植方法。请注意,以这种方式附加的共享内存段可能会附加在不同进程中的不同地址上。因此,共享内存中维护的所有指针都必须是相对的(通常相对于段的起始地址),而不是绝对的。

在Linux上,即使已将共享内存段标记为已删除,也可以附加它。但是,POSIX.1没有指定此行为,并且许多其他实现也不支持此行为。

以下系统参数影响shmat():

SHMLBA
段低边界地址的倍数。在对shmat()的调用中显式指定附加地址时,调用方应确保该地址是该值的倍数。为了确保良好的CPU缓存性能或确保同一段的不同附件在CPU缓存内具有一致的视图,这在某些体系结构上是必需的。 SHMLBA通常是系统页面大小的倍数。 (在许多Linux体系结构上,SHMLBA与系统页面大小相同。)

该实现对共享内存段(SHMSEG)的数量没有固有的按进程限制。

示例

下面显示的两个程序使用共享内存段交换字符串。有关程序的更多详细信息在下面给出。首先,我们展示一个shell会话,演示它们的用法。

在一个终端窗口中,我们运行"阅读器"程序,该程序创建一个System V共享内存段和一个System V信号量集。程序将打印出创建的对象的ID,然后等待信号量更改值。

$ ./svshm_string_read
shmid = 1114194; semid = 15

在另一个终端窗口中,我们运行" writer"程序。 " writer"程序采用三个命令行参数:" reader"创建的共享内存段的ID和信号量集,以及一个字符串。它附加现有的共享内存段,将字符串复制到共享内存,并修改信号量值。

$ ./svshm_string_write 1114194 15 aqHello, worldaq

返回到运行"读取器"的终端,我们看到程序已停止等待信号量,并打印了由写入器复制到共享内存段中的字符串:

Hello, world

Program source: svshm_string.h

"读取器"和"写入器"程序包含以下头文件。

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define errExit(msg)    do { perror(msg); exit(EXIT_FAILURE); \
                        } while (0)

union semun {                   /* Used in calls to semctl() */
    int                 val;
    struct semid_ds *   buf;
    unsigned short *    array;
#if defined(__linux__)
    struct seminfo *    __buf;
#endif
};

#define MEM_SIZE 4096

Program source: svshm_string_read.c

"阅读器"程序创建一个共享内存段和一个包含一个信号量的信号量集。然后,它将共享内存对象附加到其地址空间中,并将信号量值初始化为1。最后,程序等待信号量值变为0,然后打印由"写程序"复制到共享内存段中的字符串。 "。

/* svshm_string_read.c

   Licensed under GNU General Public License v2 or later.
*/
#include "svshm_string.h"

int
main(int argc, char *argv[])
{
    int semid, shmid;
    union semun arg, dummy;
    struct sembuf sop;
    char *addr;

    /* Create shared memory and semaphore set containing one
       semaphore */

    shmid = shmget(IPC_PRIVATE, MEM_SIZE, IPC_CREAT | 0600);
    if (shmid == -1)
        errExit("shmget");

    semid = semget(IPC_PRIVATE, 1, IPC_CREAT | 0600);
    if (shmid == -1)
        errExit("shmget");

    /* Attach shared memory into our address space */

    addr = shmat(shmid, NULL, SHM_RDONLY);
    if (addr == (void *) -1)
        errExit("shmat");

    /* Initialize semaphore 0 in set with value 1 */

    arg.val = 1;
    if (semctl(semid, 0, SETVAL, arg) == -1)
        errExit("semctl");

    printf("shmid = %d; semid = %d\n", shmid, semid);

    /* Wait for semaphore value to become 0 */

    sop.sem_num = 0;
    sop.sem_op = 0;
    sop.sem_flg = 0;

    if (semop(semid, &sop, 1) == -1)
        errExit("semop");

    /* Print the string from shared memory */

    printf("%s\n", addr);

    /* Remove shared memory and semaphore set */

    if (shmctl(shmid, IPC_RMID, NULL) == -1)
        errExit("shmctl");
    if (semctl(semid, 0, IPC_RMID, dummy) == -1)
        errExit("semctl");

    exit(EXIT_SUCCESS);
}

Program source: svshm_string_write.c

writer程序采用三个命令行参数:共享存储段和信号集的ID(已由"读取器"创建)和一个字符串。它将共享内存段附加到其地址空间中,然后将信号量值递减为0,以告知"读取器"它现在可以检查共享内存的内容。

/* svshm_string_write.c

   Licensed under GNU General Public License v2 or later.
*/
#include "svshm_string.h"

int
main(int argc, char *argv[])
{
    int semid, shmid;
    struct sembuf sop;
    char *addr;
    size_t len;

    if (argc != 4) {
        fprintf(stderr, "Usage: %s shmid semid string\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    len = strlen(argv[3]) + 1;  /* +1 to include trailing aq##代码##aq */
    if (len > MEM_SIZE) {
        fprintf(stderr, "String is too big!\n");
        exit(EXIT_FAILURE);
    }

    /* Get object IDs from command-line */

    shmid = atoi(argv[1]);
    semid = atoi(argv[2]);

    /* Attach shared memory into our address space and copy string
       (including trailing null byte) into memory. */

    addr = shmat(shmid, NULL, 0);
    if (addr == (void *) -1)
        errExit("shmat");

    memcpy(addr, argv[3], len);

    /* Decrement semaphore to 0 */

    sop.sem_num = 0;
    sop.sem_op = -1;
    sop.sem_flg = 0;

    if (semop(semid, &sop, 1) == -1)
        errExit("semop");

    exit(EXIT_SUCCESS);
}

另外参见

brk(2),mmap(2),shmctl(2),shmget(2),功能(7),shm_overview(7),sysvipc(7)

出版信息

这个页面是Linux手册页项目5.08版的一部分。有关项目的说明、有关报告错误的信息以及此页面的最新版本,请访问https://www.kernel.org/doc/man-pages/