MyTetra Share
Делитесь знаниями!
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. Подробнее эта тема раскрыта в следующих ресурсах:



 
MyTetra Share v.0.59
Яндекс индекс цитирования