Откуда берется параллелизм
Откуда берется параллелизм
При работе в пространстве пользователя необходимость синхронизации возникает из того факта, что программы выполняются преемптивно, т.е. могут быть вытеснены другой программой по воле планировщика. Поскольку процесс может быть вытеснен в любой момент и другой процесс может быть запущен планировщиком для выполнения на этом же процессоре, появляется возможность того, что процесс может быть вытеснен независящим от него образом во время выполнения критического участка. Если новый, запланированный на выполнение процесс входит в тот же критический участок (скажем, если оба процесса — потоки одной программы, которые могут обращаться к общей памяти), то может возникнуть состояние конкуренции за ресурс. Аналогичная проблема может возникнуть даже в однопоточной программе при использовании сигналов, так как сигналы приходят асинхронно. Такой тип параллелизма, когда два события происходят не одновременно, а накладываются друг на друга, так вроде они происходят в один момент времени, называется псевдопараллелизмом (pseudo-concurrency).
На машине с симметричной многопроцессорностью два процесса могут действительно выполнять критические участки в один и тот же момент времени. Это называется истинным, параллелизмом (true concurrency). Хотя причины и семантика истинного и псевдопараллелизма разные, они могут приводить к совершенно одинаковым состояниям конкуренции и требуют аналогичных средств защиты. В ядре причины параллельного выполнения кода следующие.
• Прерывания. Прерывания могут возникать асинхронно, практически в любой момент времени, прерывая код, который выполняется в данный момент.
• Отложенные прерывания и тасклеты. Ядро может выполнять обработчики softirq и тасклеты практически в любой момент времени и прерывать код, который выполняется в данный момент времени.
• Преемптивность ядра. Так как ядро является вытесняемым, то одно задание, которое работает в режиме ядра, может вытеснить другое задание, тоже работающее в пространстве ядра.
• Переход в состояние ожидания и синхронизация с пространством пользователя. Задание, работающее в пространстве ядра, может переходить в состояние ожидания, что вызывает активизацию планировщика и выполнение нового процесса.
• Симметричная многопроцессорность. Два или больше процессоров могут выполнять код в один и тот же момент времени.
Важно, что разработчики ядра поняли все причины и подготовились к возможным случаям параллелизма. Если прерывание возникает во время выполнения кода, который работает с некоторым ресурсом, и обработчик прерывания тоже обращается к этому же ресурсу, то это является ошибкой. Аналогично ошибкой является и то, что код ядра вытесняется в тот момент, когда он обращается к совместно используемому ресурсу. Переход в состояние ожидания во время выполнения критического участка в ядре открывает большой простор для состояний конкуренции за ресурсы. И наконец, два процессора никогда не должны одновременно обращаться к совместно используемым данным. Когда ясно, какие данные требуют защиты, то уже нетрудно применить соответствующие блокировки, чтобы обеспечить всем безопасность. Сложнее идентифицировать возможные условия возникновения таких ситуаций и определить, что для предотвращения конкуренции необходима та или иная форма защиты. Давайте еще раз пройдем через этот момент, потому что он очень важен. Применить блокировки в коде для того, чтобы защитить совместно используемые данные, — это не тяжело, особенно если это делается на самых ранних этапах разработки кода. Сложность состоит в том, чтобы найти эти самые совместно используемые данные и эти самые критические участки. Именно поэтому требование аккуратного использования блокировок с самого начала разработки кода — а не когда-нибудь потом — имеет первостепенную важность. Постфактум очень сложно отследить, что необходимо блокировать, и правильно внести изменения в существующий код. Результаты подобной разработки обычно не очень хорошие. Мораль — всегда нужно аккуратно учитывать необходимость применения блокировок с самого начала процесса разработки кода.
Код, который безопасно выполнять параллельно с обработчиком прерывания, называется безопасным при прерываниях (interrupt-safe). Код, который содержит защиту от конкурентного доступа к ресурсам при симметричной многопроцессорной обработке, называется безопасным при SMP-обработке (SMP-safe). Код, который имеет защиту от конкурентного доступа к ресурсам при вытеснении кода ядра, называется безопасным при вытеснения[45] (preempt-safe). Механизмы, которые во всех этих случаях используются для обеспечения синхронизации и защиты от состояний конкуренции, будут рассмотрены в следующей главе.