В общем такое повышение масштабируемости — это очень хорошая вещь, которая позволяет повысить производительность операционной системы Linux на больших и более мощных системах. Чрезмерное увлечение "ростом" масштабируемости может привести к снижению производительности на небольших многопроцессорных и однопроцессорных машинах, потому что для небольших машин не требуются такие мелкоструктурные блокировки, а им приходится иметь дело с большей сложностью и с большими накладными расходами. Рассмотрим связанный список. Первоначальная схема блокировки обеспечивает одну блокировку на весь список. Со временем эта одна блокировка может стать узким местом на очень большой многопроцессорной машине, на которой очень часто обращаются к связанному списку. Для решения проблемы одна блокировка может быть разбита на большое количество блокировок — одна блокировка на один элемент списка. Для каждого элемента списка, который необходимо прочитать или записать, необходимо захватывать уникальную блокировку этого элемента. Теперь конфликт при захвате блокировки будет только в случае, когда несколько процессоров обращаются к одному элементу списка. Что делать, если все равно есть высокий уровень конфликтов? Может быть, необходимо использовать блокировку для каждого поля элемента списка? (Ответ: НЕТ.) Если серьезно, даже когда очень мелко структурированные блокировки хорошо работают на очень больших SMP-машинах, то как они будут работать на двухпроцессорных машинах? Накладные расходы, связанные с дополнительными блокировками, будут напрасными, если на двухпроцессорной машине нет существенных конфликтов при работе с блокировками.
Тем не менее масштабируемость — это важный фактор. Важно с самого начала разрабатывать схему блокировок для обеспечения хорошей масштабируемости. Блокировки на уровне крупных структурных единиц могут стать узким местом даже на машинах с небольшим количеством процессоров. Между крупноструктурными и мелкоструктурными блокировками очень тонкая грань. Слишком крупноструктурные блокировки приводят к большому уровню конфликтов, а слишком мелкоструктурные — к напрасным накладным расходам, если уровень конфликтов при захвате блокировок не очень высокий. Оба варианта эквивалентны плохой производительности.
Необходимо начинать с простого и переходить к сложному только при необходимости. Простота — это ключевой момент.
Блокировки в вашем коде
Обеспечение безопасности кода при SMP-обработке — это не то, что можно откладывать на потом. Правильная синхронизация, блокировки без тупиковых ситуаций, масштабируемость и ясность кода- все это следует учитывать при разработке с самого начала и до самого конца. При написании кода ядра, будь то новый системный вызов или переписывание драйвера устройства, необходимо, прежде всего, позаботиться об обеспечении защиты данных от конкурентного доступа.
Обеспечение достаточной защиты для любого случая — SMP, вытеснение кода ядра и так далее — в результате приведет к гарантии того, что все данные будут защищены на любой машине и в любой конфигурации. В следующей главе будет рассказано о том, как это осуществить.
Теперь, когда мы хорошо подкованы в теории параллелизма, синхронизации и блокировок, давайте углубимся в то, какие существуют конкретные инструменты, предоставляемые ядром Linux, для того чтобы гарантировать отсутствие состояний конкуренции и тупиковых ситуаций в коде.
Глава 9
Средства синхронизации в ядре
В предыдущей главе обсуждались источники и решения проблем, связанных с конкуренцией за ресурсы. К счастью, в ядре Linux реализовано большое семейство средств синхронизации. В этой главе обсуждаются эти средства, интерфейсы к ним, а также особенности их работы и использования. Эти решения позволяют разработчикам писать код, в котором отсутствуют состояния конкуренции за ресурсы.
Атомарные операции
Атомарные операции (atomic operations) предоставляют инструкции, которые выполняются
Поток 1 Поток 2
инкремент i (7->8) -
- инкремент i (8->9)