Не за горами выход новой версии ядра Linux 5.14. За последние несколько лет это обновление ядра является самым многообещающим и одно из самых крупных. Была улучшена производительность, исправлены ошибки, добавлен новый функционал. Одной из новых функций ядра стал Core Scheduling, которому посвящена наша статья. Это нововведение горячо обсуждали в интернете последние несколько лет, и наконец-то оно было принято в ядро Linux 5.14.
Если вы работает с Linux или занимаетесь информационной безопасностью, вам интересны новые технологии, то добро пожаловать под кат.
▍Введение
Для того чтобы понять Core Scheduling и для чего он нужен, стоит разобраться, как работают многозадачные системы, и как они развивались.
На современных компьютерах одновременно могут работать сотни, а то и тысячи программ одновременно. Монопольный доступ к процессору и памяти остались в прошлом, и на данный момент практически нигде не используется кроме микроконтроллеров и RT(Real Time) задач.
Изначально все процессоры были одноядерными, и могли выполнять только одну задачу одновременно. Практически сразу появилась необходимость выполнять несколько задач одновременно на одноядерном процессоре. Для этого был придуман scheduler, он же планировщик в операционных системах, который занимается переключением задач и управляет ресурсами компьютера. Ядро операционной системы всегда работает с большими привилегиями, чем пользовательские программы, такой режим работы называют Ring 0. Поэтому планировщик может в любой момент приостанавливать выполнение одной задачи и перейти к выполнению следующей. В нём существует специальная очередь задач, в которую добавляются все работающие программы и обработчики ядра, например, обработчик прерываний. Изначально эта очередь была простая, как очередь в магазине, и каждая программа выполнялась по очереди, как бы подходя к кассе в магазине. Каждая задача, после того, как выполнила свою работу, должна была явно уведомить планировщик, что задача завершила свою работу и планировщик может переключиться на следующую задачу. Такую очередь называют FIFO — First In First Out первым пришёл, первым ушёл. А такую многозадачность называют совместной или кооперативной многозадачностью.
Дальше очередь стала более сложной и появились приоритеты с вытеснением, задачи с более высоким приоритетом могут встать в очереди перед задачами с более низким приоритетом, или некоторые задачи вовсе могут быть пропущены в этом цикле обхода очереди. Теперь же решение о переключении задач принимает OC по заданному таймеру. Такую многозадачность называют вытесняющая многозадачность или PREEMPT, preemptive multitasking. Простой пример такой очереди, когда в очереди к директору компании стоят сотрудники, но приходит какой-нибудь проверяющий, или какое-то важное лицо, и всех просят подождать, пока директор пообщается с этим более важным человеком. А могут и вовсе кого-то выгнать из кабинета начальника: -«Семён Семёнович, незамедлительно покиньте кабинет директора, к нему министр тяжёлой промышленности приехал!»
Каждая задача в ядре Linux описывается структурой task_struct, а список задач хранится в виде циклического двусвязного списка (Связный список). Описание структуры task_struct можно найти в файле: include/linux/sched.h
исходников ядра. task_struct ещё называют дескриптором процесса и в нём находится вся важная информация об исполняемом процессе. Мы не будем углубляться в эту тему слишком глубоко, так как это тема отдельной статьи. Но вы всегда можете найти комментарии и пояснения к коду, в файле: include/linux/sched.h
, и изучить эту тему самостоятельно. Умение читать исходные коды ядра, является очень важным навыком для каждого разработчика, который хочет углубиться в программирование ядра Linux. Часть полезной информации также можно найти в официальной документации ядра Linux — Scheduler, но всё же лучше смотреть исходные коды ядра.
Со временем системы стали многоядерными и имели количество потоков выполнения — равное количеству ядер процессора или количеству процессоров, в случае с одноядерными процессорами, что, в свою очередь, повысило возможности компьютеров и количество выполняемых задач одновременно. Чтобы уменьшить накладные расходы переключения задач в ОС, разработчики процессоров придумали простое и элегантное решение, переложили часть функций на процессор. Так были придуманы Intel Hyper-Threading (Intel HT) и AMD Simultaneous Multithreading (AMD SMT).
Технологии очень простые: Теперь вместо реальных ядер процессор начал показывать виртуальные ядра в количестве реальных ядер умноженных на 2. Процессоры с 1 ядром начали иметь 2 виртуальных процессора (2 потока выполнения), которые видит ОС. Теперь в процессоре, имея два потока выполнения на каждое ядро, ядро процессора может не простаивать, когда один из потоков спит, а переключится на второй поток. Тем самым процессор будет переключаться между потоками с минимальными задержками, и по максимуму использовать возможности каждого ядра.
Как работают Intel HT и AMD SMT можно понять по картинкам ниже. Первая картинка показывает, как занята очередь процессора при выполнении двух задач, а на второй картинке показана временная диаграмма, и что в случае с Intel HT и AMD SMT на обе задачи суммарно было потрачено меньше времени.
На самом деле, в некоторых случаях это решение является очень спорным и не даёт какого-либо выигрыша производительности. Но в некоторых задачах всё же есть заметный выигрыш, например, в виртуализации. Ничего не предвещало беды, но, как говорится, беда подкралась неожиданно…
Долгое время никто не замечал главной проблемы этой технологии. Все потоки используют одни и те же кэши и аппаратные возможности процессора. Это открыло уязвимость, что один процесс может извлекать данные, наблюдая за изменениями кэша другим процессом. Только с ростом потребностей бизнеса и популяризации виртуализации, такая уязвимость стала чаще обсуждаться. Единственным способом безопасно запускать два процесса не доверяющих друг-другу стало отключение Intel HT и AMD SMT. С появлением уязвимостей класса Spectre данная проблема ещё больше усугубилась и поставила облачных провайдеров перед очень тяжелым выбором: отключить Intel HT и AMD SMT? И даже вызывало сильное раздражение, так как клиенты — бизнес требуют безопасности их данных. Но что же это для них значило? Конечно же, увеличение затрат и миллионные потери прибыли. Если при наличии Intel HT и AMD SMT они могли иметь больше клиентов чем ядер, и клиенты могли балансироваться между ядрами и выравнивать нагрузку на процессор, то теперь все облачные технологии готовы были заключить в жесткие рамки количества реальных ядер. И тут облачные провайдеры задумались не на шутку, как сделать так чтобы оставить включенными Intel HT и AMD SMT, сделать данные клиента безопаснее, например, закрытые ключи шифрования и пароли. Так появился на свет Core Scheduling, который безопасно может выполнять потоки.
▍Core Scheduling
Как же работает Core Scheduling? Всё очень просто! Каждому процессу назначается метка — cookie
, по которому его можно идентифицировать. Так же свой уникальный cookie
можно назначить каждому пользователю. Тем самым ядро разрешает совместное использование процессора только в случае, если у двух процессов совпадает cookie
. Как говорится, всё гениальное просто!
Для управления процессами или потоками используется системный вызов prctl:
int prctl(int option, unsigned long arg2, unsigned long arg3, unsigned long arg4, unsigned long arg5);
В случае с Core Scheduling системный вызов принимает вид:
int prctl(PR_SCHED_CORE, int command, pid_t pid, enum pid_type type, unsigned long *cookie);
Где PR_SCHED_CORE
говорит, что это операция с Core Scheduling, command
это команда которую надо выполнить, pid process id — цифровой идентификатор процесса, pid_type
тип pid, cookie
— беззнаковое 32 битное число метки.
Для работы с Core Scheduling существует 4 команды:
PR_SCHED_CORE_CREATE
— говорит ядру создать новыйcookie
и назначить его процессу сpid
. С помощью параметраpid_type
мы можем управлять шириной назначения cookie. Если значение равноPIDTYPE_PID
, тоcookie
устанавливается для конкретного процесса сpid
, а приPIDTYPE_TGID
всей группе потоков.cookie
не должен быть равенNULL
.PR_SCHED_CORE_GET
— получаетcookie
для указанногоpid
, и сохраняет его в переменнойcookie
. Вы могли обратить внимание, что системному вызову передается не само значение cookie, а указатель на место в памяти где находится значение переменнойcookie
, тем самым системный вызов свободно может читать и писать значения по этому адресу. Полезность данной команды заключается только в получении cookie двух процессов и их сравнения. Большей ценности данная команда не имеет.PR_SCHED_CORE_SHARE_TO
— процесс который вызывалprctl
может поделится своимcookie
с процессомpid
которого был передан. В остальном команда полностью повторяет параметрыPR_SCHED_CORE_CREATE.
PR_SCHED_CORE_SHARE_FROM
— извлекаетcookie
у процесса сpid
, и назначает вызвавшемуprctl
.
Конечно же каждый процесс не может просто извлекать и назначать cookie
, как ему вздумается. Иначе это подрывает безопасность, и враждебный процесс сможет установить свой cookie
, что в свою очередь делает бессмысленным применение Core Scheduling. На этот случай в ядре Linux есть проверка, может ли процесс вызывать ptrace()
на цели. Поэтому cookie
могут распространяться только среди процессов, которые уже имеют определенный уровень доверия между собой. Так же не возможно сгенерировать cookie
в пользовательском пространстве, так как необходимо, чтобы не связанные процессы всегда получали уникальные cookie
.
Включить Core Scheduling можно при сборке ядра: General setup --> [*] Core Scheduling for SMT
. Так же вы можете прочитать мою статью LTO оптимизация ядра Linux.
Так вот, если в системе включен Core Scheduling, то когда планировщик берет новую задачу с наивысшим приоритетом из списка, то он отправляет прерывание на родственные процессоры, на что каждый из них должен проверить cookie
новой задачи и ответить есть ли задачи на нём с таким же cookie
. Таким образом, при совпадении cookie
, процессор уже имеющий задачу с таким же cookie
, начинает выполнение этой новой задачи. Если же в системе нет процессов имеющий одинаковый cookie
, то процессор будет бездействовать, пока такие процессы не появятся. Чтобы предотвратить простой процессора, планировщик будет переносить процессы между ядрами.
К сожалению первые версии Core Scheduling имели большой недостаток — высокие накладные расходы, падала производительность системы т.к. в некоторые моменты времени Core Scheduling заставлял процессор бездействовать, что в итоге стало ещё хуже чем отключение Intel HT и AMD SMT. Но со временем удалось улучшить алгоритм и решать большую часть проблем, но всё же не бесплатно. Поэтому Core Scheduling не подходит каждому т.к. немного снижает производительность системы, а только тем, кому очень важна безопасность, например облачным провайдерам. Так же Core Scheduling не подходит задачам которым важна скорость реакции, так называемые Real Time задачи, для них вовсе рекомендуется отключить Intel Hyper-Threading(Intel HT) и AMD SMT.
Как можно понять из статьи не бывает ложки меда без капли дегтя. Но развитие технологий идет и старые проблемы решаются, хоть не всё всегда идеально. Изначально Core Scheduling был протестирован мною на ядре 5.13-rc с патчами, и в принципе в нём не сложно включить Core Scheduling, но в одном из обновлений ядра 5.13 перестали работать патчи, и их надо было много править, что не имело уже большого смысла для меня и ядро 5.14 уже было более интересно для изучения. Мой актуальный набор патчей для ядра вы всегда можете скачать по ссылке, там всегда актуальные и стабильные патчи, которые не влияют на стабильность системы, но приносят ряд улучшений.
Моя прошлая статья LTO оптимизация ядра Linux вызывала у читателей много вопрос. Было много доброжелателей, которые мне писали лично и благодарили. Были и такие которые писали, чтобы поругаться и даже катали жалобы на мои проекты. Конечно же доброжелателей было значительно больше. Поэтому дам краткий ответ на частые вопросы:
- Насколько LTO ядро быстрее? Ответ с бенчмарками есть по ссылке Squeezing More Performance Out Of The Linux Kernel With Clang + LTO
- Для кого подходит LTO Оптимизация ядра? В первую очередь это разработчикам мобильных устройств, которым очень важна производительность и быстрый отклик. Изначально LTO оптимизация появилась для ядра Linux мобильных устройств, но показала свою высокую эффективность и была перенесена на
x86_64
. Поэтому LTO Оптимизация ядра Linux подходит всем, кому важна более высокая производительность и быстрый отклик от системы. Например сервера, которым важна скорость обработки и отдачи контента. За месяцы тестов не было выявлено ни одного побочного эффекта от применения LTO Оптимизации. Так же в статье LTO оптимизация ядра Linux был дан ответ почему всё работает хорошо и почему именно выбранclang+llvm
. - Несколько читателей хабра мне написали и попросили сделать репозиторий для моих измененных пакетов arch-packages. Один из них, поработал какое-то время с моими пакетами, и вышел с предложением разместить репозиторий у него на сервере. Так появился Arch Linux Club. Теперь все легко могут протестировать, как влияют измененные пакеты на производительность. Через какое-то время мой проект заметили разработчики одного Linux дистрибутива(форка Arch Linux), связались со мной и написали восторженные отзывы.
- В чем различие моих пакетов от официальных Arch Linux? Основное различие больше патчей и багфиксов. После того, как начал переделывать пакеты, был поражен, что майнтенейры не всегда понимают, что у них в сборочных скриптах, и часто забивают на исправления ошибок и совсем не читают багрепорты в апстриме. Например, не редко было, что некоторые программы у меня при работе крешились, а мейнтейнеры ничего не делали. В этом разработчики RedHat, Gentoo, Suse просто на две головы выше.
Это заставило меня ещё больше переделать пакетов и добавить патчи. Возможно мое начинание даже перейдет в свой дистрибутив. Основной упор в пакетах на безопасность и производительность где возможно включены
-z,relro,-z,now и -fstack-protector-strong -fstack-clash-protection -fPIE
. Подробнее что это даёт можно прочесть на сайте RedHat.Изначально Arch Linux собирает все пакеты с оптимизацией под старые процессоры, что не очень актуально в современных реалиях. Поэтому мною используются пакеты оптимизированные под современные процессоры особенно под AMD Ryzen, которым пользуюсь. Оптимизации под процессор:
-march=x86-64-v3 -mtune=generic и -march=znver2
. Так же были прочитано большое количество багрепортов, был переработан glibc убраны устаревшие и ненужные параметры, улучшена производительность, добавлены исправления ошибок и проблем безопасности в пакетах. На данный момент улучшена производительность и стабильность всей системы, пофиксены основные утечки памяти, которые присутствуют в официальных пакетах.
При подготовке статьи использовалась переписка разработчиков ядра Linux — Linux Kernel Mailing List.
Уважаемые читатели, спасибо за прочтение. Так как ваше мнение очень важно, вы можете оставить свои замечания по статье в комментариях. Тем самым вы поможете сделать контент для вас более качественным и интересным.
Автор:
nullc0de