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

По сути, семафор — это целочисленный счетчик, контролирующий доступ к ресурсу. Код, которому требуется доступ, должен сначала уменьшить счетчик на единицу, но только при условии, что значение этого счетчика в данный момент больше нуля. В противном случае запрашивающий код должен ожидать увеличения значения семафора. Освобождение семафора зависит от приращения значения счетчика. В реализации ядра Linux семафор получается вызовом вниз() (или один из многих вариантов). Если семафор недоступен, вызов down() будет ждать, пока другой поток освободит его. Неудивительно, что операция освобождения называется на вершине(). В классической литературе (в интерпретации Эдсгера Дейкстры) такие операции называются P() И V().

Ядро версии 0.01, выпущенное в 1991 году, не имело семафоров — собственно, как и любой другой механизм управления параллелизмом. Сначала ядро ​​работало только на однопроцессорных системах, и, как и в большинстве Unix-систем того времени, ядро ​​имело эксклюзивный доступ к процессору, пока оно работало. Процесс, работающий в ядре, не мог быть вытеснен и продолжал работать до тех пор, пока он не был явно заблокирован после события или не вернулся в пространство пользователя, поэтому проблемы с гонкой данных возникали редко. Единственным исключением являются аппаратные прерывания. Чтобы избежать нежелательных конфликтов из-за прерываний, код сильно приправлен вызовами. cli() И sti()позволяя вам блокировать (и разблокировать) прерывания по мере необходимости.

Версия 0.96 была выпущена в мае 1992 года с некоторыми существенными изменениями; в частности, была основная поддержка «сетевых» операций. Эта поддержка позволила работать с Unix-подобными сокетами с помощью специфичного для Linux системного вызова. socketcall(). Безусловно, едва ли не самым главным нововведением этой версии было то, что именно здесь была добавлена ​​поддержка SCSI-устройств. Хорошая поддержка SCSI сыграла ключевую роль в раннем развитии Linux. С появлением подсистемы SCSI семафоры впервые обсуждались в ядре; они были спрятаны глубоко в пилотном слое. Семафоры SCSI, как и многие другие, были бинарный. Таким образом, они были установлены в единицу в качестве начального значения, чтобы только один поток, управляющий им, мог получить доступ к этому ресурсу (хост-контроллеру SCSI). Версия 0.99.10, выпущенная в июне 1993 года, повторно реализовала сетевой уровень и представила поддержку семафоров System V в пользовательском пространстве, но в то время в ядре не было общей поддержки семафоров.

ЧИТАТЬ   СМИ: президент Алжира посетит Россию

Как семафоры были добавлены в ядро

Первая реализация семафоров общего назначения специально для ядра появилась в версии 0.99.15c, выпущенной в феврале 1994 года. Первоначально они использовались на уровне виртуальной файловой подсистемы ядра, где семафор был добавлен в структура инода; никакие другие варианты использования не планировались до версии 1.0 месяцем позже. В версии 2.0 (июнь 1996 г.) количество семафоров начало медленно расти, и была добавлена ​​знаменитая большая блокировка ядра (BKL), которая не была семафором.

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

В версии 2.2 (январь 1999 г.) в ядре было 71 объявление семафора структуры; в версии 2.4.0 (январь 2001 г.) это число увеличилось до 138, а в версии 2.6.0 (декабрь 2003 г.) их было 332. В версии 2.6.14, октябрь 2005 г., было 483 объявления семафоров. К этому времени отключение прерываний для управления параллелизмом становилось все более и более непригодным — просто за такую ​​практику приходилось слишком серьезно платить производительностью системы — и большая блокировка ядра начала превращаться в постороннюю проблему, усложняющую масштабирование.

Тем временем первая инфраструктура спин-блокировки была добавлена ​​в рабочее ядро ​​версии 2.1.23, но она не была реализована до тех пор, пока планировщик не был представлен в версии 2.1.30. Спин-блокировка, в отличие от семафора, является чисто взаимоисключающим примитивом; в нем нет такого счетчика, как в семафоре. Кроме того, это «неспящий» замок. Код, который ожидает спин-блокировку, будет просто зацикливаться до тех пор, пока спин-блокировка не станет доступной. До этого добавления семафоры были единственным универсальным механизмом взаимного исключения, поддерживаемым ядром.

ЧИТАТЬ   Джорджия Тоффоло любит встречаться со своим бойфрендом Джорджем Коттреллом и Найджелом Фараджем.

Во многих ситуациях спин-блокировки были более подходящими, чем семафоры, но у них есть одно ограничение: код, содержащий спин-блокировку, не может находиться в спящем режиме. Итак, для работы с семафорами всегда нужна была семафороподобная структура. Но к концу 2005 года разработчики начал думатьчто для случая бинарного семафора (именно так использовалось большинство семафоров) может быть лучшее решение. Оказалось, что оригинальная реализация мьютексов работает хуже, чем семафоры. Но, как это часто случалось в то время, Инго Молнар в течение нескольких дней развернутый более быстрая реализация. Мьютексы были быстро добавлены в ядро ​​в качестве альтернативы семафорам, и начался процесс преобразования семафоров в мьютексы.

Медленный переход

Когда появились мьютексы, разработчики опасались, что из-за мьютексов придется «жить по-новому с завтрашнего дня», и все бинарные семафоры будут заменены на новый тип. Но были добавлены мьютексы и сохранен старый тип. Так что они могли сосуществовать, и преобразование кода из одной формы в другую не составляло труда. Неудивительно, что на сегодняшний день в ядре остается объявленным более 100 семафоров, и, по-видимому, большинство из них являются двоичными. Но трудно найти патчи, добавляющие новые семафоры. Наверное, самый последний это исправление драйвера от августа 2022 года. Большинству разработчиков ядра, похоже, пока не приходится много думать о семафорах.

Недавно над этой проблемой работал Луи Чемберлен, который занимается сопровождением модулей: если много вызовов для загрузки модулей за короткое время, то могут возникнуть проблемы с подсистемой управления памятью. Посоветовавшись с коллегами, он Бесплатно механизм, который просто ограничивал бы количество операций загрузки модулей, которые могут быть выполнены в любой момент времени. Его быстро ответил Линус Торвальдс напомнил, что для этой цели можно использовать семафоры — «классический ограничитель параллелизма». Из патча был переработан именно в этом духе.

ЧИТАТЬ   Визит Жапарова в Россию состоится, несмотря на атаку на Кремль

Однако в контексте состоявшейся на эту тему дискуссии Петер Зийлстра отмеченныйЧто Макрос DEFINE_SEMAPHORE(), который объявляет и инициализирует статический семафор, устанавливает его начальное значение в единицу, т. е. по умолчанию создается двоичный семафор. Поскольку, по его словам, бинарные семафоры — это «частный случай», лучше предусмотреть такую ​​возможность: либо DEFINE_SEMAPHORE() принимает дополнительный аргумент, указывающий, каким должно быть начальное значение. Торвальд согласованный целесообразность такого изменения. «Давайте просто предположим, что сегодня семафоры должны использоваться только для подсчета семафоров и обеспечения их DEFINE_SEMAPHORE() принял этот номер. По его словам, сегодняшние семафоры — «уже почти полностью перешли в категорию устаревшего кода». С тех пор Зейлстра уже выпустил соответствующий патч.

Это небольшое изменение в API семафора, вероятно, затронет некоторых разработчиков. Однако остается открытым вопрос, что делать с этими десятками все еще используемых бинарных семафоров. Было бы здорово преобразовать их в мьютексы — и производительность улучшилась бы, и код выглядел бы более привычным для нынешних разработчиков. Но как отмеченный Сергей Сеножацкий, невозможно просто механически переделать их все, не всматриваясь в каждую. Например, двоичный семафор сохраняется в коде printk(), потому что mutex_unlock() не может быть вызван из контекста прерывания, и up() — Может.

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

Source

От admin