Есть драйвер, которому нужно периодически отправлять управляющей программе небольшие сообщения, к примеру, текстовые строки. Формирование сообщений происходит в контексте других процессов. Управляющая программа при этом не всегда имеет возможность немедленно отреагировать на уведомление драйвера, т.е., сообщения должны где-то накапливаться. Как лучше и безопаснее в плане отказоустойчивости реализовать такую передачу?
Ну про APC уже сказали. Чуть более подробно если, то это выглядит примерно так. Если нужна скорость и данных относительно много, то следует реализовать что-то вроде менеджера памяти (по сути, собственная реализация кучи, но можно использовать и системные функции типа RtlCreateHeap). Драйвер выделяет/освобождает большой (или не очень) кусок виртуальной памяти в процессе управляющего приложения, соответственно, он же решает и все проблемы с фрагментацией и пр. При наступлении события, информацию о котором необходимо передать в приложение, драйвер выделяет блок из этой кучи, блок логически делится на заголовок (длина блока, тип информации в блоке и т.п.) и непосредственно полезные данные блока (информация, которую должно получить приложение). Далее драйвер создаёт APC, в качестве параметров которого указывается NormalRoutine = некая функция в процессе приложения, и NormalContext = адрес выделенного блока, ну и хендл потока приложения, который ждёт этого APC. В приложении происходит следующее. При запуске оно создаёт поток, единственная задача которого это крутиться в цикле, пока не получена команда на выход, и постоянно звать SleepEx (..., TRUE) для того, чтобы спать в режиме тревожного ожидания (alertable). Также в приложении должна быть реализована функция, имеющая прототип Normal APC routine (см. ниже). При инициализации драйвера приложение должно передать ему, как минимум, следующую информацию: хендл ждущего потока и виртуальный адрес APC-функции, - эти данные драйвер будет использовать при инициализации структур KAPC специальными функциями вроде KeInitializeApc, KeInsertQueueApc и т.д. Код (Text): VOID NormalRoutine ( IN PVOID NormalContext OPTIONAL, IN PVOID SystemArgument1 OPTIONAL, IN PVOID SystemArgument2 OPTIONAL); Эта функция будет вызываться драйвером в контексте того самого потока, который постоянно спит в режиме тревожного ожидания. В качестве NormalContext в неё придёт виртуальный адрес блока памяти, в начале которого заголовок, потом данные. По окончании работы с этим блоком приложение должно сообщить об этом драйверу, например, посредством IOCTL-запроса. В ответ на такой запрос драйвер освободит указанный блок памяти для использования его в дальнейших передачах информации. Ну и напоследок скажем, что если данных не много или они шлются не часто, то не имеет смысла городить свою кучу, вместо этого каждый новый блок памяти можно выделять посредством ZwAllocateVirtualMemory. Если есть более конкретные вопросы по этому методу, - пишите, отвечу.
Моё мнение - стабильность данного метода зависит от прямоты рук. Ну можете попробовать доказать обратное, я с удовольствием послушаю.
ormoulu Товариш руссинович обычно реализовывал shared memory . практически не ограниченная память, производительно, стабильно , и так далее ...
shchetinin В последних его утилитах таки да, наверное, так и следует сделать. x64 Чтобы что-то доказывать, нужно в этом убедиться, а я такой уверенности не имею. APC в этом варианте используется только для уведомления приложения, ведь так? Есть ли тогда у него преимущества перед эвентом?
А какой смысл пользоваться именно APC в этом случае? Можно допустим точно так же выделить в драйвере кусок памяти в процессе и организовать там кольцевой буфер событий с данными. В юзермоде приложение будет спать на ивенте, который драйвер будет сигналить когда появляются новые данные. *Кстати тут можно обойтись lock-free доступом к буферу* Если приложение долго не обрабатывает данные (конец буфера указывает на начало) драйвер либо дропает события, либо ждёт, либо ещё чтонить - всё зависит от конкретики.
x64 Ого скока текста.. Хотя его смысла нет читать, так как галимый аверсикий изврат. Не удивительно что авер расписал этот вопрос. Надеюсь не будите оправдываться, если знаете как Т-фреймами манипулировать ? Забудьте про эту г0вноапи. Она занимает весь нтдлл. Там при совершенно левых условиях возникает фолт, да и нахера оно нужно, если есть фастколы непосредственно.
Мне нравится. Удобно. В самой винде оно тоже используется, например, в сокетной подсистеме. Во-первых, ты мог бы рассказать об этом поподробнее. Во-вторых, почти все приложения используют кучу и работают при этом. В ядре нет реализации кучи, это единственный способ использовать готовое решение. Ну то есть, Nt-сервисы здесь ни чем не помогут, к сожалению.
Во-первых, удобно. Дело в том, что, как правило, требуется передавать приложению данные в виде последовательности пакетов. Особенно наглядно это можно увидеть при написании, например, сетевого фильтра. А механизм APC значительно ближе к понятию "очередь", чем shared memory. Это означает, что разработчику не нужно писать весь этот вспомогательный код, связанный с организацией и управлением очередью. Во-вторых, APC быстрее ибо прерывания, в то время, как события и мутексы - чисто программные сущности. Конкретных выкладок я вам не скажу, разумеется, не замерял, но можете проделать эти тесты самостоятельно, думаю, прирост производительности будет очевиден. Ну и, наконец, очереди на базе APC используются повсеместно в самой Windows. Например, в подсистеме сокетов, в случае создания не-IFS сокета, в игру вступает драйвер ws2ifsl.sys, который перенаправляет все I/O-запросы в приложение, создавшее сокет, и делает он это посредством APC. А если ещё вспомнить, что Microsoft придаёт огромное значение производительности сетевой подсистемы в целом, то, думаю, что их выбор на APC пал совсем не просто так. Такие дела.
x64 Все так, только вот заряжать APC в дравере имея линк из приложения это как то ... . Уж лучше пачку MJ_READ запросов, по производительность будет очень быстро, и дизайн будет хороший и не через Ж ( хотя это тоже APC). А вообще LPC, PIPE это если приложения без привилегий, то это остается единственный вариант ... (К стати и самый универсальный, и давольно таки быстрый ... )
Я думаю с производительностью тут вопрос не так однозначно решается. Если события будут возникать "равномерно во времени", то наверное будет быстрее APC, а если события возникают в какие-то определённые моменты "пачкой" так сказать - то ивент может быть быстрее из-за того что обработчик в юзермоде будет обрабатывать события по нескольку за одну свою итерацию.
Тоже занимаюсь подобной задачей. APC и KeUserModeCallback. Вызов первого асинхонный а второго синхронный?