Возможно ли, что один сегмент совместно используемой памяти будет подключаться несколько раз к одному родительскому pid? - PullRequest
0 голосов
/ 31 октября 2018

Я создал 200 дочерних процессов родительского процесса, которые взаимодействуют через механизм IPC общей памяти:

Родитель <-> ШМ <-> ребенок

Но наблюдение странное.

Я обнаружил, что 4 процесса подключены к одному и тому же идентификатору SHM, в котором 2 - родительский pid, а 2 - дочерний pid (неожиданное поведение). и где-то 2 процесса привязаны к одному идентификатору SHM (ожидаемое поведение).

Пожалуйста, найдите ниже вывод-

-bash-4.2# grep 123076652 /proc/*/maps
/proc/27750/maps:7f1323576000-7f1323577000 rw-s 00000000 00:04 123076652  /SYSV2c006eff (deleted)    
/proc/27750/maps:7f1323676000-7f1323677000 rw-s 00000000 00:04 123076652  /SYSV2c006eff (deleted)
/proc/27827/maps:7f87ac3c0000-7f87ac3c1000 rw-s 00000000 00:04 123076652  /SYSV2c006eff (deleted)
/proc/28090/maps:7f9d33b8b000-7f9d33b8c000 rw-s 00000000 00:04 123076652  /SYSV2c006eff (deleted)

Как мы можем легко видеть, PID-27750 (родительский) прикреплен два раза к одному сегменту.

Как это возможно? Это ошибка Centos?

Ответы [ 2 ]

0 голосов
/ 01 ноября 2018

Я обнаружил проблему, я использовал тот же идентификатор для генерации ключа ftok() для двух дочерних процессов.

A key_t имеет 32 бита и может иметь любое значение , которое мы хотим.

ftok - это просто «удобная» функция для генерации уникального значения key_t, которое будет использоваться при вызове shmget (см. Ниже).

Если мы используем IPC_PRIVATE для этого значения key_t, мы получим закрытый дескриптор, который может использовать любой дочерний элемент одного родительского процесса. Он отображается в ipcs как key_t из 0 с уникальным shmid [который похож на дескриптор файла].

Итак, если у нас один родитель, а мы просто делаем fork, мы можем использовать это, потому что дети наследуют это от родителя. Это предпочтительный метод. Таким образом, в этом случае ftok не требуется.

При использовании закрытых ключей, когда все присоединенные процессы завершаются, общие области автоматически удаляются ядром.

Если мы используем ненулевое значение key_t, мы создаем перманент область. Это останется (с данными все еще там).

Чтобы удалить это, конечный процесс (то есть родительский процесс) должен сделать shmctl(shmid,IPC_RMID,NULL) для всех shmid, полученных из shmget вызовов.

Если родительский процесс умрет до того, как сделает это, область останется. Такие области все еще будут отображаться в ipcs


Вот пример кода, который иллюстрирует использование IPC_PRIVATE. Он может быть адаптирован для использования ненулевого значения key_t, но для вашего приложения это не может быть гарантировано:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/wait.h>

#define NCHILD      200
#define SIZE        4096

// child process control
typedef struct {
    int tsk_cldidx;                     // child index
    int tsk_shmid;                      // shmid for process
    pid_t tsk_pid;                      // pid of process
    void *tsk_shmadr;                   // address of attached area
} tsk_t;

tsk_t cldlist[NCHILD];

void
dofork(int cldidx)
{
    tsk_t *tskself = &cldlist[cldidx];

    do {
        tskself->tsk_pid = fork();

        // parent
        if (tskself->tsk_pid != 0)
            break;

        // detach from all areas that are not ours
        for (cldidx = 0;  cldidx < NCHILD;  ++cldidx) {
            tsk_t *tsk = &cldlist[cldidx];
            if (tsk->tsk_cldidx != tskself->tsk_cldidx)
                shmdt(tsk->tsk_shmadr);
        }

        // do something useful ...

        exit(0);
    } while (0);
}

int
main(void)
{
    int cldidx;
    tsk_t *tsk;

    // create private shared memory ids for each child
    for (cldidx = 0;  cldidx < NCHILD;  ++cldidx) {
        tsk = &cldlist[cldidx];
        tsk->tsk_cldidx = cldidx;
        tsk->tsk_shmid = shmget(IPC_PRIVATE,SIZE,0);
        tsk->tsk_shmadr = shmat(tsk->tsk_shmid,NULL,0);
    }

    // start up all children
    for (cldidx = 0;  cldidx < NCHILD;  ++cldidx)
        dofork(cldidx);

    // do something useful with children ...

    // wait for all children
    while (wait(NULL) >= 0);

    // remove all segments
    // NOTE: with IPC_PRIVATE, this may not be necessary -- it may happen
    // automatically when we exit
    for (cldidx = 0;  cldidx < NCHILD;  ++cldidx) {
        tsk = &cldlist[cldidx];
        shmctl(tsk->tsk_shmid,IPC_RMID,NULL);
    }

    return 0;
}

Если у нас есть отдельные программы, которые имеют нет отношения родитель / ребенок, нам нужен ненулевой key_t. Может быть трудно создать уникальный key_t, который не сталкивается / не конфликтует с другим, возможно, из совершенно не связанной группы программ (например, другого пользователя)

Не могли бы вы объяснить, сколько максимальных уникальных ключей можно сгенерировать с помощью ftok ().

AFAIK значат только последние младшие 8 бит. Можем ли мы использовать, можем ли мы использовать 2-байтовое целое число, например «300», для генерации ключа. Какова вероятность дублирования ключей здесь?

Для данного [существующего] файла и восемь бит proj_id, как вы заметили, существует только 256 уникальных ключей, которые могут быть сгенерированы. Нам понадобится другой аргумент файла, чтобы получить следующие 256.

Возможно, было бы лучше вообще отказаться от ftokникогда не использовал его при использовании shmget]. Я сделал 0xAA000000 в качестве базового key_t значения. Я заменил нули на любое уникальное значение подключа, в котором я нуждаюсь (есть ~ 24 миллиона возможных комбинаций).


Если мы контролируем все программы, которые обращаются к областям общей памяти, необязательно иметь несколько областей.

Это может быть достаточно, и больше желательно иметь одну область общей памяти. В этом случае мы делаем только один shmget и один shmat. Тогда ftok(myfile,0) может выдать хороший ключ.

Если размер, необходимый для общения с ребенком, составляет (например, страницу) (PER_CHILD = 4096), и у нас есть NCHILD дочерних элементов для создания, мы можем просто создать одну область размером TOTAL_SIZE = PER_CHILD * NCHILD. Тогда для данного дочернего элемента X указатель его области равен shmaddr + (X * PER_CHILD)


UPDATE:

Могу ли я использовать флаг IPC_CREAT и выполнять exec () вызов для ребенка?

Я думаю, что вы имеете в виду использование ненулевого ключа с shmget в сочетании с этим.

Вызов exec закроет все сопоставления : разделяемая память после exec ()

Он также закроет дескриптор файла, возвращаемый shmget [или shm_open].

Таким образом, использование ненулевого ключа - единственный [практический] способ убедиться, что он работает в execvp и др.

Это вызовет какие-либо проблемы. AFAIK, если мы используем exec (), тогда процесс будет иметь другое адресное пространство. Это вызовет какие-либо проблемы?

Дочерняя программа должна будет (заново) установить свое собственное отображение с помощью shmget и shmat.

Но, если мы используем shm_open [вместо shmget], мы можем оставить дескриптор файла открытым , если , мы используем fcntl, чтобы очистить FD_CLOEXEC флаг в дескрипторе перед вызовом execvp.

Однако это может быть бесполезно, поскольку дочерняя программа (цель execvp) [вероятно] не будет знать номер дескриптора файла, который родительский элемент открыл с shm_open, так что это немного спорный вопрос.

0 голосов
/ 31 октября 2018

Чтобы ответить на ваш вопрос: да, конечно. У вас есть доказательства прямо в вашем вопросе.

Как это происходит? Если вы вызываете mmap() для одного и того же файла несколько раз, он отображает его несколько раз.

Чтобы избежать этого, ответ: не делай этого.

Я просто догадываюсь, но держу пари, что один из ваших вызовов fork() не удался, и вы никогда не проверяли ошибки, и код продолжал выполнять дочерний код в родительском процессе. Это объясняет наличие двух карт на одном PID.

...