Блокировки (Spinlocks), Read-write блокировки и Big-Reader блокировки;
Начиная с первых дней Linux, разработчики сталкивались с классической проблемой доступа к данным, общим для процессов с различными типами контекста исполнения (пользовательские процессы и обработчики прерываний) и различных экземпляров одного и того же контекста на нескольких CPU.
Поддержка SMP была добавлена в Linux в версии 1.3.42 - 15 ноября 1995 (оригинальный патч был выпущен для 1.3.37 в октябре того же года).
Если критическая секция кода, исполняется на однопроцессорной системе, либо в контексте процесса, либо в контексте прерывания, то установить защиту можно использованием пары инструкций cli/sti:
unsigned long flags;
save_flags(flags); cli(); /* критичный код */ restore_flags(flags);
Вполне понятно, что такого рода защита, на SMP непригодна, поскольку критическая секция кода может исполняться одновременно и на другом процессоре, а cli()
обеспечивает защиту на каждом процессоре индивидуально и конечно же не может воспрепятствовать исполнению кода на другом процессоре. В таких случаях и используются блокировки (spinlocks).
Имеется три типа блокировок: vanilla (базовая), read-write и big-reader блокировки (spinlocks). Read-write блокировки должны использоваться в случае, когда имеется "много процессов - работающих только на чтение, и немного - на запись". Пример: доступ к списку зарегистрированных файловых систем (см. fs/super.c). Список защищен read-write блокировкой file_systems_lock, потому что исключительный доступ необходим только в случае регистрации/дерегистрации файловой системы, но любые процессы должны иметь возможность "читать" файл /proc/filesystems или делать системный вызов sysfs(2) для получения списка файловых систем. Такого рода ограничение вынуждает использовать read-write блокировки. Для случая read-write блокировки доступ "только для чтения" могут получить одновременно несколько процессов, в то время как доступ "на запись" - только один, при чем, чтобы получить доступ "на запись" не должно быть "читающих" процессов. Было бы прекрасно, если бы Linux мог корректно "обходить" проблему удовлетворения зароса "на запись", т.е. чтобы запросы "на чтение", поступившие после запроса "на запись", удовлетворялись бы только после того, как будет выполнена операция записи, избегая тем самым проблемы "подвешивания" "пишущего" процесса несколькими "читающими" процессами. Однако, на текущий момент пока не ясно - следует ли вносить изменения в логику работы, контраргумент - "считывающие" процессы запрашивают доступ к данным на очень короткое время, так должны ли они "подвисать", пока "записывающий" процесс ожидает получение доступа потенциально на более длительный период?
Блокировка big- reader представляет собой разновидность блокировки read-write сильно оптимизированной для облегчения доступа "на чтение" в ущерб доступу "на запись". На текущий момент существует пока только две таких блокировки, первая из которых используется только на платформе sparc64 (global irq), и вторая - для сетевой поддержки (networking). В любом другом случае, когда логика доступа не вписывается ни в один из этих двух сценариев, следует использовать базовые блокировки. Процесс не может быть блокирован до тех пор, пока владеет какой либо блокировкой (spinlock).
Блокировки могут быть трех подтипов: простые, _irq() и _bh().
spin_lock_irqsave()/spin_unlock_irqrestore(): более строгая форма, используется, когда состояние флага прерываний неизвестно, но только если вопрос в прерываниях вообще. Не имеет никакого смысла, если обработчик прерываний не выполняет критический код.
Не следует использовать простые spin_lock(), когда процесс конкурирует с обработчиком прерываний, потому что когда процесс выполняет spin_lock(), а затем происходит прерывание на этом же CPU, возникает ситуация "вечного ожидания": процесс, выполнивший spin_lock() будет прерван и не сможет продолжить работу, пока обработчик прерываний не вернет управление, а обработчик прерываний не сможет вернуть управление, поскольку будет стоять в ожидании снятия блокировки.
В общем случае, доступ к данным, разделяемым между контекстом пользовательского процесса и обработчиком прерываний, может быть оформлен так:
spinlock_t my_lock = SPIN_LOCK_UNLOCKED;
my_ioctl() { spin_lock_irq(&my_lock); /* критическая секция */ spin_unlock_irq(&my_lock); }
my_irq_handler() { spin_lock(&lock); /* критическая секция */ spin_unlock(&lock); }
Следует обратить внимание на:
прерывания всегда разрешены.