Если работа со структурой данных может быть четко разделена на этапы чтения/записи, как в только что рассмотренном случае, то имеет смысл использовать механизмы блокировок с аналогичной семантикой. Для таких ситуаций операционная система Linux предоставляет спин-блокировки чтения-записи. Спин-блокировки чтения-записи обеспечивают два варианта блокировки. Один или больше потоков выполнения, которые одновременно выполняют операции считывания, могут удерживать такую блокировку. Блокировка на запись, наоборот, может удерживаться в любой момент времени только одним потоком, осуществляющим запись, и никаких параллельных считываний не разрешается. Блокировки чтения-записи иногда также называются соответственно shared/exclusive (общая/ исключающая) или concurrent/exclusive (параллельная/исключающая).
Инициализировать блокировку для чтения-записи можно с помощью следующего программного кода.
rwlock_t mr_rwlock = RW_LOCK_UNLOCKED;
Следующий код осуществляет считывание.
read_lock(&mr_rwlock);
/* критический участок (только для считывания) ... */
read unlock(&mr_rwlock);
И наконец, показанный ниже код осуществляет запись.
write_lock(&mr_rwlock);
/* критический участок (чтение и запись) ... */
write_unlock{&mr_rwlock);
Обычно считывание и запись информации осуществляются в разных участках кода, как это показано в данном примере.
Заметим, что блокировку, захваченную для чтения, нельзя "повышать" до блокировки, захваченной для записи. В следующем коде
read_lock(&mr_rwlock);
write_lock(&mr_rwlock);
возникнет самоблокировка, так как при захвате блокировки на запись будет выполняться периодическая проверка, пока все потоки, которые захватили блокировку для чтения, ее не освободят; это касается и текущего потока. Если в каком-либо месте будет необходима запись, то нужно сразу же захватывать блокировку для записи. Если в вашем коде нет четкого разделения на код, который осуществляет считывание, и код, который осуществляет запись, то нет необходимости использовать блокировки чтения- записи. В таком случае оптимальным будет использование обычных спин-блокировок.
Несколько потоков чтения безопасно могут удерживать одну и ту же блокировку чтения-записи. На самом деле один поток также может безопасно рекурсивно захватывать одну и ту же блокировку для чтения. Это позволяет выполнить полезную и часто используемую оптимизацию. Если в обработчиках прерываний осуществляется только чтение и не выполняется запись, то можно "смешивать" использование блокировок с запрещением прерываний и без запрещения. Для защиты данных при чтении можно использовать функцию read_lock()
вместо read_lock_irqsave()
. При обращении к данным для записи все равно необходимо запрещать прерывания, например использовать функцию write_lock_irqsave()
, так как в обработчике прерывания может возникнуть взаимоблокировка в связи с ожиданием захвата блокировки на чтение при захваченной блокировке на запись. В табл. 9.4 показан полный список средств работы с блокировками чтения-записи.
Таблица 9.4. Список функций работы со спин-блокировками чтения-записи