|
|||||||
12. Избегание коллизий и взаимных блокировок
Время создания: 20.09.2022 09:52
Текстовые метки: linux, ядро, модуль, программирование, язык, C, Си, пособие, документация
Раздел: Компьютер - Linux - Ядро - Пособие по программированию модулей ядра Linux
Запись: xintrea/mytetra_syncro/master/base/1663656778jcps2srbhz/text.html на raw.github.com
|
|||||||
|
|||||||
12. Избегание коллизий и взаимных блокировок Если процессы, выполняющиеся на разных ядрах или в разных потоках, попытаются обратиться к одной и той же области памяти, то вполне могут случиться странности, либо система просто заблокируется. Для избежания этого в ядре существуют специальные функции взаимного исключения (мьютексы). Они показывают, «занят» или «свободен» в данный момент фрагмент кода, исключая тем самым одновременные попытки его выполнения. ▍ 12.1 МьютексыИспользуются мьютексы ядра аналогично тому, как они развертываются в пользовательской среде. И в большинстве случаев для избежания коллизий этого вполне может оказаться достаточно. example_mutex.c /* * example_mutex.c */ #include <linux/init.h> #include <linux/kernel.h> #include <linux/module.h> #include <linux/mutex.h>
static DEFINE_MUTEX(mymutex);
static int example_mutex_init(void) { int ret;
pr_info("example_mutex init\n");
ret = mutex_trylock(&mymutex); if (ret != 0) { pr_info("mutex is locked\n");
if (mutex_is_locked(&mymutex) == 0) pr_info("The mutex failed to lock!\n");
mutex_unlock(&mymutex); pr_info("mutex is unlocked\n"); } else pr_info("Failed to lock\n");
return 0; }
static void example_mutex_exit(void) { pr_info("example_mutex exit\n"); }
module_init(example_mutex_init); module_exit(example_mutex_exit);
MODULE_DESCRIPTION("Mutex example"); MODULE_LICENSE("GPL"); ▍ 12.2 Спин-блокировкиСпин-блокировки, или спинлоки, блокируют ЦПУ, на котором выполняется код, занимая 100% его ресурсов. В связи с этим механизм спинлоков желательно использовать только для кода, на выполнение которого требуется не более нескольких миллисекунд, чтобы с позиции пользователя не вызвать заметного замедления работы. Примером в данном случае является ситуация irq safe, когда прерывания, происходящие во время блокировки, не забываются, а повторно активируются при ее снятии, используя переменную flags для сохранения своего состояния. example_spinlock.c /* * example_spinlock.c */ #include <linux/init.h> #include <linux/interrupt.h> #include <linux/kernel.h> #include <linux/module.h> #include <linux/spinlock.h>
static DEFINE_SPINLOCK(sl_static); static spinlock_t sl_dynamic;
static void example_spinlock_static(void) { unsigned long flags;
spin_lock_irqsave(&sl_static, flags); pr_info("Locked static spinlock\n");
/* Безопасное выполнение задачи. Поскольку задействуется 100% ЦПУ, * выполнение кода должно занимать не более нескольких миллисекунд. */
spin_unlock_irqrestore(&sl_static, flags); pr_info("Unlocked static spinlock\n"); }
static void example_spinlock_dynamic(void) { unsigned long flags;
spin_lock_init(&sl_dynamic); spin_lock_irqsave(&sl_dynamic, flags); pr_info("Locked dynamic spinlock\n");
/* Безопасное выполнение задачи. Поскольку задействуется 100% ЦПУ, * выполнение кода должно занимать не более нескольких миллисекунд. */
spin_unlock_irqrestore(&sl_dynamic, flags); pr_info("Unlocked dynamic spinlock\n"); }
static int example_spinlock_init(void) { pr_info("example spinlock started\n");
example_spinlock_static(); example_spinlock_dynamic();
return 0; }
static void example_spinlock_exit(void) { pr_info("example spinlock exit\n"); }
module_init(example_spinlock_init); module_exit(example_spinlock_exit);
MODULE_DESCRIPTION("Spinlock example"); MODULE_LICENSE("GPL"); ▍ 12.3 Блокировки для чтения и записиБлокировки для выполнения чтения и записи – это специализированные спинлоки, позволяющие эксклюзивно считывать или производить запись. Подобно предыдущему примеру, код ниже показывает ситуацию irq safe, когда в случае активации аппаратными прерываниями других функций, которое также могут выполнять нужные вам чтение/запись, эти функции не нарушат текущую логику выполнения. Как и прежде, будет правильным решением, устанавливать подобную блокировку для максимально коротких задач, чтобы они не подвешивали систему и не вызывали недовольство пользователей относительно тирании вашего модуля. example_rwlock.c /* * example_rwlock.c */ #include <linux/interrupt.h> #include <linux/kernel.h> #include <linux/module.h>
static DEFINE_RWLOCK(myrwlock);
static void example_read_lock(void) { unsigned long flags;
read_lock_irqsave(&myrwlock, flags); pr_info("Read Locked\n");
/* Считывание. */
read_unlock_irqrestore(&myrwlock, flags); pr_info("Read Unlocked\n"); }
static void example_write_lock(void) { unsigned long flags;
write_lock_irqsave(&myrwlock, flags); pr_info("Write Locked\n");
/* Запись. */
write_unlock_irqrestore(&myrwlock, flags); pr_info("Write Unlocked\n"); }
static int example_rwlock_init(void) { pr_info("example_rwlock started\n");
example_read_lock(); example_write_lock();
return 0; }
static void example_rwlock_exit(void) { pr_info("example_rwlock exit\n"); }
module_init(example_rwlock_init); module_exit(example_rwlock_exit);
MODULE_DESCRIPTION("Read/Write locks example"); MODULE_LICENSE("GPL"); Конечно же, если вы уверены, что аппаратные прерывания не активируют никакие функции, которые могли бы нарушить логику, то можете использовать более простые read_lock(&myrwlock) и read_unlock(&myrwlock) либо соответствующие функции записи. ▍ 12.4 Атомарные операцииЕсли вы выполняете простую арифметику: сложение, вычитание или побитовые операции, тогда многоядерный и гиперпоточный мир может предложить еще один способ, как не позволить другим компонентам системы вмешаться в ваше действо. С помощью атомарных операций вы можете обеспечить, чтобы ваше сложение, вычитание или инвертирование битов произошли успешно и не были перезаписаны какими-либо сторонними процессами. Вот пример: example_atomic.c /* * example_atomic.c */ #include <linux/interrupt.h> #include <linux/kernel.h> #include <linux/module.h>
#define BYTE_TO_BINARY_PATTERN "%c%c%c%c%c%c%c%c" #define BYTE_TO_BINARY(byte) \ ((byte & 0x80) ? '1' : '0'), ((byte & 0x40) ? '1' : '0'), \ ((byte & 0x20) ? '1' : '0'), ((byte & 0x10) ? '1' : '0'), \ ((byte & 0x08) ? '1' : '0'), ((byte & 0x04) ? '1' : '0'), \ ((byte & 0x02) ? '1' : '0'), ((byte & 0x01) ? '1' : '0')
static void atomic_add_subtract(void) { atomic_t debbie; atomic_t chris = ATOMIC_INIT(50);
atomic_set(&debbie, 45);
/* Вычитание единицы. */ atomic_dec(&debbie);
atomic_add(7, &debbie);
/* Прибавление единицы. */ atomic_inc(&debbie);
pr_info("chris: %d, debbie: %d\n", atomic_read(&chris), atomic_read(&debbie)); }
static void atomic_bitwise(void) { unsigned long word = 0;
pr_info("Bits 0: " BYTE_TO_BINARY_PATTERN, BYTE_TO_BINARY(word)); set_bit(3, &word); set_bit(5, &word); pr_info("Bits 1: " BYTE_TO_BINARY_PATTERN, BYTE_TO_BINARY(word)); clear_bit(5, &word); pr_info("Bits 2: " BYTE_TO_BINARY_PATTERN, BYTE_TO_BINARY(word)); change_bit(3, &word);
pr_info("Bits 3: " BYTE_TO_BINARY_PATTERN, BYTE_TO_BINARY(word)); if (test_and_set_bit(3, &word)) pr_info("wrong\n"); pr_info("Bits 4: " BYTE_TO_BINARY_PATTERN, BYTE_TO_BINARY(word));
word = 255; pr_info("Bits 5: " BYTE_TO_BINARY_PATTERN, BYTE_TO_BINARY(word)); }
static int example_atomic_init(void) { pr_info("example_atomic started\n");
atomic_add_subtract(); atomic_bitwise();
return 0; }
static void example_atomic_exit(void) { pr_info("example_atomic exit\n"); }
module_init(example_atomic_init); module_exit(example_atomic_exit);
MODULE_DESCRIPTION("Atomic operations example"); MODULE_LICENSE("GPL"); До того, как в стандарте С11 появились встроенные атомарные типы, ядро уже предоставляло небольшой их набор, которым можно было воспользоваться с помощью хитрого архитектурно-зависимого кода. Реализация же атомарных типов в С11 позволяет ядру отказаться от этих специфичных команд, сделав его код более внятным для людей, которые данный стандарт понимают. Но есть здесь и кое-какие проблемы, например модель памяти ядра не соответствует модели, формируемой атомарными операциями в С11. Подробнее эта тема раскрыта в следующих ресурсах:
|
|||||||
Так же в этом разделе:
|
|||||||
|
|||||||
|