Как вам мое изобретение для синхронизации каллбеков в драйве
От: 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 . Предыдущая версия .
Re: Как вам мое изобретение для синхронизации каллбеков в др
От: okman Беларусь https://searchinform.ru/
Дата: 08.10.18 05:40
Оценка: 2 (1)
Здравствуйте, LimyKurn, Вы писали:

LK>Надо просто переделать архитектуру.

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

LK>А как вам?


На мой взгляд, затея так себе. Объясню почему.

* Создание потока (PsCreateSystemThread) — это достаточно дорогая операция. Не знаю, откуда ты взял,
что "создание потока почти ничего не стоит". Нужно создать и проинициализировать объект ядра, создать для
него хэндл, заполнить кучу всяких системных структур, поместить новый объект во всевозможные списки и т.д.

* Между функцией потока и менеджером фильтров (FltMgr) не будет никакой синхронизации.
А она нужна. Например, для безопасного выполнения teardown фильтра.

* Не всегда из post-калбэка вообще допустимо создавать поток и откладывать выполнение I/O-операции.
См. примечание к функции FltQueueDeferredIoWorkItem:

The I/O operation cannot be posted safely to a worker thread. Possible reasons include the following:

FltQueueDeferredIoWorkItem cannot post an I/O operation to a worker thread if the TopLevelIrp field of the
current thread is not NULL, because the resulting file system recursion could cause deadlocks or stack overflows.
(For more information, see IoGetTopLevelIrp.)

FltQueueDeferredIoWorkItem cannot post a paging I/O operation to a worker thread.


Ну и последнее. Как ты будешь создавать поток в post-калбэке, если там IRQL может быть APC_LEVEL/DISPATCH_LEVEL, а
PsCreateSystemThread требует PASSIVE_LEVEL?

LK>А какого-то другого потокобезопасного контейнера, который бы не был связным списком, в NT просто нет.


RtlInitializeGenericTableAvl с синхронизацией через спинлоки. Чем не вариант?
Отредактировано 08.10.2018 5:43 okman . Предыдущая версия .
Re[2]: Как вам мое изобретение для синхронизации каллбеков в др
От: pva  
Дата: 08.10.18 06:28
Оценка:
Здравствуйте, okman, Вы писали:

O>Ну и последнее. Как ты будешь создавать поток в post-калбэке, если там IRQL может быть APC_LEVEL/DISPATCH_LEVEL, а

O>PsCreateSystemThread требует PASSIVE_LEVEL?
Создать пул потоков при старте и рулить не выйдет?
newbie
Re[3]: Как вам мое изобретение для синхронизации каллбеков в др
От: okman Беларусь https://searchinform.ru/
Дата: 08.10.18 07:03
Оценка:
Здравствуйте, pva, Вы писали:

pva>Здравствуйте, okman, Вы писали:


O>>Ну и последнее. Как ты будешь создавать поток в post-калбэке, если там IRQL может быть APC_LEVEL/DISPATCH_LEVEL, а

O>>PsCreateSystemThread требует PASSIVE_LEVEL?
pva>Создать пул потоков при старте и рулить не выйдет?

Я пока не понимаю смысл городить все эти огороды с потоками, вот хоть убей
Есть калбэки, есть очередь, в которую надо писать данные, поступающие из калбэков.
Есть IRQL, который может быть <= DISPATCH_LEVEL.
Значит, синхронизация будет либо на спинлоках, либо на interlocked-функциях. Все.
Зачем здесь потоки? Чтобы избежать использования спинлоков?.. Подождем топикстартера.
Re: Как вам мое изобретение для синхронизации каллбеков в драйве
От: ononim  
Дата: 08.10.18 11:18
Оценка:
Если собираешься асинхронно считать хэш записываемых данных, то придется синхронизироваться с потоком (а иначе данные и/или файл могут уплыть из под ног). А этом опять — низзя .

Но на будущее, прежде чем городить костыли на потоках, узнай о существовании ExQueueWorkItem, KeInsertQueueAPC, KeInsertQueueDpc...
Как много веселых ребят, и все делают велосипед...
Re[4]: Как вам мое изобретение для синхронизации каллбеков в др
От: LimyKurn  
Дата: 09.10.18 01:18
Оценка:
Здравствуйте, okman, Вы писали:

O>Здравствуйте, pva, Вы писали:


pva>>Здравствуйте, okman, Вы писали:


O>>>Ну и последнее. Как ты будешь создавать поток в post-калбэке, если там IRQL может быть APC_LEVEL/DISPATCH_LEVEL, а

O>>>PsCreateSystemThread требует PASSIVE_LEVEL?
pva>>Создать пул потоков при старте и рулить не выйдет?

O>Я пока не понимаю смысл городить все эти огороды с потоками, вот хоть убей

O>Есть калбэки, есть очередь, в которую надо писать данные, поступающие из калбэков.
O>Есть IRQL, который может быть <= DISPATCH_LEVEL.
O>Значит, синхронизация будет либо на спинлоках, либо на interlocked-функциях. Все.
O>Зачем здесь потоки? Чтобы избежать использования спинлоков?.. Подождем топикстартера.

Сейчас у меня вопрос не только о синхронизации, но и о быстродействии. Иначе можно было бы так и оставить: просто SList в его стандартном использовании, и при каждой операции заново считать хеш
Но надо понимать требования к быстродействию. Давай посчитаем. 100.000.000 байт вес файла. Копируется он кусочками по 1.000 байт. То есть 100.000 операций. А максимальный прирост времени на копирование не должен превысить 2.000.000 мксек. То есть в среднем 20 мксек на каждый вызов каллбека.
Спинлоки и тому подобное — замедляет. Ведь в системе одновременно много какие файлы модифицируются.
Re[2]: Как вам мое изобретение для синхронизации каллбеков в
От: LimyKurn  
Дата: 09.10.18 01:27
Оценка:
Здравствуйте, okman, Вы писали:

O>RtlInitializeGenericTableAvl с синхронизацией через спинлоки. Чем не вариант?

Тем, что какой-то нетипичный тип контейнера.
Гораздо более понятный вариант: обычный SList, а где требуется "оглядываться назад", там использовать не функции для SList, а циклы через Next, завернутые в спинлок, тот же самый, который передается в функции для SList.
Этот вариант точно медленнее, чем AVL со спинлоками?
Отредактировано 09.10.2018 1:28 LimyKurn . Предыдущая версия .
Re[2]: Как вам мое изобретение для синхронизации каллбеков в
От: LimyKurn  
Дата: 09.10.18 01:39
Оценка:
Здравствуйте, ononim, Вы писали:

O>Но на будущее, прежде чем городить костыли на потоках, узнай о существовании ExQueueWorkItem, KeInsertQueueAPC, KeInsertQueueDpc...


Почитал про них. Как я понял, ты предлагаешь по сути также вынести всю обработку из каллбеков минифильтра, но штатными средствами, и потоков они создадут меньше.
Но непонятно, стоит ли вообще ее выносить и тем более делать из этого какой-то фреймворк, который использовать для разработки минифильтров в будущем, или лучше не городить подобных архитектурных изысков, а просто применить одно из решений типа "какой-то контейнер + спинлоки", как предлагает okman.
Отредактировано 09.10.2018 1:41 LimyKurn . Предыдущая версия .
Re[3]: Как вам мое изобретение для синхронизации каллбеков в
От: ononim  
Дата: 09.10.18 11:42
Оценка:
O>>Но на будущее, прежде чем городить костыли на потоках, узнай о существовании ExQueueWorkItem, KeInsertQueueAPC, KeInsertQueueDpc...
LK>Почитал про них. Как я понял, ты предлагаешь по сути также вынести всю обработку из каллбеков минифильтра, но штатными средствами, и потоков они создадут меньше.
Ох уж это клиповое мышление. Пока читал второй абзац — первый выпал из памяти.
Сокращенный вариант: использование потоков в данном решение тут бессмысленно, т.к. породит больше проблем чем было, причем не исключая имеющейся проблемы. Но если в будущем захочется использовать потоки для чего либо еще — то вот готовый инструментарий, чтоб не делать кривые велосипеды.
Как много веселых ребят, и все делают велосипед...
Re[4]: Как вам мое изобретение для синхронизации каллбеков в
От: LimyKurn  
Дата: 09.10.18 12:06
Оценка:
Здравствуйте, ononim, Вы писали:

O>Сокращенный вариант: использование потоков в данном решение тут бессмысленно, т.к. породит больше проблем чем было

Не надо все проблемы валить в одну кучу. Есть проблема потокобезопасности, а есть проблема быстродействия. И для решения второй проблемы кажется идеально логичным: раз обработка отнимает время, будучи в каллбаке, то надо убрать ее куда подальше из этого каллбака.
Re[5]: Как вам мое изобретение для синхронизации каллбеков в
От: ononim  
Дата: 09.10.18 12:37
Оценка:
O>>Сокращенный вариант: использование потоков в данном решение тут бессмысленно, т.к. породит больше проблем чем было
LK>Не надо все проблемы валить в одну кучу. Есть проблема потокобезопасности, а есть проблема быстродействия. И для решения второй проблемы кажется идеально логичным: раз обработка отнимает время, будучи в каллбаке, то надо убрать ее куда подальше из этого каллбака.
А как собираетесь просить колбэк не удалять данные, которые вы обрабатывать собираетесь в соседнем потоке?
Как много веселых ребят, и все делают велосипед...
Re[6]: Как вам мое изобретение для синхронизации каллбеков в
От: mike_rs Россия  
Дата: 12.10.18 19:25
Оценка:
Здравствуйте, ononim, Вы писали:

O>>>Сокращенный вариант: использование потоков в данном решение тут бессмысленно, т.к. породит больше проблем чем было

LK>>Не надо все проблемы валить в одну кучу. Есть проблема потокобезопасности, а есть проблема быстродействия. И для решения второй проблемы кажется идеально логичным: раз обработка отнимает время, будучи в каллбаке, то надо убрать ее куда подальше из этого каллбака.
O>А как собираетесь просить колбэк не удалять данные, которые вы обрабатывать собираетесь в соседнем потоке?
видимо он их сразу сериализует в контекст потока, какие еще варианты?
Re[5]: Как вам мое изобретение для синхронизации каллбеков в др
От: -prus-  
Дата: 14.10.18 14:03
Оценка:
LK>Сейчас у меня вопрос не только о синхронизации, но и о быстродействии. Иначе можно было бы так и оставить: просто SList в его стандартном использовании, и при каждой операции заново считать хеш
LK>Но надо понимать требования к быстродействию. Давай посчитаем. 100.000.000 байт вес файла. Копируется он кусочками по 1.000 байт. То есть 100.000 операций. А максимальный прирост времени на копирование не должен превысить 2.000.000 мксек. То есть в среднем 20 мксек на каждый вызов каллбека.
LK>Спинлоки и тому подобное — замедляет. Ведь в системе одновременно много какие файлы модифицируются.

Сейчас, как я понял, у вас один глобальный SList на всех с глобальной синхронизацией к нему... Можно попробовать для каждого отдельного stream'a в PostCreate-callback'e, например, создавать контекст FltAllocateContext(FLT_STREAM_CONTEXT) и там в нем иметь свой список SList со своей синхронизацией к нему. Таким образом у вас на каждый файл будет свой список и свой отдельный объект синхронизации. Далее наполняете списки и тут же проверяете их полноту в PreWrite/PreRead-callback'ах (ну или где удобно). Если наполнился — сбрасываете данные текущего списка куда хотите (отдельному потоку или куда там еще).
С уважением,
Евгений
Re[6]: Как вам мое изобретение для синхронизации каллбеков в др
От: LimyKurn  
Дата: 15.10.18 15:42
Оценка:
Здравствуйте, -prus-, Вы писали:

LK>>Сейчас у меня вопрос не только о синхронизации, но и о быстродействии. Иначе можно было бы так и оставить: просто SList в его стандартном использовании, и при каждой операции заново считать хеш

LK>>Но надо понимать требования к быстродействию. Давай посчитаем. 100.000.000 байт вес файла. Копируется он кусочками по 1.000 байт. То есть 100.000 операций. А максимальный прирост времени на копирование не должен превысить 2.000.000 мксек. То есть в среднем 20 мксек на каждый вызов каллбека.
LK>>Спинлоки и тому подобное — замедляет. Ведь в системе одновременно много какие файлы модифицируются.

P>Сейчас, как я понял, у вас один глобальный SList на всех с глобальной синхронизацией к нему... Можно попробовать для каждого отдельного stream'a в PostCreate-callback'e, например, создавать контекст FltAllocateContext(FLT_STREAM_CONTEXT) и там в нем иметь свой список SList со своей синхронизацией к нему. Таким образом у вас на каждый файл будет свой список и свой отдельный объект синхронизации. Далее наполняете списки и тут же проверяете их полноту в PreWrite/PreRead-callback'ах (ну или где удобно). Если наполнился — сбрасываете данные текущего списка куда хотите (отдельному потоку или куда там еще).


Спасибо. Очередной новый, незнакомый мне вариант. Но не хочется вот убирать этот общий поток. Так что начну все-таки с идеи okman'а: спинлоки, и не SList убогий, а AVL.
Re[7]: Как вам мое изобретение для синхронизации каллбеков в др
От: -prus-  
Дата: 15.10.18 17:27
Оценка:
LK>Спасибо. Очередной новый, незнакомый мне вариант. Но не хочется вот убирать этот общий поток. Так что начну все-таки с идеи okman'а: спинлоки, и не SList убогий, а AVL.

Про AVL я вам уже говорил
Автор: -prus-
Дата: 24.09.18
. Его можно и в эту логику подтянуть
С уважением,
Евгений
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.