Понимание неожиданных ошибок страниц в программе работы с памятью на Linux

Вопрос или проблема

Я написал небольшую программу на C, чтобы выделить большой участок памяти, коснуться одного байта на каждой странице размером 4 КиБ и напечатать статистику о страницах с ошибками. Цель — понять, как Linux обрабатывает выделение памяти и страницы с ошибками. Вот программа:

#include <stddef.h>
#include <stdio.h>
#include <sys/mman.h>
#include <sys/time.h>
#include <sys/resource.h>

static unsigned long
clamp(const long current_faults, const unsigned long touches)
{
  if (touches > (unsigned long) current_faults) {
    return 0;
  }
  return (unsigned long) current_faults - touches;
}

static void
print_4kib_page(const void * const address)
{
  const unsigned long number = (unsigned long) address;
  printf("%6lu|", number >> 48);
  printf("%4lu|", (number >> 39) & 0x1FF);
  printf("%4lu|", (number >> 30) & 0x1FF);
  printf("%4lu|", (number >> 21) & 0x1FF);
  printf("%4lu|", (number >> 12) & 0x1FF);
  printf("%5lu| ", number & 0xFFF);
}

int
main()
{
  const unsigned long page_size = 4096;
  const unsigned long page_number = 4096;
  const unsigned long bytes = page_size * page_number;

  char * const buffer = mmap(NULL, bytes, PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
  if (buffer == MAP_FAILED) {
    return 1;
  }

  struct rusage statistics;
  getrusage(RUSAGE_SELF, &statistics);
  const long start_faults = statistics.ru_minflt + statistics.ru_majflt;

  printf("                          Address Touches Faults Extra Faults\n");

  for (unsigned long byte = 0; byte < bytes; byte += page_size) {
    buffer[byte] = 15;

    getrusage(RUSAGE_SELF, &statistics);
    const long current_faults = statistics.ru_minflt + statistics.ru_majflt - start_faults;
    const unsigned long touches = byte / page_size + 1;
    
    print_4kib_page(buffer + byte);
    printf("%7lu%7ld%13ld\n", touches, current_faults, clamp(current_faults, touches));
  }
}

Вывод в моей системе (Ubuntu 22.04.5 LTS, 6.8.0-48-generic) выглядит следующим образом:

                          Address Touches Faults Extra Faults
     0| 254| 288| 422|   0|    0|       1      9            8
     0| 254| 288| 422|   1|    0|       2     13           11
     0| 254| 288| 422|   2|    0|       3     14           11
     0| 254| 288| 422|   3|    0|       4     15           11
     0| 254| 288| 422|   4|    0|       5     16           11
     0| 254| 288| 422|   5|    0|       6     17           11
     0| 254| 288| 422|   6|    0|       7     18           11
     0| 254| 288| 422|   7|    0|       8     19           11
     0| 254| 288| 422|   8|    0|       9     20           11
     0| 254| 288| 422|   9|    0|      10     21           11
     ...
     0| 254| 288| 429| 502|    0|    4087   4098           11
     0| 254| 288| 429| 503|    0|    4088   4099           11
     0| 254| 288| 429| 504|    0|    4089   4100           11
     0| 254| 288| 429| 505|    0|    4090   4101           11
     0| 254| 288| 429| 506|    0|    4091   4102           11
     0| 254| 288| 429| 507|    0|    4092   4103           11
     0| 254| 288| 429| 508|    0|    4093   4104           11
     0| 254| 288| 429| 509|    0|    4094   4105           11
     0| 254| 288| 429| 510|    0|    4095   4106           11
     0| 254| 288| 429| 511|    0|    4096   4107           11

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

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

Ответ или решение

Понимание Неожиданных Страницных Ошибок в Программе на Linux

В данной статье мы подробно рассмотрим поведение программы на языке C, предназначенной для выделения большой области памяти, осуществления доступа к одинокой байту в каждой странице памяти размером 4 Кбайта и сбора статистики по ошибкам страниц. Анализируя результат выполнения программы на вашем дистрибутиве Ubuntu 22.04.5 LTS, мы выявим причины неожиданных ошибок страниц.

Изучение Страницных Ошибок

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

Анализ Поведения Программы

Когда программа «прикасается» к памяти (буквально записывает значение в выделенную область), происходит ряд страницных ошибок, которые могут быть охарактеризованы как "минимальные" (minflt) и "максимальные" (majflt). Минимальные страницы ошибки указывают на доступ к невиртуальной (или еще не загруженной) памяти, тогда как максимальные страницы ошибки связаны с необходимости загрузки страницы с диска при недостатке физической памяти.

В вашей программе мы видим следующий ход событий:

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

  2. Второй доступ: На втором обращении количество дополнительных ошибок не просто увеличивается до 11, но также проявляет аномалию. Три дополнительных ошибки при первом доступе могут указывать на то, что OS потребовала дополнительные операции, связанные с иерархией таблицы страниц:

    • При первом обращении к памяти, возможно, возникли дополнительные операции по загрузке недоступных таблиц, которые требуются для правильной работы с выделенной памятью.
    • В результате этого второго доступа, возможно, произошли операции, связанные с перераспределением ресурсов или обработкой других структур данных, которые касаются управления памятью.
  3. После второго доступа: Статистика по дальнейшим доступам показывает, что число дополнительных ошибок стабилизировалось на уровне 11. Это может говорить о том, что система оптимизировала выполнение операций в предшествующей памяти, что исправило логику обработки и уменьшило требования к страницным ошибкам.

Заключение

Выводы, полученные на основе вышеизложенного анализа, подчеркивают важность понимания отзывчивости операционной системы к загрузке и доступу к памяти. Неожиданные страницы ошибки во время работы программы указывают на глубокую и сложную работу механизма управления памятью, встроенного в Linux. Это является предметом активных исследований и оптимизаций, и знание этих нюансов обязательно помогает разработчикам создавать более эффективные и производительные приложения.

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

Оцените материал
Добавить комментарий

Капча загружается...