Один из драйверов ядра Linux, которые я разрабатываю, использует сетевую связь в ядре (sock_create()
, sock->ops->bind()
и т.д.).
Проблема заключается в наличии нескольких сокетов для приема данных. Поэтому мне нужно что-то, что будет моделировать select()
или poll()
в пространстве ядра. Поскольку эти функции используют дескрипторы файлов, я не могу использовать системные вызовы, если я не использую системные вызовы для создания сокетов, но это кажется ненужным, так как я работаю в ядре.
Итак, я думал об упаковке обработчика по умолчанию sock->sk_data_ready
в моем собственном обработчике (custom_sk_data_ready()
), который бы разблокировал семафор. Затем я могу написать свою собственную функцию kernel_select()
, которая пытается заблокировать семафор и блокирует его до тех пор, пока он не будет открыт. Таким образом, функция ядра переходит в спящий режим до тех пор, пока семафор не разблокируется custom_sk_data_ready()
. Как только kernel_select()
получает блокировку, он разблокируется и вызывает custom_sk_data_ready()
, чтобы заблокировать его. Поэтому единственной дополнительной инициализацией является запуск custom_sk_data_ready()
перед привязкой сокета, поэтому первый вызов custom_select()
не вызывает ложных срабатываний.
Я вижу одну возможную проблему. Если происходит несколько попыток, то несколько вызовов на custom_sk_data_ready()
будут пытаться разблокировать семафор. Поэтому, чтобы не потерять несколько вызовов и отслеживать используемый sock
, в используемые сокеты должна быть таблица или список указателей. И custom_sk_data_ready()
должен будет указать в таблице/списке тот сокет, который был передан.
Звучит ли этот метод? Или я должен просто бороться с проблемой пространства пользователя/ядра при использовании стандартных системных вызовов?
Начальный поиск:
Все функции обратного вызова в структуре sock
вызываются в контексте прерывания. Это означает, что они не могут спать. Чтобы позволить основному потоку ядра спать в списке готовых сокетов, используются мьютексы, но custom_sk_data_ready()
должен действовать как спин-блокировка на мьютексах (многократно вызывает mutex_trylock()
). Это также означает, что любое динамическое распределение должно использовать флаг GFP_ATOMIC
.
Дополнительная возможность:
Для каждого открытого сокета замените каждый сокет sk_data_ready()
на пользовательский (custom_sk_data_ready()
) и создайте рабочего (struct work_struct
) и рабочую очередь (struct workqueue_struct
). Общая функция process_msg()
будет использоваться для каждого рабочего. Создайте глобальный список на уровне модуля ядра, в котором каждый элемент списка имеет указатель на сокет и содержит рабочую структуру. Когда данные готовы к сокету, custom_sk_data_ready()
выполнит и найдет соответствующий элемент списка с одним и тем же сокетом, а затем вызовет queue_work()
с рабочей очередью элемента списка и рабочим. Затем вызывается функция process_msg()
и может либо найти элемент списка соответствия через содержимое параметра struct work_struct *
(адрес), либо использовать макрос container_of()
, чтобы получить адрес структуры списка, который содержит структура работников.
Какая техника самая звуковая?