mprotect в сегменте разделяемой памяти mmap-ed

Когда два процесса совместно используют сегмент памяти, открытый с помощью shm_open и mmap-ed, влияет ли выполнение mprotect на часть разделяемой памяти в одном процессе на разрешения, видимые другим процессом в этой же части? то есть, если один процесс делает часть сегмента общей памяти доступной только для чтения, становится ли он доступным только для чтения для другого процесса?

2 ответа

Мне всегда нравится решать эти вопросы в двух частях.

Часть 1 - Давайте проверим

Давайте рассмотрим пример, который относительно похож на тот, что в.

Общий заголовок - shared.h

      #include <sys/mman.h>
#include <fcntl.h>
#include <semaphore.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

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

struct shmbuf {
    char   buf[4096];
    sem_t  sem;
};

Процесс Creator - (Скомпилировать с использованием gcc creator.c -o creator -lrt -lpthread)

      #include <ctype.h>
#include <string.h>
#include "shared.h"

int
main(int argc, char *argv[])
{
    if (argc != 2) {
        fprintf(stderr, "Usage: %s /shm-path\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    char *shmpath = argv[1];
    int fd = shm_open(shmpath, O_CREAT | O_EXCL | O_RDWR,
                      S_IRUSR | S_IWUSR);
    if (fd == -1)
        errExit("shm_open");

    struct shmbuf *shm;
    if (ftruncate(fd, sizeof(*shm)) == -1)
        errExit("ftruncate");

    shm = mmap(NULL, sizeof(*shm), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (shm == MAP_FAILED)
        errExit("mmap");

    if (sem_init(&shm->sem, 1, 0) == -1)
        errExit("sem_init");

    if (mprotect(&shm->buf, sizeof(shm->buf), PROT_READ) == -1)
        errExit("mprotect");

    if (sem_wait(&shm->sem) == -1)
        errExit("sem_wait");

    printf("got: %s\n", shm->buf);

    shm_unlink(shmpath);

    exit(EXIT_SUCCESS);
}

Писательский процесс - writer.c(Скомпилируйте с помощью gcc writer.c -o writer -lrt -lpthread)

      #include <string.h>
#include "shared.h"

int
main(int argc, char *argv[])
{
    if (argc != 3) {
        fprintf(stderr, "Usage: %s /shm-path string\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    char *shmpath = argv[1];
    char *string = argv[2];

    int fd = shm_open(shmpath, O_RDWR, 0);
    if (fd == -1)
        errExit("shm_open");

    struct shmbuf *shm = mmap(NULL, sizeof(*shm), PROT_READ | PROT_WRITE,
                              MAP_SHARED, fd, 0);
    if (shm == MAP_FAILED)
        errExit("mmap");

    strcpy(&shm->buf[0], string);

    if (sem_post(&shm->sem) == -1)
        errExit("sem_post");

    exit(EXIT_SUCCESS);
}

Что должно произойти? Процесс-создатель создает новый объект разделяемой памяти, «устанавливает» его размер, отображает его в память (как shm), разрешает только запись в буфер ( shm->buf) и ждет семафора, чтобы узнать, когда модуль записи (который мы обсудим чуть позже) закончит свою работу.

Процесс записи запускается, открывает тот же объект общей памяти, записывает в него все, что мы ему говорим, и сигнализирует семафору.

Вопрос в том, сможет ли писатель писать в объект общей памяти, даже если создатель изменил защиту на ТОЛЬКО ДЛЯ ЧТЕНИЯ?

Давай выясним. Мы можем запустить его, используя:

      # ./creator.c /shmulik &
# ./writer.c /shmulik hi!
got: hi!
#
[1]+  Done                    ./creator /shmulik

Как вы можете видеть, писатель смог записать в разделяемую память, даже несмотря на то, что создатель установил ее защиту ТОЛЬКО ДЛЯ ЧТЕНИЯ.

Может создатель что-то не так делает? Попробуем добавить следующую строку в creator.c:

          if (mprotect(&shm->buf, sizeof(shm->buf), PROT_READ) == -1)
        errExit("mprotect");

    memset(&shm->buf, 0, sizeof(shm->buf)); // <-- This is the new line

    if (sem_wait(&shm->sem) == -1)
        errExit("sem_wait");

Давайте перекомпилируем и снова запустим создателя:

      # gcc creator.c -o creator -lrt -lpthread
# ./creator /shmulik
Segmentation fault

Как видите, все сработало, как и ожидалось.

Как насчет того, чтобы позволить писателю отобразить общую память, а затем изменить защиту? Ну, это ничего не изменит. ТОЛЬКО влияет на защиту памяти вызывающего его процесса (и его потомков).

Часть 2 - Давайте разбираться

Во-первых, вы должны понимать, что это метод glibc, а не системный вызов. Вы можете получить glibcисходный код с их веб-сайта и просто посмотрите, чтобы увидеть это самостоятельно.

Основная реализация shm_openэто обычный призыв к open, как подсказывает shm_open(3)справочная страница .

Как мы уже видели, большая часть магии происходит в . При вызове мы должны использовать MAP_SHARED(скорее, чем MAP_PRIVATE), в противном случае каждый процесс получит отдельный сегмент памяти для начала, и, очевидно, один не повлияет на другой.

Когда мы звоним mmap, хмель примерно:

На этом последнем этапе вы могли видеть, что мы берем контекст управления памятью процесса. mmи выделить новую область виртуальной памяти:

      struct mm_struct *mm = current->mm;
...
vma = vm_area_alloc(mm);
...
vma->vm_page_prot = vm_get_page_prot(vm_flags);

Эта область памяти не используется совместно с другими процессами. С mprotectменяется только vm_page_protдля каждого процесса vma, это не влияет на другие процессы, отображающие то же самое пространство памяти.

Спецификация POSIX дляmprotect()предполагает, что изменения в защите разделяемой памяти должны затрагивать все процессы, использующие эту разделяемую память.

Подробно описаны два состояния ошибки:

  • [EAGAIN] Аргумент указывает PROT_WRITE вместо сопоставления MAP_PRIVATE, и недостаточно ресурсов памяти для резервирования для блокировки частной страницы.
  • [ЭНОМЕМ] protАргумент указывает PROT_WRITE в сопоставлении MAP_PRIVATE, и при необходимости потребуется больше места, чем система может предоставить для блокировки личных страниц.

Это настоятельно предполагает, что память, сопоставленная с MAP_SHARED, не должна выходить из строя из-за нехватки памяти для создания копий.

См. также спецификацию POSIX дляmmap().

Другие вопросы по тегам