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()
.