Система Orphus

понедельник, 5 января 2009 г.

Ещё причины, почему не надо делать ничего страшного в DllMain: случайная блокировка

Это перевод Another reason not to do anything scary in your DllMain: Inadvertent deadlock. Автор: Реймонд Чен.

Ваша функция DllMain работает внутри блокировки загрузчика - это один из немногих моментов, когда ОС позволяет вашему коду выполняться в момент удерживания внутренней блокировки. Это означает, что ваш код должен быть очень внимателен, чтобы не нарушить иерархию блокировок в своей DllMain; иначе вам грозит мёртвая блокировка.

(У вас ведь есть такая иерархия, да?).

Блокировка загрузчика ОС требуется для любой функции, которой нужен доступ к списку загруженных в процесс DLL. Это включает в себя такие функции как GetModuleHandle и GetModuleFileName. Если ваша DllMain входит в критическую секцию или ожидает на объекте синхронизации, а эти критическая секция или объект синхронизации принадлежат (owned) какому-то другому потоку, который, в свою очередь, ждёт освобождения блокировки загрузчика, то вы только что создали мёртвую блокировку (deadlock):
// Глобальная переменная
var
  csGlobal: TCriticalSection;

  // Где-то есть такой код
  csGlobal.Enter;
  ... GetModuleFileName(HInstance, ...);
  csGlobal.Leave;

// Эта процедура присваивается DllProc
procedure MyDllMain(dwReason: DWord);
begin
  case dwReason of
  ...
    DLL_THREAD_DETACH:
    begin
      csGlobal.Enter;
      ...
    end;
  ...
  end;
end;
Теперь представьте, что какой-то поток счастливо выполняет первый блок кода и входит в csGlobal, потом управление передайтся ещё кому-то. В это время другой поток завершает свою работу. При этом берётся блокировка загрузчика и рассылается сообщение DLL_THREAD_DETACH (блокировка загрузчика в это время держится).

Вы получаете DLL_THREAD_DETACH и пытаетесь войти в csGlobal. Это блокируется первым потоком, который сейчас владеет критической секцией. Потом этот поток продолжает выполнение и вызывает GetModuleFileName. Эта функция требует блокировки загрузчика (поскольку ей нужен доступ к списку DLL, загруженных в процесс), поэтому поток блокируется, потому что блокировкой загрузчика владеет кто-то ещё.

Теперь у вас deadlock:
  • csGlobal-ом владеет первый поток, который ждёт блокировки загрузчика.
  • Блокировкой загрузчика владеет второй поток, который ждёт csGlobal.
Я видел, как такое случалось. И ничего красивого в этом нет.

Мораль истории: не забывайте про блокировку загрузчика. Включайте её в свои иерархии блокировок, если вы хотите использовать любые блокировки в своей DllMain.

0 комментарий(ев):


Отправить комментарий

Можно использовать некоторые HTML-теги, например:

<b>Жирный</b>
<i>Курсив</i>
<a href="http://www.example.com/">Ссылка</a>

Вам необязательно регистрироваться для комментирования - для этого просто выберите из списка "Анонимный" (для анонимного комментария) или "Имя/URL" (для указания вашего имени и ссылки на сайт). Все прочие варианты потребуют от вас входа в вашу учётку (поддерживается OpenID).

Пожалуйста, по возможности используйте "Имя/URL" вместо "Анонимный". URL можно просто не указывать.

Ваше сообщение может быть помечено как спам спам-фильтром - не волнуйтесь, оно появится после проверки администратором.