Я никогда не понимал как работает распределение памяти во FreeBSD. Из всего многообразия документации полезное помнилось, лишь

An urban myth has circulated for years that Linux did a better job avoiding swapouts than FreeBSD, but this in fact is not true. What was actually occurring was that FreeBSD was proactively paging out unused pages in order to make room for more disk cache while Linux was keeping unused pages in core and leaving less memory available for cache and process pages.


Ну лучше чем Linux, да и пусть. Я не против. Но хуже самого непонимая процесса выделения памяти меня убивала Inactive память. Что это такое и можно ли «это» безболезненно использовать? Считать ли эту память доступной для использования приложением?

Под cut'ом больше вопросов чем ответов.

FAQ FreeBSD сообщает, что
16.2.

Why does top show very little free memory even when I have very few programs running?

The simple answer is that free memory is wasted memory. Any memory that programs do not actively allocate is used within the FreeBSD kernel as disk cache. The values shown by top(1) labeled as Inact, Cache, and Buf are all cached data at different aging levels. This cached data means the system does not have to access a slow disk again for data it has accessed recently, thus increasing overall performance. In general, a low value shown for Free memory in top(1) is good, provided it is not very low.


Хорошо, пусть это кеш какого-то уровня, но почему не поместить эту Inact память в Cache? Может быть потому, что она доступна для использования (как утверждают многочисленные форумчане) и пусть не моментально, но может быть выделена по запросу?

Попытаемся выяснить это практическим путём. Имеем:
# top -b 0
last pid:  1019;  load averages:  0.21,  0.45,  0.24  up 0+00:03:33    14:26:30
28 processes:  1 running, 27 sleeping

Mem: 18M Active, 17M Inact, 130M Wired, 24M Buf, 3756M Free
Swap: 3852M Total, 3852M Free

То есть почти вся память Free и своп полностью свободен.
Теперь, чтобы задействовать доступную память, создадим tmpfs раздел.

# mkdir /tmp/gb
# mount -t tmpfs -o mode=01777,size=3221225472 tmpfs /tmp/gb
# df -h | egrep "(Filesystem|tmpfs)"
Filesystem     Size    Used   Avail Capacity  Mounted on
tmpfs          3.0G    4.0K    3.0G     0%    /tmp/gb


При этом
# top -b 0
last pid:  1028;  load averages:  0.09,  0.19,  0.17  up 0+00:09:30    14:32:27
28 processes:  1 running, 27 sleeping

Mem: 18M Active, 17M Inact, 130M Wired, 24M Buf, 3756M Free
Swap: 3852M Total, 3852M Free

Согласен, раз раздел просто создан/примонтирован, но свободен, то незачем выделять ему память.
Поместим в него файл.
# dd if=/dev/urandom of=/tmp/gb/file.txt bs=1M count=3k
3072+0 records in
3071+0 records out
3220176896 bytes transferred in 53.334672 secs (60376801 bytes/sec)
# df -h | egrep "(Filesystem|tmpfs)"
Filesystem     Size    Used   Avail Capacity  Mounted on
tmpfs          3.0G    3.0G    1.0M   100%    /tmp/gb

3 гигабайта памяти занято, но при этом
 # top -b 0
last pid:  1040;  load averages:  0.19,  0.26,  0.20  up 0+00:16:40    14:39:37
28 processes:  1 running, 27 sleeping

Mem: 18M Active, 3088M Inact, 137M Wired, 24M Buf, 677M Free
Swap: 3852M Total, 3852M Free

они почему-то считаются Inact. Но раз она не Active, то попытаемся её задествовать. Набросаем небольшой «Hello, world!» для выделения памяти и её последующего освобождения:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
int main (int argc, char *argv[])
{
        int i;
        char *buffer[64];
        long lSize = 1024*1024*1024;
        int iMB = argc < 2 ? 1 : atoi(argv[1]);
        printf("iMB:\t%d\n", iMB);

        for(i=0; i < iMB; i++)
        {
                buffer[i] = (char*) malloc (lSize);
                if(buffer[i] != NULL)
                {
                        printf("Alloc: %d\n", i);
                        memset(buffer[i], 127, lSize);
                } else printf("Error!\n");
                sleep(1);
        }
        sleep(10);
        for(i=0; i < iMB; i++)
        {
                printf("Free: %d\n", i);
                free (buffer[i]);
        }
        return 0;
}


# time ./a.out 3
iMB:    3
Alloc: 0
Alloc: 1
Alloc: 2
Free: 0
Free: 1
Free: 2
0.915u 1.475s 1:00.16 3.9%      5+168k 0+0io 0pf+0w

Почему так долго? Вероятно наш tmpfs, якобы находящийся в Inactive, выдавливался в своп.

Дествительно, при выделении памяти (момент «sleep(10);» в коде) видим:
# top -b 0
last pid:  1128;  load averages:  0.02,  0.11,  0.14  up 0+00:28:34    14:51:31
37 processes:  1 running, 36 sleeping

Mem: 3106M Active, 621M Inact, 155M Wired, 26M Cache, 27M Buf, 14M Free
Swap: 3852M Total, 2502M Used, 1350M Free, 64% Inuse

Но хуже другое. После освобождения памяти приложением:
# top -b 0
last pid:  1129;  load averages:  0.09,  0.12,  0.15  up 0+00:28:48    14:51:45
36 processes:  1 running, 35 sleeping

Mem: 33M Active, 621M Inact, 145M Wired, 26M Cache, 27M Buf, 3095M Free
Swap: 3852M Total, 2502M Used, 1350M Free, 64% Inuse

своп остался задействован.
Обращения к файлу вновь вернули память в Inact
# time dd of=/dev/zero if=/tmp/gb/file.txt bs=1M count=3k
3071+0 records in
3071+0 records out
3220176896 bytes transferred in 40.265654 secs (79973292 bytes/sec)
0.008u 3.796s 0:40.26 9.4%      22+154k 0+0io 0pf+0w
# time dd of=/dev/zero if=/tmp/gb/file.txt bs=1M count=3k
3071+0 records in
3071+0 records out
3220176896 bytes transferred in 1.242623 secs (2591434941 bytes/sec)
0.000u 1.241s 0:01.24 100.0%    25+173k 0+0io 0pf+0w
# top -b 0
last pid:  1144;  load averages:  0.09,  0.12,  0.14  up 0+00:36:22    14:59:19
36 processes:  1 running, 35 sleeping

Mem: 29M Active, 3077M Inact, 146M Wired, 4K Cache, 27M Buf, 669M Free
Swap: 3852M Total, 2502M Used, 1350M Free, 64% Inuse

При этом непонятно почему остался Swap: 2502M Used

Но, допустим, это нормально и при наличии свободного свопа можно считать эту задействанную память неактивной и помечать её как Inact. Что же будет, если у нас нет свопа? Надеюсь, теперь задействованная память теперь будет Active.
Убираем своп, аналогично монтируем 3Gb раздел tmpfs и заполняем его.
# top -b 0
last pid:  1013;  load averages:  0.58,  0.53,  0.29  up 0+00:05:03    15:11:46
34 processes:  1 running, 33 sleeping

Mem: 21M Active, 3089M Inact, 138M Wired, 24M Buf, 673M Free
Swap:

Для очистки совести убедимся что top не врёт:
# expr `sysctl -n vm.stats.vm.v_inactive_count` \* `sysctl -n vm.stats.vm.v_page_size`
3239620608

И, несмотря на отсутствие свопа, наша занятая память по прежнему не совсем активна… Раз она не активна попробуем её вновь занять нашим приложением.
# ./a.out 3
iMB:    3
Alloc: 0
Killed

Ч.т.д. и, наконец:
# top -b 0
last pid:  1026;  load averages:  0.15,  0.22,  0.21  up 0+00:11:37    15:18:20
34 processes:  1 running, 33 sleeping

Mem: 3102M Active, 1524K Inact, 138M Wired, 200K Cache, 24M Buf, 679M Free
Swap:

# expr `sysctl -n vm.stats.vm.v_inactive_count` \* `sysctl -n vm.stats.vm.v_page_size`
1720320


Здесь, наверное, должен быть какой-то вывод, но его нет…

Комментарии (19)


  1. zuborg
    26.01.2016 17:41
    +3

    Может быть потому, что она доступна для использования (как утверждают многочисленные форумчане) и пусть не моментально, но может быть выделена по запросу
    — ошибка находится в этом месте.

    Документация FreeBSD говорит нам, что немедленному освобождению допускается только cache страницы, а не inactive.

    Active,Inactive,Cache,Free — грубо говоря, это 4 пула страниц, осортированные по частоте использования. OS перемещает страницы из пула в пул в зависимости от того, насколько активно они используются.
    Пул Free поддерживается ненулевым, чтобы во время срочного выделения страницы (например, во время прерывания) не возникло отказа или паузы.
    Cache страницы имеют дисковое отображение — это либо копия страницы из файла, либо имеет копию в свопе — в обоих случаях её можно немедленно освободить, а если понадобится — прочитаем с диска.
    Active и Inactive соотв — часто (активно) и нечасто используемые страницы. Они могут как иметь дисковое отображение, так могут и не иметь (в этом случае при нехватке памяти они вытесняются в своп)


    1. simpleadmin
      26.01.2016 18:32
      +1

      К сожалению и это не отвечает на вопрос «сколько в данный момент может быть доступно памяти приложению?»

      # top -b 0 | grep Mem
      Mem: 1925M Active, 53G Inact, 2901M Wired, 431M Cache, 3310M Buf, 4267M Free
      

      Выделяю 100M блоками пока не произойдёт исключение и освобождаю.
      # top -b 0 | grep Mem
      Mem: 1832M Active, 1398M Inact, 3091M Wired, 431M Cache, 1949M Buf, 56G Free
      

      В итоге Cache вообще остался нетронут, Inact похудел на 52G. Но как выяснить, что эти 52G были доступны?


  1. Klukonin
    26.01.2016 18:23
    +5

    Хорошая статья, но… Эм…
    Шапокляк меня немного напрягает…


  1. lexore
    26.01.2016 19:15

    Мне кажется, это проблема tmpfs.
    Я бы рекомендовал попробовать повторить эксперимент, но вместо tmpfs написать программу, которая захавает память и останется работать.

    При этом непонятно почему остался Swap: 2502M Used
    По личному опыту (и смутной памяти документации), счетчик used у свопа работает по принципу «только прибавляем».
    Т.е. swap used показывает, на сколько своп был заюзан вообще с момента примонтирования свопа.
    Отчасти на это намекает «used», а не «in use».
    Причем, как в linux, так и во freebsd.


    1. simpleadmin
      26.01.2016 21:55

      счетчик used у свопа работает по принципу «только прибавляем».

      [19:32]# swapinfo
      Device 1K-blocks Used Avail Capacity
      /dev/ada0b 5022720 311000 4711720 6%
      [21:30]# swapinfo
      Device 1K-blocks Used Avail Capacity
      /dev/ada0b 5022720 309288 4713432 6%
      т.е. работает и на освобожение.
      Другой вопрос зачем он вообще используется при 13G Free (и с момента старта системы Free ниже 12 не опускалась).
      Mem: 20M Active, 244M Inact, 2206M Wired, 10M Cache, 1643M Buf, 13G Free
      Swap: 4905M Total, 303M Used, 4602M Free, 6% Inuse

      Но вопрос свопа второстепенен.

      Мне кажется, это проблема tmpfs.

      Нет, проблемы проявляются и без него. Я задействовал tmpfs просто для наглядности.


  1. simpleadmin
    26.01.2016 21:54

    del


  1. amarao
    27.01.2016 00:01
    +2

    Во-первых, в линуксе есть vm.swappiness, указывающий степень свопливости линукса — выше величина, сильнее упреждающий своппинг. 0 отключает.

    Во-вторых, подсчёт свободной памяти на linux'е — катастрофа. Предсказать, когда именно придёт oom почти невозможно, ибо free уже нет давно, а какую часть cached/buffers можно выкинуть даже само ядро толком не знает (tmpfs жрёт buffers, но выкинуть из памяти нельзя).

    Видимо, у freebsd с этим не легче. Очень хочется, чтобы кто-то научился считать реальную свободную память.

    А ещё очень хочется научить ограничивать paging. Потому что одному «свободная память потерянная память», а другому — просранная оперативка виртуалок, которые дважды кешируют то, что хост уже и так закешировал и отдаст им бысто.


    1. pansa
      27.01.2016 00:59

      Ну если есть какая-нибудь возможность определить какие именно файлы и когда не нужно кэшировать, то может поможет fadvise?


    1. grafmishurov
      27.01.2016 03:07

      QEMU also supports a wide variety of caching modes. If you're using raw volumes or partitions, it is best to avoid the cache completely, which reduces data copies and bus traffic


      1. amarao
        27.01.2016 13:58

        Я бы предпочёл иметь ситуацию, когда объём кеширования управляется хостом, а не гостями. Сейчас картинка такая: у гостя 64Гб, реальных данных в памяти на гигабайт, 63 гигабайта протухшего кеша которым никто не будет пользоваться. У другого гостя два гигабайта памяти и из них полтора занято данными. На выходе явная диспропорция по объёму IO с дисков (второй гость делает тяжёлое IO снова и снова). Если бы кеш был на хосте, то нагрузка была бы распределена более равномерно.


        1. zuborg
          27.01.2016 16:08

          Чтобы кеш был на хосте, нужно чтобы FS была на хосте. Если хост не видет, с какого места на диске поднята страница, он не имеет права её освобождать.
          Вообще FreeBSD ведет подсчет интенсивности страниц (Linux, скорее всего, тоже). В Вашем случае можно второму гостю выделить тоже 64Г, и на хосте поставить своп (лучше на SSD) на пару сот гиг. ОС определит какие страницы не используются и положит их в своп.


          1. amarao
            27.01.2016 19:24

            Ну, общепринятым является хранить образа на файловой системе хоста. Кроме специфичных случаев с SAN/общими блочными устройствами, это де-факто стандарт.

            Проблема в другом: гость не видит нагрузки на хост и не может адекватно реагировать на «больше кеша, меньше кеша».


            1. zuborg
              27.01.2016 20:22

              Каюсь, поспешил. Хотя хост не может непосредственно знать, какая страница в виртуалке была поднята с какого места на диске (разве что ядро гостя об этом сообщит хосту), но он может самостоятельно кешировать страницы диска. Т.е. в Вашем случае нужно ограничить память виртуалок до необходимого приложениям объема, не включая дисковый кеш, и позволить хосту заниматься кешированием диска. Не самый идеальный вариант, строго говоря (двойное кеширование, гости под прессингом нехватки памяти могут заниматься лишним свопингом..), но зато память будет более эффективно использоваться.


              1. amarao
                27.01.2016 20:34

                В современных линуксах невозможно определить «необходимый объём». Потому что придёт OOM killer и всем станет понятно, что необходимый объём был другим. Полагаться на vss нельзя (он бывает кратно больше нужного объёма), отключать оверкоммит нельзя.

                Увы.


  1. Ivan_83
    27.01.2016 00:59

    Я люблю фрю, но вот с памятью там, ИМХО, хреновато, при отключённом свопе.

    Например.
    1. Есть rtorrent и он качает нечто что ощутимо больше размера оперативы. Если качает слишком быстро то начинается OMM.
    Однажды меня это сильно достало и я накидал багрепорт: https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=195882
    но не простой, а с мелкой программкой которая демонстрирует наступление OMM при отключённом свопе, при этом сама прога вообще память не потребляет. Ядро держит память ровно до того момента пока ты не прочитаешь то что она записала в файл, либо файл удалишь.
    Закрытие файла, всякие флюши не помогают совсем.

    2. VirtualBox.
    Очень любит почему то сжирать память при интенсивных дисковых в/в, даже если кеш отключен, и тоже приходит OOM.

    Аркадий говорит что так и должно быть с отключённым свопом, я с ним не согласен: не должно и свпо мне нафик не сдался. Но я не разработчик фрёвой VM и чес говоря нихрена там не понимаю как оно устроено, так что даже приватного патча к ядру для себя любимого я не осилил. :(

    По моим наблюдениям есть ещё некоторая память ядра, которая в top не отображается, а может нужно лучше смотреть в vmstat -z :)


  1. grafmishurov
    27.01.2016 03:36

    Пользовательская документация, мне так кажется, не поможет в таком болезненном для ядра вопросе. Алгоритмов несколько, в Линуксе зависит еще от аллокатора, выбранного при настройке исходников ядра, несколько уровней зонирования, buddy system, slab, помимо pages на page frames, необходимо, чтобы память сильно не фрагментировалась, многое хранится в ней до самого запроса на освобождение и т.д. Лучше сразу исходники читать и комментарии непосредственно к коду ядра. Таким методом интроспекции (top и malloc) вряд ли можно даже на интуитивном уровне догадаться, что внутри происходит.


  1. robert_ayrapetyan
    27.01.2016 04:22
    +1

    Как будто в каких-то других ОС можно адекватно понять что к чему с памятью (в той же Windows всегда занято 95% физической памяти), поэтому это не «амнезия FreeBSD», а более фундаментальная проблема. Тяжело учесть и грамотно показать все распределение памяти в современной ОС. А если сюда добавить еще shared memory (некоторые подвиды которой вы вообще нигде не увидите никакими средствами в FreeBSD (показывает только System V)), то вообще печально.

    В фре пользуюсь htop, который работает через прослойку linux procfs, которая дополнительно вносит свою лепту в вычисления, но понять самое важное всегда можно. В списке процессов в колонке RES всегда значение, которому можно верить. По наблюдениям, когда сумма чисел всех процессов в RES приближается к физическому размеру памяти, начинает расти swap. Который потом уменьшается по странным законам (а иногда и не уменьшается вовсе, как вы и описали).


    1. grafmishurov
      27.01.2016 11:50
      +1

      Менеджмент памяти ядром — это тема, скажем, примерно на 700 страниц, тут в двух словах не пересказать, не то, что «аршином измерить». Погуглите слова «linux kernel memory management pdf», если на kernel(dot)org отправит, то это то, меня, к сожалению, местные крепкие ребята лишили возможности постить ссылки (чем-то не понравилась, возможно резкостью выражений, моя приязнь к С и неприязнь к С++ в одном из тредов Интела). Не факт, что суммирование процессами занятой памяти даст адекватную картину, но это, мне кажется, ближе к теме. Ну и сама по себе задача измерить доступную память очень зыбка, даже Гугл не решается ее измерять перед компиляцией Хромиума в Генте, оценивает свободное место на диске и общее количество оперативки.


    1. Zelgadis
      29.01.2016 01:53

      htop насверное самый не аккуратный способ посмотреть память на freebsd. Начиная от того, что ZFS ARC пропадает из всех вычислений, заканчивая что cpu load тоже верить нельзя.

      У меня swap растет при «свободных» 15 гигабайтах памяти. После пары часов работы, из 32 остается «свободных» в районе 5, и только потому, что на диске занимаего простраства меньше чем оперативной памяти. Hit rate в ARC так совсем до 98%. Tmpfs руками нигде не используется. Не жалуюсь, но от нормального htop'a не отказался бы.