Форум программистов «Весельчак У»
  *
Добро пожаловать, Гость. Пожалуйста, войдите или зарегистрируйтесь.
Вам не пришло письмо с кодом активации?

  • Рекомендуем проверить настройки временной зоны в вашем профиле (страница "Внешний вид форума", пункт "Часовой пояс:").
  • У нас больше нет рассылок. Если вам приходят письма от наших бывших рассылок mail.ru и subscribe.ru, то знайте, что это не мы рассылаем.
   Начало  
Наши сайты
Помощь Поиск Календарь Почта Войти Регистрация  
 
Страниц: [1]   Вниз
  Печать  
Автор Тема: Подскажите правильный способ синхронизации  (Прочитано 12928 раз)
0 Пользователей и 2 Гостей смотрят эту тему.
ra_
Гость
« : 23-04-2008 06:02 » 

Здравствуйте.
Пишу WDM-драйвер под коммуникационное устройство с DMA. Работа с ним должна быть а-ля "файл в файловой системе": открыть, закрыть, записать, прочитать, иоктл. Это мой первый драйвер, поэтому вопросы, возможно, "чайничные".

Придумал способ, как сделать синхронизацию, но есть ли способы проще и лучше?

Рассмотрим запись данных в девайс. Вполне достаточно дать пользователю возможность блокирующей записи (если в момент вызова пользователем WriteFile ещё записывается предыдущий кадр, пусть WriteFile висит).

Для отправки нужно:
- положить данные в DMA-буфер
- записать в определённый регистр девайса длину.
После завершения записи девайс дёрнет прерывание "закончил запись".

----------------
Изначально lock свободен.

В обработчике IRP_MJ_WRITE
- жду освобождения lock, как только освободится - захватываю (KeAcquireXXX)
- копирую память через RtlMoveMemory
- пишу регистр в девайс

В обработчике прерывания ISR
- разлочиваю lock (по идее, здесь он может быть только залоченным)
----------------
Делаю это для того, чтобы повторный вызов WriteFile в момент, пока девайс ещё занят предыдущим кадром, застыл до готовности устройства.

Всё вроде прямо и логично.

Но теперь - вопросы.
1. Что лучше использовать в качестве lock (по идее, должно годиться что-то вроде mutex'а, ибо надо только два состояния)?
2. Не наступлю ли на грабли с IRQL, не может ли похериться синхронизация
3. Не заторможу ли систему излишне?
4. Как вообще люди делают в такой ситуации? Она простая и типичная, должны быть стандартные пути.

Спасибо за внимание.
Записан
Ochkarik
Модератор

ru
Offline Offline
Пол: Мужской

« Ответ #1 : 23-04-2008 11:35 » 

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

в качестве блокировки, быстрый мьютекс возможен только на IRQL <= APC_LEVEL, освобождать его не получится не в ISR не в DPCforISR.
можно было бы и SpinLock попробовать, но его ожидать другим процессом/нитью - нельзя... видимо только обычный мъютекс остается. его в DPC можно освобождать.
Еще можно попробовать делать PENDING для IRP MJ_Write и завершать его в DPC функцией IoCompletion. но тогда надо вначале IRP какое то ожидание завершения предыдущего придумать. не сильно проще мьютекса получается.

с IRQL граблей особых быть не должно. если все правильно) если железка не "забудет" прерывание вернуть.

про тормоза написал - зависит о размера пакета, частоты, и времени обработки.

не понял - зачем RtlMoveMemory а не RtlCopyMemory?


можно еще и собственную очередь запросов сделать..  если надо)
Записан

RTFM уже хоть раз наконец!  RTFM :[ ну или хотя бы STFW...
ra_
Гость
« Ответ #2 : 25-04-2008 07:14 » 

Спасибо за своевременный и точный ответ!

не понял - зачем RtlMoveMemory а не RtlCopyMemory?

Вкурил маны. Принимается: она должна быть быстрее и мне заведомо не нужны особенности MoveMemory.

Теперь о том, как реализовал и с чем столкнулся.

-------
Инициализировал в AddDevice:
  KeInitializeMutex(&fdoData->WriteMutex, 0);

В обработчике IRP_MJ_WRITE написал:
  Диагностика ("начало ожидания лока");
  status = KeWaitForSingleObject(&FdoData->WriteMutex, Executive, KernelMode, FALSE, NULL);
  Диагностика ("лок занят");
  // действия с памятью DMA
  // отмашка девайсу "бери память и шли"

В отложенном обработчике прерывания "окончания записи" написал:
  Диагностика ("(DPC) освобождение лока");
  result= KeReleaseMutex(&fdoData->WriteMutex, FALSE);
-------

В итоге получил не совсем корректное поведение.
Девайс реализует что-то вроде сети. Прерывание выставляет после того, как отправил кадр в сеть. Кадр отправляется только когда девайс "на другом конце" включает приёмник.

В случае, когда запись ведётся в готовую для этого среду (кабель подключен, девайс на другом конце готов принимать) - всё работает как надо. Вижу диагностические сообщения "начало ожидания лока", "лок занят", "(DPC) освобождение лока".  Класс!

Когда пишу в момент "неготовности" среды (выдёргиваю кабель) - получается корявство.
1. Делаю первую отправку кадра из юзерского приложения.
   1.1. Вижу "начало ожидания лока"
   1.2. Вижу "лок занят".
   1.3. Не вижу "(DPC) освобождение лока"
   Всё как надо, ибо прерывание не возникает (девайс слать не может), лок освобождаться не должен и должен висеть в занятом состоянии.  Класс!

2. Делаю вторую отправку кадра из юзерского приложения.
   2.1. Вижу "начало ожидания лока"
   2.2. Вижу "лок занят"
    Не может быть... ПОЧЕМУ? Ведь мы его уже заняли на первом шаге. WaitForSingleObject не должен отдавать управление коду дальше, пока лок не освободится!  Я шокирован!
   2.3. Приложение пользователя не висит и снова готово слать. Ожидал, что застынет.

3. Совсем странно. Втыкаю кабель (фактически - тут же получаю прерывание).
   3.1. Вижу бсод STOP: 0x008E (C0000046, 80502D11, FC8D7BD4, 00000000)  Быть такого не может

Собственно, отчего WaitForSingleObject не застывает??? Может быть, надо вызывать его с таймаутом (я NULL передавал, думая, что это бесконечность)?
Ну и откуда BSOD при вызове ReleaseMutex???
Записан
Ochkarik
Модератор

ru
Offline Offline
Пол: Мужской

« Ответ #3 : 25-04-2008 10:22 » 

NULL- это действительно бесконечность.

по поводу 1.
 - вообще оно конечно правильно, но я бы поставил ограниченное время ожидания - мало ли... вдруг вобще ответа не будет? тогда приложение зависнет намертво. и ничем снять его уже не получится! потому что зависнит оно в Kernel на этом самом ожидании. а диспечер задач - такую задачу снять не умеет. только, если вы разрешите прерывание ожидания по APC (Alertable = TRUE). думаю что так и стоит поступить. если решите - внимательно про условия прочтиайте. там их куча... и статус надо будет проверять, и вобще много чего продумать про зачистку.

по поводу 2.
- второй кадр отправляется из того же потока?
- кстати IRQL = Passive?
- status = KeWaitForSingleObject - статус проверяется? очень советую)
далее главное по KeWaitForSingleObject:
"WaitReason - If the driver is doing work _on behalf of a user_! and _is running in the context of a user thread_!, this parameter should! be set to UserRequest. " - это вроде как раз ваш случай. так что все таки UserRequest а не Executive) Executive - для нитей созданных в kernel и начверное для системных нитей.
вся беда с DDK в том что там каждое предложение значимо, надо вчитываться...))

по поводу 3.
008E - это  KERNEL_MODE_EXCEPTION_NOT_HANDLED.
соответственно эксцепшен был таким:
//  An attempt to release a mutant object was made by a thread that was not the owner of the mutant object.
#define STATUS_MUTANT_NOT_OWNED          ((NTSTATUS)0xC0000046L)
видимо, это следствие 2...
Записан

RTFM уже хоть раз наконец!  RTFM :[ ну или хотя бы STFW...
aks68
Модератор

il
Offline Offline
Пол: Мужской

« Ответ #4 : 25-04-2008 20:45 » new

Добрый день!

...можно было бы и SpinLock попробовать, но его ожидать другим процессом/нитью - нельзя...

Простите Ochkarik, а почему нельзя использовать SpinLock? По мне так самое оно, в свете ГиперСредизации и ДуалКоризации наших десктопов Улыбаюсь.

С важением,
Акс.
Записан
ra_
Гость
« Ответ #5 : 28-04-2008 05:23 » 

Спасибо за ответы по сути. Нравится мне ваш форум!

по поводу 1.
 - вообще оно конечно правильно, но я бы поставил ограниченное время ожидания - мало ли... вдруг вообще ответа не будет? тогда приложение зависнет намертво. и ничем снять его уже не получится! ... только, если вы разрешите прерывание ожидания по APC (Alertable = TRUE).
В целом принимается. Сделал "полу-поллинг": жду 10мс, затем, если таймаут, снова жду 10мс. По идее, ядро нагружается посильнее, чем при бесконечности, но не намного. Зато поток не всегда находится в Wait.
Но вот насчёт "намертво зависнет" - не противоречит ли это цитатам
Цитата
If the value of <Alertable> is FALSE then the thread cannot be alerted, no matter what the value of the WaitMode parameter or the origin of the alert. The only exception to this rule is that of a terminating thread. Under certain circumstances a terminating thread can be alerted while it is in the process of winding down.
и
Цитата
A special consideration applies when the Object parameter passed to KeWaitForSingleObject is a mutex. If the dispatcher object waited on is a mutex, APC delivery is the same as for all other dispatcher objects during the wait.
, ведь мы как раз говорим о ситуации, когда поток висит на ожидании.
Но в целом я тут новичок, и спорить не стану; поэтому сделал описанный "полу-поллинг".

- второй кадр отправляется из того же потока?
- кстати IRQL = Passive?
- status = KeWaitForSingleObject - статус проверяется? очень советую)
Да, из того же пользовательского потока. KeGetCurrentIrql() возвращает 0, то есть Passive. Со статусом - вы как в воду глядели: проверял я его через NT_SUCCESS(), то есть фактически не реагировал.

далее главное по KeWaitForSingleObject:
"WaitReason - If the driver is doing work _on behalf of a user_! and _is running in the context of a user thread_!, this parameter should! be set to UserRequest. " - это вроде как раз ваш случай. так что все таки UserRequest а не Executive) Executive - для нитей созданных в kernel и начверное для системных нитей.
вся беда с DDK в том что там каждое предложение значимо, надо вчитываться...))
Тут тоже прям в точку, "RTFM уже хоть раз наконец" Улыбаюсь Поставил UserRequest.

Но вот с этим
//  An attempt to release a mutant object was made by a thread that was not the owner of the mutant object.
главный вопрос у меня. Изначально описанное условие как раз и подразумевает, что лок захватывается в одном месте - на IRQL Passive, в коллбэке MJ_WRITE - и освобождается в другом месте (в другом потоке?) - на IRQL Dispatch, в DPC. То есть освобождаться лок должен не там, где занимается.

Что из этого следует? Что Mutex не подходит для решения задачи что ли? Быть может, воспользоваться Event'ом или чем-нибудь ещё?

Собственно начал сомневаться вот почему. Поправки описанные внёс в программу ("полу-поллинг", проверку возвращаемого значения, UserRequest), а поведение осталось в точности таким же, как в предыдущем моём посте. Те же три шага. А черт его знает...
Записан
sss
Специалист

ru
Offline Offline

« Ответ #6 : 28-04-2008 06:16 » 

ra_, покажи пользовательский CreateFile... Да и вообще, первый раз lock свободен, поэтому приходит второй... в общем, в WRITE диспетчере надо проверять состояние среды и если есть ошибки, устанавливать код и сразу выходить...
« Последнее редактирование: 28-04-2008 06:27 от sss » Записан

while (8==8)
ra_
Гость
« Ответ #7 : 29-04-2008 02:51 » 

ra_, покажи пользовательский CreateFile...
Всё прямолинейно.
m_hFile= CreateFile (
      "\\\\.\\myDevice",
      GENERIC_READ | GENERIC_WRITE,
      0,
      NULL, // no SECURITY_ATTRIBUTES structure
      OPEN_EXISTING, // No special create flags
      0,
      NULL);
...
if (WriteFile (m_hFile, buf, sizeof (buf), &ActuallyWritten, NULL))
...


Да и вообще, первый раз lock свободен, поэтому приходит второй... в общем, в WRITE диспетчере надо проверять состояние среды и если есть ошибки, устанавливать код и сразу выходить...
В первом шаге я и не удивлялся. Во втором writefile - да, когда снова лок успешно занимается. Надо сказать, что где два, там и двадцать. Двадцать раз жмём WriteFile - каждый раз лок успешно занимается.
А должен застывать на втором, если я всё правильно понимаю.
Записан
ra_
Гость
« Ответ #8 : 29-04-2008 05:39 » 

Вопрос решён.  Улыбаюсь) Спасибо всем за содействие.

Для моего случая идеальным оказалось использование Event'а.

-------
инициализация
    // изначально signaled, чтобы первое же ожидание не застряло навсегда.
    KeInitializeEvent(&fdoData->WriteLock, SynchronizationEvent, TRUE);

-------
ожидание в MJ_WRITE
Код:
tv.QuadPart= -10000L * N;	// N миллисекунд
do
{
    status = KeWaitForSingleObject(&FdoData->WriteLock, UserRequest, UserMode, FALSE, &tv);
    // два "нормальных" исхода - success и таймаут. По таймауту повторять, по success продолжать
}
while (status == STATUS_TIMEOUT);

if (status != STATUS_SUCCESS)
    // ругнуться и срочно выйти
внимание, тут есть грабли (внимательно вчитаться в описание мне надо было). В принципе, можно и без таймаута обойтись, но если уж использовать таймаут, то надо указывать его отрицательным числом. Положительные - абсолютное время, и wait будет сразу вылетать, сжирая процессор в кернелмоде.
Такое ожидание (UserRequest, UserMode) позволяет прерывать ожидание через завершение приложения диспетчером задач. Тогда возвращаемое значение будет C0, то есть STATUS_USER_APC, при этом надо побыстрее выходить из MJ_WRITE.

-------
сигнализирование из DPC
    if (KeSetEvent(&fdoData->WriteLock, IO_NO_INCREMENT, FALSE) != 0)
        // не должно быть
-------
Работает хорошо, доволен.
Записан
ra_
Гость
« Ответ #9 : 29-04-2008 06:12 » 

Вот, ещё вопрос возник.
Нужно ли вызывать какой-то ядерный CloseHandle на полученных из KeInitializeEvent и т.п. хэндлах? И, если нужно, как называется функция?

В примерах из WinDDK (во всяком случае из src/general) полученные от ядра хэндлы так и остаются незакрытыми. Это нормально?
Записан
Ochkarik
Модератор

ru
Offline Offline
Пол: Мужской

« Ответ #10 : 30-04-2008 10:44 » 

тока что их командировки - не мог ответить...
динициализации не надо. вместо нее - освобождение памяти.

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

просто у меня быстрые мьютексы стоят на службе синхронизации выполнения из разных нитей... но не освободив мьютекс - я из Dispatch ен выхожу. поэтому все корректно. а асинхронные выводы у меня на Event-ax  как раз...

в общем извиняюсь за введение в заблуждение)

Записан

RTFM уже хоть раз наконец!  RTFM :[ ну или хотя бы STFW...
Страниц: [1]   Вверх
  Печать  
 

Powered by SMF 1.1.21 | SMF © 2015, Simple Machines