Библиотека сайта rus-linux.net
Цилюрик О.И. Модули ядра Linux | ||
Назад | Внутренние механизмы ядра | Вперед |
Синхронизации
Существует множество примитивов синхронизации, как теоретически проработанных, так и конкретно используемых и доступных в ядре Linux, и число их постоянно возрастает. Эта множественность связана, главным образом, с борьбой за эффективность (производительность) выполнения кода — для отдельных функциональных потребностей вводятся новые, более эффективные для этих конкретных нужд примитивы синхронизации. Тем не менее, основная сущность работы всех примитивов синхронизации остаётся одинаковой, в том виде, как она была впервые описана Э. Дейкстрой в его знаменитой работе 1968 г. «Взаимодействие последовательных процессов».
Критические секции кода и защищаемые области данных
Для решения задачи синхронизации в ядре Linux существует множество механизмов синхронизации (сами объекты синхронизации называют примитивами синхронизации) и появляются всё новые и новые... , некоторые из механизмов вводятся даже для поддержки единичных потребностей. Условно, по функциональному использованию, примитивы синхронизации можно разделить на (хотя такое разделение часто оказывается весьма условным):
- Примитивы для защиты фрагментов исполняемого кода (критических секций) от одновременного (или псевдо-одновременного) исполнения. Классический пример: мьютекс, блокировки чтения-записи...
- Примитивы для защиты областей данных от несанкционированного изменений: атомарные переменные и операции, счётные семафоры...
Механизмы синхронизации
Обычно все предусмотренные версией ядра примитивы синхронизации доступные после включения заголовочного файла <linux/sched.h>. Ниже будут рассмотрены только некоторые из механизмов, такие как:
- переменные, локальные для каждого процессора (per-CPU variables), интерфейс которых описан в файле <linux/percpu.h>;
- атомарные переменные (описаны в архитектурно-зависимых файлах <atomic*.h>);
- спин-блокировки (<linux/spinlock.h>);
- сериальные (последовательные) блокировки (<linux/seqlock.h>);
семафоры (<linux/semaphore.h>);
- семафоры чтения и записи (<linux/rwsem.h>);
- мьютексы реального времени (<linux/rtmutex.h>);
- механизмы ожидания завершения (<linux/completion.h>);
Рассмотрение механизмов синхронизаций далее проведено как-раз в обратном порядке, с ожидания завершения, потому, что это естественным образом продолжает начатое выше рассмотрение потоков ядра.
Сюда же, к механизмам синхронизации, можно, хотя и достаточно условно, отнести механизмы, предписывающие заданный порядок выполнения операций, и препятствующие его изменению, например в процессе оптимизации кода (обычно их так и рассматривают совместно с синхронизациями, по принципу: «ну, надо же их где-то рассматривать?»).
Условные переменные и ожидание завершения
Естественным сценарием является запуск некоторой задачи в отдельном потоке и последующее ожидание завершения ее выполнения (см. аварийное завершение выше). В ядре нет аналога функции ожидания завершения потока, вместо нее требуется явно использовать механизмы синхронизации (аналогичные POSIX 1003.b определению барьеров pthread_barrier_t). Использование для ожидания какого-либо события обычного семафора не рекомендуется: в частности, реализация семафора оптимизирована исходя из предположения, что обычно (основную часть времени жизни) они открыты. Для этой задачи лучше использовать не семафоры, а специальный механизм ожидания выполнения - completion (в терминологии ядра Linux он называется условной переменной, но разительно отличается от условной переменной как её понимает стандарт POSIX). Этот механизм (<linux/completion.h>) позволяет одному или нескольким потокам ожидать наступления какого-то события, например, завершения другого потока, или перехода его в состояние готовности выполнять работу. Следующий пример демонстрирует запуск потока и ожидание завершения его выполнения (это минимальная модификация для сравнения примера запуска потока ранее):
mod_thr2.c :
#include <linux/module.h> #include <linux/sched.h> #include <linux/delay.h> static int thread( void * data ) { struct completion *finished = (struct completion*)data; struct task_struct *curr = current; /* current - указатель на дескриптор текущей задачи */ printk( KERN_INFO "child process [%d] is running\n", curr->pid ); msleep( 10000 ); /* Пауза 10 с. */ printk( KERN_INFO "child process [%d] is completed\n", curr->pid ); complete( finished ); /* Отмечаем факт выполнения условия. */ return 0; } int test_thread( void ) { DECLARE_COMPLETION( finished ); struct task_struct *curr = current; printk( KERN_INFO "main process [%d] is running\n", curr->pid ); pid_t pid = kernel_thread( thread, &finished, CLONE_FS ); /* Запускаем новый поток */ msleep( 5000 ); /* Пауза 5 с. */ wait_for_completion( &finished ); /* Ожидаем выполнения условия */ printk( KERN_INFO "main process [%d] is completed\n", curr->pid ); return -1; } module_init( test_thread );
Выполнение этого примера разительно отличается от его предыдущего прототипа (обратите внимание на временные метки сообщений!):
$ sudo insmod ./mod_thr2.ko
insmod: error inserting './mod_thr2.ko': -1 Operation not permitted
$ sudo cat /var/log/messages | tail -n4
Apr 17 21:20:23 notebook kernel: main process [12406] is running Apr 17 21:20:23 notebook kernel: child process [12407] is running Apr 17 21:20:33 notebook kernel: child process [12407] is completed Apr 17 21:20:33 notebook kernel: main process [12406] is completed
$ ps -A | grep 12406
$ ps -A | grep 12407
$
Переменные типа struct completion могут определяться либо как в показанном примере статически, макросом:
DECLARE_COMPLETION( name );
Либо инициализироваться динамически:
void init_completion( struct completion * );
Примечание: Всё разнообразие в Linux как потоков ядра (kernel_thread()), так и параллельных процессов (fork()) и потоков пространства пользователя (pthread_create()) обеспечивается тем, что потоки и процессы в этой системе фактически не разделены принципиально, и те и другие создаются единым системным вызовом clone() - все различия создания определяются набором флагов вида CLONE_* для создаваемой задачи (последний параметр kernel_thread() нашего примера).
Предыдущий раздел: | Оглавление | Следующий раздел: |
Потоки ядра | Атомарные переменные и операции |