среда, 10 июня 2015 г.

Устраняем тормоза системы при операциях ввода-вывода, или вспомним о #12309

Баг #12309 - самый знаменитый баг в ядре Linux, довольно долго досаждавший пользователям Linux на десктопах. Сам баг исправлен в ядре Linux 3.3, но симптомы, похожие на таковые при 12309, могут проявляться на некоторых конфигурациях до сих пор. В сети можно найти много инструкций по лечению этих симптомов. Приведу в пример статью с сайта linux.org.ru, оригинал которой можно найти по ссылке с некоторыми дополнениями.



На самом деле 12309 — это не один, а несколько багов, смешанных в кучу. Можно выделить следующие случаи появления:

    при копировании больших объемов данных с диска на диск (или с раздела на раздел одного диска);
    при нехватке ОЗУ (и, соответственно, диком своппинге);
    при копировании на USB-девайсы;
    при использовании зашифрованных разделов;

Соответственно, фиксы тоже будут разные.


Сначала тест: восприимчива ли ваша система к 12309? введите в терминале:

dd if=/dev/zero of=/tmp/test bs=1M count=1M


и понаблюдайте за отзывчивостью системы. Если всё по-прежнему быстро - то читать статью можно разве что для профилактики и расширения кругозора.


Оптимистическое выделение памяти


Возможно, в научных программах какого-нибудь толка позволить выделить терабайт ОЗУ при наличии 3 Гб физической памяти и считается приемлемым, но на десктопе, где много процессов должны спокойно сосуществовать, такой расклад неприемлем — зажравшаяся программа спокойно вытеснит все остальное, после чего система практически остановится. Хуже всего то, что суть бага 12309 в том, что ядро принимает решения о том, какие страницы вытеснять, мягко говоря, неоптимально, а чинить это долго, муторно, и не в каждой ситуации решение будет приемлемым.

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

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

Для этого нужно прописать в /etc/sysctl.conf

vm.overcommit_memory = 2

Максимум памяти, который можно будет выделить, будет равен в сумме
объему свопа + некоторому проценту физической памяти. Этот процент по
умолчанию равен 50, но можно его несколько увеличить. Во всяком случае, я
выставил его в 80 и пока что катастроф нет.

vm.overcommit_ratio = 80



Подкачка нужна


Некоторые люди полагают, что если отключить своп, то 12309 исчезнет. А вот как бы не так. Своп (swap) — это хранилище анонимных страниц памяти. Код исполняемых программ и всяких библиотек не анонимен и по умолчанию не изменяем. В то время как на 32-битных системах исполняемый код зачастую зависим от позиции (начального адреса), что приводит к тому, что, во-первых, динамический линковщик проводит вычисление смещений каждый раз при загрузке и, соответственно, страницы кода анонимны (это несет с собой недостаток в виде наличия нескольких копий одной и той же библиотеки, но и преимущество в виде невозможности вытеснить страницы кода для освобождения памяти), то на 64-битных системах практически весь код линкуется в независимом от позиции виде (PIC).

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

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


Уменьшение размеров дисковых буферов


С одной стороны, отдавать под дисковые буферы практически всю свободную память — здравая идея. А с другой стороны, чем больше ОЗУ, на самом деле, тем сильнее это способно ударить в критической ситуации.

Это работает вот как. У ядра есть буфер файловой системы. Мы пишем много данных. Этот буфер заполняется грязными страницами, а потом выполняется системный вызов sync() и буфер сбрасывается на носитель. Чем больше буфер, тем больше данных надо будет сбрасывать. Все бы ничего, да вот когда кому-то вдруг вздумается выделить себе памяти, в первую очередь будут сбрасываться все эти буферы, и если при этом вдруг надо будет закачать страницы с исполняемым кодом, им опять-таки придется ждать в очереди. Опять слайдшоу, с возможной цепной реакцией.

То есть, кеш на чтение — это ничего так, а слишком большой кеш на запись способен встать поперек горла в критических случаях.

Есть еще одна неприятная особенность, связанная трудно сказать, с чем — возможно, с реализацией DMA, но вполне возможно, что не с ней, или не только с ней. Берем какой-нибудь медленный для записи носитель, типа той же USB-флешки, и пробуем записать на него данных побольше, фильм какой или что-то навроде. Мы увидим, что происходит это рывками — сначала заполняется буфер, сколько влезет, а потом весь сбрасывается, потом весь заполняется... и так далее. При этом суммарно потраченное время почему-то ощутимо больше, чем как если бы мы примонтировали носитель с -o sync, а скорость записи на, собственно, носитель невообразимо мала.

Но если уменьшить порог количества грязных блоков, после которого начнется их сброс на носитель, не до сверхмалых величин, но все же — это позволит проводить зачитку данных из источника и запись на носитель параллельными DMA-трансферами. Я у себя выставил этот объем равным 2 мегабайтам, что, с одной стороны, уменьшает количество перезаписей в случае частой смены маленьких файлов и значительно увеличивает скорость переноса больших объёмов данных. Возможно, если поиграться размером, можно найти оптимальное быстродействие, но не думаю, что буфер больше 16 мегабайт будет эффективным.

echo 2097152 >/proc/sys/vm/dirty_bytes
echo 2097152 >/proc/sys/vm/dirty_background_bytes

Для сохранения после перезагрузки, прописать в /etc/sysctl.conf

vm.dirty_bytes = 2097152

vm.dirty_background_bytes = 2097152

Стоит учесть, что кеши чтения файловой системы будут все так же занимать почти все свободное ОЗУ, но при этом запись будет осуществляться, как только блоков, помеченных на запись, наберется на 2 мегабайта.

Значение dirty_bytes должно делиться на 4096 нацело.

В результате, даже при переваривании больших объемов данных, система не заикается. Может затормозить сам процесс, который выделяет память, но отзывчивость системы не теряется.


Кеш файловых систем


Иногда бывает такая проблема: у вас относительно новое ядро, тесты с dd проходят на ура, а вот когда надо много маленьких файликов создавать/менять/убивать, например, командой dpkg, система встает колом и не может даже регистрами пошевелить.

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

vm.vfs_cache_pressure = 50

По умолчанию оно почему-то 100, что просто безумно на десктопных нагрузках.

Прочие решения:

    Установить параметр swappiness равным 10 или 5, чтобы подкачка задействовалась только при исчерпании 90 и 95% памяти соответственно:

    sudo nano /etc/sysctl.conf

    Добавить в конец vm.swappiness = 10

    Сохранить и выполнить sudo sysctl -p

    Сменить планировщик ввода-вывода. В такой ситуации рекомендуют использовать BFQ, но он не в основной ветке ядра, и его нужно добавлять в ядро

    самому (в некоторых дистрибутивах, например Calculate Linux, BFQ по умолчанию). В остальных же случаях можно сменить планировщик на лету.

    Перевесить системные прерывания на одно ядро (на многоядерном процессоре) скриптом:

    #!/bin/sh

    for interruption in `grep usb /proc/interrupts | awk '{print $1}'| sed 's/\://g'` ; do

      echo 1 > /proc/irq/${interruption}/smp_affinity;

    done

    При самостоятельной сборке ядра, задействовать 100Hz таймер ядра и опцию No Force Preemption (Server) mode.

    Выставить приоритет ionice для ядра 1 (realtime) для пространства пользователя (userspace) - 3.

    Установить ядро с патчами для реализации режима реального времени (linux-lowlatency, есть по умолчанию в репозиториях

    большинства дистрибутивов).

Подводя итог, можно отметить: самого бага давно нет, но на некоторых конфигурациях могут возникнуть его симптомы, по совершенно

разным причинам, от нехватки памяти до аппаратных проблем с дисками. Пугаться этого не стоит, ибо например в Windows, эта проблема

ещё более серьёзна, а самое главное - никак не решаемая. Система может с лёгкостью встать колом при копировании больших объёмов данных,

и её придётся перезагружать кнопкой Reset. Неоднократно с этим сталкивался. А вот симптомы 12309 удалось увидеть лишь при копировании фильма с

NTFS-раздела на старую, потрёпанную жизнью, флешку. И то потом проблему так и не удалось воспроизвести, потому списал на случайность.


Дополнительный материал:

Обзор планировщиков ввода-вывода

Статья о решении проблем с 12309 (на английском).



13 комментариев:

  1. Анонимный1 июля 2016 г., 20:13

    Aleksey если это "Блог начинающего линуксоида"-то и писать нужно соответственно: с подробной последовательностью выполнения команд, с скриншётами... Тогда и начинающий линуксоид будет благодарен. И проблему с запятой-решай быстрее (ставишь не в попад-особенно перед и)

    ОтветитьУдалить
    Ответы
    1. Спасибо за замечание. Приму к сведению.

      Удалить
  2. Насчет такой же проблемы в Windows. У меня эта проблема сильно проявляется в Ubuntu 16.04 (и ниже и выше) ВСЕГДА на моем слабом ноуте с 4 гб памяти. Но в Windows 7 все работает очень быстро и никогда такого не видел. Сама проблема закличается в загрузке 80% CPU при копировании больших файлов.

    ОтветитьУдалить
    Ответы
    1. Это всё зависит от железа, в первую очередь - от диска. Поэтому если проблема есть - нужно её исправить описанными способами. В Винде так сделать нельзя, потому если не повезёт, и эта проблема там появится - только терпеть и шёпотом материться :) Кстати в Windows 10 такая же фигня присутствует.

      Удалить
    2. Так если проц загружается на 80%, - это не 12309

      Удалить
    3. Он на 80% загружается при копировании в дистрибутивах на ядре linux (разные версии Ubuntu). И происходит это на двух совершенно разных ноутбуках у которых даже производители процессоров разные: Intel, Amd. Причем один из этих ноутов достаточно мощный. 80% - это примерно, близко к пику. Даже мышка начинает медленно двигаться.
      Если это не 12309, то я без понятия что это, т.к. по описаниям подходит только он.

      Удалить
    4. Забыл добавить. После патча BFQ (вернее сборки патчей pf-kernel) баг исчез.

      Удалить
    5. При 12309 процессор не нагружается и на 15%. Проблема не в этом.

      Удалить
    6. Ставьте ядро 4.10, там исправили косяки с копированием на медленные флешки и прочие внешние носители.

      Удалить
    7. Ставил 4.10 :) Ничего не исправлено! Как были загрузки процессора, так и есть. Повторюсь, это на двух совершенно разных ноутбуках. Если бы было только на одном, я бы подумал что какой-то частный случай. Дистрибутивы Ubuntu я не настраивал как-то по особому, так что не из-за моей самодеятельности.
      И повторюсь, BFQ всё исправил или один из патчей pf-kernel сборки.

      Удалить
    8. Ну значит это не 12309, при нём процессор не грузится.

      Удалить
  3. Ну вот и вышло ядро 4.12. По крайней мере с репозитория Ubuntu его можно установить через обычный deb пакет. А потом подгрузить модуль BFQ, добавить поддержку BFQ в настройки GRUB и правила в udev.
    И вуаля! У нас все ОК.

    ОтветитьУдалить
  4. Есть проблема, при заполнении оперативки на более чем 90% система встаёт чуть ли не колом теряя отзывчивость. Может так тупить сколько угодно, убиваю самый прожорливый процесс, всё приходит в норму, смотрю заполненность Swap там от силы 200 Mb.

    У меня 8Гб RAM, на SDD под Swap выделено 2Гб.

    Включен BFQ
    vm.swappiness = 10
    vm.vfs_cache_pressure = 50

    В чём проблема и какие меры сработают в моём случае?

    ОтветитьУдалить