Управляемые ресурсы в ядре Linux (также известны как Device Resource Management или devres API), о которых я писал небольшую заметку ранее, — вещь крайне полезная, но не стоит воспринимать этот вспомогательный набор функций как серебрянную пулю при написании драйверов или модификации существующих. Рассмотрим случаи, где нужно аккуратно применять данные методы.
Про прерывания и tasklet'ы доходчиво описано в статье в блоге компании EmBox, поэтому подразумевается, что читатель уже ознакомлен с этим или подобным материалом.
Возьмём в качестве примера следующий псевдокод:
Внимательный читатель сразу же воскликнет: «Так здесь же условия гонки, приводящие к бесконечному циклу!», и будет прав.
Давайте разберёмся почему. Tasklet'ы выполняются в контексте мягких прерываний (softirq), а следовательно существует вероятность задержки между планированием (
Как вылечить? Да очень просто, следите за руками:
Как раз тот редкий случай, когда мы пользуемся devres API в момент удаления объекта.
Рассмотрим теперь следующий псевдокод:
Теперь представим себе такой сценарий:
Почему так происходит? Да потому что память, выделенная на этапе
Как лечить? Тоже просто. Не пользоваться памятью, выделенной с помощью
P.S. На самом деле проблема более широкая, и она поднимается для обсуждения на будущем Kernel Summit 2015.
Удачи в отладке!
Регистрирование обработчика прерывания при наличии 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 = …;
…
}
Теперь представим себе такой сценарий:
- Убедимся, что драйвер загрузился и привязался к устройству.
- Откроем
/dev/node_served_by_driver
, и сделаем так, чтобы устроство оставалось открытым. - Отвяжем драйвер от устройства, например, выполнив команду:
или просто отключением устройства от шины, если возможно, например, отсоединением USB накопителя.echo our_device_name > /sys/bus/platform/drivers/our_driver_name/unbind
- Теперь закроем устройство.
- Наслаждаемся падением ядра.
Почему так происходит? Да потому что память, выделенная на этапе
->probe()
высвобождается в момент отвязки устройства. А мы всё ещё используем этот участок памяти! При этом драйвер устройства не удаляется и не может быть удалён, т.к. держится программой, открывшей устройство, и остаётся в памяти до момента явного закрытия и удаления. Как лечить? Тоже просто. Не пользоваться памятью, выделенной с помощью
devm_kzalloc()
, в файловых операциях в драйвере, аккуратно следить за временем жизни объектов. По мнению автора devres API префикс dev там не просто так, а с целью указать, что ресурсы имеют непосредственное отношения к железу, а не к обработке событий от пользователя.P.S. На самом деле проблема более широкая, и она поднимается для обсуждения на будущем Kernel Summit 2015.
Удачи в отладке!