Как вам мое изобретение для синхронизации каллбеков в драйве
От: LimyKurn  
Дата: 08.10.18 04:37
Оценка:
И снова вопрос о синхронизации POST_OPERATION_CALLBACK в минифильтрах.
http://rsdn.org/forum/asm/7245876.1
Автор: LimyKurn
Дата: 14.09.18


Напомню проблематику.
Суть в том, что MSDN запрещает в этих каллбеках использовать почти все средства потоко-безопасности. Да и не очень хочется здесь использовать что-то такое, ведь, будучи в ядре ОС, оно будет замедлять работу всех приложений. Фактически из всех средств тут допустимы лишь спинлоки.

И с другой стороны, нет и готовых interlocked-контейнеров, которые могли бы хранить залогированные данные и давать с ними нормально работать.
Как я уже говорил, самым оптимальным из потокобезопасных контейнеров является SList https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/singly-and-doubly-linked-lists#sequenced-singly-linked-lists
Но, как я опять же уже говорил, связные списки — это не круто. А теперь скажу больше: потокобезопасные связные списки — это ущербно.

Рассмотрим всё ту же ситуацию. Каллбек перехватывает все IRP_MJ_CREATE, IRP_MJ_WRITE и IRP_MJ_READ, получает какие-то данные о каждой операции и кладет их в глобальный список. Отдельный поток периодически проверяет этот список, и если он полон, то вываливает его в лог и очищает.
Вроде бы всё это нормально реализуется на SList.
Но теперь я уточню, какие именно данные каллбек получает о каждой операции. Во-первых, он отслеживает именно КАЖДЫЙ IRP_MJ_WRITE, чтобы в итоге получить сколько всего байт записано в данный FileID, а во-вторых, он — не много, не мало — читает EXE (отправителя операции) и считает хеш первых нескольких мегабайт, не меньше.
И вот тут уже проблемы. Суть обоих моментов такова, что для нормального быстродействия нужно не добавлять элемент в конец списка, а использовать уже существующий элемент где-то посередине. По первому моменту еще можно сделать иначе: класть в список отдельно каждый IPR_MJ_WRITE, а уже на выходе — в одном потоке — группировать. Но вот по второму — как ни крути, а считать хеш можно лишь при первой IRP_MJ_WRITE по каждому файлу (иначе любая программа с буферной записью файла будет тормозить), а значит надо его куда-то сохранять и далее искать и использовать. А SList этого не умеет.
Выходит, что связный список SList годится только как очередь и не более. Масштабируемым решением он не является.

А какого-то другого потокобезопасного контейнера, который бы не был связным списком, в NT просто нет.
Единственный масштабируемый вариант — это взять какой-то не потокобезопасный, но зато более продвинутый, контейнер (вероятно самодельный; в тех же официальных driver samples есть примеры такого) и обеспечить ему потокобезопасность с помощью спинлока.

Но есть и альтернативное решение.
Честно говоря, меня еще ранее достал весь этот гемор с синхронизацией (а сейчас сюда добавилось еще и быстродействие) и я придумал, как можно избежать этого раз и навсегда.

Надо просто переделать архитектуру.
Непосредственно в каллбеке вообще не надо ничего делать. Надо только получать текущее системное время и создавать поток, в который передавать время и параметры операции. Каждый вызов каллбека = новый поток.
Ну, а в этом потоке уже гораздо менее критично время выполнения. Можно использовать любые типы синхронизации с отдельным потоком-writerом. А можно просто заставить эти потоки выполняться последовательно. И делать write прямо здесь же, а не в еще одном отдельном потоке.
Можно запускать гигабайтные тесты, — тест отрабатывает, в лог валится куча записей, но время выполнения не вырастает ни на миллисекунду. Ведь создание потока почти ничего не стоит.

А как вам?
Отредактировано 08.10.2018 4:39 LimyKurn . Предыдущая версия .
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.