Современная Операционная Система: Что Нужно Знать Разработчику



Александр Крижановский (англ.

НатСисЛаб.

)

Современная операционная система: что нужно знать разработчику

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



На мой взгляд, современная ОС — это плохо.



Современная операционная система: что нужно знать разработчику

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

на этой картинке видно, что на одном ядре с разной тактовой частотой до 3 ГГц Netmap позволяет 10 Гбит — 14 миллионов пакетов в секунду.

работают уже на 500 МГц.

Синяя линия — это pktgen — вообще самая быстрая вещь в ядре Linux. Это такая штука - генератор трафика, который берет один пакет и отправляет его адаптеру много раз, т.е.

ни копирования, ни создания новых пакетов, т.е.

вообще ничего - просто отправляя адаптеру один и тот же пакет. А так он сильно провисает по сравнению с Netmap (то, что делается в пользовательском пространстве, показано розовой линией), и вообще находится где-то там внизу.

Соответственно, люди, которые работают с очень быстрыми сетевыми приложениями, переходят на Netmap, Pdpdk, PF_RING — таких технологий сейчас очень много.

Второй момент касается баз данных.

Речь шла о сетях, теперь о базах данных.

Если кто-нибудь изучал базы данных, мы знаем и сегодня увидим, что вы можете использовать MMAP, вы можете использовать O_DIRECT. В больших базах данных — Postgres, MySQL, InnoDB — в основном используют O_DIRECT, а еще в 2007 году в LKML была дискуссия, и говорилось, что O_DIRECT — это грязный выбор, он неверен.

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

все, что делает операционная система на уровне виртуальной памяти, уровне файловой системы и т.д. Затем Linux сказал, что, пожалуйста, используйте madvise, чтобы вы могли работать.

Петр Зайцев спросил: «Как мы будем жить без O_DIRECT? Потому что нам нужно знать, когда и какую страницу сбросить».

Ответа не было, и мы продолжаем жить так же.

Следующий момент касается того, как мы программируем наши приложения.

Если вы пишете многопоточные сетевые демоны, вы обычно спрашиваете себя: «Что я буду использовать? Будут ли процессы, события, потоки, сколько потоков там можно запустить, как соединить конечные автоматы, чтобы что каждый поток обрабатывает множество событий, конечных автоматов и потоков или, может быть, использует сопрограммы, как это делает ErlangЭ» и т. д. Это довольно сложный вопрос.

Есть отличные статьи — «Почему события — плохая идея» (Роб фон Берен) и «SEDA: Архитектура для хорошо кондиционируемых, масштабируемых интернет-сервисов» (Мэтт Уэлш), которые полностью противоречат друг другу.

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

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

В общем, это достаточно геморройный вопрос, который нужно решать, и по сути либо мы используем какой-то стриминговый фреймворк, например Boost.Asio, либо делаем все сами и должны думать, что нужно .

Нам нужна производительность, нам нужно об этом думать.

Мы живем в мире, где у нас много распределителей памяти, т.е.

если мы пишем что-то простое, то используем malloc или new на C++ и не задумываемся о том, как мы это делаем и что происходит. Как только доходим до высоких нагрузок, начинаем перетаскивать jemalloc, hoard и другие готовые библиотеки, либо пишем свои распределители памяти — SLAB, пул и т.д. То есть.

если мы создаем сервер, сначала мы начинаем с простой программы, а затем переходим к повторной реализации ОС в пользовательском пространстве.



Современная операционная система: что нужно знать разработчику

Делаем пул буферов для быстрого ввода и вывода, делаем свои потоки, какие-то механизмы планирования, синхронизации, свои распределители памяти и т.д. С другой стороны, рабочий стол.

Вчера посмотрел, что у меня запущено - у меня было запущено 120 процессов, многие из них мне даже были неизвестны.

Я люблю легкие системы и в принципе понимаю, что мне подходит. Если я запущу KDE или GNOME, там будет море процессов — 200-300 из которых я понятия не имею, что они из себя представляют и что делают. Я тоже понятия не имею, какую нагрузку они оказывают на систему, да и мне, в общем-то, это не важно.

Пишу, собираю код, в общем меня не интересуют максимальные бенчмарки на десктопе, меня интересуют максимальные бенчмарки на моём сервере - где мне нужно ставить рекорды.

Во-вторых, десктопы сейчас достаточно маломощные, это какой-то обычный однопроцессорный, несколько ядер, немного памяти, никакого NUMA, т. е.

все очень просто, все очень маленькое.

И тем не менее, мы знаем, что на наших телефонах и на больших серверах работает Linux. Это не очень правильно.

Если посмотреть, как живет прогрессивный мир.

Может быть, если кто-то сталкивался с серьезным DDoS, то знает этот признак:

Современная операционная система: что нужно знать разработчику

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

Красным цветом показано то, что они удалили, и получили данные, что они могут снять 10 Гбит трафика с базового дешевого сервера.

Если вы просто запустите Linux, вы ничего не удалите.

Более того, эти люди делают довольно серьёзную фильтрацию DDoS на уровне приложений.

Они не только лезут в IP/TCP — что делает обычная ОС, но и парсят HTP, парсят, запускают какой-то классификатор, и умудряются разобрать один пакет за несколько тактов процессора — всё очень быстро.



Современная операционная система: что нужно знать разработчику

Если мы попытаемся ответить, почему мы живем с Linux, мы будем счастливы.

Где-то в 2000-е годы у Solaris были сильные позиции, шел спор, что Solaris старый и тяжелый, Linux быстрый и легкий, потому что маленький — это тоже можно найти в списках рассылки.

Теперь мы имеем то, что было тогда у Solaris — Linux очень большой, его код просто колоссальный, возможностей очень много, он умеет много всего, все, что вы хотите, вы можете сделать в нем.

Существуют и другие операционные системы, такие как OpenBSD, NetBSD, которые гораздо проще и менее используются, но Linux — очень мощная вещь.

Картинку выше я выдрал из Википедии — там Линукс попытались изобразить в каком-то разрезе.

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

Это очень сложный график.

На самом деле, если вы посмотрите на какой-то момент, например, на планирование ввода-вывода на уровне хранилища — синий и сине-зеленый — там показано не все.

На самом деле все сложнее, есть большие соединения, много разных очередей, много блокировок, и все это должно работать, и работает это все медленно.

Пройдемся по вертикальным столбцам, посмотрим на подсистемы Linux и что с ними можно сделать, где могут быть проблемы.

Частично с точки зрения администратора, частично с точки зрения разработчика кода приложения.



Современная операционная система: что нужно знать разработчику

Во-первых, работа с процессами и потоками.

В Linux имеется справедливый планировщик ввода-вывода, работающий за логарифмическое время.

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

Мне ни разу не приходилось лезть в сам планировщик с целью оптимизации, получения от него большей производительности, т. е.

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

Там все как-то спланировано и, наверное, хорошо спланировано.

Обычно проблемы возникают в другом месте.

Как я уже говорил, десктопы и серверы — это совершенно разные миры, т. е.

если у вас сервер с одним процессором, 8 или 16 ядрами, это один из вариантов.

Если у нас есть сервер с 4 процессорами по 10 ядер каждый, у нас в системе 40 ядер плюс гиперпоточность, у нас 8 потоков.

Сейчас процессоры стали более массовыми, параллельных контекстов стало ещё больше, и картина совсем другая.

Прежде всего, если говорить о потоках, процессах, событиях, то что бы мы ни выбрали — потоки, процессы или конечные автоматы — всё дело в вашем удобстве.

Если хотите писать быстро, возьмите какой-нибудь фреймворк и используйте его, либо Vent, либо Boost.Asio с кучей потоков, да что угодно.

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

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

Переключение контекста обходится дешево, если у вас есть системный вызов.

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

Есть небольшой размыв, но в целом дела у нас идут хорошо.

Если контексты вашего приложения начнут перепроектироваться, тогда дела пойдут очень плохо.

  • На современных процессорах Intel мы должны сделать недействительными кэши 1-го уровня, т.е.

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

    Старые кэши L2 и L3 просто вымываются.

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

    что сделал предыдущий, тот, что набрал в свои кэши.

    А когда происходит обратное переключение Контекста, когда первая программа снова получает управление, она по сути переключается на холодные кэши и начинает вытягивать данные из памяти, и у нас совершенно другой порядок времени работы.

  • Следующий.

    Чего нет на настольных компьютерах и наших смартфонах, так это NUMA. Современные X86, до этого были AMD, сейчас Intel, у них архитектура стала асимметричной.

    Дело в том, что планировщик нашей ОС — а процессоры у нас разные — это разные наши узлы.

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

    Наша память достигает информации по медленным каналам.

    К счастью, ОС знает топологию ядер, на которых она работает; оно старается этого не делать, но, тем не менее, иногда ему приходится это делать, в силу каких-то своих собственных соображений.

  • Дальше.

    Мы немного изменили механизм работы с общими данными.

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

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

    Если наши 80 ядер лопаются по одному байту, по 4 байта, то страдает наша шина данных, и все становится очень медленно, т. е.

    мы не можем использовать те механизмы синхронизации, которые использовали раньше.

Я настоятельно рекомендую прочитать «Что должен знать каждый программист о памяти» (Ульрих Дреппер) — отличная книга, она немного старая, но общие концепции очень хороши, она показывает, как она работает и что можно сделать.



Современная операционная система: что нужно знать разработчику

Первая картинка 2008 или 2009 года от AMD, когда всё стало распространяться.

Всего у нас два процессора.

Синие каналы — это каналы, по которым общаются два ядра, т. е.

здесь у нас 2 процессора с 2 ядрами.

У нас очень быстрая передача между двумя ядрами и очень медленная передача между разными процессорами.

Соответственно, если у нас ядро 0.3 — они работают с одним и тем же локом, с одним и тем же int, то они начинают гонять эти 4 байта через медленный канал памяти — это очень медленно.

Если у нас P0 и P1 работают с одними и теми же переменными int, то все происходит очень быстро.

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

Если здесь у нас плоская структура — у нас 2 процессора, быстрый и медленный каналы, то там уже трёхмерная структура, и не все ядра там со всеми общаются.

Например, P3 не общается с P4, ему необходимо сделать два прыжка.

Те.

если нам нужны данные в P3 из P4, мы сначала идем в P0, а потом в P0 идем в P4, т.е.

нам нужно использовать в нашей передаче другой процессор.

Современная архитектура Intel i7 — у нее очень серьезная шина, есть KPI. По сути, это инфраструктура, базовая связь и синхронизация данных.

Они построили свой TCP/IP, т.е.

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

Numactl показывает расстояния до узлов.

Это если смотреть по диагонали, это ядро само с собой - это какие-то гири, какие-то попугаи - у него самое дешевое.

Ему уже сложно перейти в какой-либо другой узел, т.е.

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

На самом деле это немного другое, но если мы работаем с NUMA, то проще предположить, что в локальном процессоре все быстро, а в чужом процессоре все медленно.



Современная операционная система: что нужно знать разработчику

Что мы можем сделать и что правильно сделать, чтобы код работал быстро? Во-первых, спин-блокировка, разные структуры данных, lock-free, ставшие очень популярными в 2010-х годах.

Различные lock-free очереди и прочее на больших машинах не работают. Недавно меня спросили, что «вот у нас 20 тысяч потоков, и их человек хотел написать быструю очередь, которая бы позволяла быстро вставлять и извлекать данные.

Так не получится, т. е.

все будет очень медленно.

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

Соответственно, если вы пишете код для NUMA-системы, вы думаете, что у вас внутри машины небольшой кластер, внутри одной аппаратной части, и вы строите свое программное обеспечение так, что у вас есть маленькая машина, один процессор, он многоядерный.

, там 8-10 -16 ядер, локально занимается своими делами и иногда общается с чужими.

И тогда, если вы объедините кластер машин, у вас будет еще более медленная связь.

Вы получаете такую иерархию кластеров — более быстрый кластер и более медленные кластеры.

Хорошим примером общих данных является то, что нехорошо использовать общие данные, когда C++shared_ptr, т. е.

у нас есть указатель на данные — это наши умные указатели, и у них есть счетчик ссылок, который меняется каждый раз, когда указатель копируется; соответственно, если вы пишете нагруженный сервер, то использовать Shared_ptrs нельзя.

Одна из причин, почему, например, boost donkey никогда не даст вам хорошей производительности на сервере, заключается в том, что он очень часто используетshared_ptr, умные указатели и другие высокоуровневые вещи, которые сильно замедляют действия, т. е.

этот высокоуровневый объектный код не рассчитывает на максимальную скорость на сыром оборудовании.

Дальше.

Ложное разделение.

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

Процессор оперирует 64 байтами — это одна строка кэша процессора.

Те.

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

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

Ваши разные ядра, сами того не желая, начинают бороться за одни и те же данные, хотя они должны работать параллельно.

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

Связывание процессов происходит именно с NUMA. Довольно старый тест. Была машина с 4 процессорами и 4 ядрами.

Во-первых, когда мы запускаем dd и отправляем его в nc, dd работает на одном ядре, nc — на другом.

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

Если мы строго пропишем, что оба процесса должны жить на одном процессоре, то передача сильно увеличится.

Те.

ОС не всегда делает хорошие вещи, она не всегда делает умные вещи с точки зрения планирования процессов.

Еще один момент — у нас разные сервера, т. е.

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

У нас есть сервера, на которых может быть запущен apache, nginx, MySQL, роутер или что-то еще, т. е.

есть такие сборные сборные солянки.

Но если мы строим высокопроизводительный кластер, если посмотреть, как строятся крупные компании, то, как правило, есть кластер, есть уровни, есть первый уровень — у нас есть, например, nginx на 10 машин.

, и на каждой машине только nginx и ОС.

Следующий уровень — Apache с какими-то скриптами, следующий уровень — MySQL, т.е.

получается, что одна машина — это одна высокоуровневая задача.

Это совершенно другой подход, и в этой ситуации наше приложение, nginx или apache может рассчитывать на то, что оно владеет всеми ресурсами, ему не нужна виртуализация, которую обеспечивает ОС — это виртуальная память, это изоляция адресные пространства, то есть каждая программа считает, что она работает с каким-то неизвестным количеством процессора.

Мы можем привязать, например, когда мы запускаем веб-сервер, мы запускаем воркеры либо равные количеству ядер, либо в два раза больше, т.е.

мы исходим из количества воркеров от количества ядер.

Это всего лишь пример, когда у нас есть одно приложение, которое полностью использует всю машину.



Современная операционная система: что нужно знать разработчику

О памяти.

Короче говоря, чем больше памяти вы используете, тем хуже вам становится.

Если вы посмотрите на картинку, то именно так работает наша виртуальная память.

У нас есть адрес (это строка вверху) - от 0 до 63 бит, и когда мы работаем в виртуальном адресном пространстве, и у нас есть оборудование с физическим адресным пространством, мы как всегда отображаем виртуальное адресное пространство.

к физическому.

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

Вот как работает виртуальная память.

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

Наша таблица страниц работает следующим образом — как показано.

Таблица страниц состоит из четырех уровней.

У нас есть первые четыре уровня, от которых отходят биты с 39-го по 47-й — это и есть страница.

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

Понятно, что на первом уровне старшие адреса в программе, как правило, совпадают. ОС при выделении памяти выбирает младшие адреса, максимально приближенные к тем, которые доступны, т.е.

мы получаем дерево.

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

получаем дерево с корнем на главной странице, а затем разветвляется.

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

Далее посмотрим, как все это расходуется и прежде всего потребление памяти.

Второй.

Допустим, мы создаем какое-то двоичное дерево и думаем, что у нас есть синие узлы и зеленые узлы, куда мы хотим перейти.

Для нас это дерево, для ОС это дерево внутри дерева, т.е.

когда мы идем по ключу а - с первого уровня на второй, у нас происходит переход по нашему дереву и мы полностью проходим четыре уровня дерево.

Теперь прыгаем со второго уровня на третий – на с, в следующий момент. Наше дерево снова разрешается, т. е.

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

К счастью, у нас есть TLB-кеш.

На самом деле это довольно небольшой кэш; всего там помещается около 1000 адресов, т.е.

адресуем таблицы перевода постранично.

Страницы 4 КБ, 1000 страниц — получаем 4 МБ, т.е.

4 МБ — это то, что мы закешировали в TLB. Как только ваше приложение выходит за рамки 4 МБ, вы начинаете бегать по дереву, а 4 МБ для современного приложения — это, вообще говоря, ничто.

И вы довольно часто сталкиваетесь с выходом из TLB-кеша, т.е.

чем больше памяти мы используем, тем сильнее падает производительность.



Современная операционная система: что нужно знать разработчику

О смещении страницы.

Я уже говорил, что наши базы данных это делают, и действительно, некоторые механизмы очень похожи в базах данных и в ОС, а Linux ведет двойной список LRU, т.е.

у нас есть активный список и неактивный.

Соответственно, страница, когда вы просто делаете выделение или что-то делаете со страницей, помещается в активный список.

Затем, через некоторое время, он вытесняется в неактивный список.

Демон kswapd, если сделать ps, то вы увидите kswapd — они там по одному на ядро, они как раз и занимаются этим переносом страниц с листа на лист. А когда страница второй раз полностью устаревает, она уже принудительно выводится из неактивного списка, а если это анонимная страница, например, какой-нибудь malloc, то она уходит в своп.

Если это сопоставленный файл, то страницу просто выкидываем, перейдем к сопоставленному файлу.

Помимо того, что у нас есть активный и неактивный список, наша страница может быть грязной и чистой.

Те.

если мы только читаем страницу, она чистая.

Если мы что-то записали, то оно грязное, пока не будет сброшено во вторичное хранилище — это либо своп, либо запись обратно в файл.

Каждый раз, когда мы записываем что-то в память, нам не хочется сбрасывать все это на диск.

Очевидно, это замедлит ход событий.

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

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

Соответственно, у нас есть процесс обратной записи, и файловые системы добавляют к этому процессу свою работу.

А обратная запись просто выгружает грязные страницы во вторичное хранилище.

У нас есть переменные sysctl. По sysctl я использую документацию внутри ядра, ядро у меня всегда под рукой, я смотрю там, а у ядра есть документация, где хорошо написано, что делает каждый sysctl. Соответственно,грязный_фон_{ratio,bytes} — это отправная точка отложенного сброса, т.е.

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

грязный_{ratio,bytes} — это когда наш процесс записывает данные и создает новую грязную страницу, и в этот момент он смотрит, сколько грязных страниц у нас уже есть.

Если у нас их много, то он сам их щас сбросит на вторичные хранилища, если мало, то оставит работать на обратную запись, т.е.

так мы пытаемся уйти от ситуации когда мы пишем очень интенсивно, но поток обратной записи не успевает все сбросить.

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

Cache_presure — это своего рода компромисс между тем, как мы передаем какие данные.

Те.

если это какой-то файловый сервер, то нам понадобится много индексных дескрипторов и множество авторов каталогов.

Если это просто база данных, отобразившая большую область памяти, то иноды нам не нужны.

Например, ngnix открывает файл и хранит его в своем кеше, каталоги там нам не особо нужны, иноды могут и нужны, но скорее всего нет, и ему важнее страничный кеш.



Современная операционная система: что нужно знать разработчику

О больших страницах.

Я говорю, что наши таблицы страниц работают со страницами по 4 КБ и, действительно, получается, что в TLB мы можем адресовать только 4 МБ — этого недостаточно.

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

Я, честно говоря, не знаю, как с ним работает процессор, но суть в том, что если выделять страницу, то у нас нет физической страницы размером 2 МБ или 1 ГБ.

Физически у нас осталось 4 КБ.

И когда вы выделяете большую страницу, ваша операционная система фактически ищет непрерывный блок размером 4 КБ, который и составит для вас эту большую страницу.

Эта большая страница будет иметь только одну запись в таблице страниц и одну запись в TLB. Беда здесь в том, что если наш TLB первого уровня для обычных страниц составляет около 1000 записей, то для больших страниц, вероятно, около 8 для 2 МБ и 1-2 для ГБ.

Исходя из этого, если у вас большая база данных, то большие страницы могут вам не подойти, а если вы пишете всего 1 байт, вы выделяете под этот байт 1 ГБ, и он вытягивается из файла, что в конечном итоге не приносит пользы.

ты.

Если вы держите какое-то очень интенсивное хранилище относительно небольших данных, т.е.

не потому, что вы поднимаете MySQL до 40 ГБ памяти, а если у вас небольшая база данных, буквально 1 ГБ, 2-4 МБ, которая очень интенсивно работает со структурами данных, тогда имеет смысл использовать огромные страницы.

Прелесть в том, что в Linux 2.6.38 появились прозрачные большие страницы.

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

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

Если он не сможет выделить, он вернется к 4 КБ, но сделает для этого все возможное.

Поддержку больших страниц можно найти на xdpyinfo.

Современная операционная система: что нужно знать разработчику

О дисковом вводе-выводе.

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

На фото показан нормальный режим работы.

Если мы делаем обычное открытие, делаем чтение, запись, то всё идёт через наш кеш, т.н.

страничный кеш.

Для любого текущего файлового ввода-вывода существуют страницы, в которые записываются данные, прочитанные из файла, записанные в файл, и они сбрасываются в файл с помощью kswapd, используя потоки обратной записи, или, наоборот, извлекаются из него.

Здесь мы получаем копирование.

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

делаем двойное копирование.

Это медленно, соответственно большие базы данных используют O_DIRECT I/O, мы обходим страничный кеш, говорим, что у нас есть своя большая область памяти, выделяем ее с помощью того же mmap и делаем прямой ввод-вывод с контроллера диска в эту область .

Второй подход — мы делаем mmap. Это значит, что если в случае с O_DIRECT мы сами делаем чтение или запись на определенные страницы и сами записываем их на диск, то в случае с mmap наша ОС каким-то образом что-то сбросит, мы не знаем когда и что она сбросит .

Соответственно, если у нас есть журнал транзакций, и мы говорим, что этот блок данных должен быть записан перед другим блоком, мы не можем этого сделать, мы можем это сделать только в случае O_DIRECT. Еще одна интересная вещь с mmap. Вообще говоря, у нас в X86 большое адресное пространство, есть соблазн, так как у нас есть виртуальная память, так как мы можем отобразить 128 ТБ памяти, но иметь всего 10 ГБ - это просто маппинг, маппинг большого количества файлов и как-то работать с их.

В частности мы попробовали сделать, так как мы знаем, что у нас страница 3, мы можем отобразить 128 ТБ, сделать один mmap, и эти 128 ТБ — это какие-то биты в нашем адресе.



Современная операционная система: что нужно знать разработчику

Допустим, будет несколько n бит, мы делаем mmap и получаем железное дерево, которое дает n Теги: #*nix #Системное администрирование #Администрирование сервера #Конфигурация Linux #highload #Александр Крижановский #операционная система

Вместе с данным постом часто просматривают: