Как узнать, является ли страница копирования при записи фактической копией?

когда я создаю отображение копирования при записи (MAP_PRIVATE) с помощью mmap, некоторые страницы этого отображения будут скопированы, как только я напишу по определенным адресам. В определенный момент в моей программе я хотел бы выяснить, какие страницы были скопированы. Существует вызов, называемый "mincore", но он сообщает только о том, находится ли страница в памяти или нет, что не совпадает с копируемой страницей или нет.

есть ли способ выяснить, какие страницы были скопированы ?

6 ответов


хорошо, следуя совету MarkR, Я дал ему шанс пройти через интерфейс pagemap и kpageflags. Ниже быстрый тест, чтобы проверить, находится ли страница в памяти "SWAPBACKED", как она называется. Конечно, остается одна проблема, которая заключается в том, что kpageflags доступен только корню.

int main(int argc, char* argv[])
{
  unsigned long long pagesize=getpagesize();
  assert(pagesize>0);
  int pagecount=4;
  int filesize=pagesize*pagecount;
  int fd=open("test.dat", O_RDWR);
  if (fd<=0)
    {
      fd=open("test.dat", O_CREAT|O_RDWR,S_IRUSR|S_IWUSR);
      printf("Created test.dat testfile\n");
    }
  assert(fd);
  int err=ftruncate(fd,filesize);
  assert(!err);

  char* M=(char*)mmap(NULL, filesize, PROT_READ|PROT_WRITE, MAP_PRIVATE,fd,0);
  assert(M!=(char*)-1);
  assert(M);
  printf("Successfully create private mapping\n");

тестовая настройка содержит 4 страницы. страницы 0 и 2 грязные

  strcpy(M,"I feel so dirty\n");
  strcpy(M+pagesize*2,"Christ on crutches\n");

Страница 3 была прочитана.

  char t=M[pagesize*3];

Страница 1 не будет быть доступны

файл pagemap отображает процесс его виртуальной памяти на фактические страницы, которые затем могут быть получены из глобального файла kpageflags позже. прочитайте файл / usr/src/linux/Documentation/vm / pagemap.txt

  int mapfd=open("/proc/self/pagemap",O_RDONLY);
  assert(mapfd>0);
  unsigned long long target=((unsigned long)(void*)M)/pagesize;
  err=lseek64(mapfd, target*8, SEEK_SET);
  assert(err==target*8);
  assert(sizeof(long long)==8);

здесь мы читаем номера фреймов страниц для каждой из наших виртуальных страниц

  unsigned long long page2pfn[pagecount];
  err=read(mapfd,page2pfn,sizeof(long long)*pagecount);
  if (err<0)
    perror("Reading pagemap");
  if(err!=pagecount*8)
    printf("Could only read %d bytes\n",err);

теперь мы собираемся прочитать для каждого виртуального фрейма фактические pageflags

  int pageflags=open("/proc/kpageflags",O_RDONLY);
  assert(pageflags>0);
  for(int i = 0 ; i < pagecount; i++)
    {
      unsigned long long v2a=page2pfn[i];
      printf("Page: %d, flag %llx\n",i,page2pfn[i]);

      if(v2a&0x8000000000000000LL) // Is the virtual page present ?
        {
        unsigned long long pfn=v2a&0x3fffffffffffffLL;
        err=lseek64(pageflags,pfn*8,SEEK_SET);
        assert(err==pfn*8);
        unsigned long long pf;
        err=read(pageflags,&pf,8);
        assert(err==8);
        printf("pageflags are %llx with SWAPBACKED: %d\n",pf,(pf>>14)&1);
        }
    }
}

в целом, я не особо доволен этим подходом, поскольку он требует доступа к файлу, к которому мы вообще не можем получить доступ, и это чертовски сложно (как насчет простого вызова ядра для извлечения pageflags ?).


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

это не идеально, но накладные расходы вполне управляемы, и его можно использовать в сочетании с mincore, etc. для выполнения более сложных оптимизаций, таких как управление размером рабочего набора или приблизительная информация указателя для страниц, которые вы ожидаете иметь swap out, что позволяет системе выполнения сотрудничать с ядра, а не бороться с ним.


Это не просто, но можно определить это. Чтобы узнать, является ли страница копией другой страницы (возможно, другого процесса), вам нужно сделать следующее (ядра recentish):

  1. прочитайте запись в /proc/pid / pagemap для соответствующих страниц в процессе (- ах)
  2. допросить /proc / kpageflags

затем вы можете определить, что две страницы на самом деле являются одной и той же страницей в памяти.

Это довольно сложно сделайте это, вам нужно быть root, и все, что вы делаете, вероятно, будет иметь некоторые условия расы в нем, но это возможно.


Copy-on-write реализован с использованием схемы защиты памяти оборудования виртуальной памяти.

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

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

Так все, что вам нужно сделать, это проверить флаги защиты памяти для страницы.

в Windows API является GetWorkingSet см. объяснение в VirtualQueryEx. Я не знаю, что такое соответствующий API linux.


Я дал ответ кому-то с подобной цель и ссылается на вопрос, похожий на ваш.

Я думаю bmargulies' ответ к этому вопросу подходит то, что вам нужно, когда две идеи объединены.


Я не помню, чтобы такой API экспортировался. Почему вы хотите сделать такую вещь (в чем корень проблемы, которую вы решаете?)

возможно, вы захотите взглянуть на /proc / [pid] / smaps (который предоставляет несколько подробную статистику используемых/скопированных/сохраненных страниц).

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