SHMOP(2)

НАИМЕНОВАНИЕ
shmat, shmdt - операции с общей памятью System V

БИБЛИОТЕКА
Стандартная библиотека языка C (libc, -lc)

ОБЗОР
#include <sys/shm.h>

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

ОПИСАНИЕ

  shmat()
Вызов shmat() подключает сегмент общей памяти System V с идентификатором shmid к адресному пространству вызывающего процесса. Адрес подключения, указанный в shmaddr, учитывается следующим образом:

•   Если значение shmaddr равно NULL, то система выбирает подходящий (неиспользуемый) выровненный по странице адрес для подключения сегмента.

•   Если значение shmaddr не равно NULL, а в shmflg указан флаг SHM_RND, то подключение производится по адресу shmaddr, округлённому до ближайшего значения кратного SHMLBA.

•   В противном случае shmaddr должно быть выровнено по адресу страницы, к которому производится подключение.

В дополнении к SHM_RND, в аргументе битовой маски shmflg могут быть указаны следующие флаги:

SHM_EXEC (есть только в Linux; начиная с Linux 2.6.9)
    Разрешить выполнение содержимого сегмента. Вызывающий должен иметь права на выполнение сегмента.

SHM_RDONLY
    Разрешить доступ к сегменту только для чтения. Процесс должен иметь права на чтение сегмента. Если этот флаг не указан, то сегмент подключается с правом чтения и записи, и процесс должен иметь права на чтение и запись сегмента. Об общих сегментах памяти, доступных только на запись, ничего не упоминается.

SHM_REMAP (есть только в Linux)
    Этот флаг указывает, что отображение сегмента должно замещать любые существующие отображения в диапазоне, начиная с shmaddr и до размера сегмента (обычно выдается ошибка EINVAL, если в этом диапазоне адресов уже есть отображение). В этом случае значение shmaddr не должно быть равно NULL.

Значение brk(2) вызывающего процесса от подключения не изменяется. При завершении работы процесса сегмент будет автоматически отсоединён. Один и тот же сегмент может быть подключён в адресное пространство процесса несколько раз, как только для чтения, так и для чтения-записи.

При успешном выполнении системный вызов shmat() обновляет поля структуры shmid_ds (см. shmctl(2)), связанной с общим сегментом памяти, следующим образом:

•   Полю shm_atime присваивается значение текущего времени.

•   Значение shm_lpid устанавливается равным идентификатору вызывающего процесса.

•   Значение shm_nattch увеличивается на 1.

  shmdt()
Вызов shmdt() отключает сегмент общей памяти, находящийся по адресу shmaddr, от адресного пространства вызывающего процесса. Отключаемый сегмент должен быть подключён по адресу shmaddr с помощью вызова shmat().

При успешном выполнении вызов shmdt() обновляет поля структуры shmid_ds, связанной с общим сегментом памяти, следующим образом:

•   Полю shm_dtime присваивается значение текущего времени.

•   Значение shm_lpid устанавливается равным идентификатору вызывающего процесса.

•   Значение shm_nattch уменьшается на 1. Если оно становится равным 0 и сегмент помечен для удаления, то сегмент удаляется.

ВОЗВРАЩАЕМОЕ ЗНАЧЕНИЕ
On success, shmat() returns the address of the attached shared memory segment; on error, (void *) -1 is returned, and errno is set to indicate the error.

On success, shmdt() returns 0; on error -1 is returned, and errno is set to indicate the error.

ОШИБКИ
shmat() может завершиться ошибкой из-за следующих ошибок:

EACCES
    Вызывающий процесс не имеет прав для подключения заданного типа и не имеет мандата CAP_IPC_OWNER в пространстве имён пользователя, который управляет его пространством имён IPC.

EIDRM
    Значение shmid указывает на удалённый идентификатор.

EINVAL
    Неправильное значение shmid, не выровненное (по границе страницы и не указан SHM_RND) или неправильное значение shmaddr, или невозможно подключить сегмент по адресу shmaddr, или был указан SHM_REMAP, но shmaddr равно NULL.

ENOMEM
    Невозможно выделить память для дескриптора или страничных таблиц.

shmdt() может завершиться ошибкой с одной из следующих ошибок:

EINVAL
    По адресу shmaddr подключённый общий сегмент памяти отсутствует; или значение shmaddr не выровнено по границе страницы.

СТАНДАРТЫ
POSIX.1-2008.

ИСТОРИЯ
POSIX.1-2001, SVr4.

В SVID 3 (возможно, чуть раньше) тип аргумента shmaddr изменён с char * на const void *, а тип возвращаемого значения shmat() с char * на void *.

ПРИМЕЧАНИЯ
При вызове fork(2) потомки наследуют подключённые общие сегменты памяти.

При вызове execve(2) все подключённые общие сегменты памяти отключаются.

При вызове _exit(2) все подключённые общие сегменты памяти отключаются.

Для улучшения переносимости программ при подключении общего сегмента памяти рекомендуется использовать shmat() с аргументом shmaddr, установленным в NULL. Необходимо учитывать, что сегмент памяти, подключаемый таким способом, в разных процессах может подключаться по разным адресам. Поэтому все указатели в области общей памяти должны быть не абсолютными, а относительными (как правило относительно адреса начала сегмента).

В Linux сегмент общей памяти можно подключить даже, если он помечен для удаление. Однако в POSIX.1 об этом ничего не сказано, и многие другие реализации это не поддерживают.

На работу shmat() влияют следующие системные параметры:

SHMLBA
    Кратность адреса нижней границы сегмента. При явном указании в вызове shmat() подключаемого адреса вызывающий должен убедиться, что адрес кратен этому значению. Это необходимо у некоторых архитектур, чтобы точно получить хорошую производительность от кэша ЦП или чтобы различные подключения одного сегмента имели целостный вид внутри кэша ЦП. Параметр SHMLBA, обычно, кратен нескольким размерам системной страницы (у многих архитектур Linux SHMLBA совпадает с размером системной страницы).

Реализацией не ограничивается количество общих сегментов общей памяти на процесс (SHMSEG).

ПРИМЕРЫ
The two programs shown below exchange a string using a shared memory segment. Further details about the programs are given below. First, we show a shell session demonstrating their use.

In one terminal window, we run the "reader" program, which creates a System V shared memory segment and a System V semaphore set. The program prints out the IDs of the created objects, and then waits for the semaphore to change value.

    $ ./svshm_string_read
    shmid = 1114194; semid = 15

In another terminal window, we run the "writer" program. The "writer" program takes three command-line arguments: the IDs of the shared memory segment and semaphore set created by the "reader", and a string. It attaches the existing shared memory segment, copies the string to the shared memory, and modifies the semaphore value.

    $ ./svshm_string_write 1114194 15 'Hello, world'

Returning to the terminal where the "reader" is running, we see that the program has ceased waiting on the semaphore and has printed the string that was copied into the shared memory segment by the writer:

    Hello, world

  Исходный код программы: svshm_string.h
The following header file is included by the "reader" and "writer" programs:

    /* svshm_string.h

       Licensed under GNU General Public License v2 or later.
    */
    #ifndef SVSHM_STRING_H
    #define SVSHM_STRING_H

    #include <stdio.h>
    #include <stdlib.h>
    #include <sys/sem.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

    #endif  // include guard

  Program source: svshm_string_read.c
The "reader" program creates a shared memory segment and a semaphore set containing one semaphore. It then attaches the shared memory object into its address space and initializes the semaphore value to 1. Finally, the program waits for the semaphore value to become 0, and afterwards prints the string that has been copied into the shared memory segment by the "writer".

    /* svshm_string_read.c

       Licensed under GNU General Public License v2 or later.
    */
    #include <stdio.h>
    #include <stdlib.h>
    #include <sys/ipc.h>
    #include <sys/sem.h>
    #include <sys/shm.h>

    #include "svshm_string.h"

    int
    main(void)
    {
        int            semid, shmid;
        char           *addr;
        union semun    arg, dummy;
        struct sembuf  sop;

        /* 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 (semid == -1)
            errExit("semget");

        /* 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
The writer program takes three command-line arguments: the IDs of the shared memory segment and semaphore set that have already been created by the "reader", and a string. It attaches the shared memory segment into its address space, and then decrements the semaphore value to 0 in order to inform the "reader" that it can now examine the contents of the shared memory.

    /* svshm_string_write.c

       Licensed under GNU General Public License v2 or later.
    */
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <sys/sem.h>
    #include <sys/shm.h>

    #include "svshm_string.h"

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

        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 '\0' */
        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), capabilities(7), shm_overview(7), sysvipc(7)

ПЕРЕВОД
Русский перевод этой страницы руководства разработал(и) Alexander Golubev <fatzer2@gmail.com>, Azamat Hackimov <azamat.hackimov@gmail.com>, Hotellook, Nikita <zxcvbnm3230@mail.ru>, Spiros Georgaras <sng@hellug.gr>, Vladislav <ivladislavefimov@gmail.com>, Yuri Kozlov <yuray@komyakino.ru>, Иван Павлов <pavia00@gmail.com> и Kirill Rekhov <krekhov.dev@gmail.com>

Этот перевод является свободной программной документацией; он распространяется на условиях общедоступной лицензии GNU (GNU General Public License - GPL, https://www.gnu.org/licenses/gpl-3.0.html версии 3 или более поздней) в отношении авторского права, но БЕЗ КАКИХ-ЛИБО ГАРАНТИЙ.

Если вы обнаружите какие-либо ошибки в переводе этой страницы руководства, пожалуйста, сообщите об этом разработчику(ам) по его(их) адресу(ам) электронной почты или по адресу списка рассылки русских переводчиков (<debian-l10n-russian@lists.debian.org>).