Управляемые ресурсы в ядре Linux (также известны как Device Resource Management или devres API), о которых я писал небольшую заметку ранее, — вещь крайне полезная, но не стоит воспринимать этот вспомогательный набор функций как серебрянную пулю при написании драйверов или модификации существующих. Рассмотрим случаи, где нужно аккуратно применять данные методы.

Регистрирование обработчика прерывания при наличии tasklet'ов


Про прерывания и tasklet'ы доходчиво описано в статье в блоге компании EmBox, поэтому подразумевается, что читатель уже ознакомлен с этим или подобным материалом.

Возьмём в качестве примера следующий псевдокод:
struct my_struct {
…
  struct tasklet_struct *tasklet;
  int irq;
};

void tasklet_handler(…)
{
  do_the_things_right(…); 
}

irqreturn_t irq_handler(void *param)
{
  struct my_struct *ms = param;
…
  tasklet_schedule(&ms->tasklet);
  return IRQ_HANDLED;
}

int probe(…)
{
  struct my_struct *ms;
  int err;

  ms = devm_kzalloc(…);
…
  tasklet_init(&ms->tasklet, tasklet_handler, (unsigned long)ms);
…
  err = devm_request_irq(ms->irq, irq_handler, …, ms);
  if (err)
    return err;

  return 0;
}

int remove(…)
{
  struct my_struct *ms = …;
…
  tasklet_kill(&ms->tasklet);
}

Внимательный читатель сразу же воскликнет: «Так здесь же условия гонки, приводящие к бесконечному циклу!», и будет прав.

Давайте разберёмся почему. Tasklet'ы выполняются в контексте мягких прерываний (softirq), а следовательно существует вероятность задержки между планированием (tasklet_schedule()) и выполнением задачи. В это время может произойти как раз удаление драйвера из памяти, пользователь вызвал rmmod my_module. Конечно, мы явно зовём удаление tasklet'а, см. tasklet_kill(), но обработчик прерывания всё ещё активен, т.к. мы воспользовались devres API и запланировали его удаление в порядке очереди после выполнения ->remove()!

Как вылечить? Да очень просто, следите за руками:
int remove(…)
{
  struct my_struct *ms = …;
…
  devm_free_irq(ms->irq, ms);
  tasklet_kill(&ms->tasklet);
}

Как раз тот редкий случай, когда мы пользуемся devres API в момент удаления объекта.

Что прячет драйвер, к примеру, символьного устройства?


Рассмотрим теперь следующий псевдокод:
int closecb(…)
{
  struct my_struct *ms = …;

  do_something_on_close(ms, …);
}

struct file_ops fops = {
  .close = closecb,
 …
};

int probe(…)
{
  struct my_struct *ms;
  int err;

  ms = devm_kzalloc(…);
…
  err = register_char_device(ms, "node_served_by_driver", &fops, …);
  if (err)
   return err;

  return 0;
}

int remove(…)
{
  struct my_struct *ms = …;
…
}

Теперь представим себе такой сценарий:
  1. Убедимся, что драйвер загрузился и привязался к устройству.
  2. Откроем /dev/node_served_by_driver, и сделаем так, чтобы устроство оставалось открытым.
  3. Отвяжем драйвер от устройства, например, выполнив команду:
    echo our_device_name > /sys/bus/platform/drivers/our_driver_name/unbind
    
    или просто отключением устройства от шины, если возможно, например, отсоединением USB накопителя.
  4. Теперь закроем устройство.
  5. Наслаждаемся падением ядра.

Почему так происходит? Да потому что память, выделенная на этапе ->probe() высвобождается в момент отвязки устройства. А мы всё ещё используем этот участок памяти! При этом драйвер устройства не удаляется и не может быть удалён, т.к. держится программой, открывшей устройство, и остаётся в памяти до момента явного закрытия и удаления.

Как лечить? Тоже просто. Не пользоваться памятью, выделенной с помощью devm_kzalloc(), в файловых операциях в драйвере, аккуратно следить за временем жизни объектов. По мнению автора devres API префикс dev там не просто так, а с целью указать, что ресурсы имеют непосредственное отношения к железу, а не к обработке событий от пользователя.

P.S. На самом деле проблема более широкая, и она поднимается для обсуждения на будущем Kernel Summit 2015.

Удачи в отладке!

Комментарии (0)