Ответ 1
Я попытался добиться того же (на самом деле, его взгляд проще, поскольку мне нужно только делать снимки живого региона, мне не нужно брать копии копий). Я не нашел для этого хорошего решения.
Поддержка прямого ядра (или его отсутствие): путем изменения/добавления модуля это должно быть возможно. Однако нет простого способа настроить новую область COW из существующей. Код, используемый fork (copy_page_rank
), копирует vm_area_struct
из одного пространства процессов/виртуального адреса в другое (новое), но предполагает, что адрес нового сопоставления совпадает с адресом старого. Если вы хотите реализовать функцию "переназначения", функция должна быть изменена/дублирована для копирования vm_area_struct
с преобразованием адреса.
BTRFS. Я думал об использовании COW на btrfs для этого. Я написал простую программу, сопоставляющую два файла reflink-ed и попытался их сопоставить. Однако просмотр информации о странице с помощью /proc/self/pagemap
показывает, что два экземпляра файла не используют одни и те же страницы кеша. (По крайней мере, если мой тест не соответствует действительности). Таким образом, вы не выиграете, сделав это. Физические страницы одних и тех же данных не будут использоваться для разных экземпляров.
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <assert.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <unistd.h>
#include <inttypes.h>
#include <stdio.h>
void* map_file(const char* file) {
struct stat file_stat;
int fd = open(file, O_RDWR);
assert(fd>=0);
int temp = fstat(fd, &file_stat);
assert(temp==0);
void* res = mmap(NULL, file_stat.st_size, PROT_READ, MAP_SHARED, fd, 0);
assert(res!=MAP_FAILED);
close(fd);
return res;
}
static int pagemap_fd = -1;
uint64_t pagemap_info(void* p) {
if(pagemap_fd<0) {
pagemap_fd = open("/proc/self/pagemap", O_RDONLY);
if(pagemap_fd<0) {
perror("open pagemap");
exit(1);
}
}
size_t page = ((uintptr_t) p) / getpagesize();
int temp = lseek(pagemap_fd, page*sizeof(uint64_t), SEEK_SET);
if(temp==(off_t) -1) {
perror("lseek");
exit(1);
}
uint64_t value;
temp = read(pagemap_fd, (char*)&value, sizeof(uint64_t));
if(temp<0) {
perror("lseek");
exit(1);
}
if(temp!=sizeof(uint64_t)) {
exit(1);
}
return value;
}
int main(int argc, char** argv) {
char* a = (char*) map_file(argv[1]);
char* b = (char*) map_file(argv[2]);
int fd = open("/proc/self/pagemap", O_RDONLY);
assert(fd>=0);
int x = a[0];
uint64_t info1 = pagemap_info(a);
int y = b[0];
uint64_t info2 = pagemap_info(b);
fprintf(stderr, "%" PRIx64 " %" PRIx64 "\n", info1, info2);
assert(info1==info2);
return 0;
}
mprotect
+ mmap
анонимные страницы. Это не работает в вашем случае, но решение заключается в использовании файла MAP_SHARED для моей основной области памяти. В моментальном снимке файл отображается в другом месте, и оба экземпляра защищены. При записи, анонимной странице, отображаемой в моментальном снимке, данные копируются на этой новой странице, а исходная страница не защищена. Однако это решение не работает в вашем случае, так как вы не сможете повторить процесс в снимке (потому что это не обычная область MAP_SHARED, а MAP_SHARED с некоторыми страницами MAP_ANONYMOUS. Кроме того, он не масштабируется с количеством копий: если у меня много копий COW, мне придется повторить один и тот же процесс для каждой копии, и эта страница не будет дублироваться для копий. И я не могу отобразить анонимную страницу в исходной области, так как невозможно будет отобразить анонимные страницы в копиях. Это решение не работает в любом случае.
mprotect
+ remap_file_pages
: похоже, единственный способ сделать это, не касаясь ядра Linux. Недостатком этого является то, что в общем случае вам, вероятно, придется сделать scyscall_file_page для каждой страницы при копировании: возможно, это не так эффективно, чтобы сделать много системных вызовов. При дедупликации разделяемой страницы вам необходимо, по крайней мере,: remap_file_page создать новую/бесплатную страницу для новой записи на страницу, m-un-protect для новой страницы. Необходимо ссылаться на каждую страницу.
Я не думаю, что подходы, основанные на mprotect()
, будут очень хорошо масштабироваться (если вы справитесь с такой большой памятью). В Linux mprotect()
не работает с гранулярностью страницы памяти, а с детализацией vm_area_struct
(записи, которые вы находите в /prod//maps ). Выполнение mprotect()
при гранулярности страницы памяти приведет к постоянному разделению ядра и объединению vm_area_struct:
-
у вас будет очень
mm_struct
; -
поиск vm_area_struct (который используется для журнала операций, связанных с виртуальной памятью) находится на
O(log #vm_area_struct)
, но он может по-прежнему оказывать отрицательное влияние на производительность; -
потребление памяти для этих структур.
Для этой причины, syscall для remap_file_pages() был создан [http://lwn.net/Articles/24468/], чтобы сделать отображение нелинейной памяти файл. Для этого с помощью mmap требуется журнал vm_area_struct
. Я не думаю, что они были предназначены для сопоставления гранулярности страницы: remap_file_pages() не очень оптимизирован для этого прецедента, так как для него потребуется syscall на страницу.
Я думаю, что единственным жизнеспособным решением является позволить ядру сделать это. Это можно сделать в пользовательском пространстве с помощью remap_file_pages, но, вероятно, это будет довольно неэффективно, поскольку моментальный снимок будет генерировать потребность в количестве syscalls, пропорциональном числу страниц. Вариант remap_file_pages может сделать трюк.
Этот подход, однако, дублирует логику страницы ядра. Я склонен думать, что мы должны позволить ядру сделать это. В целом, реализация в ядре, по-видимому, является лучшим решением. Для тех, кто знает эту часть ядра, это должно быть довольно легко сделать.
КСМ(Объединение Samepage ядра): есть что-то, что может сделать ядро. Он может попытаться дедуплицировать страницы. Вам все равно придется копировать данные, но ядро должно быть способно их объединить. Вам нужно снять новую анонимную область для своей копии, скопировать ее вручную с помощью memcpy и madvide(start, end, MADV_MERGEABLE)
областей. Вам нужно включить KSM (в корневом каталоге):
echo 1 > /sys/kernel/mm/ksm/run
echo 10000 > /sys/kernel/mm/ksm/pages_to_scan
Он работает, он не очень хорошо работает с моей рабочей нагрузкой, но, вероятно, потому, что в конце страницы не так много общего. Недостатком является то, что вам все равно придется делать копию (у вас не может быть эффективной COW), а затем ядро будет не объединять страницу. Он будет генерировать ошибки страницы и кеша при копировании, поток демонов KSM будет потреблять много CPU (у меня есть процессор, работающий на A00% для всей симуляции) и, вероятно, потребляет кэш журнала. Таким образом, вы не выиграете время при копировании, но можете получить некоторую память. Если ваша основная мотивация заключается в том, чтобы использовать меньшую память в долгосрочной перспективе, и вам все равно, чтобы избежать копирования, это решение может сработать для вас.